import 'dart:math' as math; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../../domain/mission/mission_warnings.dart'; import '../providers/active_drone_profile_provider.dart'; import '../providers/altitude_profile_provider.dart'; import '../providers/mission_meta_provider.dart'; import 'missions_drones_panel.dart'; import 'mountain_peak_icon.dart'; /// Statuszeile am unteren Bildschirmrand (HTML-Demonstrator: #bottomBar/ /// #statsRow). Details-Pille bleibt mittig und oeffnet die Wegpunktliste; /// Mission-Chip links und Drohnen-Chip rechts oeffnen das Verwaltungsmenue /// mit dem passenden Starttab (HTML-Demonstrator: #missionNameBtn/ /// #droneNameBtn). Warnungs- und Settings-Knopf sitzen neben dem /// Drohnen-Chip rechts aussen. Der Hoehenprofil-Toggle (Bergkuppen-Icon) /// sitzt links der Details-Pille. /// Der Send-Button ist bewusst nicht Teil dieses Schritts - die dafuer /// noetige Upload-Anbindung existiert noch nicht. class BottomStatsBar extends ConsumerWidget { const BottomStatsBar({ super.key, required this.waypointCount, required this.totalDistanceM, required this.totalDurationSec, required this.onTap, required this.onSettingsTap, this.warnings = const [], this.onWarningsTap, }); final int waypointCount; final double totalDistanceM; final double totalDurationSec; final VoidCallback onTap; /// Oeffnet das Settings-Menue (runder Zahnrad-Knopf rechts der /// Details-Pille). final VoidCallback onSettingsTap; /// Aktive Warnungen zur Mission (Doku 3.10) - der Button erscheint nur, /// solange diese Liste nicht leer ist (HTML-Demonstrator: #warningsBtn, /// per default versteckt). final List warnings; final VoidCallback? onWarningsTap; @override Widget build(BuildContext context, WidgetRef ref) { final missionName = ref.watch(currentMissionMetaProvider).name; final droneName = ref.watch(activeDroneProfileProvider).name; final isAltitudeProfileExpanded = ref.watch(altitudeProfileExpandedProvider); // Gespiegelter Randabstand (wie in PlanScreen fuer die Drehraeder, Doku: // "Symmetrischer Randabstand auf beiden Seiten"): die groessere der // beiden echten Seiten-Insets wird auf BEIDE Seiten angewendet, statt // SafeAreas Standardverhalten (jede Seite nur ihren eigenen, ggf. 0) // zu uebernehmen - sonst waere z.B. eine Frontkamera-Aussparung im // Querformat nur auf einer Seite der Fusszeile sichtbar beruecksichtigt, // je nachdem wie herum das Geraet gehalten wird. final safeEdgeInset = math.max( MediaQuery.of(context).padding.left, MediaQuery.of(context).padding.right, ); // Row statt Stack: ein Align/Center ohne width-/heightFactor nimmt sich // immer den gesamten verfuegbaren Platz seines Elternwidgets (auch wenn // sein Kind viel kleiner ist) - in einem Stack ohne begrenzende // Positioned-Kinder blaeht das dessen eigene Groesse unkontrolliert auf // und verschiebt die Details-Pille aus ihrer sichtbaren Position. // Zwei gleich grosse Expanded-Bereiche links/rechts der Pille halten sie // dagegen zuverlaessig zentriert. return SafeArea( top: false, left: false, right: false, child: Padding( padding: EdgeInsets.fromLTRB( 12 + safeEdgeInset, 10, 12 + safeEdgeInset, 10, ), child: Row( children: [ // Nur noch die Mission-Pille links - sie darf die gesamte // verfuegbare Breite dieser Seite nutzen (Align loosent die // Constraints statt sie wie Expanded zu erzwingen, das // begrenzt auf die verfuegbare Breite, ohne einen kuenstlich // kleinen Fixwert vorzugeben). Expanded( child: Align( alignment: Alignment.centerLeft, child: _NameChip( label: 'Mission: $missionName', color: Colors.white.withValues(alpha: 0.12), textColor: Colors.white, onTap: () => Navigator.of(context).push(MaterialPageRoute( builder: (_) => const MissionsDronesPanel.missions(), )), ), ), ), _AltitudeProfileToggleButton( isExpanded: isAltitudeProfileExpanded, onTap: () => ref .read(altitudeProfileExpandedProvider.notifier) .toggle(), ), const SizedBox(width: 6), _detailsPill(), // Drohnen-Pille + Warnungs-/Settings-Knopf rechts. Die Pille // sitzt in Flexible, damit sie bei langen Namen zu Gunsten der // beiden festgroessen Rundknoepfe schrumpft/ellipsiert, statt // sie aus dem sichtbaren Bereich zu draengen. Expanded( child: Align( alignment: Alignment.centerRight, child: Row( mainAxisSize: MainAxisSize.min, children: [ Flexible( child: _NameChip( label: 'Drone: $droneName', color: const Color(0xFF4D9DFF).withValues(alpha: 0.18), textColor: const Color(0xFFCFE2FF), onTap: () => Navigator.of(context).push(MaterialPageRoute( builder: (_) => const MissionsDronesPanel.drones(), )), ), ), const SizedBox(width: 6), if (warnings.isNotEmpty) ...[ _WarningButton(onTap: onWarningsTap), const SizedBox(width: 6), ], _SettingsButton(onTap: onSettingsTap), ], ), ), ), ], ), ), ); } Widget _detailsPill() { return GestureDetector( onTap: onTap, child: Container( padding: const EdgeInsets.symmetric(horizontal: 18, vertical: 9), decoration: BoxDecoration( color: Colors.white.withValues(alpha: 0.12), borderRadius: BorderRadius.circular(20), ), child: Row( mainAxisSize: MainAxisSize.min, children: [ const Text( 'Details:', style: TextStyle( color: Colors.white70, fontWeight: FontWeight.w600, fontSize: 14, ), ), const SizedBox(width: 10), const Icon(Icons.flag, size: 14, color: Colors.white), const SizedBox(width: 4), Text('$waypointCount WP', style: const TextStyle(color: Colors.white, fontSize: 14)), const SizedBox(width: 14), const Icon(Icons.straighten, size: 14, color: Colors.white), const SizedBox(width: 4), Text('${totalDistanceM.round()} m', style: const TextStyle(color: Colors.white, fontSize: 14)), const SizedBox(width: 14), const Icon(Icons.access_time, size: 14, color: Colors.white), const SizedBox(width: 4), Text(_fmtDuration(totalDurationSec), style: const TextStyle(color: Colors.white, fontSize: 14)), ], ), ), ); } String _fmtDuration(double seconds) { final total = seconds.round(); final minutes = total ~/ 60; final secs = total % 60; return '$minutes:${secs.toString().padLeft(2, '0')}'; } } /// Runder Knopf links der Details-Pille zum Ein-/Ausklappen des /// Mini-Hoehenprofils. Zeigt eine stilisierte Bergkuppe: ausgeklappt steht /// sie kopfueber (Zacken zeigen nach unten - signalisiert "einklappen"), /// eingeklappt (Standard, um moeglichst viel Kartenflaeche frei zu lassen) /// richtig herum (Zacken zeigen nach oben - signalisiert "ausklappen"). class _AltitudeProfileToggleButton extends StatelessWidget { const _AltitudeProfileToggleButton({ required this.isExpanded, required this.onTap, }); final bool isExpanded; 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.white.withValues(alpha: 0.12), ), child: Center( child: MountainPeakIcon( size: 16, color: Colors.white, upsideDown: isExpanded, ), ), ), ); } } /// Kleiner, roter Rundknopf fuer aktive Warnungen (Doku 3.10, HTML- /// Demonstrator: #warningsBtn) - oeffnet die Warnungsliste. class _WarningButton extends StatelessWidget { const _WarningButton({required this.onTap}); final VoidCallback? onTap; @override Widget build(BuildContext context) { return GestureDetector( onTap: onTap, child: Container( width: 34, height: 34, decoration: BoxDecoration( shape: BoxShape.circle, color: const Color(0xFFE2574A).withValues(alpha: 0.18), border: Border.all(color: const Color(0xFFE2574A).withValues(alpha: 0.5)), ), child: const Icon(Icons.warning_amber_rounded, color: Color(0xFFFFB3A8), size: 18), ), ); } } /// Runder Zahnrad-Knopf rechts der Details-Pille - oeffnet das /// Settings-Menue (SettingsScreen). class _SettingsButton extends StatelessWidget { const _SettingsButton({required this.onTap}); 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.white.withValues(alpha: 0.12), ), child: const Icon(Icons.settings, color: Colors.white, size: 18), ), ); } } class _NameChip extends StatelessWidget { const _NameChip({ required this.label, required this.color, required this.textColor, required this.onTap, }); final String label; final Color color; final Color textColor; final VoidCallback onTap; @override Widget build(BuildContext context) { // Kein fester maxWidth mehr - die Pille darf die verfuegbare Breite // ihres Elternwidgets nutzen (Align/Flexible am Aufrufort begrenzen sie // bereits auf die dort tatsaechlich verfuegbare Breite). return GestureDetector( onTap: onTap, child: Container( padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 7), decoration: BoxDecoration( color: color, borderRadius: BorderRadius.circular(14), ), child: Text( label, overflow: TextOverflow.ellipsis, maxLines: 1, style: TextStyle( color: textColor, fontWeight: FontWeight.w600, fontSize: 12, ), ), ), ); } }