333 lines
9.9 KiB
Dart
333 lines
9.9 KiB
Dart
import 'dart:async';
|
|
import 'package:geolocator/geolocator.dart';
|
|
import 'package:flutter/foundation.dart';
|
|
|
|
/// Statische Geolocation Services für alle Plattformen
|
|
/// Unterstützt Android, iOS, Web, Windows, macOS und Linux
|
|
class GeolocationService {
|
|
/// Private Konstruktor um Instanziierung zu verhindern
|
|
GeolocationService._();
|
|
|
|
/// Stream Controller für kontinuierliche Position Updates
|
|
static StreamSubscription<Position>? _positionStreamSubscription;
|
|
|
|
/// Prüft ob Location Services verfügbar und aktiviert sind
|
|
static Future<bool> isLocationServiceEnabled() async {
|
|
try {
|
|
return await Geolocator.isLocationServiceEnabled();
|
|
} catch (e) {
|
|
if (kDebugMode) {
|
|
print('Fehler beim Prüfen der Location Services: $e');
|
|
}
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/// Prüft die aktuellen Location Permissions
|
|
static Future<LocationPermission> checkPermission() async {
|
|
try {
|
|
return await Geolocator.checkPermission();
|
|
} catch (e) {
|
|
if (kDebugMode) {
|
|
print('Fehler beim Prüfen der Berechtigung: $e');
|
|
}
|
|
return LocationPermission.denied;
|
|
}
|
|
}
|
|
|
|
/// Fordert Location Permissions an
|
|
static Future<LocationPermission> requestPermission() async {
|
|
try {
|
|
return await Geolocator.requestPermission();
|
|
} catch (e) {
|
|
if (kDebugMode) {
|
|
print('Fehler beim Anfordern der Berechtigung: $e');
|
|
}
|
|
return LocationPermission.denied;
|
|
}
|
|
}
|
|
|
|
/// Prüft und fordert bei Bedarf Permissions an
|
|
static Future<bool> ensurePermissions() async {
|
|
// Prüfe ob Location Services aktiviert sind
|
|
bool serviceEnabled = await isLocationServiceEnabled();
|
|
if (!serviceEnabled) {
|
|
if (kDebugMode) {
|
|
print('Location Services sind nicht aktiviert');
|
|
}
|
|
return false;
|
|
}
|
|
|
|
LocationPermission permission = await checkPermission();
|
|
|
|
if (permission == LocationPermission.denied) {
|
|
permission = await requestPermission();
|
|
if (permission == LocationPermission.denied) {
|
|
if (kDebugMode) {
|
|
print('Location Permissions wurden verweigert');
|
|
}
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (permission == LocationPermission.deniedForever) {
|
|
if (kDebugMode) {
|
|
print('Location Permissions wurden dauerhaft verweigert');
|
|
}
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/// Holt die aktuelle Position einmalig
|
|
///
|
|
/// [accuracy] - Gewünschte Genauigkeit (Standard: LocationAccuracy.high)
|
|
/// [timeLimit] - Timeout für die Anfrage (Standard: 10 Sekunden)
|
|
static Future<Position?> getCurrentPosition({
|
|
LocationAccuracy accuracy = LocationAccuracy.high,
|
|
Duration timeLimit = const Duration(seconds: 10),
|
|
}) async {
|
|
try {
|
|
// Prüfe Permissions
|
|
bool hasPermission = await ensurePermissions();
|
|
if (!hasPermission) {
|
|
return null;
|
|
}
|
|
|
|
// Hole aktuelle Position
|
|
Position position = await Geolocator.getCurrentPosition(
|
|
locationSettings: LocationSettings(
|
|
accuracy: accuracy,
|
|
timeLimit: timeLimit,
|
|
),
|
|
);
|
|
|
|
return position;
|
|
} catch (e) {
|
|
if (kDebugMode) {
|
|
print('Fehler beim Abrufen der aktuellen Position: $e');
|
|
}
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/// Startet kontinuierliche Position Updates
|
|
///
|
|
/// [onPositionChanged] - Callback Funktion für neue Positionen
|
|
/// [onError] - Callback Funktion für Fehler
|
|
/// [accuracy] - Gewünschte Genauigkeit
|
|
/// [distanceFilter] - Minimale Distanz zwischen Updates in Metern
|
|
/// [intervalDuration] - Minimale Zeit zwischen Updates
|
|
static Future<bool> startPositionStream({
|
|
required Function(Position) onPositionChanged,
|
|
Function(String)? onError,
|
|
LocationAccuracy accuracy = LocationAccuracy.high,
|
|
int distanceFilter = 10,
|
|
Duration intervalDuration = const Duration(seconds: 5),
|
|
}) async {
|
|
try {
|
|
// Prüfe Permissions
|
|
bool hasPermission = await ensurePermissions();
|
|
if (!hasPermission) {
|
|
onError?.call('Keine Location Permissions');
|
|
return false;
|
|
}
|
|
|
|
// Stoppe vorherigen Stream falls aktiv
|
|
await stopPositionStream();
|
|
|
|
// Erstelle Location Settings basierend auf Plattform
|
|
LocationSettings locationSettings;
|
|
|
|
if (defaultTargetPlatform == TargetPlatform.android) {
|
|
locationSettings = AndroidSettings(
|
|
accuracy: accuracy,
|
|
distanceFilter: distanceFilter,
|
|
intervalDuration: intervalDuration,
|
|
forceLocationManager: false,
|
|
);
|
|
} else if (defaultTargetPlatform == TargetPlatform.iOS) {
|
|
locationSettings = AppleSettings(
|
|
accuracy: accuracy,
|
|
distanceFilter: distanceFilter,
|
|
activityType: ActivityType.other,
|
|
pauseLocationUpdatesAutomatically: true,
|
|
showBackgroundLocationIndicator: false,
|
|
);
|
|
} else {
|
|
locationSettings = LocationSettings(
|
|
accuracy: accuracy,
|
|
distanceFilter: distanceFilter,
|
|
);
|
|
}
|
|
|
|
// Starte Position Stream
|
|
_positionStreamSubscription = Geolocator.getPositionStream(
|
|
locationSettings: locationSettings,
|
|
).listen(
|
|
onPositionChanged,
|
|
onError: (error) {
|
|
if (kDebugMode) {
|
|
print('Position Stream Fehler: $error');
|
|
}
|
|
onError?.call(error.toString());
|
|
},
|
|
);
|
|
|
|
return true;
|
|
} catch (e) {
|
|
if (kDebugMode) {
|
|
print('Fehler beim Starten des Position Streams: $e');
|
|
}
|
|
onError?.call(e.toString());
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/// Stoppt kontinuierliche Position Updates
|
|
static Future<void> stopPositionStream() async {
|
|
try {
|
|
await _positionStreamSubscription?.cancel();
|
|
_positionStreamSubscription = null;
|
|
} catch (e) {
|
|
if (kDebugMode) {
|
|
print('Fehler beim Stoppen des Position Streams: $e');
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Prüft ob Position Stream aktiv ist
|
|
static bool isPositionStreamActive() {
|
|
return _positionStreamSubscription != null && !_positionStreamSubscription!.isPaused;
|
|
}
|
|
|
|
/// Berechnet die Distanz zwischen zwei Positionen in Metern
|
|
static double calculateDistance({
|
|
required double startLatitude,
|
|
required double startLongitude,
|
|
required double endLatitude,
|
|
required double endLongitude,
|
|
}) {
|
|
return Geolocator.distanceBetween(
|
|
startLatitude,
|
|
startLongitude,
|
|
endLatitude,
|
|
endLongitude,
|
|
);
|
|
}
|
|
|
|
/// Berechnet die Richtung zwischen zwei Positionen in Grad
|
|
static double calculateBearing({
|
|
required double startLatitude,
|
|
required double startLongitude,
|
|
required double endLatitude,
|
|
required double endLongitude,
|
|
}) {
|
|
return Geolocator.bearingBetween(
|
|
startLatitude,
|
|
startLongitude,
|
|
endLatitude,
|
|
endLongitude,
|
|
);
|
|
}
|
|
|
|
/// Öffnet die Geräte-Einstellungen für Location Services
|
|
static Future<bool> openLocationSettings() async {
|
|
try {
|
|
return await Geolocator.openLocationSettings();
|
|
} catch (e) {
|
|
if (kDebugMode) {
|
|
print('Fehler beim Öffnen der Location Settings: $e');
|
|
}
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/// Öffnet die App-Einstellungen
|
|
static Future<bool> openAppSettings() async {
|
|
try {
|
|
return await Geolocator.openAppSettings();
|
|
} catch (e) {
|
|
if (kDebugMode) {
|
|
print('Fehler beim Öffnen der App Settings: $e');
|
|
}
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/// Formatiert Position zu einem lesbaren String
|
|
static String positionToString(Position position) {
|
|
return 'Lat: ${position.latitude.toStringAsFixed(6)}, '
|
|
'Lng: ${position.longitude.toStringAsFixed(6)}, '
|
|
'Genauigkeit: ${position.accuracy.toStringAsFixed(1)}m, '
|
|
'Zeit: ${position.timestamp}';
|
|
}
|
|
|
|
/// Konvertiert Position zu Map für JSON Serialisierung
|
|
static Map<String, dynamic> positionToMap(Position position) {
|
|
return {
|
|
'latitude': position.latitude,
|
|
'longitude': position.longitude,
|
|
'accuracy': position.accuracy,
|
|
'altitude': position.altitude,
|
|
'speed': position.speed,
|
|
'speedAccuracy': position.speedAccuracy,
|
|
'heading': position.heading,
|
|
'timestamp': position.timestamp.toIso8601String(),
|
|
};
|
|
}
|
|
|
|
/// Erstellt Position aus Map
|
|
static Position? positionFromMap(Map<String, dynamic> map) {
|
|
try {
|
|
return Position(
|
|
latitude: map['latitude']?.toDouble() ?? 0.0,
|
|
longitude: map['longitude']?.toDouble() ?? 0.0,
|
|
timestamp: map['timestamp'] != null
|
|
? DateTime.parse(map['timestamp'])
|
|
: DateTime.now(),
|
|
accuracy: map['accuracy']?.toDouble() ?? 0.0,
|
|
altitude: map['altitude']?.toDouble() ?? 0.0,
|
|
altitudeAccuracy: map['altitudeAccuracy']?.toDouble() ?? 0.0,
|
|
heading: map['heading']?.toDouble() ?? 0.0,
|
|
headingAccuracy: map['headingAccuracy']?.toDouble() ?? 0.0,
|
|
speed: map['speed']?.toDouble() ?? 0.0,
|
|
speedAccuracy: map['speedAccuracy']?.toDouble() ?? 0.0,
|
|
);
|
|
} catch (e) {
|
|
if (kDebugMode) {
|
|
print('Fehler beim Erstellen der Position aus Map: $e');
|
|
}
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/// Cleanup Methode - sollte beim App Shutdown aufgerufen werden
|
|
static Future<void> dispose() async {
|
|
await stopPositionStream();
|
|
}
|
|
}
|
|
|
|
/// Hilfsklasse für Location Permissions Status
|
|
class LocationPermissionHelper {
|
|
static String getPermissionStatusText(LocationPermission permission) {
|
|
switch (permission) {
|
|
case LocationPermission.denied:
|
|
return 'Standort-Berechtigung verweigert';
|
|
case LocationPermission.deniedForever:
|
|
return 'Standort-Berechtigung dauerhaft verweigert';
|
|
case LocationPermission.whileInUse:
|
|
return 'Standort-Berechtigung während App-Nutzung';
|
|
case LocationPermission.always:
|
|
return 'Standort-Berechtigung immer';
|
|
default:
|
|
return 'Unbekannter Berechtigungs-Status';
|
|
}
|
|
}
|
|
|
|
static bool isPermissionGranted(LocationPermission permission) {
|
|
return permission == LocationPermission.whileInUse ||
|
|
permission == LocationPermission.always;
|
|
}
|
|
} |