diff --git a/android/src/main/java/com/google/android/react/navsdk/NavViewFragment.java b/android/src/main/java/com/google/android/react/navsdk/NavViewFragment.java index 16c734b..f33baf4 100644 --- a/android/src/main/java/com/google/android/react/navsdk/NavViewFragment.java +++ b/android/src/main/java/com/google/android/react/navsdk/NavViewFragment.java @@ -86,6 +86,23 @@ public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceStat applyMapColorSchemeToMap(); applyNightModePreference(); + googleMap.setOnFollowMyLocationCallback( + new GoogleMap.OnCameraFollowLocationCallback() { + @Override + public void onCameraStartedFollowingLocation() { + WritableMap map = Arguments.createMap(); + map.putBoolean("isFollowing", true); + emitEvent("onCameraFollowLocationChanged", map); + } + + @Override + public void onCameraStoppedFollowingLocation() { + WritableMap map = Arguments.createMap(); + map.putBoolean("isFollowing", false); + emitEvent("onCameraFollowLocationChanged", map); + } + }); + emitEvent("onMapReady", null); // Request layout to ensure fragment is properly sized diff --git a/ios/react-native-navigation-sdk/INavigationViewCallback.h b/ios/react-native-navigation-sdk/INavigationViewCallback.h index de2aa68..0a28a48 100644 --- a/ios/react-native-navigation-sdk/INavigationViewCallback.h +++ b/ios/react-native-navigation-sdk/INavigationViewCallback.h @@ -35,6 +35,7 @@ NS_ASSUME_NONNULL_BEGIN - (void)handleCircleClick:(GMSCircle *)circle; - (void)handleGroundOverlayClick:(GMSGroundOverlay *)groundOverlay; - (void)handlePromptVisibilityChanged:(BOOL)isVisible; +- (void)handleCameraFollowLocationChanged:(BOOL)isFollowing; @end NS_ASSUME_NONNULL_END diff --git a/ios/react-native-navigation-sdk/NavView.mm b/ios/react-native-navigation-sdk/NavView.mm index cac635d..97acfe0 100644 --- a/ios/react-native-navigation-sdk/NavView.mm +++ b/ios/react-native-navigation-sdk/NavView.mm @@ -415,6 +415,11 @@ - (void)handleCircleClick:(GMSCircle *)circle { self.eventEmitter.onCircleClick(result); } +- (void)handleCameraFollowLocationChanged:(BOOL)isFollowing { + NavViewEventEmitter::OnCameraFollowLocationChanged result = {isFollowing}; + self.eventEmitter.onCameraFollowLocationChanged(result); +} + - (void)handleGroundOverlayClick:(GMSGroundOverlay *)groundOverlay { NavViewEventEmitter::OnGroundOverlayClick result = { [ObjectTranslationUtil isIdOnUserData:groundOverlay.userData] diff --git a/ios/react-native-navigation-sdk/NavViewController.mm b/ios/react-native-navigation-sdk/NavViewController.mm index 059e5d5..3326b2f 100644 --- a/ios/react-native-navigation-sdk/NavViewController.mm +++ b/ios/react-native-navigation-sdk/NavViewController.mm @@ -37,6 +37,7 @@ @implementation NavViewController { MapViewType *_mapViewType; // Nullable - must be set before loadView id _viewCallbacks; BOOL _isSessionAttached; + BOOL _lastReportedIsFollowing; NSNumber *_isNavigationUIEnabled; NSNumber *_navigationUIEnabledPreference; // 0=AUTOMATIC, 1=DISABLED NSNumber *_navigationLightingMode; @@ -270,6 +271,29 @@ - (void)dealloc { - (void)mapViewDidTapRecenterButton:(GMSMapView *)mapView { [_viewCallbacks handleRecenterButtonClick]; + dispatch_async(dispatch_get_main_queue(), ^{ + [self reportFollowingStateIfChanged]; + }); +} + +- (void)mapView:(GMSMapView *)mapView willMove:(BOOL)gesture { + if (gesture) { + dispatch_async(dispatch_get_main_queue(), ^{ + [self reportFollowingStateIfChanged]; + }); + } +} + +- (void)mapView:(GMSMapView *)mapView idleAtCameraPosition:(GMSCameraPosition *)position { + [self reportFollowingStateIfChanged]; +} + +- (void)reportFollowingStateIfChanged { + BOOL isFollowing = (_mapView.cameraMode == GMSNavigationCameraModeFollowing); + if (_lastReportedIsFollowing != isFollowing) { + _lastReportedIsFollowing = isFollowing; + [_viewCallbacks handleCameraFollowLocationChanged:isFollowing]; + } } - (void)mapView:(GMSMapView *)mapView didTapInfoWindowOfMarker:(GMSMarker *)marker { @@ -518,6 +542,7 @@ - (void)setNightMode:(NSNumber *)index { - (void)showRouteOverview { _mapView.cameraMode = GMSNavigationCameraModeOverview; + [self reportFollowingStateIfChanged]; } - (void)setTripProgressBarEnabled:(BOOL)isEnabled { @@ -569,6 +594,7 @@ - (void)setFollowingPerspective:(NSNumber *)index { [_mapView setFollowingPerspective:GMSNavigationCameraPerspectiveTilted]; } _mapView.cameraMode = GMSNavigationCameraModeFollowing; + [self reportFollowingStateIfChanged]; } - (void)setSpeedometerEnabled:(BOOL)isEnabled { diff --git a/src/native/NativeNavViewComponent.ts b/src/native/NativeNavViewComponent.ts index a03da68..2b42395 100644 --- a/src/native/NativeNavViewComponent.ts +++ b/src/native/NativeNavViewComponent.ts @@ -194,6 +194,7 @@ export interface NativeNavViewProps extends ViewProps { }>; onRecenterButtonClick?: DirectEventHandler; onPromptVisibilityChanged?: DirectEventHandler<{ visible: boolean }>; + onCameraFollowLocationChanged?: DirectEventHandler<{ isFollowing: boolean }>; } export type NativeNavViewType = HostComponent; diff --git a/src/navigation/navigationView/navigationView.tsx b/src/navigation/navigationView/navigationView.tsx index 22f4d15..0a0e6c1 100644 --- a/src/navigation/navigationView/navigationView.tsx +++ b/src/navigation/navigationView/navigationView.tsx @@ -221,6 +221,15 @@ export const NavigationView = ( [onPromptVisibilityChangedProp] ); + const { onCameraFollowLocationChanged: onCameraFollowLocationChangedProp } = + props; + const onCameraFollowLocationChanged = useCallback( + (event: { nativeEvent: { isFollowing: boolean } }) => { + onCameraFollowLocationChangedProp?.(event.nativeEvent.isFollowing); + }, + [onCameraFollowLocationChangedProp] + ); + return ( ); }; diff --git a/src/navigation/navigationView/types.ts b/src/navigation/navigationView/types.ts index 5ac5d4b..f85eb8a 100644 --- a/src/navigation/navigationView/types.ts +++ b/src/navigation/navigationView/types.ts @@ -66,6 +66,14 @@ export interface NavigationViewProps extends MapViewProps { */ readonly onRecenterButtonClick?: () => void; + /** + * Callback invoked when the camera enters or exits follow-my-location mode. + * + * @param isFollowing - True when the camera is following the user's location, + * false when the user has panned or zoomed away. + */ + readonly onCameraFollowLocationChanged?: (isFollowing: boolean) => void; + /** * A callback function invoked before a Navigation SDK UI prompt * element is about to appear and as soon as the element is removed.