import 'package:flutter/material.dart'; import '../../../../dji_service.dart'; import '../../../../fly/sensing_distances.dart'; import '../../hud_kit.dart'; import '../camera_menu/camera_rows.dart'; /// Settings → Vision → obstacle distances (plan P8): where the aircraft brakes /// and how far it looks, per direction the aircraft reports. Read once when the /// tab opens; a step writes at once and rolls back if the aircraft refuses. class SensingDistanceRows extends StatefulWidget { const SensingDistanceRows({super.key, required this.dji, required this.connected, required this.onError}); final DjiService dji; final bool connected; final void Function(String label, Object error) onError; @override State createState() => _SensingDistanceRowsState(); } class _SensingDistanceRowsState extends State { SensingDistances? _d; bool _loading = false; final Set<(SensingKind, SensingDirection)> _pending = <(SensingKind, SensingDirection)>{}; @override void initState() { super.initState(); _load(); } @override void didUpdateWidget(SensingDistanceRows old) { super.didUpdateWidget(old); if (widget.connected && !old.connected) _load(); } Future _load() async { if (!widget.connected || _loading) return; _loading = true; try { final SensingDistances d = SensingDistances.fromBridge(await widget.dji.getSensingDistances()); if (mounted) setState(() => _d = d); } catch (_) { if (mounted) setState(() => _d = const SensingDistances()); } finally { _loading = false; } } Future _step(SensingKind k, SensingDirection dir, int steps) async { final SensingDistances? before = _d; final double? v = before?.value(k, dir); if (before == null || v == null) return; final double next = SensingDistances.stepped(k, dir, v, steps); if (next == v) return; setState(() { _d = before.withValue(k, dir, next); _pending.add((k, dir)); }); try { await (k == SensingKind.avoidance ? widget.dji.setAvoidanceDistance(dir.sdk, next) : widget.dji.setMaxPerceptionDistance(dir.sdk, next)); } catch (e) { if (mounted) setState(() => _d = before); widget.onError(k == SensingKind.avoidance ? 'Braking distance' : 'Sensing range', e); } finally { if (mounted) setState(() => _pending.remove((k, dir))); } } @override Widget build(BuildContext context) { final SensingDistances? d = _d; // Nothing reported (or not connected): the rows stay out of the way. if (d == null || d.isEmpty) return const SizedBox.shrink(); Widget row(SensingKind k, SensingDirection dir) { final double? v = d.value(k, dir); if (v == null) return const SizedBox.shrink(); final ({double min, double max, double step}) r = SensingDistances.range(k, dir); final String what = k == SensingKind.avoidance ? 'braking distance' : 'sensing range'; return StepperTile( key: ValueKey('row-sensing-${k.name}-${dir.name}'), id: 'sensing-${k.name}-${dir.name}', label: '${dir.label} $what', hint: k == SensingKind.avoidance ? 'The aircraft brakes this far from an obstacle' : 'Obstacles closer than this are reported', shown: '${v.toStringAsFixed(r.step < 1 ? 1 : 0)} m', canDown: v > r.min, canUp: v < r.max, pending: _pending.contains((k, dir)), onStep: (int s) => _step(k, dir, s), ); } return Column(mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.stretch, children: [ Padding( padding: const EdgeInsets.fromLTRB(16, 10, 16, 2), child: Text('OBSTACLE DISTANCES', style: HudText.label), ), for (final SensingKind k in SensingKind.values) for (final SensingDirection dir in SensingDirection.values) row(k, dir), ]); } }