Status bar: the Orin at a glance over every view, like a phone's
Link on the left (with the menu on the Dash), then hot temperature, CAN, OBD while connected, GNSS fix and satellites, LTE, WiFi, UPS, and the dark/bright button. The Dash's own top line is gone into it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -12,6 +12,14 @@ what the Orin publishes.
|
|||||||
|
|
||||||
## 1. Orin status in a top banner, like a phone's
|
## 1. Orin status in a top banner, like a phone's
|
||||||
|
|
||||||
|
**First cut (2026-10-15):** `widgets/status_bar.dart`, drawn by the shell over every view (the
|
||||||
|
Dash's own top line is gone; its menu button moved in). Left: the menu (Dash only) and the
|
||||||
|
link. Right: Orin temperature (only from 80 °C), CAN (alarm when absent or bus-off), OBD
|
||||||
|
(only while the ELM is connected), GNSS fix icon and satellite count, LTE bars and
|
||||||
|
technology (only with a modem), WiFi bars, UPS battery and % (caution on battery, alarm
|
||||||
|
under 20 %), then the dark/bright button. Tapping the strip does nothing yet.
|
||||||
|
What was asked:
|
||||||
|
|
||||||
A thin strip across the top of the app (the Dash's own top line included) showing the Orin
|
A thin strip across the top of the app (the Dash's own top line included) showing the Orin
|
||||||
the way a phone's status bar shows the phone: its network links and its power.
|
the way a phone's status bar shows the phone: its network links and its power.
|
||||||
|
|
||||||
@@ -65,8 +73,8 @@ chosen readouts.
|
|||||||
|
|
||||||
**Done (2026-10-15):** Settings has Auto / Dark / Bright (a segmented control, kept across
|
**Done (2026-10-15):** Settings has Auto / Dark / Bright (a segmented control, kept across
|
||||||
restarts). Auto follows Android's dark theme (`platformBrightness`, so its schedule too). The
|
restarts). Auto follows Android's dark theme (`platformBrightness`, so its schedule too). The
|
||||||
Dash's button holds the other variant, and a long press returns it to auto. Not done: the
|
status bar's button (it moved there with item 1) holds the other variant, and a long press
|
||||||
ambient light sensor as an auto source, and moving the button into the banner (item 1).
|
returns it to auto. Not done: the light sensor as an auto source.
|
||||||
What was asked:
|
What was asked:
|
||||||
|
|
||||||
Today the variant is a switch (the Dash's top-right button, and Settings). Wanted:
|
Today the variant is a switch (the Dash's top-right button, and Settings). Wanted:
|
||||||
|
|||||||
+2
-1
@@ -29,6 +29,7 @@ pirate-station-aide's (main views, then the rarely used ones grouped as Tools).
|
|||||||
| `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 |
|
||||||
| `GpsCompass` | `widgets/gps_compass.dart` | Heading arrow and bearing; "ACQ" while acquiring, "N/A" without | smart-serow, from gps-speedo |
|
| `GpsCompass` | `widgets/gps_compass.dart` | Heading arrow and bearing; "ACQ" while acquiring, "N/A" without | smart-serow, from gps-speedo |
|
||||||
| `LinkChip`, `LinkDetails` | `widgets/link_status.dart` | The link's state in good / caution / alarm; address, last-message age, clock offset, USB link | Yukart |
|
| `LinkChip`, `LinkDetails` | `widgets/link_status.dart` | The link's state in good / caution / alarm; address, last-message age, clock offset, USB link | Yukart |
|
||||||
|
| `StatusBar` | `widgets/status_bar.dart` | The Orin like a phone's status bar, over every view: link, then hot temperature, CAN, OBD (while connected), GNSS fix and satellites, LTE, WiFi, UPS; and the dark/bright button | Yukart |
|
||||||
| `MockBanner` | `widgets/mock_banner.dart` | "MOCK DATA — NOT FROM THE CAR" in caution, over every page while the gateway is mocking | Yukart |
|
| `MockBanner` | `widgets/mock_banner.dart` | "MOCK DATA — NOT FROM THE CAR" in caution, over every page while the gateway is mocking | Yukart |
|
||||||
|
|
||||||
## Conventions
|
## Conventions
|
||||||
@@ -41,7 +42,7 @@ pirate-station-aide's (main views, then the rarely used ones grouped as Tools).
|
|||||||
`onFill(colour)` (text on a fill). No widget names a colour. Themes are JSON in `assets/themes/`, `rei` by
|
`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`).
|
default; smart-serow's four-key files still load (their `highlight` is `alarm`).
|
||||||
- **Dark or bright.** `Themes.mode` is `auto` (Android's dark theme, schedule included), `dark` or
|
- **Dark or bright.** `Themes.mode` is `auto` (Android's dark theme, schedule included), `dark` or
|
||||||
`bright`, chosen in Settings and kept across restarts. The Dash's quick button holds the other variant;
|
`bright`, chosen in Settings and kept across restarts. The status bar's button holds the other variant;
|
||||||
a long press there is auto again.
|
a long press there is auto again.
|
||||||
- **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.
|
||||||
|
|||||||
+2
-46
@@ -6,7 +6,6 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
import '../link/boundary.dart';
|
import '../link/boundary.dart';
|
||||||
import '../shell_scope.dart';
|
|
||||||
import '../link/reading.dart';
|
import '../link/reading.dart';
|
||||||
import '../theme/palette.dart';
|
import '../theme/palette.dart';
|
||||||
import '../widgets/accel_graph.dart';
|
import '../widgets/accel_graph.dart';
|
||||||
@@ -16,14 +15,13 @@ import '../widgets/telltale.dart';
|
|||||||
import '../widgets/whiskey_mark.dart';
|
import '../widgets/whiskey_mark.dart';
|
||||||
|
|
||||||
class DashPage extends StatelessWidget {
|
class DashPage extends StatelessWidget {
|
||||||
const DashPage({super.key, required this.boundary, required this.themes});
|
const DashPage({super.key, required this.boundary});
|
||||||
final Boundary boundary;
|
final Boundary boundary;
|
||||||
final Themes themes;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return ListenableBuilder(
|
return ListenableBuilder(
|
||||||
listenable: Listenable.merge([boundary, themes]),
|
listenable: boundary,
|
||||||
builder: (context, _) {
|
builder: (context, _) {
|
||||||
final p = AppTheme.of(context);
|
final p = AppTheme.of(context);
|
||||||
final b = boundary;
|
final b = boundary;
|
||||||
@@ -50,7 +48,6 @@ class DashPage extends StatelessWidget {
|
|||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
children: [
|
children: [
|
||||||
_DashBar(boundary: b, themes: themes),
|
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.fromLTRB(16, 0, 16, 16),
|
padding: const EdgeInsets.fromLTRB(16, 0, 16, 16),
|
||||||
@@ -163,44 +160,3 @@ class DashPage extends StatelessWidget {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The dash's own thin top line: the menu, link state, and the dark/bright quick switch.
|
|
||||||
class _DashBar extends StatelessWidget {
|
|
||||||
const _DashBar({required this.boundary, required this.themes});
|
|
||||||
final Boundary boundary;
|
|
||||||
final Themes themes;
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
final p = AppTheme.of(context);
|
|
||||||
final (text, color) = switch (boundary.state) {
|
|
||||||
LinkState.up => ('LINK', p.subdued),
|
|
||||||
LinkState.connecting => ('CONNECTING', p.caution),
|
|
||||||
LinkState.down => ('NO LINK', p.alarm),
|
|
||||||
};
|
|
||||||
return SizedBox(
|
|
||||||
height: 40,
|
|
||||||
child: Row(
|
|
||||||
children: [
|
|
||||||
MenuButton(color: p.subdued),
|
|
||||||
Text(text, style: TextStyle(color: color, letterSpacing: 2)),
|
|
||||||
const Spacer(),
|
|
||||||
// Tap: hold the other variant. Long press: back to auto (Settings has both).
|
|
||||||
GestureDetector(
|
|
||||||
onLongPress: () => themes.mode = VariantMode.auto,
|
|
||||||
child: IconButton(
|
|
||||||
tooltip: themes.isDark ? 'Bright' : 'Dark',
|
|
||||||
onPressed: themes.toggle,
|
|
||||||
icon: Icon(
|
|
||||||
themes.mode == VariantMode.auto
|
|
||||||
? Icons.brightness_auto
|
|
||||||
: (themes.isDark ? Icons.light_mode : Icons.dark_mode),
|
|
||||||
color: p.subdued,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
+23
-6
@@ -20,6 +20,7 @@ import 'shell_scope.dart';
|
|||||||
import 'theme/palette.dart';
|
import 'theme/palette.dart';
|
||||||
import 'widgets/link_status.dart';
|
import 'widgets/link_status.dart';
|
||||||
import 'widgets/mock_banner.dart';
|
import 'widgets/mock_banner.dart';
|
||||||
|
import 'widgets/status_bar.dart';
|
||||||
|
|
||||||
class AppShell extends StatefulWidget {
|
class AppShell extends StatefulWidget {
|
||||||
const AppShell({
|
const AppShell({
|
||||||
@@ -53,7 +54,7 @@ class _AppShellState extends State<AppShell> {
|
|||||||
Widget _page(ShellView v) {
|
Widget _page(ShellView v) {
|
||||||
final b = widget.boundary;
|
final b = widget.boundary;
|
||||||
return switch (v) {
|
return switch (v) {
|
||||||
ShellView.dash => DashPage(boundary: b, themes: widget.themes),
|
ShellView.dash => DashPage(boundary: b),
|
||||||
ShellView.signals => SignalsPage(boundary: b, usb: widget.usb),
|
ShellView.signals => SignalsPage(boundary: b, usb: widget.usb),
|
||||||
ShellView.captures => Navigator(
|
ShellView.captures => Navigator(
|
||||||
key: _captures,
|
key: _captures,
|
||||||
@@ -113,14 +114,30 @@ class _AppShellState extends State<AppShell> {
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
body: ListenableBuilder(
|
body: ListenableBuilder(
|
||||||
listenable: widget.boundary,
|
listenable: Listenable.merge([widget.boundary, widget.themes]),
|
||||||
builder: (context, child) => Column(
|
builder: (context, child) => Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
children: [
|
children: [
|
||||||
if (widget.boundary.mock)
|
// The Orin's status over every view; the Dash, with no app bar, gets the menu in it.
|
||||||
const SafeArea(bottom: false, child: MockBanner()),
|
SafeArea(
|
||||||
// Pages draw their own top (app bars); keep every bottom clear of the system bar.
|
bottom: false,
|
||||||
Expanded(child: SafeArea(top: false, child: child!)),
|
child: StatusBar(
|
||||||
|
boundary: widget.boundary,
|
||||||
|
themes: widget.themes,
|
||||||
|
leading: current == ShellView.dash
|
||||||
|
? MenuButton(color: AppTheme.of(context).subdued)
|
||||||
|
: null,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if (widget.boundary.mock) const MockBanner(),
|
||||||
|
// The status bar took the top inset; keep every bottom clear of the system bar.
|
||||||
|
Expanded(
|
||||||
|
child: MediaQuery.removePadding(
|
||||||
|
context: context,
|
||||||
|
removeTop: true,
|
||||||
|
child: SafeArea(top: false, child: child!),
|
||||||
|
),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
child: IndexedStack(
|
child: IndexedStack(
|
||||||
|
|||||||
@@ -0,0 +1,232 @@
|
|||||||
|
// The Orin at a glance, the way a phone's status bar shows the phone: the link on the
|
||||||
|
// left, then its links out and its power on the right, and the dark/bright switch.
|
||||||
|
// Drawn by the shell above every view (DASH-NEXT.md item 1).
|
||||||
|
//
|
||||||
|
// An indicator shows its state in palette roles: foreground when fine, caution when
|
||||||
|
// degraded or on battery, alarm when down, subdued when stale or unknown. Things
|
||||||
|
// that are only news when wrong (temperature) appear only then; ones that are only
|
||||||
|
// news when present (the ELM, a modem) appear only then.
|
||||||
|
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
import '../link/boundary.dart';
|
||||||
|
import '../link/reading.dart';
|
||||||
|
import '../theme/palette.dart';
|
||||||
|
|
||||||
|
class StatusBar extends StatelessWidget {
|
||||||
|
const StatusBar({
|
||||||
|
super.key,
|
||||||
|
required this.boundary,
|
||||||
|
required this.themes,
|
||||||
|
this.leading,
|
||||||
|
});
|
||||||
|
final Boundary boundary;
|
||||||
|
final Themes themes;
|
||||||
|
|
||||||
|
/// Before the link state: the menu button, on a view with no app bar of its own.
|
||||||
|
final Widget? leading;
|
||||||
|
|
||||||
|
static const height = 32.0;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final p = AppTheme.of(context);
|
||||||
|
final b = boundary;
|
||||||
|
Reading r(String n) => Reading.of(b, n);
|
||||||
|
final up = b.state == LinkState.up;
|
||||||
|
final (text, color) = switch (b.state) {
|
||||||
|
LinkState.up => ('LINK', p.subdued),
|
||||||
|
LinkState.connecting => ('CONNECTING', p.caution),
|
||||||
|
LinkState.down => ('NO LINK', p.alarm),
|
||||||
|
};
|
||||||
|
return SizedBox(
|
||||||
|
height: height,
|
||||||
|
child: IconTheme(
|
||||||
|
data: IconThemeData(size: 18, color: p.foreground),
|
||||||
|
child: DefaultTextStyle(
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 14,
|
||||||
|
color: p.foreground,
|
||||||
|
fontFeatures: const [FontFeature.tabularFigures()],
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
if (leading != null) leading! else const SizedBox(width: 16),
|
||||||
|
Text(text, style: TextStyle(color: color, letterSpacing: 2)),
|
||||||
|
const Spacer(),
|
||||||
|
// Without the link, every Orin value is old news.
|
||||||
|
if (up) ...[
|
||||||
|
..._temperature(p, r('orin_tj_temp_c')),
|
||||||
|
..._can(p, r('can_up'), r('can_bus_state')),
|
||||||
|
..._elm(p, r('elm_connected')),
|
||||||
|
..._gnss(p, r('gnss_fix'), r('gnss_satellites')),
|
||||||
|
..._lte(
|
||||||
|
p,
|
||||||
|
r('net_lte_state'),
|
||||||
|
r('net_lte_tech'),
|
||||||
|
r('net_lte_signal_pct'),
|
||||||
|
),
|
||||||
|
..._wifi(p, r('net_wifi_up'), r('net_wifi_signal_dbm')),
|
||||||
|
..._ups(p, r('ups_state'), r('ups_battery_pct')),
|
||||||
|
],
|
||||||
|
_VariantButton(themes: themes),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// One indicator: an icon, maybe a short text, and the gap after it.
|
||||||
|
static List<Widget> _item(Color c, {IconData? icon, String? text}) => [
|
||||||
|
if (icon != null) Icon(icon, color: c),
|
||||||
|
if (icon != null && text != null) const SizedBox(width: 2),
|
||||||
|
if (text != null) Text(text, style: TextStyle(color: c)),
|
||||||
|
const SizedBox(width: 14),
|
||||||
|
];
|
||||||
|
|
||||||
|
/// Only when hot: caution from 80 °C, alarm from 90 (the Orin throttles near 99).
|
||||||
|
static List<Widget> _temperature(Palette p, Reading t) {
|
||||||
|
final c = t.number;
|
||||||
|
if (t.dim || c == null || c < 80) return [];
|
||||||
|
return _item(
|
||||||
|
c >= 90 ? p.alarm : p.caution,
|
||||||
|
icon: Icons.thermostat,
|
||||||
|
text: '${c.round()}°',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static List<Widget> _can(Palette p, Reading up, Reading state) {
|
||||||
|
if (up.value == null) return _item(p.subdued, text: 'CAN');
|
||||||
|
final color = switch ((up.on, state.value)) {
|
||||||
|
(false, _) || (_, 'BUS-OFF') || (_, 'STOPPED') => p.alarm,
|
||||||
|
(_, 'ERROR-WARNING') || (_, 'ERROR-PASSIVE') => p.caution,
|
||||||
|
_ => p.foreground,
|
||||||
|
};
|
||||||
|
return _item(up.dim ? p.subdued : color, text: 'CAN');
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The OBD-II adapter, only while connected (it is for calibration, not always on).
|
||||||
|
static List<Widget> _elm(Palette p, Reading c) => c.on
|
||||||
|
? _item(
|
||||||
|
c.dim ? p.subdued : p.foreground,
|
||||||
|
icon: Icons.bluetooth_connected,
|
||||||
|
text: 'OBD',
|
||||||
|
)
|
||||||
|
: [];
|
||||||
|
|
||||||
|
static List<Widget> _gnss(Palette p, Reading fix, Reading sats) {
|
||||||
|
final n = sats.number?.round();
|
||||||
|
final (icon, color) = switch (fix.value) {
|
||||||
|
'3d' => (Icons.gps_fixed, p.foreground),
|
||||||
|
'2d' => (Icons.gps_fixed, p.caution),
|
||||||
|
'none' => (Icons.gps_not_fixed, p.caution),
|
||||||
|
_ => (Icons.gps_off, p.subdued),
|
||||||
|
};
|
||||||
|
return _item(
|
||||||
|
fix.dim ? p.subdued : color,
|
||||||
|
icon: icon,
|
||||||
|
text: n == null ? null : '$n',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The modem, when there is one: bars from its signal, and the technology.
|
||||||
|
static List<Widget> _lte(
|
||||||
|
Palette p,
|
||||||
|
Reading state,
|
||||||
|
Reading tech,
|
||||||
|
Reading pct,
|
||||||
|
) {
|
||||||
|
final s = state.value as String?;
|
||||||
|
if (s == null) return [];
|
||||||
|
final q = pct.number ?? 0;
|
||||||
|
final t = (tech.value as String? ?? '').toUpperCase();
|
||||||
|
final (icon, color) = switch (s) {
|
||||||
|
'connected' => (
|
||||||
|
q >= 60
|
||||||
|
? Icons.signal_cellular_alt
|
||||||
|
: q >= 30
|
||||||
|
? Icons.signal_cellular_alt_2_bar
|
||||||
|
: Icons.signal_cellular_alt_1_bar,
|
||||||
|
p.foreground,
|
||||||
|
),
|
||||||
|
'registered' ||
|
||||||
|
'searching' ||
|
||||||
|
'connecting' ||
|
||||||
|
'enabling' => (Icons.signal_cellular_alt_1_bar, p.caution),
|
||||||
|
_ => (Icons.signal_cellular_off, p.subdued),
|
||||||
|
};
|
||||||
|
return _item(
|
||||||
|
state.dim ? p.subdued : color,
|
||||||
|
icon: icon,
|
||||||
|
text: t.isEmpty ? null : t,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static List<Widget> _wifi(Palette p, Reading up, Reading dbm) {
|
||||||
|
if (!up.on) return _item(p.subdued, icon: Icons.wifi_off);
|
||||||
|
final d = dbm.number ?? -100;
|
||||||
|
final icon = d >= -60
|
||||||
|
? Icons.wifi
|
||||||
|
: d >= -70
|
||||||
|
? Icons.wifi_2_bar
|
||||||
|
: Icons.wifi_1_bar;
|
||||||
|
return _item(up.dim ? p.subdued : p.foreground, icon: icon);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The UPS: caution on battery (the car is off), alarm under 20 %.
|
||||||
|
static List<Widget> _ups(Palette p, Reading state, Reading pct) {
|
||||||
|
final n = pct.number;
|
||||||
|
if (n == null) return _item(p.subdued, icon: Icons.battery_unknown);
|
||||||
|
const bars = [
|
||||||
|
Icons.battery_0_bar,
|
||||||
|
Icons.battery_1_bar,
|
||||||
|
Icons.battery_2_bar,
|
||||||
|
Icons.battery_3_bar,
|
||||||
|
Icons.battery_4_bar,
|
||||||
|
Icons.battery_5_bar,
|
||||||
|
Icons.battery_6_bar,
|
||||||
|
Icons.battery_full,
|
||||||
|
];
|
||||||
|
final charging = state.value == 'charging';
|
||||||
|
final icon = charging
|
||||||
|
? Icons.battery_charging_full
|
||||||
|
: bars[(n / 100 * 7).round().clamp(0, 7)];
|
||||||
|
final color = n < 20
|
||||||
|
? p.alarm
|
||||||
|
: state.value == 'discharging'
|
||||||
|
? p.caution
|
||||||
|
: p.foreground;
|
||||||
|
return _item(
|
||||||
|
pct.dim ? p.subdued : color,
|
||||||
|
icon: icon,
|
||||||
|
text: '${n.round()}%',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Tap: hold the other variant. Long press: back to auto (Settings has all three).
|
||||||
|
class _VariantButton extends StatelessWidget {
|
||||||
|
const _VariantButton({required this.themes});
|
||||||
|
final Themes themes;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final p = AppTheme.of(context);
|
||||||
|
return GestureDetector(
|
||||||
|
onLongPress: () => themes.mode = VariantMode.auto,
|
||||||
|
child: IconButton(
|
||||||
|
tooltip: themes.isDark ? 'Bright' : 'Dark',
|
||||||
|
onPressed: themes.toggle,
|
||||||
|
iconSize: 20,
|
||||||
|
visualDensity: VisualDensity.compact,
|
||||||
|
icon: Icon(
|
||||||
|
themes.mode == VariantMode.auto
|
||||||
|
? Icons.brightness_auto
|
||||||
|
: (themes.isDark ? Icons.light_mode : Icons.dark_mode),
|
||||||
|
color: p.subdued,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user