import 'package:flutter/material.dart'; import '../../fly/hud_features.dart'; import '../../fly/product_profile.dart'; import '../../fly/safety_gate.dart'; import '../../theme.dart'; import 'dialogs/rth_target_dialog.dart'; import 'dialogs/slide_to_confirm.dart'; import 'hud_kit.dart'; /// Turns a rail press into the GO 4 confirm flow (plan P3): ask the /// [SafetyGate], offer the Aircraft Status sheet before the first take-off of a /// connection, ask the RTH target when the pilot has moved, show the /// slide-to-confirm dialog, then let the gate send the command. /// /// Commands that stop the aircraft moving (cancel landing, cancel RTH, stop an /// intelligent mode) skip the slider: they make it hover, and a pilot reaching /// for them is in a hurry. They still go through the gate. class FlightCommandFlow { FlightCommandFlow({required this.gate, required this.onError, this.reviewBeforeTakeOff}); final SafetyGate gate; final void Function(String label, Object error) onError; /// Opens the Aircraft Status sheet offering take-off; resolves true when the /// pilot chose to continue. final Future Function(BuildContext context)? reviewBeforeTakeOff; bool _reviewed = false; /// The status sheet is offered again before the next connection's first take-off. void resetReview() => _reviewed = false; /// Take-off's own review step is done (the sheet was opened from the status chip). void markReviewed() => _reviewed = true; static String labelOf(FlightCommand c) => switch (c) { FlightCommand.takeOff => 'Take off', FlightCommand.land => 'Land', FlightCommand.cancelLanding => 'Cancel landing', FlightCommand.confirmLanding => 'Confirm landing', FlightCommand.goHome => 'Return to Home', FlightCommand.cancelGoHome => 'Cancel RTH', FlightCommand.stopMode => 'Stop', }; static bool _immediate(FlightCommand c) => c == FlightCommand.cancelLanding || c == FlightCommand.cancelGoHome || c == FlightCommand.stopMode; Future request(BuildContext context, FlightCommand command) async { GateVerdict verdict = gate.check(command); if (_immediate(command) && verdict.allowed) { await _send(command); return; } if (command == FlightCommand.takeOff && verdict.allowed && !_reviewed && reviewBeforeTakeOff != null) { _reviewed = true; final bool go = await reviewBeforeTakeOff!(context); if (!go || !context.mounted) return; verdict = gate.check(command); // the sheet may have been open a while } RthTarget target = RthTarget.originalHome; if (command == FlightCommand.goHome && verdict.allowed && gate.offersRthTarget) { final RthTarget? t = await showRthTargetDialog(context, pilotToHomeM: gate.pilotToHome!); if (t == null || !context.mounted) return; target = t; } if (!context.mounted) return; final SlideResult? r = await showSlideToConfirm(context, _spec(context, command, verdict, target)); if (r == null) return; await _send( command, precision: r.checked, rthHeight: command == FlightCommand.goHome ? r.number : null, homeToPilot: target == RthTarget.pilot, ); } Future _send(FlightCommand command, {bool precision = false, int? rthHeight, bool homeToPilot = false}) async { try { await gate.run(command, precision: precision, rthHeight: rthHeight, homeToPilot: homeToPilot); } catch (e) { onError(labelOf(command), e); } } SlideConfirmSpec _spec(BuildContext context, FlightCommand c, GateVerdict v, RthTarget target) { switch (c) { case FlightCommand.takeOff: final ProductProfile p = ProductProfileScope.of(context); final bool precision = p.connected && HudFeatures.precisionTakeoff.enabledFor(p); return SlideConfirmSpec( icon: 'takeoff', title: 'Take off', description: 'The aircraft will take off and hover at about 1.2 m. Keep people clear of the propellers.', sliderLabel: 'Slide to take off', checks: v.checks, checkbox: precision ? 'Precision Takeoff' : null, checkboxHint: precision ? 'Records the take-off spot with the downward cameras, so RTH lands back on it.' : null, ); case FlightCommand.land: return SlideConfirmSpec( icon: 'land', title: 'Land', description: 'The aircraft will descend and land where it is now. Make sure the ground below is clear and level.', sliderLabel: 'Slide to land', color: HudColors.caution, checks: v.checks, ); case FlightCommand.confirmLanding: return SlideConfirmSpec( icon: 'land', title: 'Confirm landing', description: 'The aircraft is hovering just above the ground and needs your OK to touch down. Check the spot is flat and clear.', sliderLabel: 'Slide to land', color: HudColors.caution, checks: v.checks, ); case FlightCommand.goHome: final int current = (gate.model.aircraft.goHomeHeight ?? 60).round().clamp(SafetyGate.minRthHeight, SafetyGate.maxRthHeight); return SlideConfirmSpec( icon: 'rth', title: 'Return to Home', description: target == RthTarget.pilot ? 'The home point moves to your position. The aircraft climbs to the RTH altitude, flies back to you and lands.' : 'The aircraft climbs to the RTH altitude, flies back to the home point and lands.', sliderLabel: 'Slide to return home', checks: v.checks, number: SlideNumber(label: 'RTH altitude', initial: current, min: SafetyGate.minRthHeight, max: SafetyGate.maxRthHeight), ); case FlightCommand.cancelLanding: case FlightCommand.cancelGoHome: case FlightCommand.stopMode: // Only reached when the gate refused: show why. return SlideConfirmSpec( icon: 'stop', title: labelOf(c), description: 'This cannot be done right now.', sliderLabel: labelOf(c), color: Glass.accent, checks: v.checks, ); } } }