From 75fe50521d544cf83b3514cf9d31a689068d6558 Mon Sep 17 00:00:00 2001 From: Joonas Kerttula Date: Tue, 30 Sep 2025 16:08:39 +0300 Subject: [PATCH 1/2] chore: reformat codebase with swift-format version 602.0.0 --- .github/workflows/analyze.yaml | 2 +- .../GoogleMapsAutoViewMessageHandler.swift | 28 ++++++------- .../GoogleMapsNavigationSessionManager.swift | 39 ++++++++++--------- ...eMapsNavigationSessionMessageHandler.swift | 39 ++++++++++--------- ...ogleMapsNavigationViewMessageHandler.swift | 28 ++++++------- 5 files changed, 73 insertions(+), 63 deletions(-) diff --git a/.github/workflows/analyze.yaml b/.github/workflows/analyze.yaml index 13d9af05..4e7683c3 100644 --- a/.github/workflows/analyze.yaml +++ b/.github/workflows/analyze.yaml @@ -70,7 +70,7 @@ jobs: with: path: /home/linuxbrew/.linuxbrew key: ${{ runner.os }}-linuxbrew - - name: Install swift-format 601.0.0 + - name: Install swift-format 602.0.0 if: steps.cache.outputs.cache-hit != 'true' run: | eval "$(/home/linuxbrew/.linuxbrew/bin/brew shellenv)" diff --git a/ios/google_navigation_flutter/Sources/google_navigation_flutter/GoogleMapsAutoViewMessageHandler.swift b/ios/google_navigation_flutter/Sources/google_navigation_flutter/GoogleMapsAutoViewMessageHandler.swift index 28a1aa15..fd1499c6 100644 --- a/ios/google_navigation_flutter/Sources/google_navigation_flutter/GoogleMapsAutoViewMessageHandler.swift +++ b/ios/google_navigation_flutter/Sources/google_navigation_flutter/GoogleMapsAutoViewMessageHandler.swift @@ -39,7 +39,7 @@ class GoogleMapsAutoViewMessageHandler: AutoMapViewApi { switch result { case .success: completion(.success(())) - case let .failure(error): + case .failure(let error): completion(.failure(error)) } } @@ -207,12 +207,13 @@ class GoogleMapsAutoViewMessageHandler: AutoMapViewApi { func animateCameraToLatLngBounds( bounds: LatLngBoundsDto, padding: Double, duration: Int64?, - completion: @escaping ( - Result< - Bool, - Error - > - ) + completion: + @escaping ( + Result< + Bool, + Error + > + ) -> Void ) { do { @@ -264,12 +265,13 @@ class GoogleMapsAutoViewMessageHandler: AutoMapViewApi { func animateCameraByZoom( zoomBy: Double, focusDx: Double?, focusDy: Double?, duration: Int64?, - completion: @escaping ( - Result< - Bool, - Error - > - ) -> Void + completion: + @escaping ( + Result< + Bool, + Error + > + ) -> Void ) { do { let focus = Convert.convertDeltaToPoint(dx: focusDx, dy: focusDy) diff --git a/ios/google_navigation_flutter/Sources/google_navigation_flutter/GoogleMapsNavigationSessionManager.swift b/ios/google_navigation_flutter/Sources/google_navigation_flutter/GoogleMapsNavigationSessionManager.swift index 4e5481cf..2066f325 100644 --- a/ios/google_navigation_flutter/Sources/google_navigation_flutter/GoogleMapsNavigationSessionManager.swift +++ b/ios/google_navigation_flutter/Sources/google_navigation_flutter/GoogleMapsNavigationSessionManager.swift @@ -349,12 +349,13 @@ class GoogleMapsNavigationSessionManager: NSObject { func simulateLocationsAlongNewRoute( waypoints: [NavigationWaypointDto], - completion: @escaping ( - Result< - RouteStatusDto, - Error - > - ) + completion: + @escaping ( + Result< + RouteStatusDto, + Error + > + ) -> Void ) throws { /// Speedmultiplier is set to default value here because the functions using @@ -394,12 +395,13 @@ class GoogleMapsNavigationSessionManager: NSObject { func simulateLocationsAlongNewRouteWithRoutingOptions( waypoints: [NavigationWaypointDto], routingOptions: RoutingOptionsDto, - completion: @escaping ( - Result< - RouteStatusDto, - Error - > - ) -> Void + completion: + @escaping ( + Result< + RouteStatusDto, + Error + > + ) -> Void ) throws { /// Speedmultiplier is set to default value here because the functions using /// SimulationOptionsDto will set it globally to a custom value. This @@ -421,12 +423,13 @@ class GoogleMapsNavigationSessionManager: NSObject { waypoints: [NavigationWaypointDto], routingOptions: RoutingOptionsDto, simulationOptions: SimulationOptionsDto, - completion: @escaping ( - Result< - RouteStatusDto, - Error - > - ) -> Void + completion: + @escaping ( + Result< + RouteStatusDto, + Error + > + ) -> Void ) throws { try getSimulator().speedMultiplier = Float(simulationOptions.speedMultiplier) try setRoutingOptionsGlobals(routingOptions, for: .simulator) diff --git a/ios/google_navigation_flutter/Sources/google_navigation_flutter/GoogleMapsNavigationSessionMessageHandler.swift b/ios/google_navigation_flutter/Sources/google_navigation_flutter/GoogleMapsNavigationSessionMessageHandler.swift index 7c673039..4a73622f 100644 --- a/ios/google_navigation_flutter/Sources/google_navigation_flutter/GoogleMapsNavigationSessionMessageHandler.swift +++ b/ios/google_navigation_flutter/Sources/google_navigation_flutter/GoogleMapsNavigationSessionMessageHandler.swift @@ -143,12 +143,13 @@ class GoogleMapsNavigationSessionMessageHandler: NavigationSessionApi { func simulateLocationsAlongNewRoute( waypoints: [NavigationWaypointDto], - completion: @escaping ( - Result< - RouteStatusDto, - Error - > - ) + completion: + @escaping ( + Result< + RouteStatusDto, + Error + > + ) -> Void ) { do { @@ -164,12 +165,13 @@ class GoogleMapsNavigationSessionMessageHandler: NavigationSessionApi { func simulateLocationsAlongNewRouteWithRoutingOptions( waypoints: [NavigationWaypointDto], routingOptions: RoutingOptionsDto, - completion: @escaping ( - Result< - RouteStatusDto, - Error - > - ) -> Void + completion: + @escaping ( + Result< + RouteStatusDto, + Error + > + ) -> Void ) { do { try GoogleMapsNavigationSessionManager.shared @@ -187,12 +189,13 @@ class GoogleMapsNavigationSessionMessageHandler: NavigationSessionApi { waypoints: [NavigationWaypointDto], routingOptions: RoutingOptionsDto, simulationOptions: SimulationOptionsDto, - completion: @escaping ( - Result< - RouteStatusDto, - Error - > - ) -> Void + completion: + @escaping ( + Result< + RouteStatusDto, + Error + > + ) -> Void ) { do { try GoogleMapsNavigationSessionManager.shared diff --git a/ios/google_navigation_flutter/Sources/google_navigation_flutter/GoogleMapsNavigationViewMessageHandler.swift b/ios/google_navigation_flutter/Sources/google_navigation_flutter/GoogleMapsNavigationViewMessageHandler.swift index eb87b9e4..c0acc666 100644 --- a/ios/google_navigation_flutter/Sources/google_navigation_flutter/GoogleMapsNavigationViewMessageHandler.swift +++ b/ios/google_navigation_flutter/Sources/google_navigation_flutter/GoogleMapsNavigationViewMessageHandler.swift @@ -40,7 +40,7 @@ class GoogleMapsNavigationViewMessageHandler: MapViewApi { switch result { case .success: completion(.success(())) - case let .failure(error): + case .failure(let error): completion(.failure(error)) } } @@ -208,12 +208,13 @@ class GoogleMapsNavigationViewMessageHandler: MapViewApi { func animateCameraToLatLngBounds( viewId: Int64, bounds: LatLngBoundsDto, padding: Double, duration: Int64?, - completion: @escaping ( - Result< - Bool, - Error - > - ) -> Void + completion: + @escaping ( + Result< + Bool, + Error + > + ) -> Void ) { do { try getView(viewId).animateCameraToLatLngBounds( @@ -264,12 +265,13 @@ class GoogleMapsNavigationViewMessageHandler: MapViewApi { func animateCameraByZoom( viewId: Int64, zoomBy: Double, focusDx: Double?, focusDy: Double?, duration: Int64?, - completion: @escaping ( - Result< - Bool, - Error - > - ) -> Void + completion: + @escaping ( + Result< + Bool, + Error + > + ) -> Void ) { do { let focus = Convert.convertDeltaToPoint(dx: focusDx, dy: focusDy) From a1c6fd9b6544cebccdb44de835d90fa60ea6a3ef Mon Sep 17 00:00:00 2001 From: Joonas Kerttula Date: Fri, 3 Oct 2025 15:45:54 +0300 Subject: [PATCH 2/2] feat: deprecate continueToNextDestination method --- analysis_options.yaml | 2 + example/android/app/build.gradle.kts | 4 +- .../t01_initialization_test.dart | 2 + .../integration_test/t03_navigation_test.dart | 63 +++++-- example/lib/pages/navigation.dart | 158 ++++++++++++++---- lib/src/method_channel/session_api.dart | 1 + .../google_navigation_flutter_navigator.dart | 4 + 7 files changed, 193 insertions(+), 41 deletions(-) diff --git a/analysis_options.yaml b/analysis_options.yaml index c72b2230..6506a5c8 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -28,6 +28,8 @@ analyzer: errors: # allow self-reference to deprecated members deprecated_member_use_from_same_package: ignore + # allow deprecated member use in test files and examples + deprecated_member_use: ignore exclude: # Ignore generated files - "**/*.g.dart" diff --git a/example/android/app/build.gradle.kts b/example/android/app/build.gradle.kts index 91329980..5a157940 100644 --- a/example/android/app/build.gradle.kts +++ b/example/android/app/build.gradle.kts @@ -58,7 +58,7 @@ android { applicationId = "com.google.maps.flutter.navigation_example" // You can update the following values to match your application needs. // For more information, see: https://flutter.dev/to/review-gradle-config. - minSdk = 23 + minSdk = flutter.minSdkVersion targetSdk = flutter.targetSdkVersion versionCode = flutter.versionCode versionName = flutter.versionName @@ -123,4 +123,4 @@ secrets { ignoreList.add("sdk.*") // Ignore all keys matching the regexp "flutter.*" ignoreList.add("flutter.*") -} \ No newline at end of file +} diff --git a/example/integration_test/t01_initialization_test.dart b/example/integration_test/t01_initialization_test.dart index 23605872..d9f3eb83 100644 --- a/example/integration_test/t01_initialization_test.dart +++ b/example/integration_test/t01_initialization_test.dart @@ -123,6 +123,8 @@ void main() { } try { + // Note: Testing deprecated continueToNextDestination for proper exception + // handling. await GoogleMapsNavigator.continueToNextDestination(); fail('Expected SessionNotInitializedException.'); } on Exception catch (e) { diff --git a/example/integration_test/t03_navigation_test.dart b/example/integration_test/t03_navigation_test.dart index 1d8e6292..af27a45f 100644 --- a/example/integration_test/t03_navigation_test.dart +++ b/example/integration_test/t03_navigation_test.dart @@ -33,6 +33,10 @@ void main() { final GoogleNavigationInspectorPlatform inspector = GoogleNavigationInspectorPlatform.instance!; + final multipleDestinationsVariants = ValueVariant({ + 'continueToNextDestination', + 'setDestinations', + }); /// Start location coordinates in Finland (Näkkäläntie). const double startLat = startLocationLat; @@ -183,6 +187,7 @@ void main() { (PatrolIntegrationTester $) async { final Completer navigationFinished = Completer(); int arrivalEventCount = 0; + List waypoints = []; /// Set up navigation view and controller. final GoogleNavigationViewController viewController = @@ -208,7 +213,39 @@ void main() { Future onArrivalEvent(OnArrivalEvent msg) async { arrivalEventCount += 1; - await GoogleMapsNavigator.continueToNextDestination(); + + if (multipleDestinationsVariants.currentValue == + 'continueToNextDestination') { + // Note: continueToNextDestination is deprecated. + // This test still uses it to verify the deprecated API works correctly. + // For new implementations, use setDestinations with updated waypoints instead. + await GoogleMapsNavigator.continueToNextDestination(); + } else { + // Find and remove the waypoint that matches the arrived waypoint + int waypointIndex = -1; + for (int i = 0; i < waypoints.length; i++) { + final NavigationWaypoint waypoint = waypoints[i]; + if (waypoint.title == msg.waypoint.title) { + waypointIndex = i; + break; + } + } + + if (waypointIndex >= 0) { + waypoints.removeAt(waypointIndex); + } + + if (waypoints.isNotEmpty) { + // Update destinations with remaining waypoints + final Destinations updatedDestinations = Destinations( + waypoints: waypoints, + displayOptions: NavigationDisplayOptions( + showDestinationMarkers: false, + ), + ); + await GoogleMapsNavigator.setDestinations(updatedDestinations); + } + } /// Finish executing the tests once 2 onArrival events come in. /// Test the guidance stops on last Arrival. @@ -228,18 +265,21 @@ void main() { tolerance, ); + /// Set up initial waypoints. + waypoints = [ + NavigationWaypoint.withLatLngTarget( + title: 'Näkkäläntie 1st stop', + target: const LatLng(latitude: midLat, longitude: midLon), + ), + NavigationWaypoint.withLatLngTarget( + title: 'Näkkäläntie 2nd stop', + target: const LatLng(latitude: endLat, longitude: endLng), + ), + ]; + /// Set Destination. final Destinations destinations = Destinations( - waypoints: [ - NavigationWaypoint.withLatLngTarget( - title: 'Näkkäläntie 1st stop', - target: const LatLng(latitude: midLat, longitude: midLon), - ), - NavigationWaypoint.withLatLngTarget( - title: 'Näkkäläntie 2nd stop', - target: const LatLng(latitude: endLat, longitude: endLng), - ), - ], + waypoints: waypoints, displayOptions: NavigationDisplayOptions(showDestinationMarkers: false), ); final NavigationRouteStatus status = @@ -293,6 +333,7 @@ void main() { await GoogleMapsNavigator.cleanup(); }, + variant: multipleDestinationsVariants, // TODO(jokerttu): Skipping Android as this fails on Android emulator on CI. skip: Platform.isAndroid, ); diff --git a/example/lib/pages/navigation.dart b/example/lib/pages/navigation.dart index 311686fe..7dee8189 100644 --- a/example/lib/pages/navigation.dart +++ b/example/lib/pages/navigation.dart @@ -47,6 +47,10 @@ enum SimulationState { /// Simulation running with outdated route. runningOutdated, + /// Simulation waiting for a new route, and continues to the next destination + /// when the new route is ready. + waitingNewRoute, + /// Simulation paused. paused, @@ -70,7 +74,7 @@ class _NavigationPageState extends ExamplePageState { static const int _userLocationTimeoutMS = 1500; /// Speed multiplier used for simulation. - static const double simulationSpeedMultiplier = 5; + static const double simulationSpeedMultiplier = 3.0; /// Navigation view controller used to interact with the navigation view. GoogleNavigationViewController? _navigationViewController; @@ -146,6 +150,12 @@ class _NavigationPageState extends ExamplePageState { int _nextWaypointIndex = 0; + /// Track when user has arrived at a waypoint but hasn't continued yet + bool _waitingForUserToContinue = false; + + /// The waypoint the user has arrived at and needs to continue from + NavigationWaypoint? _arrivedWaypoint; + EdgeInsets _mapPadding = const EdgeInsets.all(0); EdgeInsets _autoViewMapPadding = const EdgeInsets.all(0); @@ -444,8 +454,12 @@ class _NavigationPageState extends ExamplePageState { if (!mounted) { return; } + if (_simulationState == SimulationState.running) { _simulationState = SimulationState.runningOutdated; + } else if (_simulationState == SimulationState.waitingNewRoute) { + // Continue simulation with the new route as soon as it's available. + _startSimulation(); } setState(() { _onRouteChangedEventCallCount += 1; @@ -607,6 +621,8 @@ class _NavigationPageState extends ExamplePageState { _nextWaypointIndex = 0; _remainingDistance = 0; _remainingTime = 0; + _waitingForUserToContinue = false; + _arrivedWaypoint = null; }); } @@ -748,34 +764,104 @@ class _NavigationPageState extends ExamplePageState { Future _arrivedToWaypoint(NavigationWaypoint waypoint) async { debugPrint('Arrived to waypoint: ${waypoint.title}'); - // Remove the first waypoint from the list. - if (_waypoints.isNotEmpty) { - _waypoints.removeAt(0); - } - // Remove the first destination marker from the list. - if (_destinationWaypointMarkers.isNotEmpty) { - final Marker markerToRemove = _destinationWaypointMarkers.first; - await _navigationViewController!.removeMarkers([markerToRemove]); + // Find the waypoint which has been arrived to. + final int waypointIndex = _waypoints.indexWhere( + (NavigationWaypoint currentWaypoint) => + currentWaypoint.title == waypoint.title, + ); - // Unregister custom marker image. - await unregisterImage(markerToRemove.options.icon); + if (waypointIndex >= 0) { + // Pause simulation when arriving at any waypoint to preserve location + if (_simulationState != SimulationState.notRunning) { + await _pauseSimulation(); + await _simulateUserLocation(_waypoints[waypointIndex].target); + } - _destinationWaypointMarkers.removeAt(0); - } + // Remove the corresponding destination marker. + if (waypointIndex < _destinationWaypointMarkers.length) { + final Marker markerToRemove = + _destinationWaypointMarkers[waypointIndex]; + await _navigationViewController!.removeMarkers([ + markerToRemove, + ]); - await GoogleMapsNavigator.continueToNextDestination(); + // Unregister custom marker image. + await unregisterImage(markerToRemove.options.icon); + + _destinationWaypointMarkers.removeAt(waypointIndex); + } + + // Remove the arrived waypoint from the list + _waypoints.removeAt(waypointIndex); + } if (_waypoints.isEmpty) { - debugPrint('Arrived to last waypoint, stopping navigation.'); + debugPrint('Arrived to final destination, stopping navigation.'); - // If there is no next waypoint, it means we have arrived at the last - // destination. Hence, stop navigation. + // If there is no next waypoint, it means we have arrived at the final + // destination. Stop navigation completely. await _stopGuidedNavigation(); + + showMessage('You have arrived at your final destination!'); + } else { + debugPrint('Arrived at waypoint, waiting for user to continue...'); + + // Stop guidance but keep navigation session active + await _stopGuidance(); + + // Set state to waiting for user to continue + setState(() { + _waitingForUserToContinue = true; + _arrivedWaypoint = waypoint; + }); + + showMessage( + 'You have arrived at ${waypoint.title}. Tap "Continue guidance" to proceed to the next destination.', + ); } setState(() {}); } + /// Continue guidance to the next destination. + Future _continueGuidanceToNextDestination() async { + if (!_waitingForUserToContinue || _waypoints.isEmpty) { + return; + } + + debugPrint('Continuing guidance to next destination...'); + + // Update destinations with the remaining waypoints. + final Destinations? updatedDestinations = _buildDestinations(); + if (updatedDestinations != null) { + if (_simulationState != SimulationState.notRunning) { + // If simulation was active set the simulation state to waiting for new + // route, so that simulation is continued automatically when the new + // route is ready after setting the new destinations. + _simulationState = SimulationState.waitingNewRoute; + } + final NavigationRouteStatus status = + await GoogleMapsNavigator.setDestinations(updatedDestinations); + if (status == NavigationRouteStatus.statusOk) { + // Start guidance again + await _startGuidance(); + + setState(() { + _waitingForUserToContinue = false; + _arrivedWaypoint = null; + }); + + showMessage('Continuing to next destination...'); + } else { + _stopGuidance(); + _stopSimulation(); + showMessage( + 'Failed to continue to next destination. Please try again.', + ); + } + } + } + Future _clearNavigationWaypoints() async { // Stopping guided navigation will also clear the waypoints. await _stopGuidedNavigation(); @@ -972,7 +1058,7 @@ class _NavigationPageState extends ExamplePageState { final LatLng? myLocation = _userLocation ?? await _navigationViewController!.getMyLocation(); if (myLocation != null) { - await GoogleMapsNavigator.simulator.setUserLocation(myLocation); + await _simulateUserLocation(myLocation); } await GoogleMapsNavigator.simulator @@ -986,6 +1072,13 @@ class _NavigationPageState extends ExamplePageState { } } + Future _simulateUserLocation(LatLng? location) async { + if (location == null) { + return; + } + await GoogleMapsNavigator.simulator.setUserLocation(location); + } + Future _stopSimulation() async { await GoogleMapsNavigator.simulator.removeUserLocation(); setState(() { @@ -1142,25 +1235,31 @@ class _NavigationPageState extends ExamplePageState { child: const Text('Retry'), ), ], - if (_guidanceRunning && - _simulationState == SimulationState.runningOutdated) + if (_waitingForUserToContinue && _arrivedWaypoint != null) Wrap( + crossAxisAlignment: WrapCrossAlignment.center, alignment: WrapAlignment.center, spacing: 10, children: [ - const Text('Simulation is running with outdated route'), + Text('Arrived at ${_arrivedWaypoint!.title}'), ElevatedButton( - onPressed: () => _startSimulation(), - child: const Text('Update simulation'), + onPressed: _continueGuidanceToNextDestination, + child: const Text('Continue guidance'), ), ], ), + if (_guidanceRunning && + _simulationState == SimulationState.runningOutdated) + ElevatedButton( + onPressed: () => _startSimulation(), + child: const Text('Update simulation route'), + ), if (_waypoints.isNotEmpty) Wrap( alignment: WrapAlignment.center, spacing: 10, children: [ - if (!_guidanceRunning) + if (!_guidanceRunning && !_waitingForUserToContinue) ElevatedButton( onPressed: _validRoute ? _startGuidedNavigation : null, child: const Text('Start Guidance'), @@ -1183,9 +1282,8 @@ class _NavigationPageState extends ExamplePageState { child: const Text('Resume simulation state'), ), if (_guidanceRunning && - (_simulationState == SimulationState.running || - _simulationState == SimulationState.runningOutdated || - _simulationState == SimulationState.paused)) + (_simulationState != SimulationState.notRunning && + _simulationState != SimulationState.unknown)) ElevatedButton( onPressed: () => _stopSimulation(), child: const Text('Stop simulation'), @@ -1207,7 +1305,9 @@ class _NavigationPageState extends ExamplePageState { ), ElevatedButton( onPressed: - _waypoints.isNotEmpty && !_guidanceRunning + _waypoints.isNotEmpty && + !_guidanceRunning && + !_waitingForUserToContinue ? () => _clearNavigationWaypoints() : null, child: const Text('Clear waypoints'), @@ -1962,6 +2062,8 @@ extension SimulationStateDescription on SimulationState { return 'Paused'; case SimulationState.notRunning: return 'Not running'; + case SimulationState.waitingNewRoute: + return 'Waiting for new route'; } } } diff --git a/lib/src/method_channel/session_api.dart b/lib/src/method_channel/session_api.dart index 51983eb0..4cde5fed 100644 --- a/lib/src/method_channel/session_api.dart +++ b/lib/src/method_channel/session_api.dart @@ -237,6 +237,7 @@ class NavigationSessionAPIImpl { } /// Continues to next waypoint. + @Deprecated('Use setDestinations with an updated list of waypoints instead') Future continueToNextDestination() async { try { final NavigationWaypointDto? waypointDto = diff --git a/lib/src/navigator/google_navigation_flutter_navigator.dart b/lib/src/navigator/google_navigation_flutter_navigator.dart index 0b545ec1..2bda2f25 100644 --- a/lib/src/navigator/google_navigation_flutter_navigator.dart +++ b/lib/src/navigator/google_navigation_flutter_navigator.dart @@ -543,6 +543,10 @@ class GoogleMapsNavigator { /// Removes the current waypoint. /// Following this call, guidance will be toward the next destination, /// and information about the old destination is not available. + /// + /// Deprecated: Use [setDestinations] with an updated list of waypoints + /// instead. This approach provides better control over the navigation route. + @Deprecated('Use setDestinations with an updated list of waypoints instead') static Future continueToNextDestination() { return GoogleMapsNavigationPlatform.instance.navigationSessionAPI .continueToNextDestination();