added accuracy and distance traveled
This commit is contained in:
@@ -171,5 +171,6 @@
|
|||||||
"trackingAnAusschalten": "Tracking an/aus-schalten",
|
"trackingAnAusschalten": "Tracking an/aus-schalten",
|
||||||
"trackingStart": "Tracking starten",
|
"trackingStart": "Tracking starten",
|
||||||
"trackingStop": "Tracking stoppen",
|
"trackingStop": "Tracking stoppen",
|
||||||
"trackingPause": "Tracking pausieren"
|
"trackingPause": "Tracking pausieren",
|
||||||
|
"accuracy": "Genauigkeit"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -681,5 +681,10 @@
|
|||||||
|
|
||||||
"trackingStop": "Stop tracking",
|
"trackingStop": "Stop tracking",
|
||||||
|
|
||||||
"trackingPause": "Pause Tracking"
|
"trackingPause": "Pause Tracking",
|
||||||
|
|
||||||
|
"accuracy": "Accuracy",
|
||||||
|
"@accuracy": {
|
||||||
|
"description": "Accuracy of GPS position"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1126,6 +1126,12 @@ abstract class AppLocalizations {
|
|||||||
/// In en, this message translates to:
|
/// In en, this message translates to:
|
||||||
/// **'Pause Tracking'**
|
/// **'Pause Tracking'**
|
||||||
String get trackingPause;
|
String get trackingPause;
|
||||||
|
|
||||||
|
/// Accuracy of GPS position
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Accuracy'**
|
||||||
|
String get accuracy;
|
||||||
}
|
}
|
||||||
|
|
||||||
class _AppLocalizationsDelegate extends LocalizationsDelegate<AppLocalizations> {
|
class _AppLocalizationsDelegate extends LocalizationsDelegate<AppLocalizations> {
|
||||||
|
|||||||
@@ -523,4 +523,7 @@ class AppLocalizationsDe extends AppLocalizations {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
String get trackingPause => 'Tracking pausieren';
|
String get trackingPause => 'Tracking pausieren';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get accuracy => 'Genauigkeit';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -523,4 +523,7 @@ class AppLocalizationsEn extends AppLocalizations {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
String get trackingPause => 'Pause Tracking';
|
String get trackingPause => 'Pause Tracking';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get accuracy => 'Accuracy';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,6 +25,8 @@ class _TrackingState extends State<Tracking> {
|
|||||||
LocationMarkerPosition? locationMarkerPosition;
|
LocationMarkerPosition? locationMarkerPosition;
|
||||||
MapController mapController = MapController();
|
MapController mapController = MapController();
|
||||||
StreamSubscription? _positionSubscription;
|
StreamSubscription? _positionSubscription;
|
||||||
|
StreamSubscription? _statsSubscription;
|
||||||
|
TrackingStats? _currentStats;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
@@ -52,6 +54,12 @@ class _TrackingState extends State<Tracking> {
|
|||||||
accuracy: widget.startPosition.accuracy,
|
accuracy: widget.startPosition.accuracy,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Initialisiere die Statistiken sofort
|
||||||
|
setState(() {
|
||||||
|
_currentStats = _trackingService.currentStats;
|
||||||
|
});
|
||||||
|
_trackingService.requestStatsUpdate();
|
||||||
|
|
||||||
_positionSubscription = _trackingService.positionStream$.listen((position) {
|
_positionSubscription = _trackingService.positionStream$.listen((position) {
|
||||||
setState(() {
|
setState(() {
|
||||||
locationMarkerPosition = LocationMarkerPosition(
|
locationMarkerPosition = LocationMarkerPosition(
|
||||||
@@ -62,6 +70,12 @@ class _TrackingState extends State<Tracking> {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
_statsSubscription = _trackingService.statsStream$.listen((stats) {
|
||||||
|
setState(() {
|
||||||
|
_currentStats = stats;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
GeolocatorService.alwaysPositionEnabled().then((value) {
|
GeolocatorService.alwaysPositionEnabled().then((value) {
|
||||||
if (!value && mounted) {
|
if (!value && mounted) {
|
||||||
Navigator.of(context).pop();
|
Navigator.of(context).pop();
|
||||||
@@ -73,15 +87,45 @@ class _TrackingState extends State<Tracking> {
|
|||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
_positionSubscription?.cancel();
|
_positionSubscription?.cancel();
|
||||||
|
_statsSubscription?.cancel();
|
||||||
widget.weg.text = _trackingService.getPathAsString();
|
widget.weg.text = _trackingService.getPathAsString();
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
String _formatDistance(double meters) {
|
||||||
|
if (meters >= 1000) {
|
||||||
|
return '${(meters / 1000).toStringAsFixed(2)} km';
|
||||||
|
}
|
||||||
|
return '${meters.toStringAsFixed(1)} m';
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(
|
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(
|
leading: IconButton(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
Navigator.pop(context);
|
Navigator.pop(context);
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
import 'dart:math' as math;
|
||||||
|
|
||||||
import 'package:fforte/l10n/app_localizations.dart';
|
import 'package:fforte/l10n/app_localizations.dart';
|
||||||
import 'package:fforte/services/notification_service.dart';
|
import 'package:fforte/services/notification_service.dart';
|
||||||
@@ -12,35 +13,58 @@ class TrackingService {
|
|||||||
TrackingService._internal();
|
TrackingService._internal();
|
||||||
|
|
||||||
List<LatLng> pathList = [];
|
List<LatLng> pathList = [];
|
||||||
|
List<double> accuracyList = [];
|
||||||
StreamSubscription<Position>? positionStream;
|
StreamSubscription<Position>? positionStream;
|
||||||
bool isTracking = false;
|
bool isTracking = false;
|
||||||
final _positionController = StreamController<Position>.broadcast();
|
final _positionController = StreamController<Position>.broadcast();
|
||||||
|
final _statsController = StreamController<TrackingStats>.broadcast();
|
||||||
|
|
||||||
Stream<Position> get positionStream$ => _positionController.stream;
|
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 {
|
Future<void> startTracking(BuildContext context) async {
|
||||||
if (isTracking) return;
|
if (isTracking) return;
|
||||||
|
|
||||||
await NotificationService().initNotification();
|
await NotificationService().initNotification();
|
||||||
if (context.mounted) {
|
|
||||||
NotificationService().showNotification(
|
NotificationService().showNotification(
|
||||||
title: AppLocalizations.of(context)!.trackingRunningInBackground,
|
title: AppLocalizations.of(context)!.trackingRunningInBackground,
|
||||||
);
|
);
|
||||||
}
|
|
||||||
|
|
||||||
positionStream = Geolocator.getPositionStream(
|
positionStream = Geolocator.getPositionStream(
|
||||||
locationSettings: AndroidSettings(
|
locationSettings: AndroidSettings(
|
||||||
accuracy: LocationAccuracy.high,
|
accuracy: LocationAccuracy.high,
|
||||||
distanceFilter: 0,
|
distanceFilter: 0,
|
||||||
foregroundNotificationConfig: ForegroundNotificationConfig(
|
foregroundNotificationConfig: ForegroundNotificationConfig(
|
||||||
notificationTitle: context.mounted ? AppLocalizations.of(context)!.trackingRunningInBackground : "Tracking",
|
notificationTitle: AppLocalizations.of(context)!.trackingRunningInBackground,
|
||||||
notificationText: "",
|
notificationText: "",
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
).listen((Position? position) {
|
).listen((Position? position) {
|
||||||
if (position != null) {
|
if (position != null) {
|
||||||
pathList.add(LatLng(position.latitude, position.longitude));
|
pathList.add(LatLng(position.latitude, position.longitude));
|
||||||
|
accuracyList.add(position.accuracy);
|
||||||
|
currentAccuracy = position.accuracy;
|
||||||
_positionController.add(position);
|
_positionController.add(position);
|
||||||
|
_updateStats();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -52,6 +76,60 @@ class TrackingService {
|
|||||||
isTracking = true;
|
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() {
|
void pauseTracking() {
|
||||||
positionStream?.pause();
|
positionStream?.pause();
|
||||||
isTracking = false;
|
isTracking = false;
|
||||||
@@ -66,10 +144,15 @@ class TrackingService {
|
|||||||
positionStream?.cancel();
|
positionStream?.cancel();
|
||||||
NotificationService().deleteNotification();
|
NotificationService().deleteNotification();
|
||||||
isTracking = false;
|
isTracking = false;
|
||||||
|
accuracyList.clear();
|
||||||
|
currentAccuracy = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
void clearPath() {
|
void clearPath() {
|
||||||
pathList.clear();
|
pathList.clear();
|
||||||
|
accuracyList.clear();
|
||||||
|
currentAccuracy = null;
|
||||||
|
_updateStats();
|
||||||
}
|
}
|
||||||
|
|
||||||
String getPathAsString() {
|
String getPathAsString() {
|
||||||
@@ -79,5 +162,18 @@ class TrackingService {
|
|||||||
void dispose() {
|
void dispose() {
|
||||||
stopTracking();
|
stopTracking();
|
||||||
_positionController.close();
|
_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,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user