Files
PilotVault/Fly App/lib/ui/fly/dialogs/rth_target_dialog.dart
T
tajniak81andClaude Opus 5.5 6ebc567e97 Put every flight command behind a SafetyGate
Take-off, land, RTH, the landing cancel/confirm and Stop now all go
through one SafetyGate (lib/fly/safety_gate.dart). The gate sorts its
preconditions into blockers and warnings: connection, flight state,
battery against the thresholds, IMU warm-up, compass, critical
diagnostics, GPS, home point, SD card and the RTH battery budget. It
checks again right before sending.

The left rail is now a state machine (lib/fly/action_rail_state.dart):
Take-off becomes Land, then Cancel or Confirm landing. RTH becomes
Cancel RTH. A Stop button shows while an intelligent mode is flying.
Moving the aircraft needs a slide-to-confirm dialog, which can offer
Precision Takeoff or set the RTH altitude. A blocked command shows why
and has no slider. When the pilot is more than 30 m from home, RTH asks
whether to return to them or to the original home point.

The HUD gains an error stack. Its cards can be dismissed, it fits the
space it has, and a new warning triggers haptic feedback. It also gets a
smart-RTH countdown banner with Cancel / Go home now, a forced-landing
banner, and an Aircraft Status sheet. The sheet opens from the status
chip and appears before each connection's first take-off. It offers
Calibrate compass/IMU and Format SD.

On the bridge side this adds precisionTakeOff, setHomeLocation, compass
and IMU calibration start/stop, formatStorage, and compass health in the
telemetry tick.

The classic flight screen stays available until the simulator run passes.

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

96 lines
3.7 KiB
Dart

import 'package:flutter/material.dart';
import '../../../theme.dart';
import '../../pv_icons.dart';
import '../hud_kit.dart';
/// Where Return to Home flies to.
enum RthTarget {
/// The home point recorded at take-off.
originalHome,
/// The pilot's position now (the phone's GPS); the home point is moved first.
pilot,
}
/// GO 4's RTH target choice, offered when the pilot has walked more than
/// `SafetyGate.rthTargetChoiceDistance` from the recorded home point.
/// Resolves to null when dismissed.
Future<RthTarget?> showRthTargetDialog(BuildContext context, {required double pilotToHomeM}) {
return showDialog<RthTarget>(
context: context,
barrierColor: const Color(0x99050A14),
builder: (BuildContext context) => Dialog(
backgroundColor: const Color(0xF20E1A33),
insetPadding: const EdgeInsets.symmetric(horizontal: 24, vertical: 12),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(14), side: const BorderSide(color: Glass.hairline)),
child: ConstrainedBox(
constraints: const BoxConstraints(maxWidth: 460),
child: Padding(
padding: const EdgeInsets.fromLTRB(18, 16, 18, 16),
child: Column(mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.stretch, children: <Widget>[
const Text('Return to where?',
style: TextStyle(fontFamily: PV.fontSans, fontSize: 17, fontWeight: FontWeight.w700, color: Glass.ink)),
const SizedBox(height: 6),
Text('You are ${pilotToHomeM.round()} m from the home point recorded at take-off.',
style: const TextStyle(fontFamily: PV.fontSans, fontSize: 13, color: Color(0xFFB5C2DA))),
const SizedBox(height: 14),
Row(children: <Widget>[
Expanded(
child: _TargetTile(
key: const ValueKey<String>('rth-target-pilot'),
icon: 'user',
title: 'Current Position',
body: 'Move the home point to you, then return.',
onTap: () => Navigator.of(context).pop(RthTarget.pilot),
),
),
const SizedBox(width: 10),
Expanded(
child: _TargetTile(
key: const ValueKey<String>('rth-target-home'),
icon: 'home',
title: 'Original Home Point',
body: 'Return to where the aircraft took off.',
onTap: () => Navigator.of(context).pop(RthTarget.originalHome),
),
),
]),
]),
),
),
),
);
}
class _TargetTile extends StatelessWidget {
const _TargetTile({super.key, required this.icon, required this.title, required this.body, required this.onTap});
final String icon;
final String title;
final String body;
final VoidCallback onTap;
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: onTap,
child: Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: Glass.pillStrong,
borderRadius: BorderRadius.circular(10),
border: Border.all(color: Glass.hairline),
),
child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: <Widget>[
PVIcon(icon, size: 22, color: Glass.ink),
const SizedBox(height: 8),
Text(title, style: const TextStyle(fontFamily: PV.fontSans, fontSize: 13, fontWeight: FontWeight.w700, color: Glass.ink)),
const SizedBox(height: 2),
Text(body, style: const TextStyle(fontFamily: PV.fontSans, fontSize: 11, color: HudText.dim, height: 1.3)),
]),
),
);
}
}