Skip to content

Commit db20141

Browse files
sibblobviyus
andauthored
feat(android): add dark theme (#46249)
* Android: add mobile dark theme * Android: fix remaining dark mode card surfaces * Android: address dark mode review comments * fix(android): theme onboarding flow * fix: add Android dark theme coverage (#46249) (thanks @sibbl) --------- Co-authored-by: Ayaan Zaidi <hi@obviy.us>
1 parent 29fec8b commit db20141

14 files changed

Lines changed: 337 additions & 255 deletions

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ Docs: https://docs.openclaw.ai
1111
- Feishu/streaming: add `onReasoningStream` and `onReasoningEnd` support to streaming cards, so `/reasoning stream` renders thinking tokens as markdown blockquotes in the same card — matching the Telegram channel's reasoning lane behavior.
1212
- Feishu/cards: add identity-aware structured card headers and note footers for Feishu replies and direct sends, while keeping that presentation wired through the shared outbound identity path. (#29938) Thanks @nszhsl.
1313
- Gateway/health monitor: add configurable stale-event thresholds and restart limits, plus per-channel and per-account `healthMonitor.enabled` overrides, while keeping the existing global disable path on `gateway.channelHealthCheckMinutes=0`. (#42107) Thanks @rstar327.
14+
- Android/mobile: add a system-aware dark theme across onboarding and post-onboarding screens so the app follows the device theme through setup, chat, and voice flows. (#46249) Thanks @sibbl.
1415

1516
### Fixes
1617

apps/android/app/src/main/java/ai/openclaw/app/ui/ConnectTabScreen.kt

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ import androidx.compose.ui.text.font.FontWeight
5151
import androidx.compose.ui.text.input.KeyboardType
5252
import androidx.compose.ui.unit.dp
5353
import ai.openclaw.app.MainViewModel
54+
import ai.openclaw.app.ui.mobileCardSurface
5455

5556
private enum class ConnectInputMode {
5657
SetupCode,
@@ -144,7 +145,7 @@ fun ConnectTabScreen(viewModel: MainViewModel) {
144145
Surface(
145146
modifier = Modifier.fillMaxWidth(),
146147
shape = RoundedCornerShape(14.dp),
147-
color = Color.White,
148+
color = mobileCardSurface,
148149
border = BorderStroke(1.dp, mobileBorder),
149150
) {
150151
Column {
@@ -205,7 +206,7 @@ fun ConnectTabScreen(viewModel: MainViewModel) {
205206
shape = RoundedCornerShape(14.dp),
206207
colors =
207208
ButtonDefaults.buttonColors(
208-
containerColor = Color.White,
209+
containerColor = mobileCardSurface,
209210
contentColor = mobileDanger,
210211
),
211212
border = BorderStroke(1.dp, mobileDanger.copy(alpha = 0.4f)),
@@ -298,7 +299,7 @@ fun ConnectTabScreen(viewModel: MainViewModel) {
298299
Surface(
299300
modifier = Modifier.fillMaxWidth(),
300301
shape = RoundedCornerShape(14.dp),
301-
color = Color.White,
302+
color = mobileCardSurface,
302303
border = BorderStroke(1.dp, mobileBorder),
303304
) {
304305
Column(
@@ -480,7 +481,7 @@ private fun MethodChip(label: String, active: Boolean, onClick: () -> Unit) {
480481
containerColor = if (active) mobileAccent else mobileSurface,
481482
contentColor = if (active) Color.White else mobileText,
482483
),
483-
border = BorderStroke(1.dp, if (active) Color(0xFF184DAF) else mobileBorderStrong),
484+
border = BorderStroke(1.dp, if (active) mobileAccentBorderStrong else mobileBorderStrong),
484485
) {
485486
Text(label, style = mobileCaption1.copy(fontWeight = FontWeight.Bold))
486487
}
@@ -509,10 +510,10 @@ private fun CommandBlock(command: String) {
509510
modifier = Modifier.fillMaxWidth(),
510511
shape = RoundedCornerShape(12.dp),
511512
color = mobileCodeBg,
512-
border = BorderStroke(1.dp, Color(0xFF2B2E35)),
513+
border = BorderStroke(1.dp, mobileCodeBorder),
513514
) {
514515
Row(modifier = Modifier.fillMaxWidth(), verticalAlignment = Alignment.CenterVertically) {
515-
Box(modifier = Modifier.width(3.dp).height(42.dp).background(Color(0xFF3FC97A)))
516+
Box(modifier = Modifier.width(3.dp).height(42.dp).background(mobileCodeAccent))
516517
Text(
517518
text = command,
518519
modifier = Modifier.padding(horizontal = 12.dp, vertical = 10.dp),

apps/android/app/src/main/java/ai/openclaw/app/ui/MobileUiTokens.kt

Lines changed: 150 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
package ai.openclaw.app.ui
22

3+
import androidx.compose.runtime.Composable
4+
import androidx.compose.runtime.staticCompositionLocalOf
35
import androidx.compose.ui.graphics.Brush
46
import androidx.compose.ui.graphics.Color
57
import androidx.compose.ui.text.TextStyle
@@ -9,32 +11,147 @@ import androidx.compose.ui.text.font.FontWeight
911
import androidx.compose.ui.unit.sp
1012
import ai.openclaw.app.R
1113

12-
internal val mobileBackgroundGradient =
13-
Brush.verticalGradient(
14-
listOf(
15-
Color(0xFFFFFFFF),
16-
Color(0xFFF7F8FA),
17-
Color(0xFFEFF1F5),
18-
),
14+
// ---------------------------------------------------------------------------
15+
// MobileColors – semantic color tokens with light + dark variants
16+
// ---------------------------------------------------------------------------
17+
18+
internal data class MobileColors(
19+
val surface: Color,
20+
val surfaceStrong: Color,
21+
val cardSurface: Color,
22+
val border: Color,
23+
val borderStrong: Color,
24+
val text: Color,
25+
val textSecondary: Color,
26+
val textTertiary: Color,
27+
val accent: Color,
28+
val accentSoft: Color,
29+
val accentBorderStrong: Color,
30+
val success: Color,
31+
val successSoft: Color,
32+
val warning: Color,
33+
val warningSoft: Color,
34+
val danger: Color,
35+
val dangerSoft: Color,
36+
val codeBg: Color,
37+
val codeText: Color,
38+
val codeBorder: Color,
39+
val codeAccent: Color,
40+
val chipBorderConnected: Color,
41+
val chipBorderConnecting: Color,
42+
val chipBorderWarning: Color,
43+
val chipBorderError: Color,
44+
)
45+
46+
internal fun lightMobileColors() =
47+
MobileColors(
48+
surface = Color(0xFFF6F7FA),
49+
surfaceStrong = Color(0xFFECEEF3),
50+
cardSurface = Color(0xFFFFFFFF),
51+
border = Color(0xFFE5E7EC),
52+
borderStrong = Color(0xFFD6DAE2),
53+
text = Color(0xFF17181C),
54+
textSecondary = Color(0xFF5D6472),
55+
textTertiary = Color(0xFF99A0AE),
56+
accent = Color(0xFF1D5DD8),
57+
accentSoft = Color(0xFFECF3FF),
58+
accentBorderStrong = Color(0xFF184DAF),
59+
success = Color(0xFF2F8C5A),
60+
successSoft = Color(0xFFEEF9F3),
61+
warning = Color(0xFFC8841A),
62+
warningSoft = Color(0xFFFFF8EC),
63+
danger = Color(0xFFD04B4B),
64+
dangerSoft = Color(0xFFFFF2F2),
65+
codeBg = Color(0xFF15171B),
66+
codeText = Color(0xFFE8EAEE),
67+
codeBorder = Color(0xFF2B2E35),
68+
codeAccent = Color(0xFF3FC97A),
69+
chipBorderConnected = Color(0xFFCFEBD8),
70+
chipBorderConnecting = Color(0xFFD5E2FA),
71+
chipBorderWarning = Color(0xFFEED8B8),
72+
chipBorderError = Color(0xFFF3C8C8),
73+
)
74+
75+
internal fun darkMobileColors() =
76+
MobileColors(
77+
surface = Color(0xFF1A1C20),
78+
surfaceStrong = Color(0xFF24262B),
79+
cardSurface = Color(0xFF1E2024),
80+
border = Color(0xFF2E3038),
81+
borderStrong = Color(0xFF3A3D46),
82+
text = Color(0xFFE4E5EA),
83+
textSecondary = Color(0xFFA0A6B4),
84+
textTertiary = Color(0xFF6B7280),
85+
accent = Color(0xFF6EA8FF),
86+
accentSoft = Color(0xFF1A2A44),
87+
accentBorderStrong = Color(0xFF5B93E8),
88+
success = Color(0xFF5FBB85),
89+
successSoft = Color(0xFF152E22),
90+
warning = Color(0xFFE8A844),
91+
warningSoft = Color(0xFF2E2212),
92+
danger = Color(0xFFE87070),
93+
dangerSoft = Color(0xFF2E1616),
94+
codeBg = Color(0xFF111317),
95+
codeText = Color(0xFFE8EAEE),
96+
codeBorder = Color(0xFF2B2E35),
97+
codeAccent = Color(0xFF3FC97A),
98+
chipBorderConnected = Color(0xFF1E4A30),
99+
chipBorderConnecting = Color(0xFF1E3358),
100+
chipBorderWarning = Color(0xFF3E3018),
101+
chipBorderError = Color(0xFF3E1E1E),
19102
)
20103

21-
internal val mobileSurface = Color(0xFFF6F7FA)
22-
internal val mobileSurfaceStrong = Color(0xFFECEEF3)
23-
internal val mobileBorder = Color(0xFFE5E7EC)
24-
internal val mobileBorderStrong = Color(0xFFD6DAE2)
25-
internal val mobileText = Color(0xFF17181C)
26-
internal val mobileTextSecondary = Color(0xFF5D6472)
27-
internal val mobileTextTertiary = Color(0xFF99A0AE)
28-
internal val mobileAccent = Color(0xFF1D5DD8)
29-
internal val mobileAccentSoft = Color(0xFFECF3FF)
30-
internal val mobileSuccess = Color(0xFF2F8C5A)
31-
internal val mobileSuccessSoft = Color(0xFFEEF9F3)
32-
internal val mobileWarning = Color(0xFFC8841A)
33-
internal val mobileWarningSoft = Color(0xFFFFF8EC)
34-
internal val mobileDanger = Color(0xFFD04B4B)
35-
internal val mobileDangerSoft = Color(0xFFFFF2F2)
36-
internal val mobileCodeBg = Color(0xFF15171B)
37-
internal val mobileCodeText = Color(0xFFE8EAEE)
104+
internal val LocalMobileColors = staticCompositionLocalOf { lightMobileColors() }
105+
106+
internal object MobileColorsAccessor {
107+
val current: MobileColors
108+
@Composable get() = LocalMobileColors.current
109+
}
110+
111+
// ---------------------------------------------------------------------------
112+
// Backward-compatible top-level accessors (composable getters)
113+
// ---------------------------------------------------------------------------
114+
// These allow existing call sites to keep using `mobileSurface`, `mobileText`, etc.
115+
// without converting every file at once. Each resolves to the themed value.
116+
117+
internal val mobileSurface: Color @Composable get() = LocalMobileColors.current.surface
118+
internal val mobileSurfaceStrong: Color @Composable get() = LocalMobileColors.current.surfaceStrong
119+
internal val mobileCardSurface: Color @Composable get() = LocalMobileColors.current.cardSurface
120+
internal val mobileBorder: Color @Composable get() = LocalMobileColors.current.border
121+
internal val mobileBorderStrong: Color @Composable get() = LocalMobileColors.current.borderStrong
122+
internal val mobileText: Color @Composable get() = LocalMobileColors.current.text
123+
internal val mobileTextSecondary: Color @Composable get() = LocalMobileColors.current.textSecondary
124+
internal val mobileTextTertiary: Color @Composable get() = LocalMobileColors.current.textTertiary
125+
internal val mobileAccent: Color @Composable get() = LocalMobileColors.current.accent
126+
internal val mobileAccentSoft: Color @Composable get() = LocalMobileColors.current.accentSoft
127+
internal val mobileAccentBorderStrong: Color @Composable get() = LocalMobileColors.current.accentBorderStrong
128+
internal val mobileSuccess: Color @Composable get() = LocalMobileColors.current.success
129+
internal val mobileSuccessSoft: Color @Composable get() = LocalMobileColors.current.successSoft
130+
internal val mobileWarning: Color @Composable get() = LocalMobileColors.current.warning
131+
internal val mobileWarningSoft: Color @Composable get() = LocalMobileColors.current.warningSoft
132+
internal val mobileDanger: Color @Composable get() = LocalMobileColors.current.danger
133+
internal val mobileDangerSoft: Color @Composable get() = LocalMobileColors.current.dangerSoft
134+
internal val mobileCodeBg: Color @Composable get() = LocalMobileColors.current.codeBg
135+
internal val mobileCodeText: Color @Composable get() = LocalMobileColors.current.codeText
136+
internal val mobileCodeBorder: Color @Composable get() = LocalMobileColors.current.codeBorder
137+
internal val mobileCodeAccent: Color @Composable get() = LocalMobileColors.current.codeAccent
138+
139+
// Background gradient – light fades white→gray, dark fades near-black→dark-gray
140+
internal val mobileBackgroundGradient: Brush
141+
@Composable get() {
142+
val colors = LocalMobileColors.current
143+
return Brush.verticalGradient(
144+
listOf(
145+
colors.surface,
146+
colors.surfaceStrong,
147+
colors.surfaceStrong,
148+
),
149+
)
150+
}
151+
152+
// ---------------------------------------------------------------------------
153+
// Typography tokens (theme-independent)
154+
// ---------------------------------------------------------------------------
38155

39156
internal val mobileFontFamily =
40157
FontFamily(
@@ -44,6 +161,15 @@ internal val mobileFontFamily =
44161
Font(resId = R.font.manrope_700_bold, weight = FontWeight.Bold),
45162
)
46163

164+
internal val mobileDisplay =
165+
TextStyle(
166+
fontFamily = mobileFontFamily,
167+
fontWeight = FontWeight.Bold,
168+
fontSize = 34.sp,
169+
lineHeight = 40.sp,
170+
letterSpacing = (-0.8).sp,
171+
)
172+
47173
internal val mobileTitle1 =
48174
TextStyle(
49175
fontFamily = mobileFontFamily,

0 commit comments

Comments
 (0)