feat: Add heading accuracy information and warning display in DetailedCompassDialog

This commit is contained in:
Janez T
2025-10-15 15:26:42 +02:00
parent f76cd227e1
commit 3f03b2e789

View File

@@ -12,6 +12,20 @@ import 'compass/compass_filters.dart';
import 'compass/compass_sar_list.dart'; import 'compass/compass_sar_list.dart';
import 'compass/compass_contact_list.dart'; import 'compass/compass_contact_list.dart';
enum HeadingAccuracySeverity { low, medium, high }
class HeadingAccuracyInfo {
final bool isAccurate;
final String? warning;
final HeadingAccuracySeverity severity;
HeadingAccuracyInfo({
required this.isAccurate,
this.warning,
this.severity = HeadingAccuracySeverity.low,
});
}
class DetailedCompassDialog extends StatefulWidget { class DetailedCompassDialog extends StatefulWidget {
final Position? initialPosition; final Position? initialPosition;
final double? initialHeading; final double? initialHeading;
@@ -36,6 +50,7 @@ class DetailedCompassDialog extends StatefulWidget {
class _DetailedCompassDialogState extends State<DetailedCompassDialog> { class _DetailedCompassDialogState extends State<DetailedCompassDialog> {
double? _currentHeading; double? _currentHeading;
double? _compassAccuracy; // Compass accuracy in degrees
Position? _currentPosition; Position? _currentPosition;
StreamSubscription<CompassEvent>? _compassSubscription; StreamSubscription<CompassEvent>? _compassSubscription;
StreamSubscription<Position>? _positionSubscription; StreamSubscription<Position>? _positionSubscription;
@@ -78,6 +93,7 @@ class _DetailedCompassDialogState extends State<DetailedCompassDialog> {
if (mounted && event.heading != null) { if (mounted && event.heading != null) {
setState(() { setState(() {
_currentHeading = event.heading; _currentHeading = event.heading;
_compassAccuracy = event.accuracy;
}); });
} }
}); });
@@ -114,6 +130,67 @@ class _DetailedCompassDialogState extends State<DetailedCompassDialog> {
return null; return null;
} }
// Check heading accuracy and return warning information
HeadingAccuracyInfo get headingAccuracyInfo {
// Using compass
if (_currentHeading != null) {
if (_compassAccuracy == null) {
return HeadingAccuracyInfo(
isAccurate: false,
warning: 'Compass accuracy unknown',
severity: HeadingAccuracySeverity.low,
);
} else if (_compassAccuracy! > 30) {
return HeadingAccuracyInfo(
isAccurate: false,
warning: 'Low compass accuracy (±${_compassAccuracy!.round()}°). Calibrate device.',
severity: HeadingAccuracySeverity.high,
);
} else if (_compassAccuracy! > 15) {
return HeadingAccuracyInfo(
isAccurate: true,
warning: 'Moderate compass accuracy (±${_compassAccuracy!.round()}°)',
severity: HeadingAccuracySeverity.medium,
);
}
return HeadingAccuracyInfo(isAccurate: true);
}
// Using GPS heading
if (_currentPosition?.heading != null && _currentPosition!.heading >= 0) {
final headingAccuracy = _currentPosition!.headingAccuracy;
if (headingAccuracy > 0) {
if (headingAccuracy > 30) {
return HeadingAccuracyInfo(
isAccurate: false,
warning: 'Low GPS heading accuracy (±${headingAccuracy.round()}°). Move faster or use compass.',
severity: HeadingAccuracySeverity.high,
);
} else if (headingAccuracy > 15) {
return HeadingAccuracyInfo(
isAccurate: true,
warning: 'Moderate GPS heading accuracy (±${headingAccuracy.round()}°)',
severity: HeadingAccuracySeverity.medium,
);
}
return HeadingAccuracyInfo(isAccurate: true);
}
// GPS heading available but no accuracy info
return HeadingAccuracyInfo(
isAccurate: true,
warning: 'Using GPS heading (accuracy unknown)',
severity: HeadingAccuracySeverity.low,
);
}
// No heading available
return HeadingAccuracyInfo(
isAccurate: false,
warning: 'No heading available',
severity: HeadingAccuracySeverity.high,
);
}
// Filter SAR markers based on visibility settings // Filter SAR markers based on visibility settings
List<SarMarker> _getFilteredSarMarkers() { List<SarMarker> _getFilteredSarMarkers() {
return widget.sarMarkers.where((marker) { return widget.sarMarkers.where((marker) {
@@ -193,6 +270,8 @@ class _DetailedCompassDialogState extends State<DetailedCompassDialog> {
Widget build(BuildContext context) { Widget build(BuildContext context) {
final heading = currentHeading; final heading = currentHeading;
final position = _currentPosition; final position = _currentPosition;
final accuracyInfo = headingAccuracyInfo;
return Column( return Column(
children: [ children: [
// Header with back button // Header with back button
@@ -261,6 +340,9 @@ class _DetailedCompassDialogState extends State<DetailedCompassDialog> {
], ],
), ),
), ),
// Heading accuracy warning banner
if (accuracyInfo.warning != null)
_buildAccuracyWarning(context, accuracyInfo),
Expanded( Expanded(
child: SingleChildScrollView( child: SingleChildScrollView(
child: Padding( child: Padding(
@@ -338,6 +420,59 @@ class _DetailedCompassDialogState extends State<DetailedCompassDialog> {
); );
} }
Widget _buildAccuracyWarning(BuildContext context, HeadingAccuracyInfo info) {
Color backgroundColor;
Color iconColor;
IconData icon;
switch (info.severity) {
case HeadingAccuracySeverity.high:
backgroundColor = Colors.red.shade100;
iconColor = Colors.red.shade700;
icon = Icons.error_outline;
break;
case HeadingAccuracySeverity.medium:
backgroundColor = Colors.orange.shade100;
iconColor = Colors.orange.shade700;
icon = Icons.warning_amber_outlined;
break;
case HeadingAccuracySeverity.low:
backgroundColor = Colors.blue.shade100;
iconColor = Colors.blue.shade700;
icon = Icons.info_outline;
break;
}
return Container(
margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: backgroundColor,
borderRadius: BorderRadius.circular(8),
border: Border.all(
color: iconColor.withValues(alpha: 0.3),
width: 1,
),
),
child: Row(
children: [
Icon(icon, color: iconColor, size: 20),
const SizedBox(width: 12),
Expanded(
child: Text(
info.warning!,
style: TextStyle(
color: iconColor,
fontSize: 13,
fontWeight: FontWeight.w500,
),
),
),
],
),
);
}
Widget _buildSelectedItemDetail(BuildContext context, double? heading, Position? position) { Widget _buildSelectedItemDetail(BuildContext context, double? heading, Position? position) {
if (position == null) { if (position == null) {
return const SizedBox.shrink(); return const SizedBox.shrink();