diff --git a/lib/widgets/map/gps_controller.dart b/lib/widgets/map/gps_controller.dart index 33e7c04..a40fdf4 100644 --- a/lib/widgets/map/gps_controller.dart +++ b/lib/widgets/map/gps_controller.dart @@ -32,6 +32,7 @@ class GpsController { List Function()? _getNearbyNodes; List Function()? _getEnabledProfiles; VoidCallback? _onMapMovedProgrammatically; + bool Function()? _isUserInteracting; /// Get the current GPS location (if available) LatLng? get currentLocation => _currentLocation; @@ -49,6 +50,7 @@ class GpsController { required List Function() getNearbyNodes, required List Function() getEnabledProfiles, VoidCallback? onMapMovedProgrammatically, + bool Function()? isUserInteracting, }) async { debugPrint('[GpsController] Initializing GPS controller'); @@ -61,7 +63,8 @@ class GpsController { _getNearbyNodes = getNearbyNodes; _getEnabledProfiles = getEnabledProfiles; _onMapMovedProgrammatically = onMapMovedProgrammatically; - + _isUserInteracting = isUserInteracting; + // Start location tracking await _startLocationTracking(); } @@ -235,9 +238,10 @@ class GpsController { if (followMeMode == FollowMeMode.off || _mapController == null) { return; } - WidgetsBinding.instance.addPostFrameCallback((_) { try { + if (_isUserInteracting?.call() == true) return; + if (followMeMode == FollowMeMode.follow) { // Follow position, preserve rotation _mapController!.animateTo( @@ -352,5 +356,6 @@ class GpsController { _getNearbyNodes = null; _getEnabledProfiles = null; _onMapMovedProgrammatically = null; + _isUserInteracting = null; } } \ No newline at end of file diff --git a/lib/widgets/map_view.dart b/lib/widgets/map_view.dart index 9407569..96ef2b3 100644 --- a/lib/widgets/map_view.dart +++ b/lib/widgets/map_view.dart @@ -80,6 +80,9 @@ class MapViewState extends State { // State for proximity alert banner bool _showProximityBanner = false; + + // Track active pointers to suppress follow-me animations during touch + int _activePointers = 0; @@ -189,6 +192,7 @@ class MapViewState extends State { // Refresh nodes when GPS controller moves the map _refreshNodesFromProvider(); }, + isUserInteracting: () => _activePointers > 0, ); // Fetch initial cameras @@ -380,10 +384,21 @@ class MapViewState extends State { children: [ SheetAwareMap( sheetHeight: widget.sheetHeight, - child: FlutterMap( - key: ValueKey('map_${appState.offlineMode}_${appState.selectedTileType?.id ?? 'none'}_${_tileManager.mapRebuildKey}'), - mapController: _controller.mapController, - options: MapOptions( + child: Listener( + onPointerDown: (_) { + _activePointers++; + _controller.stopAnimations(); + }, + onPointerUp: (_) { + if (_activePointers > 0) _activePointers--; + }, + onPointerCancel: (_) { + if (_activePointers > 0) _activePointers--; + }, + child: FlutterMap( + key: ValueKey('map_${appState.offlineMode}_${appState.selectedTileType?.id ?? 'none'}_${_tileManager.mapRebuildKey}'), + mapController: _controller.mapController, + options: MapOptions( initialCenter: _gpsController.currentLocation ?? _positionManager.initialLocation ?? LatLng(37.7749, -122.4194), initialZoom: _positionManager.initialZoom ?? 15, minZoom: 1.0, @@ -486,30 +501,31 @@ class MapViewState extends State { _dataManager.showZoomWarningIfNeeded(context, pos.zoom, appState.uploadMode); } }, + ), + children: [ + _tileManager.buildTileLayer( + selectedProvider: appState.selectedTileProvider, + selectedTileType: appState.selectedTileType, + ), + cameraLayers, + // Custom scale bar that respects user's distance unit preference + Builder( + builder: (context) { + final safeArea = MediaQuery.of(context).padding; + return CustomScaleBar( + alignment: Alignment.bottomLeft, + padding: EdgeInsets.only( + left: leftPositionWithSafeArea(8, safeArea), + bottom: bottomPositionFromButtonBar(kScaleBarSpacingAboveButtonBar, safeArea.bottom), + ), + maxWidthPx: 120, + barHeight: 8, + ); + }, + ), + ], + ), ), - children: [ - _tileManager.buildTileLayer( - selectedProvider: appState.selectedTileProvider, - selectedTileType: appState.selectedTileType, - ), - cameraLayers, - // Custom scale bar that respects user's distance unit preference - Builder( - builder: (context) { - final safeArea = MediaQuery.of(context).padding; - return CustomScaleBar( - alignment: Alignment.bottomLeft, - padding: EdgeInsets.only( - left: leftPositionWithSafeArea(8, safeArea), - bottom: bottomPositionFromButtonBar(kScaleBarSpacingAboveButtonBar, safeArea.bottom) - ), - maxWidthPx: 120, - barHeight: 8, - ); - }, - ), - ], - ), ), // All map overlays (mode indicator, zoom, attribution, add pin)