72 lines
2.3 KiB
Dart
72 lines
2.3 KiB
Dart
|
|
// 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' : ''}',
|
|||
|
|
),
|
|||
|
|
],
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
}
|