import 'dart:math' as math; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../../app_mode/app_mode_cubit.dart'; import '../dmc_colors.dart'; import '../providers/flight_mode_provider.dart'; import 'flight_mode_pill.dart'; import 'fly_map_controls.dart'; import 'header_wind_pill.dart'; import 'map_search_controls.dart'; /// Persistente Kopfleiste zum Umschalten zwischen Plan- und Fly-Modus /// (HTML-Demonstrator: #topBarWrap/#editModeBtn/#flyModeBtn). Die Leiste /// faerbt sich in der Farbe des aktiven Modus; der jeweils andere Modus wird /// als solider Button am Rand angeboten, um in ihn zu wechseln. /// /// Traegt ausserhalb des Fly-Modus zusaetzlich die Wind-Pille, Ortssuche und /// die Home-/Fit-Buttons in derselben Pille (HTML-Demonstrator: #headerWindPill/ /// #searchWrap/#homeBtn/#fitBtn sitzen alle in #topBarMiddle, nicht in einer /// separaten Leiste) - dafuer lebt der MapController im mapControllerProvider /// statt als PlanScreen-privates Feld, siehe dort. /// /// Im Fly-Modus traegt sie stattdessen die Wind-Pille (Doku 7.2: "Wind- /// Anzeige nach Hoehe interpoliert), die Flugmodus-Auswahl (Doku 3.11, /// HTML-Demonstrator: #flightModePill) sowie Fit-/Drohnen-Zoom-Buttons /// (FlyMapControls) - Ortssuche/Home entfallen dort, da im Fly-Modus keine /// Missionsbearbeitung stattfindet und "Home" (erster Wegpunkt) waehrend /// des Flugs kein sinnvoller Bezugspunkt mehr ist. class TopModeBar extends ConsumerWidget { const TopModeBar({super.key}); @override Widget build(BuildContext context, WidgetRef ref) { final isFly = context.select((AppModeCubit c) => c.state.mode == AppMode.fly); final activeTint = isFly ? DmcColors.flyModeColor : DmcColors.planModeColor; // Kein SafeArea: das wendet links/rechts die jeweils tatsaechlichen // (oft asymmetrischen) Insets an, wodurch die Leiste sichtbar von der // Bildschirmmitte abweicht, sobald eine Seite einen groesseren // Aussparungsbereich meldet als die andere. Stattdessen derselbe // symmetrische Randabstand wie bei den Drehraedern (PlanScreen: // safeEdgeInset) - dadurch bleibt die per FractionallySizedBox // zentrierte Leiste auch bei asymmetrischen Insets echt mittig. final topInset = MediaQuery.of(context).padding.top; final safeEdgeInset = math.max( MediaQuery.of(context).padding.left, MediaQuery.of(context).padding.right, ); return Padding( padding: EdgeInsets.only( top: topInset, left: safeEdgeInset, right: safeEdgeInset, ), child: FractionallySizedBox( widthFactor: 0.77, child: Container( padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 5), decoration: BoxDecoration( color: activeTint.withValues(alpha: 0.55), borderRadius: const BorderRadius.only( bottomLeft: Radius.circular(26), bottomRight: Radius.circular(26), ), ), child: Row( children: [ _ModeButton( label: 'Plan', isActive: !isFly, inactiveColor: DmcColors.planModeColor, cornerRadius: const BorderRadius.only( bottomLeft: Radius.circular(26), ), onTap: () => context.read().toPlan(), ), if (isFly) ...[ const SizedBox(width: 8), const HeaderWindPill(), const SizedBox(width: 8), const Expanded(child: FlightModePill()), const SizedBox(width: 8), const FlyMapControls(), const SizedBox(width: 8), ] else ...[ const SizedBox(width: 8), const HeaderWindPill(), const SizedBox(width: 8), const Expanded(child: MapSearchControls()), const SizedBox(width: 12), ], _ModeButton( label: 'Fly', isActive: isFly, inactiveColor: DmcColors.flyModeColor, cornerRadius: const BorderRadius.only( bottomRight: Radius.circular(26), ), onTap: () { context.read().toFly(); // Eintritt in den Fly-Modus setzt immer auf Waypoint // zurueck (Doku 3.11). ref.read(flightModeProvider.notifier).reset(); // Kein Auto-Connect mehr beim Wechsel in den Fly-Modus // (bewusst entfernt, um das Debuggen auf echter Hardware // zu vereinfachen - Verbindungsaufbau laeuft jetzt // ausschliesslich ueber den expliziten "Connect"-Knopf in // den Settings). Die Verbindung selbst bleibt ohnehin ueber // Moduswechsel hinweg bestehen, siehe // flightControllerLinkProvider. }, ), ], ), ), ), ); } } class _ModeButton extends StatelessWidget { const _ModeButton({ required this.label, required this.isActive, required this.inactiveColor, required this.cornerRadius, required this.onTap, }); final String label; final bool isActive; final Color inactiveColor; final BorderRadius cornerRadius; final VoidCallback onTap; @override Widget build(BuildContext context) { return GestureDetector( onTap: isActive ? null : onTap, child: Container( padding: EdgeInsets.symmetric( horizontal: 24, vertical: isActive ? 6 : 11, ), decoration: BoxDecoration( color: isActive ? null : inactiveColor.withValues(alpha: 0.85), borderRadius: isActive ? null : cornerRadius, ), child: Text( label, style: const TextStyle( fontSize: 20, fontWeight: FontWeight.w800, color: Color(0xFF141414), ), ), ), ); } }