MobilApp/lib/widgets/coordinate_panel.dart

321 lines
12 KiB
Dart
Raw Normal View History

// lib/widgets/coordinate_panel.dart
import 'dart:math';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:intl/intl.dart';
import 'package:terepi_seged/services/ntrip_service.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: NtripService.to.isConnected,
ntripBytes: NtripService.to.receivedBytes,
ntripPackets: NtripService.to.packetCount,
ggaPackets: NtripService.to.ggaSentCount,
);
}
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;
}