Skip to content

Commit 6e043c6

Browse files
committed
Test new route preview
1 parent bfbadd0 commit 6e043c6

File tree

5 files changed

+290
-0
lines changed

5 files changed

+290
-0
lines changed
Lines changed: 186 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,186 @@
1+
package com.mapbox.androidauto.car.preview
2+
3+
import android.text.SpannableString
4+
import androidx.annotation.UiThread
5+
import androidx.car.app.Screen
6+
import androidx.car.app.model.Action
7+
import androidx.car.app.model.ActionStrip
8+
import androidx.car.app.model.DurationSpan
9+
import androidx.car.app.model.ItemList
10+
import androidx.car.app.model.Row
11+
import androidx.car.app.model.Template
12+
import androidx.car.app.navigation.model.RoutePreviewNavigationTemplate
13+
import androidx.lifecycle.DefaultLifecycleObserver
14+
import androidx.lifecycle.LifecycleOwner
15+
import com.mapbox.androidauto.R
16+
import com.mapbox.androidauto.car.feedback.ui.CarFeedbackAction
17+
import com.mapbox.androidauto.car.location.CarLocationRenderer
18+
import com.mapbox.androidauto.car.navigation.CarCameraMode
19+
import com.mapbox.androidauto.car.navigation.CarDistanceFormatter
20+
import com.mapbox.androidauto.car.navigation.CarNavigationCamera
21+
import com.mapbox.androidauto.car.navigation.speedlimit.CarSpeedLimitRenderer
22+
import com.mapbox.androidauto.car.placeslistonmap.PlacesListOnMapLayerUtil
23+
import com.mapbox.androidauto.car.routes.NavigationCarRoutesProvider
24+
import com.mapbox.androidauto.car.search.PlaceRecord
25+
import com.mapbox.androidauto.internal.car.extensions.addBackPressedHandler
26+
import com.mapbox.androidauto.internal.car.extensions.handleStyleOnAttached
27+
import com.mapbox.androidauto.internal.car.extensions.handleStyleOnDetached
28+
import com.mapbox.androidauto.internal.logAndroidAuto
29+
import com.mapbox.androidauto.navigation.audioguidance.muteAudioGuidance
30+
import com.mapbox.androidauto.screenmanager.MapboxScreen
31+
import com.mapbox.androidauto.screenmanager.MapboxScreenManager
32+
import com.mapbox.geojson.Feature
33+
import com.mapbox.geojson.FeatureCollection
34+
import com.mapbox.maps.MapboxExperimental
35+
import com.mapbox.maps.extension.androidauto.MapboxCarMapObserver
36+
import com.mapbox.maps.extension.androidauto.MapboxCarMapSurface
37+
import com.mapbox.maps.plugin.delegates.listeners.OnStyleLoadedListener
38+
import com.mapbox.navigation.base.ExperimentalPreviewMapboxNavigationAPI
39+
import com.mapbox.navigation.base.route.NavigationRoute
40+
import com.mapbox.navigation.core.lifecycle.MapboxNavigationApp
41+
import com.mapbox.navigation.core.preview.RoutesPreview
42+
43+
/**
44+
* After a destination has been selected. This view previews the route and lets
45+
* you select alternatives. From here, you can start turn-by-turn navigation.
46+
*/
47+
@OptIn(MapboxExperimental::class, ExperimentalPreviewMapboxNavigationAPI::class)
48+
internal class CarRoutePreviewScreen2 @UiThread constructor(
49+
private val routePreviewCarContext: RoutePreviewCarContext,
50+
private val placeRecord: PlaceRecord,
51+
private val placesLayerUtil: PlacesListOnMapLayerUtil = PlacesListOnMapLayerUtil(),
52+
) : Screen(routePreviewCarContext.carContext) {
53+
54+
private val routesProvider = NavigationCarRoutesProvider()
55+
private val carRouteLine = CarRouteLine(routesProvider)
56+
private val carLocationRenderer = CarLocationRenderer()
57+
private val carSpeedLimitRenderer = CarSpeedLimitRenderer(
58+
routePreviewCarContext.mapboxCarContext
59+
)
60+
private val carNavigationCamera = CarNavigationCamera(
61+
initialCarCameraMode = CarCameraMode.OVERVIEW,
62+
alternativeCarCameraMode = CarCameraMode.FOLLOWING,
63+
carRoutesProvider = routesProvider,
64+
)
65+
66+
private var styleLoadedListener: OnStyleLoadedListener? = null
67+
68+
private val surfaceListener = object : MapboxCarMapObserver {
69+
70+
override fun onAttached(mapboxCarMapSurface: MapboxCarMapSurface) {
71+
super.onAttached(mapboxCarMapSurface)
72+
logAndroidAuto("CarRoutePreviewScreen loaded")
73+
styleLoadedListener = mapboxCarMapSurface.handleStyleOnAttached { style ->
74+
placesLayerUtil.initializePlacesListOnMapLayer(
75+
style,
76+
carContext.resources
77+
)
78+
val coordinate = placeRecord.coordinate ?: return@handleStyleOnAttached
79+
val featureCollection =
80+
FeatureCollection.fromFeature(Feature.fromGeometry(coordinate))
81+
placesLayerUtil.updatePlacesListOnMapLayer(
82+
style,
83+
featureCollection
84+
)
85+
}
86+
}
87+
88+
override fun onDetached(mapboxCarMapSurface: MapboxCarMapSurface) {
89+
super.onDetached(mapboxCarMapSurface)
90+
logAndroidAuto("CarRoutePreviewScreen detached")
91+
mapboxCarMapSurface.handleStyleOnDetached(styleLoadedListener)?.let {
92+
placesLayerUtil.removePlacesListOnMapLayer(it)
93+
}
94+
}
95+
}
96+
97+
init {
98+
logAndroidAuto("CarRoutePreviewScreen constructor")
99+
addBackPressedHandler {
100+
logAndroidAuto("CarRoutePreviewScreen onBackPressed")
101+
routePreviewCarContext.mapboxScreenManager.goBack()
102+
}
103+
lifecycle.muteAudioGuidance()
104+
lifecycle.addObserver(object : DefaultLifecycleObserver {
105+
106+
override fun onResume(owner: LifecycleOwner) {
107+
logAndroidAuto("CarRoutePreviewScreen onResume")
108+
routePreviewCarContext.mapboxCarMap.registerObserver(carLocationRenderer)
109+
routePreviewCarContext.mapboxCarMap.registerObserver(carSpeedLimitRenderer)
110+
routePreviewCarContext.mapboxCarMap.registerObserver(carNavigationCamera)
111+
routePreviewCarContext.mapboxCarMap.registerObserver(carRouteLine)
112+
routePreviewCarContext.mapboxCarMap.registerObserver(surfaceListener)
113+
}
114+
115+
override fun onPause(owner: LifecycleOwner) {
116+
logAndroidAuto("CarRoutePreviewScreen onPause")
117+
routePreviewCarContext.mapboxCarMap.unregisterObserver(carLocationRenderer)
118+
routePreviewCarContext.mapboxCarMap.unregisterObserver(carSpeedLimitRenderer)
119+
routePreviewCarContext.mapboxCarMap.unregisterObserver(carNavigationCamera)
120+
routePreviewCarContext.mapboxCarMap.unregisterObserver(carRouteLine)
121+
routePreviewCarContext.mapboxCarMap.unregisterObserver(surfaceListener)
122+
}
123+
})
124+
}
125+
126+
override fun onGetTemplate(): Template {
127+
val listBuilder = ItemList.Builder()
128+
val routesPreview = routesProvider.routesPreview.value
129+
val navigationRoutes = routesPreview?.originalRoutesList
130+
?: emptyList()
131+
navigationRoutes.forEach { navigationRoute ->
132+
val route = navigationRoute.directionsRoute
133+
val title = route.legs()?.first()?.summary() ?: placeRecord.name
134+
val duration = CarDistanceFormatter.formatDistance(route.duration())
135+
val routeSpannableString = SpannableString("$duration $title")
136+
routeSpannableString.setSpan(
137+
DurationSpan.create(route.duration().toLong()),
138+
0,
139+
duration.length,
140+
0
141+
)
142+
143+
listBuilder.addItem(
144+
Row.Builder()
145+
.setTitle(routeSpannableString)
146+
.addText(duration)
147+
.build()
148+
)
149+
}
150+
if (routesPreview != null && navigationRoutes.isNotEmpty()) {
151+
listBuilder.setSelectedIndex(routesPreview.primaryRouteIndex)
152+
listBuilder.setOnSelectedListener { index ->
153+
MapboxNavigationApp.current()?.setRoutesPreview(
154+
routesPreview.originalRoutesList,
155+
index
156+
)
157+
}
158+
}
159+
160+
return RoutePreviewNavigationTemplate.Builder()
161+
.setItemList(listBuilder.build())
162+
.setTitle(carContext.getString(R.string.car_action_preview_title))
163+
.setActionStrip(
164+
ActionStrip.Builder()
165+
.addAction(
166+
CarFeedbackAction(
167+
MapboxScreen.ROUTE_PREVIEW_FEEDBACK
168+
).getAction(this@CarRoutePreviewScreen2)
169+
)
170+
.build()
171+
)
172+
.setHeaderAction(Action.BACK)
173+
.setNavigateAction(
174+
Action.Builder()
175+
.setTitle(carContext.getString(R.string.car_action_preview_navigate_button))
176+
.setOnClickListener {
177+
MapboxNavigationApp.current()!!.setNavigationRoutes(
178+
routesPreview!!.routesList
179+
)
180+
MapboxScreenManager.replaceTop(MapboxScreen.ACTIVE_GUIDANCE)
181+
}
182+
.build(),
183+
)
184+
.build()
185+
}
186+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
package com.mapbox.androidauto.car.preview
2+
3+
import androidx.car.app.CarContext
4+
import androidx.car.app.Screen
5+
import com.mapbox.androidauto.car.FreeDriveCarScreen
6+
import com.mapbox.androidauto.car.MapboxCarContext
7+
import com.mapbox.androidauto.car.preview.CarRoutePreviewScreen
8+
import com.mapbox.androidauto.car.preview.RoutePreviewCarContext
9+
import com.mapbox.androidauto.internal.logAndroidAuto
10+
import com.mapbox.androidauto.screenmanager.MapboxScreen
11+
import com.mapbox.androidauto.screenmanager.MapboxScreenFactory
12+
import com.mapbox.navigation.base.ExperimentalPreviewMapboxNavigationAPI
13+
14+
/**
15+
* Default screen for [MapboxScreen.ROUTE_PREVIEW].
16+
*/
17+
@ExperimentalPreviewMapboxNavigationAPI
18+
class RoutePreviewScreenFactory2(
19+
private val mapboxCarContext: MapboxCarContext
20+
) : MapboxScreenFactory {
21+
override fun create(carContext: CarContext): Screen {
22+
val repository = mapboxCarContext.carRoutePreviewRequest.repository
23+
val placeRecord = repository?.placeRecord?.value
24+
val routes = repository?.routes?.value ?: emptyList()
25+
return if (placeRecord == null || routes.isEmpty()) {
26+
logAndroidAuto(
27+
"Showing free drive screen because route preview can only be shown " +
28+
"when there is a route. placeRecord=$placeRecord routes.size=${routes.size}"
29+
)
30+
FreeDriveCarScreen(mapboxCarContext)
31+
} else {
32+
CarRoutePreviewScreen2(RoutePreviewCarContext(mapboxCarContext), placeRecord)
33+
}
34+
}
35+
}
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
package com.mapbox.androidauto.car.routes
2+
3+
import com.mapbox.navigation.base.ExperimentalPreviewMapboxNavigationAPI
4+
import com.mapbox.navigation.base.route.NavigationRoute
5+
import com.mapbox.navigation.core.MapboxNavigation
6+
import com.mapbox.navigation.core.directions.session.RoutesObserver
7+
import com.mapbox.navigation.core.lifecycle.MapboxNavigationApp
8+
import com.mapbox.navigation.core.lifecycle.MapboxNavigationObserver
9+
import com.mapbox.navigation.core.preview.RoutesPreview
10+
import com.mapbox.navigation.core.preview.RoutesPreviewObserver
11+
import kotlinx.coroutines.ExperimentalCoroutinesApi
12+
import kotlinx.coroutines.channels.awaitClose
13+
import kotlinx.coroutines.flow.Flow
14+
import kotlinx.coroutines.flow.MutableStateFlow
15+
import kotlinx.coroutines.flow.StateFlow
16+
import kotlinx.coroutines.flow.asStateFlow
17+
import kotlinx.coroutines.flow.callbackFlow
18+
19+
/**
20+
* A version of the [CarRoutesProvider] that uses the routes from [MapboxNavigation].
21+
*/
22+
@ExperimentalPreviewMapboxNavigationAPI
23+
@OptIn(ExperimentalCoroutinesApi::class)
24+
class NavigationCarRoutesProvider2 : CarRoutesProvider {
25+
26+
private val _routesPreview = MutableStateFlow<RoutesPreview?>(null)
27+
28+
val routesPreview: StateFlow<RoutesPreview?> = _routesPreview.asStateFlow()
29+
30+
/**
31+
* Observes the routes set to [MapboxNavigation].
32+
*/
33+
override val navigationRoutes: Flow<List<NavigationRoute>> = MapboxNavigationApp
34+
.flowNavigationRoutes()
35+
36+
@OptIn(ExperimentalPreviewMapboxNavigationAPI::class)
37+
private fun MapboxNavigationApp.flowNavigationRoutes(): Flow<List<NavigationRoute>> =
38+
callbackFlow {
39+
val routesObserver = RoutesPreviewObserver {
40+
val navigationRoutes: List<NavigationRoute> = it.routesPreview?.routesList
41+
?: emptyList()
42+
_routesPreview.value = it.routesPreview
43+
trySend(navigationRoutes)
44+
}
45+
val observer = object : MapboxNavigationObserver {
46+
override fun onAttached(mapboxNavigation: MapboxNavigation) {
47+
mapboxNavigation.registerRoutesPreviewObserver(routesObserver)
48+
}
49+
50+
override fun onDetached(mapboxNavigation: MapboxNavigation) {
51+
mapboxNavigation.unregisterRoutesPreviewObserver(routesObserver)
52+
trySend(emptyList())
53+
}
54+
}
55+
registerObserver(observer)
56+
awaitClose {
57+
unregisterObserver(observer)
58+
}
59+
}
60+
}

libnavui-androidauto/src/main/java/com/mapbox/androidauto/screenmanager/MapboxScreenGraph.kt

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
package com.mapbox.androidauto.screenmanager
44

55
import com.mapbox.androidauto.car.MapboxCarContext
6+
import com.mapbox.androidauto.car.preview.RoutePreviewScreenFactory2
67
import com.mapbox.androidauto.screenmanager.MapboxScreen.ACTIVE_GUIDANCE
78
import com.mapbox.androidauto.screenmanager.MapboxScreen.ACTIVE_GUIDANCE_FEEDBACK
89
import com.mapbox.androidauto.screenmanager.MapboxScreen.ARRIVAL
@@ -31,6 +32,7 @@ import com.mapbox.androidauto.screenmanager.factories.RoutePreviewScreenFactory
3132
import com.mapbox.androidauto.screenmanager.factories.SearchPlacesFeedbackScreenFactory
3233
import com.mapbox.androidauto.screenmanager.factories.SearchPlacesScreenFactory
3334
import com.mapbox.androidauto.screenmanager.factories.SettingsScreenFactory
35+
import com.mapbox.navigation.base.ExperimentalPreviewMapboxNavigationAPI
3436

3537
/**
3638
* This is a predefined application that is designed to collect feedback from drivers.
@@ -69,3 +71,8 @@ fun MapboxScreenManager.prepareScreens(mapboxCarContext: MapboxCarContext) = app
6971
to ArrivalScreenFactory(mapboxCarContext)
7072
)
7173
}
74+
75+
@ExperimentalPreviewMapboxNavigationAPI
76+
fun MapboxCarContext.prepareExperimentalScreens() = apply {
77+
mapboxScreenManager[ROUTE_PREVIEW] = RoutePreviewScreenFactory2(this)
78+
}

qa-test-app/src/main/java/com/mapbox/navigation/qa_test_app/car/MainCarSession.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import com.mapbox.androidauto.internal.logAndroidAuto
2121
import com.mapbox.androidauto.notification.CarNotificationInterceptor
2222
import com.mapbox.androidauto.screenmanager.MapboxScreen
2323
import com.mapbox.androidauto.screenmanager.MapboxScreenManager
24+
import com.mapbox.androidauto.screenmanager.prepareExperimentalScreens
2425
import com.mapbox.androidauto.screenmanager.prepareScreens
2526
import com.mapbox.maps.MapInitOptions
2627
import com.mapbox.maps.MapboxExperimental
@@ -71,6 +72,7 @@ class MainCarSession : Session() {
7172
this@MainCarSession.mapboxCarContext = it
7273
}
7374
mapboxCarContext.mapboxScreenManager.prepareScreens(mapboxCarContext)
75+
mapboxCarContext.prepareExperimentalScreens()
7476
mapboxNavigationManager = MapboxCarNavigationManager(carContext)
7577
MapboxNavigationApp.registerObserver(mapboxNavigationManager)
7678
MapboxNavigationApp.registerObserver(notificationInterceptor)

0 commit comments

Comments
 (0)