Fix north-up UX

This commit is contained in:
stopflock
2025-10-22 18:46:48 -05:00
parent c8e2727f69
commit 27c404687a
4 changed files with 52 additions and 5 deletions

View File

@@ -1,3 +1,4 @@
import 'dart:async';
import 'dart:math';
import 'package:flutter/material.dart';
import 'package:flutter_map/flutter_map.dart';
@@ -21,10 +22,16 @@ class CompassIndicator extends StatefulWidget {
}
class _CompassIndicatorState extends State<CompassIndicator> {
Timer? _animationTimer;
@override
void dispose() {
_animationTimer?.cancel();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Consumer<AppState>(
builder: (context, appState, child) {
final appState = context.watch<AppState>();
// Get current map rotation in degrees
double rotationDegrees = 0.0;
try {
@@ -46,6 +53,10 @@ class _CompassIndicatorState extends State<CompassIndicator> {
onTap: isDisabled ? null : () {
// Animate to north-up orientation
try {
// Cancel any existing animation timer
_animationTimer?.cancel();
// Start animation
widget.mapController.animateTo(
dest: widget.mapController.mapController.camera.center,
zoom: widget.mapController.mapController.camera.zoom,
@@ -53,6 +64,23 @@ class _CompassIndicatorState extends State<CompassIndicator> {
duration: const Duration(milliseconds: 500),
curve: Curves.easeOut,
);
// Start timer to force compass updates during animation
// Update every 16ms (~60fps) for smooth visual rotation
_animationTimer = Timer.periodic(const Duration(milliseconds: 16), (timer) {
if (mounted) {
setState(() {});
}
});
// Stop the timer after animation completes (with small buffer)
Timer(const Duration(milliseconds: 550), () {
_animationTimer?.cancel();
_animationTimer = null;
if (mounted) {
setState(() {}); // Final update to ensure correct end state
}
});
} catch (_) {
// Controller not ready, ignore
}
@@ -132,7 +160,5 @@ class _CompassIndicatorState extends State<CompassIndicator> {
),
),
);
},
);
}
}

View File

@@ -46,6 +46,7 @@ class GpsController {
required FollowMeMode newMode,
required FollowMeMode oldMode,
required AnimatedMapController controller,
VoidCallback? onMapMovedProgrammatically,
}) {
debugPrint('[GpsController] Follow-me mode changed: $oldMode$newMode');
@@ -62,6 +63,7 @@ class GpsController {
duration: kFollowMeAnimationDuration,
curve: Curves.easeOut,
);
onMapMovedProgrammatically?.call();
} else if (newMode == FollowMeMode.rotating) {
// When switching to rotating mode, reset to north-up first
controller.animateTo(
@@ -71,6 +73,7 @@ class GpsController {
duration: kFollowMeAnimationDuration,
curve: Curves.easeOut,
);
onMapMovedProgrammatically?.call();
}
} catch (e) {
debugPrint('[GpsController] MapController not ready for follow-me change: $e');
@@ -89,6 +92,8 @@ class GpsController {
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);
@@ -121,6 +126,9 @@ class GpsController {
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;
@@ -137,6 +145,9 @@ class GpsController {
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');
@@ -155,6 +166,7 @@ class GpsController {
required int Function() getProximityAlertDistance,
required List<OsmNode> Function() getNearbyNodes,
required List<NodeProfile> Function() getEnabledProfiles,
VoidCallback? onMapMovedProgrammatically,
}) async {
final perm = await Geolocator.requestPermission();
@@ -180,6 +192,7 @@ class GpsController {
proximityAlertDistance: proximityAlertDistance,
nearbyNodes: nearbyNodes,
enabledProfiles: enabledProfiles,
onMapMovedProgrammatically: onMapMovedProgrammatically,
);
});
}

View File

@@ -18,7 +18,6 @@ class MapOverlays extends StatelessWidget {
final EditNodeSession? editSession;
final String? attribution; // Attribution for current tile provider
final VoidCallback? onSearchPressed; // Callback for search button
const MapOverlays({
super.key,
required this.mapController,

View File

@@ -78,6 +78,8 @@ class MapViewState extends State<MapView> {
// State for proximity alert banner
bool _showProximityBanner = false;
@override
void initState() {
@@ -180,6 +182,10 @@ class MapViewState extends State<MapView> {
}
return [];
},
onMapMovedProgrammatically: () {
// Refresh nodes when GPS controller moves the map
_refreshNodesFromProvider();
},
);
@@ -302,6 +308,9 @@ class MapViewState extends State<MapView> {
newMode: widget.followMeMode,
oldMode: oldWidget.followMeMode,
controller: _controller,
onMapMovedProgrammatically: () {
_refreshNodesFromProvider();
},
);
}
}