This commit is contained in:
Dimitris
2025-12-04 08:10:03 +01:00
parent cddb193260
commit 9f53db8e76
29 changed files with 590 additions and 623 deletions

View File

@@ -12,25 +12,25 @@ android {
applicationId = "com.kouros.navigation" applicationId = "com.kouros.navigation"
minSdk = 33 minSdk = 33
targetSdk = 36 targetSdk = 36
versionCode = 1 versionCode = 2
versionName = "0.1.3" versionName = "0.1.3.1"
setProperty("archivesBaseName", "navi-$versionName") setProperty("archivesBaseName", "navi-$versionName")
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
} }
signingConfigs { signingConfigs {
// getByName("debug") { getByName("debug") {
// keyAlias = "alias" keyAlias = "release"
// keyPassword = "alpha2000" keyPassword = "zeta67#gAe3aN3"
// storeFile = file("/home/kouros/work/keystore/keystoreRelease") storeFile = file("/home/kouros/work/keystore/keystoreRelease")
// storePassword = "alpha2000" storePassword = "zeta67#gAe3aN3"
// } }
create("release") { create("release") {
keyAlias = "release" keyAlias = "release"
keyPassword = "zeta67#g" keyPassword = "zeta67#gAe3aN3"
storeFile = file("/home/kouros/work/keystore/keystoreRelease") storeFile = file("/home/kouros/work/keystore/keystoreRelease")
storePassword = "zeta67#g" storePassword = "zeta67#gAe3aN3"
} }
} }
@@ -46,11 +46,11 @@ android {
// Specifies one flavor dimension. // Specifies one flavor dimension.
flavorDimensions += "version" flavorDimensions += "version"
productFlavors { productFlavors {
// create("demo") { create("demo") {
// dimension = "version" dimension = "version"
// applicationIdSuffix = ".demo" applicationIdSuffix = ".demo"
// versionNameSuffix = "-demo" versionNameSuffix = "-demo"
// } }
create("full") { create("full") {
dimension = "version" dimension = "version"
applicationIdSuffix = ".full" applicationIdSuffix = ".full"
@@ -87,11 +87,15 @@ dependencies {
implementation(project(":common:car")) implementation(project(":common:car"))
implementation(libs.play.services.location) implementation(libs.play.services.location)
implementation(libs.androidx.compose.runtime) implementation(libs.androidx.compose.runtime)
implementation(libs.androidx.navigation.compose)
implementation(libs.androidx.compose.ui.tooling.preview)
implementation(libs.androidx.compose.material3.window.size.class1)
testImplementation(libs.junit) testImplementation(libs.junit)
androidTestImplementation(libs.androidx.junit) androidTestImplementation(libs.androidx.junit)
androidTestImplementation(libs.androidx.espresso.core) androidTestImplementation(libs.androidx.espresso.core)
androidTestImplementation(platform(libs.androidx.compose.bom)) androidTestImplementation(platform(libs.androidx.compose.bom))
debugImplementation(libs.androidx.compose.ui.tooling)
} }

View File

@@ -6,6 +6,9 @@
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/> <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/> <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
<uses-permission android:name="android.permission.READ_CONTACTS"/>
<uses-permission android:name="android.permission.ACCESS_MOCK_LOCATION"
tools:ignore="MockLocation" />
<application <application
android:name="com.kouros.navigation.MainApplication" android:name="com.kouros.navigation.MainApplication"
@@ -23,7 +26,7 @@
android:resource="@xml/automotive_app_desc" /> android:resource="@xml/automotive_app_desc" />
<activity <activity
android:name="com.kouros.navigation.MainActivity" android:name="com.kouros.navigation.ui.MainActivity"
android:exported="true" android:exported="true"
android:theme="@style/Theme.Navigation"> android:theme="@style/Theme.Navigation">
<intent-filter> <intent-filter>

View File

@@ -24,5 +24,7 @@ class MainApplication : Application() {
companion object { companion object {
var appContext: Context? = null var appContext: Context? = null
private set private set
var useContacts = true
} }
} }

View File

@@ -0,0 +1,90 @@
package com.kouros.navigation.model
import android.location.Location
import android.location.LocationManager
import android.os.SystemClock
class MockLocation (private var locationManager: LocationManager) {
fun setMockLocation(latitude: Double, longitude: Double) {
try {
// Set mock location for all providers
setMockLocationForProvider(LocationManager.GPS_PROVIDER, latitude, longitude)
setMockLocationForProvider(LocationManager.NETWORK_PROVIDER, latitude, longitude)
} catch (e: NumberFormatException) {
} catch (e: SecurityException) {
} catch (e: Exception) {
e.printStackTrace()
}
}
private fun setMockLocationForProvider(provider: String, latitude: Double, longitude: Double) {
try {
// Check if provider exists
if (!locationManager.allProviders.contains(provider)) {
return
}
// Enable test provider
// For API 31+
locationManager.addTestProvider(
provider,
false, // requiresNetwork
false, // requiresSatellite
false, // requiresCell
false, // hasMonetaryCost
true, // supportsAltitude
true, // supportsSpeed
true, // supportsBearing
android.location.provider.ProviderProperties.POWER_USAGE_LOW,
android.location.provider.ProviderProperties.ACCURACY_FINE
)
locationManager.setTestProviderEnabled(provider, true)
// Create mock location
val mockLocation = Location(provider).apply {
this.latitude = latitude
this.longitude = longitude
this.altitude = 0.0
this.accuracy = 1.0f
this.speed = 15F
this.time = System.currentTimeMillis()
this.elapsedRealtimeNanos = SystemClock.elapsedRealtimeNanos()
this.bearingAccuracyDegrees = 0.0f
this.verticalAccuracyMeters = 0.0f
this.speedAccuracyMetersPerSecond = 0.0f
}
// Set the mock location
locationManager.setTestProviderLocation(provider, mockLocation)
} catch (e: SecurityException) {
throw e
} catch (e: IllegalArgumentException) {
// Provider already exists, just update location
try {
locationManager.setTestProviderEnabled(provider, true)
val mockLocation = Location(provider).apply {
this.latitude = latitude
this.longitude = longitude
this.altitude = 0.0
this.accuracy = 1.0f
this.time = System.currentTimeMillis()
this.elapsedRealtimeNanos = SystemClock.elapsedRealtimeNanos()
this.bearingAccuracyDegrees = 0.0f
this.verticalAccuracyMeters = 0.0f
this.speedAccuracyMetersPerSecond = 0.0f
}
locationManager.setTestProviderLocation(provider, mockLocation)
} catch (ex: Exception) {
ex.printStackTrace()
}
}
}
}

View File

@@ -1,31 +1,30 @@
package com.kouros.navigation package com.kouros.navigation.ui
import android.Manifest import android.Manifest
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.app.AppOpsManager
import android.content.pm.PackageManager
import android.location.Location import android.location.Location
import android.location.LocationManager import android.location.LocationManager
import android.os.Bundle import android.os.Bundle
import android.os.Process
import android.widget.Toast
import androidx.activity.ComponentActivity import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge import androidx.activity.enableEdgeToEdge
import androidx.compose.foundation.border
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Card import androidx.compose.material3.Card
import androidx.compose.material3.ExtendedFloatingActionButton import androidx.compose.material3.ExtendedFloatingActionButton
import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.ModalDrawerSheet import androidx.compose.material3.ModalDrawerSheet
import androidx.compose.material3.ModalNavigationDrawer import androidx.compose.material3.ModalNavigationDrawer
import androidx.compose.material3.NavigationDrawerItem import androidx.compose.material3.NavigationDrawerItem
import androidx.compose.material3.Scaffold import androidx.compose.material3.Scaffold
import androidx.compose.material3.SegmentedButtonDefaults.Icon import androidx.compose.material3.SegmentedButtonDefaults
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.Text
@@ -38,46 +37,40 @@ import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
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.text.style.TextOverflow
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
import androidx.lifecycle.MutableLiveData import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.Observer import androidx.lifecycle.Observer
import com.kouros.navigation.ui.theme.NavigationTheme
import com.google.accompanist.permissions.ExperimentalPermissionsApi import com.google.accompanist.permissions.ExperimentalPermissionsApi
import com.google.accompanist.permissions.rememberMultiplePermissionsState import com.google.accompanist.permissions.rememberMultiplePermissionsState
import com.google.android.gms.location.FusedLocationProviderClient
import com.google.android.gms.location.LocationServices
import com.kouros.android.cars.carappservice.R import com.kouros.android.cars.carappservice.R
import com.kouros.navigation.MainApplication
import com.kouros.navigation.car.BuildingLayer import com.kouros.navigation.car.BuildingLayer
import com.kouros.navigation.car.Puck
import com.kouros.navigation.car.PuckState import com.kouros.navigation.car.PuckState
import com.kouros.navigation.car.RouteLayer import com.kouros.navigation.car.RouteLayer
import com.kouros.navigation.data.Category
import com.kouros.navigation.data.Constants import com.kouros.navigation.data.Constants
import com.kouros.navigation.data.Constants.SHOW_THREED_BUILDING
import com.kouros.navigation.data.NavigationRepository import com.kouros.navigation.data.NavigationRepository
import com.kouros.navigation.data.StepData import com.kouros.navigation.data.StepData
import com.kouros.navigation.model.MockLocation
import com.kouros.navigation.model.RouteModel import com.kouros.navigation.model.RouteModel
import com.kouros.navigation.model.ViewModel import com.kouros.navigation.model.ViewModel
import com.kouros.navigation.utils.NavigationUtils.getBooleanKeyValue import com.kouros.navigation.ui.theme.NavigationTheme
import com.kouros.navigation.utils.NavigationUtils.snapLocation import com.kouros.navigation.utils.NavigationUtils
import com.kouros.navigation.utils.calculateZoom import com.kouros.navigation.utils.calculateZoom
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.async
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import org.koin.androidx.compose.koinViewModel
import org.maplibre.compose.camera.CameraPosition import org.maplibre.compose.camera.CameraPosition
import org.maplibre.compose.camera.rememberCameraState import org.maplibre.compose.camera.rememberCameraState
import org.maplibre.compose.location.DesiredAccuracy import org.maplibre.compose.location.DesiredAccuracy
import org.maplibre.compose.location.LocationPuck
import org.maplibre.compose.location.LocationPuckColors
import org.maplibre.compose.location.LocationPuckSizes
import org.maplibre.compose.location.LocationTrackingEffect import org.maplibre.compose.location.LocationTrackingEffect
import org.maplibre.compose.location.rememberDefaultLocationProvider import org.maplibre.compose.location.rememberDefaultLocationProvider
import org.maplibre.compose.location.rememberUserLocationState import org.maplibre.compose.location.rememberUserLocationState
@@ -87,10 +80,12 @@ import org.maplibre.compose.style.BaseStyle
import org.maplibre.spatialk.geojson.Position import org.maplibre.spatialk.geojson.Position
import kotlin.time.Duration.Companion.seconds import kotlin.time.Duration.Companion.seconds
class MainActivity : ComponentActivity() { class MainActivity : ComponentActivity() {
private val coroutineScope: CoroutineScope = CoroutineScope(Dispatchers.IO) private val LOCATION_PERMISSION_REQUEST_CODE = 1001
private val CONTACTS_PERMISSION_REQUEST_CODE = 1002
val routeData = MutableLiveData("") val routeData = MutableLiveData("")
val vieModel = ViewModel(NavigationRepository()) val vieModel = ViewModel(NavigationRepository())
@@ -107,6 +102,8 @@ class MainActivity : ComponentActivity() {
val observer = Observer<String> { newRoute -> val observer = Observer<String> { newRoute ->
routeModel.startNavigation(newRoute) routeModel.startNavigation(newRoute)
routeData.value = routeModel.route.routeGeoJson routeData.value = routeModel.route.routeGeoJson
println("Start simulating $newRoute")
simulate()
} }
val cameraPosition = MutableLiveData( val cameraPosition = MutableLiveData(
@@ -116,33 +113,57 @@ class MainActivity : ComponentActivity() {
) )
) )
var locationIndex = 0
var simulate = false private lateinit var locationManager: LocationManager
private lateinit var fusedLocationClient: FusedLocationProviderClient
private lateinit var mock: MockLocation
init { init {
vieModel.route.observe(this, observer) vieModel.route.observe(this, observer)
if (simulate) {
vieModel.loadRoute(
Constants.homeLocation,
Constants.home2Location
)
}
} }
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
checkLocationPermissions()
if (MainApplication.useContacts) {
checkContactsPermissions()
}
checkMockLocationEnabled()
enableEdgeToEdge() enableEdgeToEdge()
setContent { setContent {
if ((checkPermissionForLocation() && !MainApplication.useContacts)
|| (checkPermissionForLocation() && MainApplication.useContacts && checkPermissionForContact())) {
Content()
} else {
}
}
}
@Composable
fun Content() {
locationManager = getSystemService(LOCATION_SERVICE) as LocationManager
fusedLocationClient = LocationServices.getFusedLocationProviderClient(this)
mock = MockLocation(locationManager)
mock.setMockLocation(
Constants.homeLocation.latitude,
Constants.homeLocation.longitude
)
val scope = rememberCoroutineScope() val scope = rememberCoroutineScope()
val snackbarHostState = remember { SnackbarHostState() } val snackbarHostState = remember { SnackbarHostState() }
var simulationText by remember { mutableStateOf("Start Simulation") }
NavigationTheme { NavigationTheme {
ModalNavigationDrawer( ModalNavigationDrawer(
drawerContent = { drawerContent = {
ModalDrawerSheet { ModalDrawerSheet {
Text("Drawer title", modifier = Modifier.padding(16.dp)) Text("Drawer title", modifier = Modifier.Companion.padding(16.dp))
HorizontalDivider() HorizontalDivider()
NavigationDrawerItem( NavigationDrawerItem(
label = { Text(text = "Drawer Item") }, label = { Text(text = "Drawer Item") },
@@ -161,32 +182,35 @@ class MainActivity : ComponentActivity() {
floatingActionButton = { floatingActionButton = {
ExtendedFloatingActionButton( ExtendedFloatingActionButton(
text = { text = {
Text("Navigate") Text(simulationText)
}, },
icon = { Icon(true) }, icon = { SegmentedButtonDefaults.Icon(true) },
onClick = { onClick = {
scope.launch { scope.launch {
snackbarHostState.showSnackbar("Starte Navigation") snackbarHostState.showSnackbar("Starte Navigation")
} }
if (!routeModel.isNavigating() && lastLocation.latitude != 0.0) { if (!routeModel.isNavigating()) {
tilt = 60.0 tilt = 60.0
vieModel.loadRoute( vieModel.loadRoute(
applicationContext,
lastLocation, lastLocation,
Constants.home2Location Constants.home2Location
) )
simulationText = "Stop Simulation"
} else { } else {
tilt = 0.0 tilt = 0.0
routeModel.stopNavigation() routeModel.stopNavigation()
routeData.value = "" routeData.value = ""
println("stopNavigation")
simulationText = "Start Simulation"
} }
} }
) )
} }
) { innerPadding -> ) { innerPadding ->
Column(modifier = Modifier.padding(innerPadding)) { Column(modifier = Modifier.Companion.padding(innerPadding)) {
CheckPermission() //CheckPermission()
} Map()
} }
} }
} }
@@ -206,7 +230,7 @@ class MainActivity : ComponentActivity() {
listOf( listOf(
Manifest.permission.ACCESS_COARSE_LOCATION, Manifest.permission.ACCESS_COARSE_LOCATION,
Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.ACCESS_FINE_LOCATION,
Manifest.permission.READ_CONTACTS, //Manifest.permission.READ_CONTACTS,
), ),
) )
@@ -272,11 +296,7 @@ class MainActivity : ComponentActivity() {
) )
val userLocationState = rememberUserLocationState(locationProvider) val userLocationState = rememberUserLocationState(locationProvider)
val locationState = locationProvider.location.collectAsState() val locationState = locationProvider.location.collectAsState()
if (!simulate) {
updateLocation(locationState.value) updateLocation(locationState.value)
} else {
simulate()
}
if (locationState.value != null && lastLocation.latitude == 0.0) { if (locationState.value != null && lastLocation.latitude == 0.0) {
lastLocation.latitude = locationState.value?.position!!.latitude lastLocation.latitude = locationState.value?.position!!.latitude
lastLocation.longitude = locationState.value?.position!!.longitude lastLocation.longitude = locationState.value?.position!!.longitude
@@ -300,29 +320,33 @@ class MainActivity : ComponentActivity() {
baseStyle = BaseStyle.Uri(Constants.STYLE), baseStyle = BaseStyle.Uri(Constants.STYLE),
) { ) {
getBaseSource(id = "openmaptiles")?.let { tiles -> getBaseSource(id = "openmaptiles")?.let { tiles ->
if (!getBooleanKeyValue(context = applicationContext, SHOW_THREED_BUILDING)) { if (!NavigationUtils.getBooleanKeyValue(
context = applicationContext,
Constants.SHOW_THREED_BUILDING
)
) {
BuildingLayer(tiles) BuildingLayer(tiles)
} }
RouteLayer(route, "") RouteLayer(route, "")
} }
if (userLocationState.location != null) {
val location = Location(LocationManager.GPS_PROVIDER) val location = Location(LocationManager.GPS_PROVIDER)
if (userLocationState.location != null) {
location.longitude = userLocationState.location!!.position.longitude location.longitude = userLocationState.location!!.position.longitude
location.latitude = userLocationState.location!!.position.latitude location.latitude = userLocationState.location!!.position.latitude
PuckState(cameraState, userLocationState,) PuckState(cameraState, userLocationState)
} }
} }
LocationTrackingEffect( LocationTrackingEffect(
locationState = userLocationState, locationState = userLocationState,
) { ) {
//cameraState.updateFromLocation()
cameraState.animateTo( cameraState.animateTo(
finalPosition = CameraPosition( finalPosition = CameraPosition(
bearing = position!!.bearing, bearing = position!!.bearing,
zoom = position!!.zoom, zoom = position!!.zoom,
target = position!!.target, target = position!!.target,
tilt = tilt tilt = tilt,
padding = PaddingValues(start = 0.dp, top = 350.dp)
), ),
duration = 1.seconds duration = 1.seconds
) )
@@ -345,86 +369,107 @@ class MainActivity : ComponentActivity() {
} }
} }
private fun checkLocationPermissions() {
val permissions = mutableListOf(
Manifest.permission.ACCESS_FINE_LOCATION,
Manifest.permission.ACCESS_COARSE_LOCATION,
)
if (MainApplication.useContacts) {
permissions.add(Manifest.permission.READ_CONTACTS)
}
fun updateTestLocation(location: Location) { val permissionsToRequest = permissions.filter {
var snapedLocation = location ContextCompat.checkSelfPermission(this, it) != PackageManager.PERMISSION_GRANTED
var bearing: Double }
if (permissionsToRequest.isNotEmpty()) {
ActivityCompat.requestPermissions(
this,
permissionsToRequest.toTypedArray(),
LOCATION_PERMISSION_REQUEST_CODE
)
}
}
fun checkPermissionForLocation(): Boolean {
val permissions = mutableListOf(
Manifest.permission.ACCESS_FINE_LOCATION,
Manifest.permission.ACCESS_COARSE_LOCATION,
)
if (MainApplication.useContacts) {
permissions.add(Manifest.permission.READ_CONTACTS)
}
val permissionsToRequest = permissions.filter {
ContextCompat.checkSelfPermission(this, it) != PackageManager.PERMISSION_GRANTED
}
return permissionsToRequest.isEmpty()
}
fun checkPermissionForContact(): Boolean {
val permissions = arrayOf(
Manifest.permission.READ_CONTACTS,
)
val permissionsToRequest = permissions.filter {
ContextCompat.checkSelfPermission(this, it) != PackageManager.PERMISSION_GRANTED
}
return permissionsToRequest.isEmpty()
}
private fun checkContactsPermissions() {
val permissions = arrayOf(
Manifest.permission.READ_CONTACTS,
)
val permissionsToRequest = permissions.filter {
ContextCompat.checkSelfPermission(this, it) != PackageManager.PERMISSION_GRANTED
}
if (permissionsToRequest.isNotEmpty()) {
ActivityCompat.requestPermissions(
this,
permissionsToRequest.toTypedArray(),
CONTACTS_PERMISSION_REQUEST_CODE
)
}
}
private fun checkMockLocationEnabled() {
try {
// Check if mock location is enabled for this app
val appOpsManager =
getSystemService(APP_OPS_SERVICE) as AppOpsManager
val mode =
appOpsManager.unsafeCheckOp(
AppOpsManager.OPSTR_MOCK_LOCATION,
Process.myUid(),
packageName
)
if (mode != AppOpsManager.MODE_ALLOWED) {
Toast.makeText(
this,
"Please select this app as mock location app in Developer Options",
Toast.LENGTH_LONG
).show()
}
} catch (e: Exception) {
e.printStackTrace()
}
}
@OptIn(DelicateCoroutinesApi::class)
fun simulate() = GlobalScope.async {
for ((i, loc) in routeModel.route.waypoints.withIndex()) {
if (routeModel.isNavigating()) { if (routeModel.isNavigating()) {
snapedLocation = snapLocation(location, routeModel.route.maneuverLocations())
routeModel.updateLocation(location)
bearing = routeModel.currentStep().bearing
instruction.postValue(routeModel.currentStep())
} else {
bearing = cameraPosition.value!!.bearing
}
val zoom = calculateZoom(snapedLocation.speed.toDouble())
cameraPosition.postValue(
cameraPosition.value!!.copy(
bearing = bearing,
zoom = zoom,
target = Position(snapedLocation.longitude, snapedLocation.latitude)
),
)
}
fun simulate() {
if (routeModel.isNavigating() && locationIndex < routeModel.route.waypoints.size) {
coroutineScope.launch {
delay(
100
)
val loc = routeModel.route.waypoints[locationIndex]
lastLocation.longitude = loc[0] lastLocation.longitude = loc[0]
lastLocation.latitude = loc[1] lastLocation.latitude = loc[1]
updateTestLocation(lastLocation) if (i == 20) {
Thread.sleep(1_000) mock.setMockLocation(loc[1] + 0.03, loc[0])
locationIndex++ } else {
} mock.setMockLocation(loc[1], loc[0])
}
}
@Composable
fun PlaceList(viewModel: ViewModel = koinViewModel()) {
var categories: List<Category>
val places = viewModel.places.observeAsState().value ?: return
val countries = places.groupBy { it.category }.map {
Category(id = Constants.RECENT, name = it.key!!)
}
categories = countries
val context = LocalContext.current
LazyColumn {
items(categories.size) {
val place = categories[it]
Row(
modifier = Modifier
.fillMaxWidth()
.padding(8.dp)
.border(
2.dp,
color = MaterialTheme.colorScheme.outline,
shape = RoundedCornerShape(8.dp)
)
.clip(RoundedCornerShape(8.dp))
//.clickable {
//context.startActivity(place.toIntent(Intent.ACTION_VIEW))
//}
.padding(8.dp)
) {
Column {
Text(
text = place.name,
style = MaterialTheme.typography.labelLarge
)
Text(
text = place.name,
style = MaterialTheme.typography.bodyMedium,
overflow = TextOverflow.Ellipsis,
maxLines = 1
)
}
} }
delay(1000L) //
} }
} }
} }

View File

@@ -1,4 +1,4 @@
package com.kouros.navigation package com.kouros.navigation.ui
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
@@ -26,6 +26,7 @@ import com.google.accompanist.permissions.MultiplePermissionsState
import com.google.accompanist.permissions.PermissionState import com.google.accompanist.permissions.PermissionState
import com.google.accompanist.permissions.rememberMultiplePermissionsState import com.google.accompanist.permissions.rememberMultiplePermissionsState
/** /**
* Simple screen that manages the location permission state * Simple screen that manages the location permission state
*/ */

View File

@@ -1,4 +1,4 @@
package com.kouros.navigation package com.kouros.navigation.ui
import androidx.compose.foundation.clickable import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box

View File

@@ -51,6 +51,7 @@ dependencies {
implementation(libs.androidx.compose.ui) implementation(libs.androidx.compose.ui)
implementation(libs.androidx.material3) implementation(libs.androidx.material3)
implementation(libs.androidx.compose.ui.text) implementation(libs.androidx.compose.ui.text)
implementation(libs.play.services.location)
androidTestImplementation(libs.androidx.junit) androidTestImplementation(libs.androidx.junit)
testImplementation(libs.junit) testImplementation(libs.junit)
} }

View File

@@ -32,7 +32,7 @@
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/> <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/> <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
<uses-permission android:name="androidx.car.app.ACCESS_SURFACE" /> <uses-permission android:name="androidx.car.app.ACCESS_SURFACE" />
<uses-permission android:name="android.permission.READ_CONTACTS" /> <uses-permission android:name="android.permission.READ_CONTACTS"/>
<application android:requestLegacyExternalStorage="true"> <application android:requestLegacyExternalStorage="true">
<!-- <!--

View File

@@ -47,13 +47,10 @@ import org.maplibre.spatialk.geojson.Position
@Composable @Composable
fun cameraState( fun cameraState(
width: Int, padding : PaddingValues,
height: Int,
position: CameraPosition?, position: CameraPosition?,
tilt: Double, tilt: Double,
preview: Boolean
): CameraState { ): CameraState {
val padding = getPaddingValues(height, preview)
return rememberCameraState( return rememberCameraState(
firstPosition = firstPosition =
CameraPosition( CameraPosition(
@@ -117,20 +114,18 @@ fun BuildingLayer(tiles: Source) {
} }
@Composable @Composable
fun DrawImage(width: Int, height: Int, location: Location, street: String) { fun DrawImage(padding: PaddingValues, location: Location, width: Int, height: Int, street: String) {
NavigationImage(height, street) NavigationImage(padding, street)
Speed(width, height, location) Speed(width, height, location)
} }
@Composable @Composable
fun NavigationImage(height: Int, street: String) { fun NavigationImage(padding: PaddingValues, street: String) {
val vector = ImageVector.vectorResource(id = R.drawable.assistant_navigation_48px) val vector = ImageVector.vectorResource(id = R.drawable.assistant_navigation_48px)
val color = remember { NavigationColor } val color = remember { NavigationColor }
BadgedBox( BadgedBox(
modifier = Modifier modifier = Modifier
.padding( .padding(padding),
start = 0.dp, top = distanceFromTop(height).dp
),
badge = { badge = {
Badge() Badge()
} }
@@ -156,7 +151,7 @@ private fun Speed(
Box( Box(
modifier = Modifier modifier = Modifier
.padding( .padding(
start = width.dp- 300.dp, start = width.dp- 250.dp,
top = height.dp- 80.dp top = height.dp- 80.dp
), ),
contentAlignment = Alignment.Center contentAlignment = Alignment.Center
@@ -210,18 +205,17 @@ private fun Speed(
} }
} }
fun getPaddingValues(height: Int, preView: Boolean): PaddingValues { fun getPaddingValues(width: Int, height: Int, preView: Boolean): PaddingValues {
val padding = PaddingValues(start = 0.dp, top = distanceFromTop(height).dp)
val prePadding = PaddingValues(start = 150.dp, bottom = 0.dp)
return if (preView) { return if (preView) {
prePadding PaddingValues(start = 150.dp, bottom = 0.dp)
} else { } else {
padding // PaddingValues(start = width.dp, top = distanceFromTop(height).dp)
PaddingValues(start = 0.dp, top = distanceFromTop(height).dp)
} }
} }
fun distanceFromTop(height: Int): Int { fun distanceFromTop(height: Int): Int {
return height - percent(height, 20) return height - percent(height, 25)
} }
fun percent(maxValue: Int, value: Int): Int { fun percent(maxValue: Int, value: Int): Int {

View File

@@ -7,9 +7,7 @@ import android.content.Intent
import android.content.pm.PackageManager import android.content.pm.PackageManager
import android.location.Location import android.location.Location
import android.location.LocationManager import android.location.LocationManager
import android.os.Build
import android.util.Log import android.util.Log
import androidx.annotation.RequiresApi
import androidx.car.app.CarContext import androidx.car.app.CarContext
import androidx.car.app.Screen import androidx.car.app.Screen
import androidx.car.app.ScreenManager import androidx.car.app.ScreenManager
@@ -27,13 +25,7 @@ import com.kouros.navigation.car.screen.SearchScreen
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.ObjectBox
import com.kouros.navigation.utils.NavigationUtils.snapLocation import com.kouros.navigation.utils.NavigationUtils.snapLocation
import com.kouros.navigation.utils.location
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
class NavigationSession : Session(), NavigationScreen.Listener { class NavigationSession : Session(), NavigationScreen.Listener {
val uriScheme = "samples"; val uriScheme = "samples";
@@ -46,16 +38,10 @@ class NavigationSession : Session(), NavigationScreen.Listener {
lateinit var surfaceRenderer: SurfaceRenderer lateinit var surfaceRenderer: SurfaceRenderer
var locationIndex = 0
val simulate = false
var mLocationListener: LocationListenerCompat = LocationListenerCompat { location: Location? -> var mLocationListener: LocationListenerCompat = LocationListenerCompat { location: Location? ->
updateLocation(location) updateLocation(location!!)
} }
private val coroutineScope: CoroutineScope = CoroutineScope(Dispatchers.IO)
private val mLifeCycleObserver: LifecycleObserver = object : DefaultLifecycleObserver { private val mLifeCycleObserver: LifecycleObserver = object : DefaultLifecycleObserver {
override fun onCreate(owner: LifecycleOwner) { override fun onCreate(owner: LifecycleOwner) {
Log.i(TAG, "In onCreate()") Log.i(TAG, "In onCreate()")
@@ -161,6 +147,7 @@ class NavigationSession : Session(), NavigationScreen.Listener {
val locationManager = val locationManager =
carContext.getSystemService(Context.LOCATION_SERVICE) as LocationManager carContext.getSystemService(Context.LOCATION_SERVICE) as LocationManager
val location = locationManager.getLastKnownLocation(LocationManager.GPS_PROVIDER) val location = locationManager.getLastKnownLocation(LocationManager.GPS_PROVIDER)
if (location != null) {
updateLocation(location) updateLocation(location)
locationManager.requestLocationUpdates( locationManager.requestLocationUpdates(
LocationManager.GPS_PROVIDER, LocationManager.GPS_PROVIDER,
@@ -169,48 +156,15 @@ class NavigationSession : Session(), NavigationScreen.Listener {
mLocationListener mLocationListener
) )
} }
fun updateLocation(location: Location?) {
if (location != null) {
if (simulate) {
simulate(location)
} else {
update(location)
}
}
} }
fun simulate(location: Location?) { fun updateLocation(location: Location) {
if (routeModel.isNavigating() && locationIndex < routeModel.route.waypoints.size) {
coroutineScope.launch {
if (locationIndex >= routeModel.route.waypoints.size) {
return@launch
}
val loc = routeModel.route.waypoints[locationIndex]
val curLocation = Location(LocationManager.GPS_PROVIDER)
curLocation.longitude = loc[0]// + 0.00001 * locationIndex
curLocation.latitude = loc[1] //+ 0.00001 * locationIndex
curLocation.speed = 15F
update(curLocation)
locationIndex += 1
if (locationIndex > routeModel.route.waypoints.size) {
val locationManager =
carContext.getSystemService(Context.LOCATION_SERVICE) as LocationManager
locationManager.removeUpdates(mLocationListener)
}
}
} else {
update(location = location!!)
}
}
fun update(location: Location) {
if (routeModel.isNavigating()) { if (routeModel.isNavigating()) {
val snapedLocation = snapLocation(location, routeModel.route.maneuverLocations()) val snapedLocation = snapLocation(location, routeModel.route.maneuverLocations())
val distance = location.distanceTo(snapedLocation) val distance = location.distanceTo(snapedLocation)
if (distance > MAXIMAL_ROUTE_DEVIATION) { if (distance > MAXIMAL_ROUTE_DEVIATION) {
// navigationScreen.calculateNewRoute() navigationScreen.calculateNewRoute(routeModel.destination)
//return return
} }
routeModel.updateLocation(location) routeModel.updateLocation(location)
navigationScreen.updateTrip() navigationScreen.updateTrip()

View File

@@ -6,13 +6,13 @@ import android.hardware.display.DisplayManager
import android.hardware.display.VirtualDisplay import android.hardware.display.VirtualDisplay
import android.location.Location import android.location.Location
import android.location.LocationManager import android.location.LocationManager
import android.os.Build
import android.util.Log import android.util.Log
import androidx.annotation.RequiresApi
import androidx.car.app.AppManager import androidx.car.app.AppManager
import androidx.car.app.CarContext import androidx.car.app.CarContext
import androidx.car.app.SurfaceCallback import androidx.car.app.SurfaceCallback
import androidx.car.app.SurfaceContainer import androidx.car.app.SurfaceContainer
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
@@ -54,7 +54,7 @@ class SurfaceRenderer(
) )
) )
var visibleArea = MutableLiveData( var visibleArea = MutableLiveData(
Rect(0,0,0,0) Rect(0, 0, 0, 0)
) )
var stableArea = Rect() var stableArea = Rect()
@@ -162,16 +162,17 @@ class SurfaceRenderer(
@Composable @Composable
fun MapView() { fun MapView() {
val stateWidth = visibleArea.observeAsState()
val position: CameraPosition? by cameraPosition.observeAsState() val position: CameraPosition? by cameraPosition.observeAsState()
val route: String? by routeData.observeAsState() val route: String? by routeData.observeAsState()
val previewRoute: String? by previewRouteData.observeAsState() val previewRoute: String? by previewRouteData.observeAsState()
val cameraState = cameraState(width, height, position, tilt, preview) val paddingValues = getPaddingValues( width - stateWidth.value!!.width(), height, preview)
val cameraState = cameraState(paddingValues, position, tilt)
val baseStyle = BaseStyle.Uri(Constants.STYLE) val baseStyle = BaseStyle.Uri(Constants.STYLE)
// if (isSystemInDarkTheme()) BaseStyle.Uri(Constants.STYLE_DARK) else BaseStyle.Uri( if (isSystemInDarkTheme()) BaseStyle.Uri(Constants.STYLE_DARK) else BaseStyle.Uri(
// Constants.STYLE Constants.STYLE
// ) )
MaplibreMap( MaplibreMap(
cameraState = cameraState, cameraState = cameraState,
baseStyle = baseStyle, baseStyle = baseStyle,
@@ -184,11 +185,11 @@ class SurfaceRenderer(
} }
//Puck(cameraState, lastLocation) //Puck(cameraState, lastLocation)
} }
ShowPosition(cameraState, position) ShowPosition(cameraState, position, paddingValues)
} }
@Composable @Composable
fun ShowPosition(cameraState: CameraState, position: CameraPosition?) { fun ShowPosition(cameraState: CameraState, position: CameraPosition?, paddingValues: PaddingValues) {
val cameraDuration = duration(position) val cameraDuration = duration(position)
var bearing = position!!.bearing var bearing = position!!.bearing
var zoom = position.zoom var zoom = position.zoom
@@ -196,9 +197,9 @@ class SurfaceRenderer(
var localTilt = tilt var localTilt = tilt
if (!preview) { if (!preview) {
if (routeModel.isNavigating()) { if (routeModel.isNavigating()) {
DrawImage(width, height, lastLocation, "") DrawImage(paddingValues, lastLocation, width, height,"")
} else { } else {
DrawImage(width, height, lastLocation, "") DrawImage(paddingValues, lastLocation,width, height, "")
} }
} else { } else {
bearing = 0.0 bearing = 0.0
@@ -213,7 +214,7 @@ class SurfaceRenderer(
zoom = zoom, zoom = zoom,
target = target, target = target,
tilt = localTilt, tilt = localTilt,
padding = getPaddingValues(height, preview) padding = paddingValues
), ),
duration = cameraDuration duration = cameraDuration
) )
@@ -259,16 +260,21 @@ class SurfaceRenderer(
fun updateLocation(location: Location) { fun updateLocation(location: Location) {
synchronized(this) { synchronized(this) {
if (!preview) { if (!preview) {
var bearing = cameraPosition.value!!.bearing val bearing = if (routeModel.isNavigating()) {
if (routeModel.isNavigating()) { routeModel.currentStep().bearing
bearing = routeModel.currentStep().bearing } else {
lastLocation.bearingTo(location).toInt().toDouble().absoluteValue
} }
val zoom = if (!panView) { val zoom = if (!panView) {
calculateZoom(location.speed.toDouble()) calculateZoom(location.speed.toDouble())
} else { } else {
cameraPosition.value!!.zoom cameraPosition.value!!.zoom
} }
updateCameraPosition(bearing, zoom, Position(location.longitude, location.latitude)) updateCameraPosition(
bearing,
zoom,
Position(location.longitude, location.latitude)
)
lastBearing = cameraPosition.value!!.bearing lastBearing = cameraPosition.value!!.bearing
lastLocation = location lastLocation = location
} else { } else {
@@ -289,7 +295,7 @@ class SurfaceRenderer(
bearing = bearing, bearing = bearing,
zoom = zoom, zoom = zoom,
tilt = 0.0, tilt = 0.0,
padding = getPaddingValues(height, preview), padding = getPaddingValues(width-visibleArea.value!!.width(), height, preview),
target = target target = target
) )
) )
@@ -315,9 +321,11 @@ class SurfaceRenderer(
in 0.0..10.0 -> { in 0.0..10.0 -> {
return 13.0 return 13.0
} }
in 10.0..20.0 -> { in 10.0..20.0 -> {
return 11.0 return 11.0
} }
in 20.0..30.0 -> { in 20.0..30.0 -> {
return 10.0 return 10.0
} }

View File

@@ -1,77 +0,0 @@
package com.kouros.navigation.car.navigation
import android.location.Location
import android.os.Environment
import org.xml.sax.SAXException
import java.io.File
import java.io.FileInputStream
import java.io.FileNotFoundException
import java.io.IOException
import javax.xml.parsers.DocumentBuilderFactory
import javax.xml.parsers.ParserConfigurationException
class Gpx {
fun loadGPX() {
val path = Environment.getExternalStorageDirectory()
.toString() + "/Download/VogelHohen.gpx"
var info = ""
val gpxFile = File(path)
info = info + gpxFile.path + "\n\n"
val gpxList = decodeGPX(gpxFile)
print(gpxList)
}
private fun decodeGPX(file: File): MutableList<Location?> {
val list: MutableList<Location?> = ArrayList()
val documentBuilderFactory = DocumentBuilderFactory.newInstance()
try {
val documentBuilder = documentBuilderFactory.newDocumentBuilder()
val fileInputStream = FileInputStream(file)
val document = documentBuilder.parse(fileInputStream)
val elementRoot = document.documentElement
val nodelist_trkpt = elementRoot.getElementsByTagName("trkpt")
for (i in 0..<nodelist_trkpt.getLength()) {
val node = nodelist_trkpt.item(i)
val attributes = node.getAttributes()
val newLatitude = attributes.getNamedItem("lat").getTextContent()
val newLatitude_double = newLatitude.toDouble()
val newLongitude = attributes.getNamedItem("lon").getTextContent()
val newLongitude_double = newLongitude.toDouble()
val newLocationName = newLatitude + ":" + newLongitude
val newLocation = Location(newLocationName)
newLocation.setLatitude(newLatitude_double)
newLocation.setLongitude(newLongitude_double)
list.add(newLocation)
}
fileInputStream.close()
} catch (e: ParserConfigurationException) {
// TODO Auto-generated catch block
e.printStackTrace()
} catch (e: FileNotFoundException) {
// TODO Auto-generated catch block
e.printStackTrace()
} catch (e: SAXException) {
// TODO Auto-generated catch block
e.printStackTrace()
} catch (e: IOException) {
// TODO Auto-generated catch block
e.printStackTrace()
}
return list
}
}

View File

@@ -15,37 +15,6 @@ import com.kouros.android.cars.carappservice.R
class NavigationMessage (private var carContext: CarContext) { class NavigationMessage (private var carContext: CarContext) {
/** Returns a sample [Alert]. */
fun createAlert(): Alert {
val title: CarText = createCarText(R.string.navigation_alert_title)
val subtitle: CarText = createCarText(R.string.navigation_alert_subtitle)
val icon = CarIcon.ALERT
val yesAction: Action = createToastAction(
R.string.yes_action_title,
R.string.yes_action_toast_msg, Action.FLAG_PRIMARY
)
val noAction: Action = createToastAction(
R.string.no_action_title, R.string.no_action_toast_msg,
Action.FLAG_DEFAULT
)
return Alert.Builder( /* alertId: */0, title, /* durationMillis: */10000)
.setSubtitle(subtitle)
.setIcon(icon)
.addAction(yesAction)
.addAction(noAction).setCallback(object : AlertCallback {
override fun onCancel(reason: Int) {
if (reason == AlertCallback.REASON_TIMEOUT) {
showToast(R.string.alert_timeout_toast_msg)
}
}
override fun onDismiss() {
}
}).build()
}
private fun createToastAction( private fun createToastAction(
@StringRes titleRes: Int, @StringRes toastStringRes: Int, @StringRes titleRes: Int, @StringRes toastStringRes: Int,
flags: Int flags: Int

View File

@@ -214,7 +214,7 @@ class RouteCarModel() : RouteModel() {
.build() .build()
} }
private fun createString( fun createString(
text: String text: String
): SpannableString { ): SpannableString {
val spannableString = SpannableString(text) val spannableString = SpannableString(text)

View File

@@ -40,7 +40,7 @@ class DisplaySettings(private val carContext: CarContext) : Screen(carContext) {
.setSingleList(listBuilder.build()) .setSingleList(listBuilder.build())
.setHeader( .setHeader(
Header.Builder() Header.Builder()
.setTitle(carContext.getString(R.string.content_limits)) .setTitle(carContext.getString(R.string.display_settings))
.setStartHeaderAction(Action.BACK) .setStartHeaderAction(Action.BACK)
.build() .build()
) )

View File

@@ -17,7 +17,6 @@ import androidx.car.app.model.Header
import androidx.car.app.model.MessageTemplate import androidx.car.app.model.MessageTemplate
import androidx.car.app.model.Template import androidx.car.app.model.Template
import androidx.car.app.navigation.model.Maneuver import androidx.car.app.navigation.model.Maneuver
import androidx.car.app.navigation.model.MapController
import androidx.car.app.navigation.model.MapWithContentTemplate 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
@@ -86,18 +85,17 @@ class NavigationScreen(
override fun onGetTemplate(): Template { override fun onGetTemplate(): Template {
val actionStripBuilder = createActionStripBuilder() val actionStripBuilder = createActionStripBuilder()
return if (routeModel.isNavigating()) {
if (calculateNewRoute) { if (calculateNewRoute) {
getNavigationLoadingTemplate(actionStripBuilder) return navigationRerouteTemplate(actionStripBuilder)
} else {
getNavigationTemplate(actionStripBuilder)
} }
return if (routeModel.isNavigating()) {
navigationTemplate(actionStripBuilder)
} else { } else {
getNavigationEndTemplate(actionStripBuilder) navigationEndTemplate(actionStripBuilder)
} }
} }
private fun getNavigationTemplate(actionStripBuilder: ActionStrip.Builder): NavigationTemplate { private fun navigationTemplate(actionStripBuilder: ActionStrip.Builder): NavigationTemplate {
actionStripBuilder.addAction( actionStripBuilder.addAction(
stopAction() stopAction()
) )
@@ -112,7 +110,7 @@ class NavigationScreen(
.build() .build()
} }
private fun getNavigationEndTemplate(actionStripBuilder: ActionStrip.Builder): Template { private fun navigationEndTemplate(actionStripBuilder: ActionStrip.Builder): Template {
if (routeModel.isArrived()) { if (routeModel.isArrived()) {
val timer = object : CountDownTimer(10000, 10000) { val timer = object : CountDownTimer(10000, 10000) {
override fun onTick(millisUntilFinished: Long) {} override fun onTick(millisUntilFinished: Long) {}
@@ -122,6 +120,21 @@ class NavigationScreen(
} }
} }
timer.start() timer.start()
return navigationArrivedTemplate(actionStripBuilder)
} else {
return if (recentPlaceFound && recentPlaceActive) {
return recentPlaceTemplate()
} else {
NavigationTemplate.Builder()
.setBackgroundColor(CarColor.SECONDARY)
.setActionStrip(actionStripBuilder.build())
.setMapActionStrip(mapActionStripBuilder().build())
.build()
}
}
}
fun navigationArrivedTemplate(actionStripBuilder: ActionStrip.Builder): NavigationTemplate {
return NavigationTemplate.Builder() return NavigationTemplate.Builder()
.setNavigationInfo( .setNavigationInfo(
MessageInfo.Builder( MessageInfo.Builder(
@@ -143,20 +156,9 @@ class NavigationScreen(
.setActionStrip(actionStripBuilder.build()) .setActionStrip(actionStripBuilder.build())
.setMapActionStrip(mapActionStripBuilder().build()) .setMapActionStrip(mapActionStripBuilder().build())
.build() .build()
} else {
return if (recentPlaceFound && recentPlaceActive) {
return getRecentPlaceTemplate()
} else {
NavigationTemplate.Builder()
.setBackgroundColor(CarColor.SECONDARY)
.setActionStrip(actionStripBuilder.build())
.setMapActionStrip(mapActionStripBuilder().build())
.build()
}
}
} }
fun getRecentPlaceTemplate(): Template { fun recentPlaceTemplate(): Template {
val messageTemplate = MessageTemplate.Builder( val messageTemplate = MessageTemplate.Builder(
recentPlace.name + "\n" recentPlace.name + "\n"
+ recentPlace.city + recentPlace.city
@@ -181,11 +183,27 @@ class NavigationScreen(
return builder.build() return builder.build()
} }
fun getNavigationLoadingTemplate(actionStripBuilder: ActionStrip.Builder): NavigationTemplate { fun navigationRerouteTemplate(actionStripBuilder: ActionStrip.Builder): NavigationTemplate {
return NavigationTemplate.Builder() return NavigationTemplate.Builder()
.setNavigationInfo(RoutingInfo.Builder().setLoading(true).build()) .setNavigationInfo(
.setActionStrip(actionStripBuilder.build()) MessageInfo.Builder(
carContext.getString(R.string.new_route)
)
.setText(routeModel.destination.street.toString())
.setImage(
CarIcon.Builder(
IconCompat.createWithResource(
carContext,
R.drawable.navigation_48px
)
)
.build()
)
.build()
)
.setBackgroundColor(CarColor.SECONDARY) .setBackgroundColor(CarColor.SECONDARY)
.setActionStrip(actionStripBuilder.build())
.setMapActionStrip(mapActionStripBuilder().build())
.build() .build()
} }
@@ -271,7 +289,7 @@ class NavigationScreen(
) )
.setOnClickListener { .setOnClickListener {
val navigateTo = location(recentPlace.latitude, recentPlace.longitude) val navigateTo = location(recentPlace.latitude, recentPlace.longitude)
viewModel.loadRoute(surfaceRenderer.lastLocation, navigateTo) viewModel.loadRoute(carContext, surfaceRenderer.lastLocation, navigateTo)
routeModel.destination = recentPlace routeModel.destination = recentPlace
} }
.build() .build()
@@ -394,7 +412,7 @@ class NavigationScreen(
location.latitude = place.latitude location.latitude = place.latitude
location.longitude = place.longitude location.longitude = place.longitude
viewModel.saveRecent(place) viewModel.saveRecent(place)
viewModel.loadRoute(surfaceRenderer.lastLocation, location) viewModel.loadRoute(carContext, surfaceRenderer.lastLocation, location)
currentNavigationLocation = location currentNavigationLocation = location
routeModel.destination = place routeModel.destination = place
invalidate() invalidate()
@@ -408,24 +426,25 @@ class NavigationScreen(
invalidate() invalidate()
} }
fun calculateNewRoute() { fun calculateNewRoute(destination: Place) {
calculateNewRoute = true calculateNewRoute = true
stopNavigation()
invalidate() invalidate()
val mainThreadhandler = Handler(carContext.mainLooper) val mainThreadHandler = Handler(carContext.mainLooper)
mainThreadhandler.post { mainThreadHandler.post {
object : CountDownTimer(5000, 1000) { object : CountDownTimer(5000, 1000) {
override fun onTick(millisUntilFinished: Long) {} override fun onTick(millisUntilFinished: Long) {}
override fun onFinish() { override fun onFinish() {
calculateNewRoute = false calculateNewRoute = false
stopNavigation() reRoute(destination)
} }
}.start() }.start()
} }
} }
fun reRoute() { fun reRoute(destination: Place) {
NavigationMessage(carContext).createAlert() val dest = location(destination.latitude, destination.longitude)
viewModel.loadRoute(surfaceRenderer.lastLocation, currentNavigationLocation) viewModel.loadRoute(carContext, surfaceRenderer.lastLocation, dest)
} }
fun updateTrip() { fun updateTrip() {

View File

@@ -1,9 +1,7 @@
package com.kouros.navigation.car.screen package com.kouros.navigation.car.screen
import androidx.car.app.CarContext import androidx.car.app.CarContext
import androidx.car.app.CarToast
import androidx.car.app.Screen import androidx.car.app.Screen
import androidx.car.app.constraints.ConstraintManager
import androidx.car.app.model.Action import androidx.car.app.model.Action
import androidx.car.app.model.Header import androidx.car.app.model.Header
import androidx.car.app.model.ItemList import androidx.car.app.model.ItemList
@@ -11,48 +9,65 @@ import androidx.car.app.model.ListTemplate
import androidx.car.app.model.Row import androidx.car.app.model.Row
import androidx.car.app.model.Template import androidx.car.app.model.Template
import androidx.car.app.model.Toggle import androidx.car.app.model.Toggle
import androidx.lifecycle.DefaultLifecycleObserver
import com.kouros.android.cars.carappservice.R import com.kouros.android.cars.carappservice.R
import com.kouros.navigation.data.Constants.SHOW_THREED_BUILDING import com.kouros.navigation.data.Constants.AVOID_MOTORWAY
import com.kouros.navigation.data.Constants.AVOID_TOLLWAY
import com.kouros.navigation.utils.NavigationUtils.getBooleanKeyValue import com.kouros.navigation.utils.NavigationUtils.getBooleanKeyValue
import com.kouros.navigation.utils.NavigationUtils.setBooleanKeyValue import com.kouros.navigation.utils.NavigationUtils.setBooleanKeyValue
class NavigationSettings(private val carContext: CarContext) : Screen(carContext) { class NavigationSettings(private val carContext: CarContext) : Screen(carContext) {
private var motorWayToggleState = false
private var tollWayToggleState = false
init {
motorWayToggleState = getBooleanKeyValue(carContext, AVOID_MOTORWAY)
tollWayToggleState = getBooleanKeyValue(carContext, AVOID_MOTORWAY)
}
override fun onGetTemplate(): Template { override fun onGetTemplate(): Template {
val listBuilder = ItemList.Builder() val listBuilder = ItemList.Builder()
val highwayToggle: Toggle =
Toggle.Builder { checked: Boolean ->
if (checked) {
setBooleanKeyValue(carContext, true, AVOID_MOTORWAY)
} else {
setBooleanKeyValue(carContext, false, AVOID_MOTORWAY)
}
motorWayToggleState = !motorWayToggleState
}.setChecked(motorWayToggleState).build()
listBuilder.addItem(buildRowForTemplate(R.string.avoid_highways_row_title, highwayToggle))
// Tollway
listBuilder.addItem( val tollwayToggle: Toggle =
buildRowForTemplate( Toggle.Builder { checked: Boolean ->
R.string.list_limit, if (checked) {
ConstraintManager.CONTENT_LIMIT_TYPE_LIST setBooleanKeyValue(carContext, true, AVOID_TOLLWAY)
) } else {
) setBooleanKeyValue(carContext, false, AVOID_TOLLWAY)
}
tollWayToggleState = !tollWayToggleState
}.setChecked(tollWayToggleState).build()
listBuilder.addItem(buildRowForTemplate(R.string.avoid_tolls_row_title, tollwayToggle))
return ListTemplate.Builder() return ListTemplate.Builder()
.setSingleList(listBuilder.build()) .setSingleList(listBuilder.build())
.setHeader( .setHeader(
Header.Builder() Header.Builder()
.setTitle(carContext.getString(R.string.content_limits)) .setTitle(carContext.getString(R.string.display_settings))
.setStartHeaderAction(Action.BACK) .setStartHeaderAction(Action.BACK)
.build() .build()
) )
.build() .build()
} }
private fun buildRowForTemplate(title: Int, contentLimitType: Int): Row { private fun buildRowForTemplate(title: Int, toggle: Toggle): Row {
return Row.Builder() return Row.Builder()
.setTitle(carContext.getString(title)) .setTitle(carContext.getString(title))
.addText( .setToggle(toggle)
carContext
.getCarService(ConstraintManager::class.java)
.getContentLimit(contentLimitType).toString()
)
.build() .build()
} }
} }

View File

@@ -53,7 +53,7 @@ class PlaceListScreen(
init { init {
if (category == Constants.RECENT) { if (category == Constants.RECENT) {
viewModel.places.observe(this, observer) viewModel.places.observe(this, observer)
viewModel.loadPlaces(location) viewModel.loadPlaces(carContext, location)
} }
if (category == Constants.CONTACTS) { if (category == Constants.CONTACTS) {
viewModel.contactAddress.observe(this, observerAddress) viewModel.contactAddress.observe(this, observerAddress)

View File

@@ -26,7 +26,7 @@ class RequestPermissionScreen(
override fun onGetTemplate(): Template { override fun onGetTemplate(): Template {
val permissions: MutableList<String?> = ArrayList() val permissions: MutableList<String?> = ArrayList()
permissions.add(permission.ACCESS_FINE_LOCATION) permissions.add(permission.ACCESS_FINE_LOCATION)
permissions.add(permission.READ_CONTACTS) //permissions.add(permission.READ_CONTACTS)
val message = "This app needs access to location in order to show the map around you" val message = "This app needs access to location in order to show the map around you"

View File

@@ -77,7 +77,7 @@ class RoutePreviewScreen(
val location = Location(LocationManager.GPS_PROVIDER) val location = Location(LocationManager.GPS_PROVIDER)
location.latitude = destination.latitude location.latitude = destination.latitude
location.longitude = destination.longitude location.longitude = destination.longitude
vieModel.loadPreviewRoute(surfaceRenderer.lastLocation, location) vieModel.loadPreviewRoute(carContext,surfaceRenderer.lastLocation, location)
} }
override fun onGetTemplate(): Template { override fun onGetTemplate(): Template {

View File

@@ -41,8 +41,6 @@
<string name="sign_out_action_title" msgid="1653943000866713010">"Abmelden"</string> <string name="sign_out_action_title" msgid="1653943000866713010">"Abmelden"</string>
<string name="yes_action_title" msgid="5507096013762092189">"Ja"</string> <string name="yes_action_title" msgid="5507096013762092189">"Ja"</string>
<string name="no_action_title" msgid="1452124604210014010">"Nein"</string> <string name="no_action_title" msgid="1452124604210014010">"Nein"</string>
<string name="disable_all_rows" msgid="3003225080532928046">"Alle Zeilen deaktivieren"</string>
<string name="enable_all_rows" msgid="7274285275711872091">"Alle Zeilen aktivieren"</string>
<string name="zoomed_in_toast_msg" msgid="8915301497303842649">"Herangezoomt"</string> <string name="zoomed_in_toast_msg" msgid="8915301497303842649">"Herangezoomt"</string>
<string name="zoomed_out_toast_msg" msgid="6260981223227212493">"Herausgezoomt"</string> <string name="zoomed_out_toast_msg" msgid="6260981223227212493">"Herausgezoomt"</string>
<string name="triggered_toast_msg" msgid="3396166539208366382">"Ausgelöst"</string> <string name="triggered_toast_msg" msgid="3396166539208366382">"Ausgelöst"</string>
@@ -55,59 +53,13 @@
<string name="parked_toast_msg" msgid="2532422265890824446">"Aktion „Geparkt“"</string> <string name="parked_toast_msg" msgid="2532422265890824446">"Aktion „Geparkt“"</string>
<string name="more_toast_msg" msgid="5938288138225509885">"„Mehr“ angeklickt"</string> <string name="more_toast_msg" msgid="5938288138225509885">"„Mehr“ angeklickt"</string>
<string name="grant_location_permission_toast_msg" msgid="268046297444808010">"Standortermittlung erlauben, um aktuellen Standort anzuzeigen"</string> <string name="grant_location_permission_toast_msg" msgid="268046297444808010">"Standortermittlung erlauben, um aktuellen Standort anzuzeigen"</string>
<string name="sign_in_with_google_toast_msg" msgid="5720947549233124775">"Über Google anmelden beginnt hier"</string>
<string name="changes_selection_to_index_toast_msg_prefix" msgid="957766225794389167">"Auswahl auf Index geändert"</string>
<string name="yes_action_toast_msg" msgid="6216215197177241247">"Schaltfläche „Ja“ gedrückt."</string>
<string name="no_action_toast_msg" msgid="6165492423831023809">"Schaltfläche „Nein“ gedrückt."</string>
<string name="alert_timeout_toast_msg" msgid="5568380708832805374">"Zeitüberschreitung bei Benachrichtigung."</string>
<string name="first_row_title" msgid="219428344573165351">"Zeile mit großem Bild und langem Text langem Text langem Text langem Text langem Text"</string>
<string name="first_row_text" msgid="3887390298628338716">"Text Text Text"</string>
<string name="other_row_title_prefix" msgid="4702355788835253197">"Zeilentitel"</string>
<string name="other_row_text" msgid="7510279447493169945">"Zeilentext"</string>
<string name="navigate" msgid="2713090390373996139">"Navigieren"</string> <string name="navigate" msgid="2713090390373996139">"Navigieren"</string>
<string name="dial" msgid="3145707439707628311">"Wählen"</string> <string name="dial" msgid="3145707439707628311">"Wählen"</string>
<string name="address" msgid="9010635942573581302">"Adresse"</string> <string name="address" msgid="9010635942573581302">"Adresse"</string>
<string name="phone" msgid="2504766809811627577">"Smartphone"</string> <string name="phone" msgid="2504766809811627577">"Smartphone"</string>
<string name="fail_start_nav" msgid="6921321606009212189">"Fehler beim Starten der Navigation"</string> <string name="fail_start_nav" msgid="6921321606009212189">"Fehler beim Starten der Navigation"</string>
<string name="fail_start_dialer" msgid="1471602619507306261">"Fehler beim Starten des Telefons"</string> <string name="fail_start_dialer" msgid="1471602619507306261">"Fehler beim Starten des Telefons"</string>
<string name="car_hardware_demo_title" msgid="3679106197233262689">"Demo der Auto-Hardware"</string> <string name="display_settings" msgid="5726880972110281095">"Einstellungen für die Anzeige"</string>
<string name="car_hardware_info" msgid="1244783247616395012">"Informationen zur Auto-Hardware"</string>
<string name="model_info" msgid="494224423025683030">"Modellinformationen"</string>
<string name="manufacturer_unavailable" msgid="4978995415869838056">"Hersteller nicht verfügbar"</string>
<string name="model_unavailable" msgid="4075463010215406573">"Modell nicht verfügbar"</string>
<string name="year_unavailable" msgid="994338773299644607">"Jahr nicht verfügbar"</string>
<string name="energy_profile" msgid="81415433590192158">"Energieprofil"</string>
<string name="no_energy_profile_permission" msgid="4662285713731308888">"Keine Berechtigung für Energieprofil"</string>
<string name="fuel_types" msgid="6811375173343218212">"Kraftstofftypen"</string>
<string name="unavailable" msgid="3636401138255192934">"Nicht verfügbar"</string>
<string name="ev_connector_types" msgid="735458637011996125">"Elektrofahrzeug-Anschlusssteckertypen"</string>
<string name="example_title" msgid="530257630320010494">"Beispiel: %d"</string>
<string name="example_1_text" msgid="8631503055894800688">"Dieser Text ist "<annotation color="red">"rot"</annotation></string>
<string name="example_2_text" msgid="1359373957397219102">"Dieser Text ist "<annotation color="green">"grün"</annotation></string>
<string name="example_3_text" msgid="2409207170762049673">"Dieser Text ist "<annotation color="blue">"blau"</annotation></string>
<string name="example_4_text" msgid="9055989886645433000">"Dieser Text ist "<annotation color="yellow">"gelb"</annotation></string>
<string name="example_5_text" msgid="8828804968749423500">"Für diesen Text wird die Primärfarbe verwendet"</string>
<string name="example_6_text" msgid="7991523168517599600">"Dieser Text verwendet die Sekundärfarbe"</string>
<string name="color_demo" msgid="1822427636476178993">"Farbdemo"</string>
<string name="list_limit" msgid="3023536401535417286">"Listenbeschränkung"</string>
<string name="grid_limit" msgid="1350116012893549206">"Rasterbegrenzung"</string>
<string name="pane_limit" msgid="981518409516855230">"Bereichsbegrenzung"</string>
<string name="place_list_limit" msgid="6785181191763056582">"Limit für Ortsliste"</string>
<string name="route_list_limit" msgid="505793441615134116">"Limit für Routenliste"</string>
<string name="content_limits" msgid="5726880972110281095">"Beschränkungen für Inhalte"</string>
<string name="content_limits_demo_title" msgid="3207211638386727610">"Demo für „Beschränkungen für Inhalte“"</string>
<string name="finish_app_msg" msgid="8354334557053141891">"Dadurch wird die App geschlossen und beim nächsten Ausführen eine Berechtigungsanfrage eingeblendet"</string>
<string name="finish_app_title" msgid="9013328479438745074">"App-Demo beenden"</string>
<string name="finish_app_demo_title" msgid="8223819062053448384">"Beim nächsten Ausführen der Demo Berechtigungsbildschirm voranstellen"</string>
<string name="preseed_permission_app_title" msgid="182847662545676962">"Beim nächsten Ausführen Demo zu App-Berechtigungen voranstellen"</string>
<string name="preseed_permission_demo_title" msgid="5476541421753978071">"Beim nächsten Ausführen der Demo Berechtigungsbildschirm voranstellen"</string>
<string name="loading_demo_title" msgid="1086529475809143517">"Demo wird geladen"</string>
<string name="loading_demo_row_title" msgid="8933049915126088142">"Ladevorgang abgeschlossen!"</string>
<string name="pop_to_root" msgid="2078277386355064198">"Zu Stammverzeichnis wechseln"</string>
<string name="pop_to_marker" msgid="5007078308762725207">"Zur Markierung für verschiedene Demos wechseln"</string>
<string name="push_stack" msgid="2433062141810168976">"Weiter in Stack verschieben"</string>
<string name="pop_to_prefix" msgid="4288884615669751608">"Wechseln zu"</string>
<string name="pop_to_title" msgid="3924696281273379455">"Demo für „Wechseln zu“"</string>
<string name="package_not_found_error_msg" msgid="7525619456883627939">"Paket wurde nicht gefunden."</string> <string name="package_not_found_error_msg" msgid="7525619456883627939">"Paket wurde nicht gefunden."</string>
<string name="permissions_granted_msg" msgid="2348556088141992714">"Alle Berechtigungen wurden erteilt. Du kannst sie in den Einstellungen deaktivieren."</string> <string name="permissions_granted_msg" msgid="2348556088141992714">"Alle Berechtigungen wurden erteilt. Du kannst sie in den Einstellungen deaktivieren."</string>
<string name="needs_access_msg_prefix" msgid="2204136858798832382">"Die App benötigt Zugriff auf die folgenden Berechtigungen:\n"</string> <string name="needs_access_msg_prefix" msgid="2204136858798832382">"Die App benötigt Zugriff auf die folgenden Berechtigungen:\n"</string>
@@ -154,7 +106,8 @@
<string name="long_route" msgid="4737969235741057506">"Lange Route"</string> <string name="long_route" msgid="4737969235741057506">"Lange Route"</string>
<string name="continue_start_nav" msgid="6231797535084469163">"Weiter, um die Navigation zu starten"</string> <string name="continue_start_nav" msgid="6231797535084469163">"Weiter, um die Navigation zu starten"</string>
<string name="continue_route" msgid="5172258139245088080">"Weiter zur Route"</string> <string name="continue_route" msgid="5172258139245088080">"Weiter zur Route"</string>
<string name="routes_title" msgid="7799772149932075357">"Routen"</string> <string name="routes_title" msgid="7799772149932075357">"Route"</string>
<string name="new_route">Neue Route Berechnung</string>
<string name="place_list_nav_template_demo_title" msgid="8019588508812955290">"Demo der Navigationsvorlage für Ortslisten"</string> <string name="place_list_nav_template_demo_title" msgid="8019588508812955290">"Demo der Navigationsvorlage für Ortslisten"</string>
<string name="route_preview_template_demo_title" msgid="7878704357953167555">"Demo der Routenvorschauvorlage"</string> <string name="route_preview_template_demo_title" msgid="7878704357953167555">"Demo der Routenvorschauvorlage"</string>
<string name="notification_template_demo_title" msgid="5076051497316030274">"Demo der Benachrichtigungsvorlage"</string> <string name="notification_template_demo_title" msgid="5076051497316030274">"Demo der Benachrichtigungsvorlage"</string>
@@ -355,7 +308,7 @@
<string name="map_template_toggle_demo_title" msgid="6510798293640092611">"Kartenvorlage mit Ein-/Aus-Schaltflächen"</string> <string name="map_template_toggle_demo_title" msgid="6510798293640092611">"Kartenvorlage mit Ein-/Aus-Schaltflächen"</string>
<string name="avoid_tolls_row_title" msgid="5194057244144831024">"Mautstraßen vermeiden"</string> <string name="avoid_tolls_row_title" msgid="5194057244144831024">"Mautstraßen vermeiden"</string>
<string name="route_options_demo_title" msgid="4599699012716426514">"Routenoptionen"</string> <string name="route_options_demo_title" msgid="4599699012716426514">"Routenoptionen"</string>
<string name="avoid_highways_row_title" msgid="4711913426200490304">"Autobahnen vermeiden"</string> <string name="avoid_highways_row_title" msgid="4711913426200490304">"Autobahnen meiden"</string>
<string name="avoid_ferries_row_title" msgid="8232883866013711974">"Fähren vermeiden"</string> <string name="avoid_ferries_row_title" msgid="8232883866013711974">"Fähren vermeiden"</string>
<string name="map_demos_title" msgid="2169766615521476592">"Kartenbezogene Demos"</string> <string name="map_demos_title" msgid="2169766615521476592">"Kartenbezogene Demos"</string>
<string name="map_with_content_demo_title" msgid="1032610482145018739">"Demos von Karten mit Inhalten"</string> <string name="map_with_content_demo_title" msgid="1032610482145018739">"Demos von Karten mit Inhalten"</string>

View File

@@ -41,8 +41,6 @@
<string name="sign_out_action_title">Sign out</string> <string name="sign_out_action_title">Sign out</string>
<string name="yes_action_title">Yes</string> <string name="yes_action_title">Yes</string>
<string name="no_action_title">No</string> <string name="no_action_title">No</string>
<string name="disable_all_rows">Disable All Rows</string>
<string name="enable_all_rows">Enable All Rows</string>
<!-- Toast Messages --> <!-- Toast Messages -->
<string name="zoomed_in_toast_msg">Zoomed in</string> <string name="zoomed_in_toast_msg">Zoomed in</string>
@@ -57,17 +55,8 @@
<string name="parked_toast_msg">Parked action</string> <string name="parked_toast_msg">Parked action</string>
<string name="more_toast_msg">Clicked More</string> <string name="more_toast_msg">Clicked More</string>
<string name="grant_location_permission_toast_msg">Grant location Permission to see current location</string> <string name="grant_location_permission_toast_msg">Grant location Permission to see current location</string>
<string name="sign_in_with_google_toast_msg">Sign-in with Google starts here</string>
<string name="changes_selection_to_index_toast_msg_prefix">Changed selection to index</string> <string name="changes_selection_to_index_toast_msg_prefix">Changed selection to index</string>
<string name="yes_action_toast_msg">Yes button pressed!</string>
<string name="no_action_toast_msg">No button pressed!</string>
<string name="alert_timeout_toast_msg">Alert is timed out!</string>
<!-- Row text -->
<string name="first_row_title">Row with a large image and long text long text long text long text long text</string>
<string name="first_row_text">Text text text</string>
<string name="other_row_title_prefix">Row title </string>
<string name="other_row_text">Row text</string>
<!-- Place Details Screen --> <!-- Place Details Screen -->
<string name="navigate">Navigate</string> <string name="navigate">Navigate</string>
@@ -78,56 +67,8 @@
<!-- CarHardwareDemoScreen --> <!-- CarHardwareDemoScreen -->
<string name="fail_start_nav">Failure starting navigation</string> <string name="fail_start_nav">Failure starting navigation</string>
<string name="fail_start_dialer">Failure starting dialer</string> <string name="fail_start_dialer">Failure starting dialer</string>
<string name="car_hardware_demo_title">Car Hardware Demo</string>
<!-- CarHardwareInfoScreen --> <string name="display_settings">Display settings</string>
<string name="car_hardware_info">Car Hardware Information</string>
<string name="model_info">Model Information</string>
<string name="manufacturer_unavailable">Manufacturer unavailable</string>
<string name="model_unavailable">Model unavailable</string>
<string name="year_unavailable">Year unavailable</string>
<string name="energy_profile">Energy Profile</string>
<string name="no_energy_profile_permission">No Energy Profile Permission</string>
<string name="fuel_types">Fuel Types</string>
<string name="unavailable">Unavailable</string>
<string name="ev_connector_types">EV Connector Types</string>
<!-- ColorDemoScreen -->
<string name="example_title">Example %d</string>
<string name="example_1_text">This text has a <annotation color="red">red</annotation> color</string>
<string name="example_2_text">This text has a <annotation color="green">green</annotation> color</string>
<string name="example_3_text">This text has a <annotation color="blue">blue</annotation> color</string>
<string name="example_4_text">This text has a <annotation color="yellow">yellow</annotation> color</string>
<string name="example_5_text">This text uses the primary color</string>
<string name="example_6_text">This text uses the secondary color</string>
<string name="color_demo">Color Demo</string>
<!-- ContentLimitsDemoScreen -->
<string name="list_limit">List Limit</string>
<string name="grid_limit">Grid Limit</string>
<string name="pane_limit">Pane Limit</string>
<string name="place_list_limit">Place List Limit</string>
<string name="route_list_limit">Route List Limit</string>
<string name="content_limits">Content Limits</string>
<string name="content_limits_demo_title">Content Limits Demo</string>
<!-- FinishAppScreen -->
<string name="finish_app_msg">This will finish the app, and when you return it will pre-seed a permission screen</string>
<string name="finish_app_title">Finish App Demo</string>
<string name="finish_app_demo_title">Pre-seed the Permission Screen on next run Demo</string>
<string name="preseed_permission_app_title">Pre-seed permission App Demo</string>
<string name="preseed_permission_demo_title">Pre-seed the Permission Screen on next run Demo</string>
<!-- LoadingDemoScreen -->
<string name="loading_demo_title">Loading Demo</string>
<string name="loading_demo_row_title">Loading Complete!</string>
<!-- PopToDemoScreen -->
<string name="pop_to_root">Pop to root</string>
<string name="pop_to_marker">Pop to Misc Demo Marker</string>
<string name="push_stack">Push further in stack</string>
<string name="pop_to_prefix">Pop To </string>
<string name="pop_to_title">PopTo Demo</string>
<!-- RequestPermissionScreen --> <!-- RequestPermissionScreen -->
<string name="package_not_found_error_msg">Package Not found.</string> <string name="package_not_found_error_msg">Package Not found.</string>
@@ -193,7 +134,6 @@
<string name="take_520">Take 520</string> <string name="take_520">Take 520</string>
<string name="gas_station">Gas Station</string> <string name="gas_station">Gas Station</string>
<!-- RoutePreviewDemoScreen -->
<string name="short_route">Short route</string> <string name="short_route">Short route</string>
<string name="less_busy">Less busy</string> <string name="less_busy">Less busy</string>
<string name="hov_friendly">HOV friendly</string> <string name="hov_friendly">HOV friendly</string>
@@ -201,6 +141,7 @@
<string name="continue_start_nav">Continue to start navigation</string> <string name="continue_start_nav">Continue to start navigation</string>
<string name="continue_route">Continue to route</string> <string name="continue_route">Continue to route</string>
<string name="routes_title">Routes</string> <string name="routes_title">Routes</string>
<string name="new_route">New Route calculation</string>
<!-- NavigationDemosScreen --> <!-- NavigationDemosScreen -->
<string name="place_list_nav_template_demo_title">Place List Navigation Template Demo</string> <string name="place_list_nav_template_demo_title">Place List Navigation Template Demo</string>

View File

@@ -5,9 +5,9 @@ import android.location.LocationManager
import com.kouros.navigation.data.Constants.home2Location import com.kouros.navigation.data.Constants.home2Location
import com.kouros.navigation.data.Constants.homeLocation import com.kouros.navigation.data.Constants.homeLocation
import com.kouros.navigation.data.NavigationRepository import com.kouros.navigation.data.NavigationRepository
import com.kouros.navigation.data.SearchFilter
import com.kouros.navigation.model.RouteModel import com.kouros.navigation.model.RouteModel
import com.kouros.navigation.model.ViewModel import com.kouros.navigation.model.ViewModel
import org.junit.Assert.assertEquals
import org.junit.Test import org.junit.Test
/** /**
@@ -30,7 +30,7 @@ class ViewModelTest {
toLocation.latitude = home2Location.latitude toLocation.latitude = home2Location.latitude
toLocation.longitude = home2Location.longitude toLocation.longitude = home2Location.longitude
val route = repo.getRoute(fromLocation, toLocation) val route = repo.getRoute(fromLocation, toLocation, SearchFilter())
model.startNavigation(route) model.startNavigation(route)
println(route) println(route)
} }

View File

@@ -16,14 +16,19 @@
package com.kouros.navigation.data package com.kouros.navigation.data
import android.R
import android.location.Location import android.location.Location
import android.location.LocationManager import android.location.LocationManager
import android.net.Uri import android.net.Uri
import com.google.gson.GsonBuilder
import com.kouros.navigation.data.valhalla.Maneuvers
import com.kouros.navigation.data.valhalla.ValhallaJson
import com.kouros.navigation.utils.NavigationUtils.createGeoJson
import com.kouros.navigation.utils.NavigationUtils.decodePolyline
import io.objectbox.annotation.Entity import io.objectbox.annotation.Entity
import io.objectbox.annotation.Id import io.objectbox.annotation.Id
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import java.time.LocalDate import org.maplibre.geojson.Point
import java.util.Date
data class Category( data class Category(
val id: String, val id: String,
@@ -60,29 +65,6 @@ data class StepData (
var bearing: Double var bearing: Double
) )
val dataPlaces = listOf(
Place(
id = 0,
name = "Vogelhartstr. 17",
category = "Favorites",
latitude = 48.1857475,
longitude = 11.5793627,
postalCode = "80807",
city = "München",
street = "Vogelhartstr. 17"
),
Place(
id = 0,
name = "Hohenwaldeckstr. 27",
category = "Recent",
latitude = 48.1165005,
longitude = 11.594349,
postalCode = "81541",
city = "München",
street = "Hohenwaldeckstr. 27",
)
)
// GeoJSON data classes // GeoJSON data classes
@Serializable @Serializable
@@ -107,9 +89,41 @@ data class GeoJsonFeatureCollection(
data class Locations ( data class Locations (
var lat : Double, var lat : Double,
var lon : Double, var lon : Double,
var street : String = "" var street : String = "",
val search_filter: SearchFilter,
) )
@Serializable
data class SearchFilter(
var max_road_class: String = "",
var exclude_toll : Boolean = false
) {
class Builder {
private var avoidMotorway = false
private var avoidTollway = false
fun avoidMotorway (value: Boolean ) = apply {
avoidMotorway = value
}
fun avoidTollway (value: Boolean ) = apply {
avoidTollway = value
}
fun build(): SearchFilter {
val filter = SearchFilter()
if (avoidMotorway) {
filter.max_road_class = "trunk"
}
if (avoidTollway) {
filter.exclude_toll = true
}
return filter
}
}
}
@Serializable @Serializable
data class ValhallaLocation ( data class ValhallaLocation (
var locations: List<Locations>, var locations: List<Locations>,
@@ -149,6 +163,10 @@ object Constants {
const val SHOW_THREED_BUILDING = "Show3D" const val SHOW_THREED_BUILDING = "Show3D"
const val AVOID_MOTORWAY = "AvoidMotorway"
const val AVOID_TOLLWAY = "AvoidTollway"
const val NEXT_STEP_THRESHOLD = 100.0 const val NEXT_STEP_THRESHOLD = 100.0
const val MAXIMAL_SNAP_CORRECTION = 50.0 const val MAXIMAL_SNAP_CORRECTION = 50.0

View File

@@ -34,10 +34,15 @@ class NavigationRepository {
private val nominatimUrl = "https://nominatim.openstreetmap.org/" private val nominatimUrl = "https://nominatim.openstreetmap.org/"
fun getRoute(currentLocation : Location, location: Location): String { // Road classes from highest to lowest are:
// motorway, trunk, primary, secondary, tertiary, unclassified, residential, service_other.
// exclude_toll
fun getRoute(currentLocation: Location, location: Location, SearchFilter: SearchFilter): String {
SearchFilter
val vLocation = listOf( val vLocation = listOf(
Locations(lat = currentLocation.latitude, lon = currentLocation.longitude), Locations(lat = currentLocation.latitude, lon = currentLocation.longitude, search_filter = SearchFilter),
Locations(lat = location.latitude, lon = location.longitude) Locations(lat = location.latitude, lon = location.longitude, search_filter = SearchFilter)
) )
val valhallaLocation = ValhallaLocation( val valhallaLocation = ValhallaLocation(
locations = vLocation, locations = vLocation,
@@ -50,8 +55,8 @@ class NavigationRepository {
return fetchUrl(routeUrl + routeLocation, true) return fetchUrl(routeUrl + routeLocation, true)
} }
fun getRouteDistance(currentLocation : Location, location: Location): Double { fun getRouteDistance(currentLocation: Location, location: Location, searchFilter: SearchFilter): Double {
val route = getRoute(currentLocation, location) val route = getRoute(currentLocation, location, searchFilter)
val routeModel = RouteModel() val routeModel = RouteModel()
routeModel.startNavigation(route) routeModel.startNavigation(route)
return routeModel.route.distance return routeModel.route.distance
@@ -108,7 +113,6 @@ class NavigationRepository {
httpURLConnection.setRequestProperty("User-Agent", "email=nominatim@kouros-online.de"); httpURLConnection.setRequestProperty("User-Agent", "email=nominatim@kouros-online.de");
httpURLConnection.requestMethod = "GET" httpURLConnection.requestMethod = "GET"
val responseCode = httpURLConnection.responseCode val responseCode = httpURLConnection.responseCode
println(responseCode)
if (responseCode == HttpURLConnection.HTTP_OK) { if (responseCode == HttpURLConnection.HTTP_OK) {
val response = httpURLConnection.inputStream.bufferedReader() val response = httpURLConnection.inputStream.bufferedReader()
.use { it.readText() } // defaults to UTF-8 .use { it.readText() } // defaults to UTF-8

View File

@@ -10,6 +10,7 @@ import com.kouros.navigation.utils.location
import org.maplibre.geojson.FeatureCollection import org.maplibre.geojson.FeatureCollection
import org.maplibre.geojson.Point import org.maplibre.geojson.Point
import org.maplibre.turf.TurfMeasurement import org.maplibre.turf.TurfMeasurement
import org.maplibre.turf.TurfMisc
import kotlin.math.absoluteValue import kotlin.math.absoluteValue
import kotlin.math.roundToInt import kotlin.math.roundToInt
@@ -72,7 +73,7 @@ open class RouteModel() {
if (distance < nearestDistance) { if (distance < nearestDistance) {
nearestDistance = distance nearestDistance = distance
route.currentManeuverIndex = i route.currentManeuverIndex = i
calculateCurrentIndex(beginShapeIndex, endShapeIndex, location) calculateCurrentShapeIndex(beginShapeIndex, endShapeIndex, location)
} }
} }
} }
@@ -83,16 +84,17 @@ open class RouteModel() {
if (maneuver.streetNames != null && maneuver.streetNames.isNotEmpty()) { if (maneuver.streetNames != null && maneuver.streetNames.isNotEmpty()) {
text = maneuver.streetNames[0] text = maneuver.streetNames[0]
} }
// TODO: +1 check
val curLocation = location( val curLocation = location(
route.pointLocations[currentShapeIndex].latitude(), route.pointLocations[currentShapeIndex].latitude(),
route.pointLocations[currentShapeIndex].longitude() route.pointLocations[currentShapeIndex].longitude()
) )
if (currentShapeIndex < route.pointLocations.size) {
val nextLocation = location( val nextLocation = location(
route.pointLocations[currentShapeIndex + 1].latitude(), route.pointLocations[currentShapeIndex + 1].latitude(),
route.pointLocations[currentShapeIndex + 1].longitude() route.pointLocations[currentShapeIndex + 1].longitude()
) )
bearing = curLocation.bearingTo(nextLocation) bearing = curLocation.bearingTo(nextLocation).absoluteValue
}
val distanceStepLeft = leftStepDistance() * 1000 val distanceStepLeft = leftStepDistance() * 1000
when (distanceStepLeft) { when (distanceStepLeft) {
in 0.0..NEXT_STEP_THRESHOLD -> { in 0.0..NEXT_STEP_THRESHOLD -> {
@@ -108,7 +110,7 @@ open class RouteModel() {
} }
/** Calculates the index in a maneuver. */ /** Calculates the index in a maneuver. */
private fun calculateCurrentIndex( private fun calculateCurrentShapeIndex(
beginShapeIndex: Int, beginShapeIndex: Int,
endShapeIndex: Int, endShapeIndex: Int,
location: Location location: Location

View File

@@ -3,8 +3,6 @@ package com.kouros.navigation.model
import android.content.Context import android.content.Context
import android.location.Geocoder import android.location.Geocoder
import android.location.Location import android.location.Location
import android.os.Build
import androidx.annotation.RequiresApi
import androidx.lifecycle.MutableLiveData import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
@@ -14,13 +12,14 @@ import com.kouros.navigation.data.NavigationRepository
import com.kouros.navigation.data.ObjectBox.boxStore import com.kouros.navigation.data.ObjectBox.boxStore
import com.kouros.navigation.data.Place import com.kouros.navigation.data.Place
import com.kouros.navigation.data.Place_ import com.kouros.navigation.data.Place_
import com.kouros.navigation.data.SearchFilter
import com.kouros.navigation.data.nominatim.Search import com.kouros.navigation.data.nominatim.Search
import com.kouros.navigation.data.nominatim.SearchResult import com.kouros.navigation.data.nominatim.SearchResult
import com.kouros.navigation.utils.NavigationUtils
import com.kouros.navigation.utils.location import com.kouros.navigation.utils.location
import io.objectbox.kotlin.boxFor import io.objectbox.kotlin.boxFor
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import java.time.LocalDate
import java.time.LocalDateTime import java.time.LocalDateTime
import java.time.ZoneOffset import java.time.ZoneOffset
@@ -66,7 +65,6 @@ class ViewModel(private val repository: NavigationRepository) : ViewModel() {
//place.distance = distance.toFloat() //place.distance = distance.toFloat()
if (place.distance == 0F) { if (place.distance == 0F) {
recentPlace.postValue(place) recentPlace.postValue(place)
println("RecentPlace $recentPlace")
return@launch return@launch
} }
} }
@@ -75,7 +73,7 @@ class ViewModel(private val repository: NavigationRepository) : ViewModel() {
} }
} }
} }
fun loadPlaces(location: Location) { fun loadPlaces(context: Context, location: Location) {
viewModelScope.launch(Dispatchers.IO) { viewModelScope.launch(Dispatchers.IO) {
try { try {
val placeBox = boxStore.boxFor(Place::class) val placeBox = boxStore.boxFor(Place::class)
@@ -87,7 +85,7 @@ class ViewModel(private val repository: NavigationRepository) : ViewModel() {
query.close() query.close()
for (place in results) { for (place in results) {
val plLocation = location(place.latitude, place.longitude) val plLocation = location(place.latitude, place.longitude)
val distance = repository.getRouteDistance(location, plLocation) val distance = repository.getRouteDistance(location, plLocation, getSearchFilter(context))
place.distance = distance.toFloat() place.distance = distance.toFloat()
} }
places.postValue(results) places.postValue(results)
@@ -97,20 +95,20 @@ class ViewModel(private val repository: NavigationRepository) : ViewModel() {
} }
} }
fun loadRoute(currentLocation: Location, location: Location) { fun loadRoute(context: Context, currentLocation: Location, location: Location) {
viewModelScope.launch(Dispatchers.IO) { viewModelScope.launch(Dispatchers.IO) {
try { try {
route.postValue(repository.getRoute(currentLocation, location)) route.postValue(repository.getRoute(currentLocation, location, getSearchFilter(context)))
} catch (e: Exception) { } catch (e: Exception) {
e.printStackTrace() e.printStackTrace()
} }
} }
} }
fun loadPreviewRoute(currentLocation: Location, location: Location) { fun loadPreviewRoute(context: Context, currentLocation: Location, location: Location) {
viewModelScope.launch(Dispatchers.IO) { viewModelScope.launch(Dispatchers.IO) {
try { try {
previewRoute.postValue(repository.getRoute(currentLocation, location)) previewRoute.postValue(repository.getRoute(currentLocation, location, getSearchFilter(context)))
} catch (e: Exception) { } catch (e: Exception) {
e.printStackTrace() e.printStackTrace()
} }
@@ -133,7 +131,7 @@ class ViewModel(private val repository: NavigationRepository) : ViewModel() {
if (addressLines.size > 1) { if (addressLines.size > 1) {
val plLocation = location(adr.latitude, adr.longitude) val plLocation = location(adr.latitude, adr.longitude)
val distance = val distance =
repository.getRouteDistance(currentLocation, plLocation) repository.getRouteDistance(currentLocation, plLocation, getSearchFilter(context))
contactList.add( contactList.add(
Place( Place(
id = address.contactId, id = address.contactId,
@@ -179,7 +177,6 @@ class ViewModel(private val repository: NavigationRepository) : ViewModel() {
fun reverseAddress(location: Location ): String { fun reverseAddress(location: Location ): String {
val address = repository.reverseAddress(location) val address = repository.reverseAddress(location)
println(address)
val gson = GsonBuilder().serializeNulls().create() val gson = GsonBuilder().serializeNulls().create()
val place = gson.fromJson(address, SearchResult::class.java) val place = gson.fromJson(address, SearchResult::class.java)
println(place.address.road) println(place.address.road)
@@ -226,5 +223,21 @@ class ViewModel(private val repository: NavigationRepository) : ViewModel() {
} }
} }
} }
fun getSearchFilter(context: Context): SearchFilter {
val avoidMotorway = NavigationUtils.getBooleanKeyValue(
context = context,
Constants.AVOID_MOTORWAY
)
val avoidTollway = NavigationUtils.getBooleanKeyValue(
context = context,
Constants.AVOID_TOLLWAY
)
return SearchFilter.Builder()
.avoidMotorway(avoidMotorway)
.avoidTollway(avoidTollway)
.build()
}
} }

View File

@@ -30,6 +30,10 @@ runtime = "1.9.5"
accompanist = "0.37.3" accompanist = "0.37.3"
uiVersion = "1.9.5" uiVersion = "1.9.5"
uiText = "1.9.5" uiText = "1.9.5"
navigationCompose = "2.9.6"
uiToolingPreview = "1.9.5"
uiTooling = "1.9.5"
material3WindowSizeClass = "1.4.0"
[libraries] [libraries]
@@ -63,6 +67,10 @@ androidx-compose-runtime = { group = "androidx.compose.runtime", name = "runtime
accompanist-permissions = { module = "com.google.accompanist:accompanist-permissions", version.ref = "accompanist" } accompanist-permissions = { module = "com.google.accompanist:accompanist-permissions", version.ref = "accompanist" }
androidx-compose-ui = { group = "androidx.compose.ui", name = "ui", version.ref = "uiVersion" } androidx-compose-ui = { group = "androidx.compose.ui", name = "ui", version.ref = "uiVersion" }
androidx-compose-ui-text = { group = "androidx.compose.ui", name = "ui-text", version.ref = "uiText" } androidx-compose-ui-text = { group = "androidx.compose.ui", name = "ui-text", version.ref = "uiText" }
androidx-navigation-compose = { group = "androidx.navigation", name = "navigation-compose", version.ref = "navigationCompose" }
androidx-compose-ui-tooling-preview = { group = "androidx.compose.ui", name = "ui-tooling-preview", version.ref = "uiToolingPreview" }
androidx-compose-ui-tooling = { group = "androidx.compose.ui", name = "ui-tooling", version.ref = "uiTooling" }
androidx-compose-material3-window-size-class1 = { group = "androidx.compose.material3", name = "material3-window-size-class", version.ref = "material3WindowSizeClass" }
[plugins] [plugins]
android-application = { id = "com.android.application", version.ref = "agp" } android-application = { id = "com.android.application", version.ref = "agp" }