Palette: seven roles (accent, good, caution added; highlight renamed alarm); no hard-coded colours left
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:
+6
-3
@@ -17,7 +17,7 @@ Laid out like smart-serow's HUD (`pi/ui/lib/`), which several pieces come 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 |
|
||||
| `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 |
|
||||
@@ -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
|
||||
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
|
||||
`subdued`; no widget names a colour. Themes are smart-serow's JSON (`assets/themes/`), `rei` by default.
|
||||
- **Colours by meaning.** `AppTheme.of(context)` gives seven roles: `background`, `foreground`,
|
||||
`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
|
||||
`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.
|
||||
|
||||
+4
-2
@@ -10,6 +10,8 @@ import 'dart:typed_data';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'theme/palette.dart';
|
||||
|
||||
const camviewPort = 7710;
|
||||
|
||||
class CamView extends ChangeNotifier {
|
||||
@@ -165,7 +167,7 @@ class _CamViewPageState extends State<CamViewPage> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
backgroundColor: Colors.black,
|
||||
backgroundColor: AppTheme.of(context).background,
|
||||
appBar: AppBar(title: const Text('Camera (test)')),
|
||||
body: SafeArea(
|
||||
child: ListenableBuilder(
|
||||
@@ -182,7 +184,7 @@ class _CamViewPageState extends State<CamViewPage> {
|
||||
cam.available == false
|
||||
? 'Camera unavailable on the Orin'
|
||||
: cam.status,
|
||||
style: const TextStyle(color: Colors.white70),
|
||||
style: TextStyle(color: AppTheme.of(context).subdued),
|
||||
),
|
||||
)
|
||||
: Image.memory(
|
||||
|
||||
+12
-8
@@ -18,6 +18,7 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
import 'boundary.dart';
|
||||
import 'theme/palette.dart';
|
||||
|
||||
const checklistAssets = 'assets/contracts/checklists/';
|
||||
|
||||
@@ -309,14 +310,17 @@ class _ChecklistPageState extends State<ChecklistPage> {
|
||||
Widget _panel(BuildContext context) {
|
||||
final s = run.current;
|
||||
final theme = Theme.of(context);
|
||||
final p = AppTheme.of(context);
|
||||
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
|
||||
? (Colors.blueGrey, Icons.skip_next)
|
||||
? (p.subdued, Icons.skip_next)
|
||||
: a!.point
|
||||
? (Colors.blue, Icons.flag)
|
||||
? (p.accent, Icons.flag)
|
||||
: run.inSpan
|
||||
? (Colors.red.shade700, Icons.stop)
|
||||
: (Colors.green.shade700, Icons.play_arrow);
|
||||
? (p.caution, Icons.stop)
|
||||
: (p.good, Icons.play_arrow);
|
||||
return Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Column(
|
||||
@@ -339,7 +343,7 @@ class _ChecklistPageState extends State<ChecklistPage> {
|
||||
child: FilledButton(
|
||||
style: FilledButton.styleFrom(
|
||||
backgroundColor: color,
|
||||
foregroundColor: Colors.white,
|
||||
foregroundColor: p.onFill(color),
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(24)),
|
||||
),
|
||||
onPressed: run.finished ? null : () => _send(run.press()),
|
||||
@@ -394,7 +398,7 @@ class _ChecklistPageState extends State<ChecklistPage> {
|
||||
style: TextStyle(
|
||||
fontSize: 32,
|
||||
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 ack = b.acks.where((x) => x.id == last.id).firstOrNull;
|
||||
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)
|
||||
? ('… unacked', Colors.orange)
|
||||
? ('… unacked', AppTheme.of(context).caution)
|
||||
: ('?', null);
|
||||
return Text(
|
||||
'"${last.text}" $status${pending > 1 ? ' · $pending waiting' : ''}',
|
||||
|
||||
+4
-4
@@ -49,13 +49,13 @@ class DashPage extends StatelessWidget {
|
||||
children: [
|
||||
if (b.mock)
|
||||
Container(
|
||||
color: p.highlight,
|
||||
color: p.caution,
|
||||
padding: const EdgeInsets.symmetric(vertical: 4),
|
||||
child: Text(
|
||||
'MOCK DATA — NOT FROM THE CAR',
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
color: p.background,
|
||||
color: p.onFill(p.caution),
|
||||
fontWeight: FontWeight.w900,
|
||||
letterSpacing: 2,
|
||||
),
|
||||
@@ -186,8 +186,8 @@ class _DashBar extends StatelessWidget {
|
||||
final p = AppTheme.of(context);
|
||||
final (text, color) = switch (boundary.state) {
|
||||
LinkState.up => ('LINK', p.subdued),
|
||||
LinkState.connecting => ('CONNECTING', p.highlight),
|
||||
LinkState.down => ('NO LINK', p.highlight),
|
||||
LinkState.connecting => ('CONNECTING', p.caution),
|
||||
LinkState.down => ('NO LINK', p.alarm),
|
||||
};
|
||||
return SizedBox(
|
||||
height: 40,
|
||||
|
||||
+30
-17
@@ -169,14 +169,15 @@ class MockBanner extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final p = AppTheme.of(context);
|
||||
return Container(
|
||||
color: Colors.amber.shade700,
|
||||
color: p.caution,
|
||||
padding: const EdgeInsets.symmetric(vertical: 6),
|
||||
child: const Text(
|
||||
child: Text(
|
||||
'MOCK DATA — NOT FROM THE CAR',
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
color: Colors.black,
|
||||
color: p.onFill(p.caution),
|
||||
fontWeight: FontWeight.w900,
|
||||
fontSize: 20,
|
||||
letterSpacing: 2,
|
||||
@@ -200,10 +201,11 @@ class LinkBar extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final b = boundary;
|
||||
final p = AppTheme.of(context);
|
||||
final (color, label) = switch (b.state) {
|
||||
LinkState.up => (Colors.green, 'CONNECTED'),
|
||||
LinkState.connecting => (Colors.orange, 'CONNECTING'),
|
||||
LinkState.down => (Colors.red, 'DOWN'),
|
||||
LinkState.up => (p.good, 'CONNECTED'),
|
||||
LinkState.connecting => (p.caution, 'CONNECTING'),
|
||||
LinkState.down => (p.alarm, 'DOWN'),
|
||||
};
|
||||
// Fixed-width numbers, padded with figure spaces (U+2007, digit-wide): the bar must
|
||||
// not reflow every time a value gains a digit.
|
||||
@@ -230,7 +232,10 @@ class LinkBar extends StatelessWidget {
|
||||
crossAxisAlignment: WrapCrossAlignment.center,
|
||||
children: [
|
||||
Chip(
|
||||
label: Text(label),
|
||||
label: Text(
|
||||
label,
|
||||
style: TextStyle(color: color, fontWeight: FontWeight.w600),
|
||||
),
|
||||
backgroundColor: color.withValues(alpha: 0.3),
|
||||
),
|
||||
Text(b.url, style: const TextStyle(fontFamily: 'monospace')),
|
||||
@@ -317,7 +322,10 @@ Future<void> orinPowerOptions(BuildContext context, Boundary b) async {
|
||||
label: const Text('Reboot'),
|
||||
),
|
||||
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'),
|
||||
icon: const Icon(Icons.power_settings_new),
|
||||
label: const Text('Power off'),
|
||||
@@ -403,26 +411,27 @@ class SignalTile extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final p = AppTheme.of(context);
|
||||
if (missing) {
|
||||
return ListTile(
|
||||
title: Text(
|
||||
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',
|
||||
style: TextStyle(color: Colors.redAccent),
|
||||
style: TextStyle(color: p.alarm),
|
||||
),
|
||||
trailing: Text.rich(
|
||||
TextSpan(
|
||||
children: [
|
||||
const TextSpan(
|
||||
TextSpan(
|
||||
text: '?',
|
||||
style: TextStyle(fontSize: 34, color: Colors.grey),
|
||||
style: TextStyle(fontSize: 34, color: p.subdued),
|
||||
),
|
||||
TextSpan(
|
||||
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,
|
||||
fontWeight: FontWeight.w600,
|
||||
fontFeatures: const [FontFeature.tabularFigures()],
|
||||
color: stale || v == null ? Colors.grey : null,
|
||||
color: stale || v == null ? p.subdued : p.foreground,
|
||||
fontStyle: stale ? FontStyle.italic : null,
|
||||
);
|
||||
return ListTile(
|
||||
title: Text(name, style: const TextStyle(fontFamily: 'monospace')),
|
||||
subtitle: Text(
|
||||
style: stale ? TextStyle(color: p.caution) : null,
|
||||
[
|
||||
if (stale) quality.toUpperCase(),
|
||||
if (v == null) 'no value',
|
||||
@@ -479,11 +489,14 @@ class RawLog extends StatelessWidget {
|
||||
if (b.pending.isNotEmpty || b.acks.isNotEmpty) ...[
|
||||
Text('Commands', style: Theme.of(context).textTheme.titleSmall),
|
||||
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)
|
||||
Text(
|
||||
'${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(),
|
||||
],
|
||||
|
||||
+81
-23
@@ -1,14 +1,22 @@
|
||||
// The palette system, from smart-serow's HUD: four semantic colours, each with a dark
|
||||
// (low ambient light) and a bright (high ambient light) variant, read from a theme
|
||||
// JSON in assets/themes/. Same file format as smart-serow's extra/themes/, so themes
|
||||
// move between the two as they are. Widgets ask for meanings, never for colours:
|
||||
// The palette system, from smart-serow's HUD, widened to seven roles: each a meaning,
|
||||
// each with a dark (low ambient light) and a bright (high ambient light) variant, read
|
||||
// from a theme JSON in assets/themes/. Widgets ask for meanings, never for colours:
|
||||
//
|
||||
// final p = AppTheme.of(context);
|
||||
// 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).
|
||||
|
||||
import 'dart:convert';
|
||||
import 'dart:math' show max, min;
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
@@ -24,45 +32,87 @@ class Palette {
|
||||
const Palette({
|
||||
required this.background,
|
||||
required this.foreground,
|
||||
required this.highlight,
|
||||
required this.subdued,
|
||||
required this.accent,
|
||||
required this.good,
|
||||
required this.caution,
|
||||
required this.alarm,
|
||||
});
|
||||
|
||||
/// Behind everything.
|
||||
final Color background;
|
||||
|
||||
/// Values: what is read at a glance.
|
||||
/// Values and primary text: what is read at a glance.
|
||||
final Color foreground;
|
||||
|
||||
/// Warnings, and the one thing that must be seen.
|
||||
final Color highlight;
|
||||
|
||||
/// Labels, units, grids, and anything missing or stale.
|
||||
/// Labels, units, grids, secondary text, and anything missing or stale.
|
||||
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) {
|
||||
Color c(String k) =>
|
||||
Color(0xFF000000 | int.parse((j[k] as String).substring(1), radix: 16));
|
||||
Color? c(String k) => j[k] is String
|
||||
? Color(
|
||||
0xFF000000 | int.parse((j[k] as String).substring(1), radix: 16),
|
||||
)
|
||||
: null;
|
||||
final foreground = c('foreground')!;
|
||||
final subdued = c('subdued')!;
|
||||
return Palette(
|
||||
background: c('background'),
|
||||
foreground: c('foreground'),
|
||||
highlight: c('highlight'),
|
||||
subdued: c('subdued'),
|
||||
background: c('background')!,
|
||||
foreground: foreground,
|
||||
subdued: 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(
|
||||
background: Color(0xFF000000),
|
||||
foreground: Color(0xFFFFFFFF),
|
||||
highlight: Color(0xFFFF5555),
|
||||
subdued: Color(0xFF808080),
|
||||
accent: Color(0xFF5EC8F2),
|
||||
good: Color(0xFF6CCB5F),
|
||||
caution: Color(0xFFFFC107),
|
||||
alarm: Color(0xFFFF5555),
|
||||
);
|
||||
static const fallbackBright = Palette(
|
||||
background: Color(0xFFF0F0F0),
|
||||
foreground: Color(0xFF1A1A1A),
|
||||
highlight: Color(0xFFCC0000),
|
||||
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);
|
||||
}
|
||||
|
||||
/// 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() {
|
||||
final p = current;
|
||||
final brightness = _isDark ? Brightness.dark : Brightness.light;
|
||||
@@ -116,13 +167,20 @@ class Themes extends ChangeNotifier {
|
||||
scaffoldBackgroundColor: p.background,
|
||||
colorScheme:
|
||||
ColorScheme.fromSeed(
|
||||
seedColor: p.highlight,
|
||||
seedColor: p.accent,
|
||||
brightness: brightness,
|
||||
).copyWith(
|
||||
surface: p.background,
|
||||
onSurface: p.foreground,
|
||||
primary: p.highlight,
|
||||
error: p.highlight,
|
||||
surfaceContainerHigh: p.surface,
|
||||
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,
|
||||
onSurfaceVariant: p.subdued,
|
||||
),
|
||||
|
||||
@@ -50,7 +50,8 @@ class GpsCompass extends StatelessWidget {
|
||||
final theme = AppTheme.of(context);
|
||||
|
||||
// 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
|
||||
final angle = _hasSignal ? (heading! * math.pi / 180.0) : 0.0;
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
// 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
|
||||
// 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
|
||||
// (app guideline 2), shown in subdued rather than hidden.
|
||||
@@ -28,7 +28,7 @@ class StatBox extends StatelessWidget {
|
||||
final String label;
|
||||
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;
|
||||
|
||||
/// Missing or stale: the value shows in subdued.
|
||||
@@ -41,7 +41,7 @@ class StatBox extends StatelessWidget {
|
||||
final valueColor = dim
|
||||
? theme.subdued
|
||||
: warning
|
||||
? theme.highlight
|
||||
? theme.alarm
|
||||
: theme.foreground;
|
||||
|
||||
return Expanded(
|
||||
@@ -168,7 +168,7 @@ class StatBoxMain extends StatelessWidget {
|
||||
color: dim
|
||||
? theme.subdued
|
||||
: warning
|
||||
? theme.highlight
|
||||
? theme.alarm
|
||||
: theme.foreground,
|
||||
height: 1,
|
||||
),
|
||||
|
||||
@@ -7,7 +7,7 @@ import '../reading.dart';
|
||||
import '../theme/palette.dart';
|
||||
|
||||
/// 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 {
|
||||
const Telltale({
|
||||
super.key,
|
||||
@@ -32,7 +32,7 @@ class Telltale extends StatelessWidget {
|
||||
final missing = reading.value == null;
|
||||
final lit = reading.on && !reading.dim;
|
||||
final color = lit
|
||||
? (warn ? p.highlight : p.foreground)
|
||||
? (warn ? p.alarm : p.foreground)
|
||||
: p.subdued.withValues(alpha: missing ? 0.35 : 0.6);
|
||||
return Expanded(
|
||||
flex: flex,
|
||||
|
||||
Reference in New Issue
Block a user