Skip to content

Commit 6894410

Browse files
[Drop-In UI] Fixed the last audio instruction repeat on the next session (#6662)
* Fixed last audio instruction from previous session becomes the 1st on next session. * Removed redundant `MapboxVoiceInstructions.tripSessionStateFlow` * CHANGELOG entry
1 parent f9fbcc0 commit 6894410

File tree

3 files changed

+68
-40
lines changed

3 files changed

+68
-40
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ Mapbox welcomes participation and contributions from everyone.
88
- Added building highlight on arrival support to `NavigationView`. Added new customization options `ViewOptionsCustomization.enableBuildingHighlightOnArrival` and `ViewOptionsCustomization.buildingHighlightOptions`. [#6651](https://github.com/mapbox/mapbox-navigation-android/pull/6651)
99
- Added `ComponentInstaller` for the `BuildingHighlightComponent` that offers simplified integration of the `MapboxBuildingsApi` and `MapboxBuildingView`. [#6651](https://github.com/mapbox/mapbox-navigation-android/pull/6651)
1010
#### Bug fixes and improvements
11+
- Fixed an issue in the `NavigationView` where the last audio instruction from the previous session becomes the 1st on the next session. [#6662](https://github.com/mapbox/mapbox-navigation-android/pull/6662)
1112
- Fixed crash in `PermissionsLauncherFragment` occurring on device rotation. [#6635](https://github.com/mapbox/mapbox-navigation-android/pull/6635)
1213
- Fixed a rare `java.lang.IllegalArgumentException: The Path cannot loop back on itself.` exception when using `NavigationLocationProvider`. [#6641](https://github.com/mapbox/mapbox-navigation-android/pull/6641)
1314
- Started clearing route geometry cache when no routes (neither routes used for Active Guidance nor previewed ones) are available. [#6617](https://github.com/mapbox/mapbox-navigation-android/pull/6617)

libnavui-voice/src/main/java/com/mapbox/navigation/ui/voice/internal/MapboxVoiceInstructions.kt

Lines changed: 8 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,6 @@ import com.mapbox.navigation.core.trip.session.VoiceInstructionsObserver
1111
import kotlinx.coroutines.ExperimentalCoroutinesApi
1212
import kotlinx.coroutines.flow.Flow
1313
import kotlinx.coroutines.flow.MutableStateFlow
14-
import kotlinx.coroutines.flow.distinctUntilChangedBy
15-
import kotlinx.coroutines.flow.flatMapLatest
16-
import kotlinx.coroutines.flow.flowOf
1714
import kotlinx.coroutines.flow.mapLatest
1815

1916
/**
@@ -23,18 +20,22 @@ import kotlinx.coroutines.flow.mapLatest
2320
class MapboxVoiceInstructions {
2421

2522
private val voiceInstructionsFlow =
26-
MutableStateFlow<State>(MapboxVoiceInstructionsState(true, null))
23+
MutableStateFlow(MapboxVoiceInstructionsState(true, null))
2724
private val routesFlow = MutableStateFlow<List<NavigationRoute>>(emptyList())
28-
private val tripSessionStateFlow = MutableStateFlow(TripSessionState.STOPPED)
2925

3026
private val voiceInstructionsObserver = VoiceInstructionsObserver {
3127
voiceInstructionsFlow.value = MapboxVoiceInstructionsState(true, it)
3228
}
3329
private val routesObserver = RoutesObserver {
3430
routesFlow.value = it.navigationRoutes
31+
if (it.navigationRoutes.isEmpty()) {
32+
voiceInstructionsFlow.value = MapboxVoiceInstructionsState()
33+
}
3534
}
3635
private val tripSessionStateObserver = TripSessionStateObserver {
37-
tripSessionStateFlow.value = it
36+
if (it == TripSessionState.STOPPED) {
37+
voiceInstructionsFlow.value = MapboxVoiceInstructionsState()
38+
}
3839
}
3940

4041
fun registerObservers(mapboxNavigation: MapboxNavigation) {
@@ -51,38 +52,16 @@ class MapboxVoiceInstructions {
5152
resetFlows()
5253
}
5354

54-
fun voiceInstructions(): Flow<State> {
55-
return tripSessionStateFlow
56-
.flatMapLatest { tripSessionState ->
57-
if (tripSessionState == TripSessionState.STARTED) {
58-
routesUpdatedResultToVoiceInstructions()
59-
} else {
60-
flowOf(MapboxVoiceInstructionsState(false, null))
61-
}
62-
}
63-
}
55+
fun voiceInstructions(): Flow<State> = voiceInstructionsFlow
6456

6557
fun voiceLanguage(): Flow<String?> {
6658
return routesFlow
6759
.mapLatest { it.firstOrNull()?.directionsRoute?.voiceLanguage() }
6860
}
6961

70-
private fun routesUpdatedResultToVoiceInstructions(): Flow<State> {
71-
return routesFlow
72-
.distinctUntilChangedBy { it.isEmpty() }
73-
.flatMapLatest { routes ->
74-
if (routes.isNotEmpty()) {
75-
voiceInstructionsFlow
76-
} else {
77-
flowOf(MapboxVoiceInstructionsState(false, null))
78-
}
79-
}
80-
}
81-
8262
private fun resetFlows() {
8363
voiceInstructionsFlow.value = MapboxVoiceInstructionsState(true, null)
8464
routesFlow.value = emptyList()
85-
tripSessionStateFlow.value = TripSessionState.STOPPED
8665
}
8766

8867
interface State {

libnavui-voice/src/test/java/com/mapbox/navigation/ui/voice/internal/impl/MapboxVoiceInstructionsTest.kt

Lines changed: 59 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,9 @@ import io.mockk.mockk
1717
import io.mockk.slot
1818
import kotlinx.coroutines.ExperimentalCoroutinesApi
1919
import kotlinx.coroutines.flow.first
20+
import kotlinx.coroutines.test.runBlockingTest
2021
import org.junit.Assert.assertEquals
22+
import org.junit.Assert.assertNotNull
2123
import org.junit.Assert.assertNull
2224
import org.junit.Assert.assertTrue
2325
import org.junit.Rule
@@ -30,7 +32,7 @@ class MapboxVoiceInstructionsTest {
3032
val coroutineRule = MainCoroutineRule()
3133

3234
private val mapboxNavigation = mockk<MapboxNavigation>(relaxUnitFun = true)
33-
private val carAppVoiceInstructions = MapboxVoiceInstructions()
35+
private val sut = MapboxVoiceInstructions()
3436

3537
@Test
3638
fun `should emit voice instruction`() = coroutineRule.runBlockingTest {
@@ -53,8 +55,8 @@ class MapboxVoiceInstructionsTest {
5355
every { announcement() } returns "Left on Broadway"
5456
}
5557

56-
carAppVoiceInstructions.registerObservers(mapboxNavigation)
57-
val flow = carAppVoiceInstructions.voiceInstructions()
58+
sut.registerObservers(mapboxNavigation)
59+
val flow = sut.voiceInstructions()
5860
val initialInstruction = flow.first()
5961
observerSlot.captured.onNewVoiceInstructions(voiceInstructions)
6062
val updatedInstruction = flow.first()
@@ -90,10 +92,10 @@ class MapboxVoiceInstructionsTest {
9092
mapboxNavigation.registerVoiceInstructionsObserver(capture(observerSlot))
9193
} just Runs
9294

93-
val flow = carAppVoiceInstructions.voiceInstructions()
95+
val flow = sut.voiceInstructions()
9496
val initialAnnouncement = flow.first().voiceInstructions?.announcement()
9597

96-
carAppVoiceInstructions.registerObservers(mapboxNavigation)
98+
sut.registerObservers(mapboxNavigation)
9799

98100
observerSlot.captured.onNewVoiceInstructions(firstVoiceInstructions)
99101
val firstAnnouncement = flow.first().voiceInstructions?.announcement()
@@ -121,7 +123,7 @@ class MapboxVoiceInstructionsTest {
121123
firstArg<RoutesObserver>().onRoutesChanged(result)
122124
}
123125

124-
val state = carAppVoiceInstructions.voiceInstructions().first()
126+
val state = sut.voiceInstructions().first()
125127

126128
assertNull(state.voiceInstructions?.announcement())
127129
}
@@ -134,7 +136,7 @@ class MapboxVoiceInstructionsTest {
134136
)
135137
}
136138

137-
val state = carAppVoiceInstructions.voiceInstructions().first()
139+
val state = sut.voiceInstructions().first()
138140

139141
assertNull(state.voiceInstructions?.announcement())
140142
}
@@ -152,9 +154,9 @@ class MapboxVoiceInstructionsTest {
152154
firstArg<RoutesObserver>().onRoutesChanged(result)
153155
}
154156

155-
val flow = carAppVoiceInstructions.voiceLanguage()
157+
val flow = sut.voiceLanguage()
156158
val initialInstruction = flow.first()
157-
carAppVoiceInstructions.registerObservers(mapboxNavigation)
159+
sut.registerObservers(mapboxNavigation)
158160
val updatedInstruction = flow.first()
159161

160162
// routesFlow() on start sends empty list to disable sound button in FreeDrive
@@ -171,17 +173,63 @@ class MapboxVoiceInstructionsTest {
171173
firstArg<RoutesObserver>().onRoutesChanged(result)
172174
}
173175

174-
assertNull(carAppVoiceInstructions.voiceLanguage().first())
176+
assertNull(sut.voiceLanguage().first())
175177
}
176178

177179
@Test
178180
fun `should emit null voice language before routes are updated`() =
179181
coroutineRule.runBlockingTest {
180182
every { mapboxNavigation.registerRoutesObserver(any()) } just Runs
181183

182-
assertNull(carAppVoiceInstructions.voiceLanguage().first())
184+
assertNull(sut.voiceLanguage().first())
183185
}
184186

187+
@Test
188+
fun `should reset voiceInstructions when empty routes are set`() = runBlockingTest {
189+
val firstVoiceInstructions = mockk<VoiceInstructions>()
190+
val routesObserver = slot<RoutesObserver>()
191+
val instructionsObserver = slot<VoiceInstructionsObserver>()
192+
every { mapboxNavigation.registerRoutesObserver(capture((routesObserver))) } returns Unit
193+
every {
194+
mapboxNavigation.registerVoiceInstructionsObserver(capture(instructionsObserver))
195+
} returns Unit
196+
sut.registerObservers(mapboxNavigation)
197+
198+
instructionsObserver.captured.onNewVoiceInstructions(firstVoiceInstructions)
199+
val firstState = sut.voiceInstructions().first()
200+
routesObserver.captured.onRoutesChanged(
201+
mockk {
202+
every { navigationRoutes } returns emptyList()
203+
}
204+
)
205+
val secondState = sut.voiceInstructions().first()
206+
207+
assertNotNull(firstState.voiceInstructions)
208+
assertNull(secondState.voiceInstructions)
209+
}
210+
211+
@Test
212+
fun `should reset voiceInstructions when TripSession is STOPPED`() = runBlockingTest {
213+
val firstVoiceInstructions = mockk<VoiceInstructions>()
214+
val instructionsObserver = slot<VoiceInstructionsObserver>()
215+
val tripSessionObserver = slot<TripSessionStateObserver>()
216+
every {
217+
mapboxNavigation.registerVoiceInstructionsObserver(capture(instructionsObserver))
218+
} returns Unit
219+
every {
220+
mapboxNavigation.registerTripSessionStateObserver(capture(tripSessionObserver))
221+
} returns Unit
222+
sut.registerObservers(mapboxNavigation)
223+
224+
instructionsObserver.captured.onNewVoiceInstructions(firstVoiceInstructions)
225+
val firstState = sut.voiceInstructions().first()
226+
tripSessionObserver.captured.onSessionStateChanged(TripSessionState.STOPPED)
227+
val secondState = sut.voiceInstructions().first()
228+
229+
assertNotNull(firstState.voiceInstructions)
230+
assertNull(secondState.voiceInstructions)
231+
}
232+
185233
private fun createRoute(voiceLanguage: String): NavigationRoute {
186234
return mockk {
187235
every { directionsRoute } returns mockk {

0 commit comments

Comments
 (0)