Skip to content

Commit

Permalink
restore overview camera when routes change during route preview state (
Browse files Browse the repository at this point in the history
  • Loading branch information
Zayankovsky authored Jan 16, 2023
1 parent 2d5ccaa commit 2275707
Show file tree
Hide file tree
Showing 9 changed files with 146 additions and 83 deletions.
1 change: 1 addition & 0 deletions changelog/unreleased/bugfixes/6840.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
- Improved `NavigationView` camera behavior to go back into overview state if routes change during route preview state.
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package com.mapbox.navigation.ui.app.internal.controller

import com.mapbox.navigation.base.ExperimentalPreviewMapboxNavigationAPI
import com.mapbox.navigation.core.MapboxNavigation
import com.mapbox.navigation.ui.app.internal.Action
import com.mapbox.navigation.ui.app.internal.State
Expand All @@ -13,7 +12,6 @@ import com.mapbox.navigation.ui.voice.api.MapboxAudioGuidance
* This class is responsible for playing voice instructions. Use the [AudioAction] to turning the
* audio on or off.
*/
@OptIn(ExperimentalPreviewMapboxNavigationAPI::class)
class AudioGuidanceStateController(
private val store: Store
) : StateController() {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package com.mapbox.navigation.ui.app.internal.controller

import com.mapbox.navigation.base.ExperimentalPreviewMapboxNavigationAPI
import com.mapbox.navigation.core.MapboxNavigation
import com.mapbox.navigation.ui.app.internal.Action
import com.mapbox.navigation.ui.app.internal.State
Expand All @@ -9,8 +8,9 @@ import com.mapbox.navigation.ui.app.internal.camera.CameraAction
import com.mapbox.navigation.ui.app.internal.camera.CameraState
import com.mapbox.navigation.ui.app.internal.camera.TargetCameraMode
import com.mapbox.navigation.ui.app.internal.navigation.NavigationState
import com.mapbox.navigation.ui.app.internal.routefetch.RoutePreviewState
import kotlinx.coroutines.flow.distinctUntilChanged

@OptIn(ExperimentalPreviewMapboxNavigationAPI::class)
class CameraStateController(
private val store: Store,
) : StateController() {
Expand All @@ -21,8 +21,16 @@ class CameraStateController(
override fun onAttached(mapboxNavigation: MapboxNavigation) {
super.onAttached(mapboxNavigation)

store.select { it.navigation }.observe { navigationState ->
when (navigationState) {
store.state.distinctUntilChanged { old, new ->
if (new.navigation is NavigationState.RoutePreview) {
new.previewRoutes !is RoutePreviewState.Ready ||
old.navigation is NavigationState.RoutePreview &&
new.previewRoutes == old.previewRoutes
} else {
new.navigation == old.navigation
}
}.observe { state ->
when (state.navigation) {
NavigationState.FreeDrive, NavigationState.RoutePreview -> {
store.dispatch(CameraAction.SetCameraMode(TargetCameraMode.Overview))
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
package com.mapbox.navigation.ui.app.internal.controller

import com.mapbox.navigation.base.ExperimentalPreviewMapboxNavigationAPI
import com.mapbox.navigation.ui.app.internal.Action
import com.mapbox.navigation.ui.app.internal.State
import com.mapbox.navigation.ui.app.internal.Store
import com.mapbox.navigation.ui.app.internal.destination.DestinationAction

@OptIn(ExperimentalPreviewMapboxNavigationAPI::class)
class DestinationStateController(
store: Store
) : StateController() {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package com.mapbox.navigation.ui.app.internal.controller

import com.mapbox.navigation.base.ExperimentalPreviewMapboxNavigationAPI
import com.mapbox.navigation.core.MapboxNavigation
import com.mapbox.navigation.core.internal.extensions.flowLocationMatcherResult
import com.mapbox.navigation.core.trip.session.LocationMatcherResult
Expand All @@ -9,7 +8,6 @@ import com.mapbox.navigation.ui.app.internal.State
import com.mapbox.navigation.ui.app.internal.Store
import com.mapbox.navigation.ui.app.internal.location.LocationAction

@OptIn(ExperimentalPreviewMapboxNavigationAPI::class)
class LocationStateController(
private val store: Store
) : StateController() {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package com.mapbox.navigation.ui.app.internal.controller

import com.mapbox.navigation.base.ExperimentalPreviewMapboxNavigationAPI
import com.mapbox.navigation.core.MapboxNavigation
import com.mapbox.navigation.core.internal.extensions.flowOnFinalDestinationArrival
import com.mapbox.navigation.ui.app.internal.Action
Expand All @@ -16,7 +15,6 @@ import kotlinx.coroutines.launch
* [NavigationStateAction] received.
* @param store the default [NavigationState]
*/
@OptIn(ExperimentalPreviewMapboxNavigationAPI::class)
class NavigationStateController(
private val store: Store
) : StateController() {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package com.mapbox.navigation.ui.app.internal.controller

import com.mapbox.navigation.base.ExperimentalPreviewMapboxNavigationAPI
import com.mapbox.navigation.base.route.NavigationRoute
import com.mapbox.navigation.core.MapboxNavigation
import com.mapbox.navigation.core.internal.extensions.flowRoutesUpdated
Expand All @@ -9,7 +8,6 @@ import com.mapbox.navigation.ui.app.internal.State
import com.mapbox.navigation.ui.app.internal.Store
import com.mapbox.navigation.ui.app.internal.routefetch.RoutesAction

@OptIn(ExperimentalPreviewMapboxNavigationAPI::class)
class RouteStateController(private val store: Store) : StateController() {
init {
store.register(this)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
package com.mapbox.navigation.ui.app.internal.controller

import com.mapbox.navigation.base.ExperimentalPreviewMapboxNavigationAPI
import com.mapbox.navigation.ui.app.internal.Reducer
import com.mapbox.navigation.ui.base.lifecycle.UIComponent

@OptIn(ExperimentalPreviewMapboxNavigationAPI::class)
abstract class StateController : UIComponent(), Reducer
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,15 @@ package com.mapbox.navigation.ui.app.internal.controller

import com.mapbox.geojson.Point
import com.mapbox.maps.EdgeInsets
import com.mapbox.navigation.base.route.NavigationRoute
import com.mapbox.navigation.core.MapboxNavigation
import com.mapbox.navigation.core.lifecycle.MapboxNavigationApp
import com.mapbox.navigation.testing.MainCoroutineRule
import com.mapbox.navigation.ui.app.internal.camera.CameraAction
import com.mapbox.navigation.ui.app.internal.camera.CameraAction.SetCameraMode
import com.mapbox.navigation.ui.app.internal.camera.TargetCameraMode
import com.mapbox.navigation.ui.app.internal.navigation.NavigationState
import com.mapbox.navigation.ui.app.internal.routefetch.RoutePreviewState
import com.mapbox.navigation.ui.app.testing.TestStore
import io.mockk.every
import io.mockk.mockk
Expand Down Expand Up @@ -40,7 +42,7 @@ class CameraStateControllerTest {
}

@Test
fun `when action toIdle updates camera mode`() = coroutineRule.runBlockingTest {
fun `when action toIdle updates camera mode`() {
val sut = CameraStateController(testStore)
sut.onAttached(mockMapboxNavigation())

Expand All @@ -51,21 +53,20 @@ class CameraStateControllerTest {
}

@Test
fun `when action toIdle should copy currentCamera mode value to savedCameraMode`() =
coroutineRule.runBlockingTest {
val sut = CameraStateController(testStore)
sut.onAttached(mockMapboxNavigation())
fun `when action toIdle should copy currentCamera mode value to savedCameraMode`() {
val sut = CameraStateController(testStore)
sut.onAttached(mockMapboxNavigation())

val initialCameraMode = TargetCameraMode.Following
testStore.dispatch(SetCameraMode(initialCameraMode))
testStore.dispatch(SetCameraMode(TargetCameraMode.Idle))
val initialCameraMode = TargetCameraMode.Following
testStore.dispatch(SetCameraMode(initialCameraMode))
testStore.dispatch(SetCameraMode(TargetCameraMode.Idle))

val cameraState = testStore.state.value.camera
assertEquals(initialCameraMode, cameraState.savedCameraMode)
}
val cameraState = testStore.state.value.camera
assertEquals(initialCameraMode, cameraState.savedCameraMode)
}

@Test
fun `when action toOverview updates camera mode`() = coroutineRule.runBlockingTest {
fun `when action toOverview updates camera mode`() {
val sut = CameraStateController(testStore)
sut.onAttached(mockMapboxNavigation())

Expand All @@ -76,29 +77,27 @@ class CameraStateControllerTest {
}

@Test
fun `when action toFollowing updates camera mode and zoomUpdatesAllowed`() =
coroutineRule.runBlockingTest {
val sut = CameraStateController(testStore)
sut.onAttached(mockMapboxNavigation())
fun `when action toFollowing updates camera mode and zoomUpdatesAllowed`() {
val sut = CameraStateController(testStore)
sut.onAttached(mockMapboxNavigation())

testStore.dispatch(SetCameraMode(TargetCameraMode.Following))
testStore.dispatch(SetCameraMode(TargetCameraMode.Following))

val cameraState = testStore.state.value.camera
assertEquals(TargetCameraMode.Following, cameraState.cameraMode)
}
val cameraState = testStore.state.value.camera
assertEquals(TargetCameraMode.Following, cameraState.cameraMode)
}

@Test
fun `when action UpdatePadding updates cameraPadding`() =
coroutineRule.runBlockingTest {
val padding = EdgeInsets(1.0, 2.0, 3.0, 4.0)
val sut = CameraStateController(testStore)
sut.onAttached(mockMapboxNavigation())
fun `when action UpdatePadding updates cameraPadding`() {
val padding = EdgeInsets(1.0, 2.0, 3.0, 4.0)
val sut = CameraStateController(testStore)
sut.onAttached(mockMapboxNavigation())

testStore.dispatch(CameraAction.UpdatePadding(padding))
testStore.dispatch(CameraAction.UpdatePadding(padding))

val cameraState = testStore.state.value.camera
assertEquals(padding, cameraState.cameraPadding)
}
val cameraState = testStore.state.value.camera
assertEquals(padding, cameraState.cameraPadding)
}

@Test
fun `on SaveMapState action should save map camera state in the store`() {
Expand All @@ -119,64 +118,131 @@ class CameraStateControllerTest {
}

@Test
fun `camera is set to overview in route preview mode`() =
coroutineRule.runBlockingTest {
val sut = CameraStateController(testStore)
sut.onAttached(mockMapboxNavigation())
fun `camera is unchanged in route preview mode without preview routes`() {
val sut = CameraStateController(testStore)
sut.onAttached(mockMapboxNavigation())

testStore.updateState { it.copy(navigation = NavigationState.DestinationPreview) }
testStore.dispatch(SetCameraMode(TargetCameraMode.Following))
testStore.updateState { state ->
state.copy(
navigation = NavigationState.RoutePreview,
previewRoutes = RoutePreviewState.Empty,
)
}

val state = testStore.state.value.copy(navigation = NavigationState.RoutePreview)
testStore.setState(state)
assertEquals(TargetCameraMode.Following, testStore.state.value.camera.cameraMode)
}

assertEquals(TargetCameraMode.Overview, testStore.state.value.camera.cameraMode)
@Test
fun `camera is set to overview in route preview mode with preview routes`() {
val sut = CameraStateController(testStore)
sut.onAttached(mockMapboxNavigation())

testStore.updateState { it.copy(navigation = NavigationState.DestinationPreview) }
testStore.dispatch(SetCameraMode(TargetCameraMode.Following))
testStore.updateState { state ->
state.copy(
navigation = NavigationState.RoutePreview,
previewRoutes = RoutePreviewState.Ready(listOf(mockk())),
)
}

assertEquals(TargetCameraMode.Overview, testStore.state.value.camera.cameraMode)
}

@Test
fun `camera is set to overview in free drive mode`() =
coroutineRule.runBlockingTest {
val sut = CameraStateController(testStore)
sut.onAttached(mockMapboxNavigation())
fun `camera is restored to overview in route preview mode with new preview routes`() {
val sut = CameraStateController(testStore)
sut.onAttached(mockMapboxNavigation())

testStore.updateState { state ->
state.copy(
navigation = NavigationState.RoutePreview,
previewRoutes = RoutePreviewState.Ready(listOf(mockk())),
)
}
testStore.dispatch(SetCameraMode(TargetCameraMode.Following))
testStore.updateState { state ->
state.copy(
navigation = NavigationState.RoutePreview,
previewRoutes = RoutePreviewState.Ready(listOf(mockk())),
)
}

assertEquals(TargetCameraMode.Overview, testStore.state.value.camera.cameraMode)
}

val state = testStore.state.value.copy(navigation = NavigationState.FreeDrive)
testStore.setState(state)
@Test
fun `camera is unchanged in route preview mode with the same preview routes`() {
val sut = CameraStateController(testStore)
sut.onAttached(mockMapboxNavigation())
val route = mockk<NavigationRoute>()

assertEquals(TargetCameraMode.Overview, testStore.state.value.camera.cameraMode)
testStore.updateState { state ->
state.copy(
navigation = NavigationState.RoutePreview,
previewRoutes = RoutePreviewState.Ready(listOf(route)),
)
}
testStore.dispatch(SetCameraMode(TargetCameraMode.Following))
testStore.updateState { state ->
state.copy(
navigation = NavigationState.RoutePreview,
previewRoutes = RoutePreviewState.Ready(listOf(route)),
)
}

assertEquals(TargetCameraMode.Following, testStore.state.value.camera.cameraMode)
}

@Test
fun `camera is set to following in active navigation mode`() =
coroutineRule.runBlockingTest {
val sut = CameraStateController(testStore)
sut.onAttached(mockMapboxNavigation())
fun `camera is set to overview in free drive mode`() {
val sut = CameraStateController(testStore)
sut.onAttached(mockMapboxNavigation())

val state = testStore.state.value.copy(navigation = NavigationState.ActiveNavigation)
testStore.setState(state)
testStore.updateState { it.copy(navigation = NavigationState.DestinationPreview) }
testStore.dispatch(SetCameraMode(TargetCameraMode.Following))
testStore.updateState { it.copy(navigation = NavigationState.FreeDrive) }

assertEquals(TargetCameraMode.Following, testStore.state.value.camera.cameraMode)
}
assertEquals(TargetCameraMode.Overview, testStore.state.value.camera.cameraMode)
}

@Test
fun `camera is set to following in arrival mode`() =
coroutineRule.runBlockingTest {
val sut = CameraStateController(testStore)
sut.onAttached(mockMapboxNavigation())
fun `camera is set to following in active navigation mode`() {
val sut = CameraStateController(testStore)
sut.onAttached(mockMapboxNavigation())

val state = testStore.state.value.copy(navigation = NavigationState.Arrival)
testStore.setState(state)
testStore.updateState { it.copy(navigation = NavigationState.RoutePreview) }
testStore.dispatch(SetCameraMode(TargetCameraMode.Overview))
testStore.updateState { it.copy(navigation = NavigationState.ActiveNavigation) }

assertEquals(TargetCameraMode.Following, testStore.state.value.camera.cameraMode)
}
assertEquals(TargetCameraMode.Following, testStore.state.value.camera.cameraMode)
}

@Test
fun `camera is set to idle in destination preview mode`() =
coroutineRule.runBlockingTest {
val sut = CameraStateController(testStore)
sut.onAttached(mockMapboxNavigation())
fun `camera is set to following in arrival mode`() {
val sut = CameraStateController(testStore)
sut.onAttached(mockMapboxNavigation())

val state = testStore.state.value.copy(navigation = NavigationState.DestinationPreview)
testStore.setState(state)
testStore.updateState { it.copy(navigation = NavigationState.ActiveNavigation) }
testStore.dispatch(SetCameraMode(TargetCameraMode.Overview))
testStore.updateState { it.copy(navigation = NavigationState.Arrival) }

assertEquals(TargetCameraMode.Idle, testStore.state.value.camera.cameraMode)
}
assertEquals(TargetCameraMode.Following, testStore.state.value.camera.cameraMode)
}

@Test
fun `camera is set to idle in destination preview mode`() {
val sut = CameraStateController(testStore)
sut.onAttached(mockMapboxNavigation())

testStore.updateState { it.copy(navigation = NavigationState.FreeDrive) }
testStore.dispatch(SetCameraMode(TargetCameraMode.Overview))
testStore.updateState { it.copy(navigation = NavigationState.DestinationPreview) }

assertEquals(TargetCameraMode.Idle, testStore.state.value.camera.cameraMode)
}

private fun mockMapboxNavigation(): MapboxNavigation {
val mapboxNavigation = mockk<MapboxNavigation>(relaxed = true)
Expand Down

0 comments on commit 2275707

Please sign in to comment.