Skip to content

Commit a4fbef7

Browse files
eriedclaude
andcommitted
Watch: action buttons stay visible disabled, accent-aware battery and speed
The Wear Material Button's built-in `enabled=false` faded the whole button to ~12% alpha, hiding the icon entirely on dark backgrounds. Removed the enabled flag, kept the onClick guard, and gave the button its own muted disabledBg/disabledFg palette so the dimmed state is visible but clearly inactive — same pattern the phone dashboard uses for its grayed action tiles. Battery chips: - Default accent → tier coloring (green/amber/red) so the rider gets a glanceable read on remaining range, matching the phone dashboard. - Any other accent → all chips wear that accent for consistent identity with the phone. Speed glyph follows the same rule: tier color under default accent, the picked accent otherwise. Details rows and second-page speed mirror it (white values for default, accent-tinted for the rest). Phone dashboard: gauge safe-band always green now, matching the watch fix. Previously a custom accent painted the safe zone in the accent color, which read as one continuous danger band when the user picked red/pink. Speed indicator arc still wears the accent. Schema: added watchStem1Click/Hold and watchStem2Click/Hold for the stem-button mapping work next, DB v28 -> v29. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 26a6fdf commit a4fbef7

4 files changed

Lines changed: 81 additions & 29 deletions

File tree

app/src/main/java/com/eried/eucplanet/data/db/AppDatabase.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import com.eried.eucplanet.data.model.TripRecord
88

