Stopover and Legs

This commit is contained in:
Dimitris
2026-03-31 17:06:55 +02:00
parent 60b842d883
commit 6838ad09c4
35 changed files with 542 additions and 280 deletions
@@ -181,7 +181,7 @@ fun Categories(
Button(onClick = { Button(onClick = {
val places = viewModel.loadRecentPlace(applicationContext) val places = viewModel.loadRecentPlace(applicationContext)
val toLocation = location(places.first()!!.longitude, places.first()!!.latitude) val toLocation = location(places.first()!!.longitude, places.first()!!.latitude)
viewModel.loadRoute(applicationContext, location, toLocation, 0F) viewModel.loadRoute(applicationContext, location, listOf(toLocation), 0F)
closeSheet() closeSheet()
}) { }) {
Icon( Icon(
@@ -248,7 +248,7 @@ private fun SearchPlaces(
viewModel.saveRecent(context, pl) viewModel.saveRecent(context, pl)
val toLocation = val toLocation =
location(place.lon.toDouble(), place.lat.toDouble()) location(place.lon.toDouble(), place.lat.toDouble())
viewModel.loadRoute(context, location, toLocation, 0F) viewModel.loadRoute(context, location, listOf(toLocation), 0F)
closeSheet() closeSheet()
} }
.fillMaxWidth() .fillMaxWidth()
@@ -118,7 +118,7 @@ fun Home(
Button(onClick = { Button(onClick = {
val places = viewModel.loadRecentPlace(applicationContext) val places = viewModel.loadRecentPlace(applicationContext)
val toLocation = location(places.first()!!.longitude, places.first()!!.latitude) val toLocation = location(places.first()!!.longitude, places.first()!!.latitude)
viewModel.loadRoute(applicationContext, location, toLocation, 0F) viewModel.loadRoute(applicationContext, location, listOf(toLocation), 0F)
closeSheet() closeSheet()
}) { }) {
Icon( Icon(
@@ -168,7 +168,7 @@ private fun RecentPlaces(
modifier = Modifier modifier = Modifier
.clickable { .clickable {
val toLocation = location(place.longitude, place.latitude) val toLocation = location(place.longitude, place.latitude)
viewModel.loadRoute(context, location, toLocation, 0F) viewModel.loadRoute(context, location, listOf(toLocation), 0F)
closeSheet() closeSheet()
} }
.fillMaxWidth() .fillMaxWidth()
@@ -5,7 +5,6 @@ import android.location.LocationManager
import androidx.car.app.navigation.model.Maneuver import androidx.car.app.navigation.model.Maneuver
import androidx.test.platform.app.InstrumentationRegistry import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.ext.junit.runners.AndroidJUnit4
import com.kouros.data.R
import com.kouros.navigation.data.Constants.homeHohenwaldeck import com.kouros.navigation.data.Constants.homeHohenwaldeck
import com.kouros.navigation.data.RouteEngine import com.kouros.navigation.data.RouteEngine
import com.kouros.navigation.data.tomtom.TomTomRepository import com.kouros.navigation.data.tomtom.TomTomRepository
@@ -182,6 +182,7 @@ class NavigationSession : Session(), NavigationListener, NavigationObserverCallb
init { init {
lifecycle.addObserver(lifecycleObserver) lifecycle.addObserver(lifecycleObserver)
repository.routingEngineFlow.asLiveData().observe(this, Observer { repository.routingEngineFlow.asLiveData().observe(this, Observer {
onRoutingEngineStateUpdated(it)
routingEngine = it routingEngine = it
}) })
@@ -199,13 +200,15 @@ class NavigationSession : Session(), NavigationListener, NavigationObserverCallb
* Creates appropriate repository based on user selection. * Creates appropriate repository based on user selection.
*/ */
fun onRoutingEngineStateUpdated(routeEngine: Int) { fun onRoutingEngineStateUpdated(routeEngine: Int) {
navigationViewModel = when (routeEngine) { if (!::navigationViewModel.isInitialized || routeEngine != routingEngine) {
RouteEngine.VALHALLA.ordinal -> NavigationViewModel(ValhallaRepository()) navigationViewModel = when (routeEngine) {
RouteEngine.OSRM.ordinal -> NavigationViewModel(OsrmRepository()) RouteEngine.VALHALLA.ordinal -> NavigationViewModel(ValhallaRepository())
else -> NavigationViewModel(TomTomRepository()) RouteEngine.OSRM.ordinal -> NavigationViewModel(OsrmRepository())
else -> NavigationViewModel(TomTomRepository())
}
observerManager = NavigationObserverManager(navigationViewModel, this)
observerManager.attachAllObservers(this)
} }
observerManager = NavigationObserverManager(navigationViewModel, this)
observerManager.attachAllObservers(this)
} }
/** /**
@@ -578,6 +581,8 @@ class NavigationSession : Session(), NavigationListener, NavigationObserverCallb
* Called when user starts navigation * Called when user starts navigation
*/ */
override fun startNavigation() { override fun startNavigation() {
Log.d(TAG, "startNavigation")
surfaceRenderer.navigation = true
surfaceRenderer.viewStyle = ViewStyle.VIEW surfaceRenderer.viewStyle = ViewStyle.VIEW
navigationManager.navigationStarted() navigationManager.navigationStarted()
navigationManagerStarted = true navigationManagerStarted = true
@@ -597,6 +602,8 @@ class NavigationSession : Session(), NavigationListener, NavigationObserverCallb
* Called when user exits navigation or arrives at destination. * Called when user exits navigation or arrives at destination.
*/ */
override fun stopNavigation() { override fun stopNavigation() {
Log.d(TAG, "stopNavigation")
surfaceRenderer.navigation = false
routeModel.stopNavigation() routeModel.stopNavigation()
navigationManager.navigationEnded() navigationManager.navigationEnded()
if (autoDriveEnabled) { if (autoDriveEnabled) {
@@ -609,6 +616,7 @@ class NavigationSession : Session(), NavigationListener, NavigationObserverCallb
navigationScreen.navigationType = NavigationType.VIEW navigationScreen.navigationType = NavigationType.VIEW
if (notificationActive) if (notificationActive)
notificationManager.stopNotificationService() notificationManager.stopNotificationService()
Log.d(TAG, "end stopNavigation")
} }
override fun updateTrip(trip: Trip) { override fun updateTrip(trip: Trip) {
@@ -723,9 +731,20 @@ class NavigationSession : Session(), NavigationListener, NavigationObserverCallb
* Loads a route to the specified place and sets it as the destination. * Loads a route to the specified place and sets it as the destination.
*/ */
override fun navigateToPlace(place: Place) { override fun navigateToPlace(place: Place) {
Log.d(TAG, "navigateToPlace ${place.street}")
var prevDestination = Place()
if (surfaceRenderer.navigation) {
prevDestination = routeModel.navState.destination
stopNavigation()
}
val preview = place.route val preview = place.route
navigationViewModel.previewRoute.value = "" navigationViewModel.previewRoute.value = ""
val location = location(place.longitude, place.latitude) val location = if (place.stopOver && prevDestination.latitude != 0.0) {
listOf(location(place.longitude, place.latitude), location(prevDestination.longitude, prevDestination.latitude))
} else {
listOf(location(place.longitude, place.latitude))
}
navigationViewModel.saveRecent(carContext, place) navigationViewModel.saveRecent(carContext, place)
routeModel.navState = routeModel.navState.copy(destination = place) routeModel.navState = routeModel.navState.copy(destination = place)
if (preview.isEmpty()) { if (preview.isEmpty()) {
@@ -813,7 +832,7 @@ class NavigationSession : Session(), NavigationListener, NavigationObserverCallb
navigationViewModel.loadRoute( navigationViewModel.loadRoute(
carContext, carContext,
location, location,
destination, listOf(destination),
surfaceRenderer.carOrientation surfaceRenderer.carOrientation
) )
} }
@@ -10,17 +10,12 @@ import androidx.car.app.AppManager
import androidx.car.app.CarContext import androidx.car.app.CarContext
import androidx.car.app.SurfaceCallback import androidx.car.app.SurfaceCallback
import androidx.car.app.SurfaceContainer import androidx.car.app.SurfaceContainer
import androidx.compose.animation.core.LinearOutSlowInEasing
import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.animation.core.tween
import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.livedata.observeAsState import androidx.compose.runtime.livedata.observeAsState
import androidx.compose.runtime.mutableFloatStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.platform.ComposeView import androidx.compose.ui.platform.ComposeView
import androidx.lifecycle.DefaultLifecycleObserver import androidx.lifecycle.DefaultLifecycleObserver
import androidx.lifecycle.Lifecycle import androidx.lifecycle.Lifecycle
@@ -54,7 +49,6 @@ import org.maplibre.compose.camera.CameraPosition
import org.maplibre.compose.camera.CameraState import org.maplibre.compose.camera.CameraState
import org.maplibre.compose.style.BaseStyle import org.maplibre.compose.style.BaseStyle
import org.maplibre.spatialk.geojson.Position import org.maplibre.spatialk.geojson.Position
import java.time.Duration
import java.time.LocalDateTime import java.time.LocalDateTime
@@ -123,6 +117,9 @@ class SurfaceRenderer(
// Current view mode (navigation, preview, etc.) // Current view mode (navigation, preview, etc.)
var viewStyle = ViewStyle.VIEW var viewStyle = ViewStyle.VIEW
// Flag to indicate if in navigation mode
var navigation = false
// Center location for route preview // Center location for route preview
lateinit var centerLocation: Location lateinit var centerLocation: Location
@@ -457,7 +454,7 @@ class SurfaceRenderer(
with(routeModel) { with(routeModel) {
routeData.value = curRoute.routeGeoJson routeData.value = curRoute.routeGeoJson
centerLocation = curRoute.centerLocation centerLocation = curRoute.centerLocation
previewDistance = curRoute.summary.distance previewDistance = curLeg.summary.distance
} }
tilt = 0.0 tilt = 0.0
updateCameraPosition( updateCameraPosition(
@@ -473,8 +470,10 @@ class SurfaceRenderer(
* Calculates appropriate zoom * Calculates appropriate zoom
*/ */
fun setStandardView() { fun setStandardView() {
if (!navigation) {
setRouteData("")
}
viewStyle = ViewStyle.VIEW viewStyle = ViewStyle.VIEW
setRouteData("")
val zoom = calculateZoom(0.0) val zoom = calculateZoom(0.0)
tilt = calculateTilt(zoom, tilt) tilt = calculateTilt(zoom, tilt)
updateCameraPosition( updateCameraPosition(
@@ -367,7 +367,7 @@ fun NavigationImage(
x = topLeftX , x = topLeftX ,
y = topLeftY, y = topLeftY,
), ),
color = if (darkMode) navigationColor else Color.White, color = if (darkMode) NavigationColorLight else Color.White,
cornerRadius = CornerRadius(x = 10f, y = 10f), cornerRadius = CornerRadius(x = 10f, y = 10f),
) )
drawText( drawText(
@@ -20,14 +20,13 @@ import androidx.car.app.model.ForegroundCarColorSpan
import androidx.car.app.navigation.model.Lane import androidx.car.app.navigation.model.Lane
import androidx.car.app.navigation.model.LaneDirection import androidx.car.app.navigation.model.LaneDirection
import androidx.car.app.navigation.model.Maneuver import androidx.car.app.navigation.model.Maneuver
import androidx.car.app.navigation.model.Maneuver.TYPE_ROUNDABOUT_ENTER_AND_EXIT_CCW
import androidx.car.app.navigation.model.Maneuver.TYPE_ROUNDABOUT_ENTER_AND_EXIT_CW
import androidx.car.app.navigation.model.Step import androidx.car.app.navigation.model.Step
import androidx.car.app.navigation.model.TravelEstimate import androidx.car.app.navigation.model.TravelEstimate
import androidx.core.graphics.drawable.IconCompat import androidx.core.graphics.drawable.IconCompat
import com.kouros.data.R import com.kouros.data.R
import com.kouros.navigation.car.screen.createCarIcon import com.kouros.navigation.car.screen.createCarIcon
import com.kouros.navigation.data.StepData import com.kouros.navigation.data.StepData
import com.kouros.navigation.data.route.ManeuverType
import com.kouros.navigation.model.RouteModel import com.kouros.navigation.model.RouteModel
import com.kouros.navigation.utils.formattedDistance import com.kouros.navigation.utils.formattedDistance
import java.time.Duration import java.time.Duration
@@ -47,17 +46,15 @@ class RouteCarModel : RouteModel() {
val maneuver = Maneuver.Builder(stepData.currentManeuverType) val maneuver = Maneuver.Builder(stepData.currentManeuverType)
.setIcon(createCarIcon(carContext, stepData.icon)) .setIcon(createCarIcon(carContext, stepData.icon))
if (stepData.currentManeuverType == TYPE_ROUNDABOUT_ENTER_AND_EXIT_CW if (stepData.currentManeuverType == ManeuverType.TYPE_ROUNDABOUT_ENTER_AND_EXIT_CW.ordinal
|| stepData.currentManeuverType == TYPE_ROUNDABOUT_ENTER_AND_EXIT_CCW || stepData.currentManeuverType == ManeuverType.TYPE_ROUNDABOUT_ENTER_AND_EXIT_CCW.ordinal
) { ) {
maneuver.setRoundaboutExitNumber(stepData.exitNumber) maneuver.setRoundaboutExitNumber(stepData.exitNumber)
} }
val step = val step =
Step.Builder(currentStepCueWithImage) Step.Builder(currentStepCueWithImage)
if (navState.destination.street != null) { step.setRoad(navState.destination.street!!)
step.setRoad(navState.destination.street!!)
}
if (stepData.lane.isNotEmpty()) { if (stepData.lane.isNotEmpty()) {
val lanesAdded = addLanes(carContext, step, stepData) val lanesAdded = addLanes(carContext, step, stepData)
if (lanesAdded) { if (lanesAdded) {
@@ -77,8 +74,8 @@ class RouteCarModel : RouteModel() {
createString(stepData.instruction) createString(stepData.instruction)
val maneuver = Maneuver.Builder(stepData.currentManeuverType) val maneuver = Maneuver.Builder(stepData.currentManeuverType)
.setIcon(createCarIcon(carContext, stepData.icon)) .setIcon(createCarIcon(carContext, stepData.icon))
if (stepData.currentManeuverType == TYPE_ROUNDABOUT_ENTER_AND_EXIT_CW if (stepData.currentManeuverType == ManeuverType.TYPE_ROUNDABOUT_ENTER_AND_EXIT_CW.ordinal
|| stepData.currentManeuverType == TYPE_ROUNDABOUT_ENTER_AND_EXIT_CCW || stepData.currentManeuverType == ManeuverType.TYPE_ROUNDABOUT_ENTER_AND_EXIT_CCW.ordinal
) { ) {
maneuver.setRoundaboutExitNumber(stepData.exitNumber) maneuver.setRoundaboutExitNumber(stepData.exitNumber)
} }
@@ -27,8 +27,8 @@ class Simulation {
) { ) {
if (routeModel.navState.route.isRouteValid()) { if (routeModel.navState.route.isRouteValid()) {
if (BuildConfig.DEBUG) { if (BuildConfig.DEBUG) {
gpxSimulation(routeModel, lifecycleScope, updateLocation) //gpxSimulation(routeModel, lifecycleScope, updateLocation)
//currentSimulation(routeModel, lifecycleScope, updateLocation) currentSimulation(routeModel, lifecycleScope, updateLocation)
} else { } else {
currentSimulation(routeModel, lifecycleScope, updateLocation) currentSimulation(routeModel, lifecycleScope, updateLocation)
} }
@@ -149,7 +149,7 @@ class CategoryScreen(
navigationViewModel.loadRoute( navigationViewModel.loadRoute(
carContext, carContext,
currentLocation = surfaceRenderer.lastLocation, currentLocation = surfaceRenderer.lastLocation,
location(it.lon, it.lat), listOf(location(it.lon, it.lat)),
surfaceRenderer.carOrientation surfaceRenderer.carOrientation
) )
setResult( setResult(
@@ -197,7 +197,7 @@ class CategoryScreen(
@DrawableRes iconRes: Int, @DrawableRes iconRes: Int,
scale: Int scale: Int
): Action { ): Action {
return createAction(carContext, iconRes, FLAG_IS_PERSISTENT, { return createAction(carContext, iconRes, FLAG_IS_PERSISTENT, {
surfaceRenderer.handleScale(scale) surfaceRenderer.handleScale(scale)
}) })
} }
@@ -117,18 +117,18 @@ open class NavigationScreen(
) )
}, { settingsAction() }) }, { settingsAction() })
return when (navigationType) { return when (navigationType) {
NavigationType.NAVIGATION -> navigationTemplate(actionStripBuilder) NavigationType.NAVIGATION -> navigation(actionStripBuilder)
NavigationType.RECENT -> navigationRecentPlacesTemplate() NavigationType.RECENT -> navigationRecentPlaces()
NavigationType.REROUTE -> navigationRerouteTemplate(actionStripBuilder) NavigationType.REROUTE -> navigationReroute(actionStripBuilder)
NavigationType.ARRIVAL -> navigationEndTemplate(actionStripBuilder) NavigationType.ARRIVAL -> navigationEnd(actionStripBuilder)
else -> navigationViewTemplate(actionStripBuilder) else -> navigationView(actionStripBuilder)
} }
} }
/** /**
* Creates and returns a NavigationTemplate for the active navigation state. * Creates and returns a NavigationTemplate for the active navigation state.
*/ */
private fun navigationTemplate(actionStripBuilder: ActionStrip.Builder): Template { private fun navigation(actionStripBuilder: ActionStrip.Builder): Template {
actionStripBuilder.addAction( actionStripBuilder.addAction(
createAction( createAction(
carContext, carContext,
@@ -150,7 +150,7 @@ open class NavigationScreen(
carContext = carContext, R.drawable.ic_pan_24, carContext = carContext, R.drawable.ic_pan_24,
0, 0,
onClickAction = { onClickAction = {
surfaceRenderer.viewStyle = ViewStyle.VIEW surfaceRenderer.setStandardView()
invalidate() invalidate()
} }
) )
@@ -163,7 +163,7 @@ open class NavigationScreen(
/** /**
* Creates and returns a template for the default view state. * Creates and returns a template for the default view state.
*/ */
private fun navigationViewTemplate(actionStripBuilder: ActionStrip.Builder): Template { private fun navigationView(actionStripBuilder: ActionStrip.Builder): Template {
return NavigationTemplate.Builder() return NavigationTemplate.Builder()
.setBackgroundColor(backGroundColor) .setBackgroundColor(backGroundColor)
.setActionStrip(actionStripBuilder.build()) .setActionStrip(actionStripBuilder.build())
@@ -187,24 +187,24 @@ open class NavigationScreen(
/** /**
* Creates and returns a template for the arrival. * Creates and returns a template for the arrival.
*/ */
private fun navigationEndTemplate(actionStripBuilder: ActionStrip.Builder): Template { private fun navigationEnd(actionStripBuilder: ActionStrip.Builder): Template {
arrivalTimer?.cancel() arrivalTimer?.cancel()
arrivalTimer = object : CountDownTimer(8000, 1000) { arrivalTimer = object : CountDownTimer(8000, 1000) {
override fun onTick(millisUntilFinished: Long) {} override fun onTick(millisUntilFinished: Long) {}
override fun onFinish() { override fun onFinish() {
// routeModel.navState = routeModel.navState.copy(arrived = false) // routeModel.navState = routeModel.navState.copy(arrived = false)
navigationType = NavigationType.VIEW navigationType = NavigationType.VIEW
invalidate() invalidate()
} }
} }
arrivalTimer?.start() arrivalTimer?.start()
return navigationArrivedTemplate(actionStripBuilder) return navigationArrived(actionStripBuilder)
} }
/** /**
* Creates and returns a NavigationTemplate specifically for when the destination is reached. * Creates and returns a NavigationTemplate specifically for when the destination is reached.
*/ */
fun navigationArrivedTemplate(actionStripBuilder: ActionStrip.Builder): NavigationTemplate { fun navigationArrived(actionStripBuilder: ActionStrip.Builder): NavigationTemplate {
var street = "" var street = ""
if (destinations.first().address != null) { if (destinations.first().address != null) {
street = destinations.first().address.toString() street = destinations.first().address.toString()
@@ -226,7 +226,7 @@ open class NavigationScreen(
) )
.build() .build()
) )
// .setBackgroundColor(routeModel.backGroundColor()) .setBackgroundColor(backGroundColor)
.setActionStrip(actionStripBuilder.build()) .setActionStrip(actionStripBuilder.build())
.setMapActionStrip( .setMapActionStrip(
mapActionStrip( mapActionStrip(
@@ -247,10 +247,10 @@ open class NavigationScreen(
/** /**
* Creates and returns a template showing recent places or destinations. * Creates and returns a template showing recent places or destinations.
*/ */
fun navigationRecentPlacesTemplate(): Template { fun navigationRecentPlaces(): Template {
if (!tripSuggestion || recentPlaces.isEmpty()) { if (!tripSuggestion || recentPlaces.isEmpty()) {
navigationType = NavigationType.VIEW navigationType = NavigationType.VIEW
return navigationViewTemplate( return navigationView(
createActionStripBuilder( createActionStripBuilder(
{ {
createAction( createAction(
@@ -263,7 +263,7 @@ open class NavigationScreen(
) )
} }
val listBuilder = ItemList.Builder() val listBuilder = ItemList.Builder()
recentPlaces.filter { it.category == Constants.RECENT && it.distance > 300F }.forEach { recentPlaces.filter { it.category == Constants.RECENT && it.distance > 300F }.forEach {
val row = Row.Builder() val row = Row.Builder()
.setTitle(it.name) .setTitle(it.name)
.addAction( .addAction(
@@ -316,7 +316,7 @@ open class NavigationScreen(
/** /**
* Creates and returns a template for when the route is being recalculated. * Creates and returns a template for when the route is being recalculated.
*/ */
fun navigationRerouteTemplate(actionStripBuilder: ActionStrip.Builder): Template { fun navigationReroute(actionStripBuilder: ActionStrip.Builder): Template {
return NavigationTemplate.Builder() return NavigationTemplate.Builder()
.setNavigationInfo(RoutingInfo.Builder().setLoading(true).build()) .setNavigationInfo(RoutingInfo.Builder().setLoading(true).build())
.setActionStrip(actionStripBuilder.build()) .setActionStrip(actionStripBuilder.build())
@@ -486,7 +486,7 @@ open class NavigationScreen(
navigationViewModel.loadRoute( navigationViewModel.loadRoute(
carContext, carContext,
surfaceRenderer.lastLocation, surfaceRenderer.lastLocation,
destination, listOf(destination),
surfaceRenderer.carOrientation surfaceRenderer.carOrientation
) )
} }
@@ -5,7 +5,6 @@ import android.text.Spannable
import android.text.SpannableString import android.text.SpannableString
import androidx.car.app.CarContext import androidx.car.app.CarContext
import androidx.car.app.CarToast import androidx.car.app.CarToast
import androidx.car.app.OnScreenResultListener
import androidx.car.app.Screen import androidx.car.app.Screen
import androidx.car.app.model.Action import androidx.car.app.model.Action
import androidx.car.app.model.CarIcon import androidx.car.app.model.CarIcon
@@ -28,7 +27,6 @@ import com.kouros.navigation.data.Constants.CONTACTS
import com.kouros.navigation.data.Constants.FAVORITES import com.kouros.navigation.data.Constants.FAVORITES
import com.kouros.navigation.data.Constants.RECENT import com.kouros.navigation.data.Constants.RECENT
import com.kouros.navigation.data.Place import com.kouros.navigation.data.Place
import com.kouros.navigation.data.ViewStyle
import com.kouros.navigation.model.NavigationViewModel import com.kouros.navigation.model.NavigationViewModel
import com.kouros.navigation.utils.getSettingsRepository import com.kouros.navigation.utils.getSettingsRepository
@@ -66,41 +64,12 @@ class PlaceListScreen(
val itemListBuilder = ItemList.Builder() val itemListBuilder = ItemList.Builder()
.setNoItemsMessage(carContext.getString(R.string.no_places)) .setNoItemsMessage(carContext.getString(R.string.no_places))
recentPlaces.filter { it.category == category }.forEach { recentPlaces.filter { it.category == category }.forEach {
val street = if (it.street != null) { val street = it.street
it.street
} else {
""
}
val row = Row.Builder() val row = Row.Builder()
.setImage(contactIcon(null, it.category)) .setImage(contactIcon(null, it.category))
.setTitle("$street ${it.city}") .setTitle("$street ${it.city}")
.setOnClickListener { .setOnClickListener {
place = Place( clickOnPlace(it)
0,
it.name,
it.category,
it.latitude,
it.longitude,
it.postalCode,
it.city,
it.street,
// avatar = null
)
screenManager
.pushForResult(
RoutePreviewScreen(
carContext,
RoutePreviewType.MULTI_ROUTE,
surfaceRenderer,
place,
navigationViewModel,
)
) { obj: Any? ->
if (obj != null) {
setResult(obj)
finish()
}
}
} }
if (category != CONTACTS) { if (category != CONTACTS) {
row.addText(SpannableString(" ").apply { row.addText(SpannableString(" ").apply {
@@ -138,6 +107,69 @@ class PlaceListScreen(
.build() .build()
} }
/**
* Creates an Action to navigate to a specific place.
*/
private fun clickOnPlace(it: Place) {
place = Place(
0,
it.name,
it.category,
it.latitude,
it.longitude,
it.postalCode,
it.city,
it.street,
// avatar = null
)
if (surfaceRenderer.navigation) {
startStopOverScreen(place)
} else {
starPreviewScreen(place)
}
}
/**
* Starts preview screen for a specific place.
*/
private fun starPreviewScreen(place: Place) {
screenManager
.pushForResult(
RoutePreviewScreen(
carContext,
RoutePreviewType.MULTI_ROUTE,
surfaceRenderer,
place,
navigationViewModel,
)
) { obj: Any? ->
if (obj != null) {
setResult(obj)
finish()
}
}
}
/**
* Starts preview screen for a specific place.
*/
private fun startStopOverScreen(place: Place) {
screenManager
.pushForResult(
StopOverScreen(
carContext,
surfaceRenderer,
navigationViewModel,
place,
)
) { obj: Any? ->
if (obj != null) {
setResult(obj)
finish()
}
}
}
/** /**
* Creates an Action to delete a place. * Creates an Action to delete a place.
*/ */
@@ -71,6 +71,8 @@ class RoutePreviewScreen(
var loading = true var loading = true
var showAlternativeRoute = true
private val backPressedCallback = object : OnBackPressedCallback(false) { private val backPressedCallback = object : OnBackPressedCallback(false) {
override fun handleOnBackPressed() { override fun handleOnBackPressed() {
} }
@@ -82,6 +84,10 @@ class RoutePreviewScreen(
routeModel.startNavigation(route) routeModel.startNavigation(route)
surfaceRenderer.setPreviewRouteData(routeModel) surfaceRenderer.setPreviewRouteData(routeModel)
loading = false loading = false
if (routeModel.route.routes.size == 1) {
routeType = RoutePreviewType.SINGLE_ROUTE
showAlternativeRoute = false
}
invalidate() invalidate()
} }
} }
@@ -139,15 +145,12 @@ class RoutePreviewScreen(
} }
} }
val street = if (destination.street.isEmpty()) { val street = destination.street.ifEmpty {
carContext.getString((R.string.route_preview)) carContext.getString((R.string.route_preview))
} else {
destination.street
} }
val header = Header.Builder() val header = Header.Builder()
.setStartHeaderAction(Action.BACK) .setStartHeaderAction(Action.BACK)
.setTitle(street) .setTitle(street)
if (routeType == RoutePreviewType.SINGLE_ROUTE) { if (routeType == RoutePreviewType.SINGLE_ROUTE) {
header.addEndHeaderAction( header.addEndHeaderAction(
favoriteAction() favoriteAction()
@@ -174,16 +177,6 @@ class RoutePreviewScreen(
.build() .build()
listContent.build() listContent.build()
} else { } else {
val navigateActionIcon: CarIcon = CarIcon.Builder(
IconCompat.createWithResource(
carContext, R.drawable.navigation_48px
)
).build()
val selectRouteIcon: CarIcon = CarIcon.Builder(
IconCompat.createWithResource(
carContext, R.drawable.alt_route_48px
)
).build()
val navigateAction = val navigateAction =
createAction(carContext, R.drawable.navigation_48px, FLAG_DEFAULT,{ createAction(carContext, R.drawable.navigation_48px, FLAG_DEFAULT,{
onNavigate(routeModel.navState.currentRouteIndex) onNavigate(routeModel.navState.currentRouteIndex)
@@ -195,7 +188,10 @@ class RoutePreviewScreen(
val listContent = MessageTemplate.Builder(message) val listContent = MessageTemplate.Builder(message)
.setHeader(header.build()) .setHeader(header.build())
.addAction(navigateAction) .addAction(navigateAction)
.addAction(selectRouteAction)
if (showAlternativeRoute) {
listContent.addAction(selectRouteAction)
}
if (loading) { if (loading) {
listContent.setLoading(true) listContent.setLoading(true)
} }
@@ -211,7 +207,7 @@ class RoutePreviewScreen(
} )).build() } )).build()
) )
if (routeType == RoutePreviewType.MULTI_ROUTE && !routeSelected) { if (routeModel.route.routes.size > 1 && routeType == RoutePreviewType.MULTI_ROUTE && !routeSelected) {
template.setActionStrip(createActionStrip { template.setActionStrip(createActionStrip {
createAction( createAction(
carContext, R.drawable.navigation_48px, carContext, R.drawable.navigation_48px,
@@ -301,7 +297,7 @@ class RoutePreviewScreen(
} }
private fun createRow(route: Routes, index: Int): Row { private fun createRow(route: Routes, index: Int): Row {
val navigateAction = createAction(carContext, R.drawable.navigation_48px ) { val navigateAction = createAction(carContext, R.drawable.navigation_48px, FLAG_DEFAULT ) {
this.onNavigate(index) this.onNavigate(index)
} }
val routeText = createRouteText(route) val routeText = createRouteText(route)
@@ -0,0 +1,68 @@
package com.kouros.navigation.car.screen
import androidx.car.app.CarContext
import androidx.car.app.Screen
import androidx.car.app.model.Action
import androidx.car.app.model.Action.FLAG_DEFAULT
import androidx.car.app.model.Action.FLAG_IS_PERSISTENT
import androidx.car.app.model.CarText
import androidx.car.app.model.Header
import androidx.car.app.model.MessageTemplate
import androidx.car.app.model.Template
import androidx.car.app.navigation.model.MapController
import androidx.car.app.navigation.model.MapWithContentTemplate
import com.kouros.data.R
import com.kouros.navigation.car.SurfaceRenderer
import com.kouros.navigation.data.Place
import com.kouros.navigation.data.ViewStyle
import com.kouros.navigation.model.NavigationViewModel
class StopOverScreen(
private val carContext: CarContext,
private val surfaceRenderer: SurfaceRenderer,
private val navigationViewModel: NavigationViewModel,
private val place: Place,
) : Screen(carContext) {
override fun onGetTemplate(): MapWithContentTemplate {
val cancelAction =
createAction(carContext, R.drawable.ic_close_white_24dp, FLAG_IS_PERSISTENT,{
finish()
})
val header = Header.Builder()
.setStartHeaderAction(Action.BACK)
.addEndHeaderAction(cancelAction)
.setTitle(place.street)
val message = CarText.Builder("Neue Fahrt oder Zwischenstopp einfügen")
.build()
val navigateAction = Action.Builder()
.setIcon(createCarIcon(carContext, R.drawable.navigation_48px))
.setFlags(FLAG_DEFAULT)
.setOnClickListener {
setResult(place)
finish()
}
.build()
val selectRouteAction = Action.Builder()
.setIcon(createCarIcon(carContext, R.drawable.alt_route_48px))
.setFlags(FLAG_IS_PERSISTENT)
.setOnClickListener {
place.stopOver = true
setResult(place)
finish()
}
.build()
val listContent = MessageTemplate.Builder(message)
.setHeader(header.build())
.addAction(navigateAction)
.addAction(selectRouteAction)
val template = MapWithContentTemplate.Builder()
.setContentTemplate(listContent.build())
return template.build()
}
}
@@ -33,6 +33,8 @@ class NavigationSettings(
private var carLocationToggleState = false private var carLocationToggleState = false
private var alternativeRoutesToggleState = false
val settingsViewModel = getSettingsViewModel(carContext) val settingsViewModel = getSettingsViewModel(carContext)
init { init {
@@ -41,6 +43,7 @@ class NavigationSettings(
settingsViewModel.avoidMotorway.first() settingsViewModel.avoidMotorway.first()
settingsViewModel.avoidFerry.first() settingsViewModel.avoidFerry.first()
settingsViewModel.carLocation.first() settingsViewModel.carLocation.first()
settingsViewModel.alternativeRoutes.first()
} }
} }
@@ -49,6 +52,7 @@ class NavigationSettings(
tollWayToggleState = settingsViewModel.avoidTollway.value tollWayToggleState = settingsViewModel.avoidTollway.value
ferryToggleState = settingsViewModel.avoidFerry.value ferryToggleState = settingsViewModel.avoidFerry.value
carLocationToggleState = settingsViewModel.carLocation.value carLocationToggleState = settingsViewModel.carLocation.value
alternativeRoutesToggleState = settingsViewModel.alternativeRoutes.value
val listBuilder = ItemList.Builder() val listBuilder = ItemList.Builder()
@@ -89,6 +93,14 @@ class NavigationSettings(
carLocationToggleState = !carLocationToggleState carLocationToggleState = !carLocationToggleState
}.setChecked(carLocationToggleState).build() }.setChecked(carLocationToggleState).build()
// Alternative routes
val alternativeRoutesToggle: Toggle =
Toggle.Builder { checked: Boolean ->
settingsViewModel.onAlternativeRoutes(checked)
alternativeRoutesToggleState = !alternativeRoutesToggleState
}.setChecked(alternativeRoutesToggleState).build()
listBuilder.addItem( listBuilder.addItem(
buildRowForTemplate( buildRowForTemplate(
R.string.use_car_location, R.string.use_car_location,
@@ -97,6 +109,14 @@ class NavigationSettings(
) )
) )
listBuilder.addItem(
buildRowForTemplate(
R.string.alternative_routes,
alternativeRoutesToggle,
createCarIcon(carContext,R.drawable.alt_route_48px)
)
)
listBuilder.addItem( listBuilder.addItem(
buildRowForScreenTemplate( buildRowForScreenTemplate(
RoutingSettings(carContext, navigationViewModel), RoutingSettings(carContext, navigationViewModel),
@@ -109,6 +129,7 @@ class NavigationSettings(
R.string.tomtom_api_key R.string.tomtom_api_key
) )
) )
return ListTemplate.Builder() return ListTemplate.Builder()
.setSingleList(listBuilder.build()) .setSingleList(listBuilder.build())
.setHeader( .setHeader(
@@ -2,11 +2,11 @@ package com.kouros.navigation.data
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
val NavigationColorLight = Color(0xFF066462) val NavigationColorLight = Color(0xFF17A119)
val NavigationColorDark = Color(0xFF10DED9) val NavigationColorDark = Color(0xFF4EDE10)
val RouteColor = Color(0xFF7B06E1) val RouteColor = Color(0xFF05692D)
val SpeedColor = Color(0xFF262525) val SpeedColor = Color(0xFF262525)
@@ -46,6 +46,7 @@ data class Place(
var lastDate: Long = 0, var lastDate: Long = 0,
var routeIndex: Int = 0, var routeIndex: Int = 0,
var route: String = "", var route: String = "",
var stopOver: Boolean = false,
) )
data class ContactData( data class ContactData(
@@ -20,7 +20,7 @@ abstract class NavigationRepository {
abstract fun getRoute( abstract fun getRoute(
context: Context, context: Context,
currentLocation: Location, currentLocation: Location,
destination: Location, location: List<Location>,
carOrientation: Float, carOrientation: Float,
searchFilter: SearchFilter searchFilter: SearchFilter
): String ): String
@@ -58,6 +58,8 @@ class DataStoreManager(private val context: Context) {
val ENGINE_TYPE = intPreferencesKey("EngineType") val ENGINE_TYPE = intPreferencesKey("EngineType")
val ALTERNATIVE_ROUTES = booleanPreferencesKey("AlternativeRoutes")
} }
// Read values // Read values
@@ -145,6 +147,11 @@ class DataStoreManager(private val context: Context) {
?: EngineType.COMBUSTION.ordinal ?: EngineType.COMBUSTION.ordinal
} }
val alternativeRoutesFlow: Flow<Boolean> =
context.dataStore.data.map { preferences ->
preferences[PreferencesKeys.ALTERNATIVE_ROUTES] == true
}
// Save values // Save values
suspend fun setShow3D(enabled: Boolean) { suspend fun setShow3D(enabled: Boolean) {
context.dataStore.edit { preferences -> context.dataStore.edit { preferences ->
@@ -235,4 +242,11 @@ class DataStoreManager(private val context: Context) {
prefs[PreferencesKeys.ENGINE_TYPE] = mode prefs[PreferencesKeys.ENGINE_TYPE] = mode
} }
} }
suspend fun setAlternativeRoutes(enabled: Boolean) {
context.dataStore.edit { preferences ->
preferences[PreferencesKeys.ALTERNATIVE_ROUTES] = enabled
}
}
} }
@@ -13,7 +13,7 @@ class OsrmRepository : NavigationRepository() {
override fun getRoute( override fun getRoute(
context: Context, context: Context,
currentLocation: Location, currentLocation: Location,
location: Location, location: List<Location>,
carOrientation: Float, carOrientation: Float,
searchFilter: SearchFilter searchFilter: SearchFilter
): String { ): String {
@@ -28,7 +28,7 @@ class OsrmRepository : NavigationRepository() {
if (searchFilter.avoidFerry) { if (searchFilter.avoidFerry) {
exclude = "$exclude&exclude=ferry" exclude = "$exclude&exclude=ferry"
} }
val routeLocation = "${currentLocation.longitude},${currentLocation.latitude};${location.longitude},${location.latitude}?steps=true&alternatives=false" val routeLocation = "${currentLocation.longitude},${currentLocation.latitude};${location.first().longitude},${location.first().latitude}?steps=true&alternatives=false"
return fetchUrl(routeUrl + routeLocation + exclude, true) return fetchUrl(routeUrl + routeLocation + exclude, true)
} }
@@ -69,7 +69,7 @@ class OsrmRoute {
steps.add(step) steps.add(step)
stepIndex += 1 stepIndex += 1
} }
legs.add(Leg(steps)) legs.add(Leg(steps, summary))
} }
val routeGeoJson = createLineStringCollection(waypoints) val routeGeoJson = createLineStringCollection(waypoints)
val centerLocation = createCenterLocation(createLineStringCollection(waypoints)) val centerLocation = createCenterLocation(createLineStringCollection(waypoints))
@@ -1,5 +1,9 @@
package com.kouros.navigation.data.route package com.kouros.navigation.data.route
import android.location.Location
import com.kouros.navigation.utils.location
data class Leg( data class Leg(
var steps : List<Step> = arrayListOf(), var steps : List<Step> = arrayListOf(),
val summary: Summary,
) )
@@ -13,3 +13,57 @@ data class Maneuver(
val message: String = "", val message: String = "",
val pointIndex : Int = 0, val pointIndex : Int = 0,
) )
enum class ManeuverType {
TYPE_UNKNOWN,
TYPE_DEPART,
TYPE_NAME_CHANGE,
TYPE_KEEP_LEFT,
TYPE_KEEP_RIGHT,
TYPE_TURN_SLIGHT_LEFT,
TYPE_TURN_SLIGHT_RIGHT,
TYPE_TURN_NORMAL_LEFT,
TYPE_TURN_NORMAL_RIGHT,
TYPE_TURN_SHARP_LEFT,
TYPE_TURN_SHARP_RIGHT,
TYPE_U_TURN_LEFT,
TYPE_U_TURN_RIGHT,
TYPE_ON_RAMP_SLIGHT_LEFT,
TYPE_ON_RAMP_SLIGHT_RIGHT,
TYPE_ON_RAMP_NORMAL_LEFT,
TYPE_ON_RAMP_NORMAL_RIGHT,
TYPE_ON_RAMP_SHARP_LEFT,
TYPE_ON_RAMP_SHARP_RIGHT,
TYPE_ON_RAMP_U_TURN_LEFT,
TYPE_ON_RAMP_U_TURN_RIGHT,
TYPE_OFF_RAMP_SLIGHT_LEFT,
TYPE_OFF_RAMP_SLIGHT_RIGHT,
TYPE_OFF_RAMP_NORMAL_LEFT,
TYPE_OFF_RAMP_NORMAL_RIGHT,
TYPE_FORK_LEFT,
TYPE_FORK_RIGHT,
TYPE_MERGE_LEFT,
TYPE_MERGE_RIGHT,
TYPE_MERGE_SIDE_UNSPECIFIED,
TYPE_ROUNDABOUT_ENTER_AND_EXIT_CW,
TYPE_ROUNDABOUT_ENTER_AND_EXIT_CW_WITH_ANGLE,
TYPE_ROUNDABOUT_ENTER_AND_EXIT_CCW,
TYPE_ROUNDABOUT_ENTER_AND_EXIT_CCW_WITH_ANGLE,
TYPE_STRAIGHT,
TYPE_FERRY_BOAT,
TYPE_FERRY_TRAIN,
TYPE_DESTINATION,
TYPE_DESTINATION_STRAIGHT,
TYPE_DESTINATION_LEFT,
TYPE_DESTINATION_RIGHT,
TYPE_ROUNDABOUT_ENTER_CW,
TYPE_ROUNDABOUT_EXIT_CW,
TYPE_ROUNDABOUT_ENTER_CCW,
TYPE_ROUNDABOUT_EXIT_CCW,
TYPE_FERRY_BOAT_LEFT,
TYPE_FERRY_BOAT_RIGHT,
TYPE_FERRY_TRAIN_LEFT,
TYPE_FERRY_TRAIN_RIGHT,
TYPE_WAYPOINT_RIGHT,
TYPE_WAYPOINT_LEFT
}
@@ -23,14 +23,14 @@ private const val tomtomFields =
val useLocal = BuildConfig.DEBUG val useLocal = BuildConfig.DEBUG
val useLocalTraffic = BuildConfig.DEBUG val useLocalTraffic = BuildConfig.DEBUG
class TomTomRepository : NavigationRepository() { class TomTomRepository : NavigationRepository() {
override fun getRoute( override fun getRoute(
context: Context, context: Context,
currentLocation: Location, currentLocation: Location,
location: Location, location: List<Location>,
carOrientation: Float, carOrientation: Float,
searchFilter: SearchFilter searchFilter: SearchFilter
): String { ): String {
@@ -55,17 +55,34 @@ class TomTomRepository : NavigationRepository() {
engineType = "electric" engineType = "electric"
} }
val repository = getSettingsRepository(context) val repository = getSettingsRepository(context)
val tomtomApiKey = runBlocking { repository.tomTomApiKeyFlow.first() } val tomtomApiKey = runBlocking {
repository.tomTomApiKeyFlow.first()
}
val alternativeRoutes = runBlocking {
repository.alternativeRoutesFlow.first()
}
val altRoutes = if (alternativeRoutes) {
"&maxAlternatives=2"
} else {
"&maxAlternatives=0"
}
val currentLocale = Locale.getDefault() val currentLocale = Locale.getDefault()
val language = currentLocale.language + "-" + currentLocale.country val language = currentLocale.language + "-" + currentLocale.country
var loc = ""
location.forEach {
loc += if (loc.isEmpty()) {
"${it.latitude},${it.longitude}"
} else {
":${it.latitude},${it.longitude}"
}
}
val url = val url =
routeUrl + "${currentLocation.latitude},${currentLocation.longitude}:${location.latitude},${location.longitude}" + routeUrl + "${currentLocation.latitude},${currentLocation.longitude}:$loc" +
"/json?sectionType=traffic&report=effectiveSettings&routeType=eco" + "/json?sectionType=traffic&report=effectiveSettings&routeType=eco" +
"&traffic=true&avoid=unpavedRoads&travelMode=car" + "&traffic=true&avoid=unpavedRoads&travelMode=car" +
"&vehicleMaxSpeed=120&vehicleCommercial=false" + "&vehicleMaxSpeed=120&vehicleCommercial=false" +
"&instructionsType=text&language=$language&sectionType=lanes" + "&instructionsType=text&language=$language&sectionType=lanes" +
"&routeRepresentation=encodedPolyline" + "&routeRepresentation=encodedPolyline$altRoutes" +
"&maxAlternatives=2" +
"&vehicleEngineType=$engineType$filter&key=$tomtomApiKey" "&vehicleEngineType=$engineType$filter&key=$tomtomApiKey"
return fetchUrl( return fetchUrl(
url, url,
@@ -1,10 +1,13 @@
package com.kouros.navigation.data.tomtom package com.kouros.navigation.data.tomtom
import com.kouros.navigation.data.Route import com.kouros.navigation.data.Route
import com.kouros.navigation.data.RouteEngine import com.kouros.navigation.data.RouteEngine
import com.kouros.navigation.data.route.Intersection import com.kouros.navigation.data.route.Intersection
import com.kouros.navigation.data.route.Lane import com.kouros.navigation.data.route.Lane
import com.kouros.navigation.data.route.Leg import com.kouros.navigation.data.route.Leg
import com.kouros.navigation.data.route.ManeuverType
import com.kouros.navigation.data.route.Routes
import com.kouros.navigation.data.route.Step import com.kouros.navigation.data.route.Step
import com.kouros.navigation.data.route.Summary import com.kouros.navigation.data.route.Summary
import com.kouros.navigation.utils.GeoUtils.createCenterLocation import com.kouros.navigation.utils.GeoUtils.createCenterLocation
@@ -17,12 +20,12 @@ import com.kouros.navigation.data.route.Maneuver as RouteManeuver
class TomTomRoute { class TomTomRoute {
fun mapToRoute(routeJson: TomTomResponse, builder: Route.Builder) { fun mapToRoute(routeJson: TomTomResponse, builder: Route.Builder) {
val routes = mutableListOf<com.kouros.navigation.data.route.Routes>() val routes = mutableListOf<Routes>()
routeJson.routes.forEach { route -> routeJson.routes.forEach { route ->
val waypoints = mutableListOf<List<Double>>() val waypoints = mutableListOf<List<Double>>()
val points = mutableListOf<List<Double>>()
val legs = mutableListOf<Leg>() val legs = mutableListOf<Leg>()
var stepIndex = 0 var stepIndex = 0
var points = listOf<List<Double>>()
val summary = Summary( val summary = Summary(
route.summary.travelTimeInSeconds.toDouble(), route.summary.travelTimeInSeconds.toDouble(),
route.summary.lengthInMeters.toDouble(), route.summary.lengthInMeters.toDouble(),
@@ -30,89 +33,98 @@ class TomTomRoute {
route.summary.trafficLengthInMeters.toDouble() route.summary.trafficLengthInMeters.toDouble()
) )
route.legs.forEach { leg -> route.legs.forEach { leg ->
points = decodePolyline(leg.encodedPolyline, leg.encodedPolylinePrecision) val p = decodePolyline(leg.encodedPolyline, leg.encodedPolylinePrecision)
waypoints.addAll(points) points.addAll(p)
waypoints.addAll(p)
} }
var stepDistance = 0.0 route.legs.forEach { leg ->
var stepDuration = 0.0 var stepDistance = 0.0
val steps = mutableListOf<Step>() var stepDuration = 0.0
var lastPointIndex = 0 val steps = mutableListOf<Step>()
for (index in 1..<route.guidance.instructions.size) { val summary = Summary(
val lastInstruction = route.guidance.instructions[index - 1] leg.summary.travelTimeInSeconds.toDouble(),
val instruction = route.guidance.instructions[index] leg.summary.lengthInMeters.toDouble(),
val street = lastInstruction.street ?: "" leg.summary.trafficDelayInSeconds.toDouble(),
val maneuverStreet = instruction.street ?: "" leg.summary.trafficLengthInMeters.toDouble()
val maneuver = RouteManeuver(
bearingBefore = 0,
bearingAfter = 0,
type = convertType(instruction.maneuver),
waypoints = points.subList(
lastPointIndex,
instruction.pointIndex + 1,
),
exit = exitNumber(instruction),
location = location(
instruction.point.longitude, instruction.point.latitude
),
street = maneuverStreet,
message = instruction.message,
pointIndex = instruction.pointIndex
) )
var lastPointIndex = 0
for (index in 1..<route.guidance.instructions.size) {
val lastInstruction = route.guidance.instructions[index - 1]
val instruction = route.guidance.instructions[index]
val street = lastInstruction.street ?: ""
val maneuverStreet = instruction.street ?: ""
val maneuver = RouteManeuver(
bearingBefore = 0,
bearingAfter = 0,
type = convertType(instruction.maneuver),
waypoints = points.subList(
lastPointIndex,
instruction.pointIndex + 1,
),
exit = exitNumber(instruction),
location = location(
instruction.point.longitude, instruction.point.latitude
),
street = maneuverStreet,
message = instruction.message,
pointIndex = instruction.pointIndex
)
lastPointIndex = instruction.pointIndex lastPointIndex = instruction.pointIndex
val intersections = mutableListOf<Intersection>() val intersections = mutableListOf<Intersection>()
route.sections?.forEach { section -> route.sections?.forEach { section ->
if (section.sectionType == "LANES" && section.startPointIndex <= lastPointIndex if (section.sectionType == "LANES" && section.startPointIndex <= lastPointIndex
&& section.endPointIndex >= lastPointIndex && section.endPointIndex >= lastPointIndex
) { ) {
val lanes = mutableListOf<Lane>() val lanes = mutableListOf<Lane>()
var startIndex = 0 var startIndex = 0
var lastLane: Lane? = null var lastLane: Lane? = null
section.lanes?.forEach { itLane -> section.lanes?.forEach { itLane ->
val lane = Lane( val lane = Lane(
location = location( location = location(
waypoints[section.startPointIndex][0], waypoints[section.startPointIndex][0],
waypoints[section.startPointIndex][1] waypoints[section.startPointIndex][1]
), ),
valid = itLane.directions.first() == itLane.follow, valid = itLane.directions.first() == itLane.follow,
indications = itLane.directions, indications = itLane.directions,
startIndex = startIndex, startIndex = startIndex,
endIndex = section.endPointIndex endIndex = section.endPointIndex
) )
startIndex = section.startPointIndex startIndex = section.startPointIndex
if (lastLane == null if (lastLane == null
|| (!(lastLane.valid && lane.valid || (!(lastLane.valid && lane.valid
&& lastLane.indications == lane.indications)) && lastLane.indications == lane.indications))
) { ) {
lanes.add(lane) lanes.add(lane)
}
lastLane = lane
} }
lastLane = lane intersections.add(Intersection(waypoints[startIndex], lanes))
} }
intersections.add(Intersection(waypoints[startIndex], lanes))
} }
stepDistance =
route.guidance.instructions[index].routeOffsetInMeters - stepDistance
stepDuration =
route.guidance.instructions[index].travelTimeInSeconds - stepDuration
val step = Step(
index = stepIndex,
street = street,
distance = stepDistance,
duration = stepDuration,
maneuver = maneuver,
intersection = intersections,
countryCode = lastInstruction.countryCode
)
stepDistance = route.guidance.instructions[index].routeOffsetInMeters.toDouble()
stepDuration = route.guidance.instructions[index].travelTimeInSeconds.toDouble()
steps.add(step)
stepIndex += 1
} }
stepDistance = legs.add(Leg(steps, summary))
route.guidance.instructions[index].routeOffsetInMeters - stepDistance
stepDuration =
route.guidance.instructions[index].travelTimeInSeconds - stepDuration
val step = Step(
index = stepIndex,
street = street,
distance = stepDistance,
duration = stepDuration,
maneuver = maneuver,
intersection = intersections,
countryCode = lastInstruction.countryCode
)
stepDistance = route.guidance.instructions[index].routeOffsetInMeters.toDouble()
stepDuration = route.guidance.instructions[index].travelTimeInSeconds.toDouble()
steps.add(step)
stepIndex += 1
} }
legs.add(Leg(steps))
val routeGeoJson = createLineStringCollection(waypoints) val routeGeoJson = createLineStringCollection(waypoints)
val centerLocation = createCenterLocation(createLineStringCollection(waypoints)) val centerLocation = createCenterLocation(createLineStringCollection(waypoints))
val newRoute = com.kouros.navigation.data.route.Routes( val newRoute = Routes(
legs, legs,
summary, summary,
routeGeoJson, routeGeoJson,
@@ -130,75 +142,79 @@ class TomTomRoute {
var newType = 0 var newType = 0
when (type) { when (type) {
"DEPART" -> { "DEPART" -> {
newType = androidx.car.app.navigation.model.Maneuver.TYPE_DEPART newType = ManeuverType.TYPE_DEPART.ordinal
} }
"ARRIVE" -> { "ARRIVE" -> {
newType = androidx.car.app.navigation.model.Maneuver.TYPE_DESTINATION newType = ManeuverType.TYPE_DESTINATION.ordinal
} }
"ARRIVE_LEFT" -> { "ARRIVE_LEFT" -> {
newType = androidx.car.app.navigation.model.Maneuver.TYPE_DESTINATION_LEFT newType = ManeuverType.TYPE_DESTINATION_LEFT.ordinal
} }
"ARRIVE_RIGHT" -> { "ARRIVE_RIGHT" -> {
newType = androidx.car.app.navigation.model.Maneuver.TYPE_DESTINATION_RIGHT newType = ManeuverType.TYPE_DESTINATION_RIGHT.ordinal
} }
"STRAIGHT", "FOLLOW" -> { "STRAIGHT", "FOLLOW" -> {
newType = androidx.car.app.navigation.model.Maneuver.TYPE_STRAIGHT newType = ManeuverType.TYPE_STRAIGHT.ordinal
} }
"KEEP_RIGHT" -> { "KEEP_RIGHT" -> {
newType = androidx.car.app.navigation.model.Maneuver.TYPE_KEEP_RIGHT newType = ManeuverType.TYPE_KEEP_RIGHT.ordinal
} }
"BEAR_RIGHT" -> { "BEAR_RIGHT" -> {
newType = androidx.car.app.navigation.model.Maneuver.TYPE_TURN_SLIGHT_RIGHT newType = ManeuverType.TYPE_TURN_SLIGHT_RIGHT.ordinal
} }
"BEAR_LEFT" -> { "BEAR_LEFT" -> {
newType = androidx.car.app.navigation.model.Maneuver.TYPE_TURN_SLIGHT_LEFT newType = ManeuverType.TYPE_TURN_SLIGHT_LEFT.ordinal
} }
"KEEP_LEFT" -> { "KEEP_LEFT" -> {
newType = androidx.car.app.navigation.model.Maneuver.TYPE_KEEP_LEFT newType = ManeuverType.TYPE_KEEP_LEFT.ordinal
} }
"TURN_LEFT" -> { "TURN_LEFT" -> {
newType = androidx.car.app.navigation.model.Maneuver.TYPE_TURN_NORMAL_LEFT newType = ManeuverType.TYPE_TURN_NORMAL_LEFT.ordinal
} }
"TURN_RIGHT" -> { "TURN_RIGHT" -> {
newType = androidx.car.app.navigation.model.Maneuver.TYPE_TURN_NORMAL_RIGHT newType = ManeuverType.TYPE_TURN_NORMAL_RIGHT.ordinal
} }
"SHARP_LEFT" -> { "SHARP_LEFT" -> {
newType = androidx.car.app.navigation.model.Maneuver.TYPE_TURN_SHARP_LEFT newType = ManeuverType.TYPE_TURN_SHARP_LEFT.ordinal
} }
"SHARP_RIGHT" -> { "SHARP_RIGHT" -> {
newType = androidx.car.app.navigation.model.Maneuver.TYPE_TURN_SHARP_RIGHT newType = ManeuverType.TYPE_TURN_SHARP_RIGHT.ordinal
} }
"ROUNDABOUT_RIGHT", "ROUNDABOUT_CROSS" -> { "ROUNDABOUT_RIGHT", "ROUNDABOUT_CROSS" -> {
newType = androidx.car.app.navigation.model.Maneuver.TYPE_ROUNDABOUT_ENTER_CCW newType = ManeuverType.TYPE_ROUNDABOUT_ENTER_CCW.ordinal
} }
"ROUNDABOUT_LEFT" -> { "ROUNDABOUT_LEFT" -> {
newType = androidx.car.app.navigation.model.Maneuver.TYPE_ROUNDABOUT_ENTER_CW newType = ManeuverType.TYPE_ROUNDABOUT_ENTER_CW.ordinal
} }
"MAKE_UTURN" -> { "MAKE_UTURN", "TRY_MAKE_UTURN" -> {
newType = androidx.car.app.navigation.model.Maneuver.TYPE_U_TURN_LEFT newType = ManeuverType.TYPE_U_TURN_LEFT.ordinal
} }
"ENTER_MOTORWAY" -> { "ENTER_MOTORWAY" -> {
newType = androidx.car.app.navigation.model.Maneuver.TYPE_MERGE_LEFT newType = ManeuverType.TYPE_MERGE_LEFT.ordinal
} }
"TAKE_EXIT" -> { "TAKE_EXIT" -> {
newType = androidx.car.app.navigation.model.Maneuver.TYPE_TURN_SLIGHT_RIGHT newType = ManeuverType.TYPE_TURN_SLIGHT_RIGHT.ordinal
}
"WAYPOINT_RIGHT" -> {
newType = ManeuverType.TYPE_WAYPOINT_RIGHT.ordinal
} }
} }
return newType return newType
@@ -16,7 +16,7 @@ class ValhallaRepository : NavigationRepository() {
override fun getRoute( override fun getRoute(
context: Context, context: Context,
currentLocation: Location, currentLocation: Location,
location: Location, location: List<Location>,
carOrientation: Float, carOrientation: Float,
searchFilter: SearchFilter searchFilter: SearchFilter
): String { ): String {
@@ -35,7 +35,7 @@ class ValhallaRepository : NavigationRepository() {
lon = currentLocation.longitude, lon = currentLocation.longitude,
searchFilter = exclude searchFilter = exclude
), ),
Locations(lat = location.latitude, lon = location.longitude, searchFilter = exclude) Locations(lat = location.first().latitude, lon = location.first().longitude, searchFilter = exclude)
) )
val valhallaLocation = ValhallaLocation( val valhallaLocation = ValhallaLocation(
locations = vLocation, locations = vLocation,
@@ -6,15 +6,12 @@ import android.graphics.BitmapFactory
import android.graphics.Canvas import android.graphics.Canvas
import android.graphics.Matrix import android.graphics.Matrix
import android.graphics.Paint import android.graphics.Paint
import androidx.annotation.DrawableRes
import androidx.car.app.model.CarIcon
import androidx.car.app.navigation.model.LaneDirection import androidx.car.app.navigation.model.LaneDirection
import androidx.car.app.navigation.model.Maneuver
import androidx.core.graphics.createBitmap import androidx.core.graphics.createBitmap
import androidx.core.graphics.drawable.IconCompat import androidx.core.graphics.drawable.IconCompat
import com.kouros.data.R import com.kouros.data.R
import com.kouros.navigation.data.StepData import com.kouros.navigation.data.StepData
import java.util.Collections import com.kouros.navigation.data.route.ManeuverType
import java.util.Locale import java.util.Locale
class IconMapper { class IconMapper {
@@ -22,62 +19,66 @@ class IconMapper {
fun maneuverIcon(routeManeuverType: Int): Int { fun maneuverIcon(routeManeuverType: Int): Int {
var currentTurnIcon = R.drawable.ic_turn_name_change var currentTurnIcon = R.drawable.ic_turn_name_change
when (routeManeuverType) { when (routeManeuverType) {
Maneuver.TYPE_STRAIGHT -> { ManeuverType.TYPE_STRAIGHT.ordinal -> {
currentTurnIcon = R.drawable.ic_turn_name_change currentTurnIcon = R.drawable.ic_turn_name_change
} }
Maneuver.TYPE_DESTINATION, ManeuverType.TYPE_DESTINATION.ordinal,
Maneuver.TYPE_DESTINATION_RIGHT, ManeuverType.TYPE_DESTINATION_RIGHT.ordinal,
Maneuver.TYPE_DESTINATION_LEFT, ManeuverType.TYPE_DESTINATION_LEFT.ordinal,
Maneuver.TYPE_DESTINATION_STRAIGHT ManeuverType.TYPE_DESTINATION_STRAIGHT.ordinal
-> { -> {
currentTurnIcon = R.drawable.ic_turn_destination currentTurnIcon = R.drawable.ic_turn_destination
} }
Maneuver.TYPE_TURN_NORMAL_RIGHT -> { ManeuverType.TYPE_TURN_NORMAL_RIGHT.ordinal -> {
currentTurnIcon = R.drawable.ic_turn_normal_right currentTurnIcon = R.drawable.ic_turn_normal_right
} }
Maneuver.TYPE_TURN_NORMAL_LEFT -> { ManeuverType.TYPE_TURN_NORMAL_LEFT.ordinal -> {
currentTurnIcon = R.drawable.ic_turn_normal_left currentTurnIcon = R.drawable.ic_turn_normal_left
} }
Maneuver.TYPE_OFF_RAMP_SLIGHT_RIGHT -> { ManeuverType.TYPE_OFF_RAMP_SLIGHT_RIGHT.ordinal -> {
currentTurnIcon = R.drawable.ic_turn_slight_right currentTurnIcon = R.drawable.ic_turn_slight_right
} }
Maneuver.TYPE_TURN_SLIGHT_RIGHT -> { ManeuverType.TYPE_TURN_SLIGHT_RIGHT.ordinal -> {
currentTurnIcon = R.drawable.ic_turn_slight_right currentTurnIcon = R.drawable.ic_turn_slight_right
} }
Maneuver.TYPE_KEEP_RIGHT -> { ManeuverType.TYPE_KEEP_RIGHT.ordinal -> {
currentTurnIcon = R.drawable.ic_turn_name_change currentTurnIcon = R.drawable.ic_turn_name_change
} }
Maneuver.TYPE_KEEP_LEFT -> { ManeuverType.TYPE_KEEP_LEFT.ordinal -> {
currentTurnIcon = R.drawable.ic_turn_name_change currentTurnIcon = R.drawable.ic_turn_name_change
} }
Maneuver.TYPE_ROUNDABOUT_ENTER_CCW -> { ManeuverType.TYPE_ROUNDABOUT_ENTER_CCW.ordinal -> {
currentTurnIcon = R.drawable.ic_roundabout_ccw currentTurnIcon = R.drawable.ic_roundabout_ccw
} }
Maneuver.TYPE_ROUNDABOUT_EXIT_CCW -> { ManeuverType.TYPE_ROUNDABOUT_EXIT_CCW.ordinal -> {
currentTurnIcon = R.drawable.ic_roundabout_ccw currentTurnIcon = R.drawable.ic_roundabout_ccw
} }
Maneuver.TYPE_U_TURN_LEFT -> { ManeuverType.TYPE_U_TURN_LEFT.ordinal -> {
currentTurnIcon = R.drawable.ic_turn_u_turn_left currentTurnIcon = R.drawable.ic_turn_u_turn_left
} }
Maneuver.TYPE_U_TURN_RIGHT -> { ManeuverType.TYPE_U_TURN_RIGHT.ordinal -> {
currentTurnIcon = R.drawable.ic_turn_u_turn_right currentTurnIcon = R.drawable.ic_turn_u_turn_right
} }
Maneuver.TYPE_MERGE_LEFT -> { ManeuverType.TYPE_MERGE_LEFT.ordinal -> {
currentTurnIcon = R.drawable.ic_turn_merge_symmetrical currentTurnIcon = R.drawable.ic_turn_merge_symmetrical
} }
ManeuverType.TYPE_WAYPOINT_RIGHT.ordinal -> {
currentTurnIcon = R.drawable.ic_turn_destination
}
} }
return currentTurnIcon return currentTurnIcon
} }
@@ -86,8 +87,8 @@ class IconMapper {
val laneDirection = when (direction.lowercase(Locale.getDefault())) { val laneDirection = when (direction.lowercase(Locale.getDefault())) {
"left_straight" -> { "left_straight" -> {
when (stepData.currentManeuverType) { when (stepData.currentManeuverType) {
Maneuver.TYPE_TURN_NORMAL_LEFT -> LaneDirection.SHAPE_NORMAL_LEFT ManeuverType.TYPE_TURN_NORMAL_LEFT.ordinal -> LaneDirection.SHAPE_NORMAL_LEFT
Maneuver.TYPE_STRAIGHT -> LaneDirection.SHAPE_STRAIGHT ManeuverType.TYPE_STRAIGHT.ordinal -> LaneDirection.SHAPE_STRAIGHT
else else
-> LaneDirection.SHAPE_UNKNOWN -> LaneDirection.SHAPE_UNKNOWN
} }
@@ -95,7 +96,7 @@ class IconMapper {
"left" -> { "left" -> {
when (stepData.currentManeuverType) { when (stepData.currentManeuverType) {
Maneuver.TYPE_TURN_NORMAL_LEFT -> LaneDirection.SHAPE_NORMAL_LEFT ManeuverType.TYPE_TURN_NORMAL_LEFT.ordinal -> LaneDirection.SHAPE_NORMAL_LEFT
else else
-> LaneDirection.SHAPE_UNKNOWN -> LaneDirection.SHAPE_UNKNOWN
} }
@@ -103,9 +104,9 @@ class IconMapper {
"straight" -> { "straight" -> {
when (stepData.currentManeuverType) { when (stepData.currentManeuverType) {
Maneuver.TYPE_STRAIGHT -> LaneDirection.SHAPE_STRAIGHT ManeuverType.TYPE_STRAIGHT.ordinal -> LaneDirection.SHAPE_STRAIGHT
Maneuver.TYPE_KEEP_LEFT -> LaneDirection.SHAPE_STRAIGHT ManeuverType.TYPE_KEEP_LEFT.ordinal -> LaneDirection.SHAPE_STRAIGHT
Maneuver.TYPE_KEEP_RIGHT -> LaneDirection.SHAPE_STRAIGHT ManeuverType.TYPE_KEEP_RIGHT.ordinal -> LaneDirection.SHAPE_STRAIGHT
else else
-> LaneDirection.SHAPE_UNKNOWN -> LaneDirection.SHAPE_UNKNOWN
} }
@@ -113,7 +114,7 @@ class IconMapper {
"right" -> { "right" -> {
when (stepData.currentManeuverType) { when (stepData.currentManeuverType) {
Maneuver.TYPE_TURN_NORMAL_RIGHT -> LaneDirection.SHAPE_NORMAL_RIGHT ManeuverType.TYPE_TURN_NORMAL_RIGHT.ordinal -> LaneDirection.SHAPE_NORMAL_RIGHT
else else
-> LaneDirection.SHAPE_UNKNOWN -> LaneDirection.SHAPE_UNKNOWN
} }
@@ -121,8 +122,8 @@ class IconMapper {
"right_straight" -> { "right_straight" -> {
when (stepData.currentManeuverType) { when (stepData.currentManeuverType) {
Maneuver.TYPE_TURN_NORMAL_RIGHT -> LaneDirection.SHAPE_NORMAL_RIGHT ManeuverType.TYPE_TURN_NORMAL_RIGHT.ordinal -> LaneDirection.SHAPE_NORMAL_RIGHT
Maneuver.TYPE_STRAIGHT -> LaneDirection.SHAPE_STRAIGHT ManeuverType.TYPE_STRAIGHT.ordinal -> LaneDirection.SHAPE_STRAIGHT
else else
-> LaneDirection.SHAPE_UNKNOWN -> LaneDirection.SHAPE_UNKNOWN
} }
@@ -130,8 +131,8 @@ class IconMapper {
"left_slight", "slight_left" -> { "left_slight", "slight_left" -> {
when (stepData.currentManeuverType) { when (stepData.currentManeuverType) {
Maneuver.TYPE_TURN_NORMAL_LEFT -> LaneDirection.SHAPE_SLIGHT_LEFT ManeuverType.TYPE_TURN_NORMAL_LEFT.ordinal -> LaneDirection.SHAPE_SLIGHT_LEFT
Maneuver.TYPE_KEEP_LEFT -> LaneDirection.SHAPE_SLIGHT_LEFT ManeuverType.TYPE_KEEP_LEFT.ordinal -> LaneDirection.SHAPE_SLIGHT_LEFT
else else
-> LaneDirection.SHAPE_UNKNOWN -> LaneDirection.SHAPE_UNKNOWN
} }
@@ -139,8 +140,8 @@ class IconMapper {
"right_slight", "slight_right" -> { "right_slight", "slight_right" -> {
when (stepData.currentManeuverType) { when (stepData.currentManeuverType) {
Maneuver.TYPE_TURN_SLIGHT_RIGHT -> LaneDirection.SHAPE_NORMAL_RIGHT ManeuverType.TYPE_TURN_SLIGHT_RIGHT.ordinal -> LaneDirection.SHAPE_NORMAL_RIGHT
Maneuver.TYPE_KEEP_RIGHT -> LaneDirection.SHAPE_SLIGHT_RIGHT ManeuverType.TYPE_KEEP_RIGHT.ordinal -> LaneDirection.SHAPE_SLIGHT_RIGHT
else else
-> LaneDirection.SHAPE_UNKNOWN -> LaneDirection.SHAPE_UNKNOWN
} }
@@ -212,8 +213,8 @@ class IconMapper {
return when (direction) { return when (direction) {
"left_straight" -> { "left_straight" -> {
when (stepData.currentManeuverType) { when (stepData.currentManeuverType) {
Maneuver.TYPE_TURN_NORMAL_LEFT -> "left_o_straight_x" ManeuverType.TYPE_TURN_NORMAL_LEFT.ordinal -> "left_o_straight_x"
Maneuver.TYPE_STRAIGHT -> "left_x_straight_o" ManeuverType.TYPE_STRAIGHT.ordinal -> "left_x_straight_o"
else else
-> "left_x_straight_x" -> "left_x_straight_x"
} }
@@ -221,29 +222,29 @@ class IconMapper {
"right_straight" -> { "right_straight" -> {
when (stepData.currentManeuverType) { when (stepData.currentManeuverType) {
Maneuver.TYPE_TURN_NORMAL_RIGHT -> "right_x_straight_x" ManeuverType.TYPE_TURN_NORMAL_RIGHT.ordinal -> "right_x_straight_x"
Maneuver.TYPE_STRAIGHT -> "right_x_straight_o" ManeuverType.TYPE_STRAIGHT.ordinal -> "right_x_straight_o"
Maneuver.TYPE_TURN_SLIGHT_RIGHT -> "right_o_straight_o" ManeuverType.TYPE_TURN_SLIGHT_RIGHT.ordinal -> "right_o_straight_o"
else else
-> "right_x_straight_x" -> "right_x_straight_x"
} }
} }
"right" -> if (stepData.currentManeuverType == Maneuver.TYPE_TURN_NORMAL_RIGHT) "${direction}_o" else "${direction}_x" "right" -> if (stepData.currentManeuverType == ManeuverType.TYPE_TURN_NORMAL_RIGHT.ordinal) "${direction}_o" else "${direction}_x"
"left" -> if (stepData.currentManeuverType == Maneuver.TYPE_TURN_NORMAL_LEFT) "${direction}_o" else "${direction}_x" "left" -> if (stepData.currentManeuverType == ManeuverType.TYPE_TURN_NORMAL_LEFT.ordinal) "${direction}_o" else "${direction}_x"
"straight" -> if (stepData.currentManeuverType == Maneuver.TYPE_STRAIGHT "straight" -> if (stepData.currentManeuverType == ManeuverType.TYPE_STRAIGHT.ordinal
|| stepData.currentManeuverType == Maneuver.TYPE_KEEP_LEFT || stepData.currentManeuverType == ManeuverType.TYPE_KEEP_LEFT.ordinal
|| stepData.currentManeuverType == Maneuver.TYPE_KEEP_RIGHT || stepData.currentManeuverType == ManeuverType.TYPE_KEEP_RIGHT.ordinal
) "${direction}_o" else "${direction}_x" ) "${direction}_o" else "${direction}_x"
"right_slight", "slight_right" -> if (stepData.currentManeuverType == Maneuver.TYPE_TURN_SLIGHT_RIGHT "right_slight", "slight_right" -> if (stepData.currentManeuverType == ManeuverType.TYPE_TURN_SLIGHT_RIGHT.ordinal
|| stepData.currentManeuverType == Maneuver.TYPE_TURN_NORMAL_RIGHT || stepData.currentManeuverType == ManeuverType.TYPE_TURN_NORMAL_RIGHT.ordinal
|| stepData.currentManeuverType == Maneuver.TYPE_KEEP_RIGHT || stepData.currentManeuverType == ManeuverType.TYPE_KEEP_RIGHT.ordinal
) "slight_right_o" else "slight_right_x" ) "slight_right_o" else "slight_right_x"
"left_slight", "slight_left" -> if (stepData.currentManeuverType == Maneuver.TYPE_TURN_SLIGHT_LEFT "left_slight", "slight_left" -> if (stepData.currentManeuverType == ManeuverType.TYPE_TURN_SLIGHT_LEFT.ordinal
|| stepData.currentManeuverType == Maneuver.TYPE_TURN_NORMAL_LEFT || stepData.currentManeuverType == ManeuverType.TYPE_TURN_NORMAL_LEFT.ordinal
|| stepData.currentManeuverType == Maneuver.TYPE_KEEP_LEFT || stepData.currentManeuverType == ManeuverType.TYPE_KEEP_LEFT.ordinal
) "slight_left_o" else "slight_left_x" ) "slight_left_o" else "slight_left_x"
else -> { else -> {
@@ -3,6 +3,7 @@ package com.kouros.navigation.model
//import com.kouros.navigation.data.Preferences.boxStore //import com.kouros.navigation.data.Preferences.boxStore
import android.content.Context import android.content.Context
import android.location.Location import android.location.Location
import android.util.Log
import androidx.compose.runtime.snapshots.SnapshotStateList import androidx.compose.runtime.snapshots.SnapshotStateList
import androidx.compose.runtime.toMutableStateList import androidx.compose.runtime.toMutableStateList
import androidx.lifecycle.MutableLiveData import androidx.lifecycle.MutableLiveData
@@ -147,7 +148,7 @@ class NavigationViewModel(private val repository: NavigationRepository) : ViewMo
fun loadRoute( fun loadRoute(
context: Context, context: Context,
currentLocation: Location, currentLocation: Location,
destination: Location, destination: List<Location>,
carOrientation: Float carOrientation: Float
) { ) {
viewModelScope.launch(Dispatchers.IO) { viewModelScope.launch(Dispatchers.IO) {
@@ -181,9 +182,11 @@ class NavigationViewModel(private val repository: NavigationRepository) : ViewMo
) )
if (data.isNotEmpty()) { if (data.isNotEmpty()) {
val trafficData = rebuildTraffic(data) val trafficData = rebuildTraffic(data)
traffic.postValue( if (trafficData.isNotEmpty()) {
trafficData traffic.postValue(
) trafficData
)
}
} }
} catch (e: Exception) { } catch (e: Exception) {
e.printStackTrace() e.printStackTrace()
@@ -234,7 +237,7 @@ class NavigationViewModel(private val repository: NavigationRepository) : ViewMo
repository.getRoute( repository.getRoute(
context, context,
currentLocation, currentLocation,
location, listOf(location),
carOrientation, carOrientation,
getSearchFilter(context) getSearchFilter(context)
) )
@@ -102,6 +102,12 @@ class SettingsViewModel(private val repository: SettingsRepository) : ViewModel(
0 0
) )
val alternativeRoutes = repository.alternativeRoutesFlow.stateIn(
viewModelScope,
SharingStarted.WhileSubscribed(5_000),
false
)
fun onShow3DChanged(enabled: Boolean) { fun onShow3DChanged(enabled: Boolean) {
viewModelScope.launch { repository.setShow3D(enabled) } viewModelScope.launch { repository.setShow3D(enabled) }
} }
@@ -159,4 +165,7 @@ class SettingsViewModel(private val repository: SettingsRepository) : ViewModel(
viewModelScope.launch { repository.setEngineType(mode) } viewModelScope.launch { repository.setEngineType(mode) }
} }
fun onAlternativeRoutes(enabled: Boolean) {
viewModelScope.launch { repository.setAlternativeRoutes(enabled) }
}
} }
@@ -50,6 +50,9 @@ class SettingsRepository(
val engineTypeFlow: Flow<Int> = val engineTypeFlow: Flow<Int> =
dataStoreManager.engineTypeFlow dataStoreManager.engineTypeFlow
val alternativeRoutesFlow: Flow<Boolean> =
dataStoreManager.alternativeRoutesFlow
suspend fun setShow3D(enabled: Boolean) { suspend fun setShow3D(enabled: Boolean) {
dataStoreManager.setShow3D(enabled) dataStoreManager.setShow3D(enabled)
} }
@@ -109,4 +112,8 @@ class SettingsRepository(
suspend fun setEngineType(mode: Int) { suspend fun setEngineType(mode: Int) {
dataStoreManager.setEngineType(mode) dataStoreManager.setEngineType(mode)
} }
suspend fun setAlternativeRoutes(enabled: Boolean) {
dataStoreManager.setAlternativeRoutes(enabled)
}
} }
@@ -71,4 +71,5 @@
<string name="combustion">Combustion</string> <string name="combustion">Combustion</string>
<string name="electric">Electric</string> <string name="electric">Electric</string>
<string name="engine_type">Engine type</string> <string name="engine_type">Engine type</string>
<string name="alternative_routes">Alternative routes</string>
</resources> </resources>
@@ -55,4 +55,5 @@
<string name="combustion">Combustion</string> <string name="combustion">Combustion</string>
<string name="electric">Electric</string> <string name="electric">Electric</string>
<string name="engine_type">Engine type</string> <string name="engine_type">Engine type</string>
<string name="alternative_routes">Alternative routes</string>
</resources> </resources>
@@ -55,4 +55,5 @@
<string name="combustion">Combustion</string> <string name="combustion">Combustion</string>
<string name="electric">Electric</string> <string name="electric">Electric</string>
<string name="engine_type">Engine type</string> <string name="engine_type">Engine type</string>
<string name="alternative_routes">Alternative routes</string>
</resources> </resources>
@@ -58,4 +58,5 @@
<string name="combustion">Combustion</string> <string name="combustion">Combustion</string>
<string name="electric">Electric</string> <string name="electric">Electric</string>
<string name="engine_type">Engine type</string> <string name="engine_type">Engine type</string>
<string name="alternative_routes">Alternative routes</string>
</resources> </resources>
@@ -55,8 +55,8 @@ class RouteCalculatorTest {
) )
} }
private fun setupRoute(steps: List<Step>, currentStepIndex: Int = 0): Route { private fun setupRoute(steps: List<Step>, currentStepIndex: Int = 0, summary: Summary): Route {
val leg = Leg(steps = steps) val leg = Leg(steps = steps, summary)
val routes = Routes( val routes = Routes(
legs = listOf(leg), legs = listOf(leg),
summary = Summary(), summary = Summary(),
@@ -74,7 +74,7 @@ class RouteCalculatorTest {
fun `findStep updates currentStepIndex to step containing the nearest waypoint`() { fun `findStep updates currentStepIndex to step containing the nearest waypoint`() {
val step0 = createStep(index = 0, numWaypoints = 2) val step0 = createStep(index = 0, numWaypoints = 2)
val step1 = createStep(index = 1, numWaypoints = 2) val step1 = createStep(index = 1, numWaypoints = 2)
routeModel.navState = routeModel.navState.copy(route = setupRoute(listOf(step0, step1))) routeModel.navState = routeModel.navState.copy(route = setupRoute(listOf(step0, step1), summary = Summary()))
val mockLocation: Location = mock() val mockLocation: Location = mock()
// step0/wp0: 500F, step0/wp1: 400F, step1/wp0: 300F, step1/wp1: 8F // step0/wp0: 500F, step0/wp1: 400F, step1/wp0: 300F, step1/wp1: 8F
@@ -88,7 +88,7 @@ class RouteCalculatorTest {
@Test @Test
fun `findStep updates waypointIndex to the nearest waypoint within the step`() { fun `findStep updates waypointIndex to the nearest waypoint within the step`() {
val step0 = createStep(index = 0, numWaypoints = 3) val step0 = createStep(index = 0, numWaypoints = 3)
routeModel.navState = routeModel.navState.copy(route = setupRoute(listOf(step0))) routeModel.navState = routeModel.navState.copy(route = setupRoute(listOf(step0), summary = Summary()))
val mockLocation: Location = mock() val mockLocation: Location = mock()
// wp0: 100F, wp1: 30F (nearest), wp2: 80F // wp0: 100F, wp1: 30F (nearest), wp2: 80F
@@ -104,7 +104,7 @@ class RouteCalculatorTest {
val step0 = createStep(index = 0, numWaypoints = 2) val step0 = createStep(index = 0, numWaypoints = 2)
val step1 = createStep(index = 1, numWaypoints = 2) val step1 = createStep(index = 1, numWaypoints = 2)
routeModel.navState = routeModel.navState.copy( routeModel.navState = routeModel.navState.copy(
route = setupRoute(listOf(step0, step1), currentStepIndex = 1) route = setupRoute(listOf(step0, step1), currentStepIndex = 1, summary = Summary())
) )
val mockLocation: Location = mock() val mockLocation: Location = mock()
@@ -123,7 +123,7 @@ class RouteCalculatorTest {
val step1 = createStep(index = 1, numWaypoints = 2) val step1 = createStep(index = 1, numWaypoints = 2)
val step2 = createStep(index = 2, numWaypoints = 2) val step2 = createStep(index = 2, numWaypoints = 2)
routeModel.navState = routeModel.navState.copy( routeModel.navState = routeModel.navState.copy(
route = setupRoute(listOf(step0, step1, step2)) route = setupRoute(listOf(step0, step1, step2), summary = Summary())
) )
val mockLocation: Location = mock() val mockLocation: Location = mock()
@@ -147,7 +147,7 @@ class RouteCalculatorTest {
val step0 = createStep(index = 0, numWaypoints = 2, duration = 60.0, waypointIndex = 0) val step0 = createStep(index = 0, numWaypoints = 2, duration = 60.0, waypointIndex = 0)
val step1 = createStep(index = 1, numWaypoints = 2, duration = 120.0) val step1 = createStep(index = 1, numWaypoints = 2, duration = 120.0)
val step2 = createStep(index = 2, numWaypoints = 2, duration = 90.0) val step2 = createStep(index = 2, numWaypoints = 2, duration = 90.0)
routeModel.navState = routeModel.navState.copy(route = setupRoute(listOf(step0, step1, step2))) routeModel.navState = routeModel.navState.copy(route = setupRoute(listOf(step0, step1, step2), summary = Summary()))
val result = routeCalculator.travelLeftTime() val result = routeCalculator.travelLeftTime()
@@ -160,7 +160,7 @@ class RouteCalculatorTest {
// waypointIndex=2, waypoints=4 → percent = 100*(4-2)/4 = 50 → time = 80*50/100 = 40s // waypointIndex=2, waypoints=4 → percent = 100*(4-2)/4 = 50 → time = 80*50/100 = 40s
val step0 = createStep(index = 0, numWaypoints = 4, duration = 80.0, waypointIndex = 2) val step0 = createStep(index = 0, numWaypoints = 4, duration = 80.0, waypointIndex = 2)
val step1 = createStep(index = 1, numWaypoints = 2, duration = 40.0) val step1 = createStep(index = 1, numWaypoints = 2, duration = 40.0)
routeModel.navState = routeModel.navState.copy(route = setupRoute(listOf(step0, step1))) routeModel.navState = routeModel.navState.copy(route = setupRoute(listOf(step0, step1), summary = Summary()))
val result = routeCalculator.travelLeftTime() val result = routeCalculator.travelLeftTime()
@@ -172,7 +172,7 @@ class RouteCalculatorTest {
fun `travelLeftTime returns only future steps when at last step`() { fun `travelLeftTime returns only future steps when at last step`() {
val step0 = createStep(index = 0, numWaypoints = 2, duration = 60.0, waypointIndex = 1) val step0 = createStep(index = 0, numWaypoints = 2, duration = 60.0, waypointIndex = 1)
routeModel.navState = routeModel.navState.copy( routeModel.navState = routeModel.navState.copy(
route = setupRoute(listOf(step0), currentStepIndex = 0) route = setupRoute(listOf(step0), currentStepIndex = 0, summary = Summary())
) )
val result = routeCalculator.travelLeftTime() val result = routeCalculator.travelLeftTime()
@@ -189,7 +189,7 @@ class RouteCalculatorTest {
fun `leftStepDistance returns 0 when waypointIndex is at the last position`() { fun `leftStepDistance returns 0 when waypointIndex is at the last position`() {
// Loop range: waypointIndex..<waypoints.size-1 = 2..<2, which is empty // Loop range: waypointIndex..<waypoints.size-1 = 2..<2, which is empty
val step0 = createStep(index = 0, numWaypoints = 3, waypointIndex = 2) val step0 = createStep(index = 0, numWaypoints = 3, waypointIndex = 2)
routeModel.navState = routeModel.navState.copy(route = setupRoute(listOf(step0))) routeModel.navState = routeModel.navState.copy(route = setupRoute(listOf(step0), summary = Summary()))
val result = routeCalculator.leftStepDistance() val result = routeCalculator.leftStepDistance()
@@ -206,7 +206,7 @@ class RouteCalculatorTest {
val step0 = createStep(index = 0, numWaypoints = 2, distance = 100.0, waypointIndex = 1) val step0 = createStep(index = 0, numWaypoints = 2, distance = 100.0, waypointIndex = 1)
val step1 = createStep(index = 1, numWaypoints = 2, distance = 200.0) val step1 = createStep(index = 1, numWaypoints = 2, distance = 200.0)
val step2 = createStep(index = 2, numWaypoints = 2, distance = 150.0) val step2 = createStep(index = 2, numWaypoints = 2, distance = 150.0)
routeModel.navState = routeModel.navState.copy(route = setupRoute(listOf(step0, step1, step2))) routeModel.navState = routeModel.navState.copy(route = setupRoute(listOf(step0, step1, step2), summary = Summary()))
val result = routeCalculator.travelLeftDistance() val result = routeCalculator.travelLeftDistance()
@@ -217,7 +217,7 @@ class RouteCalculatorTest {
@Test @Test
fun `travelLeftDistance returns 0 when on last step at last waypoint`() { fun `travelLeftDistance returns 0 when on last step at last waypoint`() {
val step0 = createStep(index = 0, numWaypoints = 2, distance = 200.0, waypointIndex = 1) val step0 = createStep(index = 0, numWaypoints = 2, distance = 200.0, waypointIndex = 1)
routeModel.navState = routeModel.navState.copy(route = setupRoute(listOf(step0))) routeModel.navState = routeModel.navState.copy(route = setupRoute(listOf(step0), summary = Summary()))
val result = routeCalculator.travelLeftDistance() val result = routeCalculator.travelLeftDistance()
@@ -232,7 +232,8 @@ class RouteCalculatorTest {
fun `arrivalTime returns a timestamp roughly travelLeftTime seconds in the future`() { fun `arrivalTime returns a timestamp roughly travelLeftTime seconds in the future`() {
// step0: 2 waypoints at wp0 → 100% of 3600s duration // step0: 2 waypoints at wp0 → 100% of 3600s duration
val step0 = createStep(index = 0, numWaypoints = 2, duration = 3600.0, waypointIndex = 0) val step0 = createStep(index = 0, numWaypoints = 2, duration = 3600.0, waypointIndex = 0)
routeModel.navState = routeModel.navState.copy(route = setupRoute(listOf(step0))) val summary = Summary(duration = 3600.0)
routeModel.navState = routeModel.navState.copy(route = setupRoute(listOf(step0), summary = summary))
val before = System.currentTimeMillis() val before = System.currentTimeMillis()
val result = routeCalculator.arrivalTime() val result = routeCalculator.arrivalTime()
@@ -48,7 +48,7 @@ class RouteModelTest {
} }
private fun setupRoute(steps: List<Step>, currentStepIndex: Int = 0): Route { private fun setupRoute(steps: List<Step>, currentStepIndex: Int = 0): Route {
val leg = Leg(steps = steps) val leg = Leg(steps = steps, summary = Summary())
val routes = Routes( val routes = Routes(
legs = listOf(leg), legs = listOf(leg),
summary = Summary(), summary = Summary(),