diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 1cb8a63..189be2d 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -42,14 +42,14 @@ This project follows follow [GitHub's directions](https://help.github.com/articles/generating-ssh-keys/) to generate an SSH key. - `git clone git@github.com:/googlemaps/react-native-navigation-sdk.git` -- `git remote add upstream git@github.com:googlemaps/react-native-sdk.git` (So that you +- `git remote add upstream git@github.com:googlemaps/react-native-navigation-sdk.git` (So that you fetch from the master repository, not your clone, when running `git fetch` et al.) #### Create branch 1. `git fetch upstream` -2. `git checkout upstream/master -b ` +2. `git checkout upstream/main -b ` 3. Start coding! #### Commit changes diff --git a/android/src/main/java/com/google/android/react/navsdk/Constants.java b/android/src/main/java/com/google/android/react/navsdk/Constants.java index 5dbe238..c6f87c8 100644 --- a/android/src/main/java/com/google/android/react/navsdk/Constants.java +++ b/android/src/main/java/com/google/android/react/navsdk/Constants.java @@ -16,4 +16,15 @@ public class Constants { public static final String LAT_FIELD_KEY = "lat"; public static final String LNG_FIELD_KEY = "lng"; + + public static final String URI_KEY = "uri"; + + public static final String X_KEY = "x"; + public static final String Y_KEY = "y"; + + public static final String CAMERA_POSITION_KEY = "cameraPosition"; + public static final String TARGET_KEY = "target"; + public static final String BEARING_KEY = "bearing"; + public static final String TILT_KEY = "tilt"; + public static final String ZOOM_KEY = "zoom"; } diff --git a/android/src/main/java/com/google/android/react/navsdk/INavigationViewCallback.java b/android/src/main/java/com/google/android/react/navsdk/INavigationViewCallback.java index 939bb6d..b449b33 100644 --- a/android/src/main/java/com/google/android/react/navsdk/INavigationViewCallback.java +++ b/android/src/main/java/com/google/android/react/navsdk/INavigationViewCallback.java @@ -13,6 +13,7 @@ */ package com.google.android.react.navsdk; +import com.google.android.gms.maps.model.CameraPosition; import com.google.android.gms.maps.model.Circle; import com.google.android.gms.maps.model.GroundOverlay; import com.google.android.gms.maps.model.LatLng; @@ -36,4 +37,8 @@ public interface INavigationViewCallback { void onMarkerInfoWindowTapped(Marker marker); void onMapClick(LatLng latLng); + + void onMapDrag(CameraPosition cameraPosition); + + void onMapDragEnd(CameraPosition cameraPosition); } diff --git a/android/src/main/java/com/google/android/react/navsdk/MapViewController.java b/android/src/main/java/com/google/android/react/navsdk/MapViewController.java index bcd1bb4..a0a1b24 100644 --- a/android/src/main/java/com/google/android/react/navsdk/MapViewController.java +++ b/android/src/main/java/com/google/android/react/navsdk/MapViewController.java @@ -83,6 +83,10 @@ public void setupMapListeners(INavigationViewCallback navigationViewCallback) { mGoogleMap.setOnInfoWindowClickListener( marker -> mNavigationViewCallback.onMarkerInfoWindowTapped(marker)); mGoogleMap.setOnMapClickListener(latLng -> mNavigationViewCallback.onMapClick(latLng)); + mGoogleMap.setOnCameraMoveListener( + () -> mNavigationViewCallback.onMapDrag(mGoogleMap.getCameraPosition())); + mGoogleMap.setOnCameraIdleListener( + () -> mNavigationViewCallback.onMapDragEnd(mGoogleMap.getCameraPosition())); } public GoogleMap getGoogleMap() { diff --git a/android/src/main/java/com/google/android/react/navsdk/MapViewFragment.java b/android/src/main/java/com/google/android/react/navsdk/MapViewFragment.java index 122e3a1..aa687c6 100644 --- a/android/src/main/java/com/google/android/react/navsdk/MapViewFragment.java +++ b/android/src/main/java/com/google/android/react/navsdk/MapViewFragment.java @@ -27,6 +27,7 @@ import com.google.android.gms.maps.GoogleMap; import com.google.android.gms.maps.GoogleMapOptions; import com.google.android.gms.maps.SupportMapFragment; +import com.google.android.gms.maps.model.CameraPosition; import com.google.android.gms.maps.model.Circle; import com.google.android.gms.maps.model.GroundOverlay; import com.google.android.gms.maps.model.LatLng; @@ -128,6 +129,24 @@ public void onMapClick(LatLng latLng) { emitEvent("onMapClick", ObjectTranslationUtil.getMapFromLatLng(latLng)); } + @Override + public void onMapDrag(CameraPosition cameraPosition) { + WritableMap map = Arguments.createMap(); + map.putMap( + Constants.CAMERA_POSITION_KEY, + ObjectTranslationUtil.getMapFromCameraPosition(cameraPosition)); + emitEvent("onMapDrag", map); + } + + @Override + public void onMapDragEnd(CameraPosition cameraPosition) { + WritableMap map = Arguments.createMap(); + map.putMap( + Constants.CAMERA_POSITION_KEY, + ObjectTranslationUtil.getMapFromCameraPosition(cameraPosition)); + emitEvent("onMapDragEnd", map); + } + public MapViewController getMapController() { return mMapViewController; } 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 c65cdff..2ce2116 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 @@ -26,6 +26,7 @@ import com.facebook.react.uimanager.events.EventDispatcher; import com.google.android.gms.maps.GoogleMap; import com.google.android.gms.maps.GoogleMapOptions; +import com.google.android.gms.maps.model.CameraPosition; import com.google.android.gms.maps.model.Circle; import com.google.android.gms.maps.model.GroundOverlay; import com.google.android.gms.maps.model.LatLng; @@ -188,6 +189,24 @@ public void onMapClick(LatLng latLng) { emitEvent("onMapClick", ObjectTranslationUtil.getMapFromLatLng(latLng)); } + @Override + public void onMapDrag(CameraPosition cameraPosition) { + WritableMap map = Arguments.createMap(); + map.putMap( + Constants.CAMERA_POSITION_KEY, + ObjectTranslationUtil.getMapFromCameraPosition(cameraPosition)); + emitEvent("onMapDrag", ObjectTranslationUtil.getMapFromCameraPosition(cameraPosition)); + } + + @Override + public void onMapDragEnd(CameraPosition cameraPosition) { + WritableMap map = Arguments.createMap(); + map.putMap( + Constants.CAMERA_POSITION_KEY, + ObjectTranslationUtil.getMapFromCameraPosition(cameraPosition)); + emitEvent("onMapDragEnd", ObjectTranslationUtil.getMapFromCameraPosition(cameraPosition)); + } + @Override public void onDestroy() { super.onDestroy(); diff --git a/android/src/main/java/com/google/android/react/navsdk/NavViewManager.java b/android/src/main/java/com/google/android/react/navsdk/NavViewManager.java index 77c10a2..e5e9289 100644 --- a/android/src/main/java/com/google/android/react/navsdk/NavViewManager.java +++ b/android/src/main/java/com/google/android/react/navsdk/NavViewManager.java @@ -624,6 +624,8 @@ public Map getExportedCustomDirectEventTypeConstants() { MapBuilder.of("registrationName", "onPromptVisibilityChanged")) .put("onMapReady", MapBuilder.of("registrationName", "onMapReady")) .put("onMapClick", MapBuilder.of("registrationName", "onMapClick")) + .put("onMapDrag", MapBuilder.of("registrationName", "onMapDrag")) + .put("onMapDragEnd", MapBuilder.of("registrationName", "onMapDragEnd")) .put("onMarkerClick", MapBuilder.of("registrationName", "onMarkerClick")) .put("onPolylineClick", MapBuilder.of("registrationName", "onPolylineClick")) .put("onPolygonClick", MapBuilder.of("registrationName", "onPolygonClick")) diff --git a/android/src/main/java/com/google/android/react/navsdk/NavViewModule.java b/android/src/main/java/com/google/android/react/navsdk/NavViewModule.java index 1fe2e2c..48dfa35 100644 --- a/android/src/main/java/com/google/android/react/navsdk/NavViewModule.java +++ b/android/src/main/java/com/google/android/react/navsdk/NavViewModule.java @@ -13,6 +13,7 @@ */ package com.google.android.react.navsdk; +import android.graphics.Point; import android.location.Location; import com.facebook.react.bridge.Arguments; import com.facebook.react.bridge.Promise; @@ -22,14 +23,18 @@ import com.facebook.react.bridge.ReadableMap; import com.facebook.react.bridge.UiThreadUtil; import com.facebook.react.bridge.WritableMap; +import com.google.android.gms.maps.CameraUpdate; +import com.google.android.gms.maps.CameraUpdateFactory; import com.google.android.gms.maps.UiSettings; import com.google.android.gms.maps.model.CameraPosition; import com.google.android.gms.maps.model.Circle; import com.google.android.gms.maps.model.GroundOverlay; import com.google.android.gms.maps.model.LatLng; +import com.google.android.gms.maps.model.LatLngBounds; import com.google.android.gms.maps.model.Marker; import com.google.android.gms.maps.model.Polygon; import com.google.android.gms.maps.model.Polyline; +import com.google.android.gms.maps.model.VisibleRegion; import java.util.HashMap; import java.util.Map; @@ -75,12 +80,7 @@ public void getCameraPosition(Integer viewId, final Promise promise) { return; } - LatLng target = cp.target; - WritableMap map = Arguments.createMap(); - map.putDouble("bearing", cp.bearing); - map.putDouble("tilt", cp.tilt); - map.putDouble("zoom", cp.zoom); - map.putMap("target", ObjectTranslationUtil.getMapFromLatLng(target)); + WritableMap map = ObjectTranslationUtil.getMapFromCameraPosition(cp); promise.resolve(map); }); @@ -110,6 +110,116 @@ public void getMyLocation(Integer viewId, final Promise promise) { }); } + @ReactMethod + public void coordinateForPoint(Integer viewId, ReadableMap pointMap, final Promise promise) { + UiThreadUtil.runOnUiThread( + () -> { + if (mNavViewManager.getGoogleMap(viewId) == null) { + promise.reject(JsErrors.NO_MAP_ERROR_CODE, JsErrors.NO_MAP_ERROR_MESSAGE); + return; + } + + try { + float density = getReactApplicationContext().getResources().getDisplayMetrics().density; + int x = (int) density * CollectionUtil.getInt("x", pointMap.toHashMap(), 0); + int y = (int) density * CollectionUtil.getInt("y", pointMap.toHashMap(), 0); + Point point = new Point(x, y); + LatLng latLng = + mNavViewManager.getGoogleMap(viewId).getProjection().fromScreenLocation(point); + + promise.resolve(ObjectTranslationUtil.getMapFromLatLng(latLng)); + } catch (Exception e) { + promise.resolve(null); + return; + } + }); + } + + @ReactMethod + public void pointForCoordinate(Integer viewId, ReadableMap latLngMap, final Promise promise) { + UiThreadUtil.runOnUiThread( + () -> { + if (mNavViewManager.getGoogleMap(viewId) == null) { + promise.reject(JsErrors.NO_MAP_ERROR_CODE, JsErrors.NO_MAP_ERROR_MESSAGE); + return; + } + + LatLng latLng = ObjectTranslationUtil.getLatLngFromMap(latLngMap.toHashMap()); + Point point = + mNavViewManager.getGoogleMap(viewId).getProjection().toScreenLocation(latLng); + float density = getReactApplicationContext().getResources().getDisplayMetrics().density; + point.x = (int) (point.x / density); + point.y = (int) (point.y / density); + + promise.resolve(ObjectTranslationUtil.getMapFromPoint(point)); + }); + } + + @ReactMethod + public void fitBounds(Integer viewId, ReadableMap boundsOptions, final Promise promise) { + UiThreadUtil.runOnUiThread( + () -> { + if (mNavViewManager.getGoogleMap(viewId) == null) { + promise.reject(JsErrors.NO_MAP_ERROR_CODE, JsErrors.NO_MAP_ERROR_MESSAGE); + return; + } + + LatLng northEast = + ObjectTranslationUtil.getLatLngFromMap( + boundsOptions.getMap("bounds").getMap("northEast").toHashMap()); + LatLng southWest = + ObjectTranslationUtil.getLatLngFromMap( + boundsOptions.getMap("bounds").getMap("southWest").toHashMap()); + + if (northEast == null || southWest == null) { + promise.resolve(null); + return; + } + + ReadableMap paddingMap = boundsOptions.getMap("padding"); + if (paddingMap != null) { + double density = + getReactApplicationContext().getResources().getDisplayMetrics().density; + int left = (int) (paddingMap.getInt("left") * density); + int top = (int) (paddingMap.getInt("top") * density); + int right = (int) (paddingMap.getInt("right") * density); + int bottom = (int) (paddingMap.getInt("bottom") * density); + mNavViewManager.getGoogleMap(viewId).setPadding(left, top, right, bottom); + } + + CameraUpdate cameraUpdate = + CameraUpdateFactory.newLatLngBounds(new LatLngBounds(southWest, northEast), 0); + mNavViewManager.getGoogleMap(viewId).animateCamera(cameraUpdate); + + promise.resolve(null); + }); + } + + @ReactMethod + public void getBounds(Integer viewId, final Promise promise) { + UiThreadUtil.runOnUiThread( + () -> { + if (mNavViewManager.getGoogleMap(viewId) == null) { + promise.reject(JsErrors.NO_MAP_ERROR_CODE, JsErrors.NO_MAP_ERROR_MESSAGE); + return; + } + + VisibleRegion visibleRegion = + mNavViewManager.getGoogleMap(viewId).getProjection().getVisibleRegion(); + LatLng northEast = visibleRegion.farRight; + LatLng southWest = visibleRegion.nearLeft; + + WritableMap northEastMap = ObjectTranslationUtil.getMapFromLatLng(northEast); + WritableMap southWestMap = ObjectTranslationUtil.getMapFromLatLng(southWest); + + WritableMap map = Arguments.createMap(); + map.putMap("northEast", northEastMap); + map.putMap("southWest", southWestMap); + + promise.resolve(map); + }); + } + @ReactMethod public void getUiSettings(Integer viewId, final Promise promise) { UiThreadUtil.runOnUiThread( diff --git a/android/src/main/java/com/google/android/react/navsdk/ObjectTranslationUtil.java b/android/src/main/java/com/google/android/react/navsdk/ObjectTranslationUtil.java index c44cdca..ead8c2f 100644 --- a/android/src/main/java/com/google/android/react/navsdk/ObjectTranslationUtil.java +++ b/android/src/main/java/com/google/android/react/navsdk/ObjectTranslationUtil.java @@ -13,11 +13,13 @@ */ package com.google.android.react.navsdk; +import android.graphics.Point; import android.location.Location; import android.os.Build; import com.facebook.react.bridge.Arguments; import com.facebook.react.bridge.WritableArray; import com.facebook.react.bridge.WritableMap; +import com.google.android.gms.maps.model.CameraPosition; import com.google.android.gms.maps.model.Circle; import com.google.android.gms.maps.model.GroundOverlay; import com.google.android.gms.maps.model.LatLng; @@ -79,6 +81,23 @@ public static WritableMap getMapFromLatLng(LatLng latLng) { return map; } + public static WritableMap getMapFromPoint(Point point) { + WritableMap map = Arguments.createMap(); + map.putDouble(Constants.X_KEY, point.x); + map.putDouble(Constants.Y_KEY, point.y); + return map; + } + + public static WritableMap getMapFromCameraPosition(CameraPosition cameraPosition) { + WritableMap map = Arguments.createMap(); + map.putMap(Constants.TARGET_KEY, getMapFromLatLng(cameraPosition.target)); + map.putDouble(Constants.BEARING_KEY, cameraPosition.bearing); + map.putDouble(Constants.TILT_KEY, cameraPosition.tilt); + map.putDouble(Constants.ZOOM_KEY, cameraPosition.zoom); + + return map; + } + public static WritableMap getMapFromWaypoint(Waypoint waypoint) { WritableMap map = Arguments.createMap(); diff --git a/example/ios/SampleApp.xcodeproj/project.pbxproj b/example/ios/SampleApp.xcodeproj/project.pbxproj index bee13b1..775b9f9 100644 --- a/example/ios/SampleApp.xcodeproj/project.pbxproj +++ b/example/ios/SampleApp.xcodeproj/project.pbxproj @@ -643,10 +643,6 @@ ENABLE_BITCODE = NO; INFOPLIST_FILE = "SampleApp/Info-CarPlay.plist"; IPHONEOS_DEPLOYMENT_TARGET = 16.6; - GCC_PREPROCESSOR_DEFINITIONS = ( - "$(inherited)", - "CARPLAY=1", - ); LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -674,12 +670,8 @@ CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = SampleApp/SampleApp.entitlements; CURRENT_PROJECT_VERSION = 1; - INFOPLIST_FILE = "SampleApp/Info-CarPlay.plist"; + INFOPLIST_FILE = SampleApp/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 16.6; - GCC_PREPROCESSOR_DEFINITIONS = ( - "$(inherited)", - "CARPLAY=1", - ); LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", diff --git a/example/src/controls/mapsControls.tsx b/example/src/controls/mapsControls.tsx index 54d736d..b620bbb 100644 --- a/example/src/controls/mapsControls.tsx +++ b/example/src/controls/mapsControls.tsx @@ -228,6 +228,36 @@ const MapsControls: React.FC = ({ setCustomPaddingEnabled(!customPaddingEnabled); }; + const coordinateForPoint = async () => { + const cameraPosition = await mapViewController.getCameraPosition(); + const point = await mapViewController.pointForCoordinate( + cameraPosition.target + ); + const coordinate = await mapViewController.coordinateForPoint(point); + console.log({ point, coordinate }); + }; + + const pointForCoordinate = async () => { + const { target: coordinate } = await mapViewController.getCameraPosition(); + const point = await mapViewController.pointForCoordinate(coordinate); + console.log({ point, coordinate }); + }; + + const fitBounds = async () => { + const bounds = await mapViewController.getBounds(); + bounds.northEast.lat -= 1; + bounds.northEast.lng -= 1; + bounds.southWest.lat += 1; + bounds.southWest.lng += 1; + + await mapViewController.fitBounds({ bounds }); + }; + + const getBounds = async () => { + const bounds = await mapViewController.getBounds(); + console.log(bounds); + }; + const setMapColorScheme = (index: number) => { const scheme = index === 1 @@ -288,6 +318,23 @@ const MapsControls: React.FC = ({ title="Get camera position" onPress={getCameraPositionClicked} /> + + + + + Location marker { onMapClick: (latLng: LatLng) => { console.log('Map 1, onMapClick:', latLng); }, + onMapDrag: (result: DragResult) => { + console.log('Map 1, onMapDrag:', result); + }, + onMapDragEnd: (result: DragResult) => { + console.log('Map 1, onMapDragEnd:', result); + }, }), [mapViewController, onMapReady] ); diff --git a/example/src/screens/MultipleMapsScreen.tsx b/example/src/screens/MultipleMapsScreen.tsx index 9e9db5a..b504933 100644 --- a/example/src/screens/MultipleMapsScreen.tsx +++ b/example/src/screens/MultipleMapsScreen.tsx @@ -49,6 +49,7 @@ import { type Polyline, useNavigation, MapView, + type DragResult, } from '@googlemaps/react-native-navigation-sdk'; import MapsControls from '../controls/mapsControls'; import NavigationControls from '../controls/navigationControls'; @@ -263,6 +264,12 @@ const MultipleMapsScreen = () => { onMapClick: (latLng: LatLng) => { console.log('Map 1, onMapClick:', latLng); }, + onMapDrag: (result: DragResult) => { + console.log('Map 1, onMapDrag:', result); + }, + onMapDragEnd: (result: DragResult) => { + console.log('Map 1, onMapDragEnd:', result); + }, }), [mapViewController1, onMap1Ready] ); @@ -291,6 +298,12 @@ const MultipleMapsScreen = () => { onMapClick: (latLng: LatLng) => { console.log('Map 2, onMapClick: ', latLng); }, + onMapDrag: (result: DragResult) => { + console.log('Map 2, onMapDrag:', result); + }, + onMapDragEnd: (result: DragResult) => { + console.log('Map 2, onMapDragEnd:', result); + }, }), [mapViewController2] ); diff --git a/example/src/screens/NavigationScreen.tsx b/example/src/screens/NavigationScreen.tsx index 9974dbb..f74f210 100644 --- a/example/src/screens/NavigationScreen.tsx +++ b/example/src/screens/NavigationScreen.tsx @@ -43,6 +43,7 @@ import { useNavigation, useNavigationAuto, type CustomNavigationAutoEvent, + type DragResult, } from '@googlemaps/react-native-navigation-sdk'; import MapsControls from '../controls/mapsControls'; import NavigationControls from '../controls/navigationControls'; @@ -296,6 +297,12 @@ const NavigationScreen = () => { onMapClick: (latLng: LatLng) => { console.log('onMapClick:', latLng); }, + onMapDrag: (result: DragResult) => { + console.log('onMapDrag:', result); + }, + onMapDragEnd: (result: DragResult) => { + console.log('onMapDragEnd:', result); + }, }; }, [mapViewController, onMapReady]); diff --git a/ios/react-native-navigation-sdk/INavigationViewCallback.h b/ios/react-native-navigation-sdk/INavigationViewCallback.h index 70488fb..7ccf23c 100644 --- a/ios/react-native-navigation-sdk/INavigationViewCallback.h +++ b/ios/react-native-navigation-sdk/INavigationViewCallback.h @@ -27,6 +27,8 @@ NS_ASSUME_NONNULL_BEGIN - (void)handleMapReady; - (void)handleMapClick:(NSDictionary *)latLngMap; +- (void)handleMapDrag:(NSDictionary *)cameraPosition; +- (void)handleMapDragEnd:(NSDictionary *)cameraPosition; - (void)handleRecenterButtonClick; - (void)handleMarkerInfoWindowTapped:(GMSMarker *)marker; - (void)handleMarkerClick:(GMSMarker *)marker; diff --git a/ios/react-native-navigation-sdk/NavAutoModule.m b/ios/react-native-navigation-sdk/NavAutoModule.m index 899558a..7e306b1 100644 --- a/ios/react-native-navigation-sdk/NavAutoModule.m +++ b/ios/react-native-navigation-sdk/NavAutoModule.m @@ -150,6 +150,68 @@ + (void)unregisterNavAutoModuleReadyCallback { }); } +RCT_EXPORT_METHOD(coordinateForPoint + : (NSDictionary *)point resolver + : (RCTPromiseResolveBlock)resolve rejecter + : (RCTPromiseRejectBlock)reject) { + dispatch_async(dispatch_get_main_queue(), ^{ + if (self->_viewController) { + [self->_viewController coordinateForPoint:point + result:^(NSDictionary *result) { + resolve(result); + }]; + } else { + reject(@"no_view_controller", @"No viewController found", nil); + } + }); +} + +RCT_EXPORT_METHOD(pointForCoordinate + : (NSDictionary *)coordinate resolver + : (RCTPromiseResolveBlock)resolve rejecter + : (RCTPromiseRejectBlock)reject) { + dispatch_async(dispatch_get_main_queue(), ^{ + if (self->_viewController) { + [self->_viewController pointForCoordinate:coordinate + result:^(NSDictionary *result) { + resolve(result); + }]; + } else { + reject(@"no_view_controller", @"No viewController found", nil); + } + }); +} + +RCT_EXPORT_METHOD(fitBounds + : (NSDictionary *)boundsOptions resolver + : (RCTPromiseResolveBlock)resolve rejecter + : (RCTPromiseRejectBlock)reject) { + dispatch_async(dispatch_get_main_queue(), ^{ + if (self->_viewController) { + [self->_viewController fitBounds:boundsOptions + result:^(NSDictionary *result) { + resolve(result); + }]; + } else { + reject(@"no_view_controller", @"No viewController found", nil); + } + }); +} + +RCT_EXPORT_METHOD(getBounds + : (RCTPromiseResolveBlock)resolve rejecter + : (RCTPromiseRejectBlock)reject) { + dispatch_async(dispatch_get_main_queue(), ^{ + if (self->_viewController) { + [self->_viewController getBounds:^(NSDictionary *result) { + resolve(result); + }]; + } else { + reject(@"no_view_controller", @"No viewController found", nil); + } + }); +} + RCT_EXPORT_METHOD(addPolyline : (NSDictionary *)polylineOptions resolver : (RCTPromiseResolveBlock)resolve rejecter diff --git a/ios/react-native-navigation-sdk/NavView.h b/ios/react-native-navigation-sdk/NavView.h index d3c646a..b8a751c 100644 --- a/ios/react-native-navigation-sdk/NavView.h +++ b/ios/react-native-navigation-sdk/NavView.h @@ -17,6 +17,7 @@ #import #import #import +#import #import "CustomTypes.h" #import "INavigationViewCallback.h" @@ -27,6 +28,8 @@ @property(nonatomic, copy) RCTDirectEventBlock onRecenterButtonClick; @property(nonatomic, copy) RCTDirectEventBlock onMapReady; @property(nonatomic, copy) RCTDirectEventBlock onMapClick; +@property(nonatomic, copy) RCTDirectEventBlock onMapDrag; +@property(nonatomic, copy) RCTDirectEventBlock onMapDragEnd; @property(nonatomic, copy) RCTDirectEventBlock onMarkerInfoWindowTapped; @property(nonatomic, copy) RCTDirectEventBlock onMarkerClick; @property(nonatomic, copy) RCTDirectEventBlock onPolylineClick; diff --git a/ios/react-native-navigation-sdk/NavView.m b/ios/react-native-navigation-sdk/NavView.m index f48a844..d6060ee 100644 --- a/ios/react-native-navigation-sdk/NavView.m +++ b/ios/react-native-navigation-sdk/NavView.m @@ -130,6 +130,18 @@ - (void)handleMapClick:(NSDictionary *)latLngMap { } } +- (void)handleMapDrag:(GMSCameraPosition *)cameraPosition { + if (self.onMapDrag) { + self.onMapDrag(@{@"cameraPosition" : cameraPosition}); + } +} + +- (void)handleMapDragEnd:(GMSCameraPosition *)cameraPosition { + if (self.onMapDragEnd) { + self.onMapDragEnd(@{@"cameraPosition" : cameraPosition}); + } +} + - (void)handleMarkerInfoWindowTapped:(GMSMarker *)marker { if (self.onMarkerInfoWindowTapped) { self.onMarkerInfoWindowTapped([ObjectTranslationUtil transformMarkerToDictionary:marker]); diff --git a/ios/react-native-navigation-sdk/NavViewController.h b/ios/react-native-navigation-sdk/NavViewController.h index a089799..f9108c9 100644 --- a/ios/react-native-navigation-sdk/NavViewController.h +++ b/ios/react-native-navigation-sdk/NavViewController.h @@ -75,6 +75,10 @@ typedef void (^OnArrayResult)(NSArray *_Nullable result); - (void)addMarker:(NSDictionary *)markerOptions result:(OnDictionaryResult)completionBlock; - (void)addPolygon:(NSDictionary *)polygonOptions result:(OnDictionaryResult)completionBlock; - (void)addPolyline:(NSDictionary *)options result:(OnDictionaryResult)completionBlock; +- (void)coordinateForPoint:(NSDictionary *)point result:(OnDictionaryResult)completionBlock; +- (void)pointForCoordinate:(NSDictionary *)coordinate result:(OnDictionaryResult)completionBlock; +- (void)fitBounds:(NSDictionary *)boundsOptions result:(OnDictionaryResult)completionBlock; +- (void)getBounds:(OnDictionaryResult)completionBlock; - (GMSMapView *)mapView; - (void)showRouteOverview; - (void)removeMarker:(NSString *)markerId; diff --git a/ios/react-native-navigation-sdk/NavViewController.m b/ios/react-native-navigation-sdk/NavViewController.m index 9e0f1a1..a1f9c2f 100644 --- a/ios/react-native-navigation-sdk/NavViewController.m +++ b/ios/react-native-navigation-sdk/NavViewController.m @@ -16,6 +16,7 @@ #import "NavViewController.h" #import +#import #import "CustomTypes.h" #import "NavModule.h" #import "ObjectTranslationUtil.h" @@ -209,6 +210,16 @@ - (void)mapView:(GMSMapView *)mapView didTapAtCoordinate:(CLLocationCoordinate2D handleMapClick:[ObjectTranslationUtil transformCoordinateToDictionary:coordinate]]; } +- (void)mapView:(GMSMapView *)mapView didChangeCameraPosition:(GMSCameraPosition *)cameraPosition { + [_viewCallbacks + handleMapDrag:[ObjectTranslationUtil transformCameraPositionToDictionary:cameraPosition]]; +} + +- (void)mapView:(GMSMapView *)mapView idleAtCameraPosition:(GMSCameraPosition *)cameraPosition { + [_viewCallbacks + handleMapDragEnd:[ObjectTranslationUtil transformCameraPositionToDictionary:cameraPosition]]; +} + - (BOOL)mapView:(GMSMapView *)mapView didTapMarker:(GMSMarker *)marker { [_viewCallbacks handleMarkerClick:marker]; return FALSE; @@ -360,17 +371,7 @@ - (void)setNavigationUIEnabled:(BOOL)isEnabled { } - (void)getCameraPosition:(OnDictionaryResult)completionBlock { - GMSCameraPosition *cam = _mapView.camera; - CLLocationCoordinate2D cameraPosition = _mapView.camera.target; - - NSMutableDictionary *map = [[NSMutableDictionary alloc] init]; - map[@"bearing"] = @(cam.bearing); - map[@"tilt"] = @(cam.viewingAngle); - map[@"zoom"] = @(cam.zoom); - - map[@"target"] = @{@"lat" : @(cameraPosition.latitude), @"lng" : @(cameraPosition.longitude)}; - - completionBlock(map); + completionBlock([ObjectTranslationUtil transformCameraPositionToDictionary:_mapView.camera]); } - (void)getMyLocation:(OnDictionaryResult)completionBlock { @@ -673,6 +674,72 @@ - (void)addCircle:(NSDictionary *)circleOptions result:(OnDictionaryResult)compl completionBlock([ObjectTranslationUtil transformCircleToDictionary:circle]); } +- (void)coordinateForPoint:(NSDictionary *)point result:(OnDictionaryResult)completionBlock { + CGPoint cgPoint = + CGPointMake([[point objectForKey:@"x"] floatValue], [[point objectForKey:@"y"] floatValue]); + CLLocationCoordinate2D coordinate = [_mapView.projection coordinateForPoint:cgPoint]; + + NSMutableDictionary *result = [[NSMutableDictionary alloc] init]; + result[@"lat"] = @(coordinate.latitude); + result[@"lng"] = @(coordinate.longitude); + + completionBlock(result); +} + +- (void)pointForCoordinate:(NSDictionary *)coordinate result:(OnDictionaryResult)completionBlock { + CLLocationCoordinate2D latLng = [ObjectTranslationUtil getLocationCoordinateFrom:coordinate]; + CGPoint point = [_mapView.projection pointForCoordinate:latLng]; + + NSMutableDictionary *result = [[NSMutableDictionary alloc] init]; + result[@"x"] = @(point.x); + result[@"y"] = @(point.y); + + completionBlock(result); +} + +- (void)fitBounds:(NSDictionary *)boundsOptions result:(OnDictionaryResult)completionBlock { + NSDictionary *bounds = [boundsOptions objectForKey:@"bounds"]; + CLLocationCoordinate2D northEast = + [ObjectTranslationUtil getLocationCoordinateFrom:[bounds objectForKey:@"northEast"]]; + CLLocationCoordinate2D southWest = + [ObjectTranslationUtil getLocationCoordinateFrom:[bounds objectForKey:@"southWest"]]; + + NSDictionary *padding = [boundsOptions objectForKey:@"padding"]; + float top = [[padding objectForKey:@"top"] floatValue]; + float left = [[padding objectForKey:@"left"] floatValue]; + float bottom = [[padding objectForKey:@"bottom"] floatValue]; + float right = [[padding objectForKey:@"right"] floatValue]; + UIEdgeInsets edgeInsets = UIEdgeInsetsMake(top, left, bottom, right); + + GMSCoordinateBounds *coordBounds = [[GMSCoordinateBounds alloc] initWithCoordinate:northEast + coordinate:southWest]; + [_mapView animateWithCameraUpdate:[GMSCameraUpdate fitBounds:coordBounds + withEdgeInsets:edgeInsets]]; + + completionBlock(nil); +} + +- (void)getBounds:(OnDictionaryResult)completionBlock { + GMSVisibleRegion visibleRegion = [_mapView.projection visibleRegion]; + GMSCoordinateBounds *bounds = + [[GMSCoordinateBounds alloc] initWithCoordinate:visibleRegion.nearLeft + coordinate:visibleRegion.farRight]; + CLLocationCoordinate2D northEast = bounds.northEast; + CLLocationCoordinate2D southWest = bounds.southWest; + + NSMutableDictionary *result = [[NSMutableDictionary alloc] init]; + result[@"northEast"] = @{ + @"lat" : @(northEast.latitude), + @"lng" : @(northEast.longitude), + }; + result[@"southWest"] = @{ + @"lat" : @(southWest.latitude), + @"lng" : @(southWest.longitude), + }; + + completionBlock(result); +} + - (void)addMarker:(NSDictionary *)markerOptions result:(OnDictionaryResult)completionBlock { NSDictionary *position = [markerOptions objectForKey:@"position"]; CLLocationCoordinate2D coordinatePosition = @@ -693,8 +760,6 @@ - (void)addMarker:(NSDictionary *)markerOptions result:(OnDictionaryResult)compl marker.userData = @[ [[NSUUID UUID] UUIDString] ]; - marker.map = _mapView; - if ([[markerOptions objectForKey:@"imgPath"] isKindOfClass:[NSString class]]) { NSString *imgPath = [markerOptions objectForKey:@"imgPath"]; if (imgPath) { @@ -704,9 +769,7 @@ - (void)addMarker:(NSDictionary *)markerOptions result:(OnDictionaryResult)compl } BOOL visible = [[markerOptions objectForKey:@"visible"] boolValue]; - if (!visible) { - marker.map = nil; // Setting map to nil hides the marker - } + marker.map = visible ? _mapView : nil; [_markerList addObject:marker]; diff --git a/ios/react-native-navigation-sdk/NavViewModule.m b/ios/react-native-navigation-sdk/NavViewModule.m index 01304ae..b37878c 100644 --- a/ios/react-native-navigation-sdk/NavViewModule.m +++ b/ios/react-native-navigation-sdk/NavViewModule.m @@ -202,6 +202,76 @@ - (NavViewController *)getViewControllerForTag:(NSNumber *)reactTag { }); } +RCT_EXPORT_METHOD(coordinateForPoint + : (nonnull NSNumber *)reactTag point + : (NSDictionary *)point resolver + : (RCTPromiseResolveBlock)resolve rejecter + : (RCTPromiseRejectBlock)reject) { + dispatch_async(dispatch_get_main_queue(), ^{ + NavViewController *viewController = [self getViewControllerForTag:reactTag]; + if (viewController) { + [viewController coordinateForPoint:point + result:^(NSDictionary *result) { + resolve(result); + }]; + } else { + reject(@"no_view_controller", @"No viewController found", nil); + } + }); +} + +RCT_EXPORT_METHOD(pointForCoordinate + : (nonnull NSNumber *)reactTag coordinate + : (NSDictionary *)coordinate resolver + : (RCTPromiseResolveBlock)resolve rejecter + : (RCTPromiseRejectBlock)reject) { + dispatch_async(dispatch_get_main_queue(), ^{ + NavViewController *viewController = [self getViewControllerForTag:reactTag]; + if (viewController) { + [viewController pointForCoordinate:coordinate + result:^(NSDictionary *result) { + resolve(result); + }]; + } else { + reject(@"no_view_controller", @"No viewController found", nil); + } + }); +} + +RCT_EXPORT_METHOD(fitBounds + : (nonnull NSNumber *)reactTag boundsOptions + : (NSDictionary *)boundsOptions resolver + : (RCTPromiseResolveBlock)resolve rejecter + : (RCTPromiseRejectBlock)reject) { + dispatch_async(dispatch_get_main_queue(), ^{ + NavViewController *viewController = [self getViewControllerForTag:reactTag]; + if (viewController) { + [viewController fitBounds:boundsOptions + result:^(NSDictionary *result) { + resolve(result); + }]; + } else { + reject(@"no_view_controller", @"No viewController found", nil); + } + }); +} + +RCT_EXPORT_METHOD(getBounds + : (nonnull NSNumber *)reactTag resolver + : (RCTPromiseResolveBlock)resolve rejecter + : (RCTPromiseRejectBlock)reject) { + dispatch_async(dispatch_get_main_queue(), ^{ + NavViewController *viewController = [self getViewControllerForTag:reactTag]; + if (viewController) { + [viewController getBounds:^(NSDictionary *result) { + resolve(result); + }]; + } else { + reject(@"no_view_controller", @"No viewController found", nil); + } + }); +} + RCT_EXPORT_METHOD(addGroundOverlay : (nonnull NSNumber *)reactTag overlayOptions : (NSDictionary *)overlayOptions resolver diff --git a/ios/react-native-navigation-sdk/ObjectTranslationUtil.h b/ios/react-native-navigation-sdk/ObjectTranslationUtil.h index f993415..aad57ec 100644 --- a/ios/react-native-navigation-sdk/ObjectTranslationUtil.h +++ b/ios/react-native-navigation-sdk/ObjectTranslationUtil.h @@ -31,6 +31,7 @@ + (NSDictionary *)transformCircleToDictionary:(GMSCircle *)circle; + (NSDictionary *)transformGroundOverlayToDictionary:(GMSGroundOverlay *)groundOverlay; + (GMSPath *)transformToPath:(NSArray *)latLngs; ++ (NSDictionary *)transformCameraPositionToDictionary:(GMSCameraPosition *)position; + (CLLocationCoordinate2D)getLocationCoordinateFrom:(NSDictionary *)latLngMap; + (BOOL)isIdOnUserData:(nullable id)userData; @end diff --git a/ios/react-native-navigation-sdk/ObjectTranslationUtil.m b/ios/react-native-navigation-sdk/ObjectTranslationUtil.m index 940a279..4ec2465 100644 --- a/ios/react-native-navigation-sdk/ObjectTranslationUtil.m +++ b/ios/react-native-navigation-sdk/ObjectTranslationUtil.m @@ -220,6 +220,17 @@ + (GMSPath *)transformToPath:(NSArray *)latLngs { return path; } ++ (NSDictionary *)transformCameraPositionToDictionary:(GMSCameraPosition *)cam { + NSMutableDictionary *dictionary = [[NSMutableDictionary alloc] init]; + dictionary[@"bearing"] = @(cam.bearing); + dictionary[@"tilt"] = @(cam.viewingAngle); + dictionary[@"zoom"] = @(cam.zoom); + + dictionary[@"target"] = @{@"lat" : @(cam.target.latitude), @"lng" : @(cam.target.longitude)}; + + return dictionary; +} + + (CLLocationCoordinate2D)getLocationCoordinateFrom:(NSDictionary *)latLngMap { double latitude = [[latLngMap objectForKey:@"lat"] doubleValue]; double longitude = [[latLngMap objectForKey:@"lng"] doubleValue]; diff --git a/ios/react-native-navigation-sdk/RCTNavViewManager.m b/ios/react-native-navigation-sdk/RCTNavViewManager.m index faccb94..139487a 100644 --- a/ios/react-native-navigation-sdk/RCTNavViewManager.m +++ b/ios/react-native-navigation-sdk/RCTNavViewManager.m @@ -94,6 +94,8 @@ - (void)unregisterViewControllerForTag:(NSNumber *)reactTag { RCT_EXPORT_VIEW_PROPERTY(onRecenterButtonClick, RCTDirectEventBlock); RCT_EXPORT_VIEW_PROPERTY(onMapReady, RCTDirectEventBlock); RCT_EXPORT_VIEW_PROPERTY(onMapClick, RCTDirectEventBlock); +RCT_EXPORT_VIEW_PROPERTY(onMapDrag, RCTDirectEventBlock); +RCT_EXPORT_VIEW_PROPERTY(onMapDragEnd, RCTDirectEventBlock); RCT_EXPORT_VIEW_PROPERTY(onMarkerInfoWindowTapped, RCTDirectEventBlock); RCT_EXPORT_VIEW_PROPERTY(onMarkerClick, RCTDirectEventBlock); RCT_EXPORT_VIEW_PROPERTY(onPolylineClick, RCTDirectEventBlock); diff --git a/src/auto/useNavigationAuto.ts b/src/auto/useNavigationAuto.ts index 7f88c15..a61a6e8 100644 --- a/src/auto/useNavigationAuto.ts +++ b/src/auto/useNavigationAuto.ts @@ -16,7 +16,13 @@ import { NativeModules } from 'react-native'; import type { MapViewAutoController, NavigationAutoCallbacks } from './types'; -import { useModuleListeners, type Location } from '../shared'; +import { + useModuleListeners, + type Location, + type Point, + type LatLng, + type Bounds, +} from '../shared'; import type { MapType, CircleOptions, @@ -30,6 +36,7 @@ import type { CameraPosition, UISettings, Padding, + BoundsOptions, } from '../maps'; import { useMemo } from 'react'; @@ -77,6 +84,22 @@ export const useNavigationAuto = (): { return await NavAutoModule.addCircle(circleOptions); }, + coordinateForPoint: async (point: Point): Promise => { + return await NavAutoModule.coordinateForPoint(point); + }, + + pointForCoordinate: async (coordinate: LatLng): Promise => { + return await NavAutoModule.pointForCoordinate(coordinate); + }, + + fitBounds: async (boundsOptions: BoundsOptions) => { + return await NavAutoModule.fitBounds(boundsOptions); + }, + + getBounds: async (): Promise => { + return await NavAutoModule.getBounds(); + }, + addMarker: async (markerOptions: MarkerOptions): Promise => { return await NavAutoModule.addMarker(markerOptions); }, diff --git a/src/maps/mapView/mapView.tsx b/src/maps/mapView/mapView.tsx index 97a8cef..6fdca2b 100644 --- a/src/maps/mapView/mapView.tsx +++ b/src/maps/mapView/mapView.tsx @@ -27,6 +27,7 @@ import { type Marker, type Polygon, type Polyline, + type DragResult, } from '..'; export const MapView = (props: MapViewProps): React.JSX.Element => { @@ -64,6 +65,20 @@ export const MapView = (props: MapViewProps): React.JSX.Element => { [props.mapViewCallbacks] ); + const onMapDrag = useCallback( + ({ nativeEvent: result }: { nativeEvent: DragResult }) => { + props.mapViewCallbacks?.onMapDrag?.(result); + }, + [props.mapViewCallbacks] + ); + + const onMapDragEnd = useCallback( + ({ nativeEvent: result }: { nativeEvent: DragResult }) => { + props.mapViewCallbacks?.onMapDragEnd?.(result); + }, + [props.mapViewCallbacks] + ); + const onMapReady = useCallback(() => { props.mapViewCallbacks?.onMapReady?.(); }, [props.mapViewCallbacks]); @@ -121,6 +136,8 @@ export const MapView = (props: MapViewProps): React.JSX.Element => { mapColorScheme: props.mapColorScheme ?? MapColorScheme.FOLLOW_SYSTEM, }} onMapClick={onMapClick} + onMapDrag={onMapDrag} + onMapDragEnd={onMapDragEnd} onMapReady={onMapReady} onMarkerClick={onMarkerClick} onPolylineClick={onPolylineClick} @@ -128,7 +145,9 @@ export const MapView = (props: MapViewProps): React.JSX.Element => { onCircleClick={onCircleClick} onGroundOverlayClick={onGroundOverlayClick} onMarkerInfoWindowTapped={onMarkerInfoWindowTapped} - /> + > + {props.children} + ); }; diff --git a/src/maps/mapView/mapViewController.ts b/src/maps/mapView/mapViewController.ts index 85581a1..f321b6f 100644 --- a/src/maps/mapView/mapViewController.ts +++ b/src/maps/mapView/mapViewController.ts @@ -15,7 +15,7 @@ */ import { NativeModules } from 'react-native'; -import type { Location } from '../../shared/types'; +import type { Location, Point, LatLng, Bounds } from '../../shared/types'; import { commands, sendCommand } from '../../shared/viewManager'; import type { CameraPosition, @@ -26,6 +26,7 @@ import type { UISettings, } from '../types'; import type { + BoundsOptions, CircleOptions, MapType, MapViewController, @@ -55,6 +56,22 @@ export const getMapViewController = (viewId: number): MapViewController => { return await NavViewModule.addCircle(viewId, circleOptions); }, + coordinateForPoint: async (point: Point): Promise => { + return await NavViewModule.coordinateForPoint(viewId, point); + }, + + pointForCoordinate: async (coordinate: LatLng): Promise => { + return await NavViewModule.pointForCoordinate(viewId, coordinate); + }, + + fitBounds: async (boundsOptions: BoundsOptions) => { + return await NavViewModule.fitBounds(viewId, boundsOptions); + }, + + getBounds: async (): Promise => { + return await NavViewModule.getBounds(viewId); + }, + addMarker: async (markerOptions: MarkerOptions): Promise => { return await NavViewModule.addMarker(viewId, markerOptions); }, diff --git a/src/maps/mapView/types.ts b/src/maps/mapView/types.ts index f08f115..54a157f 100644 --- a/src/maps/mapView/types.ts +++ b/src/maps/mapView/types.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import type { LatLng, Location } from '../../shared/types'; +import type { LatLng, Location, Point, Bounds } from '../../shared/types'; import type { CameraPosition, Circle, @@ -61,12 +61,16 @@ export interface MarkerOptions { alpha?: number; /** The rotation of the marker in degrees clockwise about the marker's anchor point. The axis of rotation is perpendicular to the marker. A rotation of 0 corresponds to the default position of the marker. When the marker is flat on the map, the default position is North aligned and the rotation is such that the marker always remains flat on the map. When the marker is a billboard, the default position is pointing up and the rotation is such that the marker is always facing the camera. The default value is 0. */ rotation?: number; + /** Sets the z-index of the marker. */ + zIndex?: number; /** Indicates whether this marker is draggable. False by default. */ draggable?: boolean; /** Indicates whether this marker should be flat against the map true or a billboard facing the camera false. */ flat?: boolean; /** Indicates the visibility of the polygon. True by default. */ visible?: boolean; + /** The ground anchor specifies the point in the icon image that is anchored to the marker’s position on the Earth’s surface. */ + groundAnchor?: [number, number]; } /** @@ -137,6 +141,16 @@ export interface Padding { right?: number; } +/** + * Defines bounds options for a geographical bounds with padding. + */ +export interface BoundsOptions { + /** Geographical bounds. */ + bounds: Bounds; + /** Padding within the bounds. */ + padding?: Padding; +} + /** * Defines the type of the map view. */ @@ -147,6 +161,14 @@ export enum MapViewType { NAVIGATION = 1, } +/** + * Defines the results of a map drag event. + */ +export interface DragResult { + /** Camera position. */ + cameraPosition: CameraPosition; +} + /** * `MapViewProps` interface provides a set of method definitions * for managing map events and debug information. @@ -192,6 +214,18 @@ export interface MapViewCallbacks { * @param latLng position where the click occurred. */ onMapClick?(latLng: LatLng): void; + + /** + * Callback invoked repeated when map is being dragged. + * @param result The drag result at that instant. + */ + onMapDrag?(result: DragResult): void; + + /** + * Callback invoked when map drag ends. + * @param result The drag result at that instant. + */ + onMapDragEnd?(result: DragResult): void; } export interface MapViewController { @@ -231,6 +265,30 @@ export interface MapViewController { */ addCircle(circleOptions: CircleOptions): Promise; + /** + * Maps a point coordinate in the map’s view to an Earth coordinate. + * @param point - Object specifying the point. + */ + coordinateForPoint(point: Point): Promise; + + /** + * Maps an Earth coordinate to a point coordinate in the map’s view. + * @param coordinate - Object specifying the coordinate. + */ + pointForCoordinate(coordinate: LatLng): Promise; + + /** + * Transforms the camera such that the specified bounds are centered on screen. + * @param boundsOptions - Object specifying the bounds options. + */ + fitBounds(boundsOptions: BoundsOptions): Promise; + + /** + * Retrieves the rectangular bounds of the map view. + * @param bounds - Object specifying the bounds. + */ + getBounds(): Promise; + /** * Add a marker to the map. * @param markerOptions - Object specifying properties of the marker, including diff --git a/src/maps/types.ts b/src/maps/types.ts index 5db7d8b..a11f3ae 100644 --- a/src/maps/types.ts +++ b/src/maps/types.ts @@ -17,6 +17,7 @@ import type { StyleProp, ViewStyle } from 'react-native'; import type { LatLng } from '../shared/types'; import type { MapViewCallbacks, MapViewController } from './mapView/types'; +import type { PropsWithChildren } from 'react'; /** * An immutable class that aggregates all camera position parameters such as @@ -175,7 +176,7 @@ export enum MapColorScheme { /** * `MapViewProps` interface provides methods focused on managing map events and state changes. */ -export interface MapViewProps { +export interface MapViewProps extends PropsWithChildren { readonly mapViewCallbacks?: MapViewCallbacks; readonly style?: StyleProp | undefined; diff --git a/src/navigation/navigationView/navigationView.tsx b/src/navigation/navigationView/navigationView.tsx index 3b40c70..7f50679 100644 --- a/src/navigation/navigationView/navigationView.tsx +++ b/src/navigation/navigationView/navigationView.tsx @@ -28,6 +28,7 @@ import { type Marker, type Polygon, type Polyline, + type DragResult, } from '../../maps'; export const NavigationView = ( @@ -79,6 +80,20 @@ export const NavigationView = ( [props.mapViewCallbacks] ); + const onMapDrag = useCallback( + ({ nativeEvent: result }: { nativeEvent: DragResult }) => { + props.mapViewCallbacks?.onMapDrag?.(result); + }, + [props.mapViewCallbacks] + ); + + const onMapDragEnd = useCallback( + ({ nativeEvent: result }: { nativeEvent: DragResult }) => { + props.mapViewCallbacks?.onMapDragEnd?.(result); + }, + [props.mapViewCallbacks] + ); + const onMapReady = useCallback(() => { props.mapViewCallbacks?.onMapReady?.(); }, [props.mapViewCallbacks]); @@ -153,6 +168,8 @@ export const NavigationView = ( : iOSStylingOptions) || {}, }} onMapClick={onMapClick} + onMapDrag={onMapDrag} + onMapDragEnd={onMapDragEnd} onMapReady={onMapReady} onMarkerClick={onMarkerClick} onPolylineClick={onPolylineClick} diff --git a/src/shared/types.ts b/src/shared/types.ts index 1a6246f..895296d 100644 --- a/src/shared/types.ts +++ b/src/shared/types.ts @@ -77,3 +77,23 @@ export interface Location { */ time: number; } + +/** + * An immutable class representing a point on the screen. + */ +export interface Point { + /** Value representing the x coordinate. */ + x: number; + /** Value representing the y coordinate. */ + y: number; +} + +/** + * An immutable class representing a rectangular bounding box on the Earth’s surface. + */ +export interface Bounds { + /** The North-East corner of these bounds. */ + northEast: LatLng; + /** The South-West corner of these bounds. */ + southWest: LatLng; +} diff --git a/src/shared/viewManager.ts b/src/shared/viewManager.ts index e0d6dc4..de31b38 100644 --- a/src/shared/viewManager.ts +++ b/src/shared/viewManager.ts @@ -24,6 +24,7 @@ import { import type { LatLng } from '.'; import type { Circle, + DragResult, MapColorScheme, GroundOverlay, Marker, @@ -87,6 +88,8 @@ export interface NativeNavViewProps extends ViewProps { }; onMapReady?: DirectEventHandler; onMapClick?: DirectEventHandler; + onMapDrag?: DirectEventHandler; + onMapDragEnd?: DirectEventHandler; onMarkerClick?: DirectEventHandler; onPolylineClick?: DirectEventHandler; onPolygonClick?: DirectEventHandler;