Skip to content

Commit d43e7b8

Browse files
committed
add ability to observe when map click was not handled by drop-in (#6313)
1 parent ca93369 commit d43e7b8

File tree

13 files changed

+229
-56
lines changed

13 files changed

+229
-56
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ Mapbox welcomes participation and contributions from everyone.
1414
- Fixed an issue with `NavigationView` that caused `ViewOptionsCustomization.routeArrowOptions` to be ignored, if arrows have already been drawn on the map. [#6333](https://github.com/mapbox/mapbox-navigation-android/pull/6333)
1515
- Replaced `ViewStyleCustomization.defaultMarkerAnnotationOptions` with `ViewStyleCustomization.defaultDestinationMarkerAnnotationOptions`. [#6365](https://github.com/mapbox/mapbox-navigation-android/pull/6365)
1616
- Fixed the issue with incorrect geometry indices for `RouteLeg#incidents` and `RouteLeg#closures` after refresh. [#6364](https://github.com/mapbox/mapbox-navigation-android/pull/6364)
17+
- Introduced `NavigationViewListener#onMapClicked` to inform when a map was clicked, but the event was not processed by `NavigationView`. [#6360](https://github.com/mapbox/mapbox-navigation-android/pull/6360)
1718

1819
## Mapbox Navigation SDK 2.9.0-alpha.2 - 16 September, 2022
1920
### Changelog

libnavui-dropin/src/main/java/com/mapbox/navigation/dropin/NavigationViewContext.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import androidx.lifecycle.LifecycleOwner
66
import androidx.lifecycle.lifecycleScope
77
import com.mapbox.maps.MapView
88
import com.mapbox.navigation.base.ExperimentalPreviewMapboxNavigationAPI
9+
import com.mapbox.navigation.dropin.binder.map.MapClickBehavior
910
import com.mapbox.navigation.dropin.component.infopanel.InfoPanelBehavior
1011
import com.mapbox.navigation.dropin.component.maneuver.ManeuverBehavior
1112
import com.mapbox.navigation.dropin.component.marker.MapMarkerFactory
@@ -46,11 +47,13 @@ internal class NavigationViewContext(
4647
val infoPanelBehavior = InfoPanelBehavior()
4748
val mapViewOwner = MapViewOwner()
4849
val mapStyleLoader = MapStyleLoader(context, options)
50+
val mapClickBehavior = MapClickBehavior()
4951
val listenerRegistry by lazy {
5052
NavigationViewListenerRegistry(
5153
store,
5254
maneuverBehavior,
5355
infoPanelBehavior,
56+
mapClickBehavior,
5457
lifecycleOwner.lifecycleScope
5558
)
5659
}

libnavui-dropin/src/main/java/com/mapbox/navigation/dropin/NavigationViewListener.kt

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,4 +147,11 @@ abstract class NavigationViewListener {
147147
* Called when maneuver view has been collapsed.
148148
*/
149149
open fun onManeuverCollapsed() = Unit
150+
151+
/**
152+
* Called when a map was clicked, but the event was not handled by NavigationView.
153+
*
154+
* @param point The projected map coordinate the user clicked on.
155+
*/
156+
open fun onMapClicked(point: Point) = Unit
150157
}

libnavui-dropin/src/main/java/com/mapbox/navigation/dropin/NavigationViewListenerRegistry.kt

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import android.view.KeyEvent
44
import android.view.View
55
import com.google.android.material.bottomsheet.BottomSheetBehavior
66
import com.mapbox.navigation.base.ExperimentalPreviewMapboxNavigationAPI
7+
import com.mapbox.navigation.dropin.binder.map.MapClickBehavior
78
import com.mapbox.navigation.dropin.component.infopanel.InfoPanelBehavior
89
import com.mapbox.navigation.dropin.component.maneuver.ManeuverBehavior
910
import com.mapbox.navigation.ui.app.internal.Store
@@ -15,13 +16,16 @@ import kotlinx.coroutines.CoroutineScope
1516
import kotlinx.coroutines.Job
1617
import kotlinx.coroutines.flow.collect
1718
import kotlinx.coroutines.flow.filterNotNull
19+
import kotlinx.coroutines.flow.launchIn
20+
import kotlinx.coroutines.flow.onEach
1821
import kotlinx.coroutines.launch
1922

2023
@ExperimentalPreviewMapboxNavigationAPI
2124
internal class NavigationViewListenerRegistry(
2225
private val store: Store,
2326
private val maneuverSubscriber: ManeuverBehavior,
2427
private val infoPanelSubscriber: InfoPanelBehavior,
28+
private val mapClickSubscriber: MapClickBehavior,
2529
private val coroutineScope: CoroutineScope
2630
) : View.OnKeyListener {
2731
private var listeners = mutableMapOf<NavigationViewListener, Job>()
@@ -133,6 +137,10 @@ internal class NavigationViewListenerRegistry(
133137
}
134138
}
135139
}
140+
141+
mapClickSubscriber.mapClickBehavior
142+
.onEach { listener.onMapClicked(it) }
143+
.launchIn(scope = this)
136144
}
137145
}
138146

libnavui-dropin/src/main/java/com/mapbox/navigation/dropin/binder/map/MapBinder.kt

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package com.mapbox.navigation.dropin.binder.map
22

33
import android.view.ViewGroup
4+
import com.mapbox.geojson.Point
45
import com.mapbox.maps.MapView
56
import com.mapbox.maps.plugin.compass.compass
67
import com.mapbox.maps.plugin.locationcomponent.location
@@ -91,7 +92,7 @@ internal class MapBinder(
9192

9293
private fun routeLineComponent(lineOptions: MapboxRouteLineOptions) =
9394
RouteLineComponent(mapView.getMapboxMap(), mapView, lineOptions, contractProvider = {
94-
RouteLineComponentContractImpl(store)
95+
RouteLineComponentContractImpl(store, context.mapClickBehavior)
9596
})
9697

9798
private fun longPressMapComponent(navigationState: NavigationState) =
@@ -129,7 +130,8 @@ internal class MapBinder(
129130

130131
@ExperimentalPreviewMapboxNavigationAPI
131132
internal class RouteLineComponentContractImpl(
132-
private val store: Store
133+
private val store: Store,
134+
private val mapClickBehavior: MapClickBehavior,
133135
) : RouteLineComponentContract {
134136
override fun setRoutes(mapboxNavigation: MapboxNavigation, routes: List<NavigationRoute>) {
135137
when (store.state.value.navigation) {
@@ -161,4 +163,8 @@ internal class RouteLineComponentContractImpl(
161163
}
162164
}
163165
}
166+
167+
override fun onMapClicked(point: Point) {
168+
mapClickBehavior.onMapClicked(point)
169+
}
164170
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
package com.mapbox.navigation.dropin.binder.map
2+
3+
import com.mapbox.geojson.Point
4+
import kotlinx.coroutines.channels.BufferOverflow
5+
import kotlinx.coroutines.flow.MutableSharedFlow
6+
import kotlinx.coroutines.flow.asSharedFlow
7+
8+
internal class MapClickBehavior {
9+
10+
private val _mapClickBehavior = MutableSharedFlow<Point>(
11+
extraBufferCapacity = 1,
12+
onBufferOverflow = BufferOverflow.DROP_OLDEST,
13+
)
14+
val mapClickBehavior = _mapClickBehavior.asSharedFlow()
15+
16+
fun onMapClicked(point: Point) {
17+
_mapClickBehavior.tryEmit(point)
18+
}
19+
}

libnavui-dropin/src/test/java/com/mapbox/navigation/dropin/NavigationViewListenerRegistryTest.kt

Lines changed: 30 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,6 @@ import com.mapbox.navigation.base.ExperimentalPreviewMapboxNavigationAPI
99
import com.mapbox.navigation.base.route.NavigationRoute
1010
import com.mapbox.navigation.base.route.RouterFailure
1111
import com.mapbox.navigation.base.route.RouterOrigin
12-
import com.mapbox.navigation.dropin.component.infopanel.InfoPanelBehavior
13-
import com.mapbox.navigation.dropin.component.maneuver.ManeuverBehavior
1412
import com.mapbox.navigation.dropin.util.TestStore
1513
import com.mapbox.navigation.testing.MainCoroutineRule
1614
import com.mapbox.navigation.ui.app.internal.State
@@ -25,10 +23,11 @@ import io.mockk.spyk
2523
import io.mockk.verify
2624
import io.mockk.verifyOrder
2725
import kotlinx.coroutines.ExperimentalCoroutinesApi
26+
import kotlinx.coroutines.flow.MutableSharedFlow
2827
import kotlinx.coroutines.flow.MutableStateFlow
28+
import kotlinx.coroutines.flow.asSharedFlow
2929
import kotlinx.coroutines.flow.asStateFlow
3030
import org.junit.Assert.assertTrue
31-
import org.junit.Before
3231
import org.junit.Rule
3332
import org.junit.Test
3433

@@ -37,34 +36,27 @@ import org.junit.Test
3736
class NavigationViewListenerRegistryTest {
3837

3938
@get:Rule
40-
var coroutineRule = MainCoroutineRule()
41-
42-
private lateinit var sut: NavigationViewListenerRegistry
43-
private lateinit var testStore: TestStore
44-
private lateinit var maneuverBehaviorFlow: MutableStateFlow<MapboxManeuverViewState>
45-
private lateinit var infoPanelBehaviorFlow: MutableStateFlow<Int?>
46-
private lateinit var testListener: NavigationViewListener
47-
48-
@Before
49-
fun setUp() {
50-
testStore = TestStore()
51-
maneuverBehaviorFlow = MutableStateFlow(MapboxManeuverViewState.COLLAPSED)
52-
infoPanelBehaviorFlow = MutableStateFlow(null)
53-
val mockManeuverBehavior = mockk<ManeuverBehavior> {
39+
val coroutineRule = MainCoroutineRule()
40+
41+
private val testStore = TestStore()
42+
private val maneuverBehaviorFlow =
43+
MutableStateFlow<MapboxManeuverViewState>(MapboxManeuverViewState.COLLAPSED)
44+
private val infoPanelBehaviorFlow = MutableStateFlow<Int?>(value = null)
45+
private val mapClickBehaviorFlow = MutableSharedFlow<Point>(extraBufferCapacity = 1)
46+
private val testListener = spyk(object : NavigationViewListener() {})
47+
private val sut = NavigationViewListenerRegistry(
48+
testStore,
49+
mockk {
5450
every { maneuverBehavior } returns maneuverBehaviorFlow.asStateFlow()
55-
}
56-
val mockInfoPanelBehavior = mockk<InfoPanelBehavior> {
51+
},
52+
mockk {
5753
every { infoPanelBehavior } returns infoPanelBehaviorFlow.asStateFlow()
58-
}
59-
testListener = spyk(object : NavigationViewListener() {})
60-
61-
sut = NavigationViewListenerRegistry(
62-
testStore,
63-
mockManeuverBehavior,
64-
mockInfoPanelBehavior,
65-
coroutineRule.coroutineScope
66-
)
67-
}
54+
},
55+
mockk {
56+
every { mapClickBehavior } returns mapClickBehaviorFlow.asSharedFlow()
57+
},
58+
coroutineRule.coroutineScope,
59+
)
6860

6961
@Test
7062
fun onDestinationChanged() {
@@ -380,4 +372,13 @@ class NavigationViewListenerRegistryTest {
380372
testListener.onManeuverCollapsed()
381373
}
382374
}
375+
376+
@Test
377+
fun onMapClicked() {
378+
sut.registerListener(testListener)
379+
val point = mockk<Point>()
380+
mapClickBehaviorFlow.tryEmit(point)
381+
382+
verify { testListener.onMapClicked(point) }
383+
}
383384
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
package com.mapbox.navigation.dropin.binder.map
2+
3+
import com.mapbox.geojson.Point
4+
import io.mockk.mockk
5+
import kotlinx.coroutines.ExperimentalCoroutinesApi
6+
import kotlinx.coroutines.cancelAndJoin
7+
import kotlinx.coroutines.flow.launchIn
8+
import kotlinx.coroutines.flow.onEach
9+
import kotlinx.coroutines.test.runBlockingTest
10+
import org.junit.Assert.assertEquals
11+
import org.junit.Test
12+
13+
@ExperimentalCoroutinesApi
14+
class MapClickBehaviorTest {
15+
16+
@Test
17+
fun `when map is clicked, event is received`() = runBlockingTest {
18+
val sut = MapClickBehavior()
19+
val events = arrayListOf<Point>()
20+
val job = sut.mapClickBehavior.onEach { events.add(it) }.launchIn(scope = this)
21+
22+
val point = mockk<Point>()
23+
sut.onMapClicked(point)
24+
job.cancelAndJoin()
25+
26+
assertEquals(listOf(point), events)
27+
}
28+
}

libnavui-maps/src/main/java/com/mapbox/navigation/ui/maps/internal/ui/RouteLineComponent.kt

Lines changed: 20 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@ interface RouteLineComponentContract {
3636
fun setRoutes(mapboxNavigation: MapboxNavigation, routes: List<NavigationRoute>)
3737

3838
fun getRouteInPreview(): Flow<List<NavigationRoute>?>
39+
40+
fun onMapClicked(point: Point)
3941
}
4042

4143
@ExperimentalPreviewMapboxNavigationAPI
@@ -47,6 +49,10 @@ internal class MapboxRouteLineComponentContract : RouteLineComponentContract {
4749
override fun getRouteInPreview(): Flow<List<NavigationRoute>?> {
4850
return flowOf(null)
4951
}
52+
53+
override fun onMapClicked(point: Point) {
54+
// do nothing
55+
}
5056
}
5157

5258
@ExperimentalPreviewMapboxNavigationAPI
@@ -70,7 +76,7 @@ class RouteLineComponent(
7076

7177
private val routeClickPadding = Utils.dpToPx(30f)
7278

73-
private var onMapClickListener = OnMapClickListener { point ->
79+
private val onMapClickListener = OnMapClickListener { point ->
7480
mapboxNavigation?.also { selectRoute(it, point) }
7581
false
7682
}
@@ -144,23 +150,20 @@ class RouteLineComponent(
144150

145151
private fun selectRoute(mapboxNavigation: MapboxNavigation, point: Point) {
146152
coroutineScope.launch {
147-
val result = routeLineApi.findClosestRoute(
148-
point,
149-
mapboxMap,
150-
routeClickPadding
151-
)
152-
153-
result.onValue { resultValue ->
154-
if (resultValue.navigationRoute != routeLineApi.getPrimaryNavigationRoute()) {
155-
val reOrderedRoutes = routeLineApi.getNavigationRoutes()
156-
.filter { it != resultValue.navigationRoute }
157-
.toMutableList()
158-
.also {
159-
it.add(0, resultValue.navigationRoute)
153+
routeLineApi.findClosestRoute(point, mapboxMap, routeClickPadding).fold(
154+
{ contractProvider.get().onMapClicked(point) },
155+
{ result ->
156+
if (result.navigationRoute != routeLineApi.getPrimaryNavigationRoute()) {
157+
val reOrderedRoutes = arrayListOf(result.navigationRoute)
158+
routeLineApi.getNavigationRoutes().filterTo(reOrderedRoutes) { route ->
159+
route != result.navigationRoute
160160
}
161-
contractProvider.get().setRoutes(mapboxNavigation, reOrderedRoutes)
162-
}
163-
}
161+
contractProvider.get().setRoutes(mapboxNavigation, reOrderedRoutes)
162+
} else {
163+
contractProvider.get().onMapClicked(point)
164+
}
165+
},
166+
)
164167
}
165168
}
166169

0 commit comments

Comments
 (0)