99
@Database(
1010
entities = [AppSettings::class, TripRecord::class, AlarmRule::class],
11-
version = 28,
11+
version = 29,
1212
exportSchema = false
1313
)
1414
abstract class AppDatabase : RoomDatabase() {

app/src/main/java/com/eried/eucplanet/data/model/AppSettings.kt

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -185,7 +185,25 @@ data class AppSettings(
185185
@ColumnInfo(defaultValue = "1")
186186
val watchShowSpeedUnit: Boolean = true,
187187
@ColumnInfo(defaultValue = "0")
188-
val watchEnableGpsSpeed: Boolean = false
188+
val watchEnableGpsSpeed: Boolean = false,
189+
190+
/**
191+
* Hardware-button bindings on the watch (Galaxy Watch Ultra exposes the
192+
* orange Action button as STEM_1 and the bottom side button as STEM_2;
193+
* Pixel Watch only has one). Stored as the [FlicAction] enum name so the
194+
* picker can reuse the same UI/string set as Flic and Volume keys. The
195+
* Wear OS side reads these via the Data Layer publish, intercepts
196+
* KEYCODE_STEM_* in MainActivity, and either fires a local control
197+
* intent or routes to the phone over /euc/control.
198+
*/
199+
@ColumnInfo(defaultValue = "HORN")
200+
val watchStem1Click: String = "HORN",
201+
@ColumnInfo(defaultValue = "NONE")
202+
val watchStem1Hold: String = "NONE",
203+
@ColumnInfo(defaultValue = "LIGHT_TOGGLE")
204+
val watchStem2Click: String = "LIGHT_TOGGLE",
205+
@ColumnInfo(defaultValue = "NONE")
206+
val watchStem2Hold: String = "NONE"
189207
)
190208

191209
enum class FlicAction(val labelRes: Int) {

app/src/main/java/com/eried/eucplanet/ui/dashboard/DashboardScreen.kt

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -400,7 +400,10 @@ fun DashboardScreen(
400400
showColorBand = showGaugeColorBand,
401401
orangeThresholdPct = gaugeOrangePct,
402402
redThresholdPct = gaugeRedPct,
403-
safeBandColor = if (useAccent) primary else AccentBlue,
403+
// Safe band is always green so it reads as the safety
404+
// signal and not the user's accent. The speed indicator
405+
// arc (overrideColor above) still wears the accent.
406+
safeBandColor = AccentGreen,
404407
modifier = Modifier
405408
.fillMaxWidth(0.75f)
406409
.aspectRatio(1.25f)

wear/src/main/java/com/eried/eucplanet/wear/ui/WatchApp.kt

Lines changed: 57 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -153,14 +153,17 @@ private fun MainScreen(state: WatchState, accent: Color) {
153153
)
154154
Spacer(Modifier.width(3.dp))
155155
}
156+
val useAccent = state.accentKey != "default"
156157
Text(
157158
text = "%.0f".format(WatchUnits.speed(state.speedKmh, state.imperialUnits)),
158159
fontSize = speedFontSp,
159160
fontWeight = FontWeight.Bold,
160-
// The gauge band already encodes the safety zones via
161-
// green/yellow/red. The speed glyph follows the user's
162-
// accent so the watch identity matches their phone.
163-
color = accent
161+
// Default accent → tier coloring (green/yellow/orange/red)
162+
// so the watch matches the phone dashboard's "rainbow"
163+
// mode. Any other accent → wear that accent so the
164+
// watch identity stays consistent with the phone's pick.
165+
color = if (useAccent) accent
166+
else speedTierColor(state.speedKmh, maxSpeed)
164167
)
165168
if (state.showSpeedUnit) {
166169
Spacer(Modifier.width(3.dp))
@@ -227,6 +230,8 @@ private fun MainScreen(state: WatchState, accent: Color) {
227230
phonePercent = state.phoneBatteryPercent.takeIf { state.showPhoneBattery },
228231
watchPercent = watchPercent.takeIf { state.showWatchBattery },
229232
showWheelChip = state.showWheelBattery,
233+
accent = accent,
234+
useAccentTint = state.accentKey != "default",
230235
fontSize = batteryFontSp,
231236
iconSize = batteryIconDp,
232237
spacing = batterySpacingDp
@@ -280,6 +285,8 @@ private fun BatteryRow(
280285
phonePercent: Int?,
281286
watchPercent: Int?,
282287
showWheelChip: Boolean,
288+
accent: Color,
289+
useAccentTint: Boolean,
283290
fontSize: TextUnit,
284291
iconSize: Dp,
285292
spacing: Dp
@@ -292,10 +299,10 @@ private fun BatteryRow(
292299
// an em-dash so the row layout stays put rather than collapsing the
293300
// moment a wheel drops out.
294301
if (showWheelChip) {
295-
BatteryChip(Icons.Filled.ElectricScooter, wheelPercent, fontSize, iconSize)
302+
BatteryChip(Icons.Filled.ElectricScooter, wheelPercent, accent, useAccentTint, fontSize, iconSize)
296303
}
297-
phonePercent?.let { BatteryChip(Icons.Filled.PhoneAndroid, it, fontSize, iconSize) }
298-
watchPercent?.let { BatteryChip(Icons.Filled.Watch, it, fontSize, iconSize) }
304+
phonePercent?.let { BatteryChip(Icons.Filled.PhoneAndroid, it, accent, useAccentTint, fontSize, iconSize) }
305+
watchPercent?.let { BatteryChip(Icons.Filled.Watch, it, accent, useAccentTint, fontSize, iconSize) }
299306
}
300307
}
301308

@@ -307,8 +314,22 @@ private fun pwmTierColor(percent: Float): Color = when {
307314
}
308315

309316
@Composable
310-
private fun BatteryChip(icon: ImageVector, percent: Int?, fontSize: TextUnit, iconSize: Dp) {
311-
val tint = if (percent != null) batteryTint(percent) else Color(0xFF606060)
317+
private fun BatteryChip(
318+
icon: ImageVector,
319+
percent: Int?,
320+
accent: Color,
321+
useAccentTint: Boolean,
322+
fontSize: TextUnit,
323+
iconSize: Dp
324+
) {
325+
// Default accent → tier coloring (green / amber / red) so the rider
326+
// gets a glanceable sense of remaining range. Any other accent →
327+
// the chip wears the accent so the watch identity stays consistent.
328+
val tint = when {
329+
percent == null -> Color(0xFF606060)
330+
useAccentTint -> accent
331+
else -> batteryTint(percent)
332+
}
312333
Row(verticalAlignment = Alignment.CenterVertically) {
313334
Icon(
314335
imageVector = icon,
@@ -336,47 +357,49 @@ private fun batteryTint(percent: Int): Color = when {
336357
@Composable
337358
private fun ActionRow(state: WatchState, accent: Color, buttonSize: Dp, iconSize: Dp) {
338359
val context = LocalContext.current
339-
val enabled = state.connected
340-
// Use the same dim grey as the phone dashboard's grayed action tiles so
341-
// disconnected reads identically across surfaces.
360+
val live = state.connected
361+
// Custom dim palette for the disconnected state — the Wear Button's own
362+
// `enabled = false` styling fades the whole button to ~12% alpha which
363+
// makes the icon nearly invisible. Keeping the button "enabled" with our
364+
// own muted colors and guarding the onClick keeps it visually present
365+
// while still being non-interactive.
342366
val disabledBg = Color(0xFF1A1A1A)
343367
val disabledFg = Color(0xFF555555)
344368
Row(horizontalArrangement = Arrangement.spacedBy(10.dp)) {
345369
if (state.hasHorn) {
346370
Button(
347371
onClick = {
348-
if (enabled) WatchStateRepository.sendControl(context, WatchControl.HORN)
372+
if (live) WatchStateRepository.sendControl(context, WatchControl.HORN)
349373
},
350-
enabled = enabled,
351374
colors = ButtonDefaults.primaryButtonColors(
352-
backgroundColor = if (enabled) accent else disabledBg,
353-
contentColor = if (enabled) Color.Black else disabledFg
375+
backgroundColor = if (live) accent else disabledBg,
376+
contentColor = if (live) Color.Black else disabledFg
354377
),
355378
modifier = Modifier.size(buttonSize)
356379
) {
357380
Icon(
358381
imageVector = Icons.Filled.Campaign,
359382
contentDescription = stringResource(R.string.watch_horn),
383+
tint = if (live) Color.Black else disabledFg,
360384
modifier = Modifier.size(iconSize)
361385
)
362386
}
363387
}
364388
if (state.hasLight) {
365389
Button(
366390
onClick = {
367-
if (!enabled) return@Button
391+
if (!live) return@Button
368392
val intent = if (state.lightOn) WatchControl.LIGHT_OFF else WatchControl.LIGHT_ON
369393
WatchStateRepository.sendControl(context, intent)
370394
},
371-
enabled = enabled,
372395
colors = ButtonDefaults.secondaryButtonColors(
373396
backgroundColor = when {
374-
!enabled -> disabledBg
397+
!live -> disabledBg
375398
state.lightOn -> accent.copy(alpha = 0.30f)
376399
else -> Color(0xFF2A2A2A)
377400
},
378401
contentColor = when {
379-
!enabled -> disabledFg
402+
!live -> disabledFg
380403
state.lightOn -> accent
381404
else -> Color(0xFFB0B0B0)
382405
}
@@ -387,7 +410,7 @@ private fun ActionRow(state: WatchState, accent: Color, buttonSize: Dp, iconSize
387410
imageVector = Icons.Filled.FlashlightOn,
388411
contentDescription = stringResource(R.string.watch_light),
389412
tint = when {
390-
!enabled -> disabledFg
413+
!live -> disabledFg
391414
state.lightOn -> Color(0xFFFFC107)
392415
else -> Color(0xFF606060)
393416
},
@@ -448,18 +471,26 @@ private fun DetailsScreen(state: WatchState, accent: Color) {
448471
fontWeight = FontWeight.SemiBold
449472
)
450473
}
474+
// Same accent rule as the dial: default accent → white speed
475+
// so the row aligns with the rest of the value column; any
476+
// custom accent → wear that accent.
477+
val useAccent = state.accentKey != "default"
451478
Text(
452479
text = "%.0f %s".format(speedDisplay, speedUnit),
453480
fontSize = speedSp,
454481
fontWeight = FontWeight.Bold,
455-
color = accent
482+
color = if (useAccent) accent else Color.White
456483
)
457484
Spacer(Modifier.height(4.dp))
458485
val live = state.connected
459-
// Accent-tint live values so the page reads as a continuation of
460-
// the dashboard's accent identity. Disconnected dashes stay grey
461-
// so the rider can tell at a glance that the row isn't live.
462-
val valueColor = if (live) accent else Color(0xFF606060)
486+
// Live values: white when default accent (matches phone dashboard's
487+
// value column) or the accent color when the user picked one.
488+
// Disconnected dashes stay muted grey.
489+
val valueColor = when {
490+
!live -> Color(0xFF606060)
491+
useAccent -> accent
492+
else -> Color.White
493+
}
463494
DetailRow(R.string.watch_voltage_label, if (live) "%.1f V".format(state.voltage) else DASH, labelSp, valueSp, labelWidth, valueWidth, valueColor)
464495
DetailRow(R.string.watch_current_label, if (live) "%.1f A".format(state.current) else DASH, labelSp, valueSp, labelWidth, valueWidth, valueColor)
465496
DetailRow(R.string.watch_power_label, if (live) "%.0f W".format(powerW) else DASH, labelSp, valueSp, labelWidth, valueWidth, valueColor)

0 commit comments

Comments
 (0)