Auto Drive Simulation

This commit is contained in:
Dimitris
2026-03-05 13:42:51 +01:00
parent e582c1e0dc
commit 8c103a1f96
12 changed files with 145 additions and 50 deletions

7
README.md Normal file
View File

@@ -0,0 +1,7 @@
# README.md
## Introduction
## Simulation
adb shell dumpsys activity service com.kouros.navigation.car.NavigationCarAppService AUTO_DRIVE

View File

@@ -13,8 +13,8 @@ android {
applicationId = "com.kouros.navigation" applicationId = "com.kouros.navigation"
minSdk = 33 minSdk = 33
targetSdk = 36 targetSdk = 36
versionCode = 60 versionCode = 61
versionName = "0.2.0.60" versionName = "0.2.0.61"
base.archivesName = "navi-$versionName" base.archivesName = "navi-$versionName"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
} }

View File

@@ -1,6 +1,7 @@
package com.kouros.navigation.model package com.kouros.navigation.model
import android.content.Context import android.content.Context
import androidx.lifecycle.lifecycleScope
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.utils.location import com.kouros.navigation.utils.location
@@ -9,18 +10,20 @@ import io.ticofab.androidgpxparser.parser.domain.Gpx
import io.ticofab.androidgpxparser.parser.domain.TrackSegment import io.ticofab.androidgpxparser.parser.domain.TrackSegment
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import org.joda.time.DateTime import org.joda.time.DateTime
import kotlin.collections.forEach import kotlin.collections.forEach
var simulationJob: Job? = null
fun simulate(routeModel: RouteModel, mock: MockLocation) { fun simulate(routeModel: RouteModel, mock: MockLocation) {
CoroutineScope(Dispatchers.IO).launch { simulationJob?.cancel()
simulationJob = CoroutineScope(Dispatchers.IO).launch {
var lastLocation = location(0.0, 0.0) var lastLocation = location(0.0, 0.0)
for ((index, waypoint) in routeModel.curRoute.waypoints.withIndex()) { for ((index, waypoint) in routeModel.curRoute.waypoints.withIndex()) {
val curLocation = location(waypoint[0], waypoint[1]) val curLocation = location(waypoint[0], waypoint[1])
if (routeModel.isNavigating()) { if (routeModel.isNavigating()) {
val deviation = 0.0
if (index in 0..routeModel.curRoute.waypoints.size) { if (index in 0..routeModel.curRoute.waypoints.size) {
val bearing = lastLocation.bearingTo(curLocation) val bearing = lastLocation.bearingTo(curLocation)
mock.setMockLocation(waypoint[1], waypoint[0], bearing) mock.setMockLocation(waypoint[1], waypoint[0], bearing)

View File

@@ -13,36 +13,30 @@ import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge import androidx.activity.enableEdgeToEdge
import androidx.annotation.RequiresPermission import androidx.annotation.RequiresPermission
import androidx.compose.foundation.gestures.Orientation
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.BottomSheetScaffold import androidx.compose.material3.BottomSheetScaffold
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.FloatingActionButton import androidx.compose.material3.FloatingActionButton
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.SnackbarHost import androidx.compose.material3.SnackbarHost
import androidx.compose.material3.SnackbarHostState import androidx.compose.material3.SnackbarHostState
import androidx.compose.material3.Text
import androidx.compose.material3.rememberBottomSheetScaffoldState import androidx.compose.material3.rememberBottomSheetScaffoldState
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
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.mutableDoubleStateOf
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.lifecycle.MutableLiveData import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.Observer import androidx.lifecycle.Observer
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
@@ -52,7 +46,6 @@ import com.google.android.gms.location.FusedLocationProviderClient
import com.google.android.gms.location.LocationServices 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.data.Constants
import com.kouros.navigation.data.Constants.DESTINATION_ARRIVAL_DISTANCE import com.kouros.navigation.data.Constants.DESTINATION_ARRIVAL_DISTANCE
import com.kouros.navigation.data.Constants.TILT import com.kouros.navigation.data.Constants.TILT
import com.kouros.navigation.data.Constants.homeVogelhart import com.kouros.navigation.data.Constants.homeVogelhart
@@ -63,6 +56,7 @@ import com.kouros.navigation.model.RouteModel
import com.kouros.navigation.model.SimulationType import com.kouros.navigation.model.SimulationType
import com.kouros.navigation.model.gpx import com.kouros.navigation.model.gpx
import com.kouros.navigation.model.simulate import com.kouros.navigation.model.simulate
import com.kouros.navigation.model.simulationJob
import com.kouros.navigation.model.test import com.kouros.navigation.model.test
import com.kouros.navigation.model.testSingle import com.kouros.navigation.model.testSingle
import com.kouros.navigation.ui.app.AppViewModel import com.kouros.navigation.ui.app.AppViewModel
@@ -92,6 +86,7 @@ class MainActivity : ComponentActivity() {
val routeModel = RouteModel() val routeModel = RouteModel()
var tilt = TILT var tilt = TILT
val useMock = false val useMock = false
val type = SimulationType.SIMULATE val type = SimulationType.SIMULATE
val stepData: MutableLiveData<StepData> by lazy { val stepData: MutableLiveData<StepData> by lazy {
MutableLiveData() MutableLiveData()
@@ -108,11 +103,8 @@ class MainActivity : ComponentActivity() {
val routingEngine = runBlocking { repository.routingEngineFlow.first() } val routingEngine = runBlocking { repository.routingEngineFlow.first() }
routeModel.navState = routeModel.navState.copy(routingEngine = routingEngine) routeModel.navState = routeModel.navState.copy(routingEngine = routingEngine)
routeModel.startNavigation(newRoute) routeModel.startNavigation(newRoute)
if (routeModel.hasLegs()) {
getSettingsViewModel(applicationContext).onLastRouteChanged(newRoute)
}
routeData.value = routeModel.curRoute.routeGeoJson routeData.value = routeModel.curRoute.routeGeoJson
checkMock() // checkMock()
} }
} }
@@ -143,6 +135,13 @@ class MainActivity : ComponentActivity() {
private lateinit var mock: MockLocation private lateinit var mock: MockLocation
private var loadRecentPlaces = false private var loadRecentPlaces = false
override fun onDestroy() {
if (simulationJob != null) {
simulationJob?.cancel()
}
super.onDestroy()
}
@RequiresPermission(allOf = [Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.ACCESS_COARSE_LOCATION]) @RequiresPermission(allOf = [Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.ACCESS_COARSE_LOCATION])
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
@@ -371,6 +370,7 @@ class MainActivity : ComponentActivity() {
routeModel.stopNavigation() routeModel.stopNavigation()
getSettingsViewModel(applicationContext).onLastRouteChanged("") getSettingsViewModel(applicationContext).onLastRouteChanged("")
if (useMock) { if (useMock) {
simulationJob?.cancel()
mock.setMockLocation(latitude, longitude, 0F) mock.setMockLocation(latitude, longitude, 0F)
} }
routeData.value = "" routeData.value = ""

View File

@@ -4,7 +4,6 @@ import android.Manifest.permission
import android.content.Intent import android.content.Intent
import android.content.pm.PackageManager import android.content.pm.PackageManager
import android.location.Location import android.location.Location
import android.speech.tts.TextToSpeech
import android.util.Log import android.util.Log
import androidx.car.app.CarContext import androidx.car.app.CarContext
import androidx.car.app.Screen import androidx.car.app.Screen
@@ -21,9 +20,10 @@ import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelStore import androidx.lifecycle.ViewModelStore
import androidx.lifecycle.ViewModelStoreOwner import androidx.lifecycle.ViewModelStoreOwner
import androidx.lifecycle.asLiveData import androidx.lifecycle.asLiveData
import androidx.lifecycle.coroutineScope
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import com.kouros.navigation.car.navigation.NavigationUtils
import com.kouros.navigation.car.navigation.RouteCarModel import com.kouros.navigation.car.navigation.RouteCarModel
import com.kouros.navigation.car.navigation.Simulation
import com.kouros.navigation.car.screen.NavigationScreen import com.kouros.navigation.car.screen.NavigationScreen
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
@@ -42,8 +42,6 @@ import com.kouros.navigation.utils.NavigationUtils.getViewModel
import com.kouros.navigation.utils.getSettingsRepository import com.kouros.navigation.utils.getSettingsRepository
import kotlinx.coroutines.awaitCancellation import kotlinx.coroutines.awaitCancellation
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import org.maplibre.compose.expressions.dsl.step
import java.util.Locale
/** /**
@@ -76,6 +74,9 @@ class NavigationSession : Session(), NavigationScreen.Listener {
lateinit var textToSpeechManager: TextToSpeechManager lateinit var textToSpeechManager: TextToSpeechManager
var autoDriveEnabled = false
val simulation = Simulation()
/** /**
* Lifecycle observer for managing session lifecycle events. * Lifecycle observer for managing session lifecycle events.
* Cleans up resources when the session is destroyed. * Cleans up resources when the session is destroyed.
@@ -91,9 +92,9 @@ class NavigationSession : Session(), NavigationScreen.Listener {
if (::deviceLocationManager.isInitialized) { if (::deviceLocationManager.isInitialized) {
deviceLocationManager.stopLocationUpdates() deviceLocationManager.stopLocationUpdates()
} }
if (::textToSpeechManager.isInitialized) { if (::textToSpeechManager.isInitialized) {
textToSpeechManager.cleanup() textToSpeechManager.cleanup()
} }
Log.i(TAG, "NavigationSession destroyed") Log.i(TAG, "NavigationSession destroyed")
} }
} }
@@ -200,19 +201,19 @@ class NavigationSession : Session(), NavigationScreen.Listener {
* Initializes managers for rendering, sensors, and location. * Initializes managers for rendering, sensors, and location.
*/ */
private fun initializeManagers() { private fun initializeManagers() {
navigationManager = carContext.getCarService(NavigationManager::class.java) navigationManager = carContext.getCarService(NavigationManager::class.java)
navigationManager.setNavigationManagerCallback(object : NavigationManagerCallback { navigationManager.setNavigationManagerCallback(object : NavigationManagerCallback {
override fun onAutoDriveEnabled() { override fun onAutoDriveEnabled() {
// Called when the app should simulate navigation (e.g., for testing) // Called when the app should simulate navigation (e.g., for testing)
// Implement your simulation logic here // Implement your simulation logic here
Log.d("CarApp", "Auto Drive Enabled") autoDriveEnabled = true
} }
override fun onStopNavigation() { override fun onStopNavigation() {
// Called when the user stops navigation in the car screen // Called when the user stops navigation in the car screen
Log.d("CarApp", "Stop Navigation Requested")
// Stop turn-by-turn logic and clean up // Stop turn-by-turn logic and clean up
routeModel.stopNavigation()
autoDriveEnabled = false
} }
}) })
surfaceRenderer = SurfaceRenderer(carContext, lifecycle, routeModel, viewModelStoreOwner) surfaceRenderer = SurfaceRenderer(carContext, lifecycle, routeModel, viewModelStoreOwner)
@@ -341,6 +342,9 @@ class NavigationSession : Session(), NavigationScreen.Listener {
* 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) {
if (routeModel.navState.carConnection == CarConnection.CONNECTION_TYPE_PROJECTION ) {
surfaceRenderer.updateCarSpeed(location.speed)
}
updateBearing(location) updateBearing(location)
if (routeModel.isNavigating()) { if (routeModel.isNavigating()) {
handleNavigationLocation(location) handleNavigationLocation(location)
@@ -364,7 +368,7 @@ class NavigationSession : Session(), NavigationScreen.Listener {
*/ */
private fun handleNavigationLocation(location: Location) { private fun handleNavigationLocation(location: Location) {
if (guidanceAudio == 1) { if (guidanceAudio == 1) {
handleGuidanceAudio() handleGuidanceAudio()
} }
navigationScreen.updateTrip(location) navigationScreen.updateTrip(location)
if (routeModel.navState.arrived) return if (routeModel.navState.arrived) return
@@ -374,9 +378,11 @@ class NavigationSession : Session(), NavigationScreen.Listener {
distance > MAXIMAL_ROUTE_DEVIATION -> { distance > MAXIMAL_ROUTE_DEVIATION -> {
navigationScreen.calculateNewRoute(routeModel.navState.destination) navigationScreen.calculateNewRoute(routeModel.navState.destination)
} }
distance < MAXIMAL_SNAP_CORRECTION -> { distance < MAXIMAL_SNAP_CORRECTION -> {
surfaceRenderer.updateLocation(snappedLocation) surfaceRenderer.updateLocation(snappedLocation)
} }
else -> { else -> {
surfaceRenderer.updateLocation(location) surfaceRenderer.updateLocation(location)
} }
@@ -390,6 +396,10 @@ class NavigationSession : Session(), NavigationScreen.Listener {
override fun stopNavigation() { override fun stopNavigation() {
routeModel.stopNavigation() routeModel.stopNavigation()
navigationManager.navigationEnded() navigationManager.navigationEnded()
if (autoDriveEnabled) {
simulation.stopSimulation()
autoDriveEnabled = false
}
} }
/** /**
@@ -398,6 +408,13 @@ class NavigationSession : Session(), NavigationScreen.Listener {
*/ */
override fun startNavigation() { override fun startNavigation() {
navigationManager.navigationStarted() navigationManager.navigationStarted()
if (autoDriveEnabled) {
simulation.startSimulation(
routeModel, lifecycle.coroutineScope
) { location ->
updateLocation(location)
}
}
} }
override fun updateTrip(trip: Trip) { override fun updateTrip(trip: Trip) {
@@ -407,7 +424,7 @@ class NavigationSession : Session(), NavigationScreen.Listener {
/** /**
* Handle guidance audio * Handle guidance audio
* Called when user wants to hear the step by step instructions * Called when user wants to hear the step-by-step instructions
*/ */
private fun handleGuidanceAudio() { private fun handleGuidanceAudio() {
val currentStep = routeModel.route.currentStep() val currentStep = routeModel.route.currentStep()

View File

@@ -172,7 +172,7 @@ class RouteCarModel : RouteModel() {
} }
fun showSpeedCamera(carContext: CarContext, distance: Double, maxSpeed: String) { fun showSpeedCamera(carContext: CarContext, distance: Double, maxSpeed: String) {
carContext.getCarService<AppManager?>(AppManager::class.java) carContext.getCarService(AppManager::class.java)
.showAlert( .showAlert(
createAlert( createAlert(
carContext, carContext,

View File

@@ -0,0 +1,50 @@
package com.kouros.navigation.car.navigation
import android.location.Location
import android.location.LocationManager
import android.os.SystemClock
import androidx.lifecycle.LifecycleCoroutineScope
import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
class Simulation {
private var simulationJob: Job? = null
fun startSimulation(
routeModel: RouteCarModel,
lifecycleScope: LifecycleCoroutineScope,
updateLocation: (Location) -> Unit
) {
val points = routeModel.curRoute.waypoints
if (points.isEmpty()) return
simulationJob?.cancel()
var lastLocation = Location(LocationManager.FUSED_PROVIDER)
var curBearing = 0f
simulationJob = lifecycleScope.launch {
for (point in points) {
val fakeLocation = Location(LocationManager.FUSED_PROVIDER).apply {
latitude = point[1]
longitude = point[0]
bearing = curBearing
speedAccuracyMetersPerSecond = 1.0f // ~1 m/s
speed = 13.0f // ~50 km/h
time = System.currentTimeMillis()
elapsedRealtimeNanos = SystemClock.elapsedRealtimeNanos()
}
curBearing = lastLocation.bearingTo(fakeLocation)
// Update your app's state as if a real GPS update occurred
updateLocation(fakeLocation)
// Wait before moving to the next point (e.g., every 2 seconds)
delay(500)
lastLocation = fakeLocation
}
routeModel.stopNavigation()
}
}
fun stopSimulation() {
simulationJob?.cancel()
}
}

View File

@@ -36,9 +36,12 @@ import com.kouros.navigation.car.screen.observers.NavigationObserverCallback
import com.kouros.navigation.car.screen.observers.NavigationObserverManager 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.DESTINATION_ARRIVAL_DISTANCE 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.overpass.Elements import com.kouros.navigation.data.overpass.Elements
import com.kouros.navigation.model.NavigationViewModel import com.kouros.navigation.model.NavigationViewModel
import com.kouros.navigation.model.SettingsViewModel
import com.kouros.navigation.repository.SettingsRepository
import com.kouros.navigation.utils.GeoUtils import com.kouros.navigation.utils.GeoUtils
import com.kouros.navigation.utils.formattedDistance import com.kouros.navigation.utils.formattedDistance
import com.kouros.navigation.utils.getSettingsRepository import com.kouros.navigation.utils.getSettingsRepository
@@ -89,13 +92,16 @@ class NavigationScreen(
val repository = getSettingsRepository(carContext) val repository = getSettingsRepository(carContext)
val settingsViewModel = getSettingsViewModel(carContext)
var distanceMode = 0 var distanceMode = 0
init { init {
observerManager.attachAllObservers(this) observerManager.attachAllObservers(this)
lifecycleScope.launch { lifecycleScope.launch {
getSettingsViewModel(carContext).routingEngine.first() settingsViewModel.routingEngine.first()
getSettingsViewModel(carContext).recentPlaces.first() settingsViewModel.recentPlaces.first()
distanceMode = repository.distanceModeFlow.first() distanceMode = repository.distanceModeFlow.first()
} }
} }
@@ -111,7 +117,7 @@ class NavigationScreen(
navigationType = NavigationType.NAVIGATION navigationType = NavigationType.NAVIGATION
routeModel.startNavigation(route) routeModel.startNavigation(route)
if (routeModel.hasLegs()) { if (routeModel.hasLegs()) {
getSettingsViewModel(carContext).onLastRouteChanged(route) settingsViewModel.onLastRouteChanged(route)
} }
surfaceRenderer.setRouteData() surfaceRenderer.setRouteData()
listener.startNavigation() listener.startNavigation()
@@ -616,30 +622,37 @@ class NavigationScreen(
fun updateTrip(location: Location) { fun updateTrip(location: Location) {
val current = LocalDateTime.now(ZoneOffset.UTC) val current = LocalDateTime.now(ZoneOffset.UTC)
val duration = Duration.between(current, lastTrafficDate) val duration = Duration.between(current, lastTrafficDate)
if (duration.abs().seconds > 360) { if (duration.abs().seconds > TRAFFIC_UPDATE) {
lastTrafficDate = current lastTrafficDate = current
navigationViewModel.loadTraffic(carContext, location, surfaceRenderer.carOrientation) navigationViewModel.loadTraffic(carContext, location, surfaceRenderer.carOrientation)
} }
updateSpeedCamera(location) updateSpeedCamera(location)
with(routeModel) { with(routeModel) {
updateLocation( location, navigationViewModel) updateLocation( location, navigationViewModel)
if ((navState.maneuverType == Maneuver.TYPE_DESTINATION checkArrival()
|| navState.maneuverType == Maneuver.TYPE_DESTINATION_LEFT
|| navState.maneuverType == Maneuver.TYPE_DESTINATION_RIGHT
|| navState.maneuverType == Maneuver.TYPE_DESTINATION_STRAIGHT)
&& routeCalculator.leftStepDistance() < DESTINATION_ARRIVAL_DISTANCE
) {
stopNavigation()
getSettingsViewModel(carContext).onLastRouteChanged("")
navState = navState.copy(arrived = true)
surfaceRenderer.routeData.value = ""
navigationType = NavigationType.ARRIVAL
invalidate()
}
} }
invalidate() invalidate()
} }
/**
* Checks for arrival
*/
private fun RouteCarModel.checkArrival() {
if ((navState.maneuverType == Maneuver.TYPE_DESTINATION
|| navState.maneuverType == Maneuver.TYPE_DESTINATION_LEFT
|| navState.maneuverType == Maneuver.TYPE_DESTINATION_RIGHT
|| navState.maneuverType == Maneuver.TYPE_DESTINATION_STRAIGHT)
&& routeCalculator.leftStepDistance() < DESTINATION_ARRIVAL_DISTANCE
) {
listener.stopNavigation()
settingsViewModel.onLastRouteChanged("")
navState = navState.copy(arrived = true)
surfaceRenderer.routeData.value = ""
navigationType = NavigationType.ARRIVAL
invalidate()
}
}
/** /**
* Updates the trip information and notifies the listener with a new Trip object. * Updates the trip information and notifies the listener with a new Trip object.
* This includes destination name, address, travel estimate, and loading status. * This includes destination name, address, travel estimate, and loading status.

View File

@@ -3,6 +3,7 @@ plugins {
alias(libs.plugins.kotlin.compose) alias(libs.plugins.kotlin.compose)
kotlin("plugin.serialization") version "2.2.21" kotlin("plugin.serialization") version "2.2.21"
alias(libs.plugins.kotlin.kapt) alias(libs.plugins.kotlin.kapt)
//id("com.google.protobuf") version "0.9.6"
} }
android { android {

View File

@@ -125,12 +125,13 @@ object Constants {
const val MAXIMAL_ROUTE_DEVIATION = 80.0 const val MAXIMAL_ROUTE_DEVIATION = 80.0
const val DESTINATION_ARRIVAL_DISTANCE = 40.0 const val DESTINATION_ARRIVAL_DISTANCE = 20.0
const val NEAREST_LOCATION_DISTANCE = 10F const val NEAREST_LOCATION_DISTANCE = 10F
const val MAXIMUM_LOCATION_DISTANCE = 100000F const val MAXIMUM_LOCATION_DISTANCE = 100000F
const val TRAFFIC_UPDATE = 300
const val GMS_CAR_SPEED_PERMISSION = "com.google.android.gms.permission.CAR_SPEED" const val GMS_CAR_SPEED_PERMISSION = "com.google.android.gms.permission.CAR_SPEED"
const val AUTOMOTIVE_CAR_SPEED_PERMISSION = "android.car.permission.CAR_SPEED" const val AUTOMOTIVE_CAR_SPEED_PERMISSION = "android.car.permission.CAR_SPEED"

View File

@@ -21,6 +21,9 @@ private const val tomtomFields =
const val useAsset = false const val useAsset = false
const val useAssetTraffic = false
class TomTomRepository : NavigationRepository() { class TomTomRepository : NavigationRepository() {
override fun getRoute( override fun getRoute(
context: Context, context: Context,
@@ -63,7 +66,7 @@ class TomTomRepository : NavigationRepository() {
val repository = getSettingsRepository(context) val repository = getSettingsRepository(context)
val tomtomApiKey = runBlocking { repository.tomTomApiKeyFlow.first() } val tomtomApiKey = runBlocking { repository.tomTomApiKeyFlow.first() }
val bbox = calculateSquareRadius(location.latitude, location.longitude, 15.0) val bbox = calculateSquareRadius(location.latitude, location.longitude, 15.0)
return if (useAsset) { return if (useAssetTraffic) {
val trafficJson = context.resources.openRawResource(R.raw.tomtom_traffic) val trafficJson = context.resources.openRawResource(R.raw.tomtom_traffic)
trafficJson.bufferedReader().use { it.readText() } trafficJson.bufferedReader().use { it.readText() }
} else { } else {

View File

@@ -1,9 +1,9 @@
[versions] [versions]
agp = "9.0.1" agp = "9.1.0"
androidGpxParser = "2.3.1" androidGpxParser = "2.3.1"
androidSdkTurf = "6.0.1" androidSdkTurf = "6.0.1"
datastore = "1.2.0" datastore = "1.2.0"
gradle = "9.0.1" gradle = "9.1.0"
koinAndroid = "4.1.1" koinAndroid = "4.1.1"
koinAndroidxCompose = "4.1.1" koinAndroidxCompose = "4.1.1"
koinComposeViewmodel = "4.1.1" koinComposeViewmodel = "4.1.1"
@@ -21,7 +21,7 @@ material = "1.13.0"
carApp = "1.7.0" carApp = "1.7.0"
androidx-car = "1.7.0" androidx-car = "1.7.0"
materialIconsExtended = "1.7.8" materialIconsExtended = "1.7.8"
mockitoCore = "5.21.0" mockitoCore = "5.22.0"
mockitoKotlin = "6.2.3" mockitoKotlin = "6.2.3"
rules = "1.7.0" rules = "1.7.0"
runner = "1.7.0" runner = "1.7.0"