import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:latlong2/latlong.dart'; import '../providers/current_mission_provider.dart'; import '../providers/geocoding_provider.dart'; import '../providers/map_controller_provider.dart'; import '../providers/mission_meta_provider.dart'; import '../providers/wind_provider.dart'; /// Ortssuche (Nominatim) + Home-/Fit-Buttons, eingebettet als Mittelteil /// der Plan/Fly-Kopfleiste (HTML-Demonstrator: #searchWrap/#homeBtn/ /// #fitBtn sitzen alle in derselben Leiste wie die Modus-Buttons). /// /// Vollstaendig selbststaendig (kein Callback-Durchreichen von aussen): /// greift direkt auf den geteilten mapControllerProvider und /// currentMissionProvider zu, da diese Aktionen konzeptionell zur /// Kartensteuerung gehoeren, nicht zum Elternwidget. class MapSearchControls extends ConsumerStatefulWidget { const MapSearchControls({super.key}); @override ConsumerState createState() => _MapSearchControlsState(); } class _MapSearchControlsState extends ConsumerState { final _controller = TextEditingController(); @override void dispose() { _controller.dispose(); super.dispose(); } Future _submitSearch() async { final query = _controller.text.trim(); FocusScope.of(context).unfocus(); if (query.isEmpty) return; final messenger = ScaffoldMessenger.of(context); try { final result = await ref.read(nominatimServiceProvider).search(query); if (result == null) { messenger.showSnackBar(const SnackBar(content: Text('Location not found'))); return; } ref.read(mapControllerProvider).move(LatLng(result.lat, result.lon), 15); // Eine Ortssuche beginnt immer eine neue, nach dem Ort benannte // Mission statt nur die Karte zu verschieben (HTML-Demonstrator: // setMissionFromPlace(), aufgerufen aus doSearch()) - vorherige // Aenderungen werden dabei zuerst autosaved, gehen also nicht verloren. await ref .read(currentMissionMetaProvider.notifier) .startNewFromPlace(result.label); await ref.read(headerWindProvider.notifier).refresh(); } catch (_) { messenger.showSnackBar(const SnackBar(content: Text('Search failed'))); } } /// Zentriert auf den ersten Wegpunkt (HTML-Demonstrator: homeBtn). void _onHomePressed() { final waypoints = ref.read(currentMissionProvider); if (waypoints.isEmpty) { ScaffoldMessenger.of(context) .showSnackBar(const SnackBar(content: Text('No waypoints yet'))); return; } final home = waypoints.first; ref.read(mapControllerProvider).move(LatLng(home.lat, home.lon), 16); } /// Zoomt auf die gesamte Mission (HTML-Demonstrator: fitToWaypoints()). void _onFitPressed() { final waypoints = ref.read(currentMissionProvider); if (waypoints.isEmpty) { ScaffoldMessenger.of(context) .showSnackBar(const SnackBar(content: Text('No waypoints to fit'))); return; } fitMapToWaypoints(ref, waypoints); } @override Widget build(BuildContext context) { return Row( children: [ Expanded( child: Container( height: 34, padding: const EdgeInsets.only(left: 12, right: 4), decoration: BoxDecoration( color: Colors.black.withValues(alpha: 0.55), borderRadius: BorderRadius.circular(17), ), child: Row( children: [ Expanded( child: TextField( controller: _controller, onSubmitted: (_) => _submitSearch(), textInputAction: TextInputAction.search, style: const TextStyle(color: Colors.white, fontSize: 13), decoration: InputDecoration( isDense: true, border: InputBorder.none, hintText: 'Search location', hintStyle: TextStyle( color: Colors.white.withValues(alpha: 0.55), ), ), ), ), IconButton( onPressed: _submitSearch, icon: const Icon(Icons.search, color: Colors.white, size: 16), padding: EdgeInsets.zero, constraints: const BoxConstraints(minWidth: 26, minHeight: 26), ), ], ), ), ), const SizedBox(width: 8), _NavIconButton(icon: Icons.home, onTap: _onHomePressed), const SizedBox(width: 8), _NavIconButton(icon: Icons.crop_free, onTap: _onFitPressed), ], ); } } class _NavIconButton extends StatelessWidget { const _NavIconButton({required this.icon, required this.onTap}); final IconData icon; final VoidCallback onTap; @override Widget build(BuildContext context) { return GestureDetector( onTap: onTap, child: Container( width: 34, height: 34, decoration: BoxDecoration( shape: BoxShape.circle, color: Colors.black.withValues(alpha: 0.55), ), child: Icon(icon, color: Colors.white, size: 16), ), ); } }