diff --git a/ANDROIDAUTO.md b/ANDROIDAUTO.md
new file mode 100644
index 0000000..e7d3202
--- /dev/null
+++ b/ANDROIDAUTO.md
@@ -0,0 +1,100 @@
+# Navigation for Android Auto
+
+This guide explains how to enable and integrate Android Auto with the React Native Navigation SDK.
+
+## Requirements
+
+- Android device
+- Android Auto test device or Android Automotive OS emulator
+
+## Setup
+
+Refer to the [Android for Cars developer documentation](https://developer.android.com/training/cars) to understand how the Android Auto works and to complete the initial setup. Key steps include:
+
+- Installing Android for Cars App Library.
+- Configuring your app's manifest file to include Android Auto.
+- Declaring a minimum car-app level in your manifest.
+- Creating 'CarAppService' and session
+
+For all the steps above, you can refer to the Android example application for guidance.
+
+### Screen for Android Auto
+
+Once your project is configured accordingly, and you are ready to build the screen for Android Auto, you can leverage the `AndroidAutoBaseScreen` provided by the SDK. This base class simplifies the setup by handling initialization, teardown, and rendering the map on the Android Auto display.
+
+Please refer to the `SampleAndroidAutoScreen.java` file in the Android example app for guidance.
+
+To customize the Android Auto experience, override the `onGetTemplate` method in your custom AndroidAutoScreen class, providing your own `Template`:
+
+```java
+@NonNull
+@Override
+public Template onGetTemplate() {
+ /** ... */
+ @SuppressLint("MissingPermission")
+ NavigationTemplate.Builder navigationTemplateBuilder =
+ new NavigationTemplate.Builder()
+ .setActionStrip(
+ new ActionStrip.Builder()
+ .addAction(
+ new Action.Builder()
+ .setTitle("Re-center")
+ .setOnClickListener(
+ () -> {
+ if (mGoogleMap == null) return;
+ mGoogleMap.followMyLocation(GoogleMap.CameraPerspective.TILTED);
+ })
+ .build())
+ .addAction(
+ new Action.Builder()
+ .setTitle("Custom event")
+ .setOnClickListener(
+ () -> {
+ WritableMap map = Arguments.createMap();
+ map.putString("sampleKey", "sampleValue");
+ sendCustomEvent("sampleEvent", map);
+ })
+ .build())
+ .build())
+ .setMapActionStrip(new ActionStrip.Builder().addAction(Action.PAN).build());
+ /** ... */
+}
+```
+
+For advanced customization, you can bypass the base class and implement your own screen by inheriting `Screen`. You can use the provided `AndroidAutoBaseScreen` base class as a reference on how to do that.
+
+### React Native specific setup
+
+On the React Native side, you can use the `useNavigationAuto` hook to interface with the Android Auto instance. The `mapViewAutoController` allows you to call map functions on the Android Auto map, and you can manage listeners using the provided functions.
+
+```tsx
+const {
+ mapViewAutoController,
+ addListeners: addAutoListener,
+ removeListeners: removeAutoListeners,
+} = useNavigationAuto();
+
+const navigationAutoCallbacks: NavigationAutoCallbacks = useMemo(
+ () => ({
+ onCustomNavigationAutoEvent: (event: CustomNavigationAutoEvent) => {
+ console.log('onCustomNavigationAutoEvent:', event);
+ },
+ onAutoScreenAvailabilityChanged: (available: boolean) => {
+ console.log('onAutoScreenAvailabilityChanged:', available);
+ setMapViewAutoAvailable(available);
+ },
+ }),
+ []
+);
+
+const setMapType = (mapType: MapType) => {
+ console.log('setMapType', mapType);
+ mapViewAutoController.setMapType(mapType);
+};
+```
+
+For a more detailed example, refer to the `NavigationScreen.tsx` in the React Native example application.
+
+## Example Project
+
+For a fully functional Android Auto implementation, check out the [SampleApp](./example/android/) Android Studio project.
diff --git a/CARPLAY.md b/CARPLAY.md
new file mode 100644
index 0000000..f46cd7b
--- /dev/null
+++ b/CARPLAY.md
@@ -0,0 +1,86 @@
+# Navigation for Apple CarPlay
+
+This guide explains how to enable and integrate Apple CarPlay with the React Native Navigation SDK.
+
+## Requirements
+
+- iOS device or iOS simulator (iOS 14.0+)
+- CarPlay Simulator
+- CarPlay entitlement for your application (provided by Apple)
+
+## Setup
+
+Refer to the [Apple CarPlay Developer Guide](https://developer.apple.com/carplay/) to understand how CarPlay works and to complete the initial setup. Key steps include:
+
+- Adding the CarPlay entitlement to your Xcode project.
+- Creating a separate scene for the CarPlay map and enabling support for multiple scenes.
+
+### SceneDelegate for CarPlay
+
+Once your project is configured to support multiple scenes, and you are setting up a dedicated scene for CarPlay, you can leverage the `BaseCarSceneDelegate` provided by the SDK. This base class simplifies the setup by handling initialization, teardown, and rendering the map on the CarPlay display.
+
+Please refer to the `CarSceneDelegate.h` and `CarSceneDelegate.m` files in the iOS example app for guidance.
+
+To customize the CarPlay experience, override the `getTemplate` method in your custom `CarSceneDelegate` class, providing your own `CPMapTemplate`:
+
+```objc
+- (CPMapTemplate *)getTemplate {
+ CPMapTemplate *template = [[CPMapTemplate alloc] init];
+ [template showPanningInterfaceAnimated:YES];
+
+ CPBarButton *customButton = [[CPBarButton alloc]
+ initWithTitle:@"Custom Event"
+ handler:^(CPBarButton ***_Nonnull** button) {
+ NSMutableDictionary *dictionary = [
+ [NSMutableDictionary alloc] init
+ ];
+ dictionary[@"sampleDataKey"] = @"sampleDataContent";
+ [[NavAutoModule getOrCreateSharedInstance]
+ onCustomNavigationAutoEvent:@"sampleEvent"
+ data:dictionary
+ ];
+ }];
+
+ template.leadingNavigationBarButtons = @[ customButton ];
+ template.trailingNavigationBarButtons = @[];
+ return template;
+}
+```
+
+For advanced customization, you can bypass the base class and implement your own delegate inheriting `CPTemplateApplicationSceneDelegate`. You can use the provided `BaseCarSceneDelegate` base class as a reference on how to do that.
+
+### React Native Setup
+
+On the React Native side, you can use the `useNavigationAuto` hook to interface with the CarPlay instance. The `mapViewAutoController` allows you to call map functions on the CarPlay map view, and you can manage listeners using the provided functions.
+
+```tsx
+const {
+ mapViewAutoController,
+ addListeners: addAutoListener,
+ removeListeners: removeAutoListeners,
+} = useNavigationAuto();
+
+const navigationAutoCallbacks: NavigationAutoCallbacks = useMemo(
+ () => ({
+ onCustomNavigationAutoEvent: (event: CustomNavigationAutoEvent) => {
+ console.log('onCustomNavigationAutoEvent:', event);
+ },
+ onAutoScreenAvailabilityChanged: (available: boolean) => {
+ console.log('onAutoScreenAvailabilityChanged:', available);
+ setMapViewAutoAvailable(available);
+ },
+ }),
+ []
+);
+
+const setMapType = (mapType: MapType) => {
+ console.log('setMapType', mapType);
+ mapViewAutoController.setMapType(mapType);
+};
+```
+
+For a more detailed example, refer to the `NavigationScreen.tsx` in the React Native example application.
+
+## Example Project
+
+For a fully functional CarPlay implementation, check out the [SampleApp](./example/ios/) Xcode project, which includes the `SampleAppCarPlay` build target. The sample already contains test entitlement so you don't need to request one from Apple to run it.
diff --git a/README.md b/README.md
index 85b96bb..e6d5b55 100644
--- a/README.md
+++ b/README.md
@@ -159,6 +159,12 @@ By default, `NavigationView` uses all the available space provided to it. To adj
/>
```
+## Support for Android Auto and Apple CarPlay
+This plugin is compatible with both Android Auto and Apple CarPlay infotainment systems. For more details, please refer to the respective platform documentation:
+
+- [Android Auto documentation](./ANDROIDAUTO.md)
+- [CarPlay documentation](./CARPLAY.md)
+
## Known issues
### Compatibility with other libraries
diff --git a/android/build.gradle b/android/build.gradle
index 51cbdf8..62ba866 100644
--- a/android/build.gradle
+++ b/android/build.gradle
@@ -1,11 +1,11 @@
// Copyright 2023 Google LLC
-//
+//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
-//
+//
// http://www.apache.org/licenses/LICENSE-2.0
-//
+//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@@ -37,7 +37,7 @@ if (isNewArchitectureEnabled()) {
android {
namespace "com.google.android.react.navsdk"
-
+
compileSdkVersion 31
compileOptions {
@@ -71,6 +71,8 @@ repositories {
}
dependencies {
+ implementation "androidx.car.app:app:1.4.0"
+ implementation "androidx.car.app:app-projected:1.4.0"
implementation 'com.facebook.react:react-native:+'
implementation 'com.android.support:multidex:1.0.3'
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
diff --git a/android/src/main/java/com/google/android/react/navsdk/AndroidAutoBaseScreen.java b/android/src/main/java/com/google/android/react/navsdk/AndroidAutoBaseScreen.java
new file mode 100644
index 0000000..c7ac8aa
--- /dev/null
+++ b/android/src/main/java/com/google/android/react/navsdk/AndroidAutoBaseScreen.java
@@ -0,0 +1,206 @@
+/*
+ * Copyright 2024 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.android.react.navsdk;
+
+import android.app.Presentation;
+import android.graphics.Point;
+import android.hardware.display.DisplayManager;
+import android.hardware.display.VirtualDisplay;
+import androidx.annotation.NonNull;
+import androidx.car.app.AppManager;
+import androidx.car.app.CarContext;
+import androidx.car.app.Screen;
+import androidx.car.app.SurfaceCallback;
+import androidx.car.app.SurfaceContainer;
+import androidx.car.app.model.Action;
+import androidx.car.app.model.ActionStrip;
+import androidx.car.app.model.Template;
+import androidx.car.app.navigation.model.NavigationTemplate;
+import androidx.lifecycle.DefaultLifecycleObserver;
+import androidx.lifecycle.Lifecycle;
+import androidx.lifecycle.LifecycleObserver;
+import androidx.lifecycle.LifecycleOwner;
+import com.facebook.react.bridge.ReadableMap;
+import com.google.android.gms.maps.CameraUpdate;
+import com.google.android.gms.maps.CameraUpdateFactory;
+import com.google.android.gms.maps.GoogleMap;
+import com.google.android.libraries.navigation.NavigationViewForAuto;
+import com.google.android.libraries.navigation.StylingOptions;
+
+// This class streamlines the Android Auto setup process by managing initialization, teardown, and
+// map rendering on the Android Auto display. You can create your own Screen class by extending this
+// one and overriding its functions as needed.
+//
+// For more information on using Android Auto with the Google Navigation SDK, refer to the official
+// documentation:
+// https://developers.google.com/maps/documentation/navigation/android-sdk/android-auto
+public abstract class AndroidAutoBaseScreen extends Screen
+ implements SurfaceCallback, INavigationViewController {
+ private static final String VIRTUAL_DISPLAY_NAME = "AndroidAutoNavScreen";
+
+ private NavigationViewForAuto mNavigationView;
+ private VirtualDisplay mVirtualDisplay;
+ private Presentation mPresentation;
+ protected GoogleMap mGoogleMap;
+ protected boolean mNavigationInitialized = false;
+ private MapViewController mMapViewController;
+
+ private boolean mAndroidAutoModuleInitialized = false;
+ private boolean mNavModuleInitialized = false;
+ private final AndroidAutoBaseScreen screenInstance = this;
+
+ @Override
+ public void setStylingOptions(StylingOptions stylingOptions) {
+ // TODO(jokerttu): set styling to the navigationView
+ }
+
+ public void onNavigationReady(boolean ready) {
+ mNavigationInitialized = ready;
+ }
+
+ public AndroidAutoBaseScreen(@NonNull CarContext carContext) {
+
+ super(carContext);
+
+ NavAutoModule.setModuleReadyListener(
+ () -> {
+ mAndroidAutoModuleInitialized = true;
+ registerControllersForAndroidAutoModule();
+ });
+
+ NavModule.setModuleReadyListener(
+ () -> {
+ mNavModuleInitialized = true;
+ NavModule.getInstance().registerNavigationReadyListener(this::onNavigationReady);
+ });
+
+ carContext.getCarService(AppManager.class).setSurfaceCallback(this);
+
+ Lifecycle lifecycle = getLifecycle();
+ lifecycle.addObserver(mLifeCycleObserver);
+ }
+
+ private final LifecycleObserver mLifeCycleObserver =
+ new DefaultLifecycleObserver() {
+ @Override
+ public void onDestroy(@NonNull LifecycleOwner lifecycleOwner) {
+ if (mNavModuleInitialized) {
+ try {
+ NavModule.getInstance()
+ .unRegisterNavigationReadyListener(screenInstance::onNavigationReady);
+ } catch (Exception e) {
+ }
+ }
+ }
+ };
+
+ private void registerControllersForAndroidAutoModule() {
+ if (mAndroidAutoModuleInitialized && mMapViewController != null) {
+ NavAutoModule.getInstance().androidAutoNavigationScreenInitialized(mMapViewController, this);
+ }
+ }
+
+ private void unRegisterControllersForAndroidAutoModule() {
+ if (mAndroidAutoModuleInitialized) {
+ NavAutoModule.getInstance().androidAutoNavigationScreenDisposed();
+ }
+ }
+
+ private boolean isSurfaceReady(SurfaceContainer surfaceContainer) {
+ return surfaceContainer.getSurface() != null
+ && surfaceContainer.getDpi() != 0
+ && surfaceContainer.getHeight() != 0
+ && surfaceContainer.getWidth() != 0;
+ }
+
+ @Override
+ public void onSurfaceAvailable(@NonNull SurfaceContainer surfaceContainer) {
+ if (!isSurfaceReady(surfaceContainer)) {
+ return;
+ }
+ mVirtualDisplay =
+ getCarContext()
+ .getSystemService(DisplayManager.class)
+ .createVirtualDisplay(
+ VIRTUAL_DISPLAY_NAME,
+ surfaceContainer.getWidth(),
+ surfaceContainer.getHeight(),
+ surfaceContainer.getDpi(),
+ surfaceContainer.getSurface(),
+ DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY);
+ mPresentation = new Presentation(getCarContext(), mVirtualDisplay.getDisplay());
+
+ mNavigationView = new NavigationViewForAuto(getCarContext());
+ mNavigationView.onCreate(null);
+ mNavigationView.onStart();
+ mNavigationView.onResume();
+
+ mPresentation.setContentView(mNavigationView);
+ mPresentation.show();
+
+ mNavigationView.getMapAsync(
+ (GoogleMap googleMap) -> {
+ mGoogleMap = googleMap;
+ mMapViewController = new MapViewController();
+ mMapViewController.initialize(googleMap, () -> null);
+ registerControllersForAndroidAutoModule();
+ invalidate();
+ });
+ }
+
+ @Override
+ public void onSurfaceDestroyed(@NonNull SurfaceContainer surfaceContainer) {
+ unRegisterControllersForAndroidAutoModule();
+ mNavigationView.onPause();
+ mNavigationView.onStop();
+ mNavigationView.onDestroy();
+ mGoogleMap = null;
+
+ mPresentation.dismiss();
+ mVirtualDisplay.release();
+ }
+
+ @Override
+ public void onScroll(float distanceX, float distanceY) {
+ if (mGoogleMap == null) {
+ return;
+ }
+ mGoogleMap.moveCamera(CameraUpdateFactory.scrollBy(distanceX, distanceY));
+ }
+
+ @Override
+ public void onScale(float focusX, float focusY, float scaleFactor) {
+ if (mGoogleMap == null) {
+ return;
+ }
+ CameraUpdate update =
+ CameraUpdateFactory.zoomBy((scaleFactor - 1), new Point((int) focusX, (int) focusY));
+ mGoogleMap.animateCamera(update); // map is set in onSurfaceAvailable.
+ }
+
+ protected void sendCustomEvent(String type, ReadableMap data) {
+ NavAutoModule.getInstance().onCustomNavigationAutoEvent(type, data);
+ }
+
+ @NonNull
+ @Override
+ public Template onGetTemplate() {
+ return new NavigationTemplate.Builder()
+ .setMapActionStrip(new ActionStrip.Builder().addAction(Action.PAN).build())
+ .build();
+ }
+}
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 df28645..0c046b3 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
@@ -15,6 +15,7 @@
public class Constants {
public static final String NAV_JAVASCRIPT_FLAG = "NavJavascriptBridge";
+ public static final String NAV_AUTO_JAVASCRIPT_FLAG = "NavAutoJavascriptBridge";
public static final String LAT_FIELD_KEY = "lat";
public static final String LNG_FIELD_KEY = "lng";
}
diff --git a/android/src/main/java/com/google/android/react/navsdk/IMapViewFragment.java b/android/src/main/java/com/google/android/react/navsdk/IMapViewFragment.java
index e835afb..f5cde85 100644
--- a/android/src/main/java/com/google/android/react/navsdk/IMapViewFragment.java
+++ b/android/src/main/java/com/google/android/react/navsdk/IMapViewFragment.java
@@ -15,85 +15,17 @@
import android.view.View;
import com.google.android.gms.maps.GoogleMap;
-import com.google.android.gms.maps.model.Circle;
-import com.google.android.gms.maps.model.GroundOverlay;
-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 java.io.IOException;
-import java.util.Map;
+import com.google.android.libraries.navigation.StylingOptions;
public interface IMapViewFragment {
- void setStylingOptions(Map stylingOptions);
+ MapViewController getMapController();
- void applyStylingOptions();
-
- void setFollowingPerspective(int jsValue);
-
- void setNightModeOption(int jsValue);
-
- void setMapType(int jsValue);
-
- void clearMapView();
-
- void resetMinMaxZoomLevel();
-
- void animateCamera(Map map);
-
- Circle addCircle(Map optionsMap);
-
- Marker addMarker(Map optionsMap);
+ void setStylingOptions(StylingOptions stylingOptions);
- Polyline addPolyline(Map optionsMap);
-
- Polygon addPolygon(Map optionsMap);
-
- void removeMarker(String id);
-
- void removePolyline(String id);
-
- void removePolygon(String id);
-
- void removeCircle(String id);
-
- void removeGroundOverlay(String id);
-
- GroundOverlay addGroundOverlay(Map map);
+ void applyStylingOptions();
void setMapStyle(String url);
- String fetchJsonFromUrl(String urlString) throws IOException;
-
- void moveCamera(Map map);
-
- void setZoomLevel(int level);
-
- void setIndoorEnabled(boolean isOn);
-
- void setTrafficEnabled(boolean isOn);
-
- void setCompassEnabled(boolean isOn);
-
- void setRotateGesturesEnabled(boolean isOn);
-
- void setScrollGesturesEnabled(boolean isOn);
-
- void setScrollGesturesEnabledDuringRotateOrZoom(boolean isOn);
-
- void setTiltGesturesEnabled(boolean isOn);
-
- void setZoomControlsEnabled(boolean isOn);
-
- void setZoomGesturesEnabled(boolean isOn);
-
- void setBuildingsEnabled(boolean isOn);
-
- void setMyLocationEnabled(boolean isOn);
-
- void setMapToolbarEnabled(boolean isOn);
-
- void setMyLocationButtonEnabled(boolean isOn);
-
GoogleMap getGoogleMap();
// Fragment
diff --git a/android/src/main/java/com/google/android/react/navsdk/INavViewFragment.java b/android/src/main/java/com/google/android/react/navsdk/INavViewFragment.java
index 1abbac8..84057f5 100644
--- a/android/src/main/java/com/google/android/react/navsdk/INavViewFragment.java
+++ b/android/src/main/java/com/google/android/react/navsdk/INavViewFragment.java
@@ -31,4 +31,6 @@ public interface INavViewFragment extends IMapViewFragment {
void setRecenterButtonEnabled(boolean enabled);
void showRouteOverview();
+
+ void setNightModeOption(int jsValue);
}
diff --git a/android/src/main/java/com/google/android/react/navsdk/INavigationAutoCallback.java b/android/src/main/java/com/google/android/react/navsdk/INavigationAutoCallback.java
new file mode 100644
index 0000000..302871e
--- /dev/null
+++ b/android/src/main/java/com/google/android/react/navsdk/INavigationAutoCallback.java
@@ -0,0 +1,20 @@
+/**
+ * Copyright 2023 Google LLC
+ *
+ *
Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
+ *
+ *
http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
+ * express or implied. See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.android.react.navsdk;
+
+import com.facebook.react.bridge.ReadableMap;
+
+public interface INavigationAutoCallback {
+ void onCustomNavigationAutoEvent(String type, ReadableMap data);
+}
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
new file mode 100644
index 0000000..f51c993
--- /dev/null
+++ b/android/src/main/java/com/google/android/react/navsdk/INavigationViewCallback.java
@@ -0,0 +1,41 @@
+/**
+ * Copyright 2023 Google LLC
+ *
+ *
Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
+ *
+ *
http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
+ * express or implied. See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.android.react.navsdk;
+
+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.Marker;
+import com.google.android.gms.maps.model.Polygon;
+import com.google.android.gms.maps.model.Polyline;
+
+public interface INavigationViewCallback {
+ void onMapReady();
+
+ void onRecenterButtonClick();
+
+ void onMarkerClick(Marker marker);
+
+ void onPolylineClick(Polyline polyline);
+
+ void onPolygonClick(Polygon polygon);
+
+ void onCircleClick(Circle circle);
+
+ void onGroundOverlayClick(GroundOverlay groundOverlay);
+
+ void onMarkerInfoWindowTapped(Marker marker);
+
+ void onMapClick(LatLng latLng);
+}
diff --git a/android/src/main/java/com/google/android/react/navsdk/INavigationViewController.java b/android/src/main/java/com/google/android/react/navsdk/INavigationViewController.java
new file mode 100644
index 0000000..b1546ef
--- /dev/null
+++ b/android/src/main/java/com/google/android/react/navsdk/INavigationViewController.java
@@ -0,0 +1,20 @@
+/**
+ * Copyright 2024 Google LLC
+ *
+ *
Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
+ *
+ *
http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
+ * express or implied. See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.android.react.navsdk;
+
+import com.google.android.libraries.navigation.StylingOptions;
+
+public interface INavigationViewController {
+ void setStylingOptions(StylingOptions stylingOptions);
+}
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
new file mode 100644
index 0000000..68f023b
--- /dev/null
+++ b/android/src/main/java/com/google/android/react/navsdk/MapViewController.java
@@ -0,0 +1,572 @@
+/**
+ * Copyright 2024 Google LLC
+ *
+ *
Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
+ *
+ *
http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
+ * express or implied. See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.android.react.navsdk;
+
+import android.annotation.SuppressLint;
+import android.app.Activity;
+import android.graphics.Color;
+import androidx.core.util.Supplier;
+import com.facebook.react.bridge.UiThreadUtil;
+import com.google.android.gms.maps.CameraUpdateFactory;
+import com.google.android.gms.maps.GoogleMap;
+import com.google.android.gms.maps.model.BitmapDescriptor;
+import com.google.android.gms.maps.model.BitmapDescriptorFactory;
+import com.google.android.gms.maps.model.CameraPosition;
+import com.google.android.gms.maps.model.Circle;
+import com.google.android.gms.maps.model.CircleOptions;
+import com.google.android.gms.maps.model.GroundOverlay;
+import com.google.android.gms.maps.model.GroundOverlayOptions;
+import com.google.android.gms.maps.model.LatLng;
+import com.google.android.gms.maps.model.MapStyleOptions;
+import com.google.android.gms.maps.model.Marker;
+import com.google.android.gms.maps.model.MarkerOptions;
+import com.google.android.gms.maps.model.Polygon;
+import com.google.android.gms.maps.model.PolygonOptions;
+import com.google.android.gms.maps.model.Polyline;
+import com.google.android.gms.maps.model.PolylineOptions;
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.net.HttpURLConnection;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.Executors;
+
+public class MapViewController {
+ private GoogleMap mGoogleMap;
+ private Supplier activitySupplier;
+ private INavigationViewCallback mNavigationViewCallback;
+ private final List markerList = new ArrayList<>();
+ private final List polylineList = new ArrayList<>();
+ private final List polygonList = new ArrayList<>();
+ private final List groundOverlayList = new ArrayList<>();
+ private final List circleList = new ArrayList<>();
+ private String style = "";
+
+ public void initialize(GoogleMap googleMap, Supplier activitySupplier) {
+ this.mGoogleMap = googleMap;
+ this.activitySupplier = activitySupplier;
+ }
+
+ public void setupMapListeners(INavigationViewCallback navigationViewCallback) {
+ this.mNavigationViewCallback = navigationViewCallback;
+ if (mGoogleMap == null || mNavigationViewCallback == null) return;
+
+ mGoogleMap.setOnMarkerClickListener(
+ marker -> {
+ mNavigationViewCallback.onMarkerClick(marker);
+ return false;
+ });
+
+ mGoogleMap.setOnPolylineClickListener(
+ polyline -> mNavigationViewCallback.onPolylineClick(polyline));
+ mGoogleMap.setOnPolygonClickListener(
+ polygon -> mNavigationViewCallback.onPolygonClick(polygon));
+ mGoogleMap.setOnCircleClickListener(circle -> mNavigationViewCallback.onCircleClick(circle));
+ mGoogleMap.setOnGroundOverlayClickListener(
+ groundOverlay -> mNavigationViewCallback.onGroundOverlayClick(groundOverlay));
+ mGoogleMap.setOnInfoWindowClickListener(
+ marker -> mNavigationViewCallback.onMarkerInfoWindowTapped(marker));
+ mGoogleMap.setOnMapClickListener(latLng -> mNavigationViewCallback.onMapClick(latLng));
+ }
+
+ public GoogleMap getGoogleMap() {
+ return mGoogleMap;
+ }
+
+ public Circle addCircle(Map optionsMap) {
+ if (mGoogleMap == null) {
+ return null;
+ }
+
+ CircleOptions options = new CircleOptions();
+
+ float strokeWidth =
+ Double.valueOf(CollectionUtil.getDouble("strokeWidth", optionsMap, 0)).floatValue();
+ options.strokeWidth(strokeWidth);
+
+ double radius = CollectionUtil.getDouble("radius", optionsMap, 0.0);
+ options.radius(radius);
+
+ boolean visible = CollectionUtil.getBool("visible", optionsMap, true);
+ options.visible(visible);
+
+ options.center(
+ ObjectTranslationUtil.getLatLngFromMap((Map) optionsMap.get("center")));
+
+ boolean clickable = CollectionUtil.getBool("clickable", optionsMap, false);
+ options.clickable(clickable);
+
+ String strokeColor = CollectionUtil.getString("strokeColor", optionsMap);
+ if (strokeColor != null) {
+ options.strokeColor(Color.parseColor(strokeColor));
+ }
+
+ String fillColor = CollectionUtil.getString("fillColor", optionsMap);
+ if (fillColor != null) {
+ options.fillColor(Color.parseColor(fillColor));
+ }
+
+ Circle circle = mGoogleMap.addCircle(options);
+ circleList.add(circle);
+
+ return circle;
+ }
+
+ public Marker addMarker(Map optionsMap) {
+ if (mGoogleMap == null) {
+ return null;
+ }
+
+ String imagePath = CollectionUtil.getString("imgPath", optionsMap);
+ String title = CollectionUtil.getString("title", optionsMap);
+ String snippet = CollectionUtil.getString("snippet", optionsMap);
+ float alpha = Double.valueOf(CollectionUtil.getDouble("alpha", optionsMap, 1)).floatValue();
+ float rotation =
+ Double.valueOf(CollectionUtil.getDouble("rotation", optionsMap, 0)).floatValue();
+ boolean draggable = CollectionUtil.getBool("draggable", optionsMap, false);
+ boolean flat = CollectionUtil.getBool("flat", optionsMap, false);
+ boolean visible = CollectionUtil.getBool("visible", optionsMap, true);
+
+ MarkerOptions options = new MarkerOptions();
+ if (imagePath != null && !imagePath.isEmpty()) {
+ BitmapDescriptor icon = BitmapDescriptorFactory.fromPath(imagePath);
+ options.icon(icon);
+ }
+
+ options.position(
+ ObjectTranslationUtil.getLatLngFromMap((Map) optionsMap.get("position")));
+
+ if (title != null) {
+ options.title(title);
+ }
+
+ if (snippet != null) {
+ options.snippet(snippet);
+ }
+
+ options.flat(flat);
+ options.alpha(alpha);
+ options.rotation(rotation);
+ options.draggable(draggable);
+ options.visible(visible);
+
+ Marker marker = mGoogleMap.addMarker(options);
+
+ markerList.add(marker);
+
+ return marker;
+ }
+
+ public Polyline addPolyline(Map optionsMap) {
+ if (mGoogleMap == null) {
+ return null;
+ }
+
+ float width = Double.valueOf(CollectionUtil.getDouble("width", optionsMap, 0)).floatValue();
+ boolean clickable = CollectionUtil.getBool("clickable", optionsMap, false);
+ boolean visible = CollectionUtil.getBool("visible", optionsMap, true);
+
+ ArrayList latLngArr = (ArrayList) optionsMap.get("points");
+
+ if (latLngArr == null) {
+ return null;
+ }
+
+ PolylineOptions options = new PolylineOptions();
+ for (int i = 0; i < latLngArr.size(); i++) {
+ Map latLngMap = (Map) latLngArr.get(i);
+ LatLng latLng = createLatLng(latLngMap);
+ options.add(latLng);
+ }
+
+ String color = CollectionUtil.getString("color", optionsMap);
+ if (color != null) {
+ options.color(Color.parseColor(color));
+ }
+
+ options.width(width);
+ options.clickable(clickable);
+ options.visible(visible);
+
+ Polyline polyline = mGoogleMap.addPolyline(options);
+ polylineList.add(polyline);
+
+ return polyline;
+ }
+
+ public Polygon addPolygon(Map optionsMap) {
+ if (mGoogleMap == null) {
+ return null;
+ }
+
+ String strokeColor = CollectionUtil.getString("strokeColor", optionsMap);
+ String fillColor = CollectionUtil.getString("fillColor", optionsMap);
+ float strokeWidth =
+ Double.valueOf(CollectionUtil.getDouble("strokeWidth", optionsMap, 0)).floatValue();
+ boolean clickable = CollectionUtil.getBool("clickable", optionsMap, false);
+ boolean geodesic = CollectionUtil.getBool("geodesic", optionsMap, false);
+ boolean visible = CollectionUtil.getBool("visible", optionsMap, true);
+
+ ArrayList latLngArr = (ArrayList) optionsMap.get("points");
+
+ PolygonOptions options = new PolygonOptions();
+ for (int i = 0; i < latLngArr.size(); i++) {
+ Map latLngMap = (Map) latLngArr.get(i);
+ LatLng latLng = createLatLng(latLngMap);
+ options.add(latLng);
+ }
+
+ ArrayList holesArr = (ArrayList) optionsMap.get("holes");
+
+ for (int i = 0; i < holesArr.size(); i++) {
+ ArrayList arr = (ArrayList) holesArr.get(i);
+
+ List listHoles = new ArrayList<>();
+
+ for (int j = 0; j < arr.size(); j++) {
+ Map latLngMap = (Map) arr.get(j);
+ LatLng latLng = createLatLng(latLngMap);
+
+ listHoles.add(latLng);
+ }
+
+ options.addHole(listHoles);
+ }
+
+ if (fillColor != null) {
+ options.fillColor(Color.parseColor(fillColor));
+ }
+
+ if (strokeColor != null) {
+ options.strokeColor(Color.parseColor(strokeColor));
+ }
+
+ options.strokeWidth(strokeWidth);
+ options.visible(visible);
+ options.geodesic(geodesic);
+ options.clickable(clickable);
+
+ Polygon polygon = mGoogleMap.addPolygon(options);
+ polygonList.add(polygon);
+
+ return polygon;
+ }
+
+ public GroundOverlay addGroundOverlay(Map map) {
+ if (mGoogleMap == null) {
+ return null;
+ }
+
+ String imagePath = CollectionUtil.getString("imgPath", map);
+ float width = Double.valueOf(CollectionUtil.getDouble("width", map, 0)).floatValue();
+ float height = Double.valueOf(CollectionUtil.getDouble("height", map, 0)).floatValue();
+ float transparency =
+ Double.valueOf(CollectionUtil.getDouble("transparency", map, 0)).floatValue();
+ boolean clickable = CollectionUtil.getBool("clickable", map, false);
+ boolean visible = CollectionUtil.getBool("visible", map, true);
+
+ Double lat = null;
+ Double lng = null;
+ if (map.containsKey("location")) {
+ Map latlng = (Map) map.get("location");
+ if (latlng.get("lat") != null) lat = Double.parseDouble(latlng.get("lat").toString());
+ if (latlng.get("lng") != null) lng = Double.parseDouble(latlng.get("lng").toString());
+ }
+
+ GroundOverlayOptions options = new GroundOverlayOptions();
+ if (imagePath != null && !imagePath.isEmpty()) {
+ BitmapDescriptor bitmapDescriptor = BitmapDescriptorFactory.fromPath(imagePath);
+ options.image(bitmapDescriptor);
+ }
+ options.position(new LatLng(lat, lng), width, height);
+ options.transparency(transparency);
+ options.clickable(clickable);
+ options.visible(visible);
+ GroundOverlay groundOverlay = mGoogleMap.addGroundOverlay(options);
+ groundOverlayList.add(groundOverlay);
+ return groundOverlay;
+ }
+
+ public void removeMarker(String id) {
+ UiThreadUtil.runOnUiThread(
+ () -> {
+ for (Marker m : markerList) {
+ if (m.getId().equals(id)) {
+ m.remove();
+ markerList.remove(m);
+ return;
+ }
+ }
+ });
+ }
+
+ public void removePolyline(String id) {
+ for (Polyline p : polylineList) {
+ if (p.getId().equals(id)) {
+ p.remove();
+ polylineList.remove(p);
+ return;
+ }
+ }
+ }
+
+ public void removePolygon(String id) {
+ for (Polygon p : polygonList) {
+ if (p.getId().equals(id)) {
+ p.remove();
+ polygonList.remove(p);
+ return;
+ }
+ }
+ }
+
+ public void removeCircle(String id) {
+ for (Circle c : circleList) {
+ if (c.getId().equals(id)) {
+ c.remove();
+ circleList.remove(c);
+ return;
+ }
+ }
+ }
+
+ public void removeGroundOverlay(String id) {
+ for (GroundOverlay g : groundOverlayList) {
+ if (g.getId().equals(id)) {
+ g.remove();
+ groundOverlayList.remove(g);
+ return;
+ }
+ }
+ }
+
+ public void setMapStyle(String url) {
+ Executors.newSingleThreadExecutor()
+ .execute(
+ () -> {
+ try {
+ style = fetchJsonFromUrl(url);
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+
+ Activity activity = activitySupplier.get();
+ if (activity != null) {
+ activity.runOnUiThread(
+ () -> {
+ MapStyleOptions options = new MapStyleOptions(style);
+ mGoogleMap.setMapStyle(options);
+ });
+ }
+ });
+ }
+
+ /** Moves the position of the camera to hover over Melbourne. */
+ public void moveCamera(Map map) {
+ LatLng latLng = ObjectTranslationUtil.getLatLngFromMap((Map) map.get("target"));
+
+ float zoom = (float) CollectionUtil.getDouble("zoom", map, 0);
+ float tilt = (float) CollectionUtil.getDouble("tilt", map, 0);
+ float bearing = (float) CollectionUtil.getDouble("bearing", map, 0);
+
+ CameraPosition cameraPosition =
+ CameraPosition.builder().target(latLng).zoom(zoom).tilt(tilt).bearing(bearing).build();
+
+ mGoogleMap.moveCamera(CameraUpdateFactory.newCameraPosition(cameraPosition));
+ }
+
+ public void animateCamera(Map map) {
+ if (mGoogleMap != null) {
+ int zoom = CollectionUtil.getInt("zoom", map, 0);
+ int tilt = CollectionUtil.getInt("tilt", map, 0);
+ int bearing = CollectionUtil.getInt("bearing", map, 0);
+ int animationDuration = CollectionUtil.getInt("duration", map, 0);
+
+ CameraPosition cameraPosition =
+ new CameraPosition.Builder()
+ .target(
+ ObjectTranslationUtil.getLatLngFromMap(
+ (Map) map.get("target"))) // Set the target location
+ .zoom(zoom) // Set the desired zoom level
+ .tilt(tilt) // Set the desired tilt angle (0 for straight down, 90 for straight up)
+ .bearing(bearing) // Set the desired bearing (rotation angle in degrees)
+ .build();
+
+ mGoogleMap.animateCamera(
+ CameraUpdateFactory.newCameraPosition(cameraPosition), animationDuration, null);
+ }
+ }
+
+ public void setZoomLevel(int level) {
+ if (mGoogleMap != null) {
+ mGoogleMap.animateCamera(CameraUpdateFactory.zoomTo(level));
+ }
+ }
+
+ public void setIndoorEnabled(boolean isOn) {
+ if (mGoogleMap != null) {
+ mGoogleMap.setIndoorEnabled(isOn);
+ }
+ }
+
+ public void setTrafficEnabled(boolean isOn) {
+ if (mGoogleMap != null) {
+ mGoogleMap.setTrafficEnabled(isOn);
+ }
+ }
+
+ public void setCompassEnabled(boolean isOn) {
+ if (mGoogleMap != null) {
+ mGoogleMap.getUiSettings().setCompassEnabled(isOn);
+ }
+ }
+
+ public void setRotateGesturesEnabled(boolean isOn) {
+ if (mGoogleMap != null) {
+ mGoogleMap.getUiSettings().setRotateGesturesEnabled(isOn);
+ }
+ }
+
+ public void setScrollGesturesEnabled(boolean isOn) {
+ if (mGoogleMap != null) {
+ mGoogleMap.getUiSettings().setScrollGesturesEnabled(isOn);
+ }
+ }
+
+ public void setScrollGesturesEnabledDuringRotateOrZoom(boolean isOn) {
+ if (mGoogleMap != null) {
+ mGoogleMap.getUiSettings().setScrollGesturesEnabledDuringRotateOrZoom(isOn);
+ }
+ }
+
+ public void setTiltGesturesEnabled(boolean isOn) {
+ if (mGoogleMap != null) {
+ mGoogleMap.getUiSettings().setTiltGesturesEnabled(isOn);
+ }
+ }
+
+ public void setZoomControlsEnabled(boolean isOn) {
+ if (mGoogleMap != null) {
+ mGoogleMap.getUiSettings().setZoomControlsEnabled(isOn);
+ }
+ }
+
+ public void setZoomGesturesEnabled(boolean isOn) {
+ if (mGoogleMap != null) {
+ mGoogleMap.getUiSettings().setZoomGesturesEnabled(isOn);
+ }
+ }
+
+ public void setBuildingsEnabled(boolean isOn) {
+ if (mGoogleMap != null) {
+ mGoogleMap.setBuildingsEnabled(isOn);
+ }
+ }
+
+ @SuppressLint("MissingPermission")
+ public void setMyLocationEnabled(boolean isOn) {
+ if (mGoogleMap != null) {
+ mGoogleMap.setMyLocationEnabled(isOn);
+ }
+ }
+
+ public void setMapToolbarEnabled(boolean isOn) {
+ if (mGoogleMap != null) {
+ mGoogleMap.getUiSettings().setMapToolbarEnabled(isOn);
+ }
+ }
+
+ /** Toggles whether the location marker is enabled. */
+ public void setMyLocationButtonEnabled(boolean isOn) {
+ if (mGoogleMap == null) {
+ return;
+ }
+
+ UiThreadUtil.runOnUiThread(
+ () -> {
+ mGoogleMap.getUiSettings().setMyLocationButtonEnabled(isOn);
+ });
+ }
+
+ public void setMapType(int jsValue) {
+ if (mGoogleMap == null) {
+ return;
+ }
+
+ mGoogleMap.setMapType(EnumTranslationUtil.getMapTypeFromJsValue(jsValue));
+ }
+
+ public void clearMapView() {
+ if (mGoogleMap == null) {
+ return;
+ }
+
+ mGoogleMap.clear();
+ }
+
+ public void resetMinMaxZoomLevel() {
+ if (mGoogleMap == null) {
+ return;
+ }
+
+ mGoogleMap.resetMinMaxZoomPreference();
+ }
+
+ @SuppressLint("MissingPermission")
+ public void setFollowingPerspective(int jsValue) {
+ if (mGoogleMap == null) {
+ return;
+ }
+
+ mGoogleMap.followMyLocation(EnumTranslationUtil.getCameraPerspectiveFromJsValue(jsValue));
+ }
+
+ private String fetchJsonFromUrl(String urlString) throws IOException {
+ URL url = new URL(urlString);
+ HttpURLConnection connection = (HttpURLConnection) url.openConnection();
+ connection.setRequestMethod("GET");
+
+ int responseCode = connection.getResponseCode();
+ if (responseCode == HttpURLConnection.HTTP_OK) {
+ InputStream inputStream = connection.getInputStream();
+ BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
+ StringBuilder stringBuilder = new StringBuilder();
+ String line;
+ while ((line = reader.readLine()) != null) {
+ stringBuilder.append(line);
+ }
+ reader.close();
+ inputStream.close();
+ return stringBuilder.toString();
+ } else {
+ // Handle error response
+ throw new IOException("Error response: " + responseCode);
+ }
+ }
+
+ private LatLng createLatLng(Map map) {
+ Double lat = null;
+ Double lng = null;
+ if (map.containsKey("lat") && map.containsKey("lng")) {
+ if (map.get("lat") != null) lat = Double.parseDouble(map.get("lat").toString());
+ if (map.get("lng") != null) lng = Double.parseDouble(map.get("lng").toString());
+ }
+
+ return new LatLng(lat, lng);
+ }
+}
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 e3e3125..1cb60d6 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
@@ -41,7 +41,6 @@
import com.google.android.gms.maps.model.GroundOverlay;
import com.google.android.gms.maps.model.GroundOverlayOptions;
import com.google.android.gms.maps.model.LatLng;
-import com.google.android.gms.maps.model.MapStyleOptions;
import com.google.android.gms.maps.model.Marker;
import com.google.android.gms.maps.model.MarkerOptions;
import com.google.android.gms.maps.model.Polygon;
@@ -49,25 +48,20 @@
import com.google.android.gms.maps.model.Polyline;
import com.google.android.gms.maps.model.PolylineOptions;
import com.google.android.libraries.navigation.StylingOptions;
-import java.io.BufferedReader;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.InputStreamReader;
-import java.net.HttpURLConnection;
-import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
-import java.util.concurrent.Executors;
/**
* A fragment that displays a view with a Google Map using MapFragment. This fragment's lifecycle is
* managed by NavViewManager.
*/
@SuppressLint("ValidFragment")
-public class MapViewFragment extends SupportMapFragment implements IMapViewFragment {
+public class MapViewFragment extends SupportMapFragment
+ implements IMapViewFragment, INavigationViewCallback {
private static final String TAG = "MapViewFragment";
private GoogleMap mGoogleMap;
+ private MapViewController mMapViewController;
private StylingOptions mStylingOptions;
private List markerList = new ArrayList<>();
@@ -96,82 +90,69 @@ public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceStat
public void onMapReady(GoogleMap googleMap) {
mGoogleMap = googleMap;
- emitEvent("onMapReady", null);
+ mMapViewController = new MapViewController();
+ mMapViewController.initialize(googleMap, () -> requireActivity());
+
+ // Setup map listeners with the provided callback
+ mMapViewController.setupMapListeners(MapViewFragment.this);
- mGoogleMap.setOnMarkerClickListener(
- new GoogleMap.OnMarkerClickListener() {
- @Override
- public boolean onMarkerClick(Marker marker) {
- emitEvent("onMarkerClick", ObjectTranslationUtil.getMapFromMarker(marker));
- return false;
- }
- });
- mGoogleMap.setOnPolylineClickListener(
- new GoogleMap.OnPolylineClickListener() {
- @Override
- public void onPolylineClick(Polyline polyline) {
- emitEvent(
- "onPolylineClick", ObjectTranslationUtil.getMapFromPolyline(polyline));
- }
- });
- mGoogleMap.setOnPolygonClickListener(
- new GoogleMap.OnPolygonClickListener() {
- @Override
- public void onPolygonClick(Polygon polygon) {
- emitEvent("onPolygonClick", ObjectTranslationUtil.getMapFromPolygon(polygon));
- }
- });
- mGoogleMap.setOnCircleClickListener(
- new GoogleMap.OnCircleClickListener() {
- @Override
- public void onCircleClick(Circle circle) {
- emitEvent("onCircleClick", ObjectTranslationUtil.getMapFromCircle(circle));
- }
- });
- mGoogleMap.setOnGroundOverlayClickListener(
- new GoogleMap.OnGroundOverlayClickListener() {
- @Override
- public void onGroundOverlayClick(GroundOverlay groundOverlay) {
- emitEvent(
- "onGroundOverlayClick",
- ObjectTranslationUtil.getMapFromGroundOverlay(groundOverlay));
- }
- });
-
- mGoogleMap.setOnInfoWindowClickListener(
- new GoogleMap.OnInfoWindowClickListener() {
- @Override
- public void onInfoWindowClick(Marker marker) {
- emitEvent(
- "onMarkerInfoWindowTapped", ObjectTranslationUtil.getMapFromMarker(marker));
- }
- });
-
- mGoogleMap.setOnMapClickListener(
- new GoogleMap.OnMapClickListener() {
- @Override
- public void onMapClick(LatLng latLng) {
- emitEvent("onMapClick", ObjectTranslationUtil.getMapFromLatLng(latLng));
- }
- });
+ emitEvent("onMapReady", null);
}
});
}
- public void applyStylingOptions() {}
+ @Override
+ public void onMapReady() {
+ emitEvent("onMapReady", null);
+ }
- public void setStylingOptions(Map stylingOptions) {}
+ @Override
+ public void onRecenterButtonClick() {
+ emitEvent("onRecenterButtonClick", null);
+ }
- @SuppressLint("MissingPermission")
- public void setFollowingPerspective(int jsValue) {
- if (mGoogleMap == null) {
- return;
- }
+ @Override
+ public void onMarkerClick(Marker marker) {
+ emitEvent("onMapReady", ObjectTranslationUtil.getMapFromMarker(marker));
+ }
+
+ @Override
+ public void onPolylineClick(Polyline polyline) {
+ emitEvent("onPolylineClick", ObjectTranslationUtil.getMapFromPolyline(polyline));
+ }
- mGoogleMap.followMyLocation(EnumTranslationUtil.getCameraPerspectiveFromJsValue(jsValue));
+ @Override
+ public void onPolygonClick(Polygon polygon) {
+ emitEvent("onPolygonClick", ObjectTranslationUtil.getMapFromPolygon(polygon));
}
- public void setNightModeOption(int jsValue) {}
+ @Override
+ public void onCircleClick(Circle circle) {
+ emitEvent("onCircleClick", ObjectTranslationUtil.getMapFromCircle(circle));
+ }
+
+ @Override
+ public void onGroundOverlayClick(GroundOverlay groundOverlay) {
+ emitEvent("onGroundOverlayClick", ObjectTranslationUtil.getMapFromGroundOverlay(groundOverlay));
+ }
+
+ @Override
+ public void onMarkerInfoWindowTapped(Marker marker) {
+ emitEvent("onInfoWindowClick", ObjectTranslationUtil.getMapFromMarker(marker));
+ }
+
+ @Override
+ public void onMapClick(LatLng latLng) {
+ emitEvent("onMapClick", ObjectTranslationUtil.getMapFromLatLng(latLng));
+ }
+
+ public MapViewController getMapController() {
+ return mMapViewController;
+ }
+
+ public void applyStylingOptions() {}
+
+ public void setStylingOptions(StylingOptions stylingOptions) {}
public void setMapType(int jsValue) {
if (mGoogleMap == null) {
@@ -492,45 +473,7 @@ public GroundOverlay addGroundOverlay(Map map) {
}
public void setMapStyle(String url) {
- Executors.newSingleThreadExecutor()
- .execute(
- () -> {
- try {
- style = fetchJsonFromUrl(url);
- } catch (IOException e) {
- throw new RuntimeException(e);
- }
- getActivity()
- .runOnUiThread(
- (Runnable)
- () -> {
- MapStyleOptions options = new MapStyleOptions(style);
- mGoogleMap.setMapStyle(options);
- });
- });
- }
-
- public String fetchJsonFromUrl(String urlString) throws IOException {
- URL url = new URL(urlString);
- HttpURLConnection connection = (HttpURLConnection) url.openConnection();
- connection.setRequestMethod("GET");
-
- int responseCode = connection.getResponseCode();
- if (responseCode == HttpURLConnection.HTTP_OK) {
- InputStream inputStream = connection.getInputStream();
- BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
- StringBuilder stringBuilder = new StringBuilder();
- String line;
- while ((line = reader.readLine()) != null) {
- stringBuilder.append(line);
- }
- reader.close();
- inputStream.close();
- return stringBuilder.toString();
- } else {
- // Handle error response
- throw new IOException("Error response: " + responseCode);
- }
+ mMapViewController.setMapStyle(url);
}
/** Moves the position of the camera to hover over Melbourne. */
@@ -658,25 +601,6 @@ public GoogleMap getGoogleMap() {
return mGoogleMap;
}
- // Navigation related function of the IViewFragment interface. Not used in this class.
- public void setNavigationUiEnabled(boolean enableNavigationUi) {}
-
- public void setTripProgressBarEnabled(boolean enabled) {}
-
- public void setSpeedometerEnabled(boolean enabled) {}
-
- public void setSpeedLimitIconEnabled(boolean enabled) {}
-
- public void setTrafficIncidentCardsEnabled(boolean enabled) {}
-
- public void setEtaCardEnabled(boolean enabled) {}
-
- public void setHeaderEnabled(boolean enabled) {}
-
- public void setRecenterButtonEnabled(boolean enabled) {}
-
- public void showRouteOverview() {}
-
public class NavViewEvent extends Event {
private String eventName;
private @Nullable WritableMap eventData;
diff --git a/android/src/main/java/com/google/android/react/navsdk/NavAutoModule.java b/android/src/main/java/com/google/android/react/navsdk/NavAutoModule.java
new file mode 100644
index 0000000..dec5202
--- /dev/null
+++ b/android/src/main/java/com/google/android/react/navsdk/NavAutoModule.java
@@ -0,0 +1,537 @@
+/**
+ * Copyright 2024 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
+ *
+ *
http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *
Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
+ * express or implied. See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.android.react.navsdk;
+
+import android.location.Location;
+import com.facebook.react.bridge.Arguments;
+import com.facebook.react.bridge.CatalystInstance;
+import com.facebook.react.bridge.NativeArray;
+import com.facebook.react.bridge.Promise;
+import com.facebook.react.bridge.ReactApplicationContext;
+import com.facebook.react.bridge.ReactContext;
+import com.facebook.react.bridge.ReactContextBaseJavaModule;
+import com.facebook.react.bridge.ReactMethod;
+import com.facebook.react.bridge.ReadableMap;
+import com.facebook.react.bridge.UiThreadUtil;
+import com.facebook.react.bridge.WritableMap;
+import com.facebook.react.bridge.WritableNativeArray;
+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.LatLng;
+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.libraries.navigation.StylingOptions;
+import java.util.Map;
+
+/**
+ * This exposes a series of methods that can be called diretly from the React Native code. They have
+ * been implemented using promises as it's not recommended for them to be synchronous.
+ */
+public class NavAutoModule extends ReactContextBaseJavaModule implements INavigationAutoCallback {
+ public static final String REACT_CLASS = "NavAutoModule";
+ private static final String TAG = "AndroidAutoModule";
+ private static NavAutoModule instance;
+ private static ModuleReadyListener moduleReadyListener;
+
+ ReactApplicationContext reactContext;
+ private MapViewController mMapViewController;
+ private StylingOptions mStylingOptions;
+ private INavigationViewController mNavigationViewController;
+
+ public interface ModuleReadyListener {
+ void onModuleReady();
+ }
+
+ public NavAutoModule(ReactApplicationContext reactContext) {
+ super(reactContext);
+ this.reactContext = reactContext;
+ instance = this;
+ if (moduleReadyListener != null) {
+ moduleReadyListener.onModuleReady();
+ }
+ }
+
+ @Override
+ public String getName() {
+ return REACT_CLASS;
+ }
+
+ // Called by the AndroidAuto implementation. See SampleApp for example.
+ public static synchronized NavAutoModule getInstance() {
+ if (instance == null) {
+ throw new IllegalStateException(REACT_CLASS + " instance is null");
+ }
+ return instance;
+ }
+
+ public static void setModuleReadyListener(ModuleReadyListener listener) {
+ moduleReadyListener = listener;
+ if (instance != null && moduleReadyListener != null) {
+ moduleReadyListener.onModuleReady();
+ }
+ }
+
+ public void androidAutoNavigationScreenInitialized(
+ MapViewController mapViewController, INavigationViewController navigationViewController) {
+ mMapViewController = mapViewController;
+ mNavigationViewController = navigationViewController;
+ if (mStylingOptions != null && mNavigationViewController != null) {
+ mNavigationViewController.setStylingOptions(mStylingOptions);
+ }
+ sendScreenState(true);
+ }
+
+ public void androidAutoNavigationScreenDisposed() {
+ sendScreenState(false);
+ mMapViewController = null;
+ mNavigationViewController = null;
+ }
+
+ public void setStylingOptions(Map stylingOptions) {
+ mStylingOptions = new StylingOptionsBuilder.Builder(stylingOptions).build();
+ if (mStylingOptions != null && mNavigationViewController != null) {
+ mNavigationViewController.setStylingOptions(mStylingOptions);
+ }
+ }
+
+ @ReactMethod
+ public void setMapType(int jsValue) {
+ UiThreadUtil.runOnUiThread(
+ () -> {
+ if (mMapViewController == null) {
+ return;
+ }
+ mMapViewController.setMapType(jsValue);
+ });
+ }
+
+ @ReactMethod
+ public void setMapStyle(String url) {
+ UiThreadUtil.runOnUiThread(
+ () -> {
+ if (mMapViewController == null) {
+ return;
+ }
+ mMapViewController.setMapStyle(url);
+ });
+ }
+
+ @ReactMethod
+ public void setMapToolbarEnabled(boolean isOn) {
+ UiThreadUtil.runOnUiThread(
+ () -> {
+ if (mMapViewController == null) {
+ return;
+ }
+ mMapViewController.setMapToolbarEnabled(isOn);
+ });
+ }
+
+ @ReactMethod
+ public void addCircle(ReadableMap circleOptionsMap, final Promise promise) {
+ UiThreadUtil.runOnUiThread(
+ () -> {
+ if (mMapViewController == null) {
+ promise.reject(JsErrors.NO_MAP_ERROR_CODE, JsErrors.NO_MAP_ERROR_MESSAGE);
+ return;
+ }
+ Circle circle = mMapViewController.addCircle(circleOptionsMap.toHashMap());
+
+ promise.resolve(ObjectTranslationUtil.getMapFromCircle(circle));
+ });
+ }
+
+ @ReactMethod
+ public void addMarker(ReadableMap markerOptionsMap, final Promise promise) {
+ UiThreadUtil.runOnUiThread(
+ () -> {
+ if (mMapViewController == null) {
+ promise.reject(JsErrors.NO_MAP_ERROR_CODE, JsErrors.NO_MAP_ERROR_MESSAGE);
+ return;
+ }
+ Marker marker = mMapViewController.addMarker(markerOptionsMap.toHashMap());
+
+ promise.resolve(ObjectTranslationUtil.getMapFromMarker(marker));
+ });
+ }
+
+ @ReactMethod
+ public void addPolyline(ReadableMap polylineOptionsMap, final Promise promise) {
+ UiThreadUtil.runOnUiThread(
+ () -> {
+ if (mMapViewController == null) {
+ promise.reject(JsErrors.NO_MAP_ERROR_CODE, JsErrors.NO_MAP_ERROR_MESSAGE);
+ return;
+ }
+ Polyline polyline = mMapViewController.addPolyline(polylineOptionsMap.toHashMap());
+
+ promise.resolve(ObjectTranslationUtil.getMapFromPolyline(polyline));
+ });
+ }
+
+ @ReactMethod
+ public void addPolygon(ReadableMap polygonOptionsMap, final Promise promise) {
+ UiThreadUtil.runOnUiThread(
+ () -> {
+ if (mMapViewController == null) {
+ promise.reject(JsErrors.NO_MAP_ERROR_CODE, JsErrors.NO_MAP_ERROR_MESSAGE);
+ return;
+ }
+ Polygon polygon = mMapViewController.addPolygon(polygonOptionsMap.toHashMap());
+
+ promise.resolve(ObjectTranslationUtil.getMapFromPolygon(polygon));
+ });
+ }
+
+ @ReactMethod
+ public void removeCircle(String id) {
+ UiThreadUtil.runOnUiThread(
+ () -> {
+ if (mMapViewController == null) {
+ return;
+ }
+ mMapViewController.removeCircle(id);
+ });
+ }
+
+ @ReactMethod
+ public void removeMarker(String id) {
+ UiThreadUtil.runOnUiThread(
+ () -> {
+ if (mMapViewController == null) {
+ return;
+ }
+ mMapViewController.removeMarker(id);
+ });
+ }
+
+ @ReactMethod
+ public void removePolyline(String id) {
+ UiThreadUtil.runOnUiThread(
+ () -> {
+ if (mMapViewController == null) {
+ return;
+ }
+ mMapViewController.removePolyline(id);
+ });
+ }
+
+ @ReactMethod
+ public void removePolygon(String id) {
+ UiThreadUtil.runOnUiThread(
+ () -> {
+ if (mMapViewController == null) {
+ return;
+ }
+ mMapViewController.removePolygon(id);
+ });
+ }
+
+ @ReactMethod
+ public void clearMapView() {
+ UiThreadUtil.runOnUiThread(
+ () -> {
+ if (mMapViewController == null) {
+ return;
+ }
+ mMapViewController.clearMapView();
+ });
+ }
+
+ @ReactMethod
+ public void setIndoorEnabled(Boolean isOn) {
+ UiThreadUtil.runOnUiThread(
+ () -> {
+ if (mMapViewController == null) {
+ return;
+ }
+ mMapViewController.setIndoorEnabled(isOn);
+ });
+ }
+
+ @ReactMethod
+ public void setTrafficEnabled(Boolean isOn) {
+ UiThreadUtil.runOnUiThread(
+ () -> {
+ if (mMapViewController == null) {
+ return;
+ }
+ mMapViewController.setTrafficEnabled(isOn);
+ });
+ }
+
+ @ReactMethod
+ public void setCompassEnabled(Boolean isOn) {
+ UiThreadUtil.runOnUiThread(
+ () -> {
+ if (mMapViewController == null) {
+ return;
+ }
+ mMapViewController.setCompassEnabled(isOn);
+ });
+ }
+
+ @ReactMethod
+ public void setMyLocationButtonEnabled(Boolean isOn) {
+ UiThreadUtil.runOnUiThread(
+ () -> {
+ if (mMapViewController == null) {
+ return;
+ }
+ mMapViewController.setMyLocationButtonEnabled(isOn);
+ });
+ }
+
+ @ReactMethod
+ public void setMyLocationEnabled(Boolean isOn) {
+ UiThreadUtil.runOnUiThread(
+ () -> {
+ if (mMapViewController == null) {
+ return;
+ }
+ mMapViewController.setMyLocationEnabled(isOn);
+ });
+ }
+
+ @ReactMethod
+ public void setRotateGesturesEnabled(Boolean isOn) {
+ UiThreadUtil.runOnUiThread(
+ () -> {
+ if (mMapViewController == null) {
+ return;
+ }
+ mMapViewController.setRotateGesturesEnabled(isOn);
+ });
+ }
+
+ @ReactMethod
+ public void setScrollGesturesEnabled(Boolean isOn) {
+ UiThreadUtil.runOnUiThread(
+ () -> {
+ if (mMapViewController == null) {
+ return;
+ }
+ mMapViewController.setScrollGesturesEnabled(isOn);
+ });
+ }
+
+ @ReactMethod
+ public void setScrollGesturesEnabledDuringRotateOrZoom(Boolean isOn) {
+ UiThreadUtil.runOnUiThread(
+ () -> {
+ if (mMapViewController == null) {
+ return;
+ }
+ mMapViewController.setScrollGesturesEnabledDuringRotateOrZoom(isOn);
+ });
+ }
+
+ @ReactMethod
+ public void setZoomControlsEnabled(Boolean isOn) {
+ UiThreadUtil.runOnUiThread(
+ () -> {
+ if (mMapViewController == null) {
+ return;
+ }
+ mMapViewController.setZoomControlsEnabled(isOn);
+ });
+ }
+
+ @ReactMethod
+ public void setZoomLevel(final Integer level, final Promise promise) {
+ UiThreadUtil.runOnUiThread(
+ () -> {
+ if (mMapViewController == null) {
+ promise.reject(JsErrors.NO_MAP_ERROR_CODE, JsErrors.NO_MAP_ERROR_MESSAGE);
+ return;
+ }
+
+ mMapViewController.setZoomLevel(level);
+ promise.resolve(true);
+ });
+ }
+
+ @ReactMethod
+ public void setTiltGesturesEnabled(Boolean isOn) {
+ UiThreadUtil.runOnUiThread(
+ () -> {
+ if (mMapViewController == null) {
+ return;
+ }
+ mMapViewController.setTiltGesturesEnabled(isOn);
+ });
+ }
+
+ @ReactMethod
+ public void setZoomGesturesEnabled(Boolean isOn) {
+ UiThreadUtil.runOnUiThread(
+ () -> {
+ if (mMapViewController == null) {
+ return;
+ }
+ mMapViewController.setZoomGesturesEnabled(isOn);
+ });
+ }
+
+ @ReactMethod
+ public void setBuildingsEnabled(Boolean isOn) {
+ UiThreadUtil.runOnUiThread(
+ () -> {
+ if (mMapViewController == null) {
+ return;
+ }
+ mMapViewController.setBuildingsEnabled(isOn);
+ });
+ }
+
+ @ReactMethod
+ public void getCameraPosition(final Promise promise) {
+ UiThreadUtil.runOnUiThread(
+ () -> {
+ if (mMapViewController == null) {
+ promise.reject(JsErrors.NO_MAP_ERROR_CODE, JsErrors.NO_MAP_ERROR_MESSAGE);
+ return;
+ }
+
+ CameraPosition cp = mMapViewController.getGoogleMap().getCameraPosition();
+
+ if (cp == null) {
+ promise.resolve(null);
+ 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));
+
+ promise.resolve(map);
+ });
+ }
+
+ @ReactMethod
+ public void getMyLocation(final Promise promise) {
+ UiThreadUtil.runOnUiThread(
+ () -> {
+ if (mMapViewController == null) {
+ promise.reject(JsErrors.NO_MAP_ERROR_CODE, JsErrors.NO_MAP_ERROR_MESSAGE);
+ return;
+ }
+
+ try {
+ Location location = mMapViewController.getGoogleMap().getMyLocation();
+ promise.resolve(ObjectTranslationUtil.getMapFromLocation(location));
+ } catch (Exception e) {
+ promise.resolve(null);
+ return;
+ }
+ });
+ }
+
+ @ReactMethod
+ public void getUiSettings(final Promise promise) {
+ UiThreadUtil.runOnUiThread(
+ () -> {
+ if (mMapViewController == null) {
+ promise.reject(JsErrors.NO_MAP_ERROR_CODE, JsErrors.NO_MAP_ERROR_MESSAGE);
+ return;
+ }
+
+ UiSettings settings = mMapViewController.getGoogleMap().getUiSettings();
+
+ if (settings == null) {
+ promise.resolve(null);
+ return;
+ }
+
+ WritableMap map = Arguments.createMap();
+ map.putBoolean("isCompassEnabled", settings.isCompassEnabled());
+ map.putBoolean("isMapToolbarEnabled", settings.isMapToolbarEnabled());
+ map.putBoolean("isIndoorLevelPickerEnabled", settings.isIndoorLevelPickerEnabled());
+ map.putBoolean("isRotateGesturesEnabled", settings.isRotateGesturesEnabled());
+ map.putBoolean("isScrollGesturesEnabled", settings.isScrollGesturesEnabled());
+ map.putBoolean(
+ "isScrollGesturesEnabledDuringRotateOrZoom",
+ settings.isScrollGesturesEnabledDuringRotateOrZoom());
+ map.putBoolean("isTiltGesturesEnabled", settings.isTiltGesturesEnabled());
+ map.putBoolean("isZoomControlsEnabled", settings.isZoomControlsEnabled());
+ map.putBoolean("isZoomGesturesEnabled", settings.isZoomGesturesEnabled());
+
+ promise.resolve(map);
+ });
+ }
+
+ @ReactMethod
+ public void isMyLocationEnabled(final Promise promise) {
+ UiThreadUtil.runOnUiThread(
+ () -> {
+ if (mMapViewController == null) {
+ promise.reject(JsErrors.NO_MAP_ERROR_CODE, JsErrors.NO_MAP_ERROR_MESSAGE);
+ return;
+ }
+
+ promise.resolve(mMapViewController.getGoogleMap().isMyLocationEnabled());
+ });
+ }
+
+ @ReactMethod
+ public void moveCamera(ReadableMap map) {
+ UiThreadUtil.runOnUiThread(
+ () -> {
+ if (mMapViewController == null) {
+ return;
+ }
+
+ mMapViewController.moveCamera(map.toHashMap());
+ });
+ }
+
+ @ReactMethod
+ public void isAutoScreenAvailable(final Promise promise) {
+ promise.resolve(mMapViewController != null);
+ }
+
+ public void sendScreenState(boolean available) {
+ WritableNativeArray params = new WritableNativeArray();
+ params.pushBoolean(available);
+
+ sendCommandToReactNative("onAutoScreenAvailabilityChanged", params);
+ }
+
+ @Override
+ public void onCustomNavigationAutoEvent(String type, ReadableMap data) {
+ WritableMap map = Arguments.createMap();
+ map.putString("type", type);
+ map.putMap("data", data);
+
+ WritableNativeArray params = new WritableNativeArray();
+ params.pushMap(map);
+
+ sendCommandToReactNative("onCustomNavigationAutoEvent", params);
+ }
+
+ /** Send command to react native. */
+ private void sendCommandToReactNative(String functionName, NativeArray params) {
+ ReactContext reactContext = getReactApplicationContext();
+
+ if (reactContext != null) {
+ CatalystInstance catalystInstance = reactContext.getCatalystInstance();
+ catalystInstance.callFunction(Constants.NAV_AUTO_JAVASCRIPT_FLAG, functionName, params);
+ }
+ }
+}
diff --git a/android/src/main/java/com/google/android/react/navsdk/NavModule.java b/android/src/main/java/com/google/android/react/navsdk/NavModule.java
index 31ca456..bcb6791 100644
--- a/android/src/main/java/com/google/android/react/navsdk/NavModule.java
+++ b/android/src/main/java/com/google/android/react/navsdk/NavModule.java
@@ -19,6 +19,7 @@
import androidx.lifecycle.Observer;
import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.CatalystInstance;
+import com.facebook.react.bridge.NativeArray;
import com.facebook.react.bridge.Promise;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContext;
@@ -51,14 +52,17 @@
import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import java.util.concurrent.CopyOnWriteArrayList;
/**
* This exposes a series of methods that can be called diretly from the React Native code. They have
* been implemented using promises as it's not recommended for them to be synchronous.
*/
public class NavModule extends ReactContextBaseJavaModule implements INavigationCallback {
+ public static final String REACT_CLASS = "NavModule";
private static final String TAG = "NavModule";
private static NavModule instance;
+ private static ModuleReadyListener moduleReadyListener;
ReactApplicationContext reactContext;
private Navigator mNavigator;
@@ -66,19 +70,40 @@ public class NavModule extends ReactContextBaseJavaModule implements INavigation
private ListenableResultFuture pendingRoute;
private RoadSnappedLocationProvider mRoadSnappedLocationProvider;
private NavViewManager mNavViewManager;
+ private final CopyOnWriteArrayList mNavigationReadyListeners =
+ new CopyOnWriteArrayList<>();
+ private boolean mIsListeningRoadSnappedLocation = false;
private HashMap tocParamsMap;
+ public interface ModuleReadyListener {
+ void onModuleReady();
+ }
+
+ public interface NavigationReadyListener {
+ void onReady(boolean ready);
+ }
+
public NavModule(ReactApplicationContext reactContext, NavViewManager navViewManager) {
super(reactContext);
this.reactContext = reactContext;
mNavViewManager = navViewManager;
instance = this;
+ if (moduleReadyListener != null) {
+ moduleReadyListener.onModuleReady();
+ }
+ }
+
+ public static void setModuleReadyListener(ModuleReadyListener listener) {
+ moduleReadyListener = listener;
+ if (instance != null && moduleReadyListener != null) {
+ moduleReadyListener.onModuleReady();
+ }
}
public static synchronized NavModule getInstance() {
if (instance == null) {
- throw new IllegalStateException("NavModule instance is null");
+ throw new IllegalStateException(REACT_CLASS + " instance is null");
}
return instance;
}
@@ -89,7 +114,7 @@ public Navigator getNavigator() {
@Override
public String getName() {
- return "NavModule";
+ return REACT_CLASS;
}
@Override
@@ -102,19 +127,15 @@ public Map getConstants() {
new Navigator.ArrivalListener() {
@Override
public void onArrival(ArrivalEvent arrivalEvent) {
- if (reactContext != null) {
- CatalystInstance catalystInstance = reactContext.getCatalystInstance();
-
- WritableMap map = Arguments.createMap();
- map.putMap(
- "waypoint", ObjectTranslationUtil.getMapFromWaypoint(arrivalEvent.getWaypoint()));
- map.putBoolean("isFinalDestination", arrivalEvent.isFinalDestination());
+ WritableMap map = Arguments.createMap();
+ map.putMap(
+ "waypoint", ObjectTranslationUtil.getMapFromWaypoint(arrivalEvent.getWaypoint()));
+ map.putBoolean("isFinalDestination", arrivalEvent.isFinalDestination());
- WritableNativeArray params = new WritableNativeArray();
- params.pushMap(map);
+ WritableNativeArray params = new WritableNativeArray();
+ params.pushMap(map);
- catalystInstance.callFunction(Constants.NAV_JAVASCRIPT_FLAG, "onArrival", params);
- }
+ sendCommandToReactNative("onArrival", params);
}
};
@@ -122,23 +143,18 @@ public void onArrival(ArrivalEvent arrivalEvent) {
new LocationListener() {
@Override
public void onLocationChanged(final Location location) {
- CatalystInstance catalystInstance = reactContext.getCatalystInstance();
-
WritableNativeArray params = new WritableNativeArray();
params.pushMap(ObjectTranslationUtil.getMapFromLocation(location));
- catalystInstance.callFunction(Constants.NAV_JAVASCRIPT_FLAG, "onLocationChanged", params);
+ sendCommandToReactNative("onLocationChanged", params);
}
@Override
public void onRawLocationUpdate(final Location location) {
- CatalystInstance catalystInstance = reactContext.getCatalystInstance();
-
WritableNativeArray params = new WritableNativeArray();
params.pushMap(ObjectTranslationUtil.getMapFromLocation(location));
- catalystInstance.callFunction(
- Constants.NAV_JAVASCRIPT_FLAG, "onRawLocationChanged", params);
+ sendCommandToReactNative("onRawLocationChanged", params);
}
};
@@ -146,7 +162,7 @@ public void onRawLocationUpdate(final Location location) {
new Navigator.RouteChangedListener() {
@Override
public void onRouteChanged() {
- sendCommandToReactNative("onRouteChanged", null);
+ sendCommandToReactNative("onRouteChanged", (NativeArray) null);
}
};
@@ -154,7 +170,7 @@ public void onRouteChanged() {
new Navigator.TrafficUpdatedListener() {
@Override
public void onTrafficUpdated() {
- sendCommandToReactNative("onTrafficUpdated", null);
+ sendCommandToReactNative("onTrafficUpdated", (NativeArray) null);
}
};
@@ -162,7 +178,7 @@ public void onTrafficUpdated() {
new Navigator.ReroutingListener() {
@Override
public void onReroutingRequestedByOffRoute() {
- sendCommandToReactNative("onReroutingRequestedByOffRoute", null);
+ sendCommandToReactNative("onReroutingRequestedByOffRoute", (NativeArray) null);
}
};
@@ -170,13 +186,15 @@ public void onReroutingRequestedByOffRoute() {
new Navigator.RemainingTimeOrDistanceChangedListener() {
@Override
public void onRemainingTimeOrDistanceChanged() {
- sendCommandToReactNative("onRemainingTimeOrDistanceChanged", null);
+ sendCommandToReactNative("onRemainingTimeOrDistanceChanged", (NativeArray) null);
}
};
@ReactMethod
private void cleanup() {
- mRoadSnappedLocationProvider.removeLocationListener(mLocationListener);
+ if (mIsListeningRoadSnappedLocation) {
+ mRoadSnappedLocationProvider.removeLocationListener(mLocationListener);
+ }
mNavigator.unregisterServiceForNavUpdates();
mNavigator.removeArrivalListener(mArrivalListener);
mNavigator.removeReroutingListener(mReroutingListener);
@@ -186,6 +204,10 @@ private void cleanup() {
mRemainingTimeOrDistanceChangedListener);
mWaypoints.clear();
+ for (NavigationReadyListener listener : mNavigationReadyListeners) {
+ listener.onReady(false);
+ }
+
UiThreadUtil.runOnUiThread(
() -> {
mNavigator.clearDestinations();
@@ -215,10 +237,24 @@ public void initializeNavigator(@Nullable ReadableMap tocParams) {
private void onNavigationReady() {
mNavViewManager.applyStylingOptions();
- CatalystInstance catalystInstance = reactContext.getCatalystInstance();
- WritableNativeArray params = new WritableNativeArray();
+ sendCommandToReactNative("onNavigationReady", (NativeArray) null);
- catalystInstance.callFunction(Constants.NAV_JAVASCRIPT_FLAG, "onNavigationReady", params);
+ for (NavigationReadyListener listener : mNavigationReadyListeners) {
+ listener.onReady(true);
+ }
+ }
+
+ public void registerNavigationReadyListener(NavigationReadyListener listener) {
+ if (listener != null && !mNavigationReadyListeners.contains(listener)) {
+ mNavigationReadyListeners.add(listener);
+ if (mNavigator != null) {
+ listener.onReady(true);
+ }
+ }
+ }
+
+ public void unRegisterNavigationReadyListener(NavigationReadyListener listener) {
+ mNavigationReadyListeners.remove(listener);
}
private void onNavigationInitError(int errorCode) {
@@ -445,7 +481,7 @@ public void startGuidance() {
}
mNavigator.startGuidance();
- sendCommandToReactNative("onStartGuidance", null);
+ sendCommandToReactNative("onStartGuidance", (NativeArray) null);
}
@ReactMethod
@@ -598,17 +634,22 @@ public void getTraveledPath(final Promise promise) {
promise.resolve(arr);
}
- private void sendCommandToReactNative(String functionName, String args) {
+ /** Send command to react native with string param. */
+ private void sendCommandToReactNative(String functionName, String stringParam) {
+ WritableNativeArray params = new WritableNativeArray();
+
+ if (stringParam != null) {
+ params.pushString("" + stringParam);
+ }
+ sendCommandToReactNative(functionName, params);
+ }
+
+ /** Send command to react native. */
+ private void sendCommandToReactNative(String functionName, NativeArray params) {
ReactContext reactContext = getReactApplicationContext();
if (reactContext != null) {
CatalystInstance catalystInstance = reactContext.getCatalystInstance();
- WritableNativeArray params = new WritableNativeArray();
-
- if (args != null) {
- params.pushString("" + args);
- }
-
catalystInstance.callFunction(Constants.NAV_JAVASCRIPT_FLAG, functionName, params);
}
}
@@ -679,10 +720,12 @@ public void resetTermsAccepted() {
@ReactMethod
public void startUpdatingLocation() {
mRoadSnappedLocationProvider.addLocationListener(mLocationListener);
+ mIsListeningRoadSnappedLocation = true;
}
@ReactMethod
public void stopUpdatingLocation() {
+ mIsListeningRoadSnappedLocation = false;
mRoadSnappedLocationProvider.removeLocationListener(mLocationListener);
}
@@ -690,8 +733,6 @@ private void showNavInfo(NavInfo navInfo) {
if (navInfo == null || reactContext == null) {
return;
}
- CatalystInstance catalystInstance = reactContext.getCatalystInstance();
-
WritableMap map = Arguments.createMap();
map.putInt("navState", navInfo.getNavState());
@@ -721,7 +762,7 @@ private void showNavInfo(NavInfo navInfo) {
WritableNativeArray params = new WritableNativeArray();
params.pushMap(map);
- catalystInstance.callFunction(Constants.NAV_JAVASCRIPT_FLAG, "onTurnByTurn", params);
+ sendCommandToReactNative("onTurnByTurn", params);
}
@Override
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 c10f9f6..a1e7bc6 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
@@ -13,68 +13,40 @@
*/
package com.google.android.react.navsdk;
-import android.Manifest.permission;
import android.annotation.SuppressLint;
-import android.content.pm.PackageManager;
-import android.graphics.Color;
import android.os.Bundle;
import android.view.View;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
-import androidx.core.app.ActivityCompat;
import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.ReactApplicationContext;
-import com.facebook.react.bridge.UiThreadUtil;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.uimanager.UIManagerHelper;
import com.facebook.react.uimanager.events.Event;
import com.facebook.react.uimanager.events.EventDispatcher;
-import com.google.android.gms.maps.CameraUpdateFactory;
import com.google.android.gms.maps.GoogleMap;
import com.google.android.gms.maps.OnMapReadyCallback;
-import com.google.android.gms.maps.model.BitmapDescriptor;
-import com.google.android.gms.maps.model.BitmapDescriptorFactory;
-import com.google.android.gms.maps.model.CameraPosition;
import com.google.android.gms.maps.model.Circle;
-import com.google.android.gms.maps.model.CircleOptions;
import com.google.android.gms.maps.model.GroundOverlay;
-import com.google.android.gms.maps.model.GroundOverlayOptions;
import com.google.android.gms.maps.model.LatLng;
-import com.google.android.gms.maps.model.MapStyleOptions;
import com.google.android.gms.maps.model.Marker;
-import com.google.android.gms.maps.model.MarkerOptions;
import com.google.android.gms.maps.model.Polygon;
-import com.google.android.gms.maps.model.PolygonOptions;
import com.google.android.gms.maps.model.Polyline;
-import com.google.android.gms.maps.model.PolylineOptions;
import com.google.android.libraries.navigation.NavigationView;
import com.google.android.libraries.navigation.StylingOptions;
import com.google.android.libraries.navigation.SupportNavigationFragment;
-import java.io.BufferedReader;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.InputStreamReader;
-import java.net.HttpURLConnection;
-import java.net.URL;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Map;
-import java.util.concurrent.Executors;
/**
* A fragment that displays a navigation view with a Google Map using SupportNavigationFragment.
* This fragment's lifecycle is managed by NavViewManager.
*/
-public class NavViewFragment extends SupportNavigationFragment implements INavViewFragment {
+public class NavViewFragment extends SupportNavigationFragment
+ implements INavViewFragment, INavigationViewCallback {
private static final String TAG = "NavViewFragment";
+ private MapViewController mMapViewController;
private GoogleMap mGoogleMap;
private StylingOptions mStylingOptions;
- private List markerList = new ArrayList<>();
- private List polylineList = new ArrayList<>();
- private List polygonList = new ArrayList<>();
- private List groundOverlayList = new ArrayList<>();
- private List circleList = new ArrayList<>();
private int viewTag; // React native view tag.
private ReactApplicationContext reactContext;
@@ -105,80 +77,26 @@ public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceStat
public void onMapReady(GoogleMap googleMap) {
mGoogleMap = googleMap;
+ mMapViewController = new MapViewController();
+ mMapViewController.initialize(googleMap, () -> requireActivity());
+
+ // Setup map listeners with the provided callback
+ mMapViewController.setupMapListeners(NavViewFragment.this);
+
emitEvent("onMapReady", null);
setNavigationUiEnabled(NavModule.getInstance().getNavigator() != null);
-
- mGoogleMap.setOnMarkerClickListener(
- new GoogleMap.OnMarkerClickListener() {
- @Override
- public boolean onMarkerClick(Marker marker) {
- emitEvent("onMarkerClick", ObjectTranslationUtil.getMapFromMarker(marker));
- return false;
- }
- });
- mGoogleMap.setOnPolylineClickListener(
- new GoogleMap.OnPolylineClickListener() {
- @Override
- public void onPolylineClick(Polyline polyline) {
- emitEvent(
- "onPolylineClick", ObjectTranslationUtil.getMapFromPolyline(polyline));
- }
- });
- mGoogleMap.setOnPolygonClickListener(
- new GoogleMap.OnPolygonClickListener() {
- @Override
- public void onPolygonClick(Polygon polygon) {
- emitEvent("onPolygonClick", ObjectTranslationUtil.getMapFromPolygon(polygon));
- }
- });
- mGoogleMap.setOnCircleClickListener(
- new GoogleMap.OnCircleClickListener() {
- @Override
- public void onCircleClick(Circle circle) {
- emitEvent("onCircleClick", ObjectTranslationUtil.getMapFromCircle(circle));
- }
- });
- mGoogleMap.setOnGroundOverlayClickListener(
- new GoogleMap.OnGroundOverlayClickListener() {
- @Override
- public void onGroundOverlayClick(GroundOverlay groundOverlay) {
- emitEvent(
- "onGroundOverlayClick",
- ObjectTranslationUtil.getMapFromGroundOverlay(groundOverlay));
- }
- });
-
- mGoogleMap.setOnInfoWindowClickListener(
- new GoogleMap.OnInfoWindowClickListener() {
- @Override
- public void onInfoWindowClick(Marker marker) {
- emitEvent(
- "onMarkerInfoWindowTapped", ObjectTranslationUtil.getMapFromMarker(marker));
- }
- });
-
- mGoogleMap.setOnMapClickListener(
- new GoogleMap.OnMapClickListener() {
- @Override
- public void onMapClick(LatLng latLng) {
- emitEvent("onMapClick", ObjectTranslationUtil.getMapFromLatLng(latLng));
- }
- });
+ addOnRecenterButtonClickedListener(onRecenterButtonClickedListener);
}
});
+ }
- Executors.newSingleThreadExecutor()
- .execute(
- () -> {
- requireActivity()
- .runOnUiThread(
- (Runnable)
- () -> {
- super.addOnRecenterButtonClickedListener(
- onRecenterButtonClickedListener);
- });
- });
+ public MapViewController getMapController() {
+ return mMapViewController;
+ }
+
+ public void setMapStyle(String url) {
+ mMapViewController.setMapStyle(url);
}
public void applyStylingOptions() {
@@ -187,490 +105,57 @@ public void applyStylingOptions() {
}
}
- public void setStylingOptions(Map stylingOptions) {
- mStylingOptions = new StylingOptionsBuilder.Builder(stylingOptions).build();
- }
-
- @SuppressLint("MissingPermission")
- public void setFollowingPerspective(int jsValue) {
- if (mGoogleMap == null) {
- return;
- }
-
- mGoogleMap.followMyLocation(EnumTranslationUtil.getCameraPerspectiveFromJsValue(jsValue));
+ public void setStylingOptions(StylingOptions stylingOptions) {
+ mStylingOptions = stylingOptions;
}
public void setNightModeOption(int jsValue) {
super.setForceNightMode(EnumTranslationUtil.getForceNightModeFromJsValue(jsValue));
}
- public void setMapType(int jsValue) {
- if (mGoogleMap == null) {
- return;
- }
-
- mGoogleMap.setMapType(EnumTranslationUtil.getMapTypeFromJsValue(jsValue));
- }
-
- public void clearMapView() {
- if (mGoogleMap == null) {
- return;
- }
-
- mGoogleMap.clear();
- }
-
- public void resetMinMaxZoomLevel() {
- if (mGoogleMap == null) {
- return;
- }
-
- mGoogleMap.resetMinMaxZoomPreference();
- }
-
- public void animateCamera(Map map) {
- if (mGoogleMap != null) {
- int zoom = CollectionUtil.getInt("zoom", map, 0);
- int tilt = CollectionUtil.getInt("tilt", map, 0);
- int bearing = CollectionUtil.getInt("bearing", map, 0);
- int animationDuration = CollectionUtil.getInt("duration", map, 0);
-
- CameraPosition cameraPosition =
- new CameraPosition.Builder()
- .target(
- ObjectTranslationUtil.getLatLngFromMap(
- (Map) map.get("target"))) // Set the target location
- .zoom(zoom) // Set the desired zoom level
- .tilt(tilt) // Set the desired tilt angle (0 for straight down, 90 for straight up)
- .bearing(bearing) // Set the desired bearing (rotation angle in degrees)
- .build();
-
- mGoogleMap.animateCamera(
- CameraUpdateFactory.newCameraPosition(cameraPosition), animationDuration, null);
- }
- }
-
- public Circle addCircle(Map optionsMap) {
- if (mGoogleMap == null) {
- return null;
- }
-
- CircleOptions options = new CircleOptions();
-
- float strokeWidth =
- Double.valueOf(CollectionUtil.getDouble("strokeWidth", optionsMap, 0)).floatValue();
- options.strokeWidth(strokeWidth);
-
- double radius = CollectionUtil.getDouble("radius", optionsMap, 0.0);
- options.radius(radius);
-
- boolean visible = CollectionUtil.getBool("visible", optionsMap, true);
- options.visible(visible);
-
- options.center(ObjectTranslationUtil.getLatLngFromMap((Map) optionsMap.get("center")));
-
- boolean clickable = CollectionUtil.getBool("clickable", optionsMap, false);
- options.clickable(clickable);
-
- String strokeColor = CollectionUtil.getString("strokeColor", optionsMap);
- if (strokeColor != null) {
- options.strokeColor(Color.parseColor(strokeColor));
- }
-
- String fillColor = CollectionUtil.getString("fillColor", optionsMap);
- if (fillColor != null) {
- options.fillColor(Color.parseColor(fillColor));
- }
-
- Circle circle = mGoogleMap.addCircle(options);
- circleList.add(circle);
-
- return circle;
- }
-
- public Marker addMarker(Map optionsMap) {
- if (mGoogleMap == null) {
- return null;
- }
-
- String imagePath = CollectionUtil.getString("imgPath", optionsMap);
- String title = CollectionUtil.getString("title", optionsMap);
- String snippet = CollectionUtil.getString("snippet", optionsMap);
- float alpha = Double.valueOf(CollectionUtil.getDouble("alpha", optionsMap, 1)).floatValue();
- float rotation =
- Double.valueOf(CollectionUtil.getDouble("rotation", optionsMap, 0)).floatValue();
- boolean draggable = CollectionUtil.getBool("draggable", optionsMap, false);
- boolean flat = CollectionUtil.getBool("flat", optionsMap, false);
- boolean visible = CollectionUtil.getBool("visible", optionsMap, true);
-
- MarkerOptions options = new MarkerOptions();
- if (imagePath != null && !imagePath.isEmpty()) {
- BitmapDescriptor icon = BitmapDescriptorFactory.fromPath(imagePath);
- options.icon(icon);
- }
-
- options.position(ObjectTranslationUtil.getLatLngFromMap((Map) optionsMap.get("position")));
-
- if (title != null) {
- options.title(title);
- }
-
- if (snippet != null) {
- options.snippet(snippet);
- }
-
- options.flat(flat);
- options.alpha(alpha);
- options.rotation(rotation);
- options.draggable(draggable);
- options.visible(visible);
-
- Marker marker = mGoogleMap.addMarker(options);
-
- markerList.add(marker);
-
- return marker;
- }
-
- public Polyline addPolyline(Map optionsMap) {
- if (mGoogleMap == null) {
- return null;
- }
-
- float width = Double.valueOf(CollectionUtil.getDouble("width", optionsMap, 0)).floatValue();
- boolean clickable = CollectionUtil.getBool("clickable", optionsMap, false);
- boolean visible = CollectionUtil.getBool("visible", optionsMap, true);
-
- ArrayList latLngArr = (ArrayList) optionsMap.get("points");
-
- PolylineOptions options = new PolylineOptions();
- for (int i = 0; i < latLngArr.size(); i++) {
- Map latLngMap = (Map) latLngArr.get(i);
- LatLng latLng = createLatLng(latLngMap);
- options.add(latLng);
- }
-
- String color = CollectionUtil.getString("color", optionsMap);
- if (color != null) {
- options.color(Color.parseColor(color));
- }
-
- options.width(width);
- options.clickable(clickable);
- options.visible(visible);
-
- Polyline polyline = mGoogleMap.addPolyline(options);
- polylineList.add(polyline);
-
- return polyline;
- }
-
- public Polygon addPolygon(Map optionsMap) {
- if (mGoogleMap == null) {
- return null;
- }
-
- String strokeColor = CollectionUtil.getString("strokeColor", optionsMap);
- String fillColor = CollectionUtil.getString("fillColor", optionsMap);
- float strokeWidth =
- Double.valueOf(CollectionUtil.getDouble("strokeWidth", optionsMap, 0)).floatValue();
- boolean clickable = CollectionUtil.getBool("clickable", optionsMap, false);
- boolean geodesic = CollectionUtil.getBool("geodesic", optionsMap, false);
- boolean visible = CollectionUtil.getBool("visible", optionsMap, true);
-
- ArrayList latLngArr = (ArrayList) optionsMap.get("points");
-
- PolygonOptions options = new PolygonOptions();
- for (int i = 0; i < latLngArr.size(); i++) {
- Map latLngMap = (Map) latLngArr.get(i);
- LatLng latLng = createLatLng(latLngMap);
- options.add(latLng);
- }
-
- ArrayList holesArr = (ArrayList) optionsMap.get("holes");
-
- for (int i = 0; i < holesArr.size(); i++) {
- ArrayList arr = (ArrayList) holesArr.get(i);
-
- List listHoles = new ArrayList<>();
-
- for (int j = 0; j < arr.size(); j++) {
- Map latLngMap = (Map) arr.get(j);
- LatLng latLng = createLatLng(latLngMap);
-
- listHoles.add(latLng);
- }
-
- options.addHole(listHoles);
- }
-
- if (fillColor != null) {
- options.fillColor(Color.parseColor(fillColor));
- }
-
- if (strokeColor != null) {
- options.strokeColor(Color.parseColor(strokeColor));
- }
-
- options.strokeWidth(strokeWidth);
- options.visible(visible);
- options.geodesic(geodesic);
- options.clickable(clickable);
-
- Polygon polygon = mGoogleMap.addPolygon(options);
- polygonList.add(polygon);
-
- return polygon;
- }
-
- public void removeMarker(String id) {
- UiThreadUtil.runOnUiThread(
- () -> {
- for (Marker m : markerList) {
- if (m.getId().equals(id)) {
- m.remove();
- markerList.remove(m);
- return;
- }
- }
- });
- }
-
- public void removePolyline(String id) {
- for (Polyline p : polylineList) {
- if (p.getId().equals(id)) {
- p.remove();
- polylineList.remove(p);
- return;
- }
- }
- }
-
- public void removePolygon(String id) {
- for (Polygon p : polygonList) {
- if (p.getId().equals(id)) {
- p.remove();
- polygonList.remove(p);
- return;
- }
- }
- }
-
- public void removeCircle(String id) {
- for (Circle c : circleList) {
- if (c.getId().equals(id)) {
- c.remove();
- circleList.remove(c);
- return;
- }
- }
- }
-
- public void removeGroundOverlay(String id) {
- for (GroundOverlay g : groundOverlayList) {
- if (g.getId().equals(id)) {
- g.remove();
- groundOverlayList.remove(g);
- return;
- }
- }
- }
-
- private LatLng createLatLng(Map map) {
- Double lat = null;
- Double lng = null;
- if (map.containsKey("lat") && map.containsKey("lng")) {
- if (map.get("lat") != null) lat = Double.parseDouble(map.get("lat").toString());
- if (map.get("lng") != null) lng = Double.parseDouble(map.get("lng").toString());
- }
-
- return new LatLng(lat, lng);
- }
-
- public GroundOverlay addGroundOverlay(Map map) {
- if (mGoogleMap == null) {
- return null;
- }
-
- String imagePath = CollectionUtil.getString("imgPath", map);
- float width = Double.valueOf(CollectionUtil.getDouble("width", map, 0)).floatValue();
- float height = Double.valueOf(CollectionUtil.getDouble("height", map, 0)).floatValue();
- float transparency =
- Double.valueOf(CollectionUtil.getDouble("transparency", map, 0)).floatValue();
- boolean clickable = CollectionUtil.getBool("clickable", map, false);
- boolean visible = CollectionUtil.getBool("visible", map, true);
-
- Double lat = null;
- Double lng = null;
- if (map.containsKey("location")) {
- Map latlng = (Map) map.get("location");
- if (latlng.get("lat") != null) lat = Double.parseDouble(latlng.get("lat").toString());
- if (latlng.get("lng") != null) lng = Double.parseDouble(latlng.get("lng").toString());
- }
-
- GroundOverlayOptions options = new GroundOverlayOptions();
- if (imagePath != null && !imagePath.isEmpty()) {
- BitmapDescriptor bitmapDescriptor = BitmapDescriptorFactory.fromPath(imagePath);
- options.image(bitmapDescriptor);
- }
- options.position(new LatLng(lat, lng), width, height);
- options.transparency(transparency);
- options.clickable(clickable);
- options.visible(visible);
- GroundOverlay groundOverlay = mGoogleMap.addGroundOverlay(options);
- groundOverlayList.add(groundOverlay);
- return groundOverlay;
- }
-
- public void setMapStyle(String url) {
- Executors.newSingleThreadExecutor()
- .execute(
- () -> {
- try {
- style = fetchJsonFromUrl(url);
- } catch (IOException e) {
- throw new RuntimeException(e);
- }
- requireActivity()
- .runOnUiThread(
- (Runnable)
- () -> {
- MapStyleOptions options = new MapStyleOptions(style);
- mGoogleMap.setMapStyle(options);
- });
- });
- }
-
- public String fetchJsonFromUrl(String urlString) throws IOException {
- URL url = new URL(urlString);
- HttpURLConnection connection = (HttpURLConnection) url.openConnection();
- connection.setRequestMethod("GET");
-
- int responseCode = connection.getResponseCode();
- if (responseCode == HttpURLConnection.HTTP_OK) {
- InputStream inputStream = connection.getInputStream();
- BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
- StringBuilder stringBuilder = new StringBuilder();
- String line;
- while ((line = reader.readLine()) != null) {
- stringBuilder.append(line);
- }
- reader.close();
- inputStream.close();
- return stringBuilder.toString();
- } else {
- // Handle error response
- throw new IOException("Error response: " + responseCode);
- }
- }
-
- /** Moves the position of the camera to hover over Melbourne. */
- public void moveCamera(Map map) {
- LatLng latLng = ObjectTranslationUtil.getLatLngFromMap((Map) map.get("target"));
-
- float zoom = (float) CollectionUtil.getDouble("zoom", map, 0);
- float tilt = (float) CollectionUtil.getDouble("tilt", map, 0);
- float bearing = (float) CollectionUtil.getDouble("bearing", map, 0);
-
- CameraPosition cameraPosition =
- CameraPosition.builder().target(latLng).zoom(zoom).tilt(tilt).bearing(bearing).build();
-
- mGoogleMap.moveCamera(CameraUpdateFactory.newCameraPosition(cameraPosition));
- }
-
- public void setZoomLevel(int level) {
- if (mGoogleMap != null) {
- mGoogleMap.animateCamera(CameraUpdateFactory.zoomTo(level));
- }
- }
-
- public void setIndoorEnabled(boolean isOn) {
- if (mGoogleMap != null) {
- mGoogleMap.setIndoorEnabled(isOn);
- }
- }
-
- public void setTrafficEnabled(boolean isOn) {
- if (mGoogleMap != null) {
- mGoogleMap.setTrafficEnabled(isOn);
- }
- }
-
- public void setCompassEnabled(boolean isOn) {
- if (mGoogleMap != null) {
- mGoogleMap.getUiSettings().setCompassEnabled(isOn);
- }
- }
-
- public void setRotateGesturesEnabled(boolean isOn) {
- if (mGoogleMap != null) {
- mGoogleMap.getUiSettings().setRotateGesturesEnabled(isOn);
- }
- }
-
- public void setScrollGesturesEnabled(boolean isOn) {
- if (mGoogleMap != null) {
- mGoogleMap.getUiSettings().setScrollGesturesEnabled(isOn);
- }
+ @Override
+ public void onMapReady() {
+ emitEvent("onMapReady", null);
}
- public void setScrollGesturesEnabledDuringRotateOrZoom(boolean isOn) {
- if (mGoogleMap != null) {
- mGoogleMap.getUiSettings().setScrollGesturesEnabledDuringRotateOrZoom(isOn);
- }
+ @Override
+ public void onRecenterButtonClick() {
+ emitEvent("onRecenterButtonClick", null);
}
- public void setTiltGesturesEnabled(boolean isOn) {
- if (mGoogleMap != null) {
- mGoogleMap.getUiSettings().setTiltGesturesEnabled(isOn);
- }
+ @Override
+ public void onMarkerClick(Marker marker) {
+ emitEvent("onMapReady", ObjectTranslationUtil.getMapFromMarker(marker));
}
- public void setZoomControlsEnabled(boolean isOn) {
- if (mGoogleMap != null) {
- mGoogleMap.getUiSettings().setZoomControlsEnabled(isOn);
- }
+ @Override
+ public void onPolylineClick(Polyline polyline) {
+ emitEvent("onPolylineClick", ObjectTranslationUtil.getMapFromPolyline(polyline));
}
- public void setZoomGesturesEnabled(boolean isOn) {
- if (mGoogleMap != null) {
- mGoogleMap.getUiSettings().setZoomGesturesEnabled(isOn);
- }
+ @Override
+ public void onPolygonClick(Polygon polygon) {
+ emitEvent("onPolygonClick", ObjectTranslationUtil.getMapFromPolygon(polygon));
}
- public void setBuildingsEnabled(boolean isOn) {
- if (mGoogleMap != null) {
- mGoogleMap.setBuildingsEnabled(isOn);
- }
+ @Override
+ public void onCircleClick(Circle circle) {
+ emitEvent("onCircleClick", ObjectTranslationUtil.getMapFromCircle(circle));
}
- public void setMyLocationEnabled(boolean isOn) {
- if (mGoogleMap != null) {
- if (ActivityCompat.checkSelfPermission(getActivity(), permission.ACCESS_FINE_LOCATION)
- == PackageManager.PERMISSION_GRANTED
- && ActivityCompat.checkSelfPermission(getActivity(), permission.ACCESS_COARSE_LOCATION)
- == PackageManager.PERMISSION_GRANTED) {
- mGoogleMap.setMyLocationEnabled(isOn);
- }
- }
+ @Override
+ public void onGroundOverlayClick(GroundOverlay groundOverlay) {
+ emitEvent("onGroundOverlayClick", ObjectTranslationUtil.getMapFromGroundOverlay(groundOverlay));
}
- public void setMapToolbarEnabled(boolean isOn) {
- if (mGoogleMap != null) {
- mGoogleMap.getUiSettings().setMapToolbarEnabled(isOn);
- }
+ @Override
+ public void onMarkerInfoWindowTapped(Marker marker) {
+ emitEvent("onInfoWindowClick", ObjectTranslationUtil.getMapFromMarker(marker));
}
- /** Toggles whether the location marker is enabled. */
- public void setMyLocationButtonEnabled(boolean isOn) {
- if (mGoogleMap == null) {
- return;
- }
-
- UiThreadUtil.runOnUiThread(
- () -> {
- mGoogleMap.getUiSettings().setMyLocationButtonEnabled(isOn);
- });
+ @Override
+ public void onMapClick(LatLng latLng) {
+ emitEvent("onMapClick", ObjectTranslationUtil.getMapFromLatLng(latLng));
}
@Override
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 25d7864..cdd7f03 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
@@ -188,7 +188,7 @@ public void receiveCommand(
}
break;
case MOVE_CAMERA:
- getFragmentForRoot(root).moveCamera(args.getMap(0).toHashMap());
+ getFragmentForRoot(root).getMapController().moveCamera(args.getMap(0).toHashMap());
break;
case SET_TRIP_PROGRESS_BAR_ENABLED:
getNavFragmentForRoot(root).setTripProgressBarEnabled(args.getBoolean(0));
@@ -197,10 +197,10 @@ public void receiveCommand(
getNavFragmentForRoot(root).setNavigationUiEnabled(args.getBoolean(0));
break;
case SET_FOLLOWING_PERSPECTIVE:
- getNavFragmentForRoot(root).setFollowingPerspective(args.getInt(0));
+ getNavFragmentForRoot(root).getMapController().setFollowingPerspective(args.getInt(0));
break;
case SET_NIGHT_MODE:
- getFragmentForRoot(root).setNightModeOption(args.getInt(0));
+ getNavFragmentForRoot(root).setNightModeOption(args.getInt(0));
break;
case SET_SPEEDOMETER_ENABLED:
getNavFragmentForRoot(root).setSpeedometerEnabled(args.getBoolean(0));
@@ -210,61 +210,63 @@ public void receiveCommand(
break;
case SET_ZOOM_LEVEL:
int level = args.getInt(0);
- getFragmentForRoot(root).setZoomLevel(level);
+ getFragmentForRoot(root).getMapController().setZoomLevel(level);
break;
case SET_INDOOR_ENABLED:
- getFragmentForRoot(root).setIndoorEnabled(args.getBoolean(0));
+ getFragmentForRoot(root).getMapController().setIndoorEnabled(args.getBoolean(0));
break;
case SET_TRAFFIC_ENABLED:
- getFragmentForRoot(root).setTrafficEnabled(args.getBoolean(0));
+ getFragmentForRoot(root).getMapController().setTrafficEnabled(args.getBoolean(0));
break;
case SET_COMPASS_ENABLED:
- getFragmentForRoot(root).setCompassEnabled(args.getBoolean(0));
+ getFragmentForRoot(root).getMapController().setCompassEnabled(args.getBoolean(0));
break;
case SET_MY_LOCATION_BUTTON_ENABLED:
- getFragmentForRoot(root).setMyLocationButtonEnabled(args.getBoolean(0));
+ getFragmentForRoot(root).getMapController().setCompassEnabled(args.getBoolean(0));
break;
case SET_MY_LOCATION_ENABLED:
- getFragmentForRoot(root).setMyLocationEnabled(args.getBoolean(0));
+ getFragmentForRoot(root).getMapController().setMyLocationEnabled(args.getBoolean(0));
break;
case SET_ROTATE_GESTURES_ENABLED:
- getFragmentForRoot(root).setRotateGesturesEnabled(args.getBoolean(0));
+ getFragmentForRoot(root).getMapController().setRotateGesturesEnabled(args.getBoolean(0));
break;
case SET_SCROLL_GESTURES_ENABLED:
- getFragmentForRoot(root).setScrollGesturesEnabled(args.getBoolean(0));
+ getFragmentForRoot(root).getMapController().setScrollGesturesEnabled(args.getBoolean(0));
break;
case SET_SCROLL_GESTURES_ENABLED_DURING_ROTATE_OR_ZOOM:
- getFragmentForRoot(root).setScrollGesturesEnabledDuringRotateOrZoom(args.getBoolean(0));
+ getFragmentForRoot(root)
+ .getMapController()
+ .setScrollGesturesEnabledDuringRotateOrZoom(args.getBoolean(0));
break;
case SET_TILT_GESTURES_ENABLED:
- getFragmentForRoot(root).setTiltGesturesEnabled(args.getBoolean(0));
+ getFragmentForRoot(root).getMapController().setTiltGesturesEnabled(args.getBoolean(0));
break;
case SET_ZOOM_CONTROLS_ENABLED:
- getFragmentForRoot(root).setZoomControlsEnabled(args.getBoolean(0));
+ getFragmentForRoot(root).getMapController().setZoomControlsEnabled(args.getBoolean(0));
break;
case SET_ZOOM_GESTURES_ENABLED:
- getFragmentForRoot(root).setZoomGesturesEnabled(args.getBoolean(0));
+ getFragmentForRoot(root).getMapController().setZoomGesturesEnabled(args.getBoolean(0));
break;
case SET_BUILDINGS_ENABLED:
- getFragmentForRoot(root).setBuildingsEnabled(args.getBoolean(0));
+ getFragmentForRoot(root).getMapController().setBuildingsEnabled(args.getBoolean(0));
break;
case SET_MAP_TYPE:
- getFragmentForRoot(root).setMapType(args.getInt(0));
+ getFragmentForRoot(root).getMapController().setMapType(args.getInt(0));
break;
case SET_MAP_TOOLBAR_ENABLED:
- getFragmentForRoot(root).setMapToolbarEnabled(args.getBoolean(0));
+ getFragmentForRoot(root).getMapController().setMapToolbarEnabled(args.getBoolean(0));
break;
case CLEAR_MAP_VIEW:
- getFragmentForRoot(root).clearMapView();
+ getFragmentForRoot(root).getMapController().clearMapView();
break;
case RESET_MIN_MAX_ZOOM_LEVEL:
- getFragmentForRoot(root).resetMinMaxZoomLevel();
+ getFragmentForRoot(root).getMapController().resetMinMaxZoomLevel();
break;
case SET_MAP_STYLE:
getFragmentForRoot(root).setMapStyle(args.getString(0));
break;
case ANIMATE_CAMERA:
- getFragmentForRoot(root).animateCamera(args.getMap(0).toHashMap());
+ getFragmentForRoot(root).getMapController().animateCamera(args.getMap(0).toHashMap());
break;
case SET_TRAFFIC_INCIDENT_CARDS_ENABLED:
getNavFragmentForRoot(root).setTrafficIncidentCardsEnabled(args.getBoolean(0));
@@ -282,19 +284,19 @@ public void receiveCommand(
getNavFragmentForRoot(root).showRouteOverview();
break;
case REMOVE_MARKER:
- getFragmentForRoot(root).removeMarker(args.getString(0));
+ getFragmentForRoot(root).getMapController().removeMarker(args.getString(0));
break;
case REMOVE_POLYLINE:
- getFragmentForRoot(root).removePolyline(args.getString(0));
+ getFragmentForRoot(root).getMapController().removePolyline(args.getString(0));
break;
case REMOVE_POLYGON:
- getFragmentForRoot(root).removePolygon(args.getString(0));
+ getFragmentForRoot(root).getMapController().removePolygon(args.getString(0));
break;
case REMOVE_CIRCLE:
- getFragmentForRoot(root).removeCircle(args.getString(0));
+ getFragmentForRoot(root).getMapController().removeCircle(args.getString(0));
break;
case REMOVE_GROUND_OVERLAY:
- getFragmentForRoot(root).removeGroundOverlay(args.getString(0));
+ getFragmentForRoot(root).getMapController().removeGroundOverlay(args.getString(0));
break;
}
}
@@ -343,7 +345,7 @@ public void createFragment(
fragment = mapFragment;
if (stylingOptions != null) {
- mapFragment.setStylingOptions(stylingOptions);
+ mapFragment.setStylingOptions(new StylingOptionsBuilder.Builder(stylingOptions).build());
}
} else {
NavViewFragment navFragment = new NavViewFragment(reactContext, root.getId());
@@ -351,7 +353,7 @@ public void createFragment(
fragment = navFragment;
if (stylingOptions != null) {
- navFragment.setStylingOptions(stylingOptions);
+ navFragment.setStylingOptions(new StylingOptionsBuilder.Builder(stylingOptions).build());
}
}
activity
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 e58d953..428599a 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
@@ -164,6 +164,7 @@ public void addMarker(int viewId, ReadableMap markerOptionsMap, final Promise pr
Marker marker =
mNavViewManager
.getFragmentForViewId(viewId)
+ .getMapController()
.addMarker(markerOptionsMap.toHashMap());
promise.resolve(ObjectTranslationUtil.getMapFromMarker(marker));
@@ -182,6 +183,7 @@ public void addPolyline(int viewId, ReadableMap polylineOptionsMap, final Promis
Polyline polyline =
mNavViewManager
.getFragmentForViewId(viewId)
+ .getMapController()
.addPolyline(polylineOptionsMap.toHashMap());
promise.resolve(ObjectTranslationUtil.getMapFromPolyline(polyline));
@@ -199,6 +201,7 @@ public void addPolygon(int viewId, ReadableMap polygonOptionsMap, final Promise
Polygon polygon =
mNavViewManager
.getFragmentForViewId(viewId)
+ .getMapController()
.addPolygon(polygonOptionsMap.toHashMap());
promise.resolve(ObjectTranslationUtil.getMapFromPolygon(polygon));
@@ -214,7 +217,10 @@ public void addCircle(int viewId, ReadableMap circleOptionsMap, final Promise pr
return;
}
Circle circle =
- mNavViewManager.getFragmentForViewId(viewId).addCircle(circleOptionsMap.toHashMap());
+ mNavViewManager
+ .getFragmentForViewId(viewId)
+ .getMapController()
+ .addCircle(circleOptionsMap.toHashMap());
promise.resolve(ObjectTranslationUtil.getMapFromCircle(circle));
});
@@ -231,6 +237,7 @@ public void addGroundOverlay(int viewId, ReadableMap overlayOptionsMap, final Pr
GroundOverlay overlay =
mNavViewManager
.getFragmentForViewId(viewId)
+ .getMapController()
.addGroundOverlay(overlayOptionsMap.toHashMap());
promise.resolve(ObjectTranslationUtil.getMapFromGroundOverlay(overlay));
diff --git a/android/src/main/java/com/google/android/react/navsdk/Package.java b/android/src/main/java/com/google/android/react/navsdk/Package.java
index dbb40b0..e7a9a2b 100644
--- a/android/src/main/java/com/google/android/react/navsdk/Package.java
+++ b/android/src/main/java/com/google/android/react/navsdk/Package.java
@@ -35,6 +35,7 @@ public List createNativeModules(ReactApplicationContext reactConte
List modules = new ArrayList<>();
NavViewManager viewManager = NavViewManager.getInstance(reactContext);
modules.add(new NavModule(reactContext, viewManager));
+ modules.add(new NavAutoModule(reactContext));
modules.add(new NavViewModule(reactContext, viewManager));
return modules;
diff --git a/example/README.md b/example/README.md
index 338d601..50c27d0 100644
--- a/example/README.md
+++ b/example/README.md
@@ -6,26 +6,32 @@ This contains a sample application to showcase the functionality of the NavSDK l
## Setup
-1. First, make sure you go through the setup from the main [README](../README.md).
+First, make sure you go through the setup from the main [README](../README.md).
-2. Open the example/android folder in Android Studio and add your api key in local.properties by adding a line like this:
+### Android
+
+1. Open the example/android folder in Android Studio and add your api key in local.properties by adding a line like this:
* ```MAPS_API_KEY=YOUR_API_KEY``` - make sure that this key is pointing to a Google Cloud project which had Nav SDK enabled.
* To enable Nav SDK in your project follow these guides:
* **Android**: https://developers.google.com/maps/documentation/navigation/android-sdk/set-up-project
* **iOS**: https://developers.google.com/maps/documentation/navigation/ios-sdk/config
-3. Using your preferred terminal, go to example/ios folder and run the command below.
+### iOS
+
+1. Using your preferred terminal, go to example/ios folder and run the command below.
- `pod install`
+ `pod install` or `yarn prepare`
-4. Copy the `Keys.plist.sample` file located in `example/ios/SampleApp/` to a new file named `Keys.plist`. This file is git ignored and won't be accidentally committed. In your Google cloud console, add the Google API key to the project and add this newly created API key to the `Keys.plist` file.
+2. Copy the `Keys.plist.sample` file located in `example/ios/SampleApp/` to a new file named `Keys.plist`. This file is git ignored and won't be accidentally committed. In your Google cloud console, add the Google API key to the project and add this newly created API key to the `Keys.plist` file.
```xml
API_KEY
Your API KEY
```
-5. To run the sample app, navigate to the `example` folder in the root directory and use the following commands for your platform in the terminal.
+## Running the app
+
+1. To run the sample app, navigate to the `example` folder in the root directory and use the following commands for your platform in the terminal.
1. Ensure all workspace dependencies are installed:
`yarn install`
@@ -37,8 +43,7 @@ This contains a sample application to showcase the functionality of the NavSDK l
`npx react-native run-ios`
-6. After the app initializes, accept the terms of services. You should see a map loaded in background if you have used the right API key.
-
+2. After the app initializes, accept the terms of services. You should see a map loaded in background if you have used the right API key.
### Android
1. On your Emulator, go to App Info for the installed app, then Permissions > Location and allow location for the app.
diff --git a/example/android/app/build.gradle b/example/android/app/build.gradle
index 11f2abb..e999c47 100644
--- a/example/android/app/build.gradle
+++ b/example/android/app/build.gradle
@@ -1,11 +1,11 @@
// Copyright 2023 Google LLC
-//
+//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
-//
+//
// http://www.apache.org/licenses/LICENSE-2.0
-//
+//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@@ -127,6 +127,11 @@ dependencies {
} else {
implementation jscFlavor
}
+
+ // For Android Auto Sample support
+ implementation "androidx.car.app:app:1.4.0"
+ implementation "androidx.car.app:app-projected:1.4.0"
+ implementation 'com.google.android.libraries.navigation:navigation:5.3.1'
}
secrets {
@@ -137,12 +142,12 @@ secrets {
// https://developers.google.com/maps/documentation/android-sdk/secrets-gradle-plugin
propertiesFileName = "local.properties"
- // For CI/CD, you can have a file with default keys that can be
+ // For CI/CD, you can have a file with default keys that can be
// safely checked in to your source code version control.
// defaultPropertiesFileName = 'local.defaults.properties'
// Ignore all keys matching the regexp "sdk.*"
- ignoreList.add("sdk.*")
+ ignoreList.add("sdk.*")
}
apply from: file("../../node_modules/@react-native-community/cli-platform-android/native_modules.gradle"); applyNativeModulesAppBuildGradle(project)
diff --git a/example/android/app/src/main/AndroidManifest.xml b/example/android/app/src/main/AndroidManifest.xml
index 375107a..5db65dd 100644
--- a/example/android/app/src/main/AndroidManifest.xml
+++ b/example/android/app/src/main/AndroidManifest.xml
@@ -16,30 +16,62 @@
-
+
+
+
+
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
+ android:icon="@mipmap/ic_launcher">
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/example/android/app/src/main/java/com/sampleapp/ManeuverConverter.java b/example/android/app/src/main/java/com/sampleapp/ManeuverConverter.java
new file mode 100644
index 0000000..b99846b
--- /dev/null
+++ b/example/android/app/src/main/java/com/sampleapp/ManeuverConverter.java
@@ -0,0 +1,265 @@
+/*
+ * Copyright 2024 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.sampleapp;
+
+import com.google.android.libraries.mapsplatform.turnbyturn.model.Maneuver;
+import com.google.common.collect.ImmutableMap;
+import javax.annotation.Nullable;
+
+/** Converter that converts between turn-by-turn and Android Auto Maneuvers. */
+public final class ManeuverConverter {
+ private ManeuverConverter() {}
+
+ // Map from turn-by-turn Maneuver to Android Auto Maneuver.Type.
+ private static final ImmutableMap MANEUVER_TO_ANDROID_AUTO_MANEUVER_TYPE =
+ ImmutableMap.builder()
+ .put(Maneuver.DEPART, androidx.car.app.navigation.model.Maneuver.TYPE_DEPART)
+ .put(Maneuver.DESTINATION, androidx.car.app.navigation.model.Maneuver.TYPE_DESTINATION)
+ .put(
+ Maneuver.DESTINATION_LEFT,
+ androidx.car.app.navigation.model.Maneuver.TYPE_DESTINATION_LEFT)
+ .put(
+ Maneuver.DESTINATION_RIGHT,
+ androidx.car.app.navigation.model.Maneuver.TYPE_DESTINATION_RIGHT)
+ .put(Maneuver.STRAIGHT, androidx.car.app.navigation.model.Maneuver.TYPE_STRAIGHT)
+ .put(Maneuver.TURN_LEFT, androidx.car.app.navigation.model.Maneuver.TYPE_TURN_NORMAL_LEFT)
+ .put(
+ Maneuver.TURN_RIGHT,
+ androidx.car.app.navigation.model.Maneuver.TYPE_TURN_NORMAL_RIGHT)
+ .put(Maneuver.TURN_KEEP_LEFT, androidx.car.app.navigation.model.Maneuver.TYPE_KEEP_LEFT)
+ .put(Maneuver.TURN_KEEP_RIGHT, androidx.car.app.navigation.model.Maneuver.TYPE_KEEP_RIGHT)
+ .put(
+ Maneuver.TURN_SLIGHT_LEFT,
+ androidx.car.app.navigation.model.Maneuver.TYPE_TURN_SLIGHT_LEFT)
+ .put(
+ Maneuver.TURN_SLIGHT_RIGHT,
+ androidx.car.app.navigation.model.Maneuver.TYPE_TURN_SLIGHT_RIGHT)
+ .put(
+ Maneuver.TURN_SHARP_LEFT,
+ androidx.car.app.navigation.model.Maneuver.TYPE_TURN_SHARP_LEFT)
+ .put(
+ Maneuver.TURN_SHARP_RIGHT,
+ androidx.car.app.navigation.model.Maneuver.TYPE_ON_RAMP_SHARP_RIGHT)
+ .put(
+ Maneuver.TURN_U_TURN_CLOCKWISE,
+ androidx.car.app.navigation.model.Maneuver.TYPE_U_TURN_RIGHT)
+ .put(
+ Maneuver.TURN_U_TURN_COUNTERCLOCKWISE,
+ androidx.car.app.navigation.model.Maneuver.TYPE_U_TURN_LEFT)
+ .put(
+ Maneuver.MERGE_UNSPECIFIED,
+ androidx.car.app.navigation.model.Maneuver.TYPE_MERGE_SIDE_UNSPECIFIED)
+ .put(Maneuver.MERGE_LEFT, androidx.car.app.navigation.model.Maneuver.TYPE_MERGE_LEFT)
+ .put(Maneuver.MERGE_RIGHT, androidx.car.app.navigation.model.Maneuver.TYPE_MERGE_RIGHT)
+ .put(Maneuver.FORK_LEFT, androidx.car.app.navigation.model.Maneuver.TYPE_FORK_LEFT)
+ .put(Maneuver.FORK_RIGHT, androidx.car.app.navigation.model.Maneuver.TYPE_FORK_RIGHT)
+ .put(
+ Maneuver.ON_RAMP_UNSPECIFIED,
+ androidx.car.app.navigation.model.Maneuver.TYPE_ON_RAMP_NORMAL_RIGHT)
+ .put(
+ Maneuver.ON_RAMP_LEFT,
+ androidx.car.app.navigation.model.Maneuver.TYPE_ON_RAMP_NORMAL_LEFT)
+ .put(
+ Maneuver.ON_RAMP_RIGHT,
+ androidx.car.app.navigation.model.Maneuver.TYPE_ON_RAMP_NORMAL_RIGHT)
+ .put(
+ Maneuver.ON_RAMP_KEEP_LEFT,
+ androidx.car.app.navigation.model.Maneuver.TYPE_ON_RAMP_NORMAL_LEFT)
+ .put(
+ Maneuver.ON_RAMP_KEEP_RIGHT,
+ androidx.car.app.navigation.model.Maneuver.TYPE_ON_RAMP_NORMAL_RIGHT)
+ .put(
+ Maneuver.ON_RAMP_SLIGHT_LEFT,
+ androidx.car.app.navigation.model.Maneuver.TYPE_ON_RAMP_SLIGHT_LEFT)
+ .put(
+ Maneuver.ON_RAMP_SLIGHT_RIGHT,
+ androidx.car.app.navigation.model.Maneuver.TYPE_ON_RAMP_SLIGHT_RIGHT)
+ .put(
+ Maneuver.ON_RAMP_SHARP_LEFT,
+ androidx.car.app.navigation.model.Maneuver.TYPE_ON_RAMP_SHARP_LEFT)
+ .put(
+ Maneuver.ON_RAMP_SHARP_RIGHT,
+ androidx.car.app.navigation.model.Maneuver.TYPE_ON_RAMP_SHARP_RIGHT)
+ .put(
+ Maneuver.ON_RAMP_U_TURN_CLOCKWISE,
+ androidx.car.app.navigation.model.Maneuver.TYPE_ON_RAMP_U_TURN_RIGHT)
+ .put(
+ Maneuver.ON_RAMP_U_TURN_COUNTERCLOCKWISE,
+ androidx.car.app.navigation.model.Maneuver.TYPE_ON_RAMP_U_TURN_LEFT)
+ .put(
+ Maneuver.OFF_RAMP_LEFT,
+ androidx.car.app.navigation.model.Maneuver.TYPE_OFF_RAMP_NORMAL_LEFT)
+ .put(
+ Maneuver.OFF_RAMP_RIGHT,
+ androidx.car.app.navigation.model.Maneuver.TYPE_OFF_RAMP_NORMAL_RIGHT)
+ .put(
+ Maneuver.OFF_RAMP_KEEP_LEFT,
+ androidx.car.app.navigation.model.Maneuver.TYPE_OFF_RAMP_SLIGHT_LEFT)
+ .put(
+ Maneuver.OFF_RAMP_KEEP_RIGHT,
+ androidx.car.app.navigation.model.Maneuver.TYPE_OFF_RAMP_SLIGHT_RIGHT)
+ .put(
+ Maneuver.OFF_RAMP_SLIGHT_LEFT,
+ androidx.car.app.navigation.model.Maneuver.TYPE_OFF_RAMP_SLIGHT_LEFT)
+ .put(
+ Maneuver.OFF_RAMP_SLIGHT_RIGHT,
+ androidx.car.app.navigation.model.Maneuver.TYPE_OFF_RAMP_SLIGHT_RIGHT)
+ .put(
+ Maneuver.OFF_RAMP_SHARP_LEFT,
+ androidx.car.app.navigation.model.Maneuver.TYPE_OFF_RAMP_NORMAL_LEFT)
+ .put(
+ Maneuver.OFF_RAMP_SHARP_RIGHT,
+ androidx.car.app.navigation.model.Maneuver.TYPE_OFF_RAMP_NORMAL_RIGHT)
+ .put(
+ Maneuver.ROUNDABOUT_CLOCKWISE,
+ androidx.car.app.navigation.model.Maneuver.TYPE_ROUNDABOUT_ENTER_AND_EXIT_CW)
+ .put(
+ Maneuver.ROUNDABOUT_COUNTERCLOCKWISE,
+ androidx.car.app.navigation.model.Maneuver.TYPE_ROUNDABOUT_ENTER_AND_EXIT_CCW)
+ .put(
+ Maneuver.ROUNDABOUT_STRAIGHT_CLOCKWISE,
+ androidx.car.app.navigation.model.Maneuver.TYPE_ROUNDABOUT_ENTER_CW)
+ .put(
+ Maneuver.ROUNDABOUT_STRAIGHT_COUNTERCLOCKWISE,
+ androidx.car.app.navigation.model.Maneuver.TYPE_ROUNDABOUT_ENTER_CCW)
+ .put(
+ Maneuver.ROUNDABOUT_LEFT_CLOCKWISE,
+ androidx.car.app.navigation.model.Maneuver
+ .TYPE_ROUNDABOUT_ENTER_AND_EXIT_CW_WITH_ANGLE)
+ .put(
+ Maneuver.ROUNDABOUT_LEFT_COUNTERCLOCKWISE,
+ androidx.car.app.navigation.model.Maneuver
+ .TYPE_ROUNDABOUT_ENTER_AND_EXIT_CCW_WITH_ANGLE)
+ .put(
+ Maneuver.ROUNDABOUT_RIGHT_CLOCKWISE,
+ androidx.car.app.navigation.model.Maneuver
+ .TYPE_ROUNDABOUT_ENTER_AND_EXIT_CW_WITH_ANGLE)
+ .put(
+ Maneuver.ROUNDABOUT_RIGHT_COUNTERCLOCKWISE,
+ androidx.car.app.navigation.model.Maneuver
+ .TYPE_ROUNDABOUT_ENTER_AND_EXIT_CCW_WITH_ANGLE)
+ .put(
+ Maneuver.ROUNDABOUT_SLIGHT_LEFT_CLOCKWISE,
+ androidx.car.app.navigation.model.Maneuver
+ .TYPE_ROUNDABOUT_ENTER_AND_EXIT_CW_WITH_ANGLE)
+ .put(
+ Maneuver.ROUNDABOUT_SLIGHT_LEFT_COUNTERCLOCKWISE,
+ androidx.car.app.navigation.model.Maneuver
+ .TYPE_ROUNDABOUT_ENTER_AND_EXIT_CCW_WITH_ANGLE)
+ .put(
+ Maneuver.ROUNDABOUT_SLIGHT_RIGHT_CLOCKWISE,
+ androidx.car.app.navigation.model.Maneuver
+ .TYPE_ROUNDABOUT_ENTER_AND_EXIT_CW_WITH_ANGLE)
+ .put(
+ Maneuver.ROUNDABOUT_SLIGHT_RIGHT_COUNTERCLOCKWISE,
+ androidx.car.app.navigation.model.Maneuver
+ .TYPE_ROUNDABOUT_ENTER_AND_EXIT_CCW_WITH_ANGLE)
+ .put(
+ Maneuver.ROUNDABOUT_SHARP_LEFT_CLOCKWISE,
+ androidx.car.app.navigation.model.Maneuver
+ .TYPE_ROUNDABOUT_ENTER_AND_EXIT_CW_WITH_ANGLE)
+ .put(
+ Maneuver.ROUNDABOUT_SHARP_LEFT_COUNTERCLOCKWISE,
+ androidx.car.app.navigation.model.Maneuver
+ .TYPE_ROUNDABOUT_ENTER_AND_EXIT_CCW_WITH_ANGLE)
+ .put(
+ Maneuver.ROUNDABOUT_SHARP_RIGHT_CLOCKWISE,
+ androidx.car.app.navigation.model.Maneuver
+ .TYPE_ROUNDABOUT_ENTER_AND_EXIT_CW_WITH_ANGLE)
+ .put(
+ Maneuver.ROUNDABOUT_SHARP_RIGHT_COUNTERCLOCKWISE,
+ androidx.car.app.navigation.model.Maneuver
+ .TYPE_ROUNDABOUT_ENTER_AND_EXIT_CCW_WITH_ANGLE)
+ .put(
+ Maneuver.ROUNDABOUT_U_TURN_CLOCKWISE,
+ androidx.car.app.navigation.model.Maneuver
+ .TYPE_ROUNDABOUT_ENTER_AND_EXIT_CW_WITH_ANGLE)
+ .put(
+ Maneuver.ROUNDABOUT_U_TURN_COUNTERCLOCKWISE,
+ androidx.car.app.navigation.model.Maneuver
+ .TYPE_ROUNDABOUT_ENTER_AND_EXIT_CCW_WITH_ANGLE)
+ .put(
+ Maneuver.ROUNDABOUT_EXIT_CLOCKWISE,
+ androidx.car.app.navigation.model.Maneuver.TYPE_ROUNDABOUT_EXIT_CW)
+ .put(
+ Maneuver.ROUNDABOUT_EXIT_COUNTERCLOCKWISE,
+ androidx.car.app.navigation.model.Maneuver.TYPE_ROUNDABOUT_EXIT_CCW)
+ .put(Maneuver.FERRY_BOAT, androidx.car.app.navigation.model.Maneuver.TYPE_FERRY_BOAT)
+ .put(Maneuver.FERRY_TRAIN, androidx.car.app.navigation.model.Maneuver.TYPE_FERRY_TRAIN)
+ .put(Maneuver.NAME_CHANGE, androidx.car.app.navigation.model.Maneuver.TYPE_NAME_CHANGE)
+ .buildOrThrow();
+
+ /** Represents the roundabout turn angle for a slight turn in either right or left directions. */
+ private static final int ROUNDABOUT_ANGLE_SLIGHT = 10;
+
+ /** Represents the roundabout turn angle for a normal turn in either right or left directions. */
+ private static final int ROUNDABOUT_ANGLE_NORMAL = 45;
+
+ /** Represents the roundabout turn angle for a sharp turn in either right or left directions. */
+ private static final int ROUNDABOUT_ANGLE_SHARP = 135;
+
+ /** Represents the roundabout turn angle for a u-turn in either right or left directions. */
+ private static final int ROUNDABOUT_ANGLE_U_TURN = 180;
+
+ /**
+ * Returns the corresponding {@link androidx.car.app.navigation.model.Maneuver.Type} for the given
+ * direction {@link Maneuver}
+ *
+ * @throws {@link IllegalArgumentException} if the given maneuver does not have a corresponding
+ * Android Auto Maneuver type.
+ */
+ public static int getAndroidAutoManeuverType(@Maneuver int maneuver) {
+ if (MANEUVER_TO_ANDROID_AUTO_MANEUVER_TYPE.containsKey(maneuver)) {
+ return MANEUVER_TO_ANDROID_AUTO_MANEUVER_TYPE.get(maneuver);
+ }
+ throw new IllegalArgumentException(
+ String.format(
+ "Given turn-by-turn Maneuver %d cannot be converted to an Android Auto equivalent.",
+ maneuver));
+ }
+
+ /**
+ * Returns the corresponding Android Auto roundabout angle for the given turn {@link Maneuver}.
+ * Returns {@code null} if given maneuver does not involve a roundabout with a turn.
+ */
+ @Nullable
+ public static Integer getAndroidAutoRoundaboutAngle(@Maneuver int maneuver) {
+ if (maneuver == Maneuver.ROUNDABOUT_LEFT_CLOCKWISE
+ || maneuver == Maneuver.ROUNDABOUT_RIGHT_CLOCKWISE
+ || maneuver == Maneuver.ROUNDABOUT_LEFT_COUNTERCLOCKWISE
+ || maneuver == Maneuver.ROUNDABOUT_RIGHT_COUNTERCLOCKWISE) {
+ return ROUNDABOUT_ANGLE_NORMAL;
+ }
+ if (maneuver == Maneuver.ROUNDABOUT_SHARP_LEFT_CLOCKWISE
+ || maneuver == Maneuver.ROUNDABOUT_SHARP_RIGHT_CLOCKWISE
+ || maneuver == Maneuver.ROUNDABOUT_SHARP_LEFT_COUNTERCLOCKWISE
+ || maneuver == Maneuver.ROUNDABOUT_SHARP_RIGHT_COUNTERCLOCKWISE) {
+ return ROUNDABOUT_ANGLE_SHARP;
+ }
+ if (maneuver == Maneuver.ROUNDABOUT_SLIGHT_LEFT_CLOCKWISE
+ || maneuver == Maneuver.ROUNDABOUT_SLIGHT_RIGHT_CLOCKWISE
+ || maneuver == Maneuver.ROUNDABOUT_SLIGHT_LEFT_COUNTERCLOCKWISE
+ || maneuver == Maneuver.ROUNDABOUT_SLIGHT_RIGHT_COUNTERCLOCKWISE) {
+ return ROUNDABOUT_ANGLE_SLIGHT;
+ }
+ if (maneuver == Maneuver.ROUNDABOUT_U_TURN_CLOCKWISE
+ || maneuver == Maneuver.ROUNDABOUT_U_TURN_COUNTERCLOCKWISE) {
+ return ROUNDABOUT_ANGLE_U_TURN;
+ }
+ return null;
+ }
+}
diff --git a/example/android/app/src/main/java/com/sampleapp/SampleAndroidAutoScreen.java b/example/android/app/src/main/java/com/sampleapp/SampleAndroidAutoScreen.java
new file mode 100644
index 0000000..e71d6c6
--- /dev/null
+++ b/example/android/app/src/main/java/com/sampleapp/SampleAndroidAutoScreen.java
@@ -0,0 +1,150 @@
+/*
+ * Copyright 2024 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.sampleapp;
+
+import static java.lang.Double.max;
+
+import android.annotation.SuppressLint;
+import androidx.annotation.NonNull;
+import androidx.car.app.CarContext;
+import androidx.car.app.model.Action;
+import androidx.car.app.model.ActionStrip;
+import androidx.car.app.model.CarIcon;
+import androidx.car.app.model.Distance;
+import androidx.car.app.model.Pane;
+import androidx.car.app.model.PaneTemplate;
+import androidx.car.app.model.Row;
+import androidx.car.app.model.Template;
+import androidx.car.app.navigation.model.Maneuver;
+import androidx.car.app.navigation.model.NavigationTemplate;
+import androidx.car.app.navigation.model.RoutingInfo;
+import androidx.car.app.navigation.model.Step;
+import androidx.core.graphics.drawable.IconCompat;
+import com.facebook.react.bridge.Arguments;
+import com.facebook.react.bridge.WritableMap;
+import com.google.android.gms.maps.GoogleMap;
+import com.google.android.libraries.mapsplatform.turnbyturn.model.NavInfo;
+import com.google.android.libraries.mapsplatform.turnbyturn.model.StepInfo;
+import com.google.android.react.navsdk.AndroidAutoBaseScreen;
+import com.google.android.react.navsdk.NavInfoReceivingService;
+
+public class SampleAndroidAutoScreen extends AndroidAutoBaseScreen {
+ protected RoutingInfo mNavInfo;
+
+ public SampleAndroidAutoScreen(@NonNull CarContext carContext) {
+ super(carContext);
+
+ // Connect to the Turn-by-Turn Navigation service to receive navigation data.
+ NavInfoReceivingService.getNavInfoLiveData().observe(this, this::buildNavInfo);
+ }
+
+ private void buildNavInfo(NavInfo navInfo) {
+ if (navInfo == null || navInfo.getCurrentStep() == null) {
+ return;
+ }
+
+ /**
+ * Converts data received from the Navigation data feed into Android-Auto compatible data
+ * structures.
+ */
+ Step currentStep = buildStepFromStepInfo(navInfo.getCurrentStep());
+ Distance distanceToStep =
+ Distance.create(max(navInfo.getDistanceToCurrentStepMeters(), 0), Distance.UNIT_METERS);
+
+ mNavInfo = new RoutingInfo.Builder().setCurrentStep(currentStep, distanceToStep).build();
+
+ // Invalidate the current template which leads to another onGetTemplate call.
+ invalidate();
+ }
+
+ @Override
+ public void onNavigationReady(boolean ready) {
+ super.onNavigationReady(ready);
+ // Invalidate template layout because of conditional rendering in the
+ // onGetTemplate method.
+ invalidate();
+ }
+
+ private Step buildStepFromStepInfo(StepInfo stepInfo) {
+ int maneuver = ManeuverConverter.getAndroidAutoManeuverType(stepInfo.getManeuver());
+ Maneuver.Builder maneuverBuilder = new Maneuver.Builder(maneuver);
+ IconCompat maneuverIcon = IconCompat.createWithBitmap(stepInfo.getManeuverBitmap());
+ CarIcon maneuverCarIcon = new CarIcon.Builder(maneuverIcon).build();
+ maneuverBuilder.setIcon(maneuverCarIcon);
+ Step.Builder stepBuilder =
+ new Step.Builder()
+ .setRoad(stepInfo.getFullRoadName())
+ .setCue(stepInfo.getFullInstructionText())
+ .setManeuver(maneuverBuilder.build());
+ return stepBuilder.build();
+ }
+
+ @NonNull
+ @Override
+ public Template onGetTemplate() {
+ if (!mNavigationInitialized) {
+ return new PaneTemplate.Builder(
+ new Pane.Builder()
+ .addRow(
+ new Row.Builder()
+ .setTitle("Nav SampleApp")
+ .addText(
+ "Initialize navigation to see navigation view on the Android Auto"
+ + " screen")
+ .build())
+ .build())
+ .build();
+ }
+
+ // Suppresses the missing permission check for the followMyLocation method, which requires
+ // "android.permission.ACCESS_COARSE_LOCATION" or "android.permission.ACCESS_FINE_LOCATION", as
+ // these permissions are already handled elsewhere.
+ @SuppressLint("MissingPermission")
+ NavigationTemplate.Builder navigationTemplateBuilder =
+ new NavigationTemplate.Builder()
+ .setActionStrip(
+ new ActionStrip.Builder()
+ .addAction(
+ new Action.Builder()
+ .setTitle("Re-center")
+ .setOnClickListener(
+ () -> {
+ if (mGoogleMap == null) return;
+ mGoogleMap.followMyLocation(GoogleMap.CameraPerspective.TILTED);
+ })
+ .build())
+ .addAction(
+ new Action.Builder()
+ .setTitle("Custom event")
+ .setOnClickListener(
+ () -> {
+ WritableMap map = Arguments.createMap();
+ map.putString("sampleKey", "sampleValue");
+ sendCustomEvent("sampleEvent", map);
+ })
+ .build())
+ .build())
+ .setMapActionStrip(new ActionStrip.Builder().addAction(Action.PAN).build());
+
+ // Show turn-by-turn navigation information if available.
+ if (mNavInfo != null) {
+ navigationTemplateBuilder.setNavigationInfo(mNavInfo);
+ }
+
+ return navigationTemplateBuilder.build();
+ }
+}
diff --git a/example/android/app/src/main/java/com/sampleapp/SampleAndroidAutoService.java b/example/android/app/src/main/java/com/sampleapp/SampleAndroidAutoService.java
new file mode 100644
index 0000000..f88a74f
--- /dev/null
+++ b/example/android/app/src/main/java/com/sampleapp/SampleAndroidAutoService.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2024 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.sampleapp;
+
+import androidx.annotation.NonNull;
+import androidx.car.app.CarAppService;
+import androidx.car.app.Session;
+import androidx.car.app.SessionInfo;
+import androidx.car.app.validation.HostValidator;
+
+public final class SampleAndroidAutoService extends CarAppService {
+ @NonNull
+ @Override
+ public HostValidator createHostValidator() {
+ // This sample allows all hosts to connect to the app.
+ return HostValidator.ALLOW_ALL_HOSTS_VALIDATOR;
+ }
+
+ @Override
+ @NonNull
+ public Session onCreateSession(@NonNull SessionInfo sessionInfo) {
+ return new SampleAndroidAutoSession(sessionInfo);
+ }
+}
diff --git a/example/android/app/src/main/java/com/sampleapp/SampleAndroidAutoSession.java b/example/android/app/src/main/java/com/sampleapp/SampleAndroidAutoSession.java
new file mode 100644
index 0000000..81c664b
--- /dev/null
+++ b/example/android/app/src/main/java/com/sampleapp/SampleAndroidAutoSession.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright 2024 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.sampleapp;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.os.IBinder;
+import android.util.Log;
+import androidx.annotation.NonNull;
+import androidx.car.app.CarContext;
+import androidx.car.app.CarToast;
+import androidx.car.app.Screen;
+import androidx.car.app.Session;
+import androidx.car.app.SessionInfo;
+import androidx.lifecycle.DefaultLifecycleObserver;
+import androidx.lifecycle.Lifecycle;
+import androidx.lifecycle.LifecycleObserver;
+import androidx.lifecycle.LifecycleOwner;
+
+public class SampleAndroidAutoSession extends Session {
+ static final String TAG = SampleAndroidAutoSession.class.getSimpleName();
+
+ public SampleAndroidAutoSession(SessionInfo sessionInfo) {
+ if (sessionInfo.getDisplayType() == SessionInfo.DISPLAY_TYPE_MAIN) {
+ Lifecycle lifecycle = getLifecycle();
+ lifecycle.addObserver(mLifeCycleObserver);
+ }
+ }
+
+ private final LifecycleObserver mLifeCycleObserver =
+ new DefaultLifecycleObserver() {
+
+ @Override
+ public void onCreate(@NonNull LifecycleOwner lifecycleOwner) {
+ Log.i(TAG, "In onCreate()");
+ }
+
+ @Override
+ public void onStart(@NonNull LifecycleOwner lifecycleOwner) {
+ Log.i(TAG, "In onStart()");
+ getCarContext()
+ .bindService(
+ new Intent(getCarContext(), SampleAndroidAutoService.class),
+ mServiceConnection,
+ Context.BIND_AUTO_CREATE);
+ }
+
+ @Override
+ public void onResume(@NonNull LifecycleOwner lifecycleOwner) {
+ Log.i(TAG, "In onResume()");
+ }
+
+ @Override
+ public void onPause(@NonNull LifecycleOwner lifecycleOwner) {
+ Log.i(TAG, "In onPause()");
+ }
+
+ @Override
+ public void onStop(@NonNull LifecycleOwner lifecycleOwner) {
+ Log.i(TAG, "In onStop()");
+ getCarContext().unbindService(mServiceConnection);
+ }
+
+ @Override
+ public void onDestroy(@NonNull LifecycleOwner lifecycleOwner) {
+ Log.i(TAG, "In onDestroy()");
+ }
+ };
+
+ // Monitors the state of the connection to the Navigation service.
+ final ServiceConnection mServiceConnection =
+ new ServiceConnection() {
+ @Override
+ public void onServiceConnected(ComponentName name, IBinder service) {
+ Log.i(TAG, "In onServiceConnected() component:" + name);
+ }
+
+ @Override
+ public void onServiceDisconnected(ComponentName name) {
+ Log.i(TAG, "In onServiceDisconnected() component:" + name);
+ }
+ };
+
+ @Override
+ @NonNull
+ public Screen onCreateScreen(@NonNull Intent intent) {
+ Log.i(TAG, "In onCreateScreen()");
+
+ String action = intent.getAction();
+ if (action != null && CarContext.ACTION_NAVIGATE.equals(action)) {
+ CarToast.makeText(
+ getCarContext(), "Navigation intent: " + intent.getDataString(), CarToast.LENGTH_LONG)
+ .show();
+ }
+
+ return new SampleAndroidAutoScreen(getCarContext());
+ }
+}
diff --git a/example/android/app/src/main/res/xml/automotive_app_desc.xml b/example/android/app/src/main/res/xml/automotive_app_desc.xml
new file mode 100644
index 0000000..cc83882
--- /dev/null
+++ b/example/android/app/src/main/res/xml/automotive_app_desc.xml
@@ -0,0 +1,19 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/example/android/build.gradle b/example/android/build.gradle
index 9484dbf..bf9dc9e 100644
--- a/example/android/build.gradle
+++ b/example/android/build.gradle
@@ -25,7 +25,7 @@ buildscript {
mavenCentral()
}
dependencies {
- classpath("com.android.tools.build:gradle")
+ classpath("com.android.tools.build:gradle:7.2.1")
classpath("com.facebook.react:react-native-gradle-plugin")
classpath("com.google.android.libraries.mapsplatform.secrets-gradle-plugin:secrets-gradle-plugin:2.0.1")
}
diff --git a/example/ios/Podfile b/example/ios/Podfile
index 686ca89..76a93f6 100644
--- a/example/ios/Podfile
+++ b/example/ios/Podfile
@@ -26,26 +26,33 @@ if linkage != nil
use_frameworks! :linkage => linkage.to_sym
end
+config = use_native_modules!
+
target 'SampleApp' do
- config = use_native_modules!
+ use_react_native!(
+ :path => config[:reactNativePath],
+ # An absolute path to your application root.
+ :app_path => "#{Pod::Config.instance.installation_root}/.."
+ )
+end
+target 'SampleAppCarPlay' do
use_react_native!(
:path => config[:reactNativePath],
# An absolute path to your application root.
:app_path => "#{Pod::Config.instance.installation_root}/.."
)
+end
- target 'SampleAppTests' do
- inherit! :complete
- # Pods for testing
- end
+target 'SampleAppTests' do
+ inherit! :complete
+ # Pods for testing, shared between both SampleApp and SampleAppCarPlay
+end
- post_install do |installer|
- # https://github.com/facebook/react-native/blob/main/packages/react-native/scripts/react_native_pods.rb#L197-L202
+post_install do |installer|
react_native_post_install(
installer,
config[:reactNativePath],
:mac_catalyst_enabled => false
)
- end
-end
+end
\ No newline at end of file
diff --git a/example/ios/SampleApp.xcodeproj/project.pbxproj b/example/ios/SampleApp.xcodeproj/project.pbxproj
index eaeecff..7fadfe9 100644
--- a/example/ios/SampleApp.xcodeproj/project.pbxproj
+++ b/example/ios/SampleApp.xcodeproj/project.pbxproj
@@ -8,14 +8,25 @@
/* Begin PBXBuildFile section */
00E356F31AD99517003FC87E /* SampleAppTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 00E356F21AD99517003FC87E /* SampleAppTests.m */; };
- 0C80B921A6F3F58F76C31292 /* libPods-SampleApp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5DCACB8F33CDC322A6C60F78 /* libPods-SampleApp.a */; };
- 13B07FBC1A68108700A75B9A /* AppDelegate.mm in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB01A68108700A75B9A /* AppDelegate.mm */; };
+ 062AC10D618662A05665211C /* libPods-SampleAppCarPlay.a in Frameworks */ = {isa = PBXBuildFile; fileRef = F7A476D4E03B73E48762E4D4 /* libPods-SampleAppCarPlay.a */; };
13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB51A68108700A75B9A /* Images.xcassets */; };
13B07FC11A68108700A75B9A /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB71A68108700A75B9A /* main.m */; };
+ 29F89EA4FF546EABE968E1D2 /* libPods-SampleApp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 354226CC8A58229ECC025410 /* libPods-SampleApp.a */; };
+ 2A20E8122C8994DB00DB7ADA /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB71A68108700A75B9A /* main.m */; };
+ 2A20E8142C8994DB00DB7ADA /* BuildFile in Frameworks */ = {isa = PBXBuildFile; };
+ 2A20E8282C899A1400DB7ADA /* Info-CarPlay.plist in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB61A68108700A75B9A /* Info-CarPlay.plist */; };
+ 2AB8C27A2C89A07000250560 /* Keys.plist in Resources */ = {isa = PBXBuildFile; fileRef = 52D4271C2C81D3F300C7FB36 /* Keys.plist */; };
+ 2AB8C27E2C89A0B400250560 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 81AB9BB72411601600AC10FF /* LaunchScreen.storyboard */; };
+ 2AB8C27F2C89A0C800250560 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB51A68108700A75B9A /* Images.xcassets */; };
+ 5205C1232C8B314F00D0FC6B /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 5205C1212C8B314F00D0FC6B /* AppDelegate.m */; };
+ 52206E7F2C8B2F9E00B34D22 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = 52206E7E2C8B2F9E00B34D22 /* PrivacyInfo.xcprivacy */; };
+ 52206E802C8B2F9E00B34D22 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = 52206E7E2C8B2F9E00B34D22 /* PrivacyInfo.xcprivacy */; };
+ 52206E852C8B2FEC00B34D22 /* CarSceneDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 52206E812C8B2FEC00B34D22 /* CarSceneDelegate.m */; };
+ 52206E862C8B2FEC00B34D22 /* PhoneSceneDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 52206E822C8B2FEC00B34D22 /* PhoneSceneDelegate.m */; };
+ 52206E892C8B303700B34D22 /* AppDelegateCarPlay.m in Sources */ = {isa = PBXBuildFile; fileRef = 52206E882C8B303700B34D22 /* AppDelegateCarPlay.m */; };
52D4271D2C81D3F300C7FB36 /* Keys.plist in Resources */ = {isa = PBXBuildFile; fileRef = 52D4271C2C81D3F300C7FB36 /* Keys.plist */; };
- 7699B88040F8A987B510C191 /* libPods-SampleApp-SampleAppTests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 19F6CBCC0A4E27FBF8BF4A61 /* libPods-SampleApp-SampleAppTests.a */; };
+ 77BA6ED186FC355B21D3A62F /* libPods-SampleAppTests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 36A733F29B6732CA2CF27A17 /* libPods-SampleAppTests.a */; };
81AB9BB82411601600AC10FF /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 81AB9BB72411601600AC10FF /* LaunchScreen.storyboard */; };
- E72EC806AB287A94E8141AD5 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = E049A7A79160D0D74CDA8CD1 /* PrivacyInfo.xcprivacy */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
@@ -32,23 +43,36 @@
00E356EE1AD99517003FC87E /* SampleAppTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = SampleAppTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
00E356F11AD99517003FC87E /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
00E356F21AD99517003FC87E /* SampleAppTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SampleAppTests.m; sourceTree = ""; };
+ 011A22ED61B5A84DFF838AB2 /* Pods-SampleAppTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SampleAppTests.debug.xcconfig"; path = "Target Support Files/Pods-SampleAppTests/Pods-SampleAppTests.debug.xcconfig"; sourceTree = ""; };
13B07F961A680F5B00A75B9A /* SampleApp.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = SampleApp.app; sourceTree = BUILT_PRODUCTS_DIR; };
- 13B07FAF1A68108700A75B9A /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = AppDelegate.h; path = SampleApp/AppDelegate.h; sourceTree = ""; };
- 13B07FB01A68108700A75B9A /* AppDelegate.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = AppDelegate.mm; path = SampleApp/AppDelegate.mm; sourceTree = ""; };
13B07FB51A68108700A75B9A /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Images.xcassets; path = SampleApp/Images.xcassets; sourceTree = ""; };
- 13B07FB61A68108700A75B9A /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = Info.plist; path = SampleApp/Info.plist; sourceTree = ""; };
+ 13B07FB61A68108700A75B9A /* Info-CarPlay.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = "Info-CarPlay.plist"; path = "SampleApp/Info-CarPlay.plist"; sourceTree = ""; };
13B07FB71A68108700A75B9A /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = main.m; path = SampleApp/main.m; sourceTree = ""; };
- 13B07FB81A68108700A75B9A /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = PrivacyInfo.xcprivacy; path = SampleApp/PrivacyInfo.xcprivacy; sourceTree = ""; };
- 19F6CBCC0A4E27FBF8BF4A61 /* libPods-SampleApp-SampleAppTests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-SampleApp-SampleAppTests.a"; sourceTree = BUILT_PRODUCTS_DIR; };
+ 2A20E8202C8994DB00DB7ADA /* SampleApp.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = SampleApp.app; sourceTree = BUILT_PRODUCTS_DIR; };
+ 2AB8C27C2C89A09D00250560 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = Info.plist; path = SampleApp/Info.plist; sourceTree = ""; };
+ 2AEB719D2C7C80F9002224A5 /* SampleApp.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; name = SampleApp.entitlements; path = SampleApp/SampleApp.entitlements; sourceTree = ""; };
+ 354226CC8A58229ECC025410 /* libPods-SampleApp.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-SampleApp.a"; sourceTree = BUILT_PRODUCTS_DIR; };
+ 36A733F29B6732CA2CF27A17 /* libPods-SampleAppTests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-SampleAppTests.a"; sourceTree = BUILT_PRODUCTS_DIR; };
3B4392A12AC88292D35C810B /* Pods-SampleApp.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SampleApp.debug.xcconfig"; path = "Target Support Files/Pods-SampleApp/Pods-SampleApp.debug.xcconfig"; sourceTree = ""; };
+ 4735EA9AB0E8831D741A3A3E /* Pods-SampleAppCarPlay.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SampleAppCarPlay.debug.xcconfig"; path = "Target Support Files/Pods-SampleAppCarPlay/Pods-SampleAppCarPlay.debug.xcconfig"; sourceTree = ""; };
+ 5205C1212C8B314F00D0FC6B /* AppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = AppDelegate.m; path = SampleApp/AppDelegate.m; sourceTree = ""; };
+ 5205C1222C8B314F00D0FC6B /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = AppDelegate.h; path = SampleApp/AppDelegate.h; sourceTree = ""; };
+ 52206E7E2C8B2F9E00B34D22 /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xml; name = PrivacyInfo.xcprivacy; path = SampleApp/PrivacyInfo.xcprivacy; sourceTree = ""; };
+ 52206E812C8B2FEC00B34D22 /* CarSceneDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = CarSceneDelegate.m; path = SampleApp/CarSceneDelegate.m; sourceTree = ""; };
+ 52206E822C8B2FEC00B34D22 /* PhoneSceneDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = PhoneSceneDelegate.m; path = SampleApp/PhoneSceneDelegate.m; sourceTree = ""; };
+ 52206E832C8B2FEC00B34D22 /* CarSceneDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = CarSceneDelegate.h; path = SampleApp/CarSceneDelegate.h; sourceTree = ""; };
+ 52206E842C8B2FEC00B34D22 /* PhoneSceneDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = PhoneSceneDelegate.h; path = SampleApp/PhoneSceneDelegate.h; sourceTree = ""; };
+ 52206E872C8B303700B34D22 /* AppDelegateCarPlay.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = AppDelegateCarPlay.h; path = SampleApp/AppDelegateCarPlay.h; sourceTree = ""; };
+ 52206E882C8B303700B34D22 /* AppDelegateCarPlay.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = AppDelegateCarPlay.m; path = SampleApp/AppDelegateCarPlay.m; sourceTree = ""; };
52D4271C2C81D3F300C7FB36 /* Keys.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = Keys.plist; path = SampleApp/Keys.plist; sourceTree = ""; };
5709B34CF0A7D63546082F79 /* Pods-SampleApp.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SampleApp.release.xcconfig"; path = "Target Support Files/Pods-SampleApp/Pods-SampleApp.release.xcconfig"; sourceTree = ""; };
5B7EB9410499542E8C5724F5 /* Pods-SampleApp-SampleAppTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SampleApp-SampleAppTests.debug.xcconfig"; path = "Target Support Files/Pods-SampleApp-SampleAppTests/Pods-SampleApp-SampleAppTests.debug.xcconfig"; sourceTree = ""; };
- 5DCACB8F33CDC322A6C60F78 /* libPods-SampleApp.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-SampleApp.a"; sourceTree = BUILT_PRODUCTS_DIR; };
+ 750C10660A3F190F6072C5DA /* Pods-SampleAppCarPlay.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SampleAppCarPlay.release.xcconfig"; path = "Target Support Files/Pods-SampleAppCarPlay/Pods-SampleAppCarPlay.release.xcconfig"; sourceTree = ""; };
81AB9BB72411601600AC10FF /* LaunchScreen.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; name = LaunchScreen.storyboard; path = SampleApp/LaunchScreen.storyboard; sourceTree = ""; };
89C6BE57DB24E9ADA2F236DE /* Pods-SampleApp-SampleAppTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SampleApp-SampleAppTests.release.xcconfig"; path = "Target Support Files/Pods-SampleApp-SampleAppTests/Pods-SampleApp-SampleAppTests.release.xcconfig"; sourceTree = ""; };
- E049A7A79160D0D74CDA8CD1 /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xml; name = PrivacyInfo.xcprivacy; path = SampleApp/PrivacyInfo.xcprivacy; sourceTree = ""; };
+ A9D547919643FC330CF84EC3 /* Pods-SampleAppTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SampleAppTests.release.xcconfig"; path = "Target Support Files/Pods-SampleAppTests/Pods-SampleAppTests.release.xcconfig"; sourceTree = ""; };
ED297162215061F000B7C4FE /* JavaScriptCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = JavaScriptCore.framework; path = System/Library/Frameworks/JavaScriptCore.framework; sourceTree = SDKROOT; };
+ F7A476D4E03B73E48762E4D4 /* libPods-SampleAppCarPlay.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-SampleAppCarPlay.a"; sourceTree = BUILT_PRODUCTS_DIR; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
@@ -56,7 +80,7 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
- 7699B88040F8A987B510C191 /* libPods-SampleApp-SampleAppTests.a in Frameworks */,
+ 77BA6ED186FC355B21D3A62F /* libPods-SampleAppTests.a in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -64,7 +88,16 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
- 0C80B921A6F3F58F76C31292 /* libPods-SampleApp.a in Frameworks */,
+ 062AC10D618662A05665211C /* libPods-SampleAppCarPlay.a in Frameworks */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ 2A20E8132C8994DB00DB7ADA /* Frameworks */ = {
+ isa = PBXFrameworksBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 2A20E8142C8994DB00DB7ADA /* BuildFile in Frameworks */,
+ 29F89EA4FF546EABE968E1D2 /* libPods-SampleApp.a in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -91,15 +124,22 @@
13B07FAE1A68108700A75B9A /* SampleApp */ = {
isa = PBXGroup;
children = (
- 13B07FAF1A68108700A75B9A /* AppDelegate.h */,
- 13B07FB01A68108700A75B9A /* AppDelegate.mm */,
+ 5205C1222C8B314F00D0FC6B /* AppDelegate.h */,
+ 5205C1212C8B314F00D0FC6B /* AppDelegate.m */,
+ 52206E872C8B303700B34D22 /* AppDelegateCarPlay.h */,
+ 52206E882C8B303700B34D22 /* AppDelegateCarPlay.m */,
+ 2AEB719D2C7C80F9002224A5 /* SampleApp.entitlements */,
+ 13B07FB71A68108700A75B9A /* main.m */,
+ 52206E832C8B2FEC00B34D22 /* CarSceneDelegate.h */,
+ 52206E812C8B2FEC00B34D22 /* CarSceneDelegate.m */,
+ 52206E842C8B2FEC00B34D22 /* PhoneSceneDelegate.h */,
+ 52206E822C8B2FEC00B34D22 /* PhoneSceneDelegate.m */,
13B07FB51A68108700A75B9A /* Images.xcassets */,
- 13B07FB61A68108700A75B9A /* Info.plist */,
+ 13B07FB61A68108700A75B9A /* Info-CarPlay.plist */,
+ 2AB8C27C2C89A09D00250560 /* Info.plist */,
52D4271C2C81D3F300C7FB36 /* Keys.plist */,
81AB9BB72411601600AC10FF /* LaunchScreen.storyboard */,
- 13B07FB71A68108700A75B9A /* main.m */,
- 13B07FB81A68108700A75B9A /* PrivacyInfo.xcprivacy */,
- E049A7A79160D0D74CDA8CD1 /* PrivacyInfo.xcprivacy */,
+ 52206E7E2C8B2F9E00B34D22 /* PrivacyInfo.xcprivacy */,
);
name = SampleApp;
sourceTree = "";
@@ -108,8 +148,9 @@
isa = PBXGroup;
children = (
ED297162215061F000B7C4FE /* JavaScriptCore.framework */,
- 5DCACB8F33CDC322A6C60F78 /* libPods-SampleApp.a */,
- 19F6CBCC0A4E27FBF8BF4A61 /* libPods-SampleApp-SampleAppTests.a */,
+ F7A476D4E03B73E48762E4D4 /* libPods-SampleAppCarPlay.a */,
+ 36A733F29B6732CA2CF27A17 /* libPods-SampleAppTests.a */,
+ 354226CC8A58229ECC025410 /* libPods-SampleApp.a */,
);
name = Frameworks;
sourceTree = "";
@@ -141,6 +182,7 @@
children = (
13B07F961A680F5B00A75B9A /* SampleApp.app */,
00E356EE1AD99517003FC87E /* SampleAppTests.xctest */,
+ 2A20E8202C8994DB00DB7ADA /* SampleApp.app */,
);
name = Products;
sourceTree = "";
@@ -152,6 +194,10 @@
5709B34CF0A7D63546082F79 /* Pods-SampleApp.release.xcconfig */,
5B7EB9410499542E8C5724F5 /* Pods-SampleApp-SampleAppTests.debug.xcconfig */,
89C6BE57DB24E9ADA2F236DE /* Pods-SampleApp-SampleAppTests.release.xcconfig */,
+ 4735EA9AB0E8831D741A3A3E /* Pods-SampleAppCarPlay.debug.xcconfig */,
+ 750C10660A3F190F6072C5DA /* Pods-SampleAppCarPlay.release.xcconfig */,
+ 011A22ED61B5A84DFF838AB2 /* Pods-SampleAppTests.debug.xcconfig */,
+ A9D547919643FC330CF84EC3 /* Pods-SampleAppTests.release.xcconfig */,
);
path = Pods;
sourceTree = "";
@@ -167,8 +213,8 @@
00E356EA1AD99517003FC87E /* Sources */,
00E356EB1AD99517003FC87E /* Frameworks */,
00E356EC1AD99517003FC87E /* Resources */,
- C59DA0FBD6956966B86A3779 /* [CP] Embed Pods Frameworks */,
- F6A41C54EA430FDDC6A6ED99 /* [CP] Copy Pods Resources */,
+ 7CDED63BA41A8102480A87C8 /* [CP] Embed Pods Frameworks */,
+ C6773F2F6DC462D1292B9F42 /* [CP] Copy Pods Resources */,
);
buildRules = (
);
@@ -180,9 +226,9 @@
productReference = 00E356EE1AD99517003FC87E /* SampleAppTests.xctest */;
productType = "com.apple.product-type.bundle.unit-test";
};
- 13B07F861A680F5B00A75B9A /* SampleApp */ = {
+ 13B07F861A680F5B00A75B9A /* SampleAppCarPlay */ = {
isa = PBXNativeTarget;
- buildConfigurationList = 13B07F931A680F5B00A75B9A /* Build configuration list for PBXNativeTarget "SampleApp" */;
+ buildConfigurationList = 13B07F931A680F5B00A75B9A /* Build configuration list for PBXNativeTarget "SampleAppCarPlay" */;
buildPhases = (
C38B50BA6285516D6DCD4F65 /* [CP] Check Pods Manifest.lock */,
13B07F871A680F5B00A75B9A /* Sources */,
@@ -196,11 +242,32 @@
);
dependencies = (
);
- name = SampleApp;
+ name = SampleAppCarPlay;
productName = SampleApp;
productReference = 13B07F961A680F5B00A75B9A /* SampleApp.app */;
productType = "com.apple.product-type.application";
};
+ 2A20E80C2C8994DB00DB7ADA /* SampleApp */ = {
+ isa = PBXNativeTarget;
+ buildConfigurationList = 2A20E81D2C8994DB00DB7ADA /* Build configuration list for PBXNativeTarget "SampleApp" */;
+ buildPhases = (
+ 2A20E80D2C8994DB00DB7ADA /* [CP] Check Pods Manifest.lock */,
+ 2A20E80E2C8994DB00DB7ADA /* Sources */,
+ 2A20E8132C8994DB00DB7ADA /* Frameworks */,
+ 2AB8C2792C89A06200250560 /* Resources */,
+ 2A20E81A2C8994DB00DB7ADA /* Bundle React Native code and images */,
+ 2A20E81B2C8994DB00DB7ADA /* [CP] Embed Pods Frameworks */,
+ 2A20E81C2C8994DB00DB7ADA /* [CP] Copy Pods Resources */,
+ );
+ buildRules = (
+ );
+ dependencies = (
+ );
+ name = SampleApp;
+ productName = SampleApp;
+ productReference = 2A20E8202C8994DB00DB7ADA /* SampleApp.app */;
+ productType = "com.apple.product-type.application";
+ };
/* End PBXNativeTarget section */
/* Begin PBXProject section */
@@ -231,7 +298,8 @@
projectDirPath = "";
projectRoot = "";
targets = (
- 13B07F861A680F5B00A75B9A /* SampleApp */,
+ 2A20E80C2C8994DB00DB7ADA /* SampleApp */,
+ 13B07F861A680F5B00A75B9A /* SampleAppCarPlay */,
00E356ED1AD99517003FC87E /* SampleAppTests */,
);
};
@@ -250,9 +318,21 @@
buildActionMask = 2147483647;
files = (
52D4271D2C81D3F300C7FB36 /* Keys.plist in Resources */,
+ 2A20E8282C899A1400DB7ADA /* Info-CarPlay.plist in Resources */,
81AB9BB82411601600AC10FF /* LaunchScreen.storyboard in Resources */,
+ 52206E802C8B2F9E00B34D22 /* PrivacyInfo.xcprivacy in Resources */,
13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */,
- E72EC806AB287A94E8141AD5 /* PrivacyInfo.xcprivacy in Resources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+ 2AB8C2792C89A06200250560 /* Resources */ = {
+ isa = PBXResourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 2AB8C27F2C89A0C800250560 /* Images.xcassets in Resources */,
+ 2AB8C27E2C89A0B400250560 /* LaunchScreen.storyboard in Resources */,
+ 52206E7F2C8B2F9E00B34D22 /* PrivacyInfo.xcprivacy in Resources */,
+ 2AB8C27A2C89A07000250560 /* Keys.plist in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -281,18 +361,18 @@
files = (
);
inputFileListPaths = (
- "${PODS_ROOT}/Target Support Files/Pods-SampleApp/Pods-SampleApp-frameworks-${CONFIGURATION}-input-files.xcfilelist",
+ "${PODS_ROOT}/Target Support Files/Pods-SampleAppCarPlay/Pods-SampleAppCarPlay-frameworks-${CONFIGURATION}-input-files.xcfilelist",
);
name = "[CP] Embed Pods Frameworks";
outputFileListPaths = (
- "${PODS_ROOT}/Target Support Files/Pods-SampleApp/Pods-SampleApp-frameworks-${CONFIGURATION}-output-files.xcfilelist",
+ "${PODS_ROOT}/Target Support Files/Pods-SampleAppCarPlay/Pods-SampleAppCarPlay-frameworks-${CONFIGURATION}-output-files.xcfilelist",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
- shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-SampleApp/Pods-SampleApp-frameworks.sh\"\n";
+ shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-SampleAppCarPlay/Pods-SampleAppCarPlay-frameworks.sh\"\n";
showEnvVarsInLog = 0;
};
- A55EABD7B0C7F3A422A6CC61 /* [CP] Check Pods Manifest.lock */ = {
+ 2A20E80D2C8994DB00DB7ADA /* [CP] Check Pods Manifest.lock */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
@@ -307,14 +387,81 @@
outputFileListPaths = (
);
outputPaths = (
- "$(DERIVED_FILE_DIR)/Pods-SampleApp-SampleAppTests-checkManifestLockResult.txt",
+ "$(DERIVED_FILE_DIR)/Pods-SampleApp-checkManifestLockResult.txt",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
showEnvVarsInLog = 0;
};
- C38B50BA6285516D6DCD4F65 /* [CP] Check Pods Manifest.lock */ = {
+ 2A20E81A2C8994DB00DB7ADA /* Bundle React Native code and images */ = {
+ isa = PBXShellScriptBuildPhase;
+ buildActionMask = 12;
+ files = (
+ );
+ inputPaths = (
+ "$(SRCROOT)/.xcode.env.local",
+ "$(SRCROOT)/.xcode.env",
+ );
+ name = "Bundle React Native code and images";
+ outputPaths = (
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ shellPath = /bin/sh;
+ shellScript = "set -e\nWITH_ENVIRONMENT=\"$REACT_NATIVE_PATH/scripts/xcode/with-environment.sh\"\nREACT_NATIVE_XCODE=\"$REACT_NATIVE_PATH/scripts/react-native-xcode.sh\"\n\n/bin/sh -c \"$WITH_ENVIRONMENT $REACT_NATIVE_XCODE\"\n";
+ };
+ 2A20E81B2C8994DB00DB7ADA /* [CP] Embed Pods Frameworks */ = {
+ isa = PBXShellScriptBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ inputFileListPaths = (
+ "${PODS_ROOT}/Target Support Files/Pods-SampleApp/Pods-SampleApp-frameworks-${CONFIGURATION}-input-files.xcfilelist",
+ );
+ name = "[CP] Embed Pods Frameworks";
+ outputFileListPaths = (
+ "${PODS_ROOT}/Target Support Files/Pods-SampleApp/Pods-SampleApp-frameworks-${CONFIGURATION}-output-files.xcfilelist",
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ shellPath = /bin/sh;
+ shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-SampleApp/Pods-SampleApp-frameworks.sh\"\n";
+ showEnvVarsInLog = 0;
+ };
+ 2A20E81C2C8994DB00DB7ADA /* [CP] Copy Pods Resources */ = {
+ isa = PBXShellScriptBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ inputFileListPaths = (
+ "${PODS_ROOT}/Target Support Files/Pods-SampleApp/Pods-SampleApp-resources-${CONFIGURATION}-input-files.xcfilelist",
+ );
+ name = "[CP] Copy Pods Resources";
+ outputFileListPaths = (
+ "${PODS_ROOT}/Target Support Files/Pods-SampleApp/Pods-SampleApp-resources-${CONFIGURATION}-output-files.xcfilelist",
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ shellPath = /bin/sh;
+ shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-SampleApp/Pods-SampleApp-resources.sh\"\n";
+ showEnvVarsInLog = 0;
+ };
+ 7CDED63BA41A8102480A87C8 /* [CP] Embed Pods Frameworks */ = {
+ isa = PBXShellScriptBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ inputFileListPaths = (
+ "${PODS_ROOT}/Target Support Files/Pods-SampleAppTests/Pods-SampleAppTests-frameworks-${CONFIGURATION}-input-files.xcfilelist",
+ );
+ name = "[CP] Embed Pods Frameworks";
+ outputFileListPaths = (
+ "${PODS_ROOT}/Target Support Files/Pods-SampleAppTests/Pods-SampleAppTests-frameworks-${CONFIGURATION}-output-files.xcfilelist",
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ shellPath = /bin/sh;
+ shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-SampleAppTests/Pods-SampleAppTests-frameworks.sh\"\n";
+ showEnvVarsInLog = 0;
+ };
+ A55EABD7B0C7F3A422A6CC61 /* [CP] Check Pods Manifest.lock */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
@@ -329,62 +476,67 @@
outputFileListPaths = (
);
outputPaths = (
- "$(DERIVED_FILE_DIR)/Pods-SampleApp-checkManifestLockResult.txt",
+ "$(DERIVED_FILE_DIR)/Pods-SampleAppTests-checkManifestLockResult.txt",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
showEnvVarsInLog = 0;
};
- C59DA0FBD6956966B86A3779 /* [CP] Embed Pods Frameworks */ = {
+ C38B50BA6285516D6DCD4F65 /* [CP] Check Pods Manifest.lock */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
- "${PODS_ROOT}/Target Support Files/Pods-SampleApp-SampleAppTests/Pods-SampleApp-SampleAppTests-frameworks-${CONFIGURATION}-input-files.xcfilelist",
);
- name = "[CP] Embed Pods Frameworks";
+ inputPaths = (
+ "${PODS_PODFILE_DIR_PATH}/Podfile.lock",
+ "${PODS_ROOT}/Manifest.lock",
+ );
+ name = "[CP] Check Pods Manifest.lock";
outputFileListPaths = (
- "${PODS_ROOT}/Target Support Files/Pods-SampleApp-SampleAppTests/Pods-SampleApp-SampleAppTests-frameworks-${CONFIGURATION}-output-files.xcfilelist",
+ );
+ outputPaths = (
+ "$(DERIVED_FILE_DIR)/Pods-SampleAppCarPlay-checkManifestLockResult.txt",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
- shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-SampleApp-SampleAppTests/Pods-SampleApp-SampleAppTests-frameworks.sh\"\n";
+ shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
showEnvVarsInLog = 0;
};
- E235C05ADACE081382539298 /* [CP] Copy Pods Resources */ = {
+ C6773F2F6DC462D1292B9F42 /* [CP] Copy Pods Resources */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
- "${PODS_ROOT}/Target Support Files/Pods-SampleApp/Pods-SampleApp-resources-${CONFIGURATION}-input-files.xcfilelist",
+ "${PODS_ROOT}/Target Support Files/Pods-SampleAppTests/Pods-SampleAppTests-resources-${CONFIGURATION}-input-files.xcfilelist",
);
name = "[CP] Copy Pods Resources";
outputFileListPaths = (
- "${PODS_ROOT}/Target Support Files/Pods-SampleApp/Pods-SampleApp-resources-${CONFIGURATION}-output-files.xcfilelist",
+ "${PODS_ROOT}/Target Support Files/Pods-SampleAppTests/Pods-SampleAppTests-resources-${CONFIGURATION}-output-files.xcfilelist",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
- shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-SampleApp/Pods-SampleApp-resources.sh\"\n";
+ shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-SampleAppTests/Pods-SampleAppTests-resources.sh\"\n";
showEnvVarsInLog = 0;
};
- F6A41C54EA430FDDC6A6ED99 /* [CP] Copy Pods Resources */ = {
+ E235C05ADACE081382539298 /* [CP] Copy Pods Resources */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
- "${PODS_ROOT}/Target Support Files/Pods-SampleApp-SampleAppTests/Pods-SampleApp-SampleAppTests-resources-${CONFIGURATION}-input-files.xcfilelist",
+ "${PODS_ROOT}/Target Support Files/Pods-SampleAppCarPlay/Pods-SampleAppCarPlay-resources-${CONFIGURATION}-input-files.xcfilelist",
);
name = "[CP] Copy Pods Resources";
outputFileListPaths = (
- "${PODS_ROOT}/Target Support Files/Pods-SampleApp-SampleAppTests/Pods-SampleApp-SampleAppTests-resources-${CONFIGURATION}-output-files.xcfilelist",
+ "${PODS_ROOT}/Target Support Files/Pods-SampleAppCarPlay/Pods-SampleAppCarPlay-resources-${CONFIGURATION}-output-files.xcfilelist",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
- shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-SampleApp-SampleAppTests/Pods-SampleApp-SampleAppTests-resources.sh\"\n";
+ shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-SampleAppCarPlay/Pods-SampleAppCarPlay-resources.sh\"\n";
showEnvVarsInLog = 0;
};
/* End PBXShellScriptBuildPhase section */
@@ -402,17 +554,28 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
- 13B07FBC1A68108700A75B9A /* AppDelegate.mm in Sources */,
+ 52206E852C8B2FEC00B34D22 /* CarSceneDelegate.m in Sources */,
+ 52206E892C8B303700B34D22 /* AppDelegateCarPlay.m in Sources */,
+ 52206E862C8B2FEC00B34D22 /* PhoneSceneDelegate.m in Sources */,
13B07FC11A68108700A75B9A /* main.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
+ 2A20E80E2C8994DB00DB7ADA /* Sources */ = {
+ isa = PBXSourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 5205C1232C8B314F00D0FC6B /* AppDelegate.m in Sources */,
+ 2A20E8122C8994DB00DB7ADA /* main.m in Sources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
/* End PBXSourcesBuildPhase section */
/* Begin PBXTargetDependency section */
00E356F51AD99517003FC87E /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
- target = 13B07F861A680F5B00A75B9A /* SampleApp */;
+ target = 13B07F861A680F5B00A75B9A /* SampleAppCarPlay */;
targetProxy = 00E356F41AD99517003FC87E /* PBXContainerItemProxy */;
};
/* End PBXTargetDependency section */
@@ -420,7 +583,7 @@
/* Begin XCBuildConfiguration section */
00E356F61AD99517003FC87E /* Debug */ = {
isa = XCBuildConfiguration;
- baseConfigurationReference = 5B7EB9410499542E8C5724F5 /* Pods-SampleApp-SampleAppTests.debug.xcconfig */;
+ baseConfigurationReference = 011A22ED61B5A84DFF838AB2 /* Pods-SampleAppTests.debug.xcconfig */;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
GCC_PREPROCESSOR_DEFINITIONS = (
@@ -447,7 +610,7 @@
};
00E356F71AD99517003FC87E /* Release */ = {
isa = XCBuildConfiguration;
- baseConfigurationReference = 89C6BE57DB24E9ADA2F236DE /* Pods-SampleApp-SampleAppTests.release.xcconfig */;
+ baseConfigurationReference = A9D547919643FC330CF84EC3 /* Pods-SampleAppTests.release.xcconfig */;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
COPY_PHASE_STRIP = NO;
@@ -470,28 +633,142 @@
name = Release;
};
13B07F941A680F5B00A75B9A /* Debug */ = {
+ isa = XCBuildConfiguration;
+ baseConfigurationReference = 4735EA9AB0E8831D741A3A3E /* Pods-SampleAppCarPlay.debug.xcconfig */;
+ buildSettings = {
+ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
+ CLANG_ENABLE_MODULES = YES;
+ CODE_SIGN_ENTITLEMENTS = SampleApp/SampleApp.entitlements;
+ CODE_SIGN_IDENTITY = "Apple Development";
+ CODE_SIGN_STYLE = Manual;
+ CURRENT_PROJECT_VERSION = 1;
+ ENABLE_BITCODE = NO;
+ GCC_PREPROCESSOR_DEFINITIONS = (
+ "$(inherited)",
+ "COCOAPODS=1",
+ "CARPLAY=1",
+ );
+ INFOPLIST_FILE = "SampleApp/Info-CarPlay.plist";
+ LD_RUNPATH_SEARCH_PATHS = (
+ "$(inherited)",
+ "@executable_path/Frameworks",
+ );
+ MARKETING_VERSION = 1.0;
+ OTHER_CPLUSPLUSFLAGS = (
+ "$(inherited)",
+ "-DFOLLY_NO_CONFIG",
+ "-DFOLLY_MOBILE=1",
+ "-DFOLLY_USE_LIBCPP=1",
+ "-DFOLLY_CFG_NO_COROUTINES=1",
+ "-DFOLLY_HAVE_CLOCK_GETTIME=1",
+ "-Wno-comma",
+ "-Wno-shorten-64-to-32",
+ "-fmodules",
+ "-fcxx-modules",
+ );
+ OTHER_LDFLAGS = (
+ "$(inherited)",
+ "-ObjC",
+ "-lc++",
+ );
+ PRODUCT_BUNDLE_IDENTIFIER = "org.reactjs.native.example.--PRODUCT-NAME-rfc1034identifier-.carplay";
+ "PRODUCT_BUNDLE_IDENTIFIER[sdk=iphoneos*]" = org.reactjs.native.example.SampleApp.carplay;
+ PRODUCT_NAME = SampleApp;
+ PROVISIONING_PROFILE_SPECIFIER = "";
+ SWIFT_OPTIMIZATION_LEVEL = "-Onone";
+ SWIFT_VERSION = 5.0;
+ VERSIONING_SYSTEM = "apple-generic";
+ };
+ name = Debug;
+ };
+ 13B07F951A680F5B00A75B9A /* Release */ = {
+ isa = XCBuildConfiguration;
+ baseConfigurationReference = 750C10660A3F190F6072C5DA /* Pods-SampleAppCarPlay.release.xcconfig */;
+ buildSettings = {
+ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
+ CLANG_ENABLE_MODULES = YES;
+ CODE_SIGN_ENTITLEMENTS = SampleApp/SampleApp.entitlements;
+ CODE_SIGN_IDENTITY = "Apple Development";
+ CODE_SIGN_STYLE = Manual;
+ CURRENT_PROJECT_VERSION = 1;
+ GCC_PREPROCESSOR_DEFINITIONS = (
+ "$(inherited)",
+ "COCOAPODS=1",
+ "CARPLAY=1",
+ );
+ INFOPLIST_FILE = "SampleApp/Info-CarPlay.plist";
+ LD_RUNPATH_SEARCH_PATHS = (
+ "$(inherited)",
+ "@executable_path/Frameworks",
+ );
+ MARKETING_VERSION = 1.0;
+ OTHER_CPLUSPLUSFLAGS = (
+ "$(inherited)",
+ "-DNDEBUG",
+ "-DFOLLY_NO_CONFIG",
+ "-DFOLLY_MOBILE=1",
+ "-DFOLLY_USE_LIBCPP=1",
+ "-DFOLLY_CFG_NO_COROUTINES=1",
+ "-DFOLLY_HAVE_CLOCK_GETTIME=1",
+ "-Wno-comma",
+ "-Wno-shorten-64-to-32",
+ "-fmodules",
+ "-fcxx-modules",
+ );
+ OTHER_LDFLAGS = (
+ "$(inherited)",
+ "-ObjC",
+ "-lc++",
+ );
+ PRODUCT_BUNDLE_IDENTIFIER = "org.reactjs.native.example.--PRODUCT-NAME-rfc1034identifier-.carplay";
+ "PRODUCT_BUNDLE_IDENTIFIER[sdk=iphoneos*]" = org.reactjs.native.example.SampleApp.carplay;
+ PRODUCT_NAME = SampleApp;
+ PROVISIONING_PROFILE_SPECIFIER = "";
+ SWIFT_VERSION = 5.0;
+ VERSIONING_SYSTEM = "apple-generic";
+ };
+ name = Release;
+ };
+ 2A20E81E2C8994DB00DB7ADA /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 3B4392A12AC88292D35C810B /* Pods-SampleApp.debug.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
+ CODE_SIGN_ENTITLEMENTS = "";
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Manual;
CURRENT_PROJECT_VERSION = 1;
ENABLE_BITCODE = NO;
+ GCC_PREPROCESSOR_DEFINITIONS = (
+ "$(inherited)",
+ "COCOAPODS=1",
+ );
INFOPLIST_FILE = SampleApp/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 1.0;
+ OTHER_CPLUSPLUSFLAGS = (
+ "$(inherited)",
+ "-DFOLLY_NO_CONFIG",
+ "-DFOLLY_MOBILE=1",
+ "-DFOLLY_USE_LIBCPP=1",
+ "-DFOLLY_CFG_NO_COROUTINES=1",
+ "-DFOLLY_HAVE_CLOCK_GETTIME=1",
+ "-Wno-comma",
+ "-Wno-shorten-64-to-32",
+ "-fmodules",
+ "-fcxx-modules",
+ );
OTHER_LDFLAGS = (
"$(inherited)",
"-ObjC",
"-lc++",
);
PRODUCT_BUNDLE_IDENTIFIER = "org.reactjs.native.example.$(PRODUCT_NAME:rfc1034identifier)";
- PRODUCT_NAME = SampleApp;
+ PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0;
@@ -499,28 +776,46 @@
};
name = Debug;
};
- 13B07F951A680F5B00A75B9A /* Release */ = {
+ 2A20E81F2C8994DB00DB7ADA /* Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 5709B34CF0A7D63546082F79 /* Pods-SampleApp.release.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
+ CODE_SIGN_ENTITLEMENTS = "";
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Manual;
CURRENT_PROJECT_VERSION = 1;
+ GCC_PREPROCESSOR_DEFINITIONS = (
+ "$(inherited)",
+ "COCOAPODS=1",
+ );
INFOPLIST_FILE = SampleApp/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 1.0;
+ OTHER_CPLUSPLUSFLAGS = (
+ "$(inherited)",
+ "-DNDEBUG",
+ "-DFOLLY_NO_CONFIG",
+ "-DFOLLY_MOBILE=1",
+ "-DFOLLY_USE_LIBCPP=1",
+ "-DFOLLY_CFG_NO_COROUTINES=1",
+ "-DFOLLY_HAVE_CLOCK_GETTIME=1",
+ "-Wno-comma",
+ "-Wno-shorten-64-to-32",
+ "-fmodules",
+ "-fcxx-modules",
+ );
OTHER_LDFLAGS = (
"$(inherited)",
"-ObjC",
"-lc++",
);
PRODUCT_BUNDLE_IDENTIFIER = "org.reactjs.native.example.$(PRODUCT_NAME:rfc1034identifier)";
- PRODUCT_NAME = SampleApp;
+ PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
SWIFT_VERSION = 5.0;
VERSIONING_SYSTEM = "apple-generic";
@@ -692,7 +987,7 @@
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
- 13B07F931A680F5B00A75B9A /* Build configuration list for PBXNativeTarget "SampleApp" */ = {
+ 13B07F931A680F5B00A75B9A /* Build configuration list for PBXNativeTarget "SampleAppCarPlay" */ = {
isa = XCConfigurationList;
buildConfigurations = (
13B07F941A680F5B00A75B9A /* Debug */,
@@ -701,6 +996,15 @@
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
+ 2A20E81D2C8994DB00DB7ADA /* Build configuration list for PBXNativeTarget "SampleApp" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ 2A20E81E2C8994DB00DB7ADA /* Debug */,
+ 2A20E81F2C8994DB00DB7ADA /* Release */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
83CBB9FA1A601CBA00E9B192 /* Build configuration list for PBXProject "SampleApp" */ = {
isa = XCConfigurationList;
buildConfigurations = (
diff --git a/example/ios/SampleApp.xcodeproj/xcshareddata/xcschemes/SampleApp.xcscheme b/example/ios/SampleApp.xcodeproj/xcshareddata/xcschemes/SampleApp.xcscheme
index 1b6ff67..84ce098 100644
--- a/example/ios/SampleApp.xcodeproj/xcshareddata/xcschemes/SampleApp.xcscheme
+++ b/example/ios/SampleApp.xcodeproj/xcshareddata/xcschemes/SampleApp.xcscheme
@@ -1,10 +1,11 @@
+ LastUpgradeVersion = "1530"
+ version = "1.7">
+ buildImplicitDependencies = "YES"
+ buildArchitectures = "Automatic">
@@ -26,19 +27,8 @@
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
- shouldUseLaunchSchemeArgsEnv = "YES">
-
-
-
-
-
-
+ shouldUseLaunchSchemeArgsEnv = "YES"
+ shouldAutocreateTestPlan = "YES">
@@ -71,7 +61,7 @@
runnableDebuggingMode = "0">
diff --git a/example/ios/SampleApp.xcodeproj/xcshareddata/xcschemes/SampleAppCarPlay.xcscheme b/example/ios/SampleApp.xcodeproj/xcshareddata/xcschemes/SampleAppCarPlay.xcscheme
new file mode 100644
index 0000000..a69286e
--- /dev/null
+++ b/example/ios/SampleApp.xcodeproj/xcshareddata/xcschemes/SampleAppCarPlay.xcscheme
@@ -0,0 +1,88 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/example/ios/SampleApp/AppDelegate.mm b/example/ios/SampleApp/AppDelegate.m
similarity index 84%
rename from example/ios/SampleApp/AppDelegate.mm
rename to example/ios/SampleApp/AppDelegate.m
index e029e96..09cb90d 100644
--- a/example/ios/SampleApp/AppDelegate.mm
+++ b/example/ios/SampleApp/AppDelegate.m
@@ -26,7 +26,7 @@ - (BOOL)application:(UIApplication *)application
// You can add your custom initial props in the dictionary below.
// They will be passed down to the ViewController used by React Native.
self.initialProps = @{};
-
+
// Note: Ensure that you have copied the Keys.plist.sample to Keys.plist
// and have added the correct API_KEY value to the file.
//
@@ -34,11 +34,10 @@ - (BOOL)application:(UIApplication *)application
NSString *path = [[NSBundle mainBundle] pathForResource:@"Keys" ofType:@"plist"];
NSDictionary *keysDictionary = [NSDictionary dictionaryWithContentsOfFile:path];
NSString *api_key = [keysDictionary objectForKey:@"API_KEY"];
-
+
[GMSServices provideAPIKey:api_key];
[GMSServices setMetalRendererEnabled:YES];
- return [super application:application
- didFinishLaunchingWithOptions:launchOptions];
+ return [super application:application didFinishLaunchingWithOptions:launchOptions];
}
- (NSURL *)sourceURLForBridge:(RCTBridge *)bridge {
@@ -47,11 +46,9 @@ - (NSURL *)sourceURLForBridge:(RCTBridge *)bridge {
- (NSURL *)bundleURL {
#if DEBUG
- return
- [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index"];
+ return [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index"];
#else
- return [[NSBundle mainBundle] URLForResource:@"main"
- withExtension:@"jsbundle"];
+ return [[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"];
#endif
}
diff --git a/example/ios/SampleApp/AppDelegateCarPlay.h b/example/ios/SampleApp/AppDelegateCarPlay.h
new file mode 100644
index 0000000..e25833c
--- /dev/null
+++ b/example/ios/SampleApp/AppDelegateCarPlay.h
@@ -0,0 +1,23 @@
+/**
+ * Copyright 2023 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#import
+#import
+
+@interface AppDelegateCarPlay : RCTAppDelegate
+@property(nonatomic, strong) UIWindow *window;
+@property(nonatomic, strong) RCTBridge *bridge;
+@property(nonatomic, strong) RCTRootView *rootView;
+@end
diff --git a/example/ios/SampleApp/AppDelegateCarPlay.m b/example/ios/SampleApp/AppDelegateCarPlay.m
new file mode 100644
index 0000000..9e75dbe
--- /dev/null
+++ b/example/ios/SampleApp/AppDelegateCarPlay.m
@@ -0,0 +1,83 @@
+/**
+ * Copyright 2023 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#import "AppDelegateCarPlay.h"
+
+#import
+#import
+#import
+#import
+#import "CarSceneDelegate.h"
+#import "PhoneSceneDelegate.h"
+
+@implementation AppDelegateCarPlay
+
+- (BOOL)application:(UIApplication *)application
+ didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
+ self.moduleName = @"SampleApp";
+
+ // Note: Ensure that you have copied the Keys.plist.sample to Keys.plist
+ // and have added the correct API_KEY value to the file.
+ //
+ // Get the path for the Keys.plist file in the main bundle and read API_KEY.
+ NSString *path = [[NSBundle mainBundle] pathForResource:@"Keys" ofType:@"plist"];
+ NSDictionary *keysDictionary = [NSDictionary dictionaryWithContentsOfFile:path];
+ NSString *api_key = [keysDictionary objectForKey:@"API_KEY"];
+
+ [GMSServices provideAPIKey:api_key];
+ [GMSServices setMetalRendererEnabled:YES];
+ RCTBridge *bridge = [[RCTBridge alloc] initWithDelegate:self launchOptions:launchOptions];
+ self.rootView = [[RCTRootView alloc] initWithBridge:bridge
+ moduleName:self.moduleName
+ initialProperties:nil];
+ return YES;
+}
+
+- (UISceneConfiguration *)application:(UIApplication *)application
+ configurationForConnectingSceneSession:(UISceneSession *)connectingSceneSession
+ options:(UISceneConnectionOptions *)options {
+ if ([connectingSceneSession.role
+ isEqualToString:@"CPTemplateApplicationSceneSessionRoleApplication"]) {
+ UISceneConfiguration *scene =
+ [[UISceneConfiguration alloc] initWithName:@"CarPlay"
+ sessionRole:connectingSceneSession.role];
+ scene.delegateClass = [CarSceneDelegate class];
+ return scene;
+ } else {
+ UISceneConfiguration *scene =
+ [[UISceneConfiguration alloc] initWithName:@"Phone"
+ sessionRole:connectingSceneSession.role];
+ scene.delegateClass = [PhoneSceneDelegate class];
+ return scene;
+ }
+}
+
+- (void)application:(UIApplication *)application
+ didDiscardSceneSessions:(NSSet *)sceneSessions {
+}
+
+- (NSURL *)sourceURLForBridge:(RCTBridge *)bridge {
+ return [self bundleURL];
+}
+
+- (NSURL *)bundleURL {
+#if DEBUG
+ return [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index"];
+#else
+ return [[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"];
+#endif
+}
+
+@end
diff --git a/example/ios/SampleApp/CarSceneDelegate.h b/example/ios/SampleApp/CarSceneDelegate.h
new file mode 100644
index 0000000..c43a3d8
--- /dev/null
+++ b/example/ios/SampleApp/CarSceneDelegate.h
@@ -0,0 +1,20 @@
+/**
+ * Copyright 2023 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#import
+#import "BaseCarSceneDelegate.h"
+
+@interface CarSceneDelegate : BaseCarSceneDelegate
+@end
diff --git a/example/ios/SampleApp/CarSceneDelegate.m b/example/ios/SampleApp/CarSceneDelegate.m
new file mode 100644
index 0000000..af87193
--- /dev/null
+++ b/example/ios/SampleApp/CarSceneDelegate.m
@@ -0,0 +1,42 @@
+/**
+ * Copyright 2023 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#import "CarSceneDelegate.h"
+#import
+#import
+#import "NavAutoModule.h"
+#import "NavModule.h"
+
+@implementation CarSceneDelegate
+
+- (CPMapTemplate *)getTemplate {
+ CPMapTemplate *template = [[CPMapTemplate alloc] init];
+ [template showPanningInterfaceAnimated:YES];
+
+ CPBarButton *customButton = [[CPBarButton alloc]
+ initWithTitle:@"Custom Event"
+ handler:^(CPBarButton *_Nonnull button) {
+ NSMutableDictionary *dictionary = [[NSMutableDictionary alloc] init];
+ dictionary[@"sampleDataKey"] = @"sampleDataContent";
+ [[NavAutoModule getOrCreateSharedInstance] onCustomNavigationAutoEvent:@"sampleEvent"
+ data:dictionary];
+ }];
+
+ template.leadingNavigationBarButtons = @[ customButton ];
+ template.trailingNavigationBarButtons = @[];
+ return template;
+}
+
+@end
diff --git a/example/ios/SampleApp/Info-CarPlay.plist b/example/ios/SampleApp/Info-CarPlay.plist
new file mode 100644
index 0000000..09ed6a7
--- /dev/null
+++ b/example/ios/SampleApp/Info-CarPlay.plist
@@ -0,0 +1,98 @@
+
+
+
+
+ CFBundleDevelopmentRegion
+ en
+ CFBundleDisplayName
+ samplenavsdk-carplay
+ CFBundleExecutable
+ $(EXECUTABLE_NAME)
+ CFBundleIdentifier
+ $(PRODUCT_BUNDLE_IDENTIFIER)
+ CFBundleInfoDictionaryVersion
+ 6.0
+ CFBundleName
+ $(PRODUCT_NAME)
+ CFBundlePackageType
+ APPL
+ CFBundleShortVersionString
+ $(MARKETING_VERSION)
+ CFBundleSignature
+ ????
+ CFBundleVersion
+ $(CURRENT_PROJECT_VERSION)
+ LSRequiresIPhoneOS
+
+ NSAppTransportSecurity
+
+ NSExceptionDomains
+
+ localhost
+
+ NSExceptionAllowsInsecureHTTPLoads
+
+
+
+
+ NSLocationAlwaysAndWhenInUseUsageDescription
+ [Enter any description related to the key]
+ NSLocationWhenInUseUsageDescription
+ [Add your description here]
+ UIApplicationSceneManifest
+
+ UIApplicationSupportsMultipleScenes
+
+ UISceneConfigurations
+
+ CPTemplateApplicationSceneSessionRoleApplication
+
+
+ UISceneClassName
+ CPTemplateApplicationScene
+ UISceneConfigurationName
+ CarPlay
+ UISceneDelegateClassName
+ $(PRODUCT_MODULE_NAME).CarSceneDelegate
+
+
+ UIWindowSceneSessionRoleApplication
+
+
+ UISceneClassName
+ UIWindowScene
+ UISceneConfigurationName
+ Phone
+ UISceneDelegateClassName
+ $(PRODUCT_MODULE_NAME).PhoneSceneDelegate
+
+
+
+
+ UIBackgroundModes
+
+ location
+ audio
+ remote-notification
+ fetch
+
+ UILaunchStoryboardName
+ LaunchScreen
+ UIRequiredDeviceCapabilities
+
+ armv7
+
+ UISupportedInterfaceOrientations
+
+ UIInterfaceOrientationPortrait
+ UIInterfaceOrientationLandscapeLeft
+ UIInterfaceOrientationLandscapeRight
+
+ UIViewControllerBasedStatusBarAppearance
+
+ [NSLocationAlwaysUsageDescription]
+ Enter any description related to the key
+ [NSLocationWhenInUseUsageDescription]
+ Enter any description related to the key
+
+
diff --git a/example/ios/SampleApp/PhoneSceneDelegate.h b/example/ios/SampleApp/PhoneSceneDelegate.h
new file mode 100644
index 0000000..860d1a2
--- /dev/null
+++ b/example/ios/SampleApp/PhoneSceneDelegate.h
@@ -0,0 +1,24 @@
+/**
+ * Copyright 2023 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#import
+#import
+#import "AppDelegateCarPlay.h"
+
+@interface PhoneSceneDelegate : UIResponder
+
+@property(nonatomic, strong) UIWindow *window;
+
+@end
diff --git a/example/ios/SampleApp/PhoneSceneDelegate.m b/example/ios/SampleApp/PhoneSceneDelegate.m
new file mode 100644
index 0000000..5e552d2
--- /dev/null
+++ b/example/ios/SampleApp/PhoneSceneDelegate.m
@@ -0,0 +1,47 @@
+/**
+ * Copyright 2023 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#import "PhoneSceneDelegate.h"
+#import
+#import
+#import "AppDelegateCarPlay.h"
+
+@implementation PhoneSceneDelegate
+
+- (void)scene:(UIScene *)scene
+ willConnectToSession:(UISceneSession *)session
+ options:(UISceneConnectionOptions *)connectionOptions {
+ AppDelegateCarPlay *appDelegate =
+ (AppDelegateCarPlay *)[UIApplication sharedApplication].delegate;
+ if (!appDelegate) {
+ return;
+ }
+
+ UIWindowScene *windowScene = (UIWindowScene *)scene;
+ if (!windowScene) {
+ return;
+ }
+
+ UIViewController *rootViewController = [[UIViewController alloc] init];
+ rootViewController.view = appDelegate.rootView;
+
+ UIWindow *window = [[UIWindow alloc] initWithWindowScene:windowScene];
+ window.rootViewController = rootViewController;
+ self.window = window;
+ [appDelegate setWindow:window];
+ [window makeKeyAndVisible];
+}
+
+@end
diff --git a/example/ios/SampleApp/SampleApp.entitlements b/example/ios/SampleApp/SampleApp.entitlements
new file mode 100644
index 0000000..d74cc08
--- /dev/null
+++ b/example/ios/SampleApp/SampleApp.entitlements
@@ -0,0 +1,8 @@
+
+
+
+
+ com.apple.developer.carplay-maps
+
+
+
diff --git a/example/ios/SampleApp/main.m b/example/ios/SampleApp/main.m
index 3465d6b..9fd1998 100644
--- a/example/ios/SampleApp/main.m
+++ b/example/ios/SampleApp/main.m
@@ -14,10 +14,18 @@
#import
+#if defined(CARPLAY)
+#import "AppDelegateCarPlay.h"
+#else
#import "AppDelegate.h"
+#endif
int main(int argc, char *argv[]) {
@autoreleasepool {
+#if defined(CARPLAY)
+ return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegateCarPlay class]));
+#else
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
+#endif
}
}
diff --git a/example/package.json b/example/package.json
index 0a36b91..4eb3b64 100644
--- a/example/package.json
+++ b/example/package.json
@@ -8,6 +8,7 @@
"ios": "react-native run-ios",
"lint": "eslint .",
"test": "jest",
+ "prepare": "cd ios && pod install",
"build:android": "cd android && ./gradlew assembleDebug --no-daemon --console=plain -PreactNativeArchitectures=arm64-v8a",
"build:ios": "cd ios && xcodebuild -workspace SampleApp.xcworkspace -scheme SampleApp -configuration Debug -sdk iphonesimulator CC=clang CPLUSPLUS=clang++ LD=clang LDPLUSPLUS=clang++ GCC_OPTIMIZATION_LEVEL=0 GCC_PRECOMPILE_PREFIX_HEADER=YES ASSETCATALOG_COMPILER_OPTIMIZATION=time DEBUG_INFORMATION_FORMAT=dwarf COMPILER_INDEX_STORE_ENABLE=NO"
},
diff --git a/example/src/App.tsx b/example/src/App.tsx
index a4389ea..66364e8 100644
--- a/example/src/App.tsx
+++ b/example/src/App.tsx
@@ -22,8 +22,8 @@ import {
} from '@react-navigation/native';
import { createStackNavigator } from '@react-navigation/stack';
import { View, Button, StyleSheet } from 'react-native';
-import NavigationScreen from './NavigationScreen';
-import MultipleMapsScreen from './MultipleMapsScreen';
+import NavigationScreen from './screens/NavigationScreen';
+import MultipleMapsScreen from './screens/MultipleMapsScreen';
import {
NavigationProvider,
type TermsAndConditionsDialogOptions,
diff --git a/example/src/mapsControls.tsx b/example/src/controls/mapsControls.tsx
similarity index 99%
rename from example/src/mapsControls.tsx
rename to example/src/controls/mapsControls.tsx
index 3c4bed7..b1fc031 100644
--- a/example/src/mapsControls.tsx
+++ b/example/src/controls/mapsControls.tsx
@@ -19,7 +19,7 @@ import React, { useEffect, useState } from 'react';
import { Button, Switch, Text, TextInput, View } from 'react-native';
import SelectDropdown from 'react-native-select-dropdown';
-import styles from './styles';
+import styles from '../styles';
import {
type MapViewController,
MapType,
diff --git a/example/src/navigationControls.tsx b/example/src/controls/navigationControls.tsx
similarity index 99%
rename from example/src/navigationControls.tsx
rename to example/src/controls/navigationControls.tsx
index 9a18795..012c970 100644
--- a/example/src/navigationControls.tsx
+++ b/example/src/controls/navigationControls.tsx
@@ -35,7 +35,7 @@ import {
} from '@googlemaps/react-native-navigation-sdk';
import SelectDropdown from 'react-native-select-dropdown';
-import styles from './styles';
+import styles from '../styles';
export interface NavigationControlsProps {
readonly navigationController: NavigationController;
diff --git a/example/src/overlayModal.tsx b/example/src/helpers/overlayModal.tsx
similarity index 100%
rename from example/src/overlayModal.tsx
rename to example/src/helpers/overlayModal.tsx
diff --git a/example/src/MultipleMapsScreen.tsx b/example/src/screens/MultipleMapsScreen.tsx
similarity index 97%
rename from example/src/MultipleMapsScreen.tsx
rename to example/src/screens/MultipleMapsScreen.tsx
index 2300a3f..1a06ec5 100644
--- a/example/src/MultipleMapsScreen.tsx
+++ b/example/src/screens/MultipleMapsScreen.tsx
@@ -17,31 +17,33 @@
import React, { useEffect, useState, useMemo, useCallback } from 'react';
import { Button, View } from 'react-native';
import Snackbar from 'react-native-snackbar';
-import MapsControls from './mapsControls';
-import NavigationControls from './navigationControls';
-import styles from './styles';
+
import {
- type MapViewController,
- type NavigationViewController,
- type ArrivalEvent,
NavigationInitErrorCode,
- type Location,
+ NavigationView,
RouteStatus,
+ type ArrivalEvent,
+ type Circle,
+ type LatLng,
+ type Location,
+ type MapViewCallbacks,
+ type MapViewController,
type Marker,
+ type NavigationCallbacks,
type NavigationViewCallbacks,
- type MapViewCallbacks,
+ type NavigationViewController,
type Polygon,
- type Circle,
type Polyline,
- type LatLng,
- type NavigationCallbacks,
useNavigation,
MapView,
- NavigationView,
} from '@googlemaps/react-native-navigation-sdk';
-import usePermissions from './checkPermissions';
-import OverlayModal from './overlayModal';
+import MapsControls from '../controls/mapsControls';
+import NavigationControls from '../controls/navigationControls';
+import OverlayModal from '../helpers/overlayModal';
+import styles from '../styles';
+import usePermissions from '../checkPermissions';
+// Utility function for showing Snackbar
const showSnackbar = (text: string, duration = Snackbar.LENGTH_SHORT) => {
Snackbar.show({ text, duration });
};
diff --git a/example/src/NavigationScreen.tsx b/example/src/screens/NavigationScreen.tsx
similarity index 83%
rename from example/src/NavigationScreen.tsx
rename to example/src/screens/NavigationScreen.tsx
index 2e2cdb8..cd14d7c 100644
--- a/example/src/NavigationScreen.tsx
+++ b/example/src/screens/NavigationScreen.tsx
@@ -17,29 +17,33 @@
import React, { useEffect, useState, useMemo, useCallback } from 'react';
import { Button, Switch, Text, View } from 'react-native';
import Snackbar from 'react-native-snackbar';
-import styles from './styles';
+
import {
+ NavigationInitErrorCode,
+ NavigationView,
+ RouteStatus,
+ type ArrivalEvent,
+ type Circle,
+ type LatLng,
+ type Location,
+ type MapViewCallbacks,
type MapViewController,
- type NavigationViewController,
type Marker,
+ type NavigationAutoCallbacks,
+ type NavigationCallbacks,
type NavigationViewCallbacks,
- type MapViewCallbacks,
+ type NavigationViewController,
type Polygon,
- type Circle,
type Polyline,
- type LatLng,
- type NavigationCallbacks,
useNavigation,
- NavigationInitErrorCode,
- RouteStatus,
- type ArrivalEvent,
- type Location,
+ useNavigationAuto,
+ type CustomNavigationAutoEvent,
} from '@googlemaps/react-native-navigation-sdk';
-import usePermissions from './checkPermissions';
-import MapsControls from './mapsControls';
-import NavigationControls from './navigationControls';
-import OverlayModal from './overlayModal';
-import { NavigationView } from '../../src/navigation/navigationView/navigationView';
+import MapsControls from '../controls/mapsControls';
+import NavigationControls from '../controls/navigationControls';
+import OverlayModal from '../helpers/overlayModal';
+import styles from '../styles';
+import usePermissions from '../checkPermissions';
// Utility function for showing Snackbar
const showSnackbar = (text: string, duration = Snackbar.LENGTH_SHORT) => {
@@ -50,6 +54,7 @@ enum OverlayType {
None = 'None',
NavControls = 'NavControls',
MapControls = 'MapControls',
+ AutoMapControls = 'AutoMapControls',
}
const marginAmount = 50;
@@ -62,6 +67,14 @@ const NavigationScreen = () => {
const [navigationViewController, setNavigationViewController] =
useState(null);
+ const {
+ mapViewAutoController,
+ addListeners: addAutoListener,
+ removeListeners: removeAutoListeners,
+ } = useNavigationAuto();
+ const [mapViewAutoAvailable, setMapViewAutoAvailable] =
+ useState(false);
+
const { navigationController, addListeners, removeListeners } =
useNavigation();
@@ -229,6 +242,27 @@ const NavigationScreen = () => {
]
);
+ const navigationAutoCallbacks: NavigationAutoCallbacks = useMemo(
+ () => ({
+ onCustomNavigationAutoEvent: (event: CustomNavigationAutoEvent) => {
+ console.log('onCustomNavigationAutoEvent:', event);
+ },
+ onAutoScreenAvailabilityChanged: (available: boolean) => {
+ console.log('onAutoScreenAvailabilityChanged:', available);
+ setMapViewAutoAvailable(available);
+ },
+ }),
+ []
+ );
+
+ useEffect(() => {
+ (async () => {
+ const isAvailable = await mapViewAutoController.isAutoScreenAvailable();
+ console.log('isAutoScreenAvailable:', isAvailable);
+ setMapViewAutoAvailable(isAvailable);
+ })();
+ }, [mapViewAutoController]);
+
useEffect(() => {
addListeners(navigationCallbacks);
return () => {
@@ -236,6 +270,13 @@ const NavigationScreen = () => {
};
}, [navigationCallbacks, addListeners, removeListeners]);
+ useEffect(() => {
+ addAutoListener(navigationAutoCallbacks);
+ return () => {
+ removeAutoListeners(navigationAutoCallbacks);
+ };
+ }, [navigationAutoCallbacks, addAutoListener, removeAutoListeners]);
+
const onMapReady = useCallback(async () => {
console.log('Map is ready, initializing navigator...');
try {
@@ -258,6 +299,10 @@ const NavigationScreen = () => {
setOverlayType(OverlayType.MapControls);
}, [setOverlayType]);
+ const onShowAutoMapsControlsClick = useCallback(() => {
+ setOverlayType(OverlayType.AutoMapControls);
+ }, [setOverlayType]);
+
const navigationViewCallbacks: NavigationViewCallbacks = {
onRecenterButtonClick,
};
@@ -343,6 +388,15 @@ const NavigationScreen = () => {
)}
+ {mapViewAutoAvailable && mapViewAutoController != null && (
+
+
+
+ )}
+
{
disabled={!navigationInitialized}
/>
+ {mapViewAutoAvailable && (
+
+ )}
Margin
+#import "NavViewController.h"
+
+@interface BaseCarSceneDelegate
+ : UIResponder
+
+@property(nonatomic, strong) CPInterfaceController *interfaceController;
+@property(nonatomic, strong) CPWindow *carWindow;
+@property(nonatomic, strong) CPMapTemplate *mapTemplate;
+@property(nonatomic, strong) NavViewController *navViewController;
+@property(nonatomic, assign) BOOL sessionAttached;
+@property(nonatomic, assign) BOOL viewControllerRegistered;
+
+- (CPMapTemplate *)getTemplate;
+
+@end
diff --git a/ios/react-native-navigation-sdk/BaseCarSceneDelegate.m b/ios/react-native-navigation-sdk/BaseCarSceneDelegate.m
new file mode 100644
index 0000000..3f48441
--- /dev/null
+++ b/ios/react-native-navigation-sdk/BaseCarSceneDelegate.m
@@ -0,0 +1,125 @@
+/**
+ * Copyright 2023 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#import "BaseCarSceneDelegate.h"
+#import
+#import
+#import "NavAutoModule.h"
+#import "NavModule.h"
+
+@implementation BaseCarSceneDelegate
+
+- (void)templateApplicationScene:(CPTemplateApplicationScene *)templateApplicationScene
+ didConnectInterfaceController:(CPInterfaceController *)interfaceController
+ toWindow:(CPWindow *)window {
+ self.interfaceController = interfaceController;
+ self.carWindow = window;
+ self.mapTemplate = [self getTemplate];
+ self.mapTemplate.mapDelegate = self;
+
+ self.navViewController = [[NavViewController alloc] init];
+ self.carWindow.rootViewController = self.navViewController;
+ [self.interfaceController setRootTemplate:self.mapTemplate animated:YES completion:nil];
+ [NavModule registerNavigationSessionReadyCallback:^{
+ [self attachSession];
+ }];
+ [NavModule registerNavigationSessionDisposedCallback:^{
+ self->_sessionAttached = NO;
+ }];
+ [NavAutoModule registerNavAutoModuleReadyCallback:^{
+ [self registerViewController];
+ }];
+}
+
+- (CPMapTemplate *)getTemplate {
+ CPMapTemplate *template = [[CPMapTemplate alloc] init];
+ [template showPanningInterfaceAnimated:YES];
+ return template;
+}
+
+- (void)templateApplicationScene:(CPTemplateApplicationScene *)templateApplicationScene
+ didDisconnectInterfaceController:(CPInterfaceController *)interfaceController {
+ [self unRegisterViewController];
+ self.interfaceController = nil;
+ self.carWindow = nil;
+ self.mapTemplate = nil;
+ self.navViewController = nil;
+ self.viewControllerRegistered = NO;
+ self.sessionAttached = NO;
+}
+
+- (void)sceneDidBecomeActive:(UIScene *)scene {
+ [self attachSession];
+ [self registerViewController];
+}
+
+- (void)attachSession {
+ if ([NavModule sharedInstance] != nil && [[NavModule sharedInstance] hasSession] &&
+ !_sessionAttached) {
+ [self.navViewController attachToNavigationSession:[[NavModule sharedInstance] getSession]];
+ [self.navViewController setHeaderEnabled:NO];
+ [self.navViewController setRecenterButtonEnabled:NO];
+ [self.navViewController setFooterEnabled:NO];
+ [self.navViewController setSpeedometerEnabled:NO];
+ _sessionAttached = YES;
+ }
+}
+
+- (void)registerViewController {
+ if ([NavAutoModule sharedInstance] != nil && !_viewControllerRegistered) {
+ [[NavAutoModule sharedInstance] registerViewController:self.navViewController];
+ _viewControllerRegistered = YES;
+ }
+}
+
+- (void)unRegisterViewController {
+ if ([NavAutoModule sharedInstance] != nil && _viewControllerRegistered) {
+ [[NavAutoModule sharedInstance] unRegisterViewController];
+ _viewControllerRegistered = NO;
+ }
+}
+
+#pragma mark - CPMapTemplateDelegate
+
+- (void)mapTemplate:(CPMapTemplate *)mapTemplate panWithDirection:(CPPanDirection)direction {
+ CGPoint scrollAmount = [self scrollAmountForPanDirection:direction];
+ GMSCameraUpdate *scroll = [GMSCameraUpdate scrollByX:scrollAmount.x Y:scrollAmount.y];
+ [self.navViewController animateCamera:scroll];
+}
+
+- (CGPoint)scrollAmountForPanDirection:(CPPanDirection)direction {
+ static const CGFloat scrollDistance = 80.;
+ CGPoint scrollAmount = {0., 0.};
+ if (direction & CPPanDirectionLeft) {
+ scrollAmount.x = -scrollDistance;
+ }
+ if (direction & CPPanDirectionRight) {
+ scrollAmount.x = scrollDistance;
+ }
+ if (direction & CPPanDirectionUp) {
+ scrollAmount.y = -scrollDistance;
+ }
+ if (direction & CPPanDirectionDown) {
+ scrollAmount.y = scrollDistance;
+ }
+ if (scrollAmount.x != 0 && scrollAmount.y != 0) {
+ // Adjust length if scrolling diagonally.
+ scrollAmount =
+ CGPointMake(scrollAmount.x * (CGFloat)M_SQRT1_2, scrollAmount.y * (CGFloat)M_SQRT1_2);
+ }
+ return scrollAmount;
+}
+
+@end
diff --git a/ios/react-native-navigation-sdk/NavAutoEventDispatcher.h b/ios/react-native-navigation-sdk/NavAutoEventDispatcher.h
new file mode 100644
index 0000000..9b217a4
--- /dev/null
+++ b/ios/react-native-navigation-sdk/NavAutoEventDispatcher.h
@@ -0,0 +1,34 @@
+/**
+ * Copyright 2023 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef NavAutoEventDispatcher_h
+#define NavAutoEventDispatcher_h
+
+#import
+#import
+
+NS_ASSUME_NONNULL_BEGIN
+
+@interface NavAutoEventDispatcher : RCTEventEmitter
+
+- (void)sendEventName:(NSString *)eventName body:(id)body;
+- (bool)hasListeners;
+
+@end
+
+NS_ASSUME_NONNULL_END
+
+#endif /* NavAutoEventDispatcher_h */
diff --git a/ios/react-native-navigation-sdk/NavAutoEventDispatcher.m b/ios/react-native-navigation-sdk/NavAutoEventDispatcher.m
new file mode 100644
index 0000000..bc1d68c
--- /dev/null
+++ b/ios/react-native-navigation-sdk/NavAutoEventDispatcher.m
@@ -0,0 +1,65 @@
+/**
+ * Copyright 2023 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#import "NavAutoEventDispatcher.h"
+
+@implementation NavAutoEventDispatcher {
+ bool hasListeners;
+}
+
+RCT_EXPORT_MODULE(NavAutoEventDispatcher);
+
++ (id)allocWithZone:(NSZone *)zone {
+ static NavAutoEventDispatcher *sharedInstance = nil;
+ static dispatch_once_t onceToken;
+ dispatch_once(&onceToken, ^{
+ sharedInstance = [super allocWithZone:zone];
+ });
+ return sharedInstance;
+}
+
+- (NSArray *)supportedEvents {
+ return @[
+ @"onCustomNavigationAutoEvent",
+ @"onAutoScreenAvailabilityChanged",
+ ];
+}
+
+// Will be called when this module's first listener is added.
+- (void)startObserving {
+ hasListeners = YES;
+ // Set up any upstream listeners or background tasks as necessary
+}
+
+// Will be called when this module's last listener is removed, or on dealloc.
+- (void)stopObserving {
+ hasListeners = NO;
+ // Remove upstream listeners, stop unnecessary background tasks
+}
+
+- (bool)hasListeners {
+ return hasListeners;
+}
+
+- (void)sendEventName:(NSString *)eventName body:(id)body {
+ if (hasListeners) {
+ [self sendEventWithName:eventName body:body];
+ } else {
+ NSLog(@"NavAutoEventDispatcher sendEventName called without listeners: %@", eventName);
+ }
+}
+
+@end
diff --git a/ios/react-native-navigation-sdk/NavAutoModule.h b/ios/react-native-navigation-sdk/NavAutoModule.h
new file mode 100644
index 0000000..8299061
--- /dev/null
+++ b/ios/react-native-navigation-sdk/NavAutoModule.h
@@ -0,0 +1,38 @@
+/**
+ * Copyright 2023 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#import
+#import "NavViewController.h"
+
+NS_ASSUME_NONNULL_BEGIN
+
+@interface NavAutoModule : NSObject
+@property(nonatomic, strong, nullable) NavViewController *viewController;
+
+typedef void (^NavAutoModuleReadyCallback)(void);
+
+- (void)registerViewController:(NavViewController *)vc;
+- (void)unRegisterViewController;
++ (void)registerNavAutoModuleReadyCallback:(NavAutoModuleReadyCallback)callback;
++ (void)unregisterNavAutoModuleReadyCallback;
+- (void)onCustomNavigationAutoEvent:(NSString *)type data:(nullable NSDictionary *)data;
+
+// Class method to access the singleton instance
++ (instancetype)sharedInstance;
++ (instancetype)getOrCreateSharedInstance;
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/ios/react-native-navigation-sdk/NavAutoModule.m b/ios/react-native-navigation-sdk/NavAutoModule.m
new file mode 100644
index 0000000..a703635
--- /dev/null
+++ b/ios/react-native-navigation-sdk/NavAutoModule.m
@@ -0,0 +1,412 @@
+/**
+ * Copyright 2023 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#import "NavAutoModule.h"
+#import "NavAutoEventDispatcher.h"
+
+@implementation NavAutoModule
+
+RCT_EXPORT_MODULE(NavAutoModule);
+
+// Static instance of the NavAutoModule to allow access from another modules.
+static NavAutoModule *sharedInstance = nil;
+
+static NavAutoModuleReadyCallback _navAutoModuleReadyCallback;
+static NavAutoEventDispatcher *_eventDispatcher;
+
++ (id)allocWithZone:(NSZone *)zone {
+ static dispatch_once_t onceToken;
+ dispatch_once(&onceToken, ^{
+ _eventDispatcher = [NavAutoEventDispatcher allocWithZone:zone];
+ sharedInstance = [super allocWithZone:zone];
+ });
+ if (_navAutoModuleReadyCallback) {
+ _navAutoModuleReadyCallback();
+ }
+ return sharedInstance;
+}
+
+// Method to get the shared instance
++ (instancetype)sharedInstance {
+ return sharedInstance;
+}
+
+// Get or create the shared instance
++ (instancetype)getOrCreateSharedInstance {
+ if (sharedInstance == nil) {
+ sharedInstance = [[NavAutoModule allocWithZone:nil] init];
+ }
+ return sharedInstance;
+}
+
+- (void)registerViewController:(NavViewController *)vc {
+ self.viewController = vc;
+ [self onScreenStateChange:true];
+}
+
+- (void)unRegisterViewController {
+ self.viewController = nil;
+ [self onScreenStateChange:false];
+}
+
++ (void)registerNavAutoModuleReadyCallback:(NavAutoModuleReadyCallback)callback {
+ _navAutoModuleReadyCallback = [callback copy];
+}
+
++ (void)unregisterNavAutoModuleReadyCallback {
+ _navAutoModuleReadyCallback = nil;
+}
+
+RCT_EXPORT_METHOD(setMapType : (NSInteger)mapType) {
+ dispatch_async(dispatch_get_main_queue(), ^{
+ if (self->_viewController) {
+ GMSMapViewType mapViewType;
+ switch (mapType) {
+ case 1:
+ mapViewType = kGMSTypeNormal;
+ break;
+ case 2:
+ mapViewType = kGMSTypeSatellite;
+ break;
+ case 3:
+ mapViewType = kGMSTypeTerrain;
+ break;
+ case 4:
+ mapViewType = kGMSTypeHybrid;
+ break;
+ default:
+ mapViewType = kGMSTypeNone;
+ break;
+ }
+ [self->_viewController setMapType:mapViewType];
+ }
+ });
+}
+
+RCT_EXPORT_METHOD(setMapStyle : (NSString *)jsonStyleString) {
+ dispatch_async(dispatch_get_main_queue(), ^{
+ if (self->_viewController) {
+ NSError *error;
+ GMSMapStyle *mapStyle = [GMSMapStyle styleWithJSONString:jsonStyleString error:&error];
+ if (!mapStyle) {
+ return;
+ }
+ [self->_viewController setMapStyle:mapStyle];
+ }
+ });
+}
+
+RCT_EXPORT_METHOD(clearMapView) {
+ dispatch_async(dispatch_get_main_queue(), ^{
+ if (self->_viewController) {
+ [self->_viewController clearMapView];
+ }
+ });
+}
+
+RCT_EXPORT_METHOD(addMarker
+ : (NSDictionary *)markerOptions resolver
+ : (RCTPromiseResolveBlock)resolve rejecter
+ : (RCTPromiseRejectBlock)reject) {
+ dispatch_async(dispatch_get_main_queue(), ^{
+ if (self->_viewController) {
+ [self->_viewController addMarker:markerOptions
+ result:^(NSDictionary *result) {
+ resolve(result);
+ }];
+ } else {
+ reject(@"no_view_controller", @"No viewController found", nil);
+ }
+ });
+}
+
+RCT_EXPORT_METHOD(addCircle
+ : (NSDictionary *)circleOptions resolver
+ : (RCTPromiseResolveBlock)resolve rejecter
+ : (RCTPromiseRejectBlock)reject) {
+ dispatch_async(dispatch_get_main_queue(), ^{
+ if (self->_viewController) {
+ [self->_viewController addCircle:circleOptions
+ result:^(NSDictionary *result) {
+ resolve(result);
+ }];
+ } else {
+ reject(@"no_view_controller", @"No viewController found", nil);
+ }
+ });
+}
+
+RCT_EXPORT_METHOD(addPolyline
+ : (NSDictionary *)polylineOptions resolver
+ : (RCTPromiseResolveBlock)resolve rejecter
+ : (RCTPromiseRejectBlock)reject) {
+ dispatch_async(dispatch_get_main_queue(), ^{
+ if (self->_viewController) {
+ [self->_viewController addPolyline:polylineOptions
+ result:^(NSDictionary *result) {
+ resolve(result);
+ }];
+ } else {
+ reject(@"no_view_controller", @"No viewController found", nil);
+ }
+ });
+}
+
+RCT_EXPORT_METHOD(addPolygon
+ : (NSDictionary *)polygonOptions resolver
+ : (RCTPromiseResolveBlock)resolve rejecter
+ : (RCTPromiseRejectBlock)reject) {
+ dispatch_async(dispatch_get_main_queue(), ^{
+ if (self->_viewController) {
+ [self->_viewController addPolygon:polygonOptions
+ result:^(NSDictionary *result) {
+ resolve(result);
+ }];
+ } else {
+ reject(@"no_view_controller", @"No viewController found", nil);
+ }
+ });
+}
+
+RCT_EXPORT_METHOD(removeMarker : (NSString *)markerId) {
+ dispatch_async(dispatch_get_main_queue(), ^{
+ if (self->_viewController) {
+ [self->_viewController removeMarker:markerId];
+ }
+ });
+}
+
+RCT_EXPORT_METHOD(removePolygon : (NSString *)polygonId) {
+ dispatch_async(dispatch_get_main_queue(), ^{
+ if (self->_viewController) {
+ [self->_viewController removePolygon:polygonId];
+ }
+ });
+}
+
+RCT_EXPORT_METHOD(removePolyline : (NSString *)polylineId) {
+ dispatch_async(dispatch_get_main_queue(), ^{
+ if (self->_viewController) {
+ [self->_viewController removePolyline:polylineId];
+ }
+ });
+}
+
+RCT_EXPORT_METHOD(removeCircle : (NSString *)circleId) {
+ dispatch_async(dispatch_get_main_queue(), ^{
+ if (self->_viewController) {
+ [self->_viewController removeCircle:circleId];
+ }
+ });
+}
+
+RCT_EXPORT_METHOD(setIndoorEnabled : (BOOL *)enabled) {
+ dispatch_async(dispatch_get_main_queue(), ^{
+ if (self->_viewController) {
+ [self->_viewController setIndoorEnabled:enabled];
+ }
+ });
+}
+
+RCT_EXPORT_METHOD(setTrafficEnabled : (BOOL *)enabled) {
+ dispatch_async(dispatch_get_main_queue(), ^{
+ if (self->_viewController) {
+ [self->_viewController setTrafficEnabled:enabled];
+ }
+ });
+}
+
+RCT_EXPORT_METHOD(setCompassEnabled : (BOOL *)enabled) {
+ dispatch_async(dispatch_get_main_queue(), ^{
+ if (self->_viewController) {
+ [self->_viewController setCompassEnabled:enabled];
+ }
+ });
+}
+
+RCT_EXPORT_METHOD(setMyLocationButtonEnabled : (BOOL *)enabled) {
+ dispatch_async(dispatch_get_main_queue(), ^{
+ if (self->_viewController) {
+ [self->_viewController setMyLocationEnabled:enabled];
+ }
+ });
+}
+
+RCT_EXPORT_METHOD(setMyLocationEnabled : (BOOL *)enabled) {
+ dispatch_async(dispatch_get_main_queue(), ^{
+ if (self->_viewController) {
+ [self->_viewController setMyLocationEnabled:enabled];
+ }
+ });
+}
+
+RCT_EXPORT_METHOD(setRotateGesturesEnabled : (BOOL *)enabled) {
+ dispatch_async(dispatch_get_main_queue(), ^{
+ if (self->_viewController) {
+ [self->_viewController setRotateGesturesEnabled:enabled];
+ }
+ });
+}
+
+RCT_EXPORT_METHOD(setScrollGesturesEnabled : (BOOL *)enabled) {
+ dispatch_async(dispatch_get_main_queue(), ^{
+ if (self->_viewController) {
+ [self->_viewController setScrollGesturesEnabled:enabled];
+ }
+ });
+}
+
+RCT_EXPORT_METHOD(setScrollGesturesEnabledDuringRotateOrZoom : (BOOL *)enabled) {
+ dispatch_async(dispatch_get_main_queue(), ^{
+ if (self->_viewController) {
+ [self->_viewController setScrollGesturesEnabledDuringRotateOrZoom:enabled];
+ }
+ });
+}
+
+RCT_EXPORT_METHOD(setZoomLevel
+ : (nonnull NSNumber *)level resolver
+ : (RCTPromiseResolveBlock)resolve rejecter
+ : (RCTPromiseRejectBlock)reject) {
+ dispatch_async(dispatch_get_main_queue(), ^{
+ if (self->_viewController) {
+ [self->_viewController setZoomLevel:level];
+ } else {
+ reject(@"no_view_controller", @"No viewController found", nil);
+ }
+ });
+}
+
+RCT_EXPORT_METHOD(setTiltGesturesEnabled : (BOOL *)enabled) {
+ dispatch_async(dispatch_get_main_queue(), ^{
+ if (self->_viewController) {
+ [self->_viewController setTiltGesturesEnabled:enabled];
+ }
+ });
+}
+
+RCT_EXPORT_METHOD(setZoomGesturesEnabled : (BOOL *)enabled) {
+ dispatch_async(dispatch_get_main_queue(), ^{
+ if (self->_viewController) {
+ [self->_viewController setZoomGesturesEnabled:enabled];
+ }
+ });
+}
+
+RCT_EXPORT_METHOD(setBuildingsEnabled : (BOOL *)enabled) {
+ dispatch_async(dispatch_get_main_queue(), ^{
+ if (self->_viewController) {
+ [self->_viewController setBuildingsEnabled:enabled];
+ }
+ });
+}
+
+RCT_EXPORT_METHOD(getCameraPosition
+ : (RCTPromiseResolveBlock)resolve rejecter
+ : (RCTPromiseRejectBlock)reject) {
+ dispatch_async(dispatch_get_main_queue(), ^{
+ if (self->_viewController) {
+ [self->_viewController getCameraPosition:^(NSDictionary *result) {
+ resolve(result);
+ }];
+ } else {
+ reject(@"no_view_controller", @"No viewController found", nil);
+ }
+ });
+}
+
+RCT_EXPORT_METHOD(getMyLocation
+ : (RCTPromiseResolveBlock)resolve rejecter
+ : (RCTPromiseRejectBlock)reject) {
+ dispatch_async(dispatch_get_main_queue(), ^{
+ if (self->_viewController) {
+ [self->_viewController getMyLocation:^(NSDictionary *_Nullable result) {
+ resolve(result);
+ }];
+ } else {
+ reject(@"no_view_controller", @"No viewController found", nil);
+ }
+ });
+}
+
+RCT_EXPORT_METHOD(getUiSettings
+ : (RCTPromiseResolveBlock)resolve rejecter
+ : (RCTPromiseRejectBlock)reject) {
+ dispatch_async(dispatch_get_main_queue(), ^{
+ if (self->_viewController) {
+ [self->_viewController getUiSettings:^(NSDictionary *_Nullable result) {
+ resolve(result);
+ }];
+ } else {
+ reject(@"no_view_controller", @"No viewController found", nil);
+ }
+ });
+}
+
+RCT_EXPORT_METHOD(isMyLocationEnabled
+ : (RCTPromiseResolveBlock)resolve rejecter
+ : (RCTPromiseRejectBlock)reject) {
+ dispatch_async(dispatch_get_main_queue(), ^{
+ if (self->_viewController) {
+ [self->_viewController isMyLocationEnabled:^(BOOL result) {
+ resolve([NSNumber numberWithBool:result]);
+ }];
+ } else {
+ reject(@"no_view_controller", @"No viewController found", nil);
+ }
+ });
+}
+
+RCT_EXPORT_METHOD(moveCamera : (NSDictionary *)cameraPosition) {
+ dispatch_async(dispatch_get_main_queue(), ^{
+ if (self->_viewController) {
+ [self->_viewController moveCamera:cameraPosition];
+ }
+ });
+}
+
+RCT_EXPORT_METHOD(isAutoScreenAvailable
+ : (RCTPromiseResolveBlock)resolve rejecter
+ : (RCTPromiseRejectBlock)reject) {
+ dispatch_async(dispatch_get_main_queue(), ^{
+ BOOL hasViewController = self->_viewController != nil;
+ resolve([NSNumber numberWithBool:hasViewController]);
+ });
+}
+
+- (void)onScreenStateChange:(BOOL)available {
+ [self sendCommandToReactNative:@"onAutoScreenAvailabilityChanged"
+ args:[NSNumber numberWithBool:available]];
+}
+
+- (void)onCustomNavigationAutoEvent:(NSString *)type data:(nullable NSDictionary *)data {
+ NSMutableDictionary *map = [NSMutableDictionary dictionary];
+ [map setObject:type forKey:@"type"];
+ [map setObject:(data != nil ? data : [NSNull null]) forKey:@"data"];
+
+ [self sendCommandToReactNative:@"onCustomNavigationAutoEvent" args:map];
+}
+
+/*
+ * Method to send command to React Native using the NavEventDispatcher instance.
+ */
+- (void)sendCommandToReactNative:(NSString *)command args:(NSObject *)args {
+ if (_eventDispatcher != NULL) {
+ [_eventDispatcher sendEventName:command body:args];
+ }
+}
+
+@end
diff --git a/ios/react-native-navigation-sdk/NavModule.h b/ios/react-native-navigation-sdk/NavModule.h
index 17fa066..6a6f5e2 100644
--- a/ios/react-native-navigation-sdk/NavModule.h
+++ b/ios/react-native-navigation-sdk/NavModule.h
@@ -28,10 +28,17 @@ NS_ASSUME_NONNULL_BEGIN
GMSRoadSnappedLocationProviderListener,
INavigationCallback>
+typedef void (^NavigationSessionReadyCallback)(void);
+typedef void (^NavigationSessionDisposedCallback)(void);
+
@property BOOL enableUpdateInfo;
- (BOOL)hasSession;
- (GMSNavigationSession *)getSession;
++ (void)unregisterNavigationSessionReadyCallback;
++ (void)registerNavigationSessionReadyCallback:(NavigationSessionReadyCallback)callback;
++ (void)unregisterNavigationSessionDisposedCallback;
++ (void)registerNavigationSessionDisposedCallback:(NavigationSessionDisposedCallback)callback;
// Class method to access the singleton instance
+ (instancetype)sharedInstance;
diff --git a/ios/react-native-navigation-sdk/NavModule.m b/ios/react-native-navigation-sdk/NavModule.m
index 290afe4..4ef37ce 100644
--- a/ios/react-native-navigation-sdk/NavModule.m
+++ b/ios/react-native-navigation-sdk/NavModule.m
@@ -15,6 +15,7 @@
*/
#import "NavModule.h"
+#import "NavAutoModule.h"
#import "NavEventDispatcher.h"
#import "NavViewModule.h"
#import "ObjectTranslationUtil.h"
@@ -27,6 +28,8 @@ @implementation NavModule {
@synthesize enableUpdateInfo = _enableUpdateInfo;
static NavEventDispatcher *_eventDispatcher;
+static NavigationSessionReadyCallback _navigationSessionReadyCallback;
+static NavigationSessionDisposedCallback _navigationSessionDisposedCallback;
// Static instance of the NavViewModule to allow access from another modules.
static NavModule *sharedInstance = nil;
@@ -92,6 +95,9 @@ - (void)initializeSession {
userInfo:nil];
}
self->_session = session;
+ if (_navigationSessionReadyCallback) {
+ _navigationSessionReadyCallback();
+ }
}
_session.started = YES;
@@ -109,6 +115,22 @@ - (void)initializeSession {
[self onNavigationReady];
}
++ (void)registerNavigationSessionReadyCallback:(NavigationSessionReadyCallback)callback {
+ _navigationSessionReadyCallback = [callback copy];
+}
+
++ (void)unregisterNavigationSessionReadyCallback {
+ _navigationSessionReadyCallback = nil;
+}
+
++ (void)registerNavigationSessionDisposedCallback:(NavigationSessionDisposedCallback)callback {
+ _navigationSessionDisposedCallback = [callback copy];
+}
+
++ (void)unregisterNavigationSessionDisposedCallback {
+ _navigationSessionDisposedCallback = nil;
+}
+
- (void)showTermsAndConditionsDialog {
BOOL showAwareness =
_tosParams[@"showOnlyDisclaimer"] != nil && [_tosParams[@"showOnlyDisclaimer"] boolValue];
@@ -161,6 +183,9 @@ - (void)showTermsAndConditionsDialog {
self->_session.started = NO;
self->_session = nil;
+ if (_navigationSessionDisposedCallback) {
+ _navigationSessionDisposedCallback();
+ }
resolve(@(YES));
});
}
diff --git a/package.json b/package.json
index 200b762..34ee83c 100644
--- a/package.json
+++ b/package.json
@@ -55,6 +55,7 @@
"lint": "eslint \"**/*.{js,ts,tsx}\"",
"clean": "del-cli android/build example/android/build example/android/app/build example/ios/build lib",
"prepare": "bob build",
+ "prepare:example": "yarn workspace react-native-navigation-sdk-sample prepare",
"build": "bob build"
},
"devDependencies": {
diff --git a/src/auto/index.ts b/src/auto/index.ts
new file mode 100644
index 0000000..28a8c7c
--- /dev/null
+++ b/src/auto/index.ts
@@ -0,0 +1,18 @@
+/**
+ * Copyright 2023 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+export * from './types';
+export * from './useNavigationAuto';
diff --git a/src/auto/types.ts b/src/auto/types.ts
new file mode 100644
index 0000000..4e34abb
--- /dev/null
+++ b/src/auto/types.ts
@@ -0,0 +1,59 @@
+/**
+ * Copyright 2023 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import type { MapViewController } from '../maps';
+
+/** Defines all callbacks to be emitted by NavViewAuto support. */
+export interface NavigationAutoCallbacks {
+ /**
+ * Callback function invoked when the screen availability changes.
+ */
+ onAutoScreenAvailabilityChanged?(available: boolean): void;
+
+ /**
+ * Callback function invoked when a custom navigation auto event is received.
+ */
+ onCustomNavigationAutoEvent?(event: CustomNavigationAutoEvent): void;
+}
+
+/**
+ * CustomNavigationAutoEvent is an event that can be sent from the
+ * native side to the React Native side, ment to be simple way to
+ * implement custom events fired by the native side.
+ */
+export interface CustomNavigationAutoEvent {
+ /**
+ * The event type.
+ */
+ type: string;
+
+ /**
+ * The event data.
+ */
+ data?: Record;
+}
+
+export interface MapViewAutoController extends MapViewController {
+ /**
+ * Cleans up the navigation module, releasing any resources that were allocated.
+ */
+ cleanup(): Promise;
+
+ /**
+ * Queries screen visibility.
+ */
+ isAutoScreenAvailable(): Promise;
+}
diff --git a/src/auto/useNavigationAuto.ts b/src/auto/useNavigationAuto.ts
new file mode 100644
index 0000000..1991c4c
--- /dev/null
+++ b/src/auto/useNavigationAuto.ts
@@ -0,0 +1,199 @@
+/**
+ * Copyright 2024 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { NativeModules } from 'react-native';
+import type { MapViewAutoController, NavigationAutoCallbacks } from './types';
+import { useModuleListeners, type Location } from '../shared';
+import type {
+ MapType,
+ CircleOptions,
+ Circle,
+ MarkerOptions,
+ Marker,
+ PolylineOptions,
+ Polyline,
+ PolygonOptions,
+ Polygon,
+ CameraPosition,
+ UISettings,
+} from '../maps';
+import { useMemo } from 'react';
+
+const { NavAutoEventDispatcher, NavAutoModule } = NativeModules;
+
+const androidBridge: string = 'NavAutoJavascriptBridge';
+
+export const useNavigationAuto = (): {
+ mapViewAutoController: MapViewAutoController;
+ addListeners: (listeners: Partial) => void;
+ removeListeners: (listeners: Partial) => void;
+ removeAllListeners: () => void;
+} => {
+ const moduleListenersHandler = useModuleListeners(
+ NavAutoEventDispatcher,
+ androidBridge,
+ ['onAutoScreenAvailabilityChanged', 'onCustomNavigationAutoEvent']
+ );
+
+ const mapViewAutoController = useMemo(
+ () => ({
+ cleanup: async () => {
+ moduleListenersHandler.removeAllListeners();
+ },
+
+ isAutoScreenAvailable: async () => {
+ return await NavAutoModule.isAutoScreenAvailable();
+ },
+
+ setMapType: (mapType: MapType) => {
+ NavAutoModule.setMapType(mapType);
+ },
+
+ setMapStyle: (mapStyle: string) => {
+ NavAutoModule.setMapStyle(mapStyle);
+ },
+
+ // Android only.
+ setMapToolbarEnabled: (enabled: boolean) => {
+ NavAutoModule.setMapToolbarEnabled(enabled);
+ },
+
+ clearMapView: () => {
+ NavAutoModule.clearMapView();
+ },
+
+ addCircle: async (circleOptions: CircleOptions): Promise => {
+ return await NavAutoModule.addCircle(circleOptions);
+ },
+
+ addMarker: async (markerOptions: MarkerOptions): Promise => {
+ return await NavAutoModule.addMarker(markerOptions);
+ },
+
+ addPolyline: async (
+ polylineOptions: PolylineOptions
+ ): Promise => {
+ return await NavAutoModule.addPolyline({
+ ...polylineOptions,
+ points: polylineOptions.points || [],
+ });
+ },
+
+ addPolygon: async (polygonOptions: PolygonOptions): Promise => {
+ return await NavAutoModule.addPolygon({
+ ...polygonOptions,
+ holes: polygonOptions.holes || [],
+ points: polygonOptions.points || [],
+ });
+ },
+
+ removeMarker: (id: string) => {
+ return NavAutoModule.removeMarker(id);
+ },
+
+ removePolyline: (id: string) => {
+ return NavAutoModule.removePolyline(id);
+ },
+
+ removePolygon: (id: string) => {
+ return NavAutoModule.removePolygon(id);
+ },
+
+ removeCircle: (id: string) => {
+ return NavAutoModule.removeCircle(id);
+ },
+
+ setIndoorEnabled: (isOn: boolean) => {
+ return NavAutoModule.setIndoorEnabled(isOn);
+ },
+
+ setTrafficEnabled: (isOn: boolean) => {
+ return NavAutoModule.setTrafficEnabled(isOn);
+ },
+
+ setCompassEnabled: (isOn: boolean) => {
+ return NavAutoModule.setCompassEnabled(isOn);
+ },
+
+ setMyLocationButtonEnabled: (isOn: boolean) => {
+ return NavAutoModule.setMyLocationButtonEnabled(isOn);
+ },
+
+ setMyLocationEnabled: (isOn: boolean) => {
+ return NavAutoModule.setMyLocationEnabled(isOn);
+ },
+
+ setRotateGesturesEnabled: (isOn: boolean) => {
+ return NavAutoModule.setRotateGesturesEnabled(isOn);
+ },
+
+ setScrollGesturesEnabled: (isOn: boolean) => {
+ return NavAutoModule.setScrollGesturesEnabled(isOn);
+ },
+
+ setScrollGesturesEnabledDuringRotateOrZoom: (isOn: boolean) => {
+ return NavAutoModule.setScrollGesturesEnabledDuringRotateOrZoom(isOn);
+ },
+
+ // Android only.
+ setZoomControlsEnabled: (isOn: boolean) => {
+ return NavAutoModule.setZoomControlsEnabled(isOn);
+ },
+
+ setZoomLevel: async (level: number) => {
+ return NavAutoModule.setZoomLevel(level);
+ },
+
+ setTiltGesturesEnabled: (isOn: boolean) => {
+ return NavAutoModule.setTiltGesturesEnabled(isOn);
+ },
+
+ setZoomGesturesEnabled: (isOn: boolean) => {
+ return NavAutoModule.setZoomGesturesEnabled(isOn);
+ },
+
+ setBuildingsEnabled: (isOn: boolean) => {
+ return NavAutoModule.setBuildingsEnabled(isOn);
+ },
+
+ getCameraPosition: async (): Promise => {
+ return await NavAutoModule.getCameraPosition();
+ },
+
+ getMyLocation: async (): Promise => {
+ return await NavAutoModule.getMyLocation();
+ },
+
+ getUiSettings: async (): Promise => {
+ return await NavAutoModule.getUiSettings();
+ },
+
+ isMyLocationEnabled: async (): Promise => {
+ return await NavAutoModule.isMyLocationEnabled();
+ },
+
+ moveCamera: (cameraPosition: CameraPosition) => {
+ return NavAutoModule.moveCamera(cameraPosition);
+ },
+ }),
+ [moduleListenersHandler]
+ );
+
+ return {
+ mapViewAutoController,
+ ...moduleListenersHandler,
+ };
+};
diff --git a/src/index.ts b/src/index.ts
index 5e07a2a..c642c7a 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -14,6 +14,7 @@
* limitations under the License.
*/
+export * from './auto';
export * from './maps';
export * from './navigation';
export * from './shared';
diff --git a/src/maps/mapView/types.ts b/src/maps/mapView/types.ts
index 92e334b..6a502f0 100644
--- a/src/maps/mapView/types.ts
+++ b/src/maps/mapView/types.ts
@@ -194,16 +194,15 @@ export interface MapViewController {
* style for the map.
*/
setMapStyle(mapStyle: string): void;
+
/**
- * Toggle the map toolbar's enabled state.
- *
- * Sends a command to the native side to enable or disable the map toolbar,
- * based on the provided index. Typically, a non-zero value enables the
- * toolbar, while a value of 0 disables it.
+ * Enable or disable the map toolbar.
+ * Android only.
*
- * @param index - A number indicating the desired state of the map toolbar.
+ * @param isOn - Boolean indicating whether to enable (true) or disable (false)
+ * the the map toolbar.
*/
- setMapToolbarEnabled(index: boolean): void;
+ setMapToolbarEnabled(isOn: boolean): void;
/**
* Clear all elements from the map view.
diff --git a/src/navigation/navigation/NavigationProvider.tsx b/src/navigation/navigation/NavigationProvider.tsx
index 690dcfa..c2059bd 100644
--- a/src/navigation/navigation/NavigationProvider.tsx
+++ b/src/navigation/navigation/NavigationProvider.tsx
@@ -20,7 +20,7 @@ import type {
NavigationCallbacks,
TermsAndConditionsDialogOptions,
} from './types';
-import useNavigationController from './useNavigationController';
+import { useNavigationController } from './useNavigationController';
interface NavigationContextProps {
navigationController: NavigationController;
diff --git a/src/navigation/navigation/types.ts b/src/navigation/navigation/types.ts
index 68485b7..913284c 100644
--- a/src/navigation/navigation/types.ts
+++ b/src/navigation/navigation/types.ts
@@ -13,21 +13,6 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-/**
- * Copyright 2023 Google LLC
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
import type { LatLng, Location } from '../../shared/types';
import type {
diff --git a/src/navigation/navigation/useNavigationController.ts b/src/navigation/navigation/useNavigationController.ts
index 300b65e..a88e775 100644
--- a/src/navigation/navigation/useNavigationController.ts
+++ b/src/navigation/navigation/useNavigationController.ts
@@ -14,9 +14,8 @@
* limitations under the License.
*/
-import { useRef, useCallback, useEffect } from 'react';
-import { NativeModules, NativeEventEmitter, Platform } from 'react-native';
-import type { LatLng } from '../../shared';
+import { NativeModules, Platform } from 'react-native';
+import { useModuleListeners, type LatLng } from '../../shared';
import type {
Waypoint,
AudioGuidance,
@@ -32,14 +31,12 @@ import type {
LocationSimulationOptions,
} from './types';
import { getRouteStatusFromStringValue } from '../navigationView';
+import { useMemo } from 'react';
const { NavModule, NavEventDispatcher } = NativeModules;
+const androidBridge: string = 'NavJavascriptBridge';
-type ListenerMap = {
- [K in keyof NavigationCallbacks]?: Array>;
-};
-
-const useNavigationController = (
+export const useNavigationController = (
termsAndConditionsDialogOptions: TermsAndConditionsDialogOptions
): {
navigationController: NavigationController;
@@ -47,43 +44,20 @@ const useNavigationController = (
removeListeners: (listeners: Partial) => void;
removeAllListeners: () => void;
} => {
- const listenersRef = useRef({});
- const eventEmitterRef = useRef(null);
-
- const getEventEmitter = () => {
- if (!eventEmitterRef.current) {
- eventEmitterRef.current = new NativeEventEmitter(NavEventDispatcher);
- }
- return eventEmitterRef.current;
- };
-
- // Conversion function to handle different types of data transformation based on the event
- const convert = (eventKey: keyof NavigationCallbacks, ...args: any[]) => {
+ const eventTransformer = (
+ eventKey: K,
+ ...args: unknown[]
+ ) => {
if (eventKey === 'onRouteStatusResult' && typeof args[0] === 'string') {
- return [getRouteStatusFromStringValue(args[0])]; // Return as array for consistent callback application
+ return [getRouteStatusFromStringValue(args[0])];
}
- return args; // For all other events, pass through the arguments unchanged
+ return args;
};
- const updateListeners = useCallback(() => {
- // Wrap the listeners in a single object to pass to the native module for event handling.
- // Multiplexes the events to all registered listeners.
- const wrappedListeners = Object.keys(listenersRef.current).reduce(
- (acc, eventName) => {
- const eventKey = eventName as keyof NavigationCallbacks;
- acc[eventKey] = (...args: any[]) => {
- listenersRef.current[eventKey]?.forEach((callback: any) =>
- callback(...convert(eventKey, ...args))
- );
- };
- return acc;
- },
- {} as {
- [K in keyof NavigationCallbacks]?: (...args: any[]) => void;
- }
- );
-
- const allEventTypes: Array = [
+ const moduleListenersHandler = useModuleListeners(
+ NavEventDispatcher,
+ androidBridge,
+ [
'onStartGuidance',
'onArrival',
'onLocationChanged',
@@ -97,226 +71,132 @@ const useNavigationController = (
'onNavigationInitError',
'onTurnByTurn',
'logDebugInfo',
- ];
-
- allEventTypes.forEach(eventType => {
- if (!wrappedListeners[eventType]) {
- wrappedListeners[eventType] = () => {};
- }
- });
-
- // Platform-specific event handling
- if (Platform.OS === 'android') {
- const BatchedBridge = require('react-native/Libraries/BatchedBridge/BatchedBridge');
- BatchedBridge.registerCallableModule(
- 'NavJavascriptBridge',
- wrappedListeners
- );
- } else if (Platform.OS === 'ios') {
- allEventTypes.forEach(eventType => {
- getEventEmitter().removeAllListeners(eventType);
- getEventEmitter().addListener(eventType, wrappedListeners[eventType]!);
- });
-
- return () =>
- allEventTypes.forEach(eventType =>
- getEventEmitter().removeAllListeners(eventType)
- );
- }
-
- return () => {};
- }, []);
-
- const addListeners = useCallback(
- (newListeners: Partial) => {
- const prevListeners = listenersRef.current;
- const updatedListeners: ListenerMap = { ...prevListeners };
- Object.keys(newListeners).forEach(eventName => {
- const eventKey = eventName as keyof NavigationCallbacks;
- (updatedListeners[eventKey] as Array<
- NonNullable
- >) = [
- ...(updatedListeners[eventKey] || []),
- newListeners[eventKey],
- ].filter(Boolean) as Array void>>;
- });
- listenersRef.current = updatedListeners;
- updateListeners();
- },
- [updateListeners]
- );
-
- const removeListeners = useCallback(
- (listenersToRemove: Partial) => {
- const prevListeners = listenersRef.current;
- const updatedListeners: ListenerMap = { ...prevListeners };
- Object.keys(listenersToRemove).forEach(eventName => {
- const eventKey = eventName as keyof NavigationCallbacks;
- if (updatedListeners[eventKey]) {
- (updatedListeners[eventKey] as Array<
- NonNullable
- >) = updatedListeners[eventKey]!.filter(
- listener => listener !== listenersToRemove[eventKey]
- ) as Array void>>;
- if (updatedListeners[eventKey]!.length === 0) {
- delete updatedListeners[eventKey];
- }
- }
- });
- listenersRef.current = updatedListeners;
- updateListeners();
- },
- [updateListeners]
+ ],
+ eventTransformer
);
- const removeAllListeners = useCallback(() => {
- Object.keys(listenersRef.current).forEach(eventName => {
- const eventKey = eventName as keyof NavigationCallbacks;
- if (listenersRef.current[eventKey]) {
- listenersRef.current[eventKey] = [];
- if (Platform.OS === 'ios') {
- getEventEmitter().removeAllListeners(eventKey);
- }
- }
- });
- if (Platform.OS === 'android') {
- // Android might need special handling to unregister all events
- const BatchedBridge = require('react-native/Libraries/BatchedBridge/BatchedBridge');
- BatchedBridge.registerCallableModule('NavJavascriptBridge', {});
- }
- }, []);
-
- // Cleanup on unmount
- useEffect(() => {
- return () => {
- removeAllListeners();
- };
- }, [removeAllListeners]);
-
- const navigationController: NavigationController = {
- init: async () => {
- updateListeners(); // Ensure listeners are up to date
- return await NavModule.initializeNavigator(
- termsAndConditionsDialogOptions
- );
- },
-
- cleanup: async () => {
- removeAllListeners();
- await NavModule.cleanup();
- },
-
- setDestination: async (
- waypoint: Waypoint,
- routingOptions?: RoutingOptions
- ) => {
- return await NavModule.setDestination(waypoint, routingOptions);
- },
-
- setDestinations: async (
- waypoints: Waypoint[],
- routingOptions?: RoutingOptions
- ) => {
- return await NavModule.setDestinations(waypoints, routingOptions);
- },
-
- continueToNextDestination: async () => {
- return await NavModule.continueToNextDestination();
- },
+ const navigationController: NavigationController = useMemo(
+ () => ({
+ init: async () => {
+ return await NavModule.initializeNavigator(
+ termsAndConditionsDialogOptions
+ );
+ },
- clearDestinations: async () => {
- return await NavModule.clearDestinations();
- },
+ cleanup: async () => {
+ moduleListenersHandler.removeAllListeners();
+ await NavModule.cleanup();
+ },
- startGuidance: async () => {
- return await NavModule.startGuidance();
- },
+ setDestination: async (
+ waypoint: Waypoint,
+ routingOptions?: RoutingOptions
+ ) => {
+ return await NavModule.setDestination(waypoint, routingOptions);
+ },
- stopGuidance: async () => {
- return await NavModule.stopGuidance();
- },
+ setDestinations: async (
+ waypoints: Waypoint[],
+ routingOptions?: RoutingOptions
+ ) => {
+ return await NavModule.setDestinations(waypoints, routingOptions);
+ },
- setSpeedAlertOptions: async (alertOptions: SpeedAlertOptions | null) => {
- return await NavModule.setSpeedAlertOptions(alertOptions);
- },
+ continueToNextDestination: async () => {
+ return await NavModule.continueToNextDestination();
+ },
- setAbnormalTerminatingReportingEnabled: (enabled: boolean) => {
- return NavModule.setAbnormalTerminatingReportingEnabled(enabled);
- },
+ clearDestinations: async () => {
+ return await NavModule.clearDestinations();
+ },
- setAudioGuidanceType: (index: AudioGuidance) => {
- NavModule.setAudioGuidanceType(index);
- },
+ startGuidance: async () => {
+ return await NavModule.startGuidance();
+ },
- setBackgroundLocationUpdatesEnabled: (isEnabled: boolean) => {
- if (Platform.OS === 'ios') {
- NavModule.setBackgroundLocationUpdatesEnabled(isEnabled);
- }
- },
+ stopGuidance: async () => {
+ return await NavModule.stopGuidance();
+ },
- setTurnByTurnLoggingEnabled: (isEnabled: boolean) => {
- NavModule.setTurnByTurnLoggingEnabled(isEnabled);
- },
+ setSpeedAlertOptions: async (alertOptions: SpeedAlertOptions | null) => {
+ return await NavModule.setSpeedAlertOptions(alertOptions);
+ },
- areTermsAccepted: async (): Promise => {
- return await NavModule.areTermsAccepted();
- },
+ setAbnormalTerminatingReportingEnabled: (enabled: boolean) => {
+ return NavModule.setAbnormalTerminatingReportingEnabled(enabled);
+ },
- getCurrentRouteSegment: async (): Promise => {
- return await NavModule.getCurrentRouteSegment();
- },
+ setAudioGuidanceType: (index: AudioGuidance) => {
+ NavModule.setAudioGuidanceType(index);
+ },
- getRouteSegments: async (): Promise => {
- return await NavModule.getRouteSegments();
- },
+ setBackgroundLocationUpdatesEnabled: (isEnabled: boolean) => {
+ if (Platform.OS === 'ios') {
+ NavModule.setBackgroundLocationUpdatesEnabled(isEnabled);
+ }
+ },
- getCurrentTimeAndDistance: async (): Promise => {
- return await NavModule.getCurrentTimeAndDistance();
- },
+ setTurnByTurnLoggingEnabled: (isEnabled: boolean) => {
+ NavModule.setTurnByTurnLoggingEnabled(isEnabled);
+ },
- getTraveledPath: async (): Promise => {
- return await NavModule.getTraveledPath();
- },
+ areTermsAccepted: async (): Promise => {
+ return await NavModule.areTermsAccepted();
+ },
- getNavSDKVersion: async (): Promise => {
- return await NavModule.getNavSDKVersion();
- },
+ getCurrentRouteSegment: async (): Promise => {
+ return await NavModule.getCurrentRouteSegment();
+ },
- stopUpdatingLocation: () => {
- NavModule.stopUpdatingLocation();
- },
+ getRouteSegments: async (): Promise => {
+ return await NavModule.getRouteSegments();
+ },
- startUpdatingLocation: () => {
- NavModule.startUpdatingLocation();
- },
+ getCurrentTimeAndDistance: async (): Promise => {
+ return await NavModule.getCurrentTimeAndDistance();
+ },
- simulator: {
- simulateLocation: (location: LatLng) => {
- NavModule.simulateLocation(location);
+ getTraveledPath: async (): Promise => {
+ return await NavModule.getTraveledPath();
},
- resumeLocationSimulation: () => {
- NavModule.resumeLocationSimulation();
+
+ getNavSDKVersion: async (): Promise => {
+ return await NavModule.getNavSDKVersion();
},
- pauseLocationSimulation: () => {
- NavModule.pauseLocationSimulation();
+
+ stopUpdatingLocation: () => {
+ NavModule.stopUpdatingLocation();
},
- simulateLocationsAlongExistingRoute: ({
- speedMultiplier,
- }: LocationSimulationOptions) => {
- NavModule.simulateLocationsAlongExistingRoute(speedMultiplier);
+
+ startUpdatingLocation: () => {
+ NavModule.startUpdatingLocation();
},
- stopLocationSimulation: () => {
- NavModule.stopLocationSimulation();
+
+ simulator: {
+ simulateLocation: (location: LatLng) => {
+ NavModule.simulateLocation(location);
+ },
+ resumeLocationSimulation: () => {
+ NavModule.resumeLocationSimulation();
+ },
+ pauseLocationSimulation: () => {
+ NavModule.pauseLocationSimulation();
+ },
+ simulateLocationsAlongExistingRoute: ({
+ speedMultiplier,
+ }: LocationSimulationOptions) => {
+ NavModule.simulateLocationsAlongExistingRoute(speedMultiplier);
+ },
+ stopLocationSimulation: () => {
+ NavModule.stopLocationSimulation();
+ },
},
- },
- };
+ }),
+ [moduleListenersHandler, termsAndConditionsDialogOptions]
+ );
return {
navigationController,
- addListeners,
- removeListeners,
- removeAllListeners,
+ ...moduleListenersHandler,
};
};
-
-export default useNavigationController;
diff --git a/src/navigation/navigationView/types.ts b/src/navigation/navigationView/types.ts
index bc8fd2f..a8dfa6c 100644
--- a/src/navigation/navigationView/types.ts
+++ b/src/navigation/navigationView/types.ts
@@ -13,21 +13,6 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-/**
- * Copyright 2023 Google LLC
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
import type { StyleProp, ViewStyle } from 'react-native';
import type {
diff --git a/src/shared/index.ts b/src/shared/index.ts
index 52de3a2..a016f53 100644
--- a/src/shared/index.ts
+++ b/src/shared/index.ts
@@ -16,3 +16,4 @@
export * from './viewManager';
export * from './types';
+export * from './useModuleListeners';
diff --git a/src/shared/useModuleListeners.ts b/src/shared/useModuleListeners.ts
new file mode 100644
index 0000000..daa8da9
--- /dev/null
+++ b/src/shared/useModuleListeners.ts
@@ -0,0 +1,127 @@
+/**
+ * Copyright 2024 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { useRef, useCallback } from 'react';
+import { NativeEventEmitter, Platform, type NativeModule } from 'react-native';
+
+type ListenerMap = {
+ [K in keyof T]?: NonNullable[];
+};
+
+export const useModuleListeners = <
+ T extends { [K in keyof T]: ((...args: any[]) => void) | undefined },
+>(
+ dispatcher: NativeModule,
+ androidBridge: string,
+ eventTypes: Array,
+ eventTransformer?: (
+ eventKey: K,
+ ...args: unknown[]
+ ) => unknown[]
+): {
+ addListeners: (listeners: Partial) => void;
+ removeListeners: (listeners: Partial) => void;
+ removeAllListeners: () => void;
+} => {
+ const listenersRef = useRef>({});
+ const eventEmitterRef = useRef(null);
+
+ const getIOSEventEmitter = useCallback(() => {
+ if (!eventEmitterRef.current) {
+ eventEmitterRef.current = new NativeEventEmitter(dispatcher);
+ }
+ return eventEmitterRef.current;
+ }, [dispatcher]);
+
+ const updateListeners = useCallback(() => {
+ // Wrap the listeners in a single object to pass to the native module for event handling.
+ // Multiplexes the events to all registered listeners.
+ const wrappedListeners = Object.keys(listenersRef.current).reduce(
+ (acc, eventName) => {
+ const eventKey = eventName as keyof T;
+ acc[eventKey] = (...args: unknown[]) => {
+ listenersRef.current[eventKey]?.forEach(callback =>
+ callback(
+ ...(eventTransformer ? eventTransformer(eventKey, ...args) : args)
+ )
+ );
+ };
+ return acc;
+ },
+ {} as {
+ [K in keyof T]?: (...args: unknown[]) => void;
+ }
+ );
+
+ eventTypes.forEach(eventType => {
+ if (!wrappedListeners[eventType]) {
+ wrappedListeners[eventType] = () => {};
+ }
+ });
+
+ // Platform-specific event handling
+ if (Platform.OS === 'android') {
+ const BatchedBridge = require('react-native/Libraries/BatchedBridge/BatchedBridge');
+ BatchedBridge.registerCallableModule(androidBridge, wrappedListeners);
+ } else if (Platform.OS === 'ios') {
+ eventTypes.forEach(eventType =>
+ getIOSEventEmitter().removeAllListeners(eventType as string)
+ );
+ eventTypes.forEach(eventType => {
+ getIOSEventEmitter().addListener(
+ eventType as string,
+ wrappedListeners[eventType]!
+ );
+ });
+ }
+ }, [eventTypes, androidBridge, getIOSEventEmitter, eventTransformer]);
+
+ const addListeners = (listeners: Partial) => {
+ (Object.keys(listeners) as [keyof T]).forEach(key => {
+ listenersRef.current[key] = [
+ ...(listenersRef.current[key] || []),
+ listeners[key]!,
+ ];
+ });
+ updateListeners();
+ };
+
+ const removeListeners = (listeners: Partial) => {
+ (Object.keys(listeners) as [keyof T]).forEach(key => {
+ listenersRef.current[key] = listenersRef.current[key]?.filter(
+ listener => listener !== listeners[key]
+ );
+ });
+ updateListeners();
+ };
+
+ const removeAllListeners = useCallback(() => {
+ if (Platform.OS === 'android') {
+ const BatchedBridge = require('react-native/Libraries/BatchedBridge/BatchedBridge');
+ BatchedBridge.registerCallableModule(androidBridge, {});
+ } else if (Platform.OS === 'ios') {
+ eventTypes.forEach(eventType =>
+ getIOSEventEmitter().removeAllListeners(eventType as string)
+ );
+ }
+ }, [androidBridge, eventTypes, getIOSEventEmitter]);
+
+ return {
+ addListeners,
+ removeListeners,
+ removeAllListeners,
+ };
+};