Skip to content

Commit 9f8ced2

Browse files
committed
Test new route preview
1 parent bfbadd0 commit 9f8ced2

File tree

5 files changed

+291
-0
lines changed

5 files changed

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

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)