// lib/widgets/coordinate_panel.dart import 'dart:math'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:intl/intl.dart'; import '../services/gnss/gnss_service.dart'; class CoordinatePanel extends StatelessWidget { final RxDouble eovY; final RxDouble eovX; final RxDouble horError; final RxDouble vertError; final RxDouble? altitudeMsl; final RxDouble? geoidSeparation; final RxBool? ntripConnected; final RxInt? ntripBytes; final RxInt? ntripPackets; final RxInt? ggaPackets; const CoordinatePanel({ super.key, required this.eovY, required this.eovX, required this.horError, required this.vertError, this.altitudeMsl, this.geoidSeparation, this.ntripConnected, this.ntripBytes, this.ntripPackets, this.ggaPackets, }); /// Gyors factory — MapSurveyController mezőiből. factory CoordinatePanel.fromController(dynamic ctrl) { final y = RxDouble(0.0); final x = RxDouble(0.0); // Figyeli az eov változást és frissíti Y/X-et ctrl.eov.listen((eov) { y.value = (eov.Y as num).toDouble(); x.value = (eov.X as num).toDouble(); }); return CoordinatePanel( eovY: y, eovX: x, horError: ctrl.gpsLatitudeError as RxDouble, vertError: ctrl.gpsAltitudeError as RxDouble, altitudeMsl: ctrl.gpsAltitude as RxDouble, geoidSeparation: ctrl.gpsGeoidSeparation as RxDouble, ntripConnected: ctrl.ntripIsConnected as RxBool, ntripBytes: ctrl.ntripReceivedData as RxInt, ntripPackets: ctrl.ntripDataPacketNumbers as RxInt, ggaPackets: ctrl.ggaSenDataPacketNumber as RxInt, ); } static final _fmtEov = NumberFormat('###,###,##0.000', 'hu-HU'); static final _fmtEovZ = NumberFormat('###,##0.000', 'hu-HU'); @override Widget build(BuildContext context) { final gnss = GnssService.to; return Obx(() { final quality = gnss.gpsQuality.value; final hasData = quality > 0; final color = _qualityColor(quality); return AnimatedContainer( duration: const Duration(milliseconds: 300), decoration: BoxDecoration( color: Colors.black.withOpacity(0.78), borderRadius: BorderRadius.circular(10), border: Border.all(color: color.withOpacity(0.6), width: 1.5), ), child: Column( mainAxisSize: MainAxisSize.min, children: [ // ── Fejléc ────────────────────────────────────────── Padding( padding: const EdgeInsets.fromLTRB(10, 8, 10, 6), child: Row(children: [ // Fix chip Container( padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 3), decoration: BoxDecoration( color: color.withOpacity(0.2), borderRadius: BorderRadius.circular(5), border: Border.all(color: color.withOpacity(0.5)), ), child: Row(mainAxisSize: MainAxisSize.min, children: [ Container( width: 7, height: 7, decoration: BoxDecoration(color: color, shape: BoxShape.circle), ), const SizedBox(width: 5), Text(_qualityLabel(quality), style: TextStyle( color: color, fontSize: 11, fontWeight: FontWeight.w700, )), ]), ), const Spacer(), // Műholdak if (hasData) ...[ Icon(Icons.satellite_alt, size: 11, color: Colors.white.withOpacity(0.5)), const SizedBox(width: 3), Text('${gnss.satelliteCount.value}', style: TextStyle( color: Colors.white.withOpacity(0.55), fontSize: 11)), const SizedBox(width: 10), ], // UTC Icon(Icons.access_time, size: 11, color: Colors.white.withOpacity(0.4)), const SizedBox(width: 3), Text( gnss.utcFix.value.isNotEmpty ? '${gnss.utcFix.value} UTC' : '--:--:-- UTC', style: TextStyle( color: Colors.white.withOpacity(0.45), fontSize: 11)), ]), ), if (hasData) ...[ _Div(), // ── EOV koordináták ────────────────────────────── Padding( padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4), child: Obx(() { final alt = altitudeMsl?.value ?? gnss.altitude.value; final geoSep = geoidSeparation?.value ?? gnss.geoidSeparation.value; return Column(children: [ _Row('EOV Y', _fmtEov.format(eovY.value), 'm', bold: true), const SizedBox(height: 3), _Row('EOV X', _fmtEov.format(eovX.value), 'm', bold: true), const SizedBox(height: 6), _Row('H (MSL)', _fmtEovZ.format(alt), 'm'), const SizedBox(height: 2), _Row('h (WGS84)', _fmtEovZ.format(alt + geoSep), 'm', dimmed: true), ]); }), ), _Div(), // ── Pontosság ──────────────────────────────────── Padding( padding: const EdgeInsets.fromLTRB(10, 4, 10, 6), child: Obx(() { final hor = max(horError.value, 0.0); final vert = vertError.value; final geo = geoidSeparation?.value ?? gnss.geoidSeparation.value; return Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ _Chip(Icons.open_in_full, 'Vízszintes', '±${hor.toStringAsFixed(3)} m', _errorColor(hor)), _Chip(Icons.height, 'Függőleges', '±${vert.toStringAsFixed(3)} m', _errorColor(vert)), _Chip(Icons.water, 'Geoid N', '${geo.toStringAsFixed(2)} m', Colors.white54), ], ); }), ), // ── NTRIP ──────────────────────────────────────── if (ntripConnected != null) Obx( () => ntripConnected!.value ? Column(children: [ _Div(), Padding( padding: const EdgeInsets.fromLTRB(10, 4, 10, 6), child: Row(children: [ Container( width: 7, height: 7, decoration: const BoxDecoration( color: Colors.greenAccent, shape: BoxShape.circle), ), const SizedBox(width: 6), const Text('NTRIP', style: TextStyle( color: Colors.greenAccent, fontSize: 11, fontWeight: FontWeight.w600, )), const Spacer(), Text( '${ntripBytes?.value ?? 0} byte' ' · ${ntripPackets?.value ?? 0} cs.', style: TextStyle( color: Colors.white.withOpacity(0.5), fontSize: 10, )), const SizedBox(width: 8), Text('GGA: ${ggaPackets?.value ?? 0}', style: TextStyle( color: Colors.white.withOpacity(0.4), fontSize: 10, )), ]), ), ]) : const SizedBox.shrink(), ), ], ], ), ); }); } } // ── Mini widgetek ───────────────────────────────────────────────────── class _Div extends StatelessWidget { @override Widget build(BuildContext context) => Container(height: 1, color: Colors.white.withOpacity(0.08)); } class _Row extends StatelessWidget { final String label, value, unit; final bool bold, dimmed; const _Row(this.label, this.value, this.unit, {this.bold = false, this.dimmed = false}); @override Widget build(BuildContext context) => Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text(label, style: TextStyle( color: Colors.white.withOpacity(0.5), fontSize: 11)), Row(children: [ Text(value, style: TextStyle( color: Colors.white.withOpacity(dimmed ? 0.45 : 1.0), fontSize: bold ? 15 : 13, fontWeight: bold ? FontWeight.w600 : FontWeight.w400, fontFeatures: const [FontFeature.tabularFigures()], )), const SizedBox(width: 3), Text(unit, style: TextStyle( color: Colors.white.withOpacity(0.35), fontSize: 10)), ]), ], ); } class _Chip extends StatelessWidget { final IconData icon; final String label, value; final Color color; const _Chip(this.icon, this.label, this.value, this.color); @override Widget build(BuildContext context) => Column( mainAxisSize: MainAxisSize.min, children: [ Icon(icon, size: 12, color: Colors.white.withOpacity(0.4)), const SizedBox(height: 2), Text(value, style: TextStyle( color: color, fontSize: 12, fontWeight: FontWeight.w600, fontFeatures: const [FontFeature.tabularFigures()], )), Text(label, style: TextStyle(color: Colors.white.withOpacity(0.4), fontSize: 9)), ], ); } // ── Segédfüggvények ─────────────────────────────────────────────────── Color _qualityColor(int q) => switch (q) { 4 => Colors.greenAccent, 5 => Colors.lightGreen, 2 => Colors.blue, 1 => Colors.orange, _ => Colors.red, }; String _qualityLabel(int q) => switch (q) { 4 => 'RTK Fix', 5 => 'RTK Float', 2 => 'DGPS', 1 => 'GPS', _ => 'Nincs fix', }; Color _errorColor(double e) { if (e < 0.05) return Colors.greenAccent; if (e < 0.2) return Colors.green; if (e < 1.0) return Colors.orange; return Colors.red; }