import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../../domain/mission/drone_profile.dart'; import '../providers/active_drone_profile_provider.dart'; import '../providers/database_provider.dart'; /// Formular zum Anlegen/Bearbeiten eines Drohnenprofils (Doku 3.5/7.3). /// Vereinfacht gegenueber dem HTML-Demonstrator: normale Zahlenfelder statt /// Swipe-Gesten-Chips (SwipeValueInput) - bei elf Feldern in Gruppen ist ein /// Formular auf einem Mobilgeraet die zugaenglichere Eingabemethode. class DroneProfileEditor extends ConsumerStatefulWidget { const DroneProfileEditor({super.key, this.existing}); /// Null fuer ein neues Profil (vorbefuellt mit T1-Ranger-Standardwerten). final DroneProfile? existing; @override ConsumerState createState() => _DroneProfileEditorState(); } class _DroneProfileEditorState extends ConsumerState { final _formKey = GlobalKey(); late final TextEditingController _nameController; late final Map _fieldControllers; static const _groups = [ ( title: 'Speed (m/s)', fields: [ ('minSpeed', 'Min (stall)'), ('cruiseSpeed', 'Cruise'), ('maxSpeed', 'Max'), ], ), ( title: 'Radii (m)', fields: [ ('minTurnRadius', 'Min turn'), ('loiterRadius', 'Loiter'), ('catchRadius', 'Waypoint catch'), ], ), ( title: 'Climb / Descent (m/s)', fields: [ ('maxClimbRate', 'Max climb'), ('maxDescentRate', 'Max descent'), ], ), ( title: 'Limits', fields: [ ('maxRangeM', 'Range (m)'), ('maxEnduranceMin', 'Endurance (min)'), ('maxAltitudeM', 'Ceiling (m)'), ], ), ]; double _fieldValue(DroneProfile p, String key) => switch (key) { 'minSpeed' => p.minSpeed, 'cruiseSpeed' => p.cruiseSpeed, 'maxSpeed' => p.maxSpeed, 'minTurnRadius' => p.minTurnRadius, 'loiterRadius' => p.loiterRadius, 'catchRadius' => p.catchRadius, 'maxClimbRate' => p.maxClimbRate, 'maxDescentRate' => p.maxDescentRate, 'maxRangeM' => p.maxRangeM, 'maxEnduranceMin' => p.maxEnduranceMin, 'maxAltitudeM' => p.maxAltitudeM, _ => throw ArgumentError(key), }; @override void initState() { super.initState(); final base = widget.existing ?? DroneProfile.t1Ranger(); _nameController = TextEditingController( text: widget.existing != null ? base.name : 'New profile', ); _fieldControllers = { for (final group in _groups) for (final (key, _) in group.fields) key: TextEditingController(text: _fieldValue(base, key).toString()), }; } @override void dispose() { _nameController.dispose(); for (final c in _fieldControllers.values) { c.dispose(); } super.dispose(); } Future _save() async { if (!_formKey.currentState!.validate()) return; double v(String key) => double.parse(_fieldControllers[key]!.text); final profile = DroneProfile( id: widget.existing?.id, name: _nameController.text.trim(), minSpeed: v('minSpeed'), cruiseSpeed: v('cruiseSpeed'), maxSpeed: v('maxSpeed'), minTurnRadius: v('minTurnRadius'), loiterRadius: v('loiterRadius'), catchRadius: v('catchRadius'), maxClimbRate: v('maxClimbRate'), maxDescentRate: v('maxDescentRate'), maxRangeM: v('maxRangeM'), maxEnduranceMin: v('maxEnduranceMin'), maxAltitudeM: v('maxAltitudeM'), ); final repo = ref.read(droneProfileRepositoryProvider); if (profile.id == null) { final id = await repo.insert(profile); await ref.read(activeDroneProfileProvider.notifier).select(profile.copyWith(id: id)); } else { await repo.update(profile); await ref.read(activeDroneProfileProvider.notifier).reloadIfActive(profile.id!); } if (mounted) Navigator.of(context).pop(); } @override Widget build(BuildContext context) { return Material( color: const Color(0xEB080808), child: SafeArea( child: Column( children: [ Padding( padding: const EdgeInsets.fromLTRB(16, 12, 8, 12), child: Row( children: [ Expanded( child: Text( widget.existing == null ? 'New drone profile' : 'Edit drone profile', style: const TextStyle( color: Colors.white, fontSize: 20, fontWeight: FontWeight.w700, ), ), ), IconButton( onPressed: () => Navigator.of(context).pop(), icon: const Icon(Icons.close, color: Colors.white), ), ], ), ), const Divider(color: Colors.white24, height: 1), Expanded( child: Form( key: _formKey, child: ListView( padding: const EdgeInsets.all(16), children: [ TextFormField( controller: _nameController, style: const TextStyle(color: Colors.white), decoration: const InputDecoration(labelText: 'Name'), validator: (v) => (v == null || v.trim().isEmpty) ? 'Required' : null, ), for (final group in _groups) ...[ const SizedBox(height: 20), Text( group.title, style: const TextStyle( color: Colors.white70, fontWeight: FontWeight.w700, fontSize: 13, ), ), const SizedBox(height: 8), Row( children: [ for (final (key, label) in group.fields) Expanded( child: Padding( padding: const EdgeInsets.only(right: 8), child: TextFormField( controller: _fieldControllers[key], style: const TextStyle(color: Colors.white), keyboardType: const TextInputType.numberWithOptions( decimal: true, ), decoration: InputDecoration(labelText: label), validator: (v) => double.tryParse(v ?? '') == null ? 'Number' : null, ), ), ), ], ), ], const SizedBox(height: 24), FilledButton( onPressed: _save, child: const Text('Save'), ), ], ), ), ), ], ), ), ); } }