import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../../domain/waypoint/flat_waypoint_list.dart'; import '../providers/current_mission_provider.dart'; import 'waypoint_chip.dart'; /// Vollbild-Wegpunktliste (HTML-Demonstrator: #wpListPanel, List-Tab). /// Nur eine Zeile gleichzeitig editierbar (rowEditIdx); Antippen einer /// Zeile ausserhalb des Edit-Modus zentriert die Karte auf diesen /// Wegpunkt und schliesst die Liste (Doku 3.6). /// /// Die Altitude/Speed-Vollbild-Charts (weitere Tabs im Demonstrator) sind /// bewusst nicht Teil dieses Schritts. class WaypointListPanel extends ConsumerStatefulWidget { const WaypointListPanel({ super.key, required this.activeIndex, required this.vertexBad, required this.altMin, required this.altMax, required this.speedMin, required this.speedMax, required this.onClose, required this.onCenterOnWaypoint, required this.onDelete, required this.onAltitudeChanged, required this.onSpeedChanged, required this.onCatchRadiusChanged, required this.onActionChanged, }); final int activeIndex; final List vertexBad; final double altMin; final double altMax; final double speedMin; final double speedMax; final VoidCallback onClose; final void Function(int index) onCenterOnWaypoint; final void Function(int index) onDelete; final void Function(int index, double value) onAltitudeChanged; final void Function(int index, double value) onSpeedChanged; final void Function(int index, double value) onCatchRadiusChanged; final void Function(int index, WaypointAction action) onActionChanged; @override ConsumerState createState() => _WaypointListPanelState(); } class _WaypointListPanelState extends ConsumerState { int _rowEditIndex = -1; static const _headerStyle = TextStyle( color: Colors.white60, fontSize: 12, fontWeight: FontWeight.w700, letterSpacing: 0.5, ); @override Widget build(BuildContext context) { final waypoints = ref.watch(currentMissionProvider); return Material( color: const Color(0xEB080808), child: SafeArea( child: Column( children: [ Padding( padding: const EdgeInsets.fromLTRB(16, 12, 8, 12), child: Row( children: [ const Expanded( child: Text( 'Waypoints', style: TextStyle( color: Colors.white, fontSize: 20, fontWeight: FontWeight.w700, ), ), ), IconButton( onPressed: widget.onClose, icon: const Icon(Icons.close, color: Colors.white), ), ], ), ), if (waypoints.isNotEmpty) Padding( padding: const EdgeInsets.symmetric(horizontal: 16), child: Row( children: const [ SizedBox(width: 34), Expanded(child: Text('ALTITUDE', style: _headerStyle)), Expanded(child: Text('SPEED', style: _headerStyle)), Expanded(child: Text('EXECUTE', style: _headerStyle)), Expanded(child: Text('CATCH', style: _headerStyle)), SizedBox(width: 76), ], ), ), const Divider(color: Colors.white24, height: 16), Expanded( child: waypoints.isEmpty ? const Center( child: Text( 'No waypoints yet', style: TextStyle(color: Colors.white60, fontSize: 15), ), ) : ListView.builder( padding: const EdgeInsets.symmetric(horizontal: 16), itemCount: waypoints.length, itemBuilder: (context, index) => _row(waypoints[index], index), ), ), ], ), ), ); } Widget _row(Waypoint wp, int index) { final isEditing = _rowEditIndex == index; final isActive = widget.activeIndex == index; final isBad = index < widget.vertexBad.length && widget.vertexBad[index]; return GestureDetector( onTap: () { if (_rowEditIndex >= 0) return; widget.onCenterOnWaypoint(index); }, child: Container( margin: const EdgeInsets.symmetric(vertical: 4), padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 8), decoration: BoxDecoration( color: isActive ? const Color(0xFF4D9DFF).withValues(alpha: 0.28) : Colors.white.withValues(alpha: 0.04), border: isActive ? Border.all(color: const Color(0xFF4D9DFF)) : null, borderRadius: BorderRadius.circular(10), ), child: Row( children: [ CircleAvatar( radius: 13, backgroundColor: isBad ? const Color(0xFFE2574A).withValues(alpha: 0.5) : Colors.white24, child: Text( '${index + 1}', style: const TextStyle(color: Colors.white, fontSize: 12), ), ), const SizedBox(width: 8), Expanded( child: isEditing ? WaypointChip( value: wp.altitudeM, unit: ' m', min: widget.altMin, max: widget.altMax, onChanged: (v) => widget.onAltitudeChanged(index, v), ) : Text('${wp.altitudeM.round()} m', style: const TextStyle(color: Colors.white)), ), Expanded( child: isEditing ? WaypointChip( value: wp.speedMs, unit: ' m/s', min: widget.speedMin, max: widget.speedMax, onChanged: (v) => widget.onSpeedChanged(index, v), ) : Text('${wp.speedMs.round()} m/s', style: const TextStyle(color: Colors.white)), ), Expanded( child: isEditing ? DropdownButton( value: wp.action, isDense: true, dropdownColor: const Color(0xFF1A1A1A), style: const TextStyle(color: Colors.white, fontSize: 13), items: const [ DropdownMenuItem( value: WaypointAction.none, child: Text('—'), ), DropdownMenuItem( value: WaypointAction.loiter, child: Text('Loiter'), ), DropdownMenuItem( value: WaypointAction.landing, child: Text('Landing'), ), ], onChanged: (action) { if (action != null) widget.onActionChanged(index, action); }, ) : Text(_actionLabel(wp.action), style: const TextStyle(color: Colors.white)), ), Expanded( child: isEditing ? WaypointChip( value: wp.catchRadiusM, unit: ' m', min: 5, max: 500, onChanged: (v) => widget.onCatchRadiusChanged(index, v), ) : Text('${wp.catchRadiusM.round()} m', style: const TextStyle(color: Colors.white)), ), IconButton( onPressed: () => setState( () => _rowEditIndex = isEditing ? -1 : index, ), icon: Icon( isEditing ? Icons.check : Icons.edit, color: Colors.white70, size: 18, ), ), IconButton( onPressed: () { widget.onDelete(index); if (_rowEditIndex == index) setState(() => _rowEditIndex = -1); }, icon: const Icon(Icons.delete_outline, color: Color(0xFFE2574A), size: 18), ), ], ), ), ); } String _actionLabel(WaypointAction action) => switch (action) { WaypointAction.loiter => 'Loiter', WaypointAction.landing => 'Landing', _ => '—', }; }