Navigation Screen to Session, Remove NavigationService

This commit is contained in:
Dimitris
2026-03-26 17:04:52 +01:00
parent aaa57c14b8
commit 263b5b576d
11 changed files with 462 additions and 337 deletions

View File

@@ -43,6 +43,7 @@ import com.google.android.gms.location.LocationServices
import com.kouros.data.R import com.kouros.data.R
import com.kouros.navigation.MainApplication.Companion.navigationViewModel import com.kouros.navigation.MainApplication.Companion.navigationViewModel
import com.kouros.navigation.car.TextToSpeechManager import com.kouros.navigation.car.TextToSpeechManager
import com.kouros.navigation.car.navigation.NavigationService
import com.kouros.navigation.data.Constants.DESTINATION_ARRIVAL_DISTANCE import com.kouros.navigation.data.Constants.DESTINATION_ARRIVAL_DISTANCE
import com.kouros.navigation.data.Constants.INSTRUCTION_DISTANCE import com.kouros.navigation.data.Constants.INSTRUCTION_DISTANCE
import com.kouros.navigation.data.Constants.TAG import com.kouros.navigation.data.Constants.TAG
@@ -79,6 +80,8 @@ import kotlin.time.Duration.Companion.seconds
class MainActivity : ComponentActivity() { class MainActivity : ComponentActivity() {
var navigationService: NavigationService? = null
var isBound: Boolean = false var isBound: Boolean = false
val routeData = MutableLiveData("") val routeData = MutableLiveData("")
val routeModel = RouteModel() val routeModel = RouteModel()
@@ -99,6 +102,20 @@ class MainActivity : ComponentActivity() {
} }
} }
// Monitors the state of the connection to the navigation service.
private val serviceConnection: ServiceConnection = object : ServiceConnection {
override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
val binder: NavigationService.LocalBinder = service as NavigationService.LocalBinder
navigationService = binder.service
isBound = true
}
override fun onServiceDisconnected(name: ComponentName?) {
navigationService = null
isBound = false
}
}
val cameraPosition = MutableLiveData( val cameraPosition = MutableLiveData(
CameraPosition( CameraPosition(
zoom = 15.0, target = Position(latitude = 48.1857475, longitude = 11.5793627) zoom = 15.0, target = Position(latitude = 48.1857475, longitude = 11.5793627)
@@ -151,6 +168,27 @@ class MainActivity : ComponentActivity() {
} }
} }
override fun onStart() {
super.onStart()
Log.i(TAG, "In onStart()")
bindService(
Intent(this, NavigationService::class.java),
serviceConnection,
BIND_AUTO_CREATE
)
requestPermissions(arrayOf(Manifest.permission.ACCESS_FINE_LOCATION), 1)
}
override fun onStop() {
Log.i(TAG, "In onStop(). bound $isBound")
if (isBound) {
unbindService(serviceConnection)
isBound = false
navigationService = null
}
super.onStop()
}
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
fun StartScreen( fun StartScreen(
@@ -318,6 +356,7 @@ class MainActivity : ComponentActivity() {
routeModel.navState = routeModel.navState.copy(routingEngine = routingEngine) routeModel.navState = routeModel.navState.copy(routingEngine = routingEngine)
routeModel.startNavigation(newRoute) routeModel.startNavigation(newRoute)
routeData.value = routeModel.curRoute.routeGeoJson routeData.value = routeModel.curRoute.routeGeoJson
navigationService?.startNavigation()
} }
fun stopNavigation(closeSheet: () -> Unit) { fun stopNavigation(closeSheet: () -> Unit) {
closeSheet() closeSheet()
@@ -325,6 +364,7 @@ class MainActivity : ComponentActivity() {
getSettingsViewModel(applicationContext).onLastRouteChanged("") getSettingsViewModel(applicationContext).onLastRouteChanged("")
routeData.value = "" routeData.value = ""
stepData.value = StepData("", "", 0.0, 0, 0, 0, 0.0) stepData.value = StepData("", "", 0.0, 0, 0, 0, 0.0)
navigationService?.stopNavigation()
} }
fun textToSpeech() { fun textToSpeech() {

View File

@@ -34,6 +34,7 @@ import com.kouros.data.R
import com.kouros.navigation.car.navigation.RouteCarModel import com.kouros.navigation.car.navigation.RouteCarModel
import com.kouros.navigation.car.screen.NavigationListener import com.kouros.navigation.car.screen.NavigationListener
import com.kouros.navigation.car.screen.NavigationScreen import com.kouros.navigation.car.screen.NavigationScreen
import com.kouros.navigation.data.Place
import kotlinx.coroutines.awaitCancellation import kotlinx.coroutines.awaitCancellation
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@@ -67,7 +68,7 @@ internal class ClusterSession : Session(), NavigationListener {
OnClickListener {}) OnClickListener {})
.build() .build()
mNavigationCarSurface = SurfaceRenderer(carContext, lifecycle, routeModel, viewModelStoreOwner) mNavigationCarSurface = SurfaceRenderer(carContext, lifecycle, viewModelStoreOwner)
// mNavigationScreen = // mNavigationScreen =
// new NavigationScreen(getCarContext(), mSettingsAction, this, mNavigationCarSurface); // new NavigationScreen(getCarContext(), mSettingsAction, this, mNavigationCarSurface);
@@ -99,6 +100,10 @@ internal class ClusterSession : Session(), NavigationListener {
override fun updateTrip(trip: Trip) { override fun updateTrip(trip: Trip) {
} }
override fun navigateToPlace(place: Place) {
}
companion object { companion object {
val TAG: String = ClusterSession::class.java.getSimpleName() val TAG: String = ClusterSession::class.java.getSimpleName()
} }

View File

@@ -14,7 +14,7 @@ import com.kouros.navigation.data.Constants.TAG
class NavigationCarAppService : CarAppService() { class NavigationCarAppService : CarAppService() {
val INTENT_ACTION_NAV_NOTIFICATION_OPEN_APP = val intentActionNavNotificationOpenApp =
"com.kouros.navigation.INTENT_ACTION_NAV_NOTIFICATION_OPEN_APP" "com.kouros.navigation.INTENT_ACTION_NAV_NOTIFICATION_OPEN_APP"
val channelId: String = "NavigationSessionChannel" val channelId: String = "NavigationSessionChannel"
@@ -31,7 +31,6 @@ class NavigationCarAppService : CarAppService() {
} }
override fun onCreateSession(sessionInfo: SessionInfo): Session { override fun onCreateSession(sessionInfo: SessionInfo): Session {
Log.d(TAG, "Display Type: ${sessionInfo.displayType}")
if (sessionInfo.displayType == SessionInfo.DISPLAY_TYPE_CLUSTER) { if (sessionInfo.displayType == SessionInfo.DISPLAY_TYPE_CLUSTER) {
return ClusterSession() return ClusterSession()
} else { } else {

View File

@@ -136,9 +136,9 @@ class NavigationNotificationService : Service() {
// heads-up notification or the rail widget. // heads-up notification or the rail widget.
val pendingIntent = CarPendingIntent.getCarApp( val pendingIntent = CarPendingIntent.getCarApp(
context, context,
NavigationCarAppService().INTENT_ACTION_NAV_NOTIFICATION_OPEN_APP.hashCode(), NavigationCarAppService().intentActionNavNotificationOpenApp.hashCode(),
Intent( Intent(
NavigationCarAppService().INTENT_ACTION_NAV_NOTIFICATION_OPEN_APP NavigationCarAppService().intentActionNavNotificationOpenApp
).setComponent( ).setComponent(
ComponentName( ComponentName(
context, context,
@@ -146,7 +146,7 @@ class NavigationNotificationService : Service() {
) )
).setData( ).setData(
NavigationCarAppService().createDeepLinkUri( NavigationCarAppService().createDeepLinkUri(
NavigationCarAppService().INTENT_ACTION_NAV_NOTIFICATION_OPEN_APP NavigationCarAppService().intentActionNavNotificationOpenApp
) )
), ),
0 0

View File

@@ -40,25 +40,38 @@ import com.kouros.navigation.car.screen.NavigationType
import com.kouros.navigation.car.screen.RequestPermissionScreen import com.kouros.navigation.car.screen.RequestPermissionScreen
import com.kouros.navigation.car.screen.SearchScreen import com.kouros.navigation.car.screen.SearchScreen
import com.kouros.navigation.car.screen.checkPermission import com.kouros.navigation.car.screen.checkPermission
import com.kouros.navigation.car.screen.observers.NavigationObserverCallback
import com.kouros.navigation.car.screen.observers.NavigationObserverManager
import com.kouros.navigation.data.Constants.AUTOMOTIVE_CAR_SPEED_PERMISSION import com.kouros.navigation.data.Constants.AUTOMOTIVE_CAR_SPEED_PERMISSION
import com.kouros.navigation.data.Constants.DESTINATION_ARRIVAL_DISTANCE
import com.kouros.navigation.data.Constants.GMS_CAR_SPEED_PERMISSION import com.kouros.navigation.data.Constants.GMS_CAR_SPEED_PERMISSION
import com.kouros.navigation.data.Constants.INSTRUCTION_DISTANCE import com.kouros.navigation.data.Constants.INSTRUCTION_DISTANCE
import com.kouros.navigation.data.Constants.MAXIMAL_ROUTE_DEVIATION import com.kouros.navigation.data.Constants.MAXIMAL_ROUTE_DEVIATION
import com.kouros.navigation.data.Constants.MAXIMAL_SNAP_CORRECTION import com.kouros.navigation.data.Constants.MAXIMAL_SNAP_CORRECTION
import com.kouros.navigation.data.Constants.TAG import com.kouros.navigation.data.Constants.TAG
import com.kouros.navigation.data.Constants.TRAFFIC_UPDATE
import com.kouros.navigation.data.Place
import com.kouros.navigation.data.RouteEngine import com.kouros.navigation.data.RouteEngine
import com.kouros.navigation.data.ViewStyle import com.kouros.navigation.data.ViewStyle
import com.kouros.navigation.data.osrm.OsrmRepository import com.kouros.navigation.data.osrm.OsrmRepository
import com.kouros.navigation.data.overpass.Elements
import com.kouros.navigation.data.tomtom.TomTomRepository import com.kouros.navigation.data.tomtom.TomTomRepository
import com.kouros.navigation.data.valhalla.ValhallaRepository import com.kouros.navigation.data.valhalla.ValhallaRepository
import com.kouros.navigation.model.NavigationViewModel import com.kouros.navigation.model.NavigationViewModel
import com.kouros.navigation.model.RouteModel
import com.kouros.navigation.utils.GeoUtils
import com.kouros.navigation.utils.GeoUtils.snapLocation import com.kouros.navigation.utils.GeoUtils.snapLocation
import com.kouros.navigation.utils.NavigationUtils.getViewModel import com.kouros.navigation.utils.NavigationUtils.getViewModel
import com.kouros.navigation.utils.formattedDistance
import com.kouros.navigation.utils.getSettingsRepository import com.kouros.navigation.utils.getSettingsRepository
import com.kouros.navigation.utils.getSettingsViewModel
import com.kouros.navigation.utils.location
import kotlinx.coroutines.awaitCancellation import kotlinx.coroutines.awaitCancellation
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import java.time.Duration
import java.time.LocalDateTime import java.time.LocalDateTime
import java.time.ZoneOffset import java.time.ZoneOffset
import kotlin.math.absoluteValue
/** /**
@@ -67,7 +80,7 @@ import java.time.ZoneOffset
* car hardware sensors, routing engine selection, and screen navigation. * car hardware sensors, routing engine selection, and screen navigation.
* Implements NavigationScreen.Listener for handling navigation events. * Implements NavigationScreen.Listener for handling navigation events.
*/ */
class NavigationSession : Session(), NavigationListener { class NavigationSession : Session(), NavigationListener, NavigationObserverCallback {
// Flag to enable/disable contact access feature // Flag to enable/disable contact access feature
val useContacts = false val useContacts = false
@@ -75,6 +88,8 @@ class NavigationSession : Session(), NavigationListener {
// Model for managing route state and navigation logic for Android Auto // Model for managing route state and navigation logic for Android Auto
lateinit var routeModel: RouteCarModel lateinit var routeModel: RouteCarModel
var route = ""
// Main navigation screen displayed to the user // Main navigation screen displayed to the user
lateinit var navigationScreen: NavigationScreen lateinit var navigationScreen: NavigationScreen
@@ -95,6 +110,15 @@ class NavigationSession : Session(), NavigationListener {
val simulation = Simulation() val simulation = Simulation()
private var routingEngine = 0
private var showTraffic = false;
var lastCameraSearch = 0
var speedCameras = listOf<Elements>()
var lastRouteDate: LocalDateTime = LocalDateTime.now()
var navigationManagerStarted = false var navigationManagerStarted = false
/** /**
@@ -130,8 +154,22 @@ class NavigationSession : Session(), NavigationListener {
var guidanceAudio = 0 var guidanceAudio = 0
var lastTrafficDate: LocalDateTime = LocalDateTime.MIN
lateinit var observerManager: NavigationObserverManager
val repository = getSettingsRepository(carContext)
val settingsViewModel = getSettingsViewModel(carContext)
init { init {
lifecycle.addObserver(lifecycleObserver) lifecycle.addObserver(lifecycleObserver)
repository.routingEngineFlow.asLiveData().observe(this, Observer {
routingEngine = it
})
repository.trafficFlow.asLiveData().observe(this, Observer {
showTraffic = it
})
} }
/** /**
@@ -144,6 +182,8 @@ class NavigationSession : Session(), NavigationListener {
RouteEngine.OSRM.ordinal -> NavigationViewModel(OsrmRepository()) RouteEngine.OSRM.ordinal -> NavigationViewModel(OsrmRepository())
else -> NavigationViewModel(TomTomRepository()) else -> NavigationViewModel(TomTomRepository())
} }
observerManager = NavigationObserverManager(navigationViewModel, this)
observerManager.attachAllObservers(this)
} }
/** /**
@@ -166,11 +206,13 @@ class NavigationSession : Session(), NavigationListener {
when (connectionState) { when (connectionState) {
CarConnection.CONNECTION_TYPE_NOT_CONNECTED -> Unit CarConnection.CONNECTION_TYPE_NOT_CONNECTED -> Unit
CarConnection.CONNECTION_TYPE_NATIVE -> { CarConnection.CONNECTION_TYPE_NATIVE -> {
navigationViewModel.permissionGranted.value = checkPermission(carContext,AUTOMOTIVE_CAR_SPEED_PERMISSION) navigationViewModel.permissionGranted.value =
checkPermission(carContext, AUTOMOTIVE_CAR_SPEED_PERMISSION)
} }
CarConnection.CONNECTION_TYPE_PROJECTION -> { CarConnection.CONNECTION_TYPE_PROJECTION -> {
navigationViewModel.permissionGranted.value = checkPermission(carContext, GMS_CAR_SPEED_PERMISSION) navigationViewModel.permissionGranted.value =
checkPermission(carContext, GMS_CAR_SPEED_PERMISSION)
} }
} }
} }
@@ -230,7 +272,7 @@ class NavigationSession : Session(), NavigationListener {
autoDriveEnabled = true autoDriveEnabled = true
startNavigation() startNavigation()
CarToast.makeText(carContext, "Auto drive enabled", CarToast.LENGTH_LONG) CarToast.makeText(carContext, "Auto drive enabled", CarToast.LENGTH_LONG)
.show() .show()
} }
override fun onStopNavigation() { override fun onStopNavigation() {
@@ -242,7 +284,7 @@ class NavigationSession : Session(), NavigationListener {
} }
} }
}) })
surfaceRenderer = SurfaceRenderer(carContext, lifecycle, routeModel, viewModelStoreOwner) surfaceRenderer = SurfaceRenderer(carContext, lifecycle, viewModelStoreOwner)
carSensorManager = CarSensorManager( carSensorManager = CarSensorManager(
carContext = carContext, carContext = carContext,
@@ -281,7 +323,6 @@ class NavigationSession : Session(), NavigationListener {
navigationScreen = NavigationScreen( navigationScreen = NavigationScreen(
carContext, carContext,
surfaceRenderer, surfaceRenderer,
routeModel,
this, this,
navigationViewModel navigationViewModel
) )
@@ -369,6 +410,11 @@ class NavigationSession : Session(), NavigationListener {
* Handles route snapping, deviation detection for rerouting, and map updates. * Handles route snapping, deviation detection for rerouting, and map updates.
*/ */
fun updateLocation(location: Location) { fun updateLocation(location: Location) {
val streetName = if (routeModel.isNavigating()) {
routeModel.currentStep().street
} else {
""
}
if (routeModel.navState.carConnection == CarConnection.CONNECTION_TYPE_PROJECTION) { if (routeModel.navState.carConnection == CarConnection.CONNECTION_TYPE_PROJECTION) {
surfaceRenderer.updateCarSpeed(location.speed) surfaceRenderer.updateCarSpeed(location.speed)
} }
@@ -376,8 +422,8 @@ class NavigationSession : Session(), NavigationListener {
if (routeModel.isNavigating()) { if (routeModel.isNavigating()) {
handleNavigationLocation(location) handleNavigationLocation(location)
} else { } else {
navigationScreen.checkTraffic(LocalDateTime.now(ZoneOffset.UTC), location) checkTraffic(LocalDateTime.now(ZoneOffset.UTC), location)
surfaceRenderer.updateLocation(location) surfaceRenderer.updateLocation(location, streetName)
} }
} }
@@ -395,11 +441,19 @@ class NavigationSession : Session(), NavigationListener {
* Snaps location to route and checks for deviation requiring reroute. * Snaps location to route and checks for deviation requiring reroute.
*/ */
private fun handleNavigationLocation(location: Location) { private fun handleNavigationLocation(location: Location) {
if (guidanceAudio == 1) { if (guidanceAudio == 1) {
handleGuidanceAudio() handleGuidanceAudio()
} }
navigationScreen.updateTrip(location) val streetName = routeModel.currentStep().street
val currentDate = LocalDateTime.now(ZoneOffset.UTC)
checkTraffic(currentDate, location)
updateSpeedCamera(location)
checkRoute(currentDate, location)
routeModel.updateLocation(location, navigationViewModel)
checkArrival()
updateTripNavigationScreen(location)
if (routeModel.navState.arrived) return if (routeModel.navState.arrived) return
val snappedLocation = snapLocation(location, routeModel.route.maneuverLocations()) val snappedLocation = snapLocation(location, routeModel.route.maneuverLocations())
val distance = location.distanceTo(snappedLocation) val distance = location.distanceTo(snappedLocation)
@@ -409,15 +463,81 @@ class NavigationSession : Session(), NavigationListener {
} }
distance < MAXIMAL_SNAP_CORRECTION -> { distance < MAXIMAL_SNAP_CORRECTION -> {
surfaceRenderer.updateLocation(snappedLocation) surfaceRenderer.updateLocation(snappedLocation, streetName)
} }
else -> { else -> {
surfaceRenderer.updateLocation(location) surfaceRenderer.updateLocation(location, streetName)
} }
} }
} }
fun updateTripNavigationScreen(location: Location) {
val travelEstimateTrip = routeModel.travelEstimateTrip(carContext, 0)
val travelEstimateStep = routeModel.travelEstimateStep(carContext, 0)
val steps = mutableListOf<Step>()
val street = if (routeModel.navState.destination.street != null) {
routeModel.navState.destination.street!!
} else {
// routeModel.navState.destination.name!!
"Street"
}
val destination = Destination.Builder()
.setName(street)
.setAddress(street)
.build()
val distance =
formattedDistance(0, routeModel.routeCalculator.leftStepDistance())
steps.add(routeModel.currentStep(carContext))
if (routeModel.navState.nextStep) {
steps.add(routeModel.nextStep(carContext = carContext))
}
navigationScreen.updateTrip(
isNavigating = routeModel.isNavigating(),
isRerouting = false,
hasArrived = routeModel.isArrival(),
destinationTravelEstimate = travelEstimateTrip,
stepTravelEstimate = travelEstimateStep,
destinations = mutableListOf(destination),
steps = steps,
nextStepRemainingDistance = Distance.create(distance.first, distance.second),
shouldShowNextStep = false,
shouldShowLanes = true,
junctionImage = null,
backGroundColor = routeModel.backGroundColor()
)
/**
* Updates the trip information and notifies the listener with a new Trip object.
* This includes destination name, address, travel estimate, and loading status.
*/
val tripBuilder = Trip.Builder()
tripBuilder.addDestination(
destination,
travelEstimateTrip
)
tripBuilder.setLoading(false)
tripBuilder.setCurrentRoad(destination.name.toString())
tripBuilder.addStep(steps.first(), travelEstimateStep)
updateTrip(tripBuilder.build())
}
/**
* Checks for arrival
*/
fun checkArrival() {
if (routeModel.isArrival()
&& routeModel.routeCalculator.leftStepDistance() < DESTINATION_ARRIVAL_DISTANCE
) {
stopNavigation()
settingsViewModel.onLastRouteChanged("")
routeModel.navState = routeModel.navState.copy(arrived = true)
surfaceRenderer.routeData.value = ""
}
}
/** /**
* Stops active navigation and clears route state. * Stops active navigation and clears route state.
* Called when user exits navigation or arrives at destination. * Called when user exits navigation or arrives at destination.
@@ -430,6 +550,7 @@ class NavigationSession : Session(), NavigationListener {
autoDriveEnabled = false autoDriveEnabled = false
} }
surfaceRenderer.routeData.value = "" surfaceRenderer.routeData.value = ""
lastCameraSearch = 0
surfaceRenderer.viewStyle = ViewStyle.VIEW surfaceRenderer.viewStyle = ViewStyle.VIEW
navigationScreen.navigationType = NavigationType.VIEW navigationScreen.navigationType = NavigationType.VIEW
} }
@@ -470,7 +591,196 @@ class NavigationSession : Session(), NavigationListener {
} }
} }
companion object { /**
* Handles the received route string.
* Starts navigation and invalidates the screen.
*/
override fun onRouteReceived(route: String) {
if (route.isNotEmpty()) {
this.route = route
if (routeModel.isNavigating()) {
updateRoute(route)
} else {
prepareRoute(route)
}
updateTripNavigationScreen(surfaceRenderer.lastLocation)
}
}
/**
* Prepare route and start navigation
*/
private fun prepareRoute(route: String) {
routeModel.navState = routeModel.navState.copy(routingEngine = routingEngine)
routeModel.startNavigation(route)
if (routeModel.hasLegs()) {
settingsViewModel.onLastRouteChanged(route)
}
surfaceRenderer.setRouteData(routeModel.curRoute.routeGeoJson)
startNavigation()
updateTripNavigationScreen(surfaceRenderer.lastLocation)
//navigationScreen.updateTrip(surfaceRenderer.lastLocation)
}
/**
* Update route and traffic data
*/
private fun updateRoute(route: String) {
val newRouteModel = RouteModel()
newRouteModel.navState = routeModel.navState.copy(routingEngine = routingEngine)
newRouteModel.startNavigation(route)
routeModel.curRoute.summary.trafficDelay = newRouteModel.curRoute.summary.trafficDelay
//navigationScreen.updateTrip(surfaceRenderer.lastLocation)
updateTripNavigationScreen(surfaceRenderer.lastLocation)
}
override fun isNavigating(): Boolean = routeModel.isNavigating()
/**
* Handles received traffic data and updates the surface renderer.
*/
override fun onTrafficReceived(traffic: Map<String, String>) {
if (traffic.isNotEmpty()) {
surfaceRenderer.setTrafficData(traffic)
}
}
/**
* Handles the received place search result.
* Navigates to the specified place.
*/
override fun onPlaceSearchResultReceived(place: Place) {
navigateToPlace(place)
}
/**
* Handles received speed camera data.
* Updates the surface renderer with the camera locations.
*/
override fun onSpeedCamerasReceived(cameras: List<Elements>) {
speedCameras = cameras
val coordinates = mutableListOf<List<Double>>()
cameras.forEach {
coordinates.add(listOf(it.lon, it.lat))
}
val speedData = GeoUtils.createPointCollection(coordinates, "radar")
surfaceRenderer.speedCamerasData.value = speedData
}
/**
* Handles received maximum speed data and updates the surface renderer.
*/
override fun onMaxSpeedReceived(speed: Int) {
surfaceRenderer.maxSpeed.value = speed
}
override fun invalidateScreen() {
navigationScreen.invalidate()
}
/**
* Loads a route to the specified place and sets it as the destination.
*/
override fun navigateToPlace(place: Place) {
val preview = place.route //navigationViewModel.previewRoute.value
navigationViewModel.previewRoute.value = ""
val location = location(place.longitude, place.latitude)
navigationViewModel.saveRecent(carContext, place)
//currentNavigationLocation = location
if (preview.isEmpty()) {
navigationViewModel.loadRoute(
carContext,
surfaceRenderer.lastLocation,
location,
surfaceRenderer.carOrientation
)
} else {
routeModel.navState = routeModel.navState.copy(currentRouteIndex = place.routeIndex)
onRouteReceived(preview)
}
routeModel.navState = routeModel.navState.copy(destination = place)
surfaceRenderer.activateNavigationView()
}
/**
* Checks if traffic data needs to be updated based on the time since the last update.
*/
fun checkTraffic(current: LocalDateTime, location: Location) {
val duration = Duration.between(current, lastTrafficDate)
if (showTraffic && duration.abs().seconds > TRAFFIC_UPDATE) {
lastTrafficDate = current
navigationViewModel.loadTraffic(carContext, location, surfaceRenderer.carOrientation)
}
}
/**
* Periodically requests speed camera information near the current location.
*/
private fun updateSpeedCamera(location: Location) {
if (lastCameraSearch++ % 100 == 0) {
navigationViewModel.getSpeedCameras(location, 5.0)
}
if (speedCameras.isNotEmpty()) {
updateDistance(location)
}
}
/**
* Updates distances to nearby speed cameras and checks for proximity alerts.
*/
private fun updateDistance(
location: Location,
) {
val updatedCameras = mutableListOf<Elements>()
speedCameras.forEach {
val plLocation =
location(longitude = it.lon, latitude = it.lat)
val distance = plLocation.distanceTo(location)
it.distance = distance.toDouble()
updatedCameras.add(it)
}
val sortedList = updatedCameras.sortedWith(compareBy { it.distance })
val camera = sortedList.firstOrNull() ?: return
val bearingRoute = surfaceRenderer.lastLocation.bearingTo(location)
val bearingSpeedCamera = if (camera.tags.direction != null) {
try {
camera.tags.direction!!.toFloat()
} catch (e: Exception) {
0F
}
} else {
location.bearingTo(location(camera.lon, camera.lat)).absoluteValue
}
if (camera.distance < 80) {
if ((bearingSpeedCamera - bearingRoute.absoluteValue).absoluteValue < 15.0) {
routeModel.showSpeedCamera(carContext, camera.distance, camera.tags.maxspeed)
}
}
}
/**
* Checks if a new route is needed based on the time since the last update.
*/
private fun checkRoute(currentDate: LocalDateTime, location: Location) {
val duration = Duration.between(currentDate, lastRouteDate)
val routeUpdate = routeModel.curRoute.summary.duration / 4
if (duration.abs().seconds > routeUpdate) {
lastRouteDate = currentDate
val destination = location(
routeModel.navState.destination.longitude,
routeModel.navState.destination.latitude
)
navigationViewModel.loadRoute(
carContext,
location,
destination,
surfaceRenderer.carOrientation
)
}
}
companion object {
// URI host for deep linking // URI host for deep linking
var uriHost: String = "navigation" var uriHost: String = "navigation"

View File

@@ -66,7 +66,7 @@ import java.time.LocalDateTime
class SurfaceRenderer( class SurfaceRenderer(
private var carContext: CarContext, private var carContext: CarContext,
private var lifecycle: Lifecycle, private var lifecycle: Lifecycle,
private var routeModel: RouteCarModel, //private var routeModel: RouteCarModel,
private var viewModelStoreOwner: ViewModelStoreOwner private var viewModelStoreOwner: ViewModelStoreOwner
) : DefaultLifecycleObserver { ) : DefaultLifecycleObserver {
@@ -362,13 +362,9 @@ class SurfaceRenderer(
* Calculates appropriate bearing, zoom, and maintains view style. * Calculates appropriate bearing, zoom, and maintains view style.
* Uses car orientation sensor if available, otherwise falls back to location bearing. * Uses car orientation sensor if available, otherwise falls back to location bearing.
*/ */
fun updateLocation(location: Location) { fun updateLocation(location: Location, streetName : String) {
synchronized(this) { synchronized(this) {
if (routeModel.isNavigating()) { street.value = streetName
street.value = routeModel.currentStep().street
} else {
street.value = ""
}
if (viewStyle == ViewStyle.VIEW || viewStyle == ViewStyle.PAN_VIEW) { if (viewStyle == ViewStyle.VIEW || viewStyle == ViewStyle.PAN_VIEW) {
val bearing = if (carOrientation == 999F) { val bearing = if (carOrientation == 999F) {
if (location.hasBearing()) { if (location.hasBearing()) {
@@ -402,8 +398,8 @@ class SurfaceRenderer(
/** /**
* Sets route data for active navigation and switches to VIEW mode. * Sets route data for active navigation and switches to VIEW mode.
*/ */
fun setRouteData() { fun setRouteData(routeGeoJson: String) {
routeData.value = routeModel.curRoute.routeGeoJson routeData.value = routeGeoJson
viewStyle = ViewStyle.VIEW viewStyle = ViewStyle.VIEW
} }
@@ -413,7 +409,7 @@ class SurfaceRenderer(
fun activateNavigationView() { fun activateNavigationView() {
viewStyle = ViewStyle.VIEW viewStyle = ViewStyle.VIEW
tilt = TILT tilt = TILT
updateLocation(lastLocation) updateLocation(lastLocation, "")
} }
/** /**
@@ -481,11 +477,11 @@ class SurfaceRenderer(
* Updates car location from the connected car system. * Updates car location from the connected car system.
* Only updates location when using OSRM routing engine. * Only updates location when using OSRM routing engine.
*/ */
fun updateCarLocation(location: Location) { fun updateCarLocation(location: Location, streetName: String) {
val repository = getSettingsRepository(carContext) val repository = getSettingsRepository(carContext)
val routingEngine = runBlocking { repository.routingEngineFlow.first() } val routingEngine = runBlocking { repository.routingEngineFlow.first() }
if (routingEngine == RouteEngine.OSRM.ordinal) { if (routingEngine == RouteEngine.OSRM.ordinal) {
updateLocation(location) updateLocation(location, streetName)
} }
} }

View File

@@ -1,7 +1,7 @@
package com.kouros.navigation.car.screen package com.kouros.navigation.car.screen
import androidx.car.app.navigation.model.Trip import androidx.car.app.navigation.model.Trip
import com.kouros.navigation.data.Place
/** A listener for navigation start and stop signals. */ /** A listener for navigation start and stop signals. */
@@ -14,4 +14,6 @@ interface NavigationListener {
/** Updates trip information. */ /** Updates trip information. */
fun updateTrip(trip: Trip) fun updateTrip(trip: Trip)
fun navigateToPlace(place: Place)
} }

View File

@@ -9,6 +9,7 @@ import androidx.car.app.Screen
import androidx.car.app.model.Action import androidx.car.app.model.Action
import androidx.car.app.model.Action.FLAG_IS_PERSISTENT import androidx.car.app.model.Action.FLAG_IS_PERSISTENT
import androidx.car.app.model.ActionStrip import androidx.car.app.model.ActionStrip
import androidx.car.app.model.CarColor
import androidx.car.app.model.CarIcon import androidx.car.app.model.CarIcon
import androidx.car.app.model.Distance import androidx.car.app.model.Distance
import androidx.car.app.model.Header import androidx.car.app.model.Header
@@ -21,7 +22,8 @@ import androidx.car.app.navigation.model.MapWithContentTemplate
import androidx.car.app.navigation.model.MessageInfo import androidx.car.app.navigation.model.MessageInfo
import androidx.car.app.navigation.model.NavigationTemplate import androidx.car.app.navigation.model.NavigationTemplate
import androidx.car.app.navigation.model.RoutingInfo import androidx.car.app.navigation.model.RoutingInfo
import androidx.car.app.navigation.model.Trip import androidx.car.app.navigation.model.Step
import androidx.car.app.navigation.model.TravelEstimate
import androidx.core.graphics.drawable.IconCompat import androidx.core.graphics.drawable.IconCompat
import androidx.lifecycle.DefaultLifecycleObserver import androidx.lifecycle.DefaultLifecycleObserver
import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.LifecycleOwner
@@ -30,29 +32,16 @@ import androidx.lifecycle.asLiveData
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import com.kouros.data.R import com.kouros.data.R
import com.kouros.navigation.car.SurfaceRenderer import com.kouros.navigation.car.SurfaceRenderer
import com.kouros.navigation.car.navigation.RouteCarModel
import com.kouros.navigation.car.screen.observers.NavigationObserverCallback
import com.kouros.navigation.car.screen.observers.NavigationObserverManager
import com.kouros.navigation.car.screen.settings.SettingsScreen import com.kouros.navigation.car.screen.settings.SettingsScreen
import com.kouros.navigation.data.Constants import com.kouros.navigation.data.Constants
import com.kouros.navigation.data.Constants.DESTINATION_ARRIVAL_DISTANCE
import com.kouros.navigation.data.Constants.TRAFFIC_UPDATE
import com.kouros.navigation.data.Place import com.kouros.navigation.data.Place
import com.kouros.navigation.data.ViewStyle import com.kouros.navigation.data.ViewStyle
import com.kouros.navigation.data.overpass.Elements
import com.kouros.navigation.model.NavigationViewModel import com.kouros.navigation.model.NavigationViewModel
import com.kouros.navigation.model.RouteModel
import com.kouros.navigation.utils.GeoUtils
import com.kouros.navigation.utils.formattedDistance
import com.kouros.navigation.utils.getSettingsRepository import com.kouros.navigation.utils.getSettingsRepository
import com.kouros.navigation.utils.getSettingsViewModel import com.kouros.navigation.utils.getSettingsViewModel
import com.kouros.navigation.utils.location import com.kouros.navigation.utils.location
import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.first
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import java.time.Duration
import java.time.LocalDateTime
import java.time.ZoneOffset
import kotlin.math.absoluteValue
/** /**
* Main screen for car navigation. * Main screen for car navigation.
@@ -61,10 +50,9 @@ import kotlin.math.absoluteValue
open class NavigationScreen( open class NavigationScreen(
carContext: CarContext, carContext: CarContext,
private var surfaceRenderer: SurfaceRenderer, private var surfaceRenderer: SurfaceRenderer,
private var routeModel: RouteCarModel,
private var listener: NavigationListener, private var listener: NavigationListener,
private val navigationViewModel: NavigationViewModel private val navigationViewModel: NavigationViewModel
) : Screen(carContext), NavigationObserverCallback { ) : Screen(carContext) {
var currentNavigationLocation = Location(LocationManager.GPS_PROVIDER) var currentNavigationLocation = Location(LocationManager.GPS_PROVIDER)
@@ -73,13 +61,6 @@ open class NavigationScreen(
var recentPlace: Place = Place() var recentPlace: Place = Place()
var navigationType = NavigationType.VIEW var navigationType = NavigationType.VIEW
var lastTrafficDate: LocalDateTime = LocalDateTime.MIN
var lastRouteDate: LocalDateTime = LocalDateTime.now()
var lastCameraSearch = 0
var speedCameras = listOf<Elements>()
val observerManager = NavigationObserverManager(navigationViewModel, this)
val repository = getSettingsRepository(carContext) val repository = getSettingsRepository(carContext)
val settingsViewModel = getSettingsViewModel(carContext) val settingsViewModel = getSettingsViewModel(carContext)
@@ -89,13 +70,31 @@ open class NavigationScreen(
private var tripSuggestion = false private var tripSuggestion = false
private var tripSuggestionCalled = false private var tripSuggestionCalled = false
private var routingEngine = 0
private var showTraffic = false;
private var arrivalTimer: CountDownTimer? = null private var arrivalTimer: CountDownTimer? = null
private var reRouteTimer: CountDownTimer? = null private var reRouteTimer: CountDownTimer? = null
private var isNavigating = false
private var isRerouting = false
private var hasArrived = false
private lateinit var destinations: MutableList<Destination>
private lateinit var stepRemainingDistance: Distance
private lateinit var destinationTravelEstimate: TravelEstimate
private lateinit var stepTravelEstimate: TravelEstimate
private var shouldShowNextStep = false
private var shouldShowLanes = false
private lateinit var steps: MutableList<Step>
var junctionImage: CarIcon? = null
var backGroundColor = CarColor.BLUE
val observerRecentPlaces = Observer<List<Place>> { newPlaces -> val observerRecentPlaces = Observer<List<Place>> { newPlaces ->
recentPlaces.addAll(newPlaces) recentPlaces.addAll(newPlaces)
if (newPlaces.isNotEmpty() && !tripSuggestionCalled) { if (newPlaces.isNotEmpty() && !tripSuggestionCalled) {
@@ -106,25 +105,17 @@ open class NavigationScreen(
} }
init { init {
observerManager.attachAllObservers(this)
lifecycleScope.launch { lifecycleScope.launch {
settingsViewModel.tripSuggestion.first() settingsViewModel.tripSuggestion.first()
settingsViewModel.routingEngine.first()
} }
repository.distanceModeFlow.asLiveData().observe(this, Observer { repository.distanceModeFlow.asLiveData().observe(this, Observer {
distanceMode = it distanceMode = it
}) })
repository.trafficFlow.asLiveData().observe(this, Observer {
showTraffic = it
})
repository.tripSuggestionFlow.asLiveData().observe(this, Observer { repository.tripSuggestionFlow.asLiveData().observe(this, Observer {
navigationViewModel.recentPlaces.observe(this, observerRecentPlaces) navigationViewModel.recentPlaces.observe(this, observerRecentPlaces)
tripSuggestion = it tripSuggestion = it
}) })
repository.routingEngineFlow.asLiveData().observe(this, Observer {
routingEngine = it
})
lifecycle.addObserver(object : DefaultLifecycleObserver { lifecycle.addObserver(object : DefaultLifecycleObserver {
override fun onStop(owner: LifecycleOwner) { override fun onStop(owner: LifecycleOwner) {
arrivalTimer?.cancel() arrivalTimer?.cancel()
@@ -164,12 +155,11 @@ open class NavigationScreen(
0, 0,
{ stopNavigation() }) { stopNavigation() })
) )
updateTrip()
return NavigationTemplate.Builder() return NavigationTemplate.Builder()
.setNavigationInfo( .setNavigationInfo(
getRoutingInfo() getRoutingInfo()
) )
.setDestinationTravelEstimate(routeModel.travelEstimateTrip(carContext, distanceMode)) .setDestinationTravelEstimate(destinationTravelEstimate)
.setActionStrip(actionStripBuilder.build()) .setActionStrip(actionStripBuilder.build())
.setMapActionStrip( .setMapActionStrip(
mapActionStrip( mapActionStrip(
@@ -185,7 +175,7 @@ open class NavigationScreen(
) )
}) })
) )
.setBackgroundColor(routeModel.backGroundColor()) .setBackgroundColor(backGroundColor)
.build() .build()
} }
@@ -194,7 +184,7 @@ open class NavigationScreen(
*/ */
private fun navigationViewTemplate(actionStripBuilder: ActionStrip.Builder): Template { private fun navigationViewTemplate(actionStripBuilder: ActionStrip.Builder): Template {
return NavigationTemplate.Builder() return NavigationTemplate.Builder()
.setBackgroundColor(routeModel.backGroundColor()) .setBackgroundColor(backGroundColor)
.setActionStrip(actionStripBuilder.build()) .setActionStrip(actionStripBuilder.build())
.setMapActionStrip( .setMapActionStrip(
mapActionStrip( mapActionStrip(
@@ -221,7 +211,7 @@ open class NavigationScreen(
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()
} }
@@ -235,8 +225,8 @@ open class NavigationScreen(
*/ */
fun navigationArrivedTemplate(actionStripBuilder: ActionStrip.Builder): NavigationTemplate { fun navigationArrivedTemplate(actionStripBuilder: ActionStrip.Builder): NavigationTemplate {
var street = "" var street = ""
if (routeModel.navState.destination.street != null) { if (destinations.first().address != null) {
street = routeModel.navState.destination.street!! street = destinations.first().address.toString()
} }
return NavigationTemplate.Builder() return NavigationTemplate.Builder()
.setNavigationInfo( .setNavigationInfo(
@@ -255,7 +245,7 @@ open class NavigationScreen(
) )
.build() .build()
) )
.setBackgroundColor(routeModel.backGroundColor()) // .setBackgroundColor(routeModel.backGroundColor())
.setActionStrip(actionStripBuilder.build()) .setActionStrip(actionStripBuilder.build())
.setMapActionStrip( .setMapActionStrip(
mapActionStrip( mapActionStrip(
@@ -299,7 +289,7 @@ open class NavigationScreen(
createNavigateAction(it) createNavigateAction(it)
) )
.setOnClickListener { .setOnClickListener {
navigateToPlace(it) listener.navigateToPlace(it)
} }
listBuilder.addItem( listBuilder.addItem(
row.build() row.build()
@@ -349,7 +339,7 @@ open class NavigationScreen(
return NavigationTemplate.Builder() return NavigationTemplate.Builder()
.setNavigationInfo(RoutingInfo.Builder().setLoading(true).build()) .setNavigationInfo(RoutingInfo.Builder().setLoading(true).build())
.setActionStrip(actionStripBuilder.build()) .setActionStrip(actionStripBuilder.build())
.setBackgroundColor(routeModel.backGroundColor()) // .setBackgroundColor(routeModel.backGroundColor())
.build() .build()
} }
@@ -357,16 +347,13 @@ open class NavigationScreen(
* Builds and returns RoutingInfo based on the current step and distance. * Builds and returns RoutingInfo based on the current step and distance.
*/ */
fun getRoutingInfo(): RoutingInfo { fun getRoutingInfo(): RoutingInfo {
val distance =
formattedDistance(distanceMode, routeModel.routeCalculator.leftStepDistance())
val routingInfo = RoutingInfo.Builder() val routingInfo = RoutingInfo.Builder()
.setCurrentStep( .setCurrentStep(
routeModel.currentStep(carContext = carContext), steps.first(),
Distance.create(distance.first, distance.second) stepRemainingDistance
) )
if (routeModel.navState.nextStep) { if (shouldShowNextStep && steps.size > 1) {
val nextStep = routeModel.nextStep(carContext = carContext) routingInfo.setNextStep(steps[1])
routingInfo.setNextStep(nextStep)
} }
return routingInfo.build() return routingInfo.build()
} }
@@ -391,7 +378,7 @@ open class NavigationScreen(
) )
) { obj: Any? -> ) { obj: Any? ->
if (obj != null) { if (obj != null) {
navigateToPlace(place) listener.navigateToPlace(place)
} }
} }
} }
@@ -475,44 +462,18 @@ open class NavigationScreen(
) )
// result see observer // result see observer
} else { } else {
navigateToPlace(place) listener.navigateToPlace(place)
} }
} }
} }
} }
/**
* Loads a route to the specified place and sets it as the destination.
*/
fun navigateToPlace(place: Place) {
val preview = navigationViewModel.previewRoute.value
navigationViewModel.previewRoute.value = ""
val location = location(place.longitude, place.latitude)
navigationViewModel.saveRecent(carContext, place)
currentNavigationLocation = location
if (preview.isNullOrEmpty()) {
navigationViewModel.loadRoute(
carContext,
surfaceRenderer.lastLocation,
location,
surfaceRenderer.carOrientation
)
} else {
routeModel.navState = routeModel.navState.copy(currentRouteIndex = place.routeIndex)
navigationViewModel.route.value = preview
}
routeModel.navState = routeModel.navState.copy(destination = place)
surfaceRenderer.activateNavigationView()
invalidate()
}
/** /**
* Stops navigation, resets state, and notifies listeners. * Stops navigation, resets state, and notifies listeners.
*/ */
fun stopNavigation() { fun stopNavigation() {
navigationType = NavigationType.VIEW navigationType = NavigationType.VIEW
listener.stopNavigation() listener.stopNavigation()
lastCameraSearch = 0
invalidate() invalidate()
} }
@@ -550,227 +511,40 @@ open class NavigationScreen(
) )
} }
/** /**
* Updates navigation state with the current location, checks for arrival, and traffic updates. * Updates navigation state with the current location, checks for arrival, and traffic updates.
*/ */
fun updateTrip(location: Location) { fun updateTrip(
val currentDate = LocalDateTime.now(ZoneOffset.UTC) isNavigating: Boolean,
checkRoute(currentDate, location) isRerouting: Boolean,
checkTraffic(currentDate, location) hasArrived: Boolean,
destinations: MutableList<Destination>,
updateSpeedCamera(location) steps: MutableList<Step>,
destinationTravelEstimate: TravelEstimate,
routeModel.updateLocation(location, navigationViewModel) stepTravelEstimate: TravelEstimate,
checkArrival() nextStepRemainingDistance: Distance,
shouldShowNextStep: Boolean,
invalidate() shouldShowLanes: Boolean,
} junctionImage: CarIcon?,
backGroundColor: CarColor
/**
* Checks if a new route is needed based on the time since the last update.
*/
private fun checkRoute(currentDate: LocalDateTime, location: Location) {
val duration = Duration.between(currentDate, lastRouteDate)
val routeUpdate = routeModel.curRoute.summary.duration / 4
if (duration.abs().seconds > routeUpdate) {
lastRouteDate = currentDate
val destination = location(
routeModel.navState.destination.longitude,
routeModel.navState.destination.latitude
)
navigationViewModel.loadRoute(
carContext,
location,
destination,
surfaceRenderer.carOrientation
)
}
}
/**
* Checks if traffic data needs to be updated based on the time since the last update.
*/
fun checkTraffic(current: LocalDateTime, location: Location) {
val duration = Duration.between(current, lastTrafficDate)
if (showTraffic && duration.abs().seconds > TRAFFIC_UPDATE) {
lastTrafficDate = current
navigationViewModel.loadTraffic(carContext, location, surfaceRenderer.carOrientation)
}
}
/**
* Checks for arrival
*/
fun checkArrival() {
if (routeModel.isArrival()
&& routeModel.routeCalculator.leftStepDistance() < DESTINATION_ARRIVAL_DISTANCE
) {
listener.stopNavigation()
settingsViewModel.onLastRouteChanged("")
routeModel.navState = routeModel.navState.copy(arrived = true)
surfaceRenderer.routeData.value = ""
navigationType = NavigationType.ARRIVAL
invalidate()
}
}
/**
* Updates the trip information and notifies the listener with a new Trip object.
* This includes destination name, address, travel estimate, and loading status.
*/
private fun updateTrip() {
if (routeModel.isNavigating() && !routeModel.navState.destination.name.isNullOrEmpty()) {
val tripBuilder = Trip.Builder()
val destination = Destination.Builder()
.setName(routeModel.navState.destination.name ?: "")
.setAddress(routeModel.navState.destination.street ?: "")
.build()
tripBuilder.addDestination(
destination,
routeModel.travelEstimateTrip(carContext, distanceMode)
)
tripBuilder.setLoading(false)
tripBuilder.setCurrentRoad(routeModel.currentStep.street)
tripBuilder.addStep(routeModel.currentStep(carContext), routeModel.travelEstimateStep(carContext, distanceMode ))
listener.updateTrip(tripBuilder.build())
}
}
/**
* Periodically requests speed camera information near the current location.
*/
private fun updateSpeedCamera(location: Location) {
if (lastCameraSearch++ % 100 == 0) {
navigationViewModel.getSpeedCameras(location, 5.0)
}
if (speedCameras.isNotEmpty()) {
updateDistance(location)
}
}
/**
* Updates distances to nearby speed cameras and checks for proximity alerts.
*/
private fun updateDistance(
location: Location,
) { ) {
val updatedCameras = mutableListOf<Elements>() this.isNavigating = isNavigating
speedCameras.forEach { this.isRerouting = isRerouting
val plLocation = this.hasArrived = hasArrived
location(longitude = it.lon, latitude = it.lat) this.destinations = destinations
val distance = plLocation.distanceTo(location) this.steps = steps
it.distance = distance.toDouble() stepRemainingDistance = nextStepRemainingDistance
updatedCameras.add(it) this.destinationTravelEstimate = destinationTravelEstimate
} this.stepTravelEstimate = stepTravelEstimate
val sortedList = updatedCameras.sortedWith(compareBy { it.distance }) this.shouldShowNextStep = shouldShowNextStep
val camera = sortedList.firstOrNull() ?: return this.shouldShowLanes = shouldShowLanes
val bearingRoute = surfaceRenderer.lastLocation.bearingTo(location) this.junctionImage = junctionImage
val bearingSpeedCamera = if (camera.tags.direction != null) { this.backGroundColor = backGroundColor
try {
camera.tags.direction!!.toFloat()
} catch (e: Exception) {
0F
}
} else {
location.bearingTo(location(camera.lon, camera.lat)).absoluteValue
}
if (camera.distance < 80) {
if ((bearingSpeedCamera - bearingRoute.absoluteValue).absoluteValue < 15.0) {
routeModel.showSpeedCamera(carContext, camera.distance, camera.tags.maxspeed)
}
}
}
/**
* Handles the received route string.
* Starts navigation and invalidates the screen.
*/
override fun onRouteReceived(route: String) {
if (route.isNotEmpty()) {
if (routeModel.isNavigating()) {
updateRoute(route)
} else {
prepareRoute(route)
}
invalidate()
}
}
/**
* Prepare route and start navigation
*/
private fun prepareRoute(route: String) {
routeModel.navState = routeModel.navState.copy(routingEngine = routingEngine)
navigationType = NavigationType.NAVIGATION navigationType = NavigationType.NAVIGATION
routeModel.startNavigation(route)
if (routeModel.hasLegs()) {
settingsViewModel.onLastRouteChanged(route)
}
surfaceRenderer.setRouteData()
listener.startNavigation()
}
/**
* Update route and traffic data
*/
private fun updateRoute(route: String) {
val newRouteModel = RouteModel()
newRouteModel.navState = routeModel.navState.copy(routingEngine = routingEngine)
navigationType = NavigationType.NAVIGATION
newRouteModel.startNavigation(route)
routeModel.curRoute.summary.trafficDelay = newRouteModel.curRoute.summary.trafficDelay
}
/**
* Checks if navigation is currently active.
*/
override fun isNavigating(): Boolean = routeModel.isNavigating()
/**
* Handles received traffic data and updates the surface renderer.
*/
override fun onTrafficReceived(traffic: Map<String, String>) {
if (traffic.isNotEmpty()) {
surfaceRenderer.setTrafficData(traffic)
}
}
/**
* Handles the received place search result.
* Navigates to the specified place.
*/
override fun onPlaceSearchResultReceived(place: Place) {
navigateToPlace(place)
}
/**
* Handles received speed camera data.
* Updates the surface renderer with the camera locations.
*/
override fun onSpeedCamerasReceived(cameras: List<Elements>) {
speedCameras = cameras
val coordinates = mutableListOf<List<Double>>()
cameras.forEach {
coordinates.add(listOf(it.lon, it.lat))
}
val speedData = GeoUtils.createPointCollection(coordinates, "radar")
surfaceRenderer.speedCamerasData.value = speedData
}
/**
* Handles received maximum speed data and updates the surface renderer.
*/
override fun onMaxSpeedReceived(speed: Int) {
surfaceRenderer.maxSpeed.value = speed
}
/**
* Invalidates the screen.
*/
override fun invalidateScreen() {
invalidate() invalidate()
} }
} }
/** /**

View File

@@ -347,6 +347,7 @@ class RoutePreviewScreen(
private fun onNavigate(index: Int) { private fun onNavigate(index: Int) {
destination.routeIndex = index destination.routeIndex = index
destination.route = navigationViewModel.previewRoute.value.toString()
setResult(destination) setResult(destination)
finish() finish()
} }

View File

@@ -1,5 +1,6 @@
package com.kouros.navigation.car.screen.observers package com.kouros.navigation.car.screen.observers
import com.kouros.navigation.car.NavigationSession
import com.kouros.navigation.model.NavigationViewModel import com.kouros.navigation.model.NavigationViewModel
/** /**
@@ -17,18 +18,14 @@ class NavigationObserverManager(
val speedCameraObserver = SpeedCameraObserver(callback) val speedCameraObserver = SpeedCameraObserver(callback)
val maxSpeedObserver = MaxSpeedObserver(callback) val maxSpeedObserver = MaxSpeedObserver(callback)
/** fun attachAllObservers(session: NavigationSession) {
* Attaches all observers to the ViewModel. viewModel.route.observe(session, routeObserver)
* Call this from NavigationScreen's init block or lifecycle method. viewModel.traffic.observe(session, trafficObserver)
*/ viewModel.placeLocation.observe(session, placeSearchObserver)
fun attachAllObservers(screen: androidx.car.app.Screen) { viewModel.speedCameras.observe(session, speedCameraObserver)
viewModel.route.observe(screen, routeObserver) viewModel.maxSpeed.observe(session, maxSpeedObserver)
viewModel.traffic.observe(screen, trafficObserver)
viewModel.placeLocation.observe(screen, placeSearchObserver)
viewModel.speedCameras.observe(screen, speedCameraObserver)
viewModel.maxSpeed.observe(screen, maxSpeedObserver)
} }
/** /**
* Detaches all observers from the ViewModel. * Detaches all observers from the ViewModel.
* Call this when the screen is being destroyed. * Call this when the screen is being destroyed.

View File

@@ -44,7 +44,8 @@ data class Place(
var distance: Float = 0F, var distance: Float = 0F,
//var avatar: Uri? = null, //var avatar: Uri? = null,
var lastDate: Long = 0, var lastDate: Long = 0,
var routeIndex: Int = 0 var routeIndex: Int = 0,
var route: String = "",
) )
data class ContactData( data class ContactData(