Skip to content

Commit e12957d

Browse files
committed
Test new route preview
1 parent 7d2b3ed commit e12957d

File tree

5 files changed

+305
-1
lines changed

5 files changed

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

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.MapboxCarContext
6+
import com.mapbox.androidauto.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.
@@ -70,3 +72,8 @@ fun MapboxCarContext.prepareScreens() = apply {
7072
to ArrivalScreenFactory(mapboxCarContext)
7173
)
7274
}
75+
76+
@ExperimentalPreviewMapboxNavigationAPI
77+
fun MapboxCarContext.prepareExperimentalScreens() = apply {
78+
mapboxScreenManager[ROUTE_PREVIEW] = RoutePreviewScreenFactory2(this)
79+
}

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

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,25 +19,29 @@ import com.mapbox.androidauto.map.logo.CarLogoSurfaceRenderer
1919
import com.mapbox.androidauto.notification.MapboxCarNotificationOptions
2020
import com.mapbox.androidauto.screenmanager.MapboxScreen
2121
import com.mapbox.androidauto.screenmanager.MapboxScreenManager
22+
import com.mapbox.androidauto.screenmanager.prepareExperimentalScreens
2223
import com.mapbox.androidauto.screenmanager.prepareScreens
2324
import com.mapbox.maps.MapInitOptions
2425
import com.mapbox.maps.MapboxExperimental
2526
import com.mapbox.maps.extension.androidauto.MapboxCarMap
27+
import com.mapbox.navigation.base.ExperimentalPreviewMapboxNavigationAPI
2628
import com.mapbox.navigation.base.options.NavigationOptions
2729
import com.mapbox.navigation.core.lifecycle.MapboxNavigationApp
2830
import com.mapbox.navigation.core.trip.session.TripSessionState
2931
import com.mapbox.navigation.qa_test_app.utils.Utils
3032
import kotlinx.coroutines.CoroutineScope
3133
import kotlinx.coroutines.launch
3234

33-
@OptIn(MapboxExperimental::class)
35+
@OptIn(MapboxExperimental::class, ExperimentalPreviewMapboxNavigationAPI::class)
3436
class MainCarSession : Session() {
3537

3638
private val carMapLoader = MainCarMapLoader()
3739
private val mapboxCarMap = MapboxCarMap()
3840
.registerObserver(carMapLoader)
41+
3942
private val mapboxCarContext = MapboxCarContext(lifecycle, mapboxCarMap)
4043
.prepareScreens()
44+
.prepareExperimentalScreens()
4145
.customize {
4246
notificationOptions = MapboxCarNotificationOptions.Builder()
4347
.startAppService(MainCarAppService::class.java)

0 commit comments

Comments
 (0)