App structure: a swipe-in menu shell; pages/, link/, actions/; Settings
The shell (shell.dart): a drawer from the left edge or each page's menu button. Dash, Signals, Captures; Tools (Camera, Link test, Orin power options), the rarely used ones, after pirate-station-aide; Settings (gateway address, dark/bright, build-time theme and landing). Landing view at build time: YUKART_HOME=dash|signals, signals for now. Dash, Signals and Captures stay alive while hidden; Captures has its own navigator, so a run survives switching views. Back goes inside Captures, then to the landing view, then out. The mock banner sits over every page. main.dart is startup only; the old home screen is pages/signals.dart; dialogs and sheets moved to actions/; the protocol client, USB binding and Reading to link/. lib/README.md catalogues it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DDwfd6Ki8UfG4hK6W5YpJz
This commit is contained in:
@@ -0,0 +1,71 @@
|
||||
// The link to the Orin at a glance: the state chip, and the details behind it.
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import '../link/boundary.dart';
|
||||
import '../link/net.dart';
|
||||
import '../theme/palette.dart';
|
||||
|
||||
/// CONNECTED / CONNECTING / DOWN in good / caution / alarm.
|
||||
class LinkChip extends StatelessWidget {
|
||||
const LinkChip({super.key, required this.boundary});
|
||||
final Boundary boundary;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final p = AppTheme.of(context);
|
||||
final (color, label) = switch (boundary.state) {
|
||||
LinkState.up => (p.good, 'CONNECTED'),
|
||||
LinkState.connecting => (p.caution, 'CONNECTING'),
|
||||
LinkState.down => (p.alarm, 'DOWN'),
|
||||
};
|
||||
return Chip(
|
||||
label: Text(
|
||||
label,
|
||||
style: TextStyle(color: color, fontWeight: FontWeight.w600),
|
||||
),
|
||||
backgroundColor: color.withValues(alpha: 0.3),
|
||||
side: BorderSide.none,
|
||||
visualDensity: VisualDensity.compact,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Address, gateway, last-message age, clock offset, USB link. Fixed-width numbers,
|
||||
/// padded with figure spaces (U+2007, digit-wide): nothing reflows as a value gains a digit.
|
||||
class LinkDetails extends StatelessWidget {
|
||||
const LinkDetails({super.key, required this.boundary, required this.usb});
|
||||
final Boundary boundary;
|
||||
final UsbLink usb;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final b = boundary;
|
||||
String pad(String v, int n) => v.padLeft(n, ' ');
|
||||
final age = b.lastMessage == null
|
||||
? pad('—', 4)
|
||||
: pad(
|
||||
'${DateTime.now().difference(b.lastMessage!).inMilliseconds.clamp(0, 9999)}',
|
||||
4,
|
||||
);
|
||||
final offset = b.clockOffset == null
|
||||
? pad('—', 6)
|
||||
: pad((b.clockOffset!.inMilliseconds / 1000).toStringAsFixed(1), 6);
|
||||
const fixed = TextStyle(fontFeatures: [FontFeature.tabularFigures()]);
|
||||
return Wrap(
|
||||
spacing: 16,
|
||||
runSpacing: 4,
|
||||
crossAxisAlignment: WrapCrossAlignment.center,
|
||||
children: [
|
||||
Text(b.url, style: const TextStyle(fontFamily: 'monospace')),
|
||||
Text(b.detail),
|
||||
Text('last msg $age ms', style: fixed),
|
||||
Text('Orin−tablet clock $offset s', style: fixed),
|
||||
if (usb.supported)
|
||||
Text(
|
||||
'USB link ${usb.up ? 'up' : 'absent'}${usb.bound ? ', bound' : ''}',
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
// Mock data must look like mock data: on the surface of every page, not in a menu.
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import '../theme/palette.dart';
|
||||
|
||||
class MockBanner extends StatelessWidget {
|
||||
const MockBanner({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final p = AppTheme.of(context);
|
||||
return Container(
|
||||
color: p.caution,
|
||||
padding: const EdgeInsets.symmetric(vertical: 6),
|
||||
child: Text(
|
||||
'MOCK DATA — NOT FROM THE CAR',
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
color: p.onFill(p.caution),
|
||||
fontWeight: FontWeight.w900,
|
||||
fontSize: 20,
|
||||
letterSpacing: 2,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,9 @@
|
||||
// An on/off lamp in the boxed pattern (lib/widgets/stat_box.dart): the label is the
|
||||
// lamp. Takes a [Reading] (lib/reading.dart) so missing and stale draw as they do elsewhere.
|
||||
// lamp. Takes a [Reading] (lib/link/reading.dart) so missing and stale draw as they do elsewhere.
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import '../reading.dart';
|
||||
import '../link/reading.dart';
|
||||
import '../theme/palette.dart';
|
||||
|
||||
/// An on/off lamp in the boxed pattern: the label is the lamp. Lit in foreground
|
||||
|
||||
Reference in New Issue
Block a user