diff --git a/README.md b/README.md index 36408286..6680b59c 100644 --- a/README.md +++ b/README.md @@ -645,7 +645,8 @@ const { | `getCurrentRouteSegment()` | `Promise` | Get the current route segment | | `getRouteSegments()` | `Promise` | Get all route segments | | `getTraveledPath()` | `Promise` | Get the path traveled so far | -| `setAudioGuidanceType(type: AudioGuidanceType)` | `Promise` | Set audio guidance type (SILENT, ALERTS_ONLY, VOICE_ALERTS_AND_GUIDANCE) | +| `setAudioGuidanceType(type: AudioGuidance)` | `Promise` | Deprecated. Set the voice guidance mode only | +| `setAudioGuidanceSettings(settings: AudioGuidanceSettings)` | `Promise` | Set voice guidance mode and enable/disable vibration and Bluetooth audio | | `setSpeedAlertOptions(options: SpeedAlertOptions)` | `Promise` | Configure speed alert thresholds | | `setAbnormalTerminatingReportingEnabled(enabled: boolean)` | `void` | Enable/disable abnormal termination reporting | | `startUpdatingLocation()` | `Promise` | Start receiving location updates | 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 234cc1c2..3763bbac 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 @@ -832,6 +832,36 @@ public void setAudioGuidanceType(double index, final Promise promise) { promise.resolve(null); } + @Override + public void setAudioGuidanceSettings(@NonNull ReadableMap settings, final Promise promise) { + if (mNavigator == null) { + promise.reject(JsErrors.NO_NAVIGATOR_ERROR_CODE, JsErrors.NO_NAVIGATOR_ERROR_MESSAGE); + return; + } + if (!settings.hasKey("guidanceMode") + || !settings.hasKey("vibrationEnabled") + || !settings.hasKey("bluetoothAudioEnabled")) { + promise.reject(JsErrors.INVALID_OPTIONS_ERROR_CODE, "Invalid audio guidance settings."); + return; + } + + int guidanceMode = (int) settings.getDouble("guidanceMode"); + boolean vibrationEnabled = settings.getBoolean("vibrationEnabled"); + boolean bluetoothAudioEnabled = settings.getBoolean("bluetoothAudioEnabled"); + + UiThreadUtil.runOnUiThread( + () -> { + mNavigator.setAudioGuidanceSettings( + AudioGuidanceSettings.builder() + .setGuidanceMode( + EnumTranslationUtil.getAudioGuidanceModeFromJsValue(guidanceMode)) + .setVibrationEnabled(vibrationEnabled) + .setBluetoothAudioEnabled(bluetoothAudioEnabled) + .build()); + }); + promise.resolve(null); + } + @Override public void getCurrentTimeAndDistance(final Promise promise) { if (mNavigator == null) { diff --git a/example/src/controls/navigationControls.tsx b/example/src/controls/navigationControls.tsx index 8a09bba2..e77b3a1d 100644 --- a/example/src/controls/navigationControls.tsx +++ b/example/src/controls/navigationControls.tsx @@ -20,6 +20,7 @@ import { ExampleAppButton } from './ExampleAppButton'; import { Accordion } from './Accordion'; import { showSnackbar, Snackbar } from '../helpers/snackbar'; import { + AudioGuidanceMode, CameraPerspective, NavigationNightMode, type NavigationViewController, @@ -87,6 +88,11 @@ const NavigationControls: React.FC = ({ : 0; const nightModeLabel = nightModeOptions[nightModeIndex]; const audioGuidanceOptions = ['Silent', 'Alerts only', 'Alerts and guidance']; + const [audioGuidanceMode, setAudioGuidanceMode] = useState( + AudioGuidanceMode.VOICE_ALERTS_AND_GUIDANCE + ); + const [vibrationEnabled, setVibrationEnabled] = useState(false); + const [bluetoothAudioEnabled, setBluetoothAudioEnabled] = useState(false); const [tripProgressBarEnabled, setTripProgressBarEnabled] = useState(false); const [reportIncidentButtonEnabled, setReportIncidentButtonEnabled] = useState(true); @@ -309,8 +315,23 @@ const NavigationControls: React.FC = ({ onNavigationNightModeChange?.(mode); }; - const setAudioGuidanceType = (index: number) => { - navigationController.setAudioGuidanceType(index); + const setAudioGuidanceSettings = async ( + guidanceMode: AudioGuidanceMode, + isVibrationEnabled: boolean, + isBluetoothAudioEnabled: boolean + ) => { + try { + await navigationController.setAudioGuidanceSettings({ + guidanceMode, + vibrationEnabled: isVibrationEnabled, + bluetoothAudioEnabled: isBluetoothAudioEnabled, + }); + setAudioGuidanceMode(guidanceMode); + setVibrationEnabled(isVibrationEnabled); + setBluetoothAudioEnabled(isBluetoothAudioEnabled); + } catch (e) { + showSnackbar(`Error setting audio guidance: ${e}`); + } }; const getCurrentRouteSegment = async () => { @@ -681,11 +702,15 @@ const NavigationControls: React.FC = ({ {/* Audio & Logging */} - Audio guidance type + Audio guidance mode { - setAudioGuidanceType(index); + setAudioGuidanceSettings( + index as AudioGuidanceMode, + vibrationEnabled, + bluetoothAudioEnabled + ); }} renderButton={(selectedItem, _isOpened) => { return ( @@ -708,9 +733,36 @@ const NavigationControls: React.FC = ({ ); }} + defaultValue={audioGuidanceOptions[audioGuidanceMode]} dropdownStyle={ControlStyles.dropdownMenu} /> + + Vibration + { + setAudioGuidanceSettings( + audioGuidanceMode, + !vibrationEnabled, + bluetoothAudioEnabled + ); + }} + /> + + + Bluetooth audio + { + setAudioGuidanceSettings( + audioGuidanceMode, + vibrationEnabled, + !bluetoothAudioEnabled + ); + }} + /> + Turn-by-turn logging { - navigationController.setAudioGuidanceType(AudioGuidance.SILENT); -}; +) => + navigationController.setAudioGuidanceSettings({ + guidanceMode: AudioGuidanceMode.SILENT, + vibrationEnabled: false, + bluetoothAudioEnabled: false, + }); const LOCATION_THRESHOLD_METERS = 100; const LOCATION_WAIT_TIMEOUT_MS = 15000; diff --git a/ios/react-native-navigation-sdk/NavModule.mm b/ios/react-native-navigation-sdk/NavModule.mm index a13d210e..ab80b1cf 100644 --- a/ios/react-native-navigation-sdk/NavModule.mm +++ b/ios/react-native-navigation-sdk/NavModule.mm @@ -345,6 +345,36 @@ - (void)setAudioGuidanceType:(double)index }); } +- (void)setAudioGuidanceSettings:(AudioGuidanceSettingsSpec &)settings + resolve:(RCTPromiseResolveBlock)resolve + reject:(RCTPromiseRejectBlock)reject { + AudioGuidanceSettingsSpec settingsCopy(settings); + dispatch_async(dispatch_get_main_queue(), ^{ + GMSNavigator *navigator = nil; + if (![self checkNavigatorWithError:reject navigator:&navigator]) { + return; + } + + double guidanceMode = settingsCopy.guidanceMode(); + if (guidanceMode == 0) { + navigator.voiceGuidance = GMSNavigationVoiceGuidanceSilent; + } else if (guidanceMode == 1) { + navigator.voiceGuidance = GMSNavigationVoiceGuidanceAlertsOnly; + } else if (guidanceMode == 2) { + navigator.voiceGuidance = GMSNavigationVoiceGuidanceAlertsAndGuidance; + } else { + reject(@"INVALID_OPTIONS", @"Invalid audio guidance mode.", nil); + return; + } + + navigator.vibrationEnabled = settingsCopy.vibrationEnabled(); + navigator.audioDeviceType = settingsCopy.bluetoothAudioEnabled() + ? GMSVoiceGuidanceAudioDeviceTypeBluetooth + : GMSVoiceGuidanceAudioDeviceTypeBuiltInOnly; + resolve(@(YES)); + }); +} + - (void)startGuidance:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject { dispatch_async(dispatch_get_main_queue(), ^{ GMSNavigator *navigator = nil; diff --git a/src/native/NativeNavModule.ts b/src/native/NativeNavModule.ts index ff163773..65934b55 100644 --- a/src/native/NativeNavModule.ts +++ b/src/native/NativeNavModule.ts @@ -96,6 +96,12 @@ type LocationSimulationOptionsSpec = Readonly<{ readonly speedMultiplier: Float; }>; +type AudioGuidanceSettingsSpec = Readonly<{ + guidanceMode: Double; + vibrationEnabled: boolean; + bluetoothAudioEnabled: boolean; +}>; + type ArrivalEventSpec = Readonly<{ waypoint: WaypointSpec; isFinalDestination?: boolean; @@ -186,6 +192,7 @@ export interface Spec extends TurboModule { setSpeedAlertOptions(alertOptions: SpeedAlertOptionsSpec): Promise; setAbnormalTerminatingReportingEnabled(enabled: boolean): void; setAudioGuidanceType(index: Double): Promise; + setAudioGuidanceSettings(settings: AudioGuidanceSettingsSpec): Promise; setBackgroundLocationUpdatesEnabled(isEnabled: boolean): void; setTurnByTurnLoggingEnabled(isEnabled: boolean): void; getCurrentRouteSegment(): Promise; diff --git a/src/navigation/navigation/types.ts b/src/navigation/navigation/types.ts index 06827b65..0a90c9ed 100644 --- a/src/navigation/navigation/types.ts +++ b/src/navigation/navigation/types.ts @@ -18,6 +18,7 @@ import type { LatLng, Location } from '../../shared/types'; import type { AlternateRoutingStrategy, AudioGuidance, + AudioGuidanceSettings, RouteSegment, RouteStatus, RoutingStrategy, @@ -504,12 +505,16 @@ export interface NavigationController { setSpeedAlertOptions(speed: SpeedAlertOptions | null): void; /** - * Sets the audio guidance type according to the provided index. + * @deprecated Use setAudioGuidanceSettings instead. + */ + setAudioGuidanceType(index: AudioGuidance): Promise; + + /** + * Sets the voice guidance mode, vibration, and Bluetooth-audio behavior. * - * @param index - The index representing the desired audio - * guidance type. + * @param settings - The audio guidance settings to apply on Android and iOS. */ - setAudioGuidanceType(index: AudioGuidance): void; + setAudioGuidanceSettings(settings: AudioGuidanceSettings): Promise; /** * Disables location updates by the library. This should be diff --git a/src/navigation/navigation/useNavigationController.ts b/src/navigation/navigation/useNavigationController.ts index 9dc04de1..bd27382a 100644 --- a/src/navigation/navigation/useNavigationController.ts +++ b/src/navigation/navigation/useNavigationController.ts @@ -25,11 +25,12 @@ import { import type { Waypoint, AudioGuidance, + AudioGuidanceSettings, RouteSegment, TimeAndDistance, RouteStatus, } from '../types'; -import { NavigationSessionStatus } from '../types'; +import { AudioGuidanceMode, NavigationSessionStatus } from '../types'; import { TaskRemovedBehavior, type TurnByTurnEvent, @@ -44,6 +45,20 @@ import { const { NavModule } = NativeModules; +const validateAudioGuidanceSettings = ( + settings: AudioGuidanceSettings +): void => { + if ( + !settings || + !Number.isInteger(settings.guidanceMode) || + !Object.values(AudioGuidanceMode).includes(settings.guidanceMode) || + typeof settings.vibrationEnabled !== 'boolean' || + typeof settings.bluetoothAudioEnabled !== 'boolean' + ) { + throw new Error('Invalid audio guidance settings.'); + } +}; + /** * Individual listener setters type - maps each callback key to a setter function. */ @@ -485,8 +500,13 @@ export const useNavigationController = ( return NavModule.setAbnormalTerminatingReportingEnabled(enabled); }, - setAudioGuidanceType: (index: AudioGuidance) => { - NavModule.setAudioGuidanceType(index); + setAudioGuidanceType: async (index: AudioGuidance) => { + return await NavModule.setAudioGuidanceType(index); + }, + + setAudioGuidanceSettings: async (settings: AudioGuidanceSettings) => { + validateAudioGuidanceSettings(settings); + return await NavModule.setAudioGuidanceSettings(settings); }, setBackgroundLocationUpdatesEnabled: (isEnabled: boolean) => { diff --git a/src/navigation/types.ts b/src/navigation/types.ts index ef08c3fb..ec857040 100644 --- a/src/navigation/types.ts +++ b/src/navigation/types.ts @@ -132,8 +132,8 @@ export enum Status { } /** - * AudioGuidance is a set of flags used to specify what kinds of audio alerts and guidance are - * used during navigation. + * @deprecated Use AudioGuidanceSettings with setAudioGuidanceSettings instead. + * This legacy enum is retained for setAudioGuidanceType compatibility only. */ export enum AudioGuidance { /** @@ -164,6 +164,30 @@ export enum AudioGuidance { VOICE_ALERTS_ONLY = 2, } +/** + * The voice guidance mode used during navigation. + */ +export enum AudioGuidanceMode { + /** Disables voice guidance. */ + SILENT = 0, + /** Enables voice guidance for alerts only. */ + VOICE_ALERTS_ONLY, + /** Enables voice guidance for alerts and turn-by-turn instructions. */ + VOICE_ALERTS_AND_GUIDANCE, +} + +/** + * Settings for audio guidance during navigation on Android and iOS. + */ +export interface AudioGuidanceSettings { + /** The voice guidance mode. */ + guidanceMode: AudioGuidanceMode; + /** Whether the device vibrates when voice alerts are played. */ + vibrationEnabled: boolean; + /** Whether voice guidance uses Bluetooth when it is available. */ + bluetoothAudioEnabled: boolean; +} + /** * Defines an individual road stretch within a route polyline, and its rendering style based on * traffic conditions. This is a NavSDK equivalent of the Google Maps RoadStretch.