feat: add Jordan places migration scripts and enhance map controller with style-load safety and camera controls
This commit is contained in:
@@ -56,6 +56,7 @@ class NavigationCubit extends Cubit<NavigationState> {
|
||||
Timer? _movementInterpolationTimer;
|
||||
LatLng? _currentDisplayPosition;
|
||||
double _currentDisplayHeading = 0.0;
|
||||
double _currentCameraBearing = 0.0;
|
||||
LatLng? _animStartPosition;
|
||||
LatLng? _animTargetPosition;
|
||||
double _animStartHeading = 0.0;
|
||||
@@ -64,6 +65,13 @@ class NavigationCubit extends Cubit<NavigationState> {
|
||||
static const int _animTotalSteps = 25; // 25 steps * 40ms = 1000ms duration
|
||||
static const Duration _animTickDuration = Duration(milliseconds: 40);
|
||||
|
||||
void updateCameraBearing(double bearing) {
|
||||
_currentCameraBearing = bearing;
|
||||
if (_currentDisplayPosition != null) {
|
||||
_updateCarMarker(_currentDisplayPosition!, _currentDisplayHeading);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _init() async {
|
||||
print("🚀 [NavigationCubit] Initializing NavigationCubit...");
|
||||
CarPlatformBridge.ensureInitialized();
|
||||
@@ -493,20 +501,48 @@ class NavigationCubit extends Cubit<NavigationState> {
|
||||
|
||||
void _onNewLocationFix(
|
||||
LatLng newLoc, double heading, double speedKmH, double alt) {
|
||||
double resolvedHeading = heading;
|
||||
|
||||
// Calculate heading from real movement if GPS course is missing / 0.0
|
||||
if (resolvedHeading <= 0.0 || resolvedHeading.isNaN) {
|
||||
if (_currentDisplayPosition != null) {
|
||||
final dist = locationService.calculateDistance(_currentDisplayPosition!, newLoc);
|
||||
if (dist >= 1.5) {
|
||||
final computed = locationService.calculateBearing(_currentDisplayPosition!, newLoc);
|
||||
resolvedHeading = (computed % 360.0 + 360.0) % 360.0;
|
||||
} else {
|
||||
resolvedHeading = _currentDisplayHeading;
|
||||
}
|
||||
} else {
|
||||
resolvedHeading = _currentDisplayHeading;
|
||||
}
|
||||
}
|
||||
|
||||
// In active navigation, if stopped or crawling, lock heading to route segment ahead
|
||||
if (state.isNavigating && speedKmH < 4.0 && state.currentRoute != null) {
|
||||
final coords = state.currentRoute!.coordinates;
|
||||
if (_lastTraveledIndexInFullRoute + 1 < coords.length) {
|
||||
resolvedHeading = _calculateBearing(
|
||||
coords[_lastTraveledIndexInFullRoute],
|
||||
coords[_lastTraveledIndexInFullRoute + 1],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
emit(state.copyWith(
|
||||
myLocation: newLoc,
|
||||
altitude: alt,
|
||||
heading: heading,
|
||||
heading: resolvedHeading,
|
||||
speed: speedKmH,
|
||||
));
|
||||
|
||||
// Smooth movement interpolation for the vehicle marker and 3D camera
|
||||
if (_currentDisplayPosition == null) {
|
||||
_currentDisplayPosition = newLoc;
|
||||
_currentDisplayHeading = heading;
|
||||
_updateCarMarker(newLoc, heading);
|
||||
_currentDisplayHeading = resolvedHeading;
|
||||
_updateCarMarker(newLoc, resolvedHeading);
|
||||
} else {
|
||||
_startMovementInterpolation(newLoc, heading);
|
||||
_startMovementInterpolation(newLoc, resolvedHeading);
|
||||
}
|
||||
|
||||
// Proactively move camera on first acquired GPS lock (only when style is loaded)
|
||||
@@ -514,9 +550,10 @@ class NavigationCubit extends Cubit<NavigationState> {
|
||||
mapController != null &&
|
||||
_isMapStyleLoaded) {
|
||||
_hasInitiallyCenteredCamera = true;
|
||||
_currentCameraBearing = resolvedHeading;
|
||||
mapController!.animateCamera(
|
||||
CameraUpdate.newCameraPosition(
|
||||
CameraPosition(target: newLoc, zoom: 16.5, bearing: heading),
|
||||
CameraPosition(target: newLoc, zoom: 16.5, bearing: resolvedHeading),
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -527,11 +564,11 @@ class NavigationCubit extends Cubit<NavigationState> {
|
||||
latitude: newLoc.latitude,
|
||||
longitude: newLoc.longitude,
|
||||
speedKmH: speedKmH,
|
||||
heading: heading,
|
||||
heading: resolvedHeading,
|
||||
elevation: alt,
|
||||
remainingDistance: state.remainingDistance,
|
||||
);
|
||||
_processActiveNavigationTick(newLoc, speedKmH, heading);
|
||||
_processActiveNavigationTick(newLoc, speedKmH, resolvedHeading);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -624,6 +661,8 @@ class NavigationCubit extends Cubit<NavigationState> {
|
||||
}
|
||||
}
|
||||
|
||||
_currentCameraBearing = effectiveBearing;
|
||||
|
||||
// 3. Shift camera target ahead along heading vector
|
||||
// This positions the car in the bottom ~28% of the viewport with 45° tilt!
|
||||
final cameraTarget = _computeOffset(pos, lookAheadMeters, effectiveBearing);
|
||||
@@ -672,13 +711,21 @@ class NavigationCubit extends Cubit<NavigationState> {
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _updateCarMarker(LatLng position, double bearing) async {
|
||||
Future<void> _updateCarMarker(LatLng position, double worldHeading) async {
|
||||
if (mapController == null || !_isMapStyleLoaded) return;
|
||||
try {
|
||||
// MapLibre symbols have viewport-aligned rotation.
|
||||
// (worldHeading - _currentCameraBearing) keeps the vehicle oriented correctly:
|
||||
// In active navigation with locked camera, (worldHeading - _currentCameraBearing) = 0° (facing UP/forward along the road).
|
||||
// During turns, it tilts smoothly into the corner as the car turns.
|
||||
// In free map exploration, it points towards true geographic direction relative to rotated map.
|
||||
final double screenRotation =
|
||||
(worldHeading - _currentCameraBearing + 360.0) % 360.0;
|
||||
|
||||
await mapController!.setUserMarker(Marker(
|
||||
markerId: const MarkerId('current_user_car'),
|
||||
position: position,
|
||||
rotation: bearing,
|
||||
rotation: screenRotation,
|
||||
anchor: const Offset(0.5, 0.5),
|
||||
flat: true,
|
||||
icon: InlqBitmap.fromStyleImage('current_vehicle_icon',
|
||||
@@ -709,6 +756,7 @@ class NavigationCubit extends Cubit<NavigationState> {
|
||||
state.distanceToNextStep,
|
||||
);
|
||||
} else {
|
||||
_currentCameraBearing = state.heading;
|
||||
mapController!.animateCamera(
|
||||
CameraUpdate.newCameraPosition(
|
||||
CameraPosition(
|
||||
|
||||
@@ -1705,6 +1705,7 @@ class _MapViewState extends State<MapView> {
|
||||
zoomControlsEnabled: false,
|
||||
compassEnabled: false,
|
||||
onCameraMove: (pos) {
|
||||
cubit.updateCameraBearing(pos.bearing);
|
||||
if (state.isSelectingLocationOnMap) {
|
||||
cubit.updatePickedLocation(pos.target);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user