1- package com.mapbox.navigation.ui.voice.internal.impl
1+ package com.mapbox.navigation.ui.voice.api
22
3- import android.content.Context
4- import com.mapbox.api.directions.v5.models.VoiceInstructions
5- import com.mapbox.navigation.base.ExperimentalPreviewMapboxNavigationAPI
3+ import androidx.annotation.VisibleForTesting
64import com.mapbox.navigation.core.MapboxNavigation
5+ import com.mapbox.navigation.core.lifecycle.MapboxNavigationApp
6+ import com.mapbox.navigation.core.lifecycle.MapboxNavigationObserver
77import com.mapbox.navigation.ui.utils.internal.configuration.NavigationConfigOwner
88import com.mapbox.navigation.ui.utils.internal.datastore.NavigationDataStoreOwner
99import com.mapbox.navigation.ui.utils.internal.datastore.booleanDataStoreKey
10- import com.mapbox.navigation.ui.voice.internal.MapboxAudioGuidance
11- import com.mapbox.navigation.ui.voice.internal.MapboxAudioGuidanceServices
1210import com.mapbox.navigation.ui.voice.internal.MapboxAudioGuidanceVoice
13- import com.mapbox.navigation.ui.voice.model.SpeechAnnouncement
11+ import com.mapbox.navigation.ui.voice.internal.impl.MapboxAudioGuidanceServices
1412import kotlinx.coroutines.CoroutineDispatcher
1513import kotlinx.coroutines.CoroutineScope
1614import kotlinx.coroutines.Dispatchers
@@ -35,30 +33,41 @@ import kotlinx.coroutines.launch
3533/* *
3634 * Implementation of [MapboxAudioGuidance]. See interface for details.
3735 */
38- @ExperimentalPreviewMapboxNavigationAPI
39- class MapboxAudioGuidanceImpl (
36+ class MapboxAudioGuidance
37+ @VisibleForTesting(otherwise = VisibleForTesting .PRIVATE )
38+ internal constructor (
4039 private val audioGuidanceServices: MapboxAudioGuidanceServices ,
41- private val configOwner : NavigationConfigOwner ,
42- dispatcher : CoroutineDispatcher = Dispatchers .Main ,
43- ) : MapboxAudioGuidance {
40+ dispatcher: CoroutineDispatcher ,
41+ ) : MapboxNavigationObserver {
4442
45- var dataStoreOwner : NavigationDataStoreOwner ? = null
43+ constructor () : this ( MapboxAudioGuidanceServices (), Dispatchers . Main )
4644
45+ private var dataStoreOwner: NavigationDataStoreOwner ? = null
46+ private var configOwner: NavigationConfigOwner ? = null
4747 private var mutedStateFlow = MutableStateFlow (false )
4848 private val internalStateFlow = MutableStateFlow (MapboxAudioGuidanceState ())
4949 private val scope = CoroutineScope (SupervisorJob () + dispatcher)
5050 private val mapboxVoiceInstructions = audioGuidanceServices.mapboxVoiceInstructions()
5151
5252 private var job: Job ? = null
5353
54+ /* *
55+ * @see [MapboxNavigationApp]
56+ */
5457 override fun onAttached (mapboxNavigation : MapboxNavigation ) {
58+ val context = mapboxNavigation.navigationOptions.applicationContext
59+ dataStoreOwner = audioGuidanceServices.dataStoreOwner(context, DEFAULT_DATA_STORE_NAME )
60+ configOwner = audioGuidanceServices.configOwner(context)
5561 mapboxVoiceInstructions.registerObservers(mapboxNavigation)
5662 job = scope.launch {
5763 restoreMutedState()
5864 audioGuidanceFlow(mapboxNavigation).collect()
5965 }
6066 }
6167
68+ /* *
69+ * @see [MapboxNavigationApp]
70+ */
6271 override fun onDetached (mapboxNavigation : MapboxNavigation ) {
6372 mapboxVoiceInstructions.unregisterObservers(mapboxNavigation)
6473 job?.cancel()
@@ -73,12 +82,12 @@ class MapboxAudioGuidanceImpl(
7382 *
7483 * You can also control audio guidance by calling [mute], [unmute] or [toggle]
7584 */
76- override fun stateFlow (): StateFlow <MapboxAudioGuidance . State > = internalStateFlow
85+ fun stateFlow (): StateFlow <MapboxAudioGuidanceState > = internalStateFlow
7786
7887 /* *
7988 * Explicit call to mute the audio guidance state.
8089 */
81- override fun mute () {
90+ fun mute () {
8291 scope.launch {
8392 setMutedState(true )
8493 }
@@ -87,7 +96,7 @@ class MapboxAudioGuidanceImpl(
8796 /* *
8897 * Explicit call to unmute the audio guidance state.
8998 */
90- override fun unmute () {
99+ fun unmute () {
91100 scope.launch {
92101 setMutedState(false )
93102 }
@@ -96,7 +105,7 @@ class MapboxAudioGuidanceImpl(
96105 /* *
97106 * Toggle the muted state. E.g., if audio is muted, make it unmuted.
98107 */
99- override fun toggle () {
108+ fun toggle () {
100109 scope.launch {
101110 if (mutedStateFlow.value) {
102111 unmute()
@@ -112,10 +121,10 @@ class MapboxAudioGuidanceImpl(
112121 @OptIn(ExperimentalCoroutinesApi ::class )
113122 private fun audioGuidanceFlow (
114123 mapboxNavigation : MapboxNavigation
115- ): Flow <MapboxAudioGuidance . State > {
124+ ): Flow <MapboxAudioGuidanceState > {
116125 return combine(
117126 mapboxVoiceInstructions.voiceLanguage(),
118- configOwner.language(),
127+ configOwner!! .language(),
119128 ) { voiceLanguage, deviceLanguage -> voiceLanguage ? : deviceLanguage }
120129 .distinctUntilChanged()
121130 .flatMapLatest { language ->
@@ -134,7 +143,7 @@ class MapboxAudioGuidanceImpl(
134143 /* *
135144 * This flow will monitor navigation state to determine if audio is available.
136145 */
137- private fun silentFlow (): Flow <MapboxAudioGuidance . State > {
146+ private fun silentFlow (): Flow <MapboxAudioGuidanceState > {
138147 return mapboxVoiceInstructions.voiceInstructions()
139148 .map { state ->
140149 internalStateFlow.updateAndGet {
@@ -153,7 +162,7 @@ class MapboxAudioGuidanceImpl(
153162 @OptIn(FlowPreview ::class )
154163 private fun speechFlow (
155164 audioGuidance : MapboxAudioGuidanceVoice
156- ): Flow <MapboxAudioGuidance . State > {
165+ ): Flow <MapboxAudioGuidanceState > {
157166 return mapboxVoiceInstructions.voiceInstructions()
158167 .flatMapConcat { voice ->
159168 internalStateFlow.update {
@@ -166,7 +175,14 @@ class MapboxAudioGuidanceImpl(
166175 audioGuidance.speak(voice.voiceInstructions)
167176 }
168177 .map { speechAnnouncement ->
169- internalStateFlow.updateAndGet { it.copy(speechAnnouncement = speechAnnouncement) }
178+ internalStateFlow.updateAndGet {
179+ MapboxAudioGuidanceState (
180+ isPlayable = it.isPlayable,
181+ isMuted = it.isMuted,
182+ voiceInstructions = it.voiceInstructions,
183+ speechAnnouncement = speechAnnouncement
184+ )
185+ }
170186 }
171187 }
172188
@@ -181,22 +197,9 @@ class MapboxAudioGuidanceImpl(
181197 dataStoreOwner?.write(STORE_AUDIO_GUIDANCE_MUTED , muted)
182198 }
183199
184- companion object {
185- val STORE_AUDIO_GUIDANCE_MUTED =
200+ private companion object {
201+ private val STORE_AUDIO_GUIDANCE_MUTED =
186202 booleanDataStoreKey(" audio_guidance_muted" , false )
187-
188- fun create (context : Context ): MapboxAudioGuidanceImpl {
189- return MapboxAudioGuidanceImpl (
190- MapboxAudioGuidanceServicesImpl (),
191- NavigationConfigOwner (context)
192- )
193- }
203+ private const val DEFAULT_DATA_STORE_NAME = " mapbox_navigation_preferences"
194204 }
195205}
196-
197- private data class MapboxAudioGuidanceState (
198- override val isPlayable : Boolean = false ,
199- override val isMuted : Boolean = false ,
200- override val voiceInstructions : VoiceInstructions ? = null ,
201- override val speechAnnouncement : SpeechAnnouncement ? = null ,
202- ) : MapboxAudioGuidance.State
0 commit comments