Files
PilotVault/Fly App/lib/ui/fly/bottom/telemetry_strip.dart
T
tajniak81andClaude Opus 5.5 590f2a6152 Give the Fly App a GO 4-style flight HUD
The new flight screen (lib/ui/fly/fly_screen.dart) is built from layers.
Each readout listens to its own telemetry sub-model, so a 10 Hz tick only
repaints what changed. It adds a status bar with a status chip and tip
marquee, a smart battery bar with land/RTH markers, a camera info strip,
an attitude ball, an H/D/VS/HS/VPS strip, and a minimap that swaps places
with the video when tapped. The HUD is laid out in design pixels and
scaled to phone or tablet inside the safe area.

What the HUD shows depends on the product: dual or multi battery bars,
SSD status, a thermal spot reading, a master/slave RC badge, and Wi-Fi
bars in place of RC/HD. The bridges now report the data for these: the
camera's ssd/thermal groups and the RC's master/slave mode.

The old FlightControlPage can still be opened from Settings > About >
Classic flight screen until the P3 safety flows land. The same tab also
gets a toggle for the performance overlay.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 18:54:09 +02:00

94 lines
2.9 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import 'package:flutter/material.dart';
import '../../../fly/hud_features.dart';
import '../../../fly/state/aircraft_state.dart';
import '../hud_kit.dart';
/// The bottom telemetry strip (plan P2): H · D · VS (with arrow) · HS · VPS.
class TelemetryStrip extends StatelessWidget {
const TelemetryStrip({super.key, required this.aircraft});
final AircraftState aircraft;
@override
Widget build(BuildContext context) {
return ListenableBuilder(
listenable: aircraft,
builder: (BuildContext context, _) {
final AircraftState a = aircraft;
final double? vs = a.verticalSpeed;
// Under 0.1 m/s is hover noise; no arrow for it.
final int dir = vs == null || vs.abs() < 0.1 ? 0 : vs.sign.toInt();
return GlassPill(
height: 40,
padding: const EdgeInsets.symmetric(horizontal: 12),
child: Row(mainAxisSize: MainAxisSize.min, children: <Widget>[
_Field('H', fmtNum(a.altitude), 'm'),
_Field('D', fmtNum(a.homeDistance, digits: 0), 'm'),
_Field('VS', fmtNum(vs?.abs()), 'm/s', arrow: dir),
_Field('HS', fmtNum(a.horizontalSpeed), 'm/s'),
Requires(HudFeatures.vpsReadout, child: _Field('VPS', fmtNum(a.vpsHeight), 'm')),
]),
);
},
);
}
}
class _Field extends StatelessWidget {
const _Field(this.label, this.value, this.unit, {this.arrow = 0});
final String label;
final String value;
final String unit;
/// 1 climbing, −1 descending, 0 no arrow. Painted: Space Mono has no ▲ / ▼.
final int arrow;
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 6),
child: Column(mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: <Widget>[
Text(label, style: HudText.label),
const SizedBox(height: 2),
Row(mainAxisSize: MainAxisSize.min, children: <Widget>[
if (arrow != 0)
Padding(
padding: const EdgeInsets.only(right: 2),
child: CustomPaint(size: const Size(7, 6), painter: _ArrowPainter(up: arrow > 0)),
),
Text.rich(TextSpan(children: <TextSpan>[
TextSpan(text: value, style: HudText.value),
TextSpan(text: ' $unit', style: HudText.unit),
])),
]),
]),
);
}
}
class _ArrowPainter extends CustomPainter {
const _ArrowPainter({required this.up});
final bool up;
@override
void paint(Canvas canvas, Size size) {
final double w = size.width, h = size.height;
final Path p = up
? (Path()
..moveTo(w / 2, 0)
..lineTo(w, h)
..lineTo(0, h))
: (Path()
..moveTo(0, 0)
..lineTo(w, 0)
..lineTo(w / 2, h));
canvas.drawPath(p..close(), Paint()..color = HudColors.ready);
}
@override
bool shouldRepaint(_ArrowPainter old) => old.up != up;
}