From 283a985fb6d1e4e7646c82fe7fc770612454ea4f Mon Sep 17 00:00:00 2001 From: "paseo-bot[bot]" <266920839+paseo-bot[bot]@users.noreply.github.com> Date: Mon, 12 Oct 2026 14:04:41 +0200 Subject: [PATCH] fix(app): keep Android status icons readable as themes change (#6707) * fix(app): sync Android system bar icon style with app theme With edge-to-edge enabled the app draws under the Android system bars, but the bars keep their default light icon style regardless of theme, so the status bar clock/battery/icons are invisible on light themes (white on white). Call SystemBars.setStyle from the appearance provider whenever the theme is applied: dark icons on light themes, light icons on dark themes, auto following the system for adaptive themes and plugin-contributed themes. Refs #1272 (runtime only; the splash screen phase is not covered). * fix(app): keep Android status icons readable as themes change Keep system-bar contrast subscribed to the resolved app theme so automatic appearance changes update the icons as well as the app surface. Add a native regression covering explicit and automatic themes. Co-authored-by: TeraYang <4110825+terayang@users.noreply.github.com> * fix(app): map system-bar appearance through color scheme Unistyles reserves style mappings for React Native style objects. Keep SystemBars style selection inside its appearance adapter and map the colorScheme prop instead. --------- Co-authored-by: TeraYang <4110825+terayang@users.noreply.github.com> Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com> --- .../app/e2e/mobile/system-bars/android.mjs | 62 +++++++++++++++++++ packages/app/src/appearance/provider.tsx | 6 +- .../src/appearance/system-bars.android.tsx | 11 ++++ packages/app/src/appearance/system-bars.tsx | 3 + 4 files changed, 81 insertions(+), 1 deletion(-) create mode 100644 packages/app/e2e/mobile/system-bars/android.mjs create mode 100644 packages/app/src/appearance/system-bars.android.tsx create mode 100644 packages/app/src/appearance/system-bars.tsx diff --git a/packages/app/e2e/mobile/system-bars/android.mjs b/packages/app/e2e/mobile/system-bars/android.mjs new file mode 100644 index 0000000000..8201f3dc12 --- /dev/null +++ b/packages/app/e2e/mobile/system-bars/android.mjs @@ -0,0 +1,62 @@ +// Open Settings > Appearance in a native Android app loaded from this checkout. +// AGENT_DEVICE_STATE_DIR= node packages/app/e2e/mobile/system-bars/android.mjs +// Requires English UI and a dedicated emulator. Leaves System theme and light OS appearance. +import assert from "node:assert/strict"; +import { execFileSync } from "node:child_process"; +import { setTimeout as delay } from "node:timers/promises"; + +const [, , session, serial, appId] = process.argv; +assert(session && serial && appId, "Pass the agent-device session, adb serial, and app ID"); + +function ad(...args) { + return execFileSync("agent-device", [...args, "--session", session], { + encoding: "utf8", + timeout: 60_000, + }); +} + +function windowState() { + const state = execFileSync("adb", ["-s", serial, "shell", "dumpsys", "window"], { + encoding: "utf8", + timeout: 10_000, + }); + assert(state.match(/mCurrentFocus=.*\n/)?.[0].includes(`${appId}/`), "App must be focused"); + const regions = state.match(/mLastStatusBarAppearanceRegions=\s*\n([^\n]+)/)?.[1]; + assert(regions, "Android must report the status-bar appearance region"); + return regions.includes("LIGHT_STATUS_BARS"); +} + +async function expectDarkIcons(expected, context) { + const deadline = Date.now() + 5_000; + do { + if (windowState() === expected) { + console.log(`PASS: ${context}`); + return; + } + await delay(100); + } while (Date.now() < deadline); + assert.equal(windowState(), expected, context); +} + +function selectTheme(theme) { + const current = ad("snapshot", "-i").match(/"Theme: ([^"]+)"/)?.[1]; + assert(current, "Start on Settings > Appearance"); + ad("press", `label="Theme: ${current}"`, "--settle"); + ad("press", `label="${theme}"`, "--settle"); + ad("wait", "text", `Theme: ${theme}`, "5000"); +} + +ad("settings", "appearance", "light"); +selectTheme("Light"); +await expectDarkIcons(true, "Light app theme uses dark status icons"); +selectTheme("Dark"); +await expectDarkIcons(false, "Dark app theme uses light status icons"); +selectTheme("System"); +await expectDarkIcons(true, "System theme starts with dark icons on light OS appearance"); +ad("settings", "appearance", "dark"); +await expectDarkIcons( + false, + "System theme follows an OS change to dark without reopening settings", +); +ad("settings", "appearance", "light"); +await expectDarkIcons(true, "System theme follows an OS change back to light"); diff --git a/packages/app/src/appearance/provider.tsx b/packages/app/src/appearance/provider.tsx index d52864e435..77addad9a3 100644 --- a/packages/app/src/appearance/provider.tsx +++ b/packages/app/src/appearance/provider.tsx @@ -21,6 +21,7 @@ import { } from "@/plugins/themes"; import { PLUGIN_THEME_NAMES, PLUGIN_THEME_PREFERENCE, THEME_TO_UNISTYLES } from "@/styles/theme"; import { applyAppearance } from "./apply"; +import { AppearanceSystemBars } from "./system-bars"; interface ContributedThemes { options: PluginThemeOption[]; @@ -107,7 +108,10 @@ export function AppearanceProvider({ children }: { children: ReactNode }) { if (!hasAppliedAppearance) return null; return ( - {children} + + + {children} + ); } diff --git a/packages/app/src/appearance/system-bars.android.tsx b/packages/app/src/appearance/system-bars.android.tsx new file mode 100644 index 0000000000..f36fb60599 --- /dev/null +++ b/packages/app/src/appearance/system-bars.android.tsx @@ -0,0 +1,11 @@ +import { SystemBars } from "react-native-edge-to-edge"; +import { withUnistyles } from "react-native-unistyles"; +import type { Theme } from "@/styles/theme"; + +function ThemeSystemBars({ colorScheme }: Pick) { + return ; +} + +export const AppearanceSystemBars = withUnistyles(ThemeSystemBars, (theme) => ({ + colorScheme: theme.colorScheme, +})); diff --git a/packages/app/src/appearance/system-bars.tsx b/packages/app/src/appearance/system-bars.tsx new file mode 100644 index 0000000000..930579bc4a --- /dev/null +++ b/packages/app/src/appearance/system-bars.tsx @@ -0,0 +1,3 @@ +export function AppearanceSystemBars() { + return null; +}