added accuracy and distance traveled

This commit is contained in:
Nico
2025-06-04 21:12:16 +02:00
parent 9261abfc57
commit 820b19bd29
7 changed files with 164 additions and 6 deletions

View File

@@ -171,5 +171,6 @@
"trackingAnAusschalten": "Tracking an/aus-schalten",
"trackingStart": "Tracking starten",
"trackingStop": "Tracking stoppen",
"trackingPause": "Tracking pausieren"
"trackingPause": "Tracking pausieren",
"accuracy": "Genauigkeit"
}

View File

@@ -681,5 +681,10 @@
"trackingStop": "Stop tracking",
"trackingPause": "Pause Tracking"
"trackingPause": "Pause Tracking",
"accuracy": "Accuracy",
"@accuracy": {
"description": "Accuracy of GPS position"
}
}

View File

@@ -1126,6 +1126,12 @@ abstract class AppLocalizations {
/// In en, this message translates to:
/// **'Pause Tracking'**
String get trackingPause;
/// Accuracy of GPS position
///
/// In en, this message translates to:
/// **'Accuracy'**
String get accuracy;
}
class _AppLocalizationsDelegate extends LocalizationsDelegate<AppLocalizations> {

View File

@@ -523,4 +523,7 @@ class AppLocalizationsDe extends AppLocalizations {
@override
String get trackingPause => 'Tracking pausieren';
@override
String get accuracy => 'Genauigkeit';
}

View File

@@ -523,4 +523,7 @@ class AppLocalizationsEn extends AppLocalizations {
@override
String get trackingPause => 'Pause Tracking';
@override
String get accuracy => 'Accuracy';
}

View File

@@ -25,6 +25,8 @@ class _TrackingState extends State<Tracking> {
LocationMarkerPosition? locationMarkerPosition;
MapController mapController = MapController();
StreamSubscription? _positionSubscription;
StreamSubscription? _statsSubscription;
TrackingStats? _currentStats;
@override
void initState() {
@@ -52,6 +54,12 @@ class _TrackingState extends State<Tracking> {
accuracy: widget.startPosition.accuracy,
);
// Initialisiere die Statistiken sofort
setState(() {
_currentStats = _trackingService.currentStats;
});
_trackingService.requestStatsUpdate();
_positionSubscription = _trackingService.positionStream$.listen((position) {
setState(() {
locationMarkerPosition = LocationMarkerPosition(
@@ -62,6 +70,12 @@ class _TrackingState extends State<Tracking> {
});
});
_statsSubscription = _trackingService.statsStream$.listen((stats) {
setState(() {
_currentStats = stats;
});
});
GeolocatorService.alwaysPositionEnabled().then((value) {
if (!value && mounted) {
Navigator.of(context).pop();
@@ -73,15 +87,45 @@ class _TrackingState extends State<Tracking> {
@override
void dispose() {
_positionSubscription?.cancel();
_statsSubscription?.cancel();
widget.weg.text = _trackingService.getPathAsString();
super.dispose();
}
String _formatDistance(double meters) {
if (meters >= 1000) {
return '${(meters / 1000).toStringAsFixed(2)} km';
}
return '${meters.toStringAsFixed(1)} m';
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(AppLocalizations.of(context)!.tracking),
title: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(AppLocalizations.of(context)!.tracking),
if (_currentStats != null)
DefaultTextStyle(
style: Theme.of(context).textTheme.bodySmall!,
child: Row(
children: [
Expanded(
child: Text(
'${AppLocalizations.of(context)!.accuracy}: ${_currentStats!.currentAccuracy.toStringAsFixed(1)}m (∅ ${_currentStats!.averageAccuracy.toStringAsFixed(1)}m)',
overflow: TextOverflow.ellipsis,
),
),
Text(
_formatDistance(_currentStats!.totalDistanceMeters),
),
],
),
),
],
),
leading: IconButton(
onPressed: () {
Navigator.pop(context);

View File

@@ -1,4 +1,5 @@
import 'dart:async';
import 'dart:math' as math;
import 'package:fforte/l10n/app_localizations.dart';
import 'package:fforte/services/notification_service.dart';
@@ -12,35 +13,58 @@ class TrackingService {
TrackingService._internal();
List<LatLng> pathList = [];
List<double> accuracyList = [];
StreamSubscription<Position>? positionStream;
bool isTracking = false;
final _positionController = StreamController<Position>.broadcast();
final _statsController = StreamController<TrackingStats>.broadcast();
Stream<Position> get positionStream$ => _positionController.stream;
Stream<TrackingStats> get statsStream$ => _statsController.stream;
double? currentAccuracy;
double _calculateMedianAccuracy(List<double> accuracies) {
if (accuracies.isEmpty) return 0;
if (accuracies.length == 1) return accuracies.first;
// Kopiere die Liste, um die Originaldaten nicht zu verändern
var sorted = List<double>.from(accuracies)..sort();
if (sorted.length % 2 == 0) {
// Bei gerader Anzahl: Durchschnitt der beiden mittleren Werte
int midIndex = sorted.length ~/ 2;
return (sorted[midIndex - 1] + sorted[midIndex]) / 2;
} else {
// Bei ungerader Anzahl: Der mittlere Wert
return sorted[sorted.length ~/ 2];
}
}
Future<void> startTracking(BuildContext context) async {
if (isTracking) return;
await NotificationService().initNotification();
if (context.mounted) {
NotificationService().showNotification(
title: AppLocalizations.of(context)!.trackingRunningInBackground,
);
}
positionStream = Geolocator.getPositionStream(
locationSettings: AndroidSettings(
accuracy: LocationAccuracy.high,
distanceFilter: 0,
foregroundNotificationConfig: ForegroundNotificationConfig(
notificationTitle: context.mounted ? AppLocalizations.of(context)!.trackingRunningInBackground : "Tracking",
notificationTitle: AppLocalizations.of(context)!.trackingRunningInBackground,
notificationText: "",
),
),
).listen((Position? position) {
if (position != null) {
pathList.add(LatLng(position.latitude, position.longitude));
accuracyList.add(position.accuracy);
currentAccuracy = position.accuracy;
_positionController.add(position);
_updateStats();
}
});
@@ -52,6 +76,60 @@ class TrackingService {
isTracking = true;
}
TrackingStats? _lastStats;
TrackingStats? get currentStats => _lastStats;
void _updateStats() {
if (pathList.isEmpty) {
_lastStats = TrackingStats(
currentAccuracy: currentAccuracy ?? 0,
averageAccuracy: 0,
totalDistanceMeters: 0
);
_statsController.add(_lastStats!);
return;
}
double totalDistance = 0;
for (int i = 1; i < pathList.length; i++) {
totalDistance += _calculateDistance(
pathList[i-1].latitude,
pathList[i-1].longitude,
pathList[i].latitude,
pathList[i].longitude
);
}
double medianAccuracy = _calculateMedianAccuracy(accuracyList);
_lastStats = TrackingStats(
currentAccuracy: currentAccuracy ?? 0,
averageAccuracy: medianAccuracy,
totalDistanceMeters: totalDistance
);
_statsController.add(_lastStats!);
}
void requestStatsUpdate() {
_updateStats();
}
double _calculateDistance(double lat1, double lon1, double lat2, double lon2) {
const double earthRadius = 6371000; // Erdradius in Metern
double lat1Rad = lat1 * math.pi / 180;
double lat2Rad = lat2 * math.pi / 180;
double deltaLat = (lat2 - lat1) * math.pi / 180;
double deltaLon = (lon2 - lon1) * math.pi / 180;
double a = math.sin(deltaLat/2) * math.sin(deltaLat/2) +
math.cos(lat1Rad) * math.cos(lat2Rad) *
math.sin(deltaLon/2) * math.sin(deltaLon/2);
double c = 2 * math.atan2(math.sqrt(a), math.sqrt(1-a));
return earthRadius * c;
}
void pauseTracking() {
positionStream?.pause();
isTracking = false;
@@ -66,10 +144,15 @@ class TrackingService {
positionStream?.cancel();
NotificationService().deleteNotification();
isTracking = false;
accuracyList.clear();
currentAccuracy = null;
}
void clearPath() {
pathList.clear();
accuracyList.clear();
currentAccuracy = null;
_updateStats();
}
String getPathAsString() {
@@ -79,5 +162,18 @@ class TrackingService {
void dispose() {
stopTracking();
_positionController.close();
_statsController.close();
}
}
class TrackingStats {
final double currentAccuracy;
final double averageAccuracy;
final double totalDistanceMeters;
TrackingStats({
required this.currentAccuracy,
required this.averageAccuracy,
required this.totalDistanceMeters,
});
}