import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../../services/wind/wind_service.dart'; import '../providers/wind_provider.dart'; /// Zeigt die Rohdaten hinter dem interpolierten Wind an einem Ort: /// gemessene Werte je fester Nabenhoehe (inkl. Boeen) und je Druckflaeche, /// sowie das daraus interpolierte Ergebnis auf 120 m AGL (Doku 3.8/4.9: /// Boeen nur hier, nicht in der Wegpunktanzeige). Fullscreen-Route wie /// WaypointListPanel (HTML-Demonstrator: #windValidatePanel, ebenfalls /// .panel). class WindValidatePanel extends ConsumerStatefulWidget { const WindValidatePanel({super.key, required this.lat, required this.lon}); final double lat; final double lon; @override ConsumerState createState() => _WindValidatePanelState(); } class _WindValidatePanelState extends ConsumerState { late final Future _future; @override void initState() { super.initState(); _future = ref .read(windServiceProvider) .fetchValidation(widget.lat, widget.lon); } static const _labelStyle = TextStyle(color: Colors.white, fontSize: 14, height: 1.6); static const _errorStyle = TextStyle(color: Color(0xFFFF8A7A), fontSize: 14, height: 1.6); static const _excludedStyle = TextStyle( color: Colors.white38, fontSize: 14, height: 1.6, decoration: TextDecoration.lineThrough, ); @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: [ const Expanded( child: Text( 'Wind Validation', style: 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: FutureBuilder( future: _future, builder: (context, snapshot) { if (!snapshot.hasData) { return const Center( child: CircularProgressIndicator(color: Color(0xFF4D9DFF)), ); } return _body(snapshot.data!); }, ), ), ], ), ), ); } Widget _body(WindValidationResult result) { return ListView( padding: const EdgeInsets.all(16), children: [ Text( 'Location: ${result.lat.toStringAsFixed(5)}, ${result.lon.toStringAsFixed(5)} ' '· ground elevation ${result.elevationM.round()} m ASL', style: _labelStyle, ), const SizedBox(height: 12), const Text('Fixed hub heights', style: TextStyle(color: Colors.white, fontWeight: FontWeight.w700, fontSize: 15)), const SizedBox(height: 4), if (result.fixedRequestFailed) const Text('Request failed', style: _errorStyle) else ...[ for (final r in result.fixedReadings) Text( '${r.heightAglM} m AGL: ' '${r.speedMs != null ? '${r.speedMs!.toStringAsFixed(1)} m/s' : 'n/a'}' '${r.dirFromDeg != null ? ', ${r.dirFromDeg!.round()}°' : ''}', style: _labelStyle, ), Text( 'Gusts (10 m AGL): ${result.gustMs != null ? '${result.gustMs!.toStringAsFixed(1)} m/s' : 'n/a'}', style: _labelStyle, ), ], const SizedBox(height: 16), const Text('Pressure levels', style: TextStyle(color: Colors.white, fontWeight: FontWeight.w700, fontSize: 15)), const Text( 'discarded if below ground or incomplete', style: TextStyle(color: Colors.white38, fontSize: 12), ), const SizedBox(height: 4), if (result.pressureRequestFailed) const Text('Request failed', style: _errorStyle) else for (final r in result.pressureReadings) Text( '${r.pressureHpa} hPa: ' '${r.heightAmslM != null ? '${r.heightAmslM!.round()} m ASL' : '?'}, ' '${r.speedMs != null ? '${r.speedMs!.toStringAsFixed(1)} m/s' : 'n/a'}' '${r.excluded ? (r.heightAmslM != null && r.heightAmslM! < result.elevationM ? ' (below ground, excluded)' : ' (incomplete, excluded)') : ''}', style: r.excluded ? _excludedStyle : _labelStyle, ), const SizedBox(height: 16), Container( padding: const EdgeInsets.only(top: 12), decoration: const BoxDecoration( border: Border(top: BorderSide(color: Colors.white24)), ), child: Text( 'Interpolated & referenced at 120 m AGL ' '(= ${(result.elevationM + 120).round()} m ASL, using pressure levels + fixed heights combined): ' '${result.interpolatedAt120 != null ? '${result.interpolatedAt120!.speedMs.toStringAsFixed(1)} m/s, ${result.interpolatedAt120!.dirFromDeg.round()}°' : 'n/a (missing data at boundary level)'}', style: const TextStyle(color: Colors.white, fontWeight: FontWeight.w600, fontSize: 14, height: 1.5), ), ), ], ); } }