117 lines
4.0 KiB
Dart
117 lines
4.0 KiB
Dart
import 'package:flutter/foundation.dart';
|
|
import 'package:geolocator/geolocator.dart';
|
|
|
|
class LocationService {
|
|
static Future<Position?> Function()? mockDeterminator;
|
|
static Stream<Position>? mockStream;
|
|
|
|
static Stream<Position> getPositionStream() {
|
|
if (mockStream != null) {
|
|
return mockStream!;
|
|
}
|
|
return Geolocator.getPositionStream(
|
|
locationSettings: const LocationSettings(
|
|
accuracy: LocationAccuracy.high,
|
|
distanceFilter: 1, // update only when moved 1 meter
|
|
),
|
|
);
|
|
}
|
|
|
|
static Future<Position?> determinePosition() async {
|
|
if (mockDeterminator != null) {
|
|
return mockDeterminator!();
|
|
}
|
|
try {
|
|
bool serviceEnabled = await Geolocator.isLocationServiceEnabled();
|
|
if (!serviceEnabled) {
|
|
debugPrint('Location services are disabled.');
|
|
return null;
|
|
}
|
|
|
|
LocationPermission permission = await Geolocator.checkPermission();
|
|
if (permission == LocationPermission.denied) {
|
|
permission = await Geolocator.requestPermission();
|
|
if (permission == LocationPermission.denied) {
|
|
debugPrint('Location permission denied.');
|
|
return null;
|
|
}
|
|
}
|
|
|
|
if (permission == LocationPermission.deniedForever) {
|
|
debugPrint('Location permission denied forever.');
|
|
return null;
|
|
}
|
|
|
|
// Strategy 1: Try balanced/network-based location (fast, works indoors)
|
|
try {
|
|
final position = await Geolocator.getCurrentPosition(
|
|
desiredAccuracy: LocationAccuracy.medium,
|
|
timeLimit: const Duration(seconds: 7),
|
|
);
|
|
debugPrint(
|
|
'GPS balanced/indoor fix: ${position.latitude}, ${position.longitude} '
|
|
'(accuracy: ${position.accuracy.toStringAsFixed(1)}m)',
|
|
);
|
|
return position;
|
|
} catch (e) {
|
|
debugPrint('getCurrentPosition balanced failed: $e. Trying high accuracy...');
|
|
}
|
|
|
|
// Strategy 2: Try high accuracy (GPS satellites) with forceAndroidLocationManager to bypass cache
|
|
try {
|
|
final position = await Geolocator.getCurrentPosition(
|
|
desiredAccuracy: LocationAccuracy.high,
|
|
forceAndroidLocationManager: true,
|
|
timeLimit: const Duration(seconds: 10),
|
|
);
|
|
debugPrint(
|
|
'GPS high fix: ${position.latitude}, ${position.longitude} '
|
|
'(accuracy: ${position.accuracy.toStringAsFixed(1)}m)',
|
|
);
|
|
return position;
|
|
} catch (e) {
|
|
debugPrint('getCurrentPosition high failed: $e. Checking last known position...');
|
|
}
|
|
|
|
// Strategy 3: Use last known position ONLY if it is super recent (within 2 minutes)
|
|
final lastKnown = await Geolocator.getLastKnownPosition();
|
|
if (lastKnown != null) {
|
|
final age = DateTime.now().difference(lastKnown.timestamp);
|
|
if (age.inMinutes <= 2) {
|
|
debugPrint(
|
|
'Using recent last-known position (${age.inSeconds}s old): '
|
|
'${lastKnown.latitude}, ${lastKnown.longitude}',
|
|
);
|
|
return lastKnown;
|
|
}
|
|
debugPrint(
|
|
'Last-known position is too stale (${age.inMinutes}m old) — '
|
|
'retrying low accuracy GPS fix...',
|
|
);
|
|
}
|
|
|
|
// Strategy 4: Last resort — bypass Google Fused Location and try low accuracy
|
|
// forceAndroidLocationManager forces a real hardware GPS read if enabled
|
|
try {
|
|
final position = await Geolocator.getCurrentPosition(
|
|
desiredAccuracy: LocationAccuracy.low,
|
|
forceAndroidLocationManager: true,
|
|
timeLimit: const Duration(seconds: 5),
|
|
);
|
|
debugPrint(
|
|
'GPS low fix via LocationManager: ${position.latitude}, ${position.longitude} '
|
|
'(accuracy: ${position.accuracy.toStringAsFixed(1)}m)',
|
|
);
|
|
return position;
|
|
} catch (e) {
|
|
debugPrint('All GPS strategies failed: $e');
|
|
return null;
|
|
}
|
|
} catch (e) {
|
|
debugPrint('Error in LocationService: $e');
|
|
return null;
|
|
}
|
|
}
|
|
}
|
|
|