MENA region data enrichment scripts` -> Wait, let's keep it concise and clean.
* *Option 3:* `feat: add tourist landmarks support with UI sheets, data models
This commit is contained in:
@@ -19,6 +19,7 @@ import '../../../core/services/vehicle_icon_generator.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import '../../../data/models/hazard_model.dart';
|
||||
import '../../../data/models/route_model.dart';
|
||||
import '../../../data/models/tourist_landmark_model.dart';
|
||||
import '../../../data/repositories/map_saas_repository.dart';
|
||||
import 'navigation_state.dart';
|
||||
|
||||
@@ -45,7 +46,8 @@ class NavigationCubit extends Cubit<NavigationState> {
|
||||
ConnectivityService? connectivityService,
|
||||
}) : locationService = locationService ?? LocationService.instance,
|
||||
ttsService = ttsService ?? TtsService.instance,
|
||||
connectivityService = connectivityService ?? ConnectivityService.instance,
|
||||
connectivityService =
|
||||
connectivityService ?? ConnectivityService.instance,
|
||||
super(const NavigationState()) {
|
||||
_init();
|
||||
}
|
||||
@@ -90,22 +92,28 @@ class NavigationCubit extends Cubit<NavigationState> {
|
||||
// Check & listen to network connectivity
|
||||
connectivityService.initialize();
|
||||
final isOnline = await connectivityService.checkConnection();
|
||||
print("🌐 [NavigationCubit] Network connectivity status: isOnline=$isOnline");
|
||||
print(
|
||||
"🌐 [NavigationCubit] Network connectivity status: isOnline=$isOnline");
|
||||
emit(state.copyWith(isOnline: isOnline));
|
||||
_connectivitySub = connectivityService.onConnectivityChanged.listen((online) {
|
||||
_connectivitySub =
|
||||
connectivityService.onConnectivityChanged.listen((online) {
|
||||
print("🌐 [NavigationCubit] Connectivity changed event: online=$online");
|
||||
emit(state.copyWith(isOnline: online));
|
||||
});
|
||||
|
||||
// Default to Amman if initial position is fetching
|
||||
final defaultPos = const LatLng(ApiConstants.defaultLat, ApiConstants.defaultLng);
|
||||
final defaultPos =
|
||||
const LatLng(ApiConstants.defaultLat, ApiConstants.defaultLng);
|
||||
emit(state.copyWith(myLocation: defaultPos, altitude: 0.0));
|
||||
|
||||
final position = await locationService.getCurrentPosition();
|
||||
if (position != null) {
|
||||
final loc = LatLng(position.latitude, position.longitude);
|
||||
final alt = (position.altitude.isNaN || position.altitude.isInfinite) ? 0.0 : position.altitude;
|
||||
print("📍 [NavigationCubit] Initial GPS location acquired: lat=${loc.latitude.toStringAsFixed(6)}, lng=${loc.longitude.toStringAsFixed(6)}, alt=${alt.toStringAsFixed(1)}m, heading=${position.heading.toStringAsFixed(1)}°");
|
||||
final alt = (position.altitude.isNaN || position.altitude.isInfinite)
|
||||
? 0.0
|
||||
: position.altitude;
|
||||
print(
|
||||
"📍 [NavigationCubit] Initial GPS location acquired: lat=${loc.latitude.toStringAsFixed(6)}, lng=${loc.longitude.toStringAsFixed(6)}, alt=${alt.toStringAsFixed(1)}m, heading=${position.heading.toStringAsFixed(1)}°");
|
||||
emit(state.copyWith(
|
||||
myLocation: loc,
|
||||
altitude: alt,
|
||||
@@ -116,7 +124,8 @@ class NavigationCubit extends Cubit<NavigationState> {
|
||||
_currentDisplayHeading = position.heading;
|
||||
_updateCarMarker(loc, position.heading);
|
||||
} else {
|
||||
print("⚠️ [NavigationCubit] Initial GPS returned null, using default Amman center");
|
||||
print(
|
||||
"⚠️ [NavigationCubit] Initial GPS returned null, using default Amman center");
|
||||
}
|
||||
|
||||
_startLocationUpdates();
|
||||
@@ -128,7 +137,8 @@ class NavigationCubit extends Cubit<NavigationState> {
|
||||
int? _hasAnnouncedEarlyStepIndex;
|
||||
|
||||
void onMapCreated(IntaleqMapController controller) {
|
||||
print("🗺️ [NavigationCubit] onMapCreated: Native map view created, controller attached.");
|
||||
print(
|
||||
"🗺️ [NavigationCubit] onMapCreated: Native map view created, controller attached.");
|
||||
mapController = controller;
|
||||
emit(state.copyWith(status: NavigationStatus.mapReady));
|
||||
}
|
||||
@@ -136,13 +146,18 @@ class NavigationCubit extends Cubit<NavigationState> {
|
||||
Future<void> _animateCameraToCurrentPosition() async {
|
||||
LatLng? target = state.myLocation;
|
||||
double heading = state.heading;
|
||||
print("🎥 [NavigationCubit] _animateCameraToCurrentPosition (target=$target, styleLoaded=$_isMapStyleLoaded)");
|
||||
print(
|
||||
"🎥 [NavigationCubit] _animateCameraToCurrentPosition (target=$target, styleLoaded=$_isMapStyleLoaded)");
|
||||
|
||||
if (target == null || (target.latitude == ApiConstants.defaultLat && target.longitude == ApiConstants.defaultLng)) {
|
||||
if (target == null ||
|
||||
(target.latitude == ApiConstants.defaultLat &&
|
||||
target.longitude == ApiConstants.defaultLng)) {
|
||||
final pos = await locationService.getCurrentPosition();
|
||||
if (pos != null) {
|
||||
final loc = LatLng(pos.latitude, pos.longitude);
|
||||
final alt = (pos.altitude.isNaN || pos.altitude.isInfinite) ? 0.0 : pos.altitude;
|
||||
final alt = (pos.altitude.isNaN || pos.altitude.isInfinite)
|
||||
? 0.0
|
||||
: pos.altitude;
|
||||
target = loc;
|
||||
heading = pos.heading;
|
||||
emit(state.copyWith(
|
||||
@@ -178,7 +193,8 @@ class NavigationCubit extends Cubit<NavigationState> {
|
||||
primaryColor: Color(state.selectedVehicleColor),
|
||||
);
|
||||
await mapController!.addImage('current_vehicle_icon', bytes);
|
||||
print("🚗 [NavigationCubit] Registered dynamic current_vehicle_icon (style=${state.selectedVehicleStyle}, color=0x${state.selectedVehicleColor.toRadixString(16)}, scale=${state.vehicleScale})");
|
||||
print(
|
||||
"🚗 [NavigationCubit] Registered dynamic current_vehicle_icon (style=${state.selectedVehicleStyle}, color=0x${state.selectedVehicleColor.toRadixString(16)}, scale=${state.vehicleScale})");
|
||||
} catch (e) {
|
||||
print("⚠️ [NavigationCubit] Error generating vehicle icon: $e");
|
||||
}
|
||||
@@ -199,25 +215,225 @@ class NavigationCubit extends Cubit<NavigationState> {
|
||||
// 3. Start & Destination pins
|
||||
try {
|
||||
final startBytes = await rootBundle.load('assets/images/A.png');
|
||||
await mapController!.addImage('start_icon', startBytes.buffer.asUint8List());
|
||||
await mapController!.addImage('asset_assets_images_A_png', startBytes.buffer.asUint8List());
|
||||
await mapController!
|
||||
.addImage('start_icon', startBytes.buffer.asUint8List());
|
||||
await mapController!.addImage(
|
||||
'asset_assets_images_A_png', startBytes.buffer.asUint8List());
|
||||
} catch (_) {}
|
||||
|
||||
try {
|
||||
final destBytes = await rootBundle.load('assets/images/b.png');
|
||||
await mapController!.addImage('dest_icon', destBytes.buffer.asUint8List());
|
||||
await mapController!.addImage('asset_assets_images_b_png', destBytes.buffer.asUint8List());
|
||||
await mapController!
|
||||
.addImage('dest_icon', destBytes.buffer.asUint8List());
|
||||
await mapController!.addImage(
|
||||
'asset_assets_images_b_png', destBytes.buffer.asUint8List());
|
||||
} catch (_) {}
|
||||
}
|
||||
|
||||
Future<void> onStyleLoaded() async {
|
||||
print("🎨 [NavigationCubit] onStyleLoaded: Map style rendered successfully! Registering custom icons & centering camera.");
|
||||
print(
|
||||
"🎨 [NavigationCubit] onStyleLoaded: Map style rendered successfully! Registering custom icons & centering camera.");
|
||||
_isMapStyleLoaded = true;
|
||||
await _loadCustomIcons();
|
||||
if (state.myLocation != null) {
|
||||
_updateCarMarker(_currentDisplayPosition ?? state.myLocation!, _currentDisplayHeading);
|
||||
_updateCarMarker(
|
||||
_currentDisplayPosition ?? state.myLocation!, _currentDisplayHeading);
|
||||
}
|
||||
_animateCameraToCurrentPosition();
|
||||
|
||||
// Apply 3D terrain exaggeration (default 2.0x) and sun lighting
|
||||
applySunLighting(state.sunTimeMinutes, state.terrainExaggeration);
|
||||
mapController?.setTilt(state.cameraTilt);
|
||||
}
|
||||
|
||||
// ── 3D CINEMATIC STUDIO & ORBIT ENGINE ────────────────────────
|
||||
|
||||
Timer? _orbitTimer;
|
||||
Timer? _sunTimer;
|
||||
|
||||
void toggleOrbit() {
|
||||
final nextOrbiting = !state.isOrbiting;
|
||||
emit(state.copyWith(isOrbiting: nextOrbiting));
|
||||
|
||||
_orbitTimer?.cancel();
|
||||
if (nextOrbiting) {
|
||||
_orbitTimer = Timer.periodic(const Duration(milliseconds: 60), (_) {
|
||||
if (isClosed || !state.isOrbiting) return;
|
||||
mapController?.orbitStep(
|
||||
state.orbitSpeed,
|
||||
duration: const Duration(milliseconds: 60),
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void stopOrbit() {
|
||||
_orbitTimer?.cancel();
|
||||
emit(state.copyWith(isOrbiting: false));
|
||||
}
|
||||
|
||||
void setOrbitSpeed(double speed) {
|
||||
emit(state.copyWith(orbitSpeed: speed));
|
||||
}
|
||||
|
||||
void toggleSunAutoPlay() {
|
||||
final nextSunPlaying = !state.isSunPlaying;
|
||||
emit(state.copyWith(isSunPlaying: nextSunPlaying));
|
||||
|
||||
_sunTimer?.cancel();
|
||||
if (nextSunPlaying) {
|
||||
_sunTimer = Timer.periodic(const Duration(milliseconds: 130), (_) {
|
||||
if (isClosed || !state.isSunPlaying) return;
|
||||
int nextMinutes = state.sunTimeMinutes + 12;
|
||||
if (nextMinutes > 1260) {
|
||||
nextMinutes = 330;
|
||||
}
|
||||
emit(state.copyWith(sunTimeMinutes: nextMinutes));
|
||||
applySunLighting(nextMinutes);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void stopSunAutoPlay() {
|
||||
_sunTimer?.cancel();
|
||||
emit(state.copyWith(isSunPlaying: false));
|
||||
}
|
||||
|
||||
void setSunTimeMinutes(int minutes) {
|
||||
emit(state.copyWith(sunTimeMinutes: minutes));
|
||||
applySunLighting(minutes);
|
||||
}
|
||||
|
||||
void setTerrainExaggeration(double value) {
|
||||
emit(state.copyWith(terrainExaggeration: value));
|
||||
applySunLighting(state.sunTimeMinutes, value);
|
||||
}
|
||||
|
||||
void setCameraTilt(double tilt) {
|
||||
emit(state.copyWith(cameraTilt: tilt));
|
||||
mapController?.setTilt(tilt);
|
||||
}
|
||||
|
||||
void toggleBuildings() {
|
||||
final next = !state.showBuildings;
|
||||
emit(state.copyWith(showBuildings: next));
|
||||
mapController?.setLayerVisibility('building-3d', next);
|
||||
mapController?.setLayerVisibility('building-3d-osm', next);
|
||||
}
|
||||
|
||||
void toggleContours() {
|
||||
final next = !state.showContours;
|
||||
emit(state.copyWith(showContours: next));
|
||||
mapController?.setLayerVisibility('tactical-contour-minor', next);
|
||||
mapController?.setLayerVisibility('tactical-contour-major', next);
|
||||
mapController?.setLayerVisibility('opentopo-contour-lines', next);
|
||||
}
|
||||
|
||||
void toggleHillshading() {
|
||||
final next = !state.showHillshading;
|
||||
emit(state.copyWith(showHillshading: next));
|
||||
mapController?.setLayerVisibility('hillshading', next);
|
||||
}
|
||||
|
||||
void applySunLighting([int? minutes, double? exaggeration]) {
|
||||
final m = minutes ?? state.sunTimeMinutes;
|
||||
final ex = exaggeration ?? state.terrainExaggeration;
|
||||
final double fraction = ((m - 330) / (1260 - 330)).clamp(0.0, 1.0);
|
||||
final double sunAzimuth = 75.0 + (fraction * 215.0);
|
||||
|
||||
String shadowColor;
|
||||
String highlightColor;
|
||||
String buildingColor;
|
||||
double opacity = 0.88;
|
||||
|
||||
if (m < 420) {
|
||||
shadowColor = '#4c1d95';
|
||||
highlightColor = '#fed7aa';
|
||||
buildingColor = '#fbcfe8';
|
||||
opacity = 0.85;
|
||||
} else if (m < 660) {
|
||||
shadowColor = '#334155';
|
||||
highlightColor = '#ffffff';
|
||||
buildingColor = '#fef08a';
|
||||
opacity = 0.88;
|
||||
} else if (m < 840) {
|
||||
shadowColor = '#1e293b';
|
||||
highlightColor = '#ffffff';
|
||||
buildingColor = '#f8fafc';
|
||||
opacity = 0.90;
|
||||
} else if (m < 1050) {
|
||||
shadowColor = '#374151';
|
||||
highlightColor = '#ffedd5';
|
||||
buildingColor = '#fed7aa';
|
||||
opacity = 0.88;
|
||||
} else if (m < 1170) {
|
||||
shadowColor = '#3b0764';
|
||||
highlightColor = '#ffedd5';
|
||||
buildingColor = '#ea580c';
|
||||
opacity = 0.92;
|
||||
} else {
|
||||
shadowColor = '#020617';
|
||||
highlightColor = '#60a5fa';
|
||||
buildingColor = '#1e293b';
|
||||
opacity = 0.80;
|
||||
}
|
||||
|
||||
mapController?.updateSunSimulation(
|
||||
sunAzimuth: sunAzimuth,
|
||||
shadowColorHex: shadowColor,
|
||||
highlightColorHex: highlightColor,
|
||||
buildingColorHex: buildingColor,
|
||||
buildingOpacity: opacity,
|
||||
hillshadeExaggeration: ex,
|
||||
);
|
||||
}
|
||||
|
||||
// ── TOURISM & LANDMARKS 3D ───────────────────────────────────
|
||||
|
||||
void toggleTouristMode() {
|
||||
final next = !state.isTouristModeEnabled;
|
||||
emit(state.copyWith(isTouristModeEnabled: next));
|
||||
if (next) {
|
||||
// Switch map to topographical/scenic view with 2.0x 3D terrain
|
||||
setTerrainExaggeration(2.0);
|
||||
setCameraTilt(55.0);
|
||||
if (!state.showHillshading) toggleHillshading();
|
||||
}
|
||||
}
|
||||
|
||||
void flyToLandmark(TouristLandmark landmark) {
|
||||
emit(state.copyWith(
|
||||
selectedLandmarkId: landmark.id,
|
||||
isTouristModeEnabled: true,
|
||||
));
|
||||
|
||||
// Ensure 3D terrain & hillshading are active
|
||||
if (!state.showHillshading) toggleHillshading();
|
||||
setTerrainExaggeration(2.0);
|
||||
|
||||
// Smoothly animate camera to the landmark in 3D
|
||||
mapController?.animateCamera(
|
||||
CameraUpdate.newCameraPosition(
|
||||
CameraPosition(
|
||||
target: landmark.coordinates,
|
||||
zoom: 14.5,
|
||||
tilt: 58.0,
|
||||
bearing: 45.0,
|
||||
),
|
||||
),
|
||||
// duration: const Duration(milliseconds: 1600),
|
||||
);
|
||||
}
|
||||
|
||||
void startOrbitAroundLandmark(TouristLandmark landmark) {
|
||||
flyToLandmark(landmark);
|
||||
if (!state.isOrbiting) {
|
||||
toggleOrbit();
|
||||
}
|
||||
}
|
||||
|
||||
void clearSelectedLandmark() {
|
||||
emit(state.copyWith(clearSelectedLandmark: true));
|
||||
}
|
||||
|
||||
// ── VEHICLE CUSTOMIZATION HANDLERS ──────────────────────────
|
||||
@@ -230,7 +446,8 @@ class NavigationCubit extends Cubit<NavigationState> {
|
||||
} catch (_) {}
|
||||
await _registerCurrentVehicleIcon();
|
||||
if (state.myLocation != null) {
|
||||
_updateCarMarker(_currentDisplayPosition ?? state.myLocation!, _currentDisplayHeading);
|
||||
_updateCarMarker(
|
||||
_currentDisplayPosition ?? state.myLocation!, _currentDisplayHeading);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -242,7 +459,8 @@ class NavigationCubit extends Cubit<NavigationState> {
|
||||
} catch (_) {}
|
||||
await _registerCurrentVehicleIcon();
|
||||
if (state.myLocation != null) {
|
||||
_updateCarMarker(_currentDisplayPosition ?? state.myLocation!, _currentDisplayHeading);
|
||||
_updateCarMarker(
|
||||
_currentDisplayPosition ?? state.myLocation!, _currentDisplayHeading);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -253,7 +471,8 @@ class NavigationCubit extends Cubit<NavigationState> {
|
||||
await prefs.setDouble('siro_vehicle_scale', scale);
|
||||
} catch (_) {}
|
||||
if (state.myLocation != null) {
|
||||
_updateCarMarker(_currentDisplayPosition ?? state.myLocation!, _currentDisplayHeading);
|
||||
_updateCarMarker(
|
||||
_currentDisplayPosition ?? state.myLocation!, _currentDisplayHeading);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -265,13 +484,15 @@ class NavigationCubit extends Cubit<NavigationState> {
|
||||
final newLoc = LatLng(pos.latitude, pos.longitude);
|
||||
final speedKmH = pos.speed * 3.6;
|
||||
final heading = pos.heading;
|
||||
final alt = (pos.altitude.isNaN || pos.altitude.isInfinite) ? 0.0 : pos.altitude;
|
||||
final alt =
|
||||
(pos.altitude.isNaN || pos.altitude.isInfinite) ? 0.0 : pos.altitude;
|
||||
|
||||
_onNewLocationFix(newLoc, heading, speedKmH, alt);
|
||||
});
|
||||
}
|
||||
|
||||
void _onNewLocationFix(LatLng newLoc, double heading, double speedKmH, double alt) {
|
||||
void _onNewLocationFix(
|
||||
LatLng newLoc, double heading, double speedKmH, double alt) {
|
||||
emit(state.copyWith(
|
||||
myLocation: newLoc,
|
||||
altitude: alt,
|
||||
@@ -289,7 +510,9 @@ class NavigationCubit extends Cubit<NavigationState> {
|
||||
}
|
||||
|
||||
// Proactively move camera on first acquired GPS lock (only when style is loaded)
|
||||
if (!_hasInitiallyCenteredCamera && mapController != null && _isMapStyleLoaded) {
|
||||
if (!_hasInitiallyCenteredCamera &&
|
||||
mapController != null &&
|
||||
_isMapStyleLoaded) {
|
||||
_hasInitiallyCenteredCamera = true;
|
||||
mapController!.animateCamera(
|
||||
CameraUpdate.newCameraPosition(
|
||||
@@ -328,8 +551,10 @@ class NavigationCubit extends Cubit<NavigationState> {
|
||||
// Smooth ease-out curve for natural vehicle movement
|
||||
final double curvedT = 1.0 - (1.0 - t) * (1.0 - t);
|
||||
|
||||
final interpPos = _lerpLatLng(_animStartPosition!, _animTargetPosition!, curvedT);
|
||||
final interpHeading = _lerpAngle(_animStartHeading, _animTargetHeading, curvedT);
|
||||
final interpPos =
|
||||
_lerpLatLng(_animStartPosition!, _animTargetPosition!, curvedT);
|
||||
final interpHeading =
|
||||
_lerpAngle(_animStartHeading, _animTargetHeading, curvedT);
|
||||
|
||||
_currentDisplayPosition = interpPos;
|
||||
_currentDisplayHeading = interpHeading;
|
||||
@@ -337,9 +562,13 @@ class NavigationCubit extends Cubit<NavigationState> {
|
||||
_updateCarMarker(interpPos, interpHeading);
|
||||
|
||||
// Smoothly update Google Maps style 3D camera periodically (~every 200ms) or on completion
|
||||
if (state.isCameraLocked && mapController != null && _isMapStyleLoaded && state.isNavigating) {
|
||||
if (state.isCameraLocked &&
|
||||
mapController != null &&
|
||||
_isMapStyleLoaded &&
|
||||
state.isNavigating) {
|
||||
if (_animCurrentStep % 5 == 0 || _animCurrentStep == _animTotalSteps) {
|
||||
_updateNavigationCamera(interpPos, interpHeading, state.speed, state.distanceToNextStep);
|
||||
_updateNavigationCamera(
|
||||
interpPos, interpHeading, state.speed, state.distanceToNextStep);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -352,13 +581,16 @@ class NavigationCubit extends Cubit<NavigationState> {
|
||||
|
||||
// ── GOOGLE MAPS NAVIGATION PERSPECTIVE & DYNAMIC ZOOM ─────────
|
||||
|
||||
void _updateNavigationCamera(LatLng pos, double heading, double speedKmH, double distanceToStep) {
|
||||
if (mapController == null || !_isMapStyleLoaded || !state.isCameraLocked) return;
|
||||
void _updateNavigationCamera(
|
||||
LatLng pos, double heading, double speedKmH, double distanceToStep) {
|
||||
if (mapController == null || !_isMapStyleLoaded || !state.isCameraLocked)
|
||||
return;
|
||||
|
||||
// 1. Dynamic speed-adaptive zoom & lookahead calculation
|
||||
double targetZoom;
|
||||
double lookAheadMeters;
|
||||
const double targetTilt = 45.0; // Fixed 45-degree angle as requested by user
|
||||
const double targetTilt =
|
||||
45.0; // Fixed 45-degree angle as requested by user
|
||||
|
||||
if (distanceToStep > 0 && distanceToStep < 100.0) {
|
||||
// Approaching turn / maneuver: camera descends down close to show intersection
|
||||
@@ -408,7 +640,8 @@ class NavigationCubit extends Cubit<NavigationState> {
|
||||
);
|
||||
}
|
||||
|
||||
LatLng _computeOffset(LatLng from, double distanceMeters, double bearingDegrees) {
|
||||
LatLng _computeOffset(
|
||||
LatLng from, double distanceMeters, double bearingDegrees) {
|
||||
const double earthRadius = 6378137.0; // WGS-84 earth radius in meters
|
||||
final double dByR = distanceMeters / earthRadius;
|
||||
final double latRad = from.latitude * (pi / 180.0);
|
||||
@@ -418,10 +651,11 @@ class NavigationCubit extends Cubit<NavigationState> {
|
||||
final double targetLatRad = asin(
|
||||
sin(latRad) * cos(dByR) + cos(latRad) * sin(dByR) * cos(bearingRad),
|
||||
);
|
||||
final double targetLonRad = lonRad + atan2(
|
||||
sin(bearingRad) * sin(dByR) * cos(latRad),
|
||||
cos(dByR) - sin(latRad) * sin(targetLatRad),
|
||||
);
|
||||
final double targetLonRad = lonRad +
|
||||
atan2(
|
||||
sin(bearingRad) * sin(dByR) * cos(latRad),
|
||||
cos(dByR) - sin(latRad) * sin(targetLatRad),
|
||||
);
|
||||
|
||||
return LatLng(targetLatRad * (180.0 / pi), targetLonRad * (180.0 / pi));
|
||||
}
|
||||
@@ -447,7 +681,8 @@ class NavigationCubit extends Cubit<NavigationState> {
|
||||
rotation: bearing,
|
||||
anchor: const Offset(0.5, 0.5),
|
||||
flat: true,
|
||||
icon: InlqBitmap.fromStyleImage('current_vehicle_icon', size: state.vehicleScale),
|
||||
icon: InlqBitmap.fromStyleImage('current_vehicle_icon',
|
||||
size: state.vehicleScale),
|
||||
zIndex: 100,
|
||||
));
|
||||
} catch (e) {
|
||||
@@ -460,9 +695,12 @@ class NavigationCubit extends Cubit<NavigationState> {
|
||||
}
|
||||
|
||||
void relockCameraToUser() {
|
||||
print("🎯 [NavigationCubit] relockCameraToUser requested (styleLoaded=$_isMapStyleLoaded, loc=${state.myLocation})");
|
||||
print(
|
||||
"🎯 [NavigationCubit] relockCameraToUser requested (styleLoaded=$_isMapStyleLoaded, loc=${state.myLocation})");
|
||||
emit(state.copyWith(isCameraLocked: true));
|
||||
if (state.myLocation != null && mapController != null && _isMapStyleLoaded) {
|
||||
if (state.myLocation != null &&
|
||||
mapController != null &&
|
||||
_isMapStyleLoaded) {
|
||||
if (state.isNavigating) {
|
||||
_updateNavigationCamera(
|
||||
_currentDisplayPosition ?? state.myLocation!,
|
||||
@@ -486,7 +724,8 @@ class NavigationCubit extends Cubit<NavigationState> {
|
||||
}
|
||||
|
||||
void setMapTheme(MapThemeType theme) {
|
||||
print("🎨 [NavigationCubit] Changing map theme to: $theme (resetting _isMapStyleLoaded=false)");
|
||||
print(
|
||||
"🎨 [NavigationCubit] Changing map theme to: $theme (resetting _isMapStyleLoaded=false)");
|
||||
_isMapStyleLoaded = false;
|
||||
emit(state.copyWith(mapTheme: theme));
|
||||
}
|
||||
@@ -567,7 +806,9 @@ class NavigationCubit extends Cubit<NavigationState> {
|
||||
// ── STEP BOUNDING OVERVIEW ──────────────────────────────────
|
||||
|
||||
void overviewStepBounding(int stepIndex) {
|
||||
if (state.currentRoute == null || stepIndex < 0 || stepIndex >= state.routeSteps.length) return;
|
||||
if (state.currentRoute == null ||
|
||||
stepIndex < 0 ||
|
||||
stepIndex >= state.routeSteps.length) return;
|
||||
final step = state.routeSteps[stepIndex];
|
||||
final interval = step['interval'];
|
||||
final coords = state.currentRoute!.coordinates;
|
||||
@@ -587,10 +828,14 @@ class NavigationCubit extends Cubit<NavigationState> {
|
||||
|
||||
final stepLat = (step['lat'] as num?)?.toDouble() ?? 0.0;
|
||||
final stepLng = (step['lng'] as num?)?.toDouble() ?? 0.0;
|
||||
if (stepLat != 0.0 && stepLng != 0.0 && mapController != null && _isMapStyleLoaded) {
|
||||
if (stepLat != 0.0 &&
|
||||
stepLng != 0.0 &&
|
||||
mapController != null &&
|
||||
_isMapStyleLoaded) {
|
||||
mapController!.animateCamera(
|
||||
CameraUpdate.newCameraPosition(
|
||||
CameraPosition(target: LatLng(stepLat, stepLng), zoom: 17.5, tilt: 45.0),
|
||||
CameraPosition(
|
||||
target: LatLng(stepLat, stepLng), zoom: 17.5, tilt: 45.0),
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -601,9 +846,11 @@ class NavigationCubit extends Cubit<NavigationState> {
|
||||
String title = 'وجهة مختارة',
|
||||
String originTitle = 'موقعي الحالي',
|
||||
}) async {
|
||||
print("🛣️ [NavigationCubit] calculateRouteTo: origin=$originTitle, target=$destination, title=$title, myLocation=${state.myLocation}");
|
||||
print(
|
||||
"🛣️ [NavigationCubit] calculateRouteTo: origin=$originTitle, target=$destination, title=$title, myLocation=${state.myLocation}");
|
||||
if (state.myLocation == null) {
|
||||
print("⚠️ [NavigationCubit] Cannot calculate route: current GPS location is null!");
|
||||
print(
|
||||
"⚠️ [NavigationCubit] Cannot calculate route: current GPS location is null!");
|
||||
return;
|
||||
}
|
||||
emit(state.copyWith(
|
||||
@@ -631,7 +878,8 @@ class NavigationCubit extends Cubit<NavigationState> {
|
||||
}
|
||||
|
||||
final primaryRoute = routes.first;
|
||||
print("✅ [NavigationCubit] Route calculated successfully: ${routes.length} routes found, primary: dist=${primaryRoute.formattedDistance}, dur=${primaryRoute.formattedDuration}, coordsCount=${primaryRoute.coordinates.length}");
|
||||
print(
|
||||
"✅ [NavigationCubit] Route calculated successfully: ${routes.length} routes found, primary: dist=${primaryRoute.formattedDistance}, dur=${primaryRoute.formattedDuration}, coordsCount=${primaryRoute.coordinates.length}");
|
||||
final polylines = _createPolylines(routes, 0);
|
||||
|
||||
// Origin Pin A
|
||||
@@ -655,7 +903,8 @@ class NavigationCubit extends Cubit<NavigationState> {
|
||||
);
|
||||
|
||||
final updatedMarkers = Set<Marker>.from(state.markers)
|
||||
..removeWhere((m) => m.markerId.value == 'origin_pin' || m.markerId.value == 'dest_pin')
|
||||
..removeWhere((m) =>
|
||||
m.markerId.value == 'origin_pin' || m.markerId.value == 'dest_pin')
|
||||
..add(startMarker)
|
||||
..add(destMarker);
|
||||
|
||||
@@ -677,15 +926,22 @@ class NavigationCubit extends Cubit<NavigationState> {
|
||||
if (mapController != null && _isMapStyleLoaded) {
|
||||
await mapController!.addMarker(startMarker);
|
||||
await mapController!.addMarker(destMarker);
|
||||
print("📍 [NavigationCubit] Directly added startMarker & destMarker to native map engine.");
|
||||
print(
|
||||
"📍 [NavigationCubit] Directly added startMarker & destMarker to native map engine.");
|
||||
}
|
||||
|
||||
_fitRouteInView(primaryRoute.coordinates);
|
||||
} on RouteException catch (e) {
|
||||
print("❌ [NavigationCubit] RouteException: ${e.message}");
|
||||
emit(state.copyWith(
|
||||
status: NavigationStatus.error,
|
||||
errorMessage: e.message,
|
||||
));
|
||||
} catch (e) {
|
||||
print("❌ [NavigationCubit] Error calculating route: $e");
|
||||
emit(state.copyWith(
|
||||
status: NavigationStatus.error,
|
||||
errorMessage: 'حدث خطأ أثناء استدعاء خدمة التوجيه.',
|
||||
errorMessage: 'حدث خطأ غير متوقع أثناء استدعاء خدمة التوجيه.',
|
||||
));
|
||||
}
|
||||
}
|
||||
@@ -712,13 +968,15 @@ class NavigationCubit extends Cubit<NavigationState> {
|
||||
void startNavigation() {
|
||||
print("🧭 [NavigationCubit] startNavigation triggered!");
|
||||
if (state.currentRoute == null) {
|
||||
print("⚠️ [NavigationCubit] startNavigation aborted: currentRoute is null");
|
||||
print(
|
||||
"⚠️ [NavigationCubit] startNavigation aborted: currentRoute is null");
|
||||
return;
|
||||
}
|
||||
final route = state.currentRoute!;
|
||||
TelemetryTrackingService.instance.initialize();
|
||||
final steps = route.steps;
|
||||
print("🧭 [NavigationCubit] Starting navigation along route: ${route.formattedDistance}, ETA ${route.formattedDuration}, ${steps.length} steps");
|
||||
print(
|
||||
"🧭 [NavigationCubit] Starting navigation along route: ${route.formattedDistance}, ETA ${route.formattedDuration}, ${steps.length} steps");
|
||||
|
||||
_lastTraveledIndexInFullRoute = 0;
|
||||
_offRouteStartTime = null;
|
||||
@@ -727,11 +985,11 @@ class NavigationCubit extends Cubit<NavigationState> {
|
||||
? (steps[0]['text']?.toString() ?? 'انطلق نحو الوجهة')
|
||||
: 'انطلق نحو الوجهة';
|
||||
|
||||
final nextInst = steps.length > 1
|
||||
? (steps[1]['text']?.toString() ?? '')
|
||||
: '';
|
||||
final nextInst =
|
||||
steps.length > 1 ? (steps[1]['text']?.toString() ?? '') : '';
|
||||
|
||||
final initialModifier = steps.isNotEmpty ? (steps[0]['sign'] ?? steps[0]['modifier'] ?? 0) : 0;
|
||||
final initialModifier =
|
||||
steps.isNotEmpty ? (steps[0]['sign'] ?? steps[0]['modifier'] ?? 0) : 0;
|
||||
|
||||
emit(state.copyWith(
|
||||
status: NavigationStatus.navigating,
|
||||
@@ -740,13 +998,16 @@ class NavigationCubit extends Cubit<NavigationState> {
|
||||
nextInstruction: nextInst,
|
||||
currentManeuverModifier: initialModifier,
|
||||
isCameraLocked: true,
|
||||
polylines: _createPolylines(state.routes, state.selectedRouteIndex, traveledIndex: 0),
|
||||
polylines: _createPolylines(state.routes, state.selectedRouteIndex,
|
||||
traveledIndex: 0),
|
||||
));
|
||||
|
||||
ttsService.speak(firstInstruction);
|
||||
|
||||
// Immediate 3D Heading-Up camera orientation with 45° tilt and lookahead offset
|
||||
if (state.myLocation != null && mapController != null && _isMapStyleLoaded) {
|
||||
if (state.myLocation != null &&
|
||||
mapController != null &&
|
||||
_isMapStyleLoaded) {
|
||||
double bearing = state.heading;
|
||||
if (bearing == 0.0 && route.coordinates.length > 1) {
|
||||
bearing = _calculateBearing(route.coordinates[0], route.coordinates[1]);
|
||||
@@ -785,7 +1046,8 @@ class NavigationCubit extends Cubit<NavigationState> {
|
||||
_hasAnnouncedEarlyStepIndex = null;
|
||||
|
||||
final remainingMarkers = Set<Marker>.from(state.markers)
|
||||
..removeWhere((m) => m.markerId.value == 'origin_pin' || m.markerId.value == 'dest_pin');
|
||||
..removeWhere((m) =>
|
||||
m.markerId.value == 'origin_pin' || m.markerId.value == 'dest_pin');
|
||||
|
||||
if (mapController != null && _isMapStyleLoaded) {
|
||||
mapController!.removeMarker(const MarkerId('origin_pin'));
|
||||
@@ -805,7 +1067,9 @@ class NavigationCubit extends Cubit<NavigationState> {
|
||||
nextInstruction: '',
|
||||
isCameraLocked: true,
|
||||
));
|
||||
if (state.myLocation != null && mapController != null && _isMapStyleLoaded) {
|
||||
if (state.myLocation != null &&
|
||||
mapController != null &&
|
||||
_isMapStyleLoaded) {
|
||||
mapController!.animateCamera(
|
||||
CameraUpdate.newCameraPosition(
|
||||
CameraPosition(target: state.myLocation!, zoom: 16.5, tilt: 0),
|
||||
@@ -814,11 +1078,13 @@ class NavigationCubit extends Cubit<NavigationState> {
|
||||
}
|
||||
}
|
||||
|
||||
void simulateLocationTick(LatLng pos, {double speed = 60.0, double heading = 0.0, double altitude = 0.0}) {
|
||||
void simulateLocationTick(LatLng pos,
|
||||
{double speed = 60.0, double heading = 0.0, double altitude = 0.0}) {
|
||||
_onNewLocationFix(pos, heading, speed, altitude);
|
||||
}
|
||||
|
||||
void _processActiveNavigationTick(LatLng pos, double speedKmH, double heading) {
|
||||
void _processActiveNavigationTick(
|
||||
LatLng pos, double speedKmH, double heading) {
|
||||
if (state.currentRoute == null || state.routeSteps.isEmpty) return;
|
||||
|
||||
final route = state.currentRoute!;
|
||||
@@ -864,7 +1130,9 @@ class NavigationCubit extends Cubit<NavigationState> {
|
||||
emit(state.copyWith(distanceToNextStep: distToStep));
|
||||
|
||||
// Advance voice announcement when approaching turn (150m - 200m)
|
||||
if (distToStep <= 180.0 && distToStep > 50.0 && _hasAnnouncedEarlyStepIndex != stepIdx) {
|
||||
if (distToStep <= 180.0 &&
|
||||
distToStep > 50.0 &&
|
||||
_hasAnnouncedEarlyStepIndex != stepIdx) {
|
||||
_hasAnnouncedEarlyStepIndex = stepIdx;
|
||||
final roundDist = ((distToStep / 50).round() * 50).clamp(50, 200);
|
||||
final text = step['text']?.toString() ?? '';
|
||||
@@ -873,17 +1141,22 @@ class NavigationCubit extends Cubit<NavigationState> {
|
||||
}
|
||||
}
|
||||
|
||||
final stepEndIdx = (step['interval'] is List && (step['interval'] as List).length >= 2)
|
||||
? ((step['interval'] as List)[1] as num).toInt()
|
||||
: -1;
|
||||
final hasPassedStep = stepEndIdx > 0 && _lastTraveledIndexInFullRoute >= stepEndIdx;
|
||||
final stepEndIdx =
|
||||
(step['interval'] is List && (step['interval'] as List).length >= 2)
|
||||
? ((step['interval'] as List)[1] as num).toInt()
|
||||
: -1;
|
||||
final hasPassedStep =
|
||||
stepEndIdx > 0 && _lastTraveledIndexInFullRoute >= stepEndIdx;
|
||||
|
||||
if ((distToStep < 35.0 || hasPassedStep) && stepIdx + 1 < steps.length) {
|
||||
if ((distToStep < 35.0 || hasPassedStep) &&
|
||||
stepIdx + 1 < steps.length) {
|
||||
stepIdx++;
|
||||
_hasAnnouncedEarlyStepIndex = null;
|
||||
final nextStep = steps[stepIdx];
|
||||
final text = nextStep['text']?.toString() ?? '';
|
||||
final upcomingText = stepIdx + 1 < steps.length ? (steps[stepIdx + 1]['text']?.toString() ?? '') : '';
|
||||
final upcomingText = stepIdx + 1 < steps.length
|
||||
? (steps[stepIdx + 1]['text']?.toString() ?? '')
|
||||
: '';
|
||||
final modifier = nextStep['sign'] ?? nextStep['modifier'] ?? 0;
|
||||
|
||||
emit(state.copyWith(
|
||||
@@ -922,8 +1195,10 @@ class NavigationCubit extends Cubit<NavigationState> {
|
||||
if (coords.length < 2) return;
|
||||
|
||||
// Search window constrained around vehicle's last known route progress
|
||||
final int startWindow = (_lastTraveledIndexInFullRoute - 3).clamp(0, coords.length - 2);
|
||||
final int endWindow = (_lastTraveledIndexInFullRoute + 45).clamp(0, coords.length - 1);
|
||||
final int startWindow =
|
||||
(_lastTraveledIndexInFullRoute - 3).clamp(0, coords.length - 2);
|
||||
final int endWindow =
|
||||
(_lastTraveledIndexInFullRoute + 45).clamp(0, coords.length - 1);
|
||||
|
||||
double minDistance = double.infinity;
|
||||
int closestSegmentIndex = _lastTraveledIndexInFullRoute;
|
||||
@@ -1035,14 +1310,18 @@ class NavigationCubit extends Cubit<NavigationState> {
|
||||
// ── USER SUBMISSIONS: PLACES & HAZARDS (WITH PIN PICKER) ─────
|
||||
|
||||
void startLocationPicking(String mode) {
|
||||
final targetLoc = mapController?.cameraPosition?.target ?? state.myLocation ?? const LatLng(31.9539, 35.9106);
|
||||
final targetLoc = mapController?.cameraPosition?.target ??
|
||||
state.myLocation ??
|
||||
const LatLng(31.9539, 35.9106);
|
||||
final pickMarker = Marker(
|
||||
markerId: const MarkerId('picker_pin'),
|
||||
position: targetLoc,
|
||||
icon: InlqBitmap.fromStyleImage('dest_icon'),
|
||||
anchor: const Offset(0.5, 1.0),
|
||||
infoWindow: InfoWindow(
|
||||
title: mode == 'place' ? '📍 موقع المنشأة المحددة' : '⚠️ موقع البلاغ المحدد',
|
||||
title: mode == 'place'
|
||||
? '📍 موقع المنشأة المحددة'
|
||||
: '⚠️ موقع البلاغ المحدد',
|
||||
),
|
||||
zIndex: 95,
|
||||
);
|
||||
@@ -1072,7 +1351,9 @@ class NavigationCubit extends Cubit<NavigationState> {
|
||||
icon: InlqBitmap.fromStyleImage('dest_icon'),
|
||||
anchor: const Offset(0.5, 1.0),
|
||||
infoWindow: InfoWindow(
|
||||
title: state.activePickerMode == 'place' ? '📍 موقع المنشأة المحددة' : '⚠️ موقع البلاغ المحدد',
|
||||
title: state.activePickerMode == 'place'
|
||||
? '📍 موقع المنشأة المحددة'
|
||||
: '⚠️ موقع البلاغ المحدد',
|
||||
),
|
||||
zIndex: 95,
|
||||
);
|
||||
@@ -1107,8 +1388,12 @@ class NavigationCubit extends Cubit<NavigationState> {
|
||||
}
|
||||
}
|
||||
|
||||
Future<bool> submitPlace(String name, String category, {LatLng? position}) async {
|
||||
final targetPos = position ?? state.pickedLocation ?? mapController?.cameraPosition?.target ?? state.myLocation;
|
||||
Future<bool> submitPlace(String name, String category,
|
||||
{LatLng? position}) async {
|
||||
final targetPos = position ??
|
||||
state.pickedLocation ??
|
||||
mapController?.cameraPosition?.target ??
|
||||
state.myLocation;
|
||||
if (targetPos == null) return false;
|
||||
|
||||
final success = await repository.submitNewPlace(
|
||||
@@ -1187,7 +1472,8 @@ class NavigationCubit extends Cubit<NavigationState> {
|
||||
return locationService.calculateDistance(p, LatLng(projLat, projLng));
|
||||
}
|
||||
|
||||
Set<Polyline> _createPolylines(List<RouteData> routes, int activeIndex, {int traveledIndex = 0}) {
|
||||
Set<Polyline> _createPolylines(List<RouteData> routes, int activeIndex,
|
||||
{int traveledIndex = 0}) {
|
||||
final Set<Polyline> set = {};
|
||||
|
||||
// 1. Render alternative routes first
|
||||
@@ -1234,7 +1520,8 @@ class NavigationCubit extends Cubit<NavigationState> {
|
||||
|
||||
void _fitRouteInView(List<LatLng> coords) {
|
||||
if (coords.isEmpty || mapController == null || !_isMapStyleLoaded) {
|
||||
print("⚠️ [NavigationCubit] _fitRouteInView deferred/skipped (coordsCount=${coords.length}, mapController=${mapController != null}, styleLoaded=$_isMapStyleLoaded)");
|
||||
print(
|
||||
"⚠️ [NavigationCubit] _fitRouteInView deferred/skipped (coordsCount=${coords.length}, mapController=${mapController != null}, styleLoaded=$_isMapStyleLoaded)");
|
||||
return;
|
||||
}
|
||||
double minLat = coords.first.latitude;
|
||||
@@ -1249,7 +1536,8 @@ class NavigationCubit extends Cubit<NavigationState> {
|
||||
if (c.longitude > maxLng) maxLng = c.longitude;
|
||||
}
|
||||
|
||||
print("🎬 [NavigationCubit] _fitRouteInView: bounds SW=($minLat, $minLng), NE=($maxLat, $maxLng)");
|
||||
print(
|
||||
"🎬 [NavigationCubit] _fitRouteInView: bounds SW=($minLat, $minLng), NE=($maxLat, $maxLng)");
|
||||
mapController!.animateCamera(
|
||||
CameraUpdate.newLatLngBounds(
|
||||
LatLngBounds(
|
||||
@@ -1265,12 +1553,15 @@ class NavigationCubit extends Cubit<NavigationState> {
|
||||
}
|
||||
|
||||
String _calculateArrivalTime(double durationSeconds) {
|
||||
final arrival = DateTime.now().add(Duration(seconds: durationSeconds.round()));
|
||||
final arrival =
|
||||
DateTime.now().add(Duration(seconds: durationSeconds.round()));
|
||||
return DateFormat('hh:mm a').format(arrival);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> close() {
|
||||
_orbitTimer?.cancel();
|
||||
_sunTimer?.cancel();
|
||||
_movementInterpolationTimer?.cancel();
|
||||
_positionStreamSub?.cancel();
|
||||
_connectivitySub?.cancel();
|
||||
|
||||
@@ -55,6 +55,17 @@ class NavigationState extends Equatable {
|
||||
final double vehicleScale;
|
||||
final List<String> recentSearches;
|
||||
final bool isSearching;
|
||||
final bool isOrbiting;
|
||||
final double orbitSpeed;
|
||||
final bool isSunPlaying;
|
||||
final int sunTimeMinutes;
|
||||
final double terrainExaggeration;
|
||||
final double cameraTilt;
|
||||
final bool showBuildings;
|
||||
final bool showContours;
|
||||
final bool showHillshading;
|
||||
final bool isTouristModeEnabled;
|
||||
final String? selectedLandmarkId;
|
||||
|
||||
const NavigationState({
|
||||
this.status = NavigationStatus.initial,
|
||||
@@ -92,6 +103,17 @@ class NavigationState extends Equatable {
|
||||
this.vehicleScale = 1.8,
|
||||
this.recentSearches = const [],
|
||||
this.isSearching = false,
|
||||
this.isOrbiting = false,
|
||||
this.orbitSpeed = 1.0,
|
||||
this.isSunPlaying = false,
|
||||
this.sunTimeMinutes = 1110,
|
||||
this.terrainExaggeration = 2.0,
|
||||
this.cameraTilt = 55.0,
|
||||
this.showBuildings = true,
|
||||
this.showContours = true,
|
||||
this.showHillshading = true,
|
||||
this.isTouristModeEnabled = false,
|
||||
this.selectedLandmarkId,
|
||||
});
|
||||
|
||||
RouteData? get currentRoute =>
|
||||
@@ -166,6 +188,18 @@ class NavigationState extends Equatable {
|
||||
double? vehicleScale,
|
||||
List<String>? recentSearches,
|
||||
bool? isSearching,
|
||||
bool? isOrbiting,
|
||||
double? orbitSpeed,
|
||||
bool? isSunPlaying,
|
||||
int? sunTimeMinutes,
|
||||
double? terrainExaggeration,
|
||||
double? cameraTilt,
|
||||
bool? showBuildings,
|
||||
bool? showContours,
|
||||
bool? showHillshading,
|
||||
bool? isTouristModeEnabled,
|
||||
String? selectedLandmarkId,
|
||||
bool clearSelectedLandmark = false,
|
||||
}) {
|
||||
return NavigationState(
|
||||
status: status ?? this.status,
|
||||
@@ -205,6 +239,17 @@ class NavigationState extends Equatable {
|
||||
vehicleScale: vehicleScale ?? this.vehicleScale,
|
||||
recentSearches: recentSearches ?? this.recentSearches,
|
||||
isSearching: isSearching ?? this.isSearching,
|
||||
isOrbiting: isOrbiting ?? this.isOrbiting,
|
||||
orbitSpeed: orbitSpeed ?? this.orbitSpeed,
|
||||
isSunPlaying: isSunPlaying ?? this.isSunPlaying,
|
||||
sunTimeMinutes: sunTimeMinutes ?? this.sunTimeMinutes,
|
||||
terrainExaggeration: terrainExaggeration ?? this.terrainExaggeration,
|
||||
cameraTilt: cameraTilt ?? this.cameraTilt,
|
||||
showBuildings: showBuildings ?? this.showBuildings,
|
||||
showContours: showContours ?? this.showContours,
|
||||
showHillshading: showHillshading ?? this.showHillshading,
|
||||
isTouristModeEnabled: isTouristModeEnabled ?? this.isTouristModeEnabled,
|
||||
selectedLandmarkId: clearSelectedLandmark ? null : (selectedLandmarkId ?? this.selectedLandmarkId),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -245,5 +290,16 @@ class NavigationState extends Equatable {
|
||||
vehicleScale,
|
||||
recentSearches,
|
||||
isSearching,
|
||||
isOrbiting,
|
||||
orbitSpeed,
|
||||
isSunPlaying,
|
||||
sunTimeMinutes,
|
||||
terrainExaggeration,
|
||||
cameraTilt,
|
||||
showBuildings,
|
||||
showContours,
|
||||
showHillshading,
|
||||
isTouristModeEnabled,
|
||||
selectedLandmarkId,
|
||||
];
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user