stopNavigation method

Future<void> stopNavigation()

Implementation

Future<void> stopNavigation() async {
  _ticker?.stop();
  _ticker?.dispose();
  _ticker = null;

  _gpsAnimationTicker?.stop();
  _gpsAnimationTicker?.dispose();
  _gpsAnimationTicker = null;
  _compassSubscription?.cancel();

  _locationTracker.stopTracking();
  _locationTracker.currentLocation.removeListener(_onGpsLocationUpdated);

  _flutterTts.stop();
  _smoothedGps = null;
  _nearDestSince = null;
  _hasTriggeredArrival = false;
  _isCameraTransitioning = false;
  _isReturningToVehicle = false;
  _lastSegmentIndex = 0; // D+1: Reset
  _lastGpsUpdateTime = null; // D+2: Reset
  _playedDistanceAnnouncements.clear(); // D+3: Reset
  _inTurnApproach = false;
  _speedLimitService.reset();
  _unregisterLifecycleObserver();
  stateNotifier.value = null;

  if (_vehicleSymbolAdded) {
    try {
      _controller.mapController.removeLayer("vehicle-hitbox-layer");
      _controller.mapController.removeLayer("vehicle-layer");
      _controller.mapController.removeSource("vehicle-source");
    } catch (e) {
      // Ignored if layer or source is already removed
    }
    _vehicleSymbolAdded = false;
  }

  // C5: Remove traveled route layer and off-route connector layer
  if (_traveledRouteAdded) {
    try {
      _controller.mapController.removeLayer('traveled-route-layer');
      _controller.mapController.removeSource('traveled-route-source');
    } catch (_) {}
    _traveledRouteAdded = false;
    _traveledPoints.clear();
  }
  if (_offRouteConnectorAdded) {
    try {
      _controller.mapController.removeLayer('offroute-connector-layer');
      _controller.mapController.removeSource('offroute-connector-source');
    } catch (_) {}
    _offRouteConnectorAdded = false;
  }

  // Clean up active navigation turn guidance arrows
  try {
    await _controller.routing.disposeStepTurnArrows();
  } catch (_) {}

  // Restore user location marker (Ghost Marker prevention)
  _controller.showUserLocation(true);

  // Unregister tap listener
  _controller.mapController.onFeatureTapped.remove(_handleFeatureTapped);

  // Restore camera and Original Route
  final routeToRestore = _originalRoute;
  _originalRoute = null; // Clear before use to allow fresh navigation
  if (routeToRestore != null && routeToRestore.geometry.isNotEmpty) {
    // Restore route line visually
    _controller.routing.drawSavedRoute(routeToRestore.geometry);

    // 2-step smooth exit animation:
    // Step 1 — instantly flatten map to 2D at current vehicle position.
    //           MUST be instant: MapLibre calculates wrong viewport if tilt > 0.
    final currentCam = _controller.mapController.cameraPosition;
    if (currentCam != null) {
      _controller.mapController.moveCamera(
        CameraUpdate.newCameraPosition(
          CameraPosition(
            target: currentCam.target,
            zoom: currentCam.zoom,
            tilt: 0.0,
            bearing: 0.0,
          ),
        ),
      );
    }
    // Step 2 — smooth 800ms animate back to pre-navigation overview.
    if (routeToRestore.geometry.length >= 2) {
      double minLat = routeToRestore.geometry.first.latitude;
      double maxLat = minLat;
      double minLng = routeToRestore.geometry.first.longitude;
      double maxLng = minLng;
      for (final p in routeToRestore.geometry) {
        if (p.latitude < minLat) minLat = p.latitude;
        if (p.latitude > maxLat) maxLat = p.latitude;
        if (p.longitude < minLng) minLng = p.longitude;
        if (p.longitude > maxLng) maxLng = p.longitude;
      }
      await Future.delayed(const Duration(milliseconds: 16));
      _controller.mapController.animateCamera(
        CameraUpdate.newLatLngBounds(
          LatLngBounds(
            southwest: LatLng(minLat, minLng),
            northeast: LatLng(maxLat, maxLng),
          ),
          left: 50, right: 50, top: 80, bottom: 200,
        ),
        duration: const Duration(milliseconds: 800),
      );
    } else {
      final restoreTarget = _savedCenter ?? routeToRestore.geometry.first;
      final restoreZoom = _savedZoom ?? 14.0;
      await Future.delayed(const Duration(milliseconds: 16)); // one frame gap
      _controller.mapController.animateCamera(
        CameraUpdate.newCameraPosition(
          CameraPosition(
            target: restoreTarget,
            zoom: restoreZoom,
            tilt: 0.0,
            bearing: 0.0,
          ),
        ),
        duration: const Duration(milliseconds: 800),
      );
    }
  } else {
    // Fallback: just flatten the tilt
    _controller.mapController.animateCamera(CameraUpdate.tiltTo(0.0));
  }
  _savedCenter = null;
  _savedZoom = null;
}