mirror of
https://github.com/FoggedLens/deflock-app.git
synced 2026-02-12 16:52:51 +00:00
Refactor GPS again, fix losing GPS lock
This commit is contained in:
@@ -1,10 +1,15 @@
|
||||
{
|
||||
"2.2.0": {
|
||||
"content": [
|
||||
"• Fixed follow-me sync issues where tracking would sometimes stop working after mode changes"
|
||||
]
|
||||
},
|
||||
"2.1.3": {
|
||||
"content": [
|
||||
"• Fixed nodes losing their greyed-out appearance when map is moved while viewing a node's tag sheet",
|
||||
"• Improved GPS location handling - follow-me button is now greyed out when location is unavailable",
|
||||
"• Added approximate location fallback - if precise location is denied, app will use approximate location",
|
||||
"• Higher frequency GPS updates when follow-me modes are active for smoother tracking (1-second updates vs 5-second)"
|
||||
"• Higher frequency GPS updates when follow-me modes are active for smoother tracking (1-meter updates vs 5-meter)"
|
||||
]
|
||||
},
|
||||
"2.1.2": {
|
||||
|
||||
@@ -14,219 +14,34 @@ import '../../models/node_profile.dart';
|
||||
/// Handles GPS permissions, position streams, and follow-me behavior.
|
||||
class GpsController {
|
||||
StreamSubscription<Position>? _positionSub;
|
||||
LatLng? _currentLatLng;
|
||||
bool _hasLocation = false;
|
||||
Timer? _retryTimer;
|
||||
|
||||
// Location state
|
||||
LatLng? _currentLocation;
|
||||
bool _hasLocation = false;
|
||||
|
||||
// Current tracking settings
|
||||
FollowMeMode _currentFollowMeMode = FollowMeMode.off;
|
||||
|
||||
// Callbacks - set once during initialization
|
||||
AnimatedMapController? _mapController;
|
||||
VoidCallback? _onLocationUpdated;
|
||||
FollowMeMode Function()? _getCurrentFollowMeMode;
|
||||
bool Function()? _getProximityAlertsEnabled;
|
||||
int Function()? _getProximityAlertDistance;
|
||||
List<OsmNode> Function()? _getNearbyNodes;
|
||||
List<NodeProfile> Function()? _getEnabledProfiles;
|
||||
VoidCallback? _onMapMovedProgrammatically;
|
||||
|
||||
/// Get the current GPS location (if available)
|
||||
LatLng? get currentLocation => _currentLatLng;
|
||||
LatLng? get currentLocation => _currentLocation;
|
||||
|
||||
/// Whether we currently have a valid GPS location
|
||||
bool get hasLocation => _hasLocation;
|
||||
|
||||
/// Initialize GPS location tracking
|
||||
Future<void> initializeLocation() async {
|
||||
// Check if location services are enabled first
|
||||
bool serviceEnabled = await Geolocator.isLocationServiceEnabled();
|
||||
if (!serviceEnabled) {
|
||||
debugPrint('[GpsController] Location services disabled');
|
||||
_hasLocation = false;
|
||||
_scheduleRetry();
|
||||
return;
|
||||
}
|
||||
|
||||
final perm = await Geolocator.requestPermission();
|
||||
debugPrint('[GpsController] Location permission result: $perm');
|
||||
|
||||
if (perm == LocationPermission.denied ||
|
||||
perm == LocationPermission.deniedForever) {
|
||||
debugPrint('[GpsController] Precise location permission denied, trying approximate location');
|
||||
|
||||
// Try approximate location as fallback
|
||||
try {
|
||||
await Geolocator.getCurrentPosition(
|
||||
desiredAccuracy: LocationAccuracy.low,
|
||||
timeLimit: const Duration(seconds: 10),
|
||||
);
|
||||
debugPrint('[GpsController] Approximate location available, proceeding with location stream');
|
||||
// If we got here, approximate location works, continue with stream setup below
|
||||
} catch (e) {
|
||||
debugPrint('[GpsController] Approximate location also unavailable: $e');
|
||||
_hasLocation = false;
|
||||
_scheduleRetry();
|
||||
return;
|
||||
}
|
||||
} else if (perm == LocationPermission.whileInUse || perm == LocationPermission.always) {
|
||||
debugPrint('[GpsController] Location permission granted: $perm');
|
||||
// Permission is granted, continue with normal setup
|
||||
} else {
|
||||
debugPrint('[GpsController] Unexpected permission state: $perm');
|
||||
_hasLocation = false;
|
||||
_scheduleRetry();
|
||||
return;
|
||||
}
|
||||
|
||||
_positionSub?.cancel(); // Cancel any existing subscription
|
||||
debugPrint('[GpsController] Starting GPS position stream');
|
||||
_positionSub = Geolocator.getPositionStream(
|
||||
locationSettings: const LocationSettings(
|
||||
accuracy: LocationAccuracy.high,
|
||||
distanceFilter: 5, // Update when moved at least 5 meters (standard frequency)
|
||||
),
|
||||
).listen(
|
||||
(Position position) {
|
||||
final latLng = LatLng(position.latitude, position.longitude);
|
||||
_currentLatLng = latLng;
|
||||
if (!_hasLocation) {
|
||||
debugPrint('[GpsController] GPS location acquired');
|
||||
}
|
||||
_hasLocation = true;
|
||||
_cancelRetry(); // Got location, stop retrying
|
||||
debugPrint('[GpsController] GPS position updated: ${latLng.latitude}, ${latLng.longitude} (accuracy: ${position.accuracy}m)');
|
||||
},
|
||||
onError: (error) {
|
||||
debugPrint('[GpsController] Position stream error: $error');
|
||||
if (_hasLocation) {
|
||||
debugPrint('[GpsController] GPS location lost, starting retry attempts');
|
||||
}
|
||||
_hasLocation = false;
|
||||
_currentLatLng = null;
|
||||
_scheduleRetry(); // Lost location, start retrying
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/// Retry location initialization (e.g., after permission granted)
|
||||
Future<void> retryLocationInit() async {
|
||||
debugPrint('[GpsController] Manual retry of location initialization');
|
||||
_cancelRetry(); // Cancel automatic retries, this is a manual retry
|
||||
await initializeLocation();
|
||||
}
|
||||
|
||||
/// Handle follow-me mode changes and animate map accordingly
|
||||
void handleFollowMeModeChange({
|
||||
required FollowMeMode newMode,
|
||||
required FollowMeMode oldMode,
|
||||
required AnimatedMapController controller,
|
||||
VoidCallback? onMapMovedProgrammatically,
|
||||
}) {
|
||||
debugPrint('[GpsController] Follow-me mode changed: $oldMode → $newMode');
|
||||
|
||||
// Restart position stream with appropriate frequency for new mode
|
||||
_restartPositionStream(newMode);
|
||||
|
||||
// Only act when follow-me is first enabled and we have a current location
|
||||
if (newMode != FollowMeMode.off &&
|
||||
oldMode == FollowMeMode.off &&
|
||||
_currentLatLng != null) {
|
||||
|
||||
try {
|
||||
if (newMode == FollowMeMode.follow) {
|
||||
controller.animateTo(
|
||||
dest: _currentLatLng!,
|
||||
zoom: controller.mapController.camera.zoom,
|
||||
duration: kFollowMeAnimationDuration,
|
||||
curve: Curves.easeOut,
|
||||
);
|
||||
onMapMovedProgrammatically?.call();
|
||||
} else if (newMode == FollowMeMode.rotating) {
|
||||
// When switching to rotating mode, reset to north-up first
|
||||
controller.animateTo(
|
||||
dest: _currentLatLng!,
|
||||
zoom: controller.mapController.camera.zoom,
|
||||
rotation: 0.0,
|
||||
duration: kFollowMeAnimationDuration,
|
||||
curve: Curves.easeOut,
|
||||
);
|
||||
onMapMovedProgrammatically?.call();
|
||||
}
|
||||
} catch (e) {
|
||||
debugPrint('[GpsController] MapController not ready for follow-me change: $e');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Process GPS position updates and handle follow-me animations
|
||||
void processPositionUpdate({
|
||||
required Position position,
|
||||
required FollowMeMode followMeMode,
|
||||
required AnimatedMapController controller,
|
||||
required VoidCallback onLocationUpdated,
|
||||
// Optional parameters for proximity alerts
|
||||
bool proximityAlertsEnabled = false,
|
||||
int proximityAlertDistance = 200,
|
||||
List<OsmNode> nearbyNodes = const [],
|
||||
List<NodeProfile> enabledProfiles = const [],
|
||||
// Optional callback when map is moved programmatically
|
||||
VoidCallback? onMapMovedProgrammatically,
|
||||
|
||||
}) {
|
||||
final latLng = LatLng(position.latitude, position.longitude);
|
||||
_currentLatLng = latLng;
|
||||
_hasLocation = true;
|
||||
_cancelRetry(); // Got location, stop any retries
|
||||
|
||||
// Notify that location was updated (for setState, etc.)
|
||||
onLocationUpdated();
|
||||
|
||||
// Check proximity alerts if enabled
|
||||
if (proximityAlertsEnabled && nearbyNodes.isNotEmpty) {
|
||||
ProximityAlertService().checkProximity(
|
||||
userLocation: latLng,
|
||||
nodes: nearbyNodes,
|
||||
enabledProfiles: enabledProfiles,
|
||||
alertDistance: proximityAlertDistance,
|
||||
);
|
||||
}
|
||||
|
||||
// Handle follow-me animations if enabled - use current mode from app state
|
||||
if (followMeMode != FollowMeMode.off) {
|
||||
debugPrint('[GpsController] GPS position update: ${latLng.latitude}, ${latLng.longitude}, follow-me: $followMeMode');
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
try {
|
||||
if (followMeMode == FollowMeMode.follow) {
|
||||
// Follow position only, keep current rotation
|
||||
controller.animateTo(
|
||||
dest: latLng,
|
||||
zoom: controller.mapController.camera.zoom,
|
||||
rotation: controller.mapController.camera.rotation,
|
||||
duration: kFollowMeAnimationDuration,
|
||||
curve: Curves.easeOut,
|
||||
);
|
||||
|
||||
// Notify that we moved the map programmatically (for node refresh)
|
||||
onMapMovedProgrammatically?.call();
|
||||
} else if (followMeMode == FollowMeMode.rotating) {
|
||||
// Follow position and rotation based on heading
|
||||
final heading = position.heading;
|
||||
final speed = position.speed; // Speed in m/s
|
||||
|
||||
// Only apply rotation if moving fast enough to avoid wild spinning when stationary
|
||||
final shouldRotate = !speed.isNaN && speed >= kMinSpeedForRotationMps && !heading.isNaN;
|
||||
final rotation = shouldRotate ? -heading : controller.mapController.camera.rotation;
|
||||
|
||||
controller.animateTo(
|
||||
dest: latLng,
|
||||
zoom: controller.mapController.camera.zoom,
|
||||
rotation: rotation,
|
||||
duration: kFollowMeAnimationDuration,
|
||||
curve: Curves.easeOut,
|
||||
);
|
||||
|
||||
// Notify that we moved the map programmatically (for node refresh)
|
||||
onMapMovedProgrammatically?.call();
|
||||
}
|
||||
} catch (e) {
|
||||
debugPrint('[GpsController] MapController not ready for position animation: $e');
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// Initialize GPS with custom position processing callback
|
||||
Future<void> initializeWithCallback({
|
||||
required FollowMeMode followMeMode,
|
||||
required AnimatedMapController controller,
|
||||
/// Initialize GPS tracking with callbacks for UI integration
|
||||
Future<void> initialize({
|
||||
required AnimatedMapController mapController,
|
||||
required VoidCallback onLocationUpdated,
|
||||
required FollowMeMode Function() getCurrentFollowMeMode,
|
||||
required bool Function() getProximityAlertsEnabled,
|
||||
@@ -234,92 +49,262 @@ class GpsController {
|
||||
required List<OsmNode> Function() getNearbyNodes,
|
||||
required List<NodeProfile> Function() getEnabledProfiles,
|
||||
VoidCallback? onMapMovedProgrammatically,
|
||||
|
||||
}) async {
|
||||
// Check if location services are enabled first
|
||||
debugPrint('[GpsController] Initializing GPS controller');
|
||||
|
||||
// Store callbacks
|
||||
_mapController = mapController;
|
||||
_onLocationUpdated = onLocationUpdated;
|
||||
_getCurrentFollowMeMode = getCurrentFollowMeMode;
|
||||
_getProximityAlertsEnabled = getProximityAlertsEnabled;
|
||||
_getProximityAlertDistance = getProximityAlertDistance;
|
||||
_getNearbyNodes = getNearbyNodes;
|
||||
_getEnabledProfiles = getEnabledProfiles;
|
||||
_onMapMovedProgrammatically = onMapMovedProgrammatically;
|
||||
|
||||
// Start location tracking
|
||||
await _startLocationTracking();
|
||||
}
|
||||
|
||||
/// Update follow-me mode and restart tracking with appropriate frequency
|
||||
void updateFollowMeMode({
|
||||
required FollowMeMode newMode,
|
||||
required FollowMeMode oldMode,
|
||||
}) {
|
||||
debugPrint('[GpsController] Follow-me mode changed: $oldMode → $newMode');
|
||||
_currentFollowMeMode = newMode;
|
||||
|
||||
// Restart tracking with new frequency
|
||||
_startLocationTracking();
|
||||
|
||||
// Handle initial animation when follow-me is first enabled
|
||||
if (newMode != FollowMeMode.off &&
|
||||
oldMode == FollowMeMode.off &&
|
||||
_currentLocation != null &&
|
||||
_mapController != null) {
|
||||
|
||||
_animateToCurrentLocation(newMode);
|
||||
}
|
||||
}
|
||||
|
||||
/// Manually retry location initialization (e.g., after permission granted)
|
||||
Future<void> retryLocationInit() async {
|
||||
debugPrint('[GpsController] Manual retry of location initialization');
|
||||
_cancelRetry();
|
||||
await _startLocationTracking();
|
||||
}
|
||||
|
||||
/// Start or restart GPS location tracking
|
||||
Future<void> _startLocationTracking() async {
|
||||
_stopLocationTracking(); // Clean slate
|
||||
|
||||
// Check location services availability
|
||||
if (!await _checkLocationAvailability()) {
|
||||
_scheduleRetry();
|
||||
return;
|
||||
}
|
||||
|
||||
// Determine frequency settings based on current follow-me mode
|
||||
final settings = _getLocationSettings();
|
||||
|
||||
debugPrint('[GpsController] Starting GPS position stream (${_currentFollowMeMode == FollowMeMode.off ? 'standard' : 'high'} frequency)');
|
||||
|
||||
try {
|
||||
_positionSub = Geolocator.getPositionStream(locationSettings: settings).listen(
|
||||
_onPositionReceived,
|
||||
onError: _onPositionError,
|
||||
);
|
||||
} catch (e) {
|
||||
debugPrint('[GpsController] Failed to start position stream: $e');
|
||||
_hasLocation = false;
|
||||
_scheduleRetry();
|
||||
}
|
||||
}
|
||||
|
||||
/// Check if location services are available and permissions are granted
|
||||
Future<bool> _checkLocationAvailability() async {
|
||||
// Check if location services are enabled
|
||||
bool serviceEnabled = await Geolocator.isLocationServiceEnabled();
|
||||
if (!serviceEnabled) {
|
||||
debugPrint('[GpsController] Location services disabled');
|
||||
_hasLocation = false;
|
||||
_scheduleRetry();
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check permissions
|
||||
final perm = await Geolocator.requestPermission();
|
||||
debugPrint('[GpsController] Location permission result: $perm');
|
||||
|
||||
if (perm == LocationPermission.denied ||
|
||||
perm == LocationPermission.deniedForever) {
|
||||
debugPrint('[GpsController] Precise location permission denied, trying approximate location');
|
||||
|
||||
if (perm == LocationPermission.whileInUse || perm == LocationPermission.always) {
|
||||
debugPrint('[GpsController] Location permission granted: $perm');
|
||||
return true;
|
||||
}
|
||||
|
||||
if (perm == LocationPermission.denied || perm == LocationPermission.deniedForever) {
|
||||
// Try approximate location as fallback
|
||||
debugPrint('[GpsController] Precise location permission denied, trying approximate location');
|
||||
try {
|
||||
await Geolocator.getCurrentPosition(
|
||||
desiredAccuracy: LocationAccuracy.low,
|
||||
timeLimit: const Duration(seconds: 10),
|
||||
);
|
||||
debugPrint('[GpsController] Approximate location available, proceeding with location stream');
|
||||
// If we got here, approximate location works, continue with stream setup below
|
||||
debugPrint('[GpsController] Approximate location available');
|
||||
return true;
|
||||
} catch (e) {
|
||||
debugPrint('[GpsController] Approximate location also unavailable: $e');
|
||||
_hasLocation = false;
|
||||
_scheduleRetry();
|
||||
return;
|
||||
}
|
||||
} else if (perm == LocationPermission.whileInUse || perm == LocationPermission.always) {
|
||||
debugPrint('[GpsController] Location permission granted: $perm');
|
||||
// Permission is granted, continue with normal setup
|
||||
} else {
|
||||
debugPrint('[GpsController] Unexpected permission state: $perm');
|
||||
_hasLocation = false;
|
||||
_scheduleRetry();
|
||||
return;
|
||||
}
|
||||
|
||||
debugPrint('[GpsController] Location unavailable, permission: $perm');
|
||||
_hasLocation = false;
|
||||
return false;
|
||||
}
|
||||
|
||||
_positionSub?.cancel(); // Cancel any existing subscription
|
||||
debugPrint('[GpsController] Starting GPS position stream');
|
||||
_positionSub = Geolocator.getPositionStream(
|
||||
locationSettings: const LocationSettings(
|
||||
/// Get location settings based on current follow-me mode
|
||||
LocationSettings _getLocationSettings() {
|
||||
if (_currentFollowMeMode != FollowMeMode.off) {
|
||||
// High frequency for follow-me modes
|
||||
return const LocationSettings(
|
||||
accuracy: LocationAccuracy.high,
|
||||
distanceFilter: 5, // Update when moved at least 5 meters (standard frequency)
|
||||
),
|
||||
).listen(
|
||||
(Position position) {
|
||||
if (!_hasLocation) {
|
||||
debugPrint('[GpsController] GPS location acquired');
|
||||
distanceFilter: 1, // Update when moved 1+ meter
|
||||
);
|
||||
} else {
|
||||
// Standard frequency when not following
|
||||
return const LocationSettings(
|
||||
accuracy: LocationAccuracy.high,
|
||||
distanceFilter: 5, // Update when moved 5+ meters
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Handle position updates from GPS stream
|
||||
void _onPositionReceived(Position position) {
|
||||
final newLocation = LatLng(position.latitude, position.longitude);
|
||||
_currentLocation = newLocation;
|
||||
|
||||
if (!_hasLocation) {
|
||||
debugPrint('[GpsController] GPS location acquired');
|
||||
}
|
||||
_hasLocation = true;
|
||||
_cancelRetry();
|
||||
|
||||
debugPrint('[GpsController] GPS position updated: ${newLocation.latitude}, ${newLocation.longitude} (accuracy: ${position.accuracy}m)');
|
||||
|
||||
// Notify UI that location was updated
|
||||
_onLocationUpdated?.call();
|
||||
|
||||
// Handle proximity alerts
|
||||
_checkProximityAlerts(newLocation);
|
||||
|
||||
// Handle follow-me animations
|
||||
_handleFollowMeUpdate(position, newLocation);
|
||||
}
|
||||
|
||||
/// Handle position stream errors
|
||||
void _onPositionError(error) {
|
||||
debugPrint('[GpsController] Position stream error: $error');
|
||||
if (_hasLocation) {
|
||||
debugPrint('[GpsController] GPS location lost, starting retry attempts');
|
||||
}
|
||||
_hasLocation = false;
|
||||
_currentLocation = null;
|
||||
_onLocationUpdated?.call();
|
||||
_scheduleRetry();
|
||||
}
|
||||
|
||||
/// Check proximity alerts if enabled
|
||||
void _checkProximityAlerts(LatLng userLocation) {
|
||||
final proximityEnabled = _getProximityAlertsEnabled?.call() ?? false;
|
||||
final nearbyNodes = _getNearbyNodes?.call() ?? [];
|
||||
|
||||
if (proximityEnabled && nearbyNodes.isNotEmpty) {
|
||||
final alertDistance = _getProximityAlertDistance?.call() ?? 200;
|
||||
final enabledProfiles = _getEnabledProfiles?.call() ?? [];
|
||||
|
||||
ProximityAlertService().checkProximity(
|
||||
userLocation: userLocation,
|
||||
nodes: nearbyNodes,
|
||||
enabledProfiles: enabledProfiles,
|
||||
alertDistance: alertDistance,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Handle follow-me animations and map updates
|
||||
void _handleFollowMeUpdate(Position position, LatLng location) {
|
||||
// Get current follow-me mode from app state (in case it changed)
|
||||
final followMeMode = _getCurrentFollowMeMode?.call() ?? FollowMeMode.off;
|
||||
|
||||
if (followMeMode == FollowMeMode.off || _mapController == null) {
|
||||
return; // Not following or no map controller
|
||||
}
|
||||
|
||||
debugPrint('[GpsController] GPS position update for follow-me: ${location.latitude}, ${location.longitude}, mode: $followMeMode');
|
||||
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
try {
|
||||
if (followMeMode == FollowMeMode.follow) {
|
||||
// Follow position only, preserve current rotation
|
||||
_mapController!.animateTo(
|
||||
dest: location,
|
||||
zoom: _mapController!.mapController.camera.zoom,
|
||||
rotation: _mapController!.mapController.camera.rotation,
|
||||
duration: kFollowMeAnimationDuration,
|
||||
curve: Curves.easeOut,
|
||||
);
|
||||
} else if (followMeMode == FollowMeMode.rotating) {
|
||||
// Follow position and rotation based on heading
|
||||
final heading = position.heading;
|
||||
final speed = position.speed;
|
||||
|
||||
// Only apply rotation if moving fast enough to avoid wild spinning
|
||||
final shouldRotate = !speed.isNaN && speed >= kMinSpeedForRotationMps && !heading.isNaN;
|
||||
final rotation = shouldRotate ? -heading : _mapController!.mapController.camera.rotation;
|
||||
|
||||
_mapController!.animateTo(
|
||||
dest: location,
|
||||
zoom: _mapController!.mapController.camera.zoom,
|
||||
rotation: rotation,
|
||||
duration: kFollowMeAnimationDuration,
|
||||
curve: Curves.easeOut,
|
||||
);
|
||||
}
|
||||
_hasLocation = true;
|
||||
_cancelRetry(); // Got location, stop retrying
|
||||
|
||||
// Get the current follow-me mode from the app state each time
|
||||
final currentFollowMeMode = getCurrentFollowMeMode();
|
||||
final proximityAlertsEnabled = getProximityAlertsEnabled();
|
||||
final proximityAlertDistance = getProximityAlertDistance();
|
||||
final nearbyNodes = getNearbyNodes();
|
||||
final enabledProfiles = getEnabledProfiles();
|
||||
processPositionUpdate(
|
||||
position: position,
|
||||
followMeMode: currentFollowMeMode,
|
||||
controller: controller,
|
||||
onLocationUpdated: onLocationUpdated,
|
||||
proximityAlertsEnabled: proximityAlertsEnabled,
|
||||
proximityAlertDistance: proximityAlertDistance,
|
||||
nearbyNodes: nearbyNodes,
|
||||
enabledProfiles: enabledProfiles,
|
||||
onMapMovedProgrammatically: onMapMovedProgrammatically,
|
||||
// Notify that we moved the map programmatically (for node refresh)
|
||||
_onMapMovedProgrammatically?.call();
|
||||
} catch (e) {
|
||||
debugPrint('[GpsController] MapController not ready for position animation: $e');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// Animate to current location when follow-me is first enabled
|
||||
void _animateToCurrentLocation(FollowMeMode mode) {
|
||||
if (_currentLocation == null || _mapController == null) return;
|
||||
|
||||
try {
|
||||
if (mode == FollowMeMode.follow) {
|
||||
_mapController!.animateTo(
|
||||
dest: _currentLocation!,
|
||||
zoom: _mapController!.mapController.camera.zoom,
|
||||
duration: kFollowMeAnimationDuration,
|
||||
curve: Curves.easeOut,
|
||||
);
|
||||
},
|
||||
onError: (error) {
|
||||
debugPrint('[GpsController] Position stream error: $error');
|
||||
if (_hasLocation) {
|
||||
debugPrint('[GpsController] GPS location lost, starting retry attempts');
|
||||
}
|
||||
_hasLocation = false;
|
||||
_currentLatLng = null;
|
||||
onLocationUpdated(); // Notify UI that location was lost
|
||||
_scheduleRetry(); // Lost location, start retrying
|
||||
},
|
||||
);
|
||||
} else if (mode == FollowMeMode.rotating) {
|
||||
// When switching to rotating mode, reset to north-up first
|
||||
_mapController!.animateTo(
|
||||
dest: _currentLocation!,
|
||||
zoom: _mapController!.mapController.camera.zoom,
|
||||
rotation: 0.0,
|
||||
duration: kFollowMeAnimationDuration,
|
||||
curve: Curves.easeOut,
|
||||
);
|
||||
}
|
||||
|
||||
_onMapMovedProgrammatically?.call();
|
||||
} catch (e) {
|
||||
debugPrint('[GpsController] MapController not ready for initial follow-me animation: $e');
|
||||
}
|
||||
}
|
||||
|
||||
/// Schedule periodic retry attempts to get location
|
||||
@@ -327,7 +312,7 @@ class GpsController {
|
||||
_retryTimer?.cancel();
|
||||
_retryTimer = Timer.periodic(const Duration(seconds: 15), (timer) {
|
||||
debugPrint('[GpsController] Automatic retry of location initialization (attempt ${timer.tick})');
|
||||
initializeLocation(); // This will cancel the timer if successful
|
||||
_startLocationTracking();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -340,80 +325,26 @@ class GpsController {
|
||||
}
|
||||
}
|
||||
|
||||
/// Restart position stream with frequency optimized for follow-me mode
|
||||
void _restartPositionStream(FollowMeMode followMeMode) {
|
||||
if (_positionSub == null || !_hasLocation) {
|
||||
// No active stream or no location - let normal initialization handle it
|
||||
return;
|
||||
}
|
||||
|
||||
_positionSub?.cancel();
|
||||
|
||||
// Use higher frequency when follow-me is enabled
|
||||
if (followMeMode != FollowMeMode.off) {
|
||||
debugPrint('[GpsController] Starting high-frequency GPS updates for follow-me mode');
|
||||
_positionSub = Geolocator.getPositionStream(
|
||||
locationSettings: const LocationSettings(
|
||||
accuracy: LocationAccuracy.high,
|
||||
distanceFilter: 1, // Update when moved at least 1 meter
|
||||
),
|
||||
).listen(
|
||||
(Position position) {
|
||||
final latLng = LatLng(position.latitude, position.longitude);
|
||||
_currentLatLng = latLng;
|
||||
if (!_hasLocation) {
|
||||
debugPrint('[GpsController] GPS location acquired');
|
||||
}
|
||||
_hasLocation = true;
|
||||
_cancelRetry(); // Got location, stop retrying
|
||||
debugPrint('[GpsController] GPS position updated: ${latLng.latitude}, ${latLng.longitude} (accuracy: ${position.accuracy}m)');
|
||||
},
|
||||
onError: (error) {
|
||||
debugPrint('[GpsController] Position stream error: $error');
|
||||
if (_hasLocation) {
|
||||
debugPrint('[GpsController] GPS location lost, starting retry attempts');
|
||||
}
|
||||
_hasLocation = false;
|
||||
_currentLatLng = null;
|
||||
_scheduleRetry(); // Lost location, start retrying
|
||||
},
|
||||
);
|
||||
} else {
|
||||
debugPrint('[GpsController] Starting standard-frequency GPS updates');
|
||||
_positionSub = Geolocator.getPositionStream(
|
||||
locationSettings: const LocationSettings(
|
||||
accuracy: LocationAccuracy.high,
|
||||
distanceFilter: 5, // Update when moved at least 5 meters
|
||||
),
|
||||
).listen(
|
||||
(Position position) {
|
||||
final latLng = LatLng(position.latitude, position.longitude);
|
||||
_currentLatLng = latLng;
|
||||
if (!_hasLocation) {
|
||||
debugPrint('[GpsController] GPS location acquired');
|
||||
}
|
||||
_hasLocation = true;
|
||||
_cancelRetry(); // Got location, stop retrying
|
||||
debugPrint('[GpsController] GPS position updated: ${latLng.latitude}, ${latLng.longitude} (accuracy: ${position.accuracy}m)');
|
||||
},
|
||||
onError: (error) {
|
||||
debugPrint('[GpsController] Position stream error: $error');
|
||||
if (_hasLocation) {
|
||||
debugPrint('[GpsController] GPS location lost, starting retry attempts');
|
||||
}
|
||||
_hasLocation = false;
|
||||
_currentLatLng = null;
|
||||
_scheduleRetry(); // Lost location, start retrying
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Dispose of GPS resources
|
||||
void dispose() {
|
||||
/// Stop location tracking and clean up
|
||||
void _stopLocationTracking() {
|
||||
_positionSub?.cancel();
|
||||
_positionSub = null;
|
||||
}
|
||||
|
||||
/// Dispose of all GPS resources
|
||||
void dispose() {
|
||||
debugPrint('[GpsController] Disposing GPS controller');
|
||||
_stopLocationTracking();
|
||||
_cancelRetry();
|
||||
debugPrint('[GpsController] GPS controller disposed');
|
||||
|
||||
// Clear callbacks
|
||||
_mapController = null;
|
||||
_onLocationUpdated = null;
|
||||
_getCurrentFollowMeMode = null;
|
||||
_getProximityAlertsEnabled = null;
|
||||
_getProximityAlertDistance = null;
|
||||
_getNearbyNodes = null;
|
||||
_getEnabledProfiles = null;
|
||||
_onMapMovedProgrammatically = null;
|
||||
}
|
||||
}
|
||||
@@ -120,9 +120,8 @@ class MapViewState extends State<MapView> {
|
||||
});
|
||||
|
||||
// Initialize GPS with callback for position updates and follow-me
|
||||
_gpsController.initializeWithCallback(
|
||||
followMeMode: widget.followMeMode,
|
||||
controller: _controller,
|
||||
_gpsController.initialize(
|
||||
mapController: _controller,
|
||||
onLocationUpdated: () {
|
||||
setState(() {});
|
||||
widget.onLocationStatusChanged?.call(); // Notify parent about location status change
|
||||
@@ -195,7 +194,6 @@ class MapViewState extends State<MapView> {
|
||||
// Refresh nodes when GPS controller moves the map
|
||||
_refreshNodesFromProvider();
|
||||
},
|
||||
|
||||
);
|
||||
|
||||
// Fetch initial cameras
|
||||
@@ -267,13 +265,9 @@ class MapViewState extends State<MapView> {
|
||||
super.didUpdateWidget(oldWidget);
|
||||
// Handle follow-me mode changes - only if it actually changed
|
||||
if (widget.followMeMode != oldWidget.followMeMode) {
|
||||
_gpsController.handleFollowMeModeChange(
|
||||
_gpsController.updateFollowMeMode(
|
||||
newMode: widget.followMeMode,
|
||||
oldMode: oldWidget.followMeMode,
|
||||
controller: _controller,
|
||||
onMapMovedProgrammatically: () {
|
||||
_refreshNodesFromProvider();
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
name: deflockapp
|
||||
description: Map public surveillance infrastructure with OpenStreetMap
|
||||
publish_to: "none"
|
||||
version: 2.1.3+36 # The thing after the + is the version code, incremented with each release
|
||||
version: 2.2.0+36 # The thing after the + is the version code, incremented with each release
|
||||
|
||||
environment:
|
||||
sdk: ">=3.5.0 <4.0.0" # oauth2_client 4.x needs Dart 3.5+
|
||||
|
||||
Reference in New Issue
Block a user