import 'dart:async'; import 'dart:math' as math; import 'package:fforte/l10n/app_localizations.dart'; import 'package:fforte/services/notification_service.dart'; import 'package:flutter/material.dart'; import 'package:geolocator/geolocator.dart'; import 'package:latlong2/latlong.dart'; class TrackingService { static final TrackingService _instance = TrackingService._internal(); factory TrackingService() => _instance; TrackingService._internal(); List pathList = []; List accuracyList = []; StreamSubscription? positionStream; bool isTracking = false; final _positionController = StreamController.broadcast(); final _statsController = StreamController.broadcast(); Stream get positionStream$ => _positionController.stream; Stream get statsStream$ => _statsController.stream; double? currentAccuracy; double _calculateMedianAccuracy(List 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.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 startTracking(BuildContext context) async { if (isTracking) return; await NotificationService().initNotification(); NotificationService().showNotification( title: AppLocalizations.of(context)!.trackingRunningInBackground, ); positionStream = Geolocator.getPositionStream( locationSettings: AndroidSettings( accuracy: LocationAccuracy.high, distanceFilter: 0, foregroundNotificationConfig: ForegroundNotificationConfig( 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(); } }); positionStream!.onError((e) { NotificationService().deleteNotification(); NotificationService().showNotification(title: "ERROR: $e"); }); 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; } void resumeTracking() { positionStream?.resume(); isTracking = true; } void stopTracking() { positionStream?.cancel(); NotificationService().deleteNotification(); isTracking = false; accuracyList.clear(); currentAccuracy = null; } void clearPath() { pathList.clear(); accuracyList.clear(); currentAccuracy = null; _updateStats(); } String getPathAsString() { return pathList.map((pos) => "${pos.latitude},${pos.longitude}").join(";"); } 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, }); }