mirror of
https://github.com/FoggedLens/deflock-app.git
synced 2026-05-25 09:34:16 +02:00
Fix map jitter when touching map during follow-me animations
Cancel in-progress follow-me animations on pointer-down and suppress new ones while any pointer is on the map. Without this, GPS position updates trigger 600ms animateTo() calls that fight with the user's stationary finger, causing visible wiggle — especially at low zoom where small geographic shifts cover more pixels. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -32,6 +32,7 @@ class GpsController {
|
||||
List<OsmNode> Function()? _getNearbyNodes;
|
||||
List<NodeProfile> 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<OsmNode> Function() getNearbyNodes,
|
||||
required List<NodeProfile> 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;
|
||||
}
|
||||
}
|
||||
+43
-27
@@ -80,6 +80,9 @@ class MapViewState extends State<MapView> {
|
||||
|
||||
// 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<MapView> {
|
||||
// Refresh nodes when GPS controller moves the map
|
||||
_refreshNodesFromProvider();
|
||||
},
|
||||
isUserInteracting: () => _activePointers > 0,
|
||||
);
|
||||
|
||||
// Fetch initial cameras
|
||||
@@ -380,10 +384,21 @@ class MapViewState extends State<MapView> {
|
||||
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<MapView> {
|
||||
_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)
|
||||
|
||||
Reference in New Issue
Block a user