Auto Drive Simulation
This commit is contained in:
7
README.md
Normal file
7
README.md
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
# README.md
|
||||||
|
|
||||||
|
## Introduction
|
||||||
|
|
||||||
|
## Simulation
|
||||||
|
|
||||||
|
adb shell dumpsys activity service com.kouros.navigation.car.NavigationCarAppService AUTO_DRIVE
|
||||||
@@ -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"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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 = ""
|
||||||
|
|||||||
@@ -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.
|
||||||
@@ -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)
|
||||||
@@ -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()
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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,29 +622,36 @@ 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)
|
||||||
|
checkArrival()
|
||||||
|
}
|
||||||
|
invalidate()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks for arrival
|
||||||
|
*/
|
||||||
|
private fun RouteCarModel.checkArrival() {
|
||||||
if ((navState.maneuverType == Maneuver.TYPE_DESTINATION
|
if ((navState.maneuverType == Maneuver.TYPE_DESTINATION
|
||||||
|| navState.maneuverType == Maneuver.TYPE_DESTINATION_LEFT
|
|| navState.maneuverType == Maneuver.TYPE_DESTINATION_LEFT
|
||||||
|| navState.maneuverType == Maneuver.TYPE_DESTINATION_RIGHT
|
|| navState.maneuverType == Maneuver.TYPE_DESTINATION_RIGHT
|
||||||
|| navState.maneuverType == Maneuver.TYPE_DESTINATION_STRAIGHT)
|
|| navState.maneuverType == Maneuver.TYPE_DESTINATION_STRAIGHT)
|
||||||
&& routeCalculator.leftStepDistance() < DESTINATION_ARRIVAL_DISTANCE
|
&& routeCalculator.leftStepDistance() < DESTINATION_ARRIVAL_DISTANCE
|
||||||
) {
|
) {
|
||||||
stopNavigation()
|
listener.stopNavigation()
|
||||||
getSettingsViewModel(carContext).onLastRouteChanged("")
|
settingsViewModel.onLastRouteChanged("")
|
||||||
navState = navState.copy(arrived = true)
|
navState = navState.copy(arrived = true)
|
||||||
surfaceRenderer.routeData.value = ""
|
surfaceRenderer.routeData.value = ""
|
||||||
navigationType = NavigationType.ARRIVAL
|
navigationType = NavigationType.ARRIVAL
|
||||||
invalidate()
|
invalidate()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
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.
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
Reference in New Issue
Block a user