Palette: seven roles (accent, good, caution added; highlight renamed alarm); no hard-coded colours left
/ crosscheck (push) Successful in 0s
CI / Analyze & Test (push) Successful in 13s
CI / Debug APK (push) Successful in 22s

background, foreground, subdued, accent (emphasis without danger), and the state
gradient good → caution → alarm; surface and onFill derived. smart-serow's
four-key themes still load (highlight = alarm; the new roles fall back).
Material's scheme follows: buttons in accent instead of the alarm colour.
Link state, mock banner, signal tiles, raw log, checklist button and marks,
camera page all take palette roles.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DDwfd6Ki8UfG4hK6W5YpJz
This commit is contained in:
2026-10-14 13:55:52 +09:00
parent 54ea278ca2
commit 3aeb09d61f
11 changed files with 165 additions and 72 deletions
+10 -4
View File
@@ -2,13 +2,19 @@
"dark": { "dark": {
"background": "#000000", "background": "#000000",
"foreground": "#FFFFFF", "foreground": "#FFFFFF",
"highlight": "#FF5555", "subdued": "#808080",
"subdued": "#808080" "accent": "#5EC8F2",
"good": "#6CCB5F",
"caution": "#FFC107",
"alarm": "#FF5555"
}, },
"bright": { "bright": {
"background": "#F0F0F0", "background": "#F0F0F0",
"foreground": "#1A1A1A", "foreground": "#1A1A1A",
"highlight": "#CC0000", "subdued": "#606060",
"subdued": "#606060" "accent": "#0277BD",
"good": "#2E7D32",
"caution": "#8D6E00",
"alarm": "#CC0000"
} }
} }
+10 -4
View File
@@ -2,13 +2,19 @@
"dark": { "dark": {
"background": "#303030", "background": "#303030",
"foreground": "#EAEAEA", "foreground": "#EAEAEA",
"highlight": "#FA1504", "subdued": "#fda052",
"subdued": "#fda052" "accent": "#5EC8F2",
"good": "#7CC46A",
"caution": "#FFD54F",
"alarm": "#FA1504"
}, },
"bright": { "bright": {
"background": "#fda052", "background": "#fda052",
"foreground": "#303030", "foreground": "#303030",
"highlight": "#df2100", "subdued": "#EAEAEA",
"subdued": "#EAEAEA" "accent": "#0D47A1",
"good": "#1B5E20",
"caution": "#5D4000",
"alarm": "#df2100"
} }
} }
+6 -3
View File
@@ -17,7 +17,7 @@ Laid out like smart-serow's HUD (`pi/ui/lib/`), which several pieces come from.
| Widget | File | Shows | From | | Widget | File | Shows | From |
|---|---|---|---| |---|---|---|---|
| `StatBox`, `StatBoxMain` | `widgets/stat_box.dart` | A value, its unit and label, sized from its box's height; highlight on warning, subdued when dim | smart-serow | | `StatBox`, `StatBoxMain` | `widgets/stat_box.dart` | A value, its unit and label, sized from its box's height; alarm on warning, subdued when dim | smart-serow |
| `Telltale` | `widgets/telltale.dart` | An on/off lamp: a label or an icon, lit or dark, "—" when missing | Yukart | | `Telltale` | `widgets/telltale.dart` | An on/off lamp: a label or an icon, lit or dark, "—" when missing | Yukart |
| `WhiskeyMark` | `widgets/whiskey_mark.dart` | Attitude: a horizon tilting with roll, ladder moving with pitch | smart-serow | | `WhiskeyMark` | `widgets/whiskey_mark.dart` | Attitude: a horizon tilting with roll, ladder moving with pitch | smart-serow |
| `AccelGraph` | `widgets/accel_graph.dart` | G-meter: a dot for lateral/longitudinal g, a trace and a peak marker over a window | smart-serow | | `AccelGraph` | `widgets/accel_graph.dart` | G-meter: a dot for lateral/longitudinal g, a trace and a peak marker over a window | smart-serow |
@@ -27,8 +27,11 @@ Laid out like smart-serow's HUD (`pi/ui/lib/`), which several pieces come from.
- **The boxed pattern.** A widget takes its share of a row and sizes its type from its own height, so a layout - **The boxed pattern.** A widget takes its share of a row and sizes its type from its own height, so a layout
is a grid of boxes and a value gaining a digit never moves anything. Labels shrink to fit, never wrap. is a grid of boxes and a value gaining a digit never moves anything. Labels shrink to fit, never wrap.
- **Colours by meaning.** `AppTheme.of(context)` gives the palette's `background`, `foreground`, `highlight` and - **Colours by meaning.** `AppTheme.of(context)` gives seven roles: `background`, `foreground`,
`subdued`; no widget names a colour. Themes are smart-serow's JSON (`assets/themes/`), `rei` by default. `subdued` (muted, missing, stale), `accent` (emphasis without danger: buttons, links, the current item),
and the state gradient `good` → `caution` → `alarm`; plus the derived `surface` (raised bars) and
`onFill(colour)` (text on a fill). No widget names a colour. Themes are JSON in `assets/themes/`, `rei` by
default; smart-serow's four-key files still load (their `highlight` is `alarm`).
- **Cosmetic fonts are opt-in.** A widget that wants the HUD face names `fontFamily: hudFont` in its own - **Cosmetic fonts are opt-in.** A widget that wants the HUD face names `fontFamily: hudFont` in its own
`TextStyle`; everything else keeps the basic font, which covers CJK where DIN 1451 does not. `TextStyle`; everything else keeps the basic font, which covers CJK where DIN 1451 does not.
- **Missing is a state** (app guideline 2): a widget given no value draws "—" or subdued, never an error. - **Missing is a state** (app guideline 2): a widget given no value draws "—" or subdued, never an error.
+4 -2
View File
@@ -10,6 +10,8 @@ import 'dart:typed_data';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'theme/palette.dart';
const camviewPort = 7710; const camviewPort = 7710;
class CamView extends ChangeNotifier { class CamView extends ChangeNotifier {
@@ -165,7 +167,7 @@ class _CamViewPageState extends State<CamViewPage> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
backgroundColor: Colors.black, backgroundColor: AppTheme.of(context).background,
appBar: AppBar(title: const Text('Camera (test)')), appBar: AppBar(title: const Text('Camera (test)')),
body: SafeArea( body: SafeArea(
child: ListenableBuilder( child: ListenableBuilder(
@@ -182,7 +184,7 @@ class _CamViewPageState extends State<CamViewPage> {
cam.available == false cam.available == false
? 'Camera unavailable on the Orin' ? 'Camera unavailable on the Orin'
: cam.status, : cam.status,
style: const TextStyle(color: Colors.white70), style: TextStyle(color: AppTheme.of(context).subdued),
), ),
) )
: Image.memory( : Image.memory(
+12 -8
View File
@@ -18,6 +18,7 @@ import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'boundary.dart'; import 'boundary.dart';
import 'theme/palette.dart';
const checklistAssets = 'assets/contracts/checklists/'; const checklistAssets = 'assets/contracts/checklists/';
@@ -309,14 +310,17 @@ class _ChecklistPageState extends State<ChecklistPage> {
Widget _panel(BuildContext context) { Widget _panel(BuildContext context) {
final s = run.current; final s = run.current;
final theme = Theme.of(context); final theme = Theme.of(context);
final p = AppTheme.of(context);
final a = run.currentAction; final a = run.currentAction;
// The big button by what pressing it does: start (good), stop a running span
// (caution: something is in progress), a point mark (accent), skip on (subdued).
final (Color color, IconData icon) = run.stepDone final (Color color, IconData icon) = run.stepDone
? (Colors.blueGrey, Icons.skip_next) ? (p.subdued, Icons.skip_next)
: a!.point : a!.point
? (Colors.blue, Icons.flag) ? (p.accent, Icons.flag)
: run.inSpan : run.inSpan
? (Colors.red.shade700, Icons.stop) ? (p.caution, Icons.stop)
: (Colors.green.shade700, Icons.play_arrow); : (p.good, Icons.play_arrow);
return Padding( return Padding(
padding: const EdgeInsets.all(16), padding: const EdgeInsets.all(16),
child: Column( child: Column(
@@ -339,7 +343,7 @@ class _ChecklistPageState extends State<ChecklistPage> {
child: FilledButton( child: FilledButton(
style: FilledButton.styleFrom( style: FilledButton.styleFrom(
backgroundColor: color, backgroundColor: color,
foregroundColor: Colors.white, foregroundColor: p.onFill(color),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(24)), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(24)),
), ),
onPressed: run.finished ? null : () => _send(run.press()), onPressed: run.finished ? null : () => _send(run.press()),
@@ -394,7 +398,7 @@ class _ChecklistPageState extends State<ChecklistPage> {
style: TextStyle( style: TextStyle(
fontSize: 32, fontSize: 32,
fontFeatures: const [FontFeature.tabularFigures()], fontFeatures: const [FontFeature.tabularFigures()],
color: reached ? Colors.lightGreenAccent : Colors.white, color: reached ? AppTheme.of(context).good : AppTheme.of(context).foreground,
), ),
); );
} }
@@ -409,9 +413,9 @@ class _ChecklistPageState extends State<ChecklistPage> {
final pending = b.pending.length; final pending = b.pending.length;
final ack = b.acks.where((x) => x.id == last.id).firstOrNull; final ack = b.acks.where((x) => x.id == last.id).firstOrNull;
final (String status, Color? color) = ack != null final (String status, Color? color) = ack != null
? (ack.ok ? ('✓', null) : ('✗ ${ack.detail}', Colors.redAccent)) ? (ack.ok ? ('✓', AppTheme.of(context).good) : ('✗ ${ack.detail}', AppTheme.of(context).alarm))
: b.pending.any((p) => p.id == last.id) : b.pending.any((p) => p.id == last.id)
? ('… unacked', Colors.orange) ? ('… unacked', AppTheme.of(context).caution)
: ('?', null); : ('?', null);
return Text( return Text(
'"${last.text}" $status${pending > 1 ? ' · $pending waiting' : ''}', '"${last.text}" $status${pending > 1 ? ' · $pending waiting' : ''}',
+4 -4
View File
@@ -49,13 +49,13 @@ class DashPage extends StatelessWidget {
children: [ children: [
if (b.mock) if (b.mock)
Container( Container(
color: p.highlight, color: p.caution,
padding: const EdgeInsets.symmetric(vertical: 4), padding: const EdgeInsets.symmetric(vertical: 4),
child: Text( child: Text(
'MOCK DATA — NOT FROM THE CAR', 'MOCK DATA — NOT FROM THE CAR',
textAlign: TextAlign.center, textAlign: TextAlign.center,
style: TextStyle( style: TextStyle(
color: p.background, color: p.onFill(p.caution),
fontWeight: FontWeight.w900, fontWeight: FontWeight.w900,
letterSpacing: 2, letterSpacing: 2,
), ),
@@ -186,8 +186,8 @@ class _DashBar extends StatelessWidget {
final p = AppTheme.of(context); final p = AppTheme.of(context);
final (text, color) = switch (boundary.state) { final (text, color) = switch (boundary.state) {
LinkState.up => ('LINK', p.subdued), LinkState.up => ('LINK', p.subdued),
LinkState.connecting => ('CONNECTING', p.highlight), LinkState.connecting => ('CONNECTING', p.caution),
LinkState.down => ('NO LINK', p.highlight), LinkState.down => ('NO LINK', p.alarm),
}; };
return SizedBox( return SizedBox(
height: 40, height: 40,
+30 -17
View File
@@ -169,14 +169,15 @@ class MockBanner extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final p = AppTheme.of(context);
return Container( return Container(
color: Colors.amber.shade700, color: p.caution,
padding: const EdgeInsets.symmetric(vertical: 6), padding: const EdgeInsets.symmetric(vertical: 6),
child: const Text( child: Text(
'MOCK DATA — NOT FROM THE CAR', 'MOCK DATA — NOT FROM THE CAR',
textAlign: TextAlign.center, textAlign: TextAlign.center,
style: TextStyle( style: TextStyle(
color: Colors.black, color: p.onFill(p.caution),
fontWeight: FontWeight.w900, fontWeight: FontWeight.w900,
fontSize: 20, fontSize: 20,
letterSpacing: 2, letterSpacing: 2,
@@ -200,10 +201,11 @@ class LinkBar extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final b = boundary; final b = boundary;
final p = AppTheme.of(context);
final (color, label) = switch (b.state) { final (color, label) = switch (b.state) {
LinkState.up => (Colors.green, 'CONNECTED'), LinkState.up => (p.good, 'CONNECTED'),
LinkState.connecting => (Colors.orange, 'CONNECTING'), LinkState.connecting => (p.caution, 'CONNECTING'),
LinkState.down => (Colors.red, 'DOWN'), LinkState.down => (p.alarm, 'DOWN'),
}; };
// Fixed-width numbers, padded with figure spaces (U+2007, digit-wide): the bar must // Fixed-width numbers, padded with figure spaces (U+2007, digit-wide): the bar must
// not reflow every time a value gains a digit. // not reflow every time a value gains a digit.
@@ -230,7 +232,10 @@ class LinkBar extends StatelessWidget {
crossAxisAlignment: WrapCrossAlignment.center, crossAxisAlignment: WrapCrossAlignment.center,
children: [ children: [
Chip( Chip(
label: Text(label), label: Text(
label,
style: TextStyle(color: color, fontWeight: FontWeight.w600),
),
backgroundColor: color.withValues(alpha: 0.3), backgroundColor: color.withValues(alpha: 0.3),
), ),
Text(b.url, style: const TextStyle(fontFamily: 'monospace')), Text(b.url, style: const TextStyle(fontFamily: 'monospace')),
@@ -317,7 +322,10 @@ Future<void> orinPowerOptions(BuildContext context, Boundary b) async {
label: const Text('Reboot'), label: const Text('Reboot'),
), ),
FilledButton.icon( FilledButton.icon(
style: FilledButton.styleFrom(backgroundColor: Colors.red.shade700), style: FilledButton.styleFrom(
backgroundColor: AppTheme.of(context).alarm,
foregroundColor: AppTheme.of(context).onFill(AppTheme.of(context).alarm),
),
onPressed: () => Navigator.of(context).pop('poweroff'), onPressed: () => Navigator.of(context).pop('poweroff'),
icon: const Icon(Icons.power_settings_new), icon: const Icon(Icons.power_settings_new),
label: const Text('Power off'), label: const Text('Power off'),
@@ -403,26 +411,27 @@ class SignalTile extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final p = AppTheme.of(context);
if (missing) { if (missing) {
return ListTile( return ListTile(
title: Text( title: Text(
name, name,
style: const TextStyle(fontFamily: 'monospace', color: Colors.grey), style: TextStyle(fontFamily: 'monospace', color: p.subdued),
), ),
subtitle: const Text( subtitle: Text(
'MISSING · not in the latest state', 'MISSING · not in the latest state',
style: TextStyle(color: Colors.redAccent), style: TextStyle(color: p.alarm),
), ),
trailing: Text.rich( trailing: Text.rich(
TextSpan( TextSpan(
children: [ children: [
const TextSpan( TextSpan(
text: '?', text: '?',
style: TextStyle(fontSize: 34, color: Colors.grey), style: TextStyle(fontSize: 34, color: p.subdued),
), ),
TextSpan( TextSpan(
text: ' ${envelope['unit'] ?? ''}', text: ' ${envelope['unit'] ?? ''}',
style: const TextStyle(fontSize: 16, color: Colors.grey), style: TextStyle(fontSize: 16, color: p.subdued),
), ),
], ],
), ),
@@ -439,12 +448,13 @@ class SignalTile extends StatelessWidget {
fontSize: 34, fontSize: 34,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
fontFeatures: const [FontFeature.tabularFigures()], fontFeatures: const [FontFeature.tabularFigures()],
color: stale || v == null ? Colors.grey : null, color: stale || v == null ? p.subdued : p.foreground,
fontStyle: stale ? FontStyle.italic : null, fontStyle: stale ? FontStyle.italic : null,
); );
return ListTile( return ListTile(
title: Text(name, style: const TextStyle(fontFamily: 'monospace')), title: Text(name, style: const TextStyle(fontFamily: 'monospace')),
subtitle: Text( subtitle: Text(
style: stale ? TextStyle(color: p.caution) : null,
[ [
if (stale) quality.toUpperCase(), if (stale) quality.toUpperCase(),
if (v == null) 'no value', if (v == null) 'no value',
@@ -479,11 +489,14 @@ class RawLog extends StatelessWidget {
if (b.pending.isNotEmpty || b.acks.isNotEmpty) ...[ if (b.pending.isNotEmpty || b.acks.isNotEmpty) ...[
Text('Commands', style: Theme.of(context).textTheme.titleSmall), Text('Commands', style: Theme.of(context).textTheme.titleSmall),
for (final p in b.pending) for (final p in b.pending)
Text('… ${p.cmd} ${p.args['text'] ?? ''} (unacked)'), Text(
'… ${p.cmd} ${p.args['text'] ?? ''} (unacked)',
style: TextStyle(color: AppTheme.of(context).caution),
),
for (final a in b.acks) for (final a in b.acks)
Text( Text(
'${a.ok ? '✓' : '✗'} ${a.detail}', '${a.ok ? '✓' : '✗'} ${a.detail}',
style: TextStyle(color: a.ok ? null : Colors.redAccent), style: TextStyle(color: a.ok ? AppTheme.of(context).good : AppTheme.of(context).alarm),
), ),
const Divider(), const Divider(),
], ],
+81 -23
View File
@@ -1,14 +1,22 @@
// The palette system, from smart-serow's HUD: four semantic colours, each with a dark // The palette system, from smart-serow's HUD, widened to seven roles: each a meaning,
// (low ambient light) and a bright (high ambient light) variant, read from a theme // each with a dark (low ambient light) and a bright (high ambient light) variant, read
// JSON in assets/themes/. Same file format as smart-serow's extra/themes/, so themes // from a theme JSON in assets/themes/. Widgets ask for meanings, never for colours:
// move between the two as they are. Widgets ask for meanings, never for colours:
// //
// final p = AppTheme.of(context); // final p = AppTheme.of(context);
// color: p.foreground // color: p.foreground
// //
// The roles: background, foreground, subdued (muted), accent (emphasis without danger:
// buttons, links, the current item), and the good → caution → alarm gradient for state.
// Two more are derived, never authored: surface (raised bars and panels) and onFill
// (text on a filled colour).
//
// smart-serow's four-key files load unchanged: their `highlight` is `alarm`, and the
// three newer roles fall back (accent and good to foreground, caution to subdued).
//
// Pick the theme at build time: `--dart-define=YUKART_THEME=default` (default: rei, smart-serow's). // Pick the theme at build time: `--dart-define=YUKART_THEME=default` (default: rei, smart-serow's).
import 'dart:convert'; import 'dart:convert';
import 'dart:math' show max, min;
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
@@ -24,45 +32,87 @@ class Palette {
const Palette({ const Palette({
required this.background, required this.background,
required this.foreground, required this.foreground,
required this.highlight,
required this.subdued, required this.subdued,
required this.accent,
required this.good,
required this.caution,
required this.alarm,
}); });
/// Behind everything. /// Behind everything.
final Color background; final Color background;
/// Values: what is read at a glance. /// Values and primary text: what is read at a glance.
final Color foreground; final Color foreground;
/// Warnings, and the one thing that must be seen. /// Labels, units, grids, secondary text, and anything missing or stale.
final Color highlight;
/// Labels, units, grids, and anything missing or stale.
final Color subdued; final Color subdued;
/// Emphasis without danger: buttons, links, the current item.
final Color accent;
/// Connected, done, healthy.
final Color good;
/// In progress, unconfirmed, mock data.
final Color caution;
/// Down, refused, destructive, over a limit: the one thing that must be seen.
final Color alarm;
/// Raised bars and panels: background with a little foreground mixed in.
Color get surface => Color.lerp(background, foreground, 0.08)!;
/// Text drawn on a filled [fill]: background or foreground, whichever contrasts more.
Color onFill(Color fill) {
double contrast(Color a, Color b) {
final (x, y) = (a.computeLuminance(), b.computeLuminance());
return (max(x, y) + 0.05) / (min(x, y) + 0.05);
}
return contrast(fill, background) >= contrast(fill, foreground)
? background
: foreground;
}
static Palette _fromJson(Map<String, dynamic> j) { static Palette _fromJson(Map<String, dynamic> j) {
Color c(String k) => Color? c(String k) => j[k] is String
Color(0xFF000000 | int.parse((j[k] as String).substring(1), radix: 16)); ? Color(
0xFF000000 | int.parse((j[k] as String).substring(1), radix: 16),
)
: null;
final foreground = c('foreground')!;
final subdued = c('subdued')!;
return Palette( return Palette(
background: c('background'), background: c('background')!,
foreground: c('foreground'), foreground: foreground,
highlight: c('highlight'), subdued: subdued,
subdued: c('subdued'), accent: c('accent') ?? foreground,
good: c('good') ?? foreground,
caution: c('caution') ?? subdued,
// smart-serow calls it highlight.
alarm: (c('alarm') ?? c('highlight'))!,
); );
} }
/// smart-serow's hard-coded last resort, used when no theme file loads. /// The last resort, used when no theme file loads (smart-serow's four, plus three).
static const fallbackDark = Palette( static const fallbackDark = Palette(
background: Color(0xFF000000), background: Color(0xFF000000),
foreground: Color(0xFFFFFFFF), foreground: Color(0xFFFFFFFF),
highlight: Color(0xFFFF5555),
subdued: Color(0xFF808080), subdued: Color(0xFF808080),
accent: Color(0xFF5EC8F2),
good: Color(0xFF6CCB5F),
caution: Color(0xFFFFC107),
alarm: Color(0xFFFF5555),
); );
static const fallbackBright = Palette( static const fallbackBright = Palette(
background: Color(0xFFF0F0F0), background: Color(0xFFF0F0F0),
foreground: Color(0xFF1A1A1A), foreground: Color(0xFF1A1A1A),
highlight: Color(0xFFCC0000),
subdued: Color(0xFF606060), subdued: Color(0xFF606060),
accent: Color(0xFF0277BD),
good: Color(0xFF2E7D32),
caution: Color(0xFF8D6E00),
alarm: Color(0xFFCC0000),
); );
} }
@@ -106,7 +156,8 @@ class Themes extends ChangeNotifier {
return Themes('fallback', Palette.fallbackDark, Palette.fallbackBright); return Themes('fallback', Palette.fallbackDark, Palette.fallbackBright);
} }
/// Material's theme for the rest of the app, from the same four colours. /// Material's theme for the rest of the app, from the same roles: buttons and
/// selection in accent, errors in alarm, raised surfaces in surface.
ThemeData material() { ThemeData material() {
final p = current; final p = current;
final brightness = _isDark ? Brightness.dark : Brightness.light; final brightness = _isDark ? Brightness.dark : Brightness.light;
@@ -116,13 +167,20 @@ class Themes extends ChangeNotifier {
scaffoldBackgroundColor: p.background, scaffoldBackgroundColor: p.background,
colorScheme: colorScheme:
ColorScheme.fromSeed( ColorScheme.fromSeed(
seedColor: p.highlight, seedColor: p.accent,
brightness: brightness, brightness: brightness,
).copyWith( ).copyWith(
surface: p.background, surface: p.background,
onSurface: p.foreground, onSurface: p.foreground,
primary: p.highlight, surfaceContainerHigh: p.surface,
error: p.highlight, surfaceContainerHighest: p.surface,
primary: p.accent,
onPrimary: p.onFill(p.accent),
primaryContainer: Color.lerp(p.background, p.accent, 0.25),
onPrimaryContainer: p.foreground,
secondary: p.accent,
error: p.alarm,
onError: p.onFill(p.alarm),
outline: p.subdued, outline: p.subdued,
onSurfaceVariant: p.subdued, onSurfaceVariant: p.subdued,
), ),
+2 -1
View File
@@ -50,7 +50,8 @@ class GpsCompass extends StatelessWidget {
final theme = AppTheme.of(context); final theme = AppTheme.of(context);
// No signal = subdued color, valid = foreground // No signal = subdued color, valid = foreground
final iconColour = _hasSignal ? theme.foreground : theme.highlight; // Heading: foreground; acquiring: caution; no receiver at all: alarm.
final iconColour = _hasSignal ? theme.foreground : (_isAcquiring ? theme.caution : theme.alarm);
// Convert to radians, 0 = no rotation when no signal // Convert to radians, 0 = no rotation when no signal
final angle = _hasSignal ? (heading! * math.pi / 180.0) : 0.0; final angle = _hasSignal ? (heading! * math.pi / 180.0) : 0.0;
+4 -4
View File
@@ -2,7 +2,7 @@
// every readout is a box that takes its share of a row (`flex`) and sizes its type // every readout is a box that takes its share of a row (`flex`) and sizes its type
// from its own height, so a layout is a grid of boxes, and a value changing digits // from its own height, so a layout is a grid of boxes, and a value changing digits
// never moves anything. Value in foreground with tabular figures, unit and label in // never moves anything. Value in foreground with tabular figures, unit and label in
// subdued; a warning turns the value to highlight. // subdued; a warning turns the value to alarm.
// //
// Added for Yukart: [dim]. A missing or stale value is a normal state in a car // Added for Yukart: [dim]. A missing or stale value is a normal state in a car
// (app guideline 2), shown in subdued rather than hidden. // (app guideline 2), shown in subdued rather than hidden.
@@ -28,7 +28,7 @@ class StatBox extends StatelessWidget {
final String label; final String label;
final int flex; final int flex;
/// If it returns true, the value shows in highlight. /// If it returns true, the value shows in alarm.
final bool Function()? isWarning; final bool Function()? isWarning;
/// Missing or stale: the value shows in subdued. /// Missing or stale: the value shows in subdued.
@@ -41,7 +41,7 @@ class StatBox extends StatelessWidget {
final valueColor = dim final valueColor = dim
? theme.subdued ? theme.subdued
: warning : warning
? theme.highlight ? theme.alarm
: theme.foreground; : theme.foreground;
return Expanded( return Expanded(
@@ -168,7 +168,7 @@ class StatBoxMain extends StatelessWidget {
color: dim color: dim
? theme.subdued ? theme.subdued
: warning : warning
? theme.highlight ? theme.alarm
: theme.foreground, : theme.foreground,
height: 1, height: 1,
), ),
+2 -2
View File
@@ -7,7 +7,7 @@ import '../reading.dart';
import '../theme/palette.dart'; import '../theme/palette.dart';
/// An on/off lamp in the boxed pattern: the label is the lamp. Lit in foreground /// An on/off lamp in the boxed pattern: the label is the lamp. Lit in foreground
/// (highlight when [warn]), dark in subdued; missing shows as a dash. /// (alarm when [warn]), dark in subdued; missing shows as a dash.
class Telltale extends StatelessWidget { class Telltale extends StatelessWidget {
const Telltale({ const Telltale({
super.key, super.key,
@@ -32,7 +32,7 @@ class Telltale extends StatelessWidget {
final missing = reading.value == null; final missing = reading.value == null;
final lit = reading.on && !reading.dim; final lit = reading.on && !reading.dim;
final color = lit final color = lit
? (warn ? p.highlight : p.foreground) ? (warn ? p.alarm : p.foreground)
: p.subdued.withValues(alpha: missing ? 0.35 : 0.6); : p.subdued.withValues(alpha: missing ? 0.35 : 0.6);
return Expanded( return Expanded(
flex: flex, flex: flex,