Diverse Änderungen
This commit is contained in:
@@ -14,8 +14,8 @@ android {
|
||||
applicationId = "com.kouros.navigation"
|
||||
minSdk = 33
|
||||
targetSdk = 36
|
||||
versionCode = 45
|
||||
versionName = "0.2.0.45"
|
||||
versionCode = 49
|
||||
versionName = "0.2.0.49"
|
||||
base.archivesName = "navi-$versionName"
|
||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||
}
|
||||
@@ -82,6 +82,7 @@ dependencies {
|
||||
implementation(libs.koin.androidx.compose)
|
||||
implementation(libs.maplibre.compose)
|
||||
//implementation(libs.maplibre.composeMaterial3)
|
||||
implementation(libs.androidx.app.projected)
|
||||
implementation(libs.accompanist.permissions)
|
||||
|
||||
implementation(project(":common:data"))
|
||||
@@ -97,7 +98,7 @@ dependencies {
|
||||
implementation("com.github.ticofab:android-gpx-parser:2.3.1")
|
||||
implementation(libs.androidx.navigation.compose)
|
||||
implementation(libs.kotlinx.serialization.json)
|
||||
implementation(libs.androidx.foundation.layout)
|
||||
implementation(libs.androidx.compose.foundation.layout)
|
||||
testImplementation(libs.junit)
|
||||
androidTestImplementation(libs.androidx.junit)
|
||||
androidTestImplementation(libs.androidx.espresso.core)
|
||||
|
||||
@@ -7,11 +7,16 @@ import android.os.SystemClock
|
||||
class MockLocation (private var locationManager: LocationManager) {
|
||||
|
||||
var curSpeed = 0F
|
||||
fun setMockLocation(latitude: Double, longitude: Double) {
|
||||
fun setMockLocation(latitude: Double, longitude: Double, bearing : Float) {
|
||||
try {
|
||||
// Set mock location for all providers
|
||||
setMockLocationForProvider(LocationManager.GPS_PROVIDER, latitude, longitude)
|
||||
setMockLocationForProvider(LocationManager.NETWORK_PROVIDER, latitude, longitude)
|
||||
setMockLocationForProvider(LocationManager.GPS_PROVIDER, latitude, longitude, bearing)
|
||||
setMockLocationForProvider(
|
||||
LocationManager.NETWORK_PROVIDER,
|
||||
latitude,
|
||||
longitude,
|
||||
bearing
|
||||
)
|
||||
} catch (e: NumberFormatException) {
|
||||
} catch (e: SecurityException) {
|
||||
} catch (e: Exception) {
|
||||
@@ -19,7 +24,12 @@ class MockLocation (private var locationManager: LocationManager) {
|
||||
}
|
||||
}
|
||||
|
||||
private fun setMockLocationForProvider(provider: String, latitude: Double, longitude: Double) {
|
||||
private fun setMockLocationForProvider(
|
||||
provider: String,
|
||||
latitude: Double,
|
||||
longitude: Double,
|
||||
bearing: Float
|
||||
) {
|
||||
try {
|
||||
// Check if provider exists
|
||||
if (!locationManager.allProviders.contains(provider)) {
|
||||
@@ -52,10 +62,10 @@ class MockLocation (private var locationManager: LocationManager) {
|
||||
this.speed = 0f
|
||||
this.time = System.currentTimeMillis()
|
||||
this.elapsedRealtimeNanos = SystemClock.elapsedRealtimeNanos()
|
||||
|
||||
this.bearingAccuracyDegrees = 0.0f
|
||||
this.verticalAccuracyMeters = 0.0f
|
||||
this.speedAccuracyMetersPerSecond = 0.0f
|
||||
this.bearing = bearing
|
||||
}
|
||||
// Set the mock location
|
||||
locationManager.setTestProviderLocation(provider, mockLocation)
|
||||
@@ -79,6 +89,7 @@ class MockLocation (private var locationManager: LocationManager) {
|
||||
this.bearingAccuracyDegrees = 0.0f
|
||||
this.verticalAccuracyMeters = 0.0f
|
||||
this.speedAccuracyMetersPerSecond = 0.0f
|
||||
this.bearing = bearing
|
||||
}
|
||||
locationManager.setTestProviderLocation(provider, mockLocation)
|
||||
} catch (ex: Exception) {
|
||||
|
||||
@@ -77,19 +77,16 @@ import org.maplibre.compose.location.DesiredAccuracy
|
||||
import org.maplibre.compose.location.Location
|
||||
import org.maplibre.compose.location.rememberDefaultLocationProvider
|
||||
import org.maplibre.compose.location.rememberUserLocationState
|
||||
import org.maplibre.compose.style.BaseStyle
|
||||
import org.maplibre.spatialk.geojson.Position
|
||||
import kotlin.time.Duration.Companion.seconds
|
||||
|
||||
|
||||
class MainActivity : ComponentActivity() {
|
||||
|
||||
val routeData = MutableLiveData("")
|
||||
|
||||
val routeModel = RouteModel()
|
||||
var tilt = 50.0
|
||||
val useMock = false
|
||||
val type = 1 // 1 simulate 2 test 3 gpx 4 testSingle
|
||||
val type = 3 // 1 simulate 2 test 3 gpx 4 testSingle
|
||||
|
||||
val stepData: MutableLiveData<StepData> by lazy {
|
||||
MutableLiveData()
|
||||
@@ -125,7 +122,6 @@ class MainActivity : ComponentActivity() {
|
||||
private lateinit var fusedLocationClient: FusedLocationProviderClient
|
||||
private lateinit var mock: MockLocation
|
||||
private var loadRecentPlaces = false
|
||||
lateinit var baseStyle: BaseStyle.Json
|
||||
|
||||
@RequiresPermission(allOf = [Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.ACCESS_COARSE_LOCATION])
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
@@ -140,7 +136,7 @@ class MainActivity : ComponentActivity() {
|
||||
if (useMock) {
|
||||
mock = MockLocation(locationManager)
|
||||
mock.setMockLocation(
|
||||
homeVogelhart.latitude, homeVogelhart.longitude
|
||||
homeVogelhart.latitude, homeVogelhart.longitude, 0F
|
||||
)
|
||||
navigationViewModel.route.observe(this, observer)
|
||||
}
|
||||
@@ -190,8 +186,9 @@ class MainActivity : ComponentActivity() {
|
||||
|
||||
val appViewModel: AppViewModel = appViewModel()
|
||||
val darkMode by appViewModel.darkMode.collectAsState()
|
||||
|
||||
val sheetPeekHeight = 250.dp
|
||||
baseStyle = BaseStyleModel().readStyle(applicationContext, darkMode, darkMode == 1)
|
||||
val baseStyle = BaseStyleModel().readStyle(applicationContext, darkMode, darkMode == 1)
|
||||
val scaffoldState = rememberBottomSheetScaffoldState()
|
||||
val snackbarHostState = remember { SnackbarHostState() }
|
||||
val scope = rememberCoroutineScope()
|
||||
@@ -240,7 +237,7 @@ class MainActivity : ComponentActivity() {
|
||||
cameraPosition,
|
||||
routeData,
|
||||
tilt,
|
||||
baseStyle
|
||||
baseStyle,
|
||||
)
|
||||
}
|
||||
if (!routeModel.isNavigating()) {
|
||||
@@ -304,6 +301,9 @@ class MainActivity : ComponentActivity() {
|
||||
fun updateLocation(location: Location?) {
|
||||
if (location != null && lastLocation.latitude != location.position.latitude && lastLocation.longitude != location.position.longitude) {
|
||||
val currentLocation = location(location.position.longitude, location.position.latitude)
|
||||
if (location.bearing != null) {
|
||||
currentLocation.bearing = location.bearing!!.toFloat()
|
||||
}
|
||||
if (routeModel.isNavigating()) {
|
||||
val snapedLocation =
|
||||
snapLocation(currentLocation, routeModel.route.maneuverLocations())
|
||||
@@ -315,7 +315,15 @@ class MainActivity : ComponentActivity() {
|
||||
}
|
||||
|
||||
fun updateLocationInternal(currentLocation: android.location.Location, location: Location?) {
|
||||
val bearing = bearing(lastLocation, currentLocation, cameraPosition.value!!.bearing)
|
||||
if (currentLocation.hasBearing()) {
|
||||
routeModel.navState = routeModel.navState.copy(routeBearing = currentLocation.bearing)
|
||||
}
|
||||
val bearing = if (currentLocation.hasBearing()) {
|
||||
currentLocation.bearing.toDouble()
|
||||
} else {
|
||||
bearing(lastLocation, currentLocation, cameraPosition.value!!.bearing)
|
||||
}
|
||||
|
||||
with(routeModel) {
|
||||
if (isNavigating()) {
|
||||
updateLocation(applicationContext, currentLocation, navigationViewModel)
|
||||
@@ -348,7 +356,7 @@ class MainActivity : ComponentActivity() {
|
||||
closeSheet()
|
||||
routeModel.stopNavigation(applicationContext)
|
||||
if (useMock) {
|
||||
mock.setMockLocation(latitude, longitude)
|
||||
mock.setMockLocation(latitude, longitude, 0F)
|
||||
}
|
||||
routeData.value = ""
|
||||
stepData.value = StepData("", 0.0, 0, 0, 0, 0.0)
|
||||
@@ -380,14 +388,18 @@ class MainActivity : ComponentActivity() {
|
||||
|
||||
fun simulate() {
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
var lastLocation = location(0.0, 0.0)
|
||||
for ((index, waypoint) in routeModel.curRoute.waypoints.withIndex()) {
|
||||
val curLocation = location(waypoint[0], waypoint[1])
|
||||
if (routeModel.isNavigating()) {
|
||||
val deviation = 0.0
|
||||
if (index in 0..routeModel.curRoute.waypoints.size) {
|
||||
mock.setMockLocation(waypoint[1], waypoint[0])
|
||||
val bearing = lastLocation.bearingTo(curLocation)
|
||||
mock.setMockLocation(waypoint[1], waypoint[0], bearing)
|
||||
Thread.sleep(1000)
|
||||
}
|
||||
}
|
||||
lastLocation = curLocation
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -416,7 +428,7 @@ class MainActivity : ComponentActivity() {
|
||||
|
||||
fun testSingleUpdate(latitude: Double, longitude: Double) {
|
||||
if (1 == 1) {
|
||||
mock.setMockLocation(latitude, longitude)
|
||||
mock.setMockLocation(latitude, longitude, 0F)
|
||||
} else {
|
||||
routeModel.updateLocation(
|
||||
applicationContext,
|
||||
@@ -425,12 +437,12 @@ class MainActivity : ComponentActivity() {
|
||||
}
|
||||
val step = routeModel.currentStep()
|
||||
val nextStep = routeModel.nextStep()
|
||||
println("Step: ${step.instruction} ${step.leftStepDistance} ${nextStep.currentManeuverType}")
|
||||
Thread.sleep(1_000)
|
||||
}
|
||||
|
||||
fun gpx(context: Context) {
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
var lastLocation = location(0.0, 0.0)
|
||||
val parser = GPXParser()
|
||||
val input = context.resources.openRawResource(R.raw.vh)
|
||||
val parsedGpx: Gpx? = parser.parse(input) // consider using a background thread
|
||||
@@ -441,18 +453,23 @@ class MainActivity : ComponentActivity() {
|
||||
segments!!.forEach { seg ->
|
||||
var lastTime = DateTime.now()
|
||||
seg!!.trackPoints.forEach { p ->
|
||||
val curLocation = location(p.longitude, p.latitude)
|
||||
val ext = p.extensions
|
||||
val speed: Double?
|
||||
if (ext != null) {
|
||||
speed = ext.speed
|
||||
mock.curSpeed = speed.toFloat()
|
||||
}
|
||||
|
||||
val duration = p.time.millis - lastTime.millis
|
||||
mock.setMockLocation(p.latitude, p.longitude)
|
||||
val bearing = lastLocation.bearingTo(curLocation)
|
||||
println("Bearing $bearing")
|
||||
mock.setMockLocation(p.latitude, p.longitude, bearing)
|
||||
if (duration > 0) {
|
||||
delay(duration / 5)
|
||||
}
|
||||
lastTime = p.time
|
||||
lastLocation = curLocation
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,26 +4,21 @@ import android.content.Context
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.livedata.observeAsState
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import androidx.window.layout.WindowMetricsCalculator
|
||||
import com.kouros.navigation.car.ViewStyle
|
||||
import com.kouros.navigation.car.map.MapLibre
|
||||
import com.kouros.navigation.car.map.NavigationImage
|
||||
import com.kouros.navigation.car.map.rememberBaseStyle
|
||||
import com.kouros.navigation.data.StepData
|
||||
import com.kouros.navigation.ui.app.AppViewModel
|
||||
import com.kouros.navigation.ui.app.appViewModel
|
||||
import com.kouros.navigation.utils.settingsViewModel
|
||||
import org.maplibre.compose.camera.CameraPosition
|
||||
import org.maplibre.compose.camera.rememberCameraState
|
||||
import org.maplibre.compose.location.LocationTrackingEffect
|
||||
@@ -64,8 +59,6 @@ fun MapView(
|
||||
)
|
||||
)
|
||||
|
||||
val rememberBaseStyle = rememberBaseStyle(baseStyle)
|
||||
|
||||
val appViewModel: AppViewModel = appViewModel()
|
||||
val showBuildings by appViewModel.show3D.collectAsState()
|
||||
|
||||
@@ -73,9 +66,8 @@ fun MapView(
|
||||
NavigationInfo(step, nextStep)
|
||||
Box(contentAlignment = Alignment.Center) {
|
||||
MapLibre(
|
||||
applicationContext,
|
||||
cameraState,
|
||||
rememberBaseStyle,
|
||||
baseStyle,
|
||||
route,
|
||||
emptyMap(),
|
||||
ViewStyle.VIEW,
|
||||
|
||||
@@ -8,9 +8,9 @@
|
||||
<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_COARSE_LOCATION" />
|
||||
<uses-permission android:name="android.car.permission.CAR_SPEED"/>
|
||||
<uses-permission android:name="androidx.car.app.ACCESS_SURFACE" />
|
||||
|
||||
<!-- Various required feature settings for an automotive app. -->
|
||||
<uses-feature
|
||||
android:name="android.hardware.type.automotive"
|
||||
android:required="true" />
|
||||
|
||||
@@ -46,7 +46,6 @@ dependencies {
|
||||
implementation(libs.androidx.lifecycle.runtime.ktx)
|
||||
implementation(libs.androidx.ui)
|
||||
implementation(libs.maplibre.compose)
|
||||
implementation(libs.androidx.app.projected)
|
||||
//implementation(libs.maplibre.composeMaterial3)
|
||||
|
||||
implementation(project(":common:data"))
|
||||
|
||||
@@ -49,6 +49,7 @@
|
||||
<category android:name="androidx.car.app.category.WEATHER" />
|
||||
<category android:name="androidx.car.app.category.POI"/>
|
||||
<category android:name="androidx.car.app.category.NAVIGATION"/>
|
||||
<category android:name="androidx.car.app.category.FEATURE_CLUSTER"/>
|
||||
</intent-filter>
|
||||
</service>
|
||||
</application>
|
||||
|
||||
@@ -13,6 +13,7 @@ import androidx.car.app.CarContext
|
||||
import androidx.car.app.Screen
|
||||
import androidx.car.app.ScreenManager
|
||||
import androidx.car.app.Session
|
||||
import androidx.car.app.connection.CarConnection
|
||||
import androidx.car.app.hardware.CarHardwareManager
|
||||
import androidx.car.app.hardware.common.CarValue
|
||||
import androidx.car.app.hardware.common.OnCarDataAvailableListener
|
||||
@@ -36,6 +37,7 @@ import com.kouros.navigation.car.screen.SearchScreen
|
||||
import com.kouros.navigation.data.Constants.MAXIMAL_ROUTE_DEVIATION
|
||||
import com.kouros.navigation.data.Constants.MAXIMAL_SNAP_CORRECTION
|
||||
import com.kouros.navigation.data.Constants.TAG
|
||||
import com.kouros.navigation.data.ObjectBox
|
||||
import com.kouros.navigation.data.RouteEngine
|
||||
import com.kouros.navigation.data.osrm.OsrmRepository
|
||||
import com.kouros.navigation.data.tomtom.TomTomRepository
|
||||
@@ -48,7 +50,7 @@ import kotlinx.coroutines.awaitCancellation
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.runBlocking
|
||||
|
||||
import android.Manifest.permission
|
||||
|
||||
class NavigationSession : Session(), NavigationScreen.Listener {
|
||||
|
||||
@@ -82,14 +84,7 @@ class NavigationSession : Session(), NavigationScreen.Listener {
|
||||
}
|
||||
|
||||
override fun onDestroy(owner: LifecycleOwner) {
|
||||
val carInfo = carContext.getCarService(CarHardwareManager::class.java).carInfo
|
||||
val repository = getSettingsRepository(carContext)
|
||||
val useCarLocation = runBlocking { repository.carLocationFlow.first() }
|
||||
if (useCarLocation) {
|
||||
val carSensors = carContext.getCarService(CarHardwareManager::class.java).carSensors
|
||||
carSensors.removeCarHardwareLocationListener(carLocationListener)
|
||||
}
|
||||
carInfo.removeSpeedListener(carSpeedListener)
|
||||
removeSensors()
|
||||
Log.i(TAG, "In onDestroy()")
|
||||
val locationManager =
|
||||
carContext.getSystemService(Context.LOCATION_SERVICE) as LocationManager
|
||||
@@ -139,6 +134,24 @@ class NavigationSession : Session(), NavigationScreen.Listener {
|
||||
}
|
||||
}
|
||||
|
||||
fun onPermissionGranted(permission : Boolean) {
|
||||
addSensors(routeModel.navState.carConnection)
|
||||
}
|
||||
|
||||
fun onConnectionStateUpdated(connectionState: Int) {
|
||||
routeModel.navState = routeModel.navState.copy(carConnection = connectionState)
|
||||
when (connectionState) {
|
||||
CarConnection.CONNECTION_TYPE_NOT_CONNECTED -> "Not connected to a head unit"
|
||||
CarConnection.CONNECTION_TYPE_NATIVE -> {
|
||||
ObjectBox.init(carContext)
|
||||
navigationScreen.checkPermission("android.car.permission.CAR_SPEED")
|
||||
} // Automotive OS
|
||||
CarConnection.CONNECTION_TYPE_PROJECTION -> {
|
||||
"Connected to Android Auto"
|
||||
navigationScreen.checkPermission("com.google.android.gms.permission.CAR_SPEED")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreateScreen(intent: Intent): Screen {
|
||||
|
||||
@@ -153,26 +166,23 @@ class NavigationSession : Session(), NavigationScreen.Listener {
|
||||
}
|
||||
}
|
||||
|
||||
// lifecycleScope.launch {
|
||||
|
||||
|
||||
//}
|
||||
|
||||
|
||||
navigationViewModel = getViewModel(carContext)
|
||||
|
||||
navigationViewModel.routingEngine.observe(this, ::onRoutingEngineStateUpdated)
|
||||
|
||||
navigationViewModel.permissionGranted.observe(this, ::onPermissionGranted)
|
||||
|
||||
routeModel = RouteCarModel()
|
||||
|
||||
CarConnection(carContext).type.observe(this, ::onConnectionStateUpdated)
|
||||
|
||||
surfaceRenderer = SurfaceRenderer(carContext, lifecycle, routeModel, viewModelStoreOwner)
|
||||
|
||||
navigationScreen =
|
||||
NavigationScreen(carContext, surfaceRenderer, routeModel, this, navigationViewModel)
|
||||
|
||||
if (carContext.checkSelfPermission(Manifest.permission.ACCESS_FINE_LOCATION)
|
||||
if ( carContext.checkSelfPermission(Manifest.permission.ACCESS_FINE_LOCATION)
|
||||
== PackageManager.PERMISSION_GRANTED
|
||||
&& carContext.checkSelfPermission("com.google.android.gms.permission.CAR_SPEED") == PackageManager.PERMISSION_GRANTED
|
||||
&& !useContacts
|
||||
|| (useContacts && carContext.checkSelfPermission(Manifest.permission.READ_CONTACTS)
|
||||
== PackageManager.PERMISSION_GRANTED)
|
||||
@@ -180,27 +190,24 @@ class NavigationSession : Session(), NavigationScreen.Listener {
|
||||
requestLocationUpdates()
|
||||
} else {
|
||||
// If we do not have the location permission, show the request permission screen.
|
||||
val permissions: MutableList<String?> = ArrayList()
|
||||
permissions.add(permission.ACCESS_FINE_LOCATION)
|
||||
val screenManager =
|
||||
carContext.getCarService(ScreenManager::class.java)
|
||||
screenManager
|
||||
.push(navigationScreen)
|
||||
return RequestPermissionScreen(
|
||||
carContext,
|
||||
mLocationPermissionCheckCallback = {
|
||||
permissionCheckCallback = {
|
||||
screenManager.pop()
|
||||
},
|
||||
mContactsPermissionCheckCallback = {
|
||||
screenManager.pop()
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
addSensors()
|
||||
|
||||
return navigationScreen
|
||||
}
|
||||
|
||||
fun addSensors() {
|
||||
fun addSensors(connectionState: Int) {
|
||||
val carInfo = carContext.getCarService(CarHardwareManager::class.java).carInfo
|
||||
val repository = getSettingsRepository(carContext)
|
||||
val useCarLocation = runBlocking { repository.carLocationFlow.first() }
|
||||
@@ -215,7 +222,24 @@ class NavigationSession : Session(), NavigationScreen.Listener {
|
||||
carLocationListener
|
||||
)
|
||||
}
|
||||
carInfo.addSpeedListener(carContext.mainExecutor, carSpeedListener)
|
||||
if (connectionState == CarConnection.CONNECTION_TYPE_NATIVE
|
||||
|| connectionState == CarConnection.CONNECTION_TYPE_PROJECTION) {
|
||||
carInfo.addSpeedListener(carContext.mainExecutor, carSpeedListener)
|
||||
}
|
||||
}
|
||||
|
||||
fun removeSensors() {
|
||||
val carInfo = carContext.getCarService(CarHardwareManager::class.java).carInfo
|
||||
val repository = getSettingsRepository(carContext)
|
||||
val useCarLocation = runBlocking { repository.carLocationFlow.first() }
|
||||
if (useCarLocation) {
|
||||
val carSensors = carContext.getCarService(CarHardwareManager::class.java).carSensors
|
||||
carSensors.removeCarHardwareLocationListener(carLocationListener)
|
||||
}
|
||||
if (routeModel.navState.carConnection == CarConnection.CONNECTION_TYPE_NATIVE
|
||||
|| routeModel.navState.carConnection == CarConnection.CONNECTION_TYPE_PROJECTION) {
|
||||
carInfo.removeSpeedListener(carSpeedListener)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onNewIntent(intent: Intent) {
|
||||
@@ -277,6 +301,9 @@ class NavigationSession : Session(), NavigationScreen.Listener {
|
||||
}
|
||||
|
||||
fun updateLocation(location: Location) {
|
||||
if (location.hasBearing()) {
|
||||
routeModel.navState = routeModel.navState.copy(routeBearing = location.bearing)
|
||||
}
|
||||
if (routeModel.isNavigating()) {
|
||||
navigationScreen.updateTrip(location)
|
||||
if (!routeModel.navState.arrived) {
|
||||
|
||||
@@ -29,7 +29,6 @@ import com.kouros.navigation.car.map.DrawNavigationImages
|
||||
import com.kouros.navigation.car.map.MapLibre
|
||||
import com.kouros.navigation.car.map.cameraState
|
||||
import com.kouros.navigation.car.map.getPaddingValues
|
||||
import com.kouros.navigation.car.map.rememberBaseStyle
|
||||
import com.kouros.navigation.car.navigation.RouteCarModel
|
||||
import com.kouros.navigation.data.Constants.homeVogelhart
|
||||
import com.kouros.navigation.data.ObjectBox
|
||||
@@ -79,6 +78,7 @@ class SurfaceRenderer(
|
||||
val trafficData = MutableLiveData(emptyMap<String, String>())
|
||||
val speedCamerasData = MutableLiveData("")
|
||||
val speed = MutableLiveData(0F)
|
||||
|
||||
val maxSpeed = MutableLiveData(0)
|
||||
var viewStyle = ViewStyle.VIEW
|
||||
lateinit var centerLocation: Location
|
||||
@@ -175,14 +175,6 @@ class SurfaceRenderer(
|
||||
|
||||
}
|
||||
|
||||
fun onConnectionStateUpdated(connectionState: Int) {
|
||||
when (connectionState) {
|
||||
CarConnection.CONNECTION_TYPE_NOT_CONNECTED -> "Not connected to a head unit"
|
||||
CarConnection.CONNECTION_TYPE_PROJECTION -> "Connected to Android Auto"
|
||||
CarConnection.CONNECTION_TYPE_NATIVE -> ObjectBox.init(carContext) // Automotive OS
|
||||
}
|
||||
}
|
||||
|
||||
fun onBaseStyleStateUpdated(style: BaseStyle) {
|
||||
|
||||
}
|
||||
@@ -190,29 +182,24 @@ class SurfaceRenderer(
|
||||
@Composable
|
||||
fun MapView() {
|
||||
|
||||
|
||||
//val appViewModel: AppViewModel = appViewModel(viewModelStoreOwner)
|
||||
//val darkMode by appViewModel.darkMode.collectAsState()
|
||||
val darkMode = settingsViewModel(carContext, viewModelStoreOwner).darkMode.collectAsState().value
|
||||
|
||||
val baseStyle = BaseStyleModel().readStyle(carContext, darkMode, carContext.isDarkMode)
|
||||
val showBuildings = settingsViewModel(carContext, viewModelStoreOwner).show3D.collectAsState().value
|
||||
val position: CameraPosition? by cameraPosition.observeAsState()
|
||||
val route: String? by routeData.observeAsState()
|
||||
val traffic: Map<String, String> ? by trafficData.observeAsState()
|
||||
val speedCameras: String? by speedCamerasData.observeAsState()
|
||||
val paddingValues = getPaddingValues(height, viewStyle)
|
||||
val cameraState = cameraState(paddingValues, position, tilt)
|
||||
val rememberBaseStyle = rememberBaseStyle(baseStyle)
|
||||
val baseStyle = BaseStyleModel().readStyle(carContext, darkMode, carContext.isDarkMode)
|
||||
|
||||
MapLibre(
|
||||
carContext,
|
||||
cameraState,
|
||||
rememberBaseStyle,
|
||||
baseStyle,
|
||||
route,
|
||||
traffic,
|
||||
viewStyle,
|
||||
speedCameras,
|
||||
false
|
||||
showBuildings
|
||||
)
|
||||
ShowPosition(cameraState, position, paddingValues)
|
||||
}
|
||||
@@ -226,12 +213,12 @@ class SurfaceRenderer(
|
||||
val cameraDuration =
|
||||
duration(viewStyle == ViewStyle.PREVIEW, position!!.bearing, lastBearing)
|
||||
val currentSpeed: Float? by speed.observeAsState()
|
||||
val maxSpeed: Int? by maxSpeed.observeAsState()
|
||||
val speed: Int? by maxSpeed.observeAsState()
|
||||
if (viewStyle == ViewStyle.VIEW || viewStyle == ViewStyle.PAN_VIEW) {
|
||||
DrawNavigationImages(
|
||||
paddingValues,
|
||||
currentSpeed,
|
||||
maxSpeed!!,
|
||||
speed!!,
|
||||
width,
|
||||
height
|
||||
)
|
||||
@@ -251,7 +238,6 @@ class SurfaceRenderer(
|
||||
}
|
||||
|
||||
override fun onCreate(owner: LifecycleOwner) {
|
||||
CarConnection(carContext).type.observe(owner, ::onConnectionStateUpdated)
|
||||
style.observe(owner, :: onBaseStyleStateUpdated)
|
||||
Log.i(TAG, "SurfaceRenderer created")
|
||||
carContext.getCarService(AppManager::class.java)
|
||||
@@ -281,15 +267,19 @@ class SurfaceRenderer(
|
||||
fun updateLocation(location: Location) {
|
||||
synchronized(this) {
|
||||
if (viewStyle == ViewStyle.VIEW || viewStyle == ViewStyle.PAN_VIEW) {
|
||||
val bearing = if (carOrientation == 999F)
|
||||
bearing(
|
||||
lastLocation,
|
||||
location,
|
||||
cameraPosition.value!!.bearing
|
||||
) else {
|
||||
val bearing = if (carOrientation == 999F) {
|
||||
if (location.hasBearing()) {
|
||||
location.bearing.toDouble()
|
||||
} else {
|
||||
bearing(
|
||||
lastLocation,
|
||||
location,
|
||||
cameraPosition.value!!.bearing
|
||||
)
|
||||
}
|
||||
} else {
|
||||
carOrientation.toDouble()
|
||||
}
|
||||
println("Bearing $bearing")
|
||||
val zoom = if (viewStyle == ViewStyle.VIEW) {
|
||||
calculateZoom(location.speed.toDouble())
|
||||
} else {
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package com.kouros.navigation.car.map
|
||||
|
||||
import android.content.Context
|
||||
import android.location.Location
|
||||
import androidx.compose.foundation.Canvas
|
||||
import androidx.compose.foundation.layout.Box
|
||||
@@ -10,8 +9,6 @@ import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
@@ -26,18 +23,13 @@ import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.rememberTextMeasurer
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import com.kouros.data.R
|
||||
import com.kouros.navigation.car.ViewStyle
|
||||
import com.kouros.navigation.data.Constants
|
||||
import com.kouros.navigation.data.NavigationColor
|
||||
import com.kouros.navigation.data.RouteColor
|
||||
import com.kouros.navigation.data.SpeedColor
|
||||
import com.kouros.navigation.data.datastore.DataStoreManager
|
||||
import com.kouros.navigation.model.RouteModel
|
||||
import com.kouros.navigation.model.SettingsViewModel
|
||||
import com.kouros.navigation.repository.SettingsRepository
|
||||
import org.maplibre.compose.camera.CameraPosition
|
||||
import org.maplibre.compose.camera.CameraState
|
||||
import org.maplibre.compose.camera.rememberCameraState
|
||||
@@ -89,7 +81,6 @@ fun cameraState(
|
||||
|
||||
@Composable
|
||||
fun MapLibre(
|
||||
context: Context,
|
||||
cameraState: CameraState,
|
||||
baseStyle: BaseStyle.Json,
|
||||
route: String?,
|
||||
@@ -229,15 +220,15 @@ fun trafficColor(key: String): Expression<ColorValue> {
|
||||
|
||||
@Composable
|
||||
fun AmenityLayer(routeData: String?) {
|
||||
if (routeData != null && routeData.isNotEmpty()) {
|
||||
if (!routeData.isNullOrEmpty()) {
|
||||
var color = const(Color.Red)
|
||||
var img = image(painterResource(R.drawable.local_pharmacy_48px), drawAsSdf = true)
|
||||
if (routeData.contains(Constants.CHARGING_STATION)) {
|
||||
color = const(Color.Green)
|
||||
color = const(Color(0xFF054603))
|
||||
img = image(painterResource(R.drawable.ev_station_48px), drawAsSdf = true)
|
||||
} else if (routeData.contains(Constants.FUEL_STATION)) {
|
||||
color = const(Color.Black)
|
||||
img = image(painterResource(R.drawable.local_gas_station_48px), drawAsSdf = true)
|
||||
color = const(Color.Blue)
|
||||
img = image(painterResource(R.drawable.local_gas_station_24), drawAsSdf = true)
|
||||
}
|
||||
val routes = rememberGeoJsonSource(GeoJsonData.JsonString(routeData))
|
||||
SymbolLayer(
|
||||
@@ -261,7 +252,7 @@ fun AmenityLayer(routeData: String?) {
|
||||
|
||||
@Composable
|
||||
fun SpeedCameraLayer(speedCameras: String?) {
|
||||
if (speedCameras != null && speedCameras.isNotEmpty()) {
|
||||
if (!speedCameras.isNullOrEmpty()) {
|
||||
val color = const(Color.Red)
|
||||
val cameraSource = rememberGeoJsonSource(GeoJsonData.JsonString(speedCameras))
|
||||
SymbolLayer(
|
||||
@@ -306,7 +297,7 @@ fun DrawNavigationImages(
|
||||
if (speed != null) {
|
||||
CurrentSpeed(width, height, speed, maxSpeed)
|
||||
}
|
||||
if (speed != null && maxSpeed > 0 && (speed * 3.6) > maxSpeed) {
|
||||
if (speed != null && maxSpeed > 0 && (speed * 3.6) > maxSpeed) {
|
||||
MaxSpeed(width, height, maxSpeed)
|
||||
}
|
||||
//DebugInfo(width, height, routeModel)
|
||||
@@ -497,14 +488,6 @@ fun DebugInfo(
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun rememberBaseStyle(baseStyle: BaseStyle.Json): BaseStyle.Json {
|
||||
val rememberBaseStyle by remember() {
|
||||
mutableStateOf(baseStyle)
|
||||
}
|
||||
return rememberBaseStyle
|
||||
}
|
||||
|
||||
fun getPaddingValues(height: Int, viewStyle: ViewStyle): PaddingValues {
|
||||
return when (viewStyle) {
|
||||
ViewStyle.VIEW, ViewStyle.PAN_VIEW -> PaddingValues(
|
||||
|
||||
@@ -76,7 +76,7 @@ class CategoriesScreen(
|
||||
|
||||
fun carIcon(context: CarContext, id: String): CarIcon {
|
||||
val resId = when (id) {
|
||||
FUEL_STATION -> R.drawable.local_gas_station_48px
|
||||
FUEL_STATION -> R.drawable.local_gas_station_24
|
||||
PHARMACY -> R.drawable.local_pharmacy_48px
|
||||
CHARGING_STATION -> R.drawable.ev_station_48px
|
||||
else -> {}
|
||||
|
||||
@@ -46,7 +46,7 @@ class DarkModeSettings(private val carContext: CarContext) : Screen(carContext)
|
||||
)
|
||||
.addItem(
|
||||
buildRowForTemplate(
|
||||
R.string.use_telephon_settings,
|
||||
R.string.use_car_settings,
|
||||
)
|
||||
)
|
||||
.setOnSelectedListener { index: Int ->
|
||||
@@ -74,14 +74,6 @@ class DarkModeSettings(private val carContext: CarContext) : Screen(carContext)
|
||||
|
||||
private fun onSelected(index: Int) {
|
||||
settingsViewModel.onDarkModeChanged(index)
|
||||
CarToast.makeText(
|
||||
carContext,
|
||||
(carContext
|
||||
.getString(R.string.display_settings)
|
||||
+ ":"
|
||||
+ " " + index), CarToast.LENGTH_LONG
|
||||
)
|
||||
.show()
|
||||
}
|
||||
|
||||
private fun buildRowForTemplate(title: Int): Row {
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
package com.kouros.navigation.car.screen
|
||||
|
||||
import android.Manifest
|
||||
import android.content.pm.PackageManager
|
||||
import android.location.Location
|
||||
import android.location.LocationManager
|
||||
import android.os.CountDownTimer
|
||||
import android.os.Handler
|
||||
import androidx.car.app.CarContext
|
||||
import androidx.car.app.Screen
|
||||
import androidx.car.app.ScreenManager
|
||||
import androidx.car.app.model.Action
|
||||
import androidx.car.app.model.Action.FLAG_DEFAULT
|
||||
import androidx.car.app.model.ActionStrip
|
||||
@@ -80,7 +83,7 @@ class NavigationScreen(
|
||||
invalidate()
|
||||
}
|
||||
}
|
||||
val trafficObserver = Observer<Map<String, String> > { traffic ->
|
||||
val trafficObserver = Observer<Map<String, String>> { traffic ->
|
||||
surfaceRenderer.setTrafficData(traffic)
|
||||
invalidate()
|
||||
}
|
||||
@@ -111,6 +114,9 @@ class NavigationScreen(
|
||||
surfaceRenderer.speedCamerasData.value = speedData
|
||||
}
|
||||
|
||||
val maxSpeedObserver = Observer<Int> { speed ->
|
||||
surfaceRenderer.maxSpeed.value = speed
|
||||
}
|
||||
|
||||
init {
|
||||
navigationViewModel.route.observe(this, observer)
|
||||
@@ -118,6 +124,7 @@ class NavigationScreen(
|
||||
navigationViewModel.recentPlace.observe(this, recentObserver)
|
||||
navigationViewModel.placeLocation.observe(this, placeObserver)
|
||||
navigationViewModel.speedCameras.observe(this, speedObserver)
|
||||
navigationViewModel.maxSpeed.observe(this, maxSpeedObserver)
|
||||
lifecycleScope.launch {
|
||||
getSettingsViewModel(carContext).routingEngine.collect {
|
||||
}
|
||||
@@ -497,7 +504,7 @@ class NavigationScreen(
|
||||
}
|
||||
updateSpeedCamera(location)
|
||||
with(routeModel) {
|
||||
updateLocation(carContext,location, navigationViewModel)
|
||||
updateLocation(carContext, location, navigationViewModel)
|
||||
if ((navState.maneuverType == Maneuver.TYPE_DESTINATION
|
||||
|| navState.maneuverType == Maneuver.TYPE_DESTINATION_LEFT
|
||||
|| navState.maneuverType == Maneuver.TYPE_DESTINATION_RIGHT
|
||||
@@ -540,7 +547,7 @@ class NavigationScreen(
|
||||
val bearingSpeedCamera = if (camera.tags.direction != null) {
|
||||
try {
|
||||
camera.tags.direction!!.toFloat()
|
||||
} catch ( e: Exception) {
|
||||
} catch (e: Exception) {
|
||||
0F
|
||||
}
|
||||
} else {
|
||||
@@ -552,6 +559,29 @@ class NavigationScreen(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun checkPermission(permission: String) {
|
||||
println("Car connection permission: $permission")
|
||||
if (carContext.checkSelfPermission(permission) != PackageManager.PERMISSION_GRANTED) {
|
||||
val permissions: MutableList<String?> = ArrayList()
|
||||
permissions.add(permission)
|
||||
val screenManager =
|
||||
carContext.getCarService(ScreenManager::class.java)
|
||||
screenManager
|
||||
.push(
|
||||
RequestPermissionScreen(
|
||||
carContext,
|
||||
permissionCheckCallback = {
|
||||
screenManager.pop()
|
||||
navigationViewModel.permissionGranted.value = true
|
||||
},
|
||||
permissions
|
||||
)
|
||||
)
|
||||
} else {
|
||||
navigationViewModel.permissionGranted.value = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum class NavigationType {
|
||||
|
||||
@@ -16,7 +16,10 @@ import com.kouros.navigation.utils.getSettingsViewModel
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
|
||||
class NavigationSettings(private val carContext: CarContext, private var navigationViewModel: NavigationViewModel) :
|
||||
class NavigationSettings(
|
||||
private val carContext: CarContext,
|
||||
private var navigationViewModel: NavigationViewModel
|
||||
) :
|
||||
Screen(carContext) {
|
||||
|
||||
private var motorWayToggleState = false
|
||||
@@ -79,6 +82,12 @@ class NavigationSettings(private val carContext: CarContext, private var navigat
|
||||
R.string.routing_engine
|
||||
)
|
||||
)
|
||||
.addItem(
|
||||
buildRowForScreenTemplate(
|
||||
PasswordSettings(carContext, navigationViewModel),
|
||||
R.string.tomtom_api_key
|
||||
)
|
||||
)
|
||||
return ListTemplate.Builder()
|
||||
.setSingleList(listBuilder.build())
|
||||
.setHeader(
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
package com.kouros.navigation.car.screen
|
||||
|
||||
import androidx.car.app.CarContext
|
||||
import androidx.car.app.Screen
|
||||
import androidx.car.app.model.Action
|
||||
import androidx.car.app.model.InputCallback
|
||||
import androidx.car.app.model.ParkedOnlyOnClickListener
|
||||
import androidx.car.app.model.Template
|
||||
import androidx.car.app.model.signin.InputSignInMethod
|
||||
import androidx.car.app.model.signin.SignInTemplate
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import com.kouros.data.R
|
||||
import com.kouros.navigation.model.NavigationViewModel
|
||||
import com.kouros.navigation.utils.getSettingsViewModel
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
class PasswordSettings(
|
||||
private val carContext: CarContext,
|
||||
private var navigationViewModel: NavigationViewModel
|
||||
) : Screen(carContext) {
|
||||
|
||||
var errorMessage: String? = null
|
||||
|
||||
val settingsViewModel = getSettingsViewModel(carContext)
|
||||
|
||||
init {
|
||||
lifecycleScope.launch {
|
||||
settingsViewModel.tomTomApiKey.collect {
|
||||
invalidate()
|
||||
}
|
||||
}
|
||||
}
|
||||
override fun onGetTemplate(): Template {
|
||||
val apiKey = settingsViewModel.tomTomApiKey.value
|
||||
val callback: InputCallback = object : InputCallback {
|
||||
override fun onInputSubmitted(text: String) {
|
||||
settingsViewModel.onTomTomApiKeyChanged(text)
|
||||
//errorMessage = carContext.getString(R.string.tomtom_api_key)
|
||||
invalidate()
|
||||
}
|
||||
}
|
||||
|
||||
val builder = InputSignInMethod.Builder(callback)
|
||||
.setHint(apiKey)
|
||||
.setInputType(InputSignInMethod.INPUT_TYPE_PASSWORD)
|
||||
if (errorMessage != null) {
|
||||
// builder.setErrorMessage(errorMessage)
|
||||
}
|
||||
val signInMethod = builder.build()
|
||||
|
||||
val pinSignInAction = Action.Builder()
|
||||
.setTitle(carContext.getString(R.string.stop_action_title))
|
||||
.setOnClickListener(ParkedOnlyOnClickListener.create {
|
||||
println("Sign")
|
||||
invalidate()
|
||||
})
|
||||
.build()
|
||||
return SignInTemplate.Builder(signInMethod)
|
||||
//.addAction(pinSignInAction)
|
||||
.setTitle(carContext.getString(R.string.settings_action_title))
|
||||
.setInstructions(
|
||||
carContext.getString(R.string.tomtom_api_key)
|
||||
)
|
||||
.setHeaderAction(Action.BACK)
|
||||
.build()
|
||||
}
|
||||
}
|
||||
@@ -14,22 +14,26 @@ import androidx.car.app.model.Template
|
||||
/** Screen for asking the user to grant location permission. */
|
||||
class RequestPermissionScreen(
|
||||
carContext: CarContext,
|
||||
var mLocationPermissionCheckCallback: LocationPermissionCheckCallback,
|
||||
var mContactsPermissionCheckCallback: LocationPermissionCheckCallback,
|
||||
var permissionCheckCallback: PermissionCheckCallback,
|
||||
//var mContactsPermissionCheckCallback: LocationPermissionCheckCallback,
|
||||
val permissions: MutableList<String?> = ArrayList()
|
||||
) : Screen(carContext) {
|
||||
/** Callback called when the location permission is granted. */
|
||||
fun interface LocationPermissionCheckCallback {
|
||||
/** Callback called when the location permission is granted. */
|
||||
|
||||
/** Callback called when the permission is granted. */
|
||||
fun interface PermissionCheckCallback {
|
||||
/** Callback called when the permission is granted. */
|
||||
fun onPermissionGranted()
|
||||
}
|
||||
|
||||
override fun onGetTemplate(): Template {
|
||||
val permissions: MutableList<String?> = ArrayList()
|
||||
permissions.add(permission.ACCESS_FINE_LOCATION)
|
||||
permissions.add("com.google.android.gms.permission.CAR_SPEED")
|
||||
//permissions.add(permission.READ_CONTACTS)
|
||||
|
||||
val message = "This app needs access to location and to car speed"
|
||||
var message = ""
|
||||
if (permissions.contains(permission.ACCESS_FINE_LOCATION))
|
||||
message = "This app needs access to location and to car speed"
|
||||
if (permissions.contains("android.car.permission.CAR_SPEED"))
|
||||
message = "This app needs access to car speed"
|
||||
if (permissions.contains("com.google.android.gms.permission.CAR_SPEED"))
|
||||
message = "This app needs access to car speed"
|
||||
|
||||
val listener: OnClickListener = ParkedOnlyOnClickListener.create {
|
||||
carContext.requestPermissions(
|
||||
@@ -41,8 +45,8 @@ class RequestPermissionScreen(
|
||||
CarToast.LENGTH_LONG
|
||||
).show()
|
||||
if (!approved!!.isEmpty()) {
|
||||
mLocationPermissionCheckCallback.onPermissionGranted()
|
||||
mContactsPermissionCheckCallback.onPermissionGranted()
|
||||
permissionCheckCallback.onPermissionGranted()
|
||||
//mContactsPermissionCheckCallback.onPermissionGranted()
|
||||
finish()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -79,7 +79,7 @@ class RoutePreviewScreen(
|
||||
|
||||
val itemListBuilder = ItemList.Builder()
|
||||
var i = 0
|
||||
routeModel.route.routes.forEach { it ->
|
||||
routeModel.route.routes.forEach { _ ->
|
||||
itemListBuilder.addItem(createRow(i++, navigateAction))
|
||||
}
|
||||
|
||||
@@ -95,7 +95,7 @@ class RoutePreviewScreen(
|
||||
.build()
|
||||
|
||||
val message =
|
||||
if (routeModel.isNavigating() && routeModel.curRoute.waypoints!!.isNotEmpty()) {
|
||||
if (routeModel.isNavigating() && routeModel.curRoute.waypoints.isNotEmpty()) {
|
||||
createRouteText(0)
|
||||
} else {
|
||||
CarText.Builder("Wait")
|
||||
@@ -190,7 +190,7 @@ class RoutePreviewScreen(
|
||||
private fun createRouteText(index: Int): CarText {
|
||||
val time = routeModel.route.routes[index].summary.duration
|
||||
val length =
|
||||
BigDecimal(routeModel.route.routes[index].summary.distance).setScale(
|
||||
BigDecimal((routeModel.route.routes[index].summary.distance) / 1000).setScale(
|
||||
1,
|
||||
RoundingMode.HALF_EVEN
|
||||
)
|
||||
@@ -207,7 +207,7 @@ class RoutePreviewScreen(
|
||||
val titleText = "$index"
|
||||
return Row.Builder()
|
||||
.setTitle(route)
|
||||
.setOnClickListener(OnClickListener { onRouteSelected(index) })
|
||||
.setOnClickListener { onRouteSelected(index) }
|
||||
.addText(titleText)
|
||||
.addAction(action)
|
||||
.build()
|
||||
|
||||
@@ -73,14 +73,6 @@ class RoutingSettings(private val carContext: CarContext, private var navigation
|
||||
private fun onSelected(index: Int) {
|
||||
settingsViewModel.onRoutingEngineChanged(index)
|
||||
navigationViewModel.routingEngine.value = index
|
||||
CarToast.makeText(
|
||||
carContext,
|
||||
(carContext
|
||||
.getString(R.string.routing_engine)
|
||||
+ ":"
|
||||
+ " " + index), CarToast.LENGTH_LONG
|
||||
)
|
||||
.show()
|
||||
}
|
||||
|
||||
private fun buildRowForTemplate(title: Int): Row {
|
||||
|
||||
@@ -124,7 +124,7 @@ object Constants {
|
||||
val homeVogelhart = location(11.5793748, 48.185749)
|
||||
val homeHohenwaldeck = location( 11.594322, 48.1164817)
|
||||
|
||||
const val NEXT_STEP_THRESHOLD = 500.0
|
||||
const val NEXT_STEP_THRESHOLD = 300.0
|
||||
|
||||
const val MAXIMAL_SNAP_CORRECTION = 50.0
|
||||
|
||||
|
||||
@@ -33,59 +33,87 @@ import org.maplibre.geojson.FeatureCollection
|
||||
import java.time.LocalDateTime
|
||||
import java.time.ZoneOffset
|
||||
|
||||
/**
|
||||
* ViewModel for navigation-related data operations.
|
||||
* Handles route calculation, place search, traffic information, and local data persistence.
|
||||
*/
|
||||
class NavigationViewModel(private val repository: NavigationRepository) : ViewModel() {
|
||||
|
||||
/** LiveData containing the calculated route JSON string */
|
||||
val route: MutableLiveData<String> by lazy {
|
||||
MutableLiveData()
|
||||
}
|
||||
|
||||
/** LiveData containing categorized traffic incidents map */
|
||||
val traffic: MutableLiveData<Map<String, String>> by lazy {
|
||||
MutableLiveData()
|
||||
}
|
||||
|
||||
|
||||
/** LiveData containing a preview route JSON string for route preview screens */
|
||||
val previewRoute: MutableLiveData<String> by lazy {
|
||||
MutableLiveData()
|
||||
}
|
||||
|
||||
/** LiveData containing the most recent place used for navigation */
|
||||
val recentPlace: MutableLiveData<Place> by lazy {
|
||||
MutableLiveData()
|
||||
}
|
||||
|
||||
/** LiveData containing list of recent navigation destinations */
|
||||
val places: MutableLiveData<List<Place>> by lazy {
|
||||
MutableLiveData()
|
||||
}
|
||||
|
||||
/** LiveData containing list of favorite saved places */
|
||||
val favorites: MutableLiveData<List<Place>> by lazy {
|
||||
MutableLiveData()
|
||||
}
|
||||
|
||||
/** LiveData containing search results from Nominatim geocoding */
|
||||
val searchPlaces: MutableLiveData<List<SearchResult>> by lazy {
|
||||
MutableLiveData()
|
||||
}
|
||||
|
||||
/** LiveData containing the best matching place from address search */
|
||||
val placeLocation: MutableLiveData<SearchResult> by lazy {
|
||||
MutableLiveData()
|
||||
}
|
||||
|
||||
/** LiveData containing contacts with addresses */
|
||||
val contactAddress: MutableLiveData<List<Place>> by lazy {
|
||||
MutableLiveData()
|
||||
}
|
||||
|
||||
/** LiveData containing POI elements from Overpass API */
|
||||
val elements: MutableLiveData<List<Elements>> by lazy {
|
||||
MutableLiveData()
|
||||
}
|
||||
|
||||
/** LiveData containing speed camera locations */
|
||||
val speedCameras: MutableLiveData<List<Elements>> by lazy {
|
||||
MutableLiveData()
|
||||
}
|
||||
|
||||
/** LiveData containing current road speed limit */
|
||||
val maxSpeed: MutableLiveData<Int> by lazy {
|
||||
MutableLiveData()
|
||||
}
|
||||
|
||||
/** LiveData containing selected routing engine index */
|
||||
val routingEngine: MutableLiveData<Int> by lazy {
|
||||
MutableLiveData()
|
||||
}
|
||||
|
||||
/** LiveData indicating whether permission is granted */
|
||||
val permissionGranted: MutableLiveData<Boolean> by lazy {
|
||||
MutableLiveData()
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Loads the most recent place from ObjectBox and calculates its distance.
|
||||
* Posts the result to recentPlace LiveData if distance > 1km.
|
||||
*/
|
||||
fun loadRecentPlace(location: Location, carOrientation: Float, context: Context) {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
try {
|
||||
@@ -116,6 +144,10 @@ class NavigationViewModel(private val repository: NavigationRepository) : ViewMo
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads all recent places from ObjectBox and calculates distances.
|
||||
* Posts the sorted list to places LiveData.
|
||||
*/
|
||||
fun loadRecentPlaces(context: Context, location: Location, carOrientation: Float) {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
try {
|
||||
@@ -146,6 +178,10 @@ class NavigationViewModel(private val repository: NavigationRepository) : ViewMo
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads favorite places from ObjectBox and calculates distances.
|
||||
* Posts the sorted list to favorites LiveData.
|
||||
*/
|
||||
fun loadFavorites(context: Context, location: Location, carOrientation: Float) {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
try {
|
||||
@@ -175,6 +211,10 @@ class NavigationViewModel(private val repository: NavigationRepository) : ViewMo
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates a route between current location and destination.
|
||||
* Posts the route JSON to route LiveData.
|
||||
*/
|
||||
fun loadRoute(
|
||||
context: Context,
|
||||
currentLocation: Location,
|
||||
@@ -198,6 +238,10 @@ class NavigationViewModel(private val repository: NavigationRepository) : ViewMo
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches traffic incident data and categorizes by severity.
|
||||
* Posts categorized traffic map to traffic LiveData.
|
||||
*/
|
||||
fun loadTraffic(context: Context, currentLocation: Location, carOrientation: Float) {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
try {
|
||||
@@ -216,6 +260,11 @@ class NavigationViewModel(private val repository: NavigationRepository) : ViewMo
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Categorizes traffic incidents by type (queuing, stationary, slow, heavy, roadworks).
|
||||
* @param data Raw traffic GeoJSON string
|
||||
* @return Map of incident type to GeoJSON FeatureCollection
|
||||
*/
|
||||
private fun rebuildTraffic(data: String): Map<String, String> {
|
||||
val featureCollection = FeatureCollection.fromJson(data)
|
||||
val incidents = mutableMapOf<String, String>()
|
||||
@@ -238,6 +287,10 @@ class NavigationViewModel(private val repository: NavigationRepository) : ViewMo
|
||||
return incidents
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates a preview route for route preview screen.
|
||||
* Posts the route JSON to previewRoute LiveData.
|
||||
*/
|
||||
fun loadPreviewRoute(
|
||||
context: Context,
|
||||
currentLocation: Location,
|
||||
@@ -261,6 +314,10 @@ class NavigationViewModel(private val repository: NavigationRepository) : ViewMo
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads device contacts with addresses and converts to Place objects.
|
||||
* Posts results to contactAddress LiveData.
|
||||
*/
|
||||
fun loadContacts(context: Context) {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
val contactList = mutableListOf<Place>()
|
||||
@@ -288,7 +345,10 @@ class NavigationViewModel(private val repository: NavigationRepository) : ViewMo
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Finds the closest matching address for a search query.
|
||||
* Posts the best result to placeLocation LiveData.
|
||||
*/
|
||||
fun findAddress(search: String, location: Location) {
|
||||
var sortedList: List<SearchResult>
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
@@ -310,6 +370,10 @@ class NavigationViewModel(private val repository: NavigationRepository) : ViewMo
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Searches for places using Nominatim geocoding API.
|
||||
* Posts sorted results to searchPlaces LiveData.
|
||||
*/
|
||||
fun searchPlaces(search: String, location: Location) {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
val placesJson = repository.searchPlaces(search, location)
|
||||
@@ -330,6 +394,10 @@ class NavigationViewModel(private val repository: NavigationRepository) : ViewMo
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs reverse geocoding to get street name from coordinates.
|
||||
* @return Street name or empty string
|
||||
*/
|
||||
fun reverseAddress(location: Location): String {
|
||||
val address = repository.reverseAddress(location)
|
||||
val gson = GsonBuilder().serializeNulls().create()
|
||||
@@ -337,6 +405,10 @@ class NavigationViewModel(private val repository: NavigationRepository) : ViewMo
|
||||
return place.address.road
|
||||
}
|
||||
|
||||
/**
|
||||
* Queries Overpass API for nearby amenities of a specific category.
|
||||
* Posts sorted results to elements LiveData.
|
||||
*/
|
||||
fun getAmenities(category: String, location: Location) {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
val amenities = Overpass().getAmenities("amenity", category, location, 5.0)
|
||||
@@ -353,6 +425,10 @@ class NavigationViewModel(private val repository: NavigationRepository) : ViewMo
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Queries Overpass API for speed cameras within a radius.
|
||||
* Posts sorted results to speedCameras LiveData.
|
||||
*/
|
||||
fun getSpeedCameras(location: Location, radius: Double) {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
val amenities = Overpass().getAmenities("highway", "speed_camera", location, radius)
|
||||
@@ -369,6 +445,10 @@ class NavigationViewModel(private val repository: NavigationRepository) : ViewMo
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Queries Overpass API for speed limit on current road using fuzzy matching.
|
||||
* Posts speed limit to maxSpeed LiveData.
|
||||
*/
|
||||
fun getMaxSpeed(location: Location, street: String) {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
val levenshtein = Levenshtein()
|
||||
@@ -387,11 +467,18 @@ class NavigationViewModel(private val repository: NavigationRepository) : ViewMo
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves a place as a favorite in ObjectBox.
|
||||
*/
|
||||
fun saveFavorite(place: Place) {
|
||||
place.category = Constants.FAVORITES
|
||||
savePlace(place)
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves a place to recent destinations in ObjectBox.
|
||||
* Skips fuel stations, charging stations, and pharmacies.
|
||||
*/
|
||||
fun saveRecent(place: Place) {
|
||||
if (place.category == Constants.FUEL_STATION
|
||||
|| place.category == Constants.CHARGING_STATION
|
||||
@@ -403,6 +490,10 @@ class NavigationViewModel(private val repository: NavigationRepository) : ViewMo
|
||||
savePlace(place)
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves a place to ObjectBox, removing existing duplicates first.
|
||||
* Updates the timestamp to current time.
|
||||
*/
|
||||
private fun savePlace(place: Place) {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
try {
|
||||
@@ -426,16 +517,25 @@ class NavigationViewModel(private val repository: NavigationRepository) : ViewMo
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes a place from favorites in ObjectBox.
|
||||
*/
|
||||
fun deleteFavorite(place: Place) {
|
||||
place.category = Constants.FAVORITES
|
||||
deletePlace(place)
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes a place from recent destinations in ObjectBox.
|
||||
*/
|
||||
fun deleteRecent(place: Place) {
|
||||
place.category = Constants.RECENT
|
||||
deletePlace(place)
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes a place from ObjectBox matching name and category.
|
||||
*/
|
||||
fun deletePlace(place: Place) {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
try {
|
||||
@@ -456,6 +556,10 @@ class NavigationViewModel(private val repository: NavigationRepository) : ViewMo
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves search filter settings from preferences.
|
||||
* @return SearchFilter with avoid motorway/tollway flags
|
||||
*/
|
||||
fun getSearchFilter(context: Context): SearchFilter {
|
||||
val repository = getSettingsRepository(context)
|
||||
val avoidMotorway = runBlocking { repository.avoidMotorwayFlow.first() }
|
||||
@@ -463,7 +567,10 @@ class NavigationViewModel(private val repository: NavigationRepository) : ViewMo
|
||||
return SearchFilter(avoidMotorway, avoidTollway)
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Loads recent places with calculated distances for Compose state.
|
||||
* @return SnapshotStateList of recent places with distances
|
||||
*/
|
||||
fun loadPlaces2(
|
||||
context: Context,
|
||||
location: Location,
|
||||
@@ -495,6 +602,10 @@ class NavigationViewModel(private val repository: NavigationRepository) : ViewMo
|
||||
return results.toMutableStateList()
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads recent places as Compose SnapshotStateList.
|
||||
* @return SnapshotStateList of recent places
|
||||
*/
|
||||
fun loadRecentPlace(): SnapshotStateList<Place?> {
|
||||
val results = listOf<Place>()
|
||||
try {
|
||||
@@ -511,5 +622,4 @@ class NavigationViewModel(private val repository: NavigationRepository) : ViewMo
|
||||
}
|
||||
return results.toMutableStateList()
|
||||
}
|
||||
|
||||
}
|
||||
@@ -26,9 +26,9 @@ class RouteCalculator(var routeModel: RouteModel) {
|
||||
routeModel.navState.route.currentStepIndex = step.index
|
||||
step.waypointIndex = wayIndex
|
||||
step.wayPointLocation = location(waypoint[0], waypoint[1])
|
||||
routeModel.navState = routeModel.navState.copy(
|
||||
routeBearing = routeModel.navState.lastLocation.bearingTo(location)
|
||||
)
|
||||
//routeModel.navState = routeModel.navState.copy(
|
||||
// routeBearing = routeModel.navState.lastLocation.bearingTo(location)
|
||||
// )
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,8 @@ package com.kouros.navigation.model
|
||||
|
||||
import android.content.Context
|
||||
import android.location.Location
|
||||
import androidx.car.app.connection.CarConnection.CONNECTION_TYPE_NATIVE
|
||||
import androidx.car.app.connection.CarConnection.CONNECTION_TYPE_PROJECTION
|
||||
import androidx.car.app.navigation.model.Maneuver
|
||||
import com.kouros.navigation.data.Constants.NEXT_STEP_THRESHOLD
|
||||
import com.kouros.navigation.data.Place
|
||||
@@ -33,7 +35,8 @@ open class RouteModel {
|
||||
val currentLocation: Location = location(0.0, 0.0),
|
||||
val routeBearing: Float = 0F,
|
||||
val currentRouteIndex: Int = 0,
|
||||
val destination: Place = Place()
|
||||
val destination: Place = Place(),
|
||||
val carConnection: Int = 0,
|
||||
)
|
||||
|
||||
var navState = NavigationState()
|
||||
@@ -81,9 +84,8 @@ open class RouteModel {
|
||||
fun updateLocation(context: Context, curLocation: Location, viewModel: NavigationViewModel) {
|
||||
navState = navState.copy(currentLocation = curLocation)
|
||||
routeCalculator.findStep(curLocation)
|
||||
val repository = getSettingsRepository(context)
|
||||
val carLocation = runBlocking { repository.carLocationFlow.first() }
|
||||
if (carLocation) {
|
||||
if (navState.carConnection == CONNECTION_TYPE_PROJECTION
|
||||
|| navState.carConnection == CONNECTION_TYPE_NATIVE) {
|
||||
routeCalculator.updateSpeedLimit(curLocation, viewModel)
|
||||
}
|
||||
navState = navState.copy(lastLocation = navState.currentLocation)
|
||||
|
||||
@@ -27,6 +27,9 @@ object GeoUtils {
|
||||
val point = pointFeature.geometry() as Point
|
||||
newLocation.latitude = point.latitude()
|
||||
newLocation.longitude = point.longitude()
|
||||
if (location.hasBearing()) {
|
||||
newLocation.bearing = location.bearing
|
||||
}
|
||||
}
|
||||
return newLocation
|
||||
}
|
||||
|
||||
@@ -52,7 +52,7 @@ fun calculateZoom(speed: Double?): Double {
|
||||
}
|
||||
|
||||
fun previewZoom(previewDistance: Double): Double {
|
||||
when (previewDistance) {
|
||||
when (previewDistance / 1000) {
|
||||
in 0.0..10.0 -> return 13.5
|
||||
in 10.0..20.0 -> return 11.5
|
||||
in 20.0..30.0 -> return 10.5
|
||||
|
||||
10
common/data/src/main/res/drawable/local_gas_station_24.xml
Normal file
10
common/data/src/main/res/drawable/local_gas_station_24.xml
Normal file
@@ -0,0 +1,10 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24"
|
||||
android:tint="?attr/colorControlNormal">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M19.77,7.23l0.01,-0.01 -3.72,-3.72L15,4.56l2.11,2.11c-0.94,0.36 -1.61,1.26 -1.61,2.33 0,1.38 1.12,2.5 2.5,2.5 0.36,0 0.69,-0.08 1,-0.21v7.21c0,0.55 -0.45,1 -1,1s-1,-0.45 -1,-1L17,14c0,-1.1 -0.9,-2 -2,-2h-1L14,5c0,-1.1 -0.9,-2 -2,-2L6,3c-1.1,0 -2,0.9 -2,2v16h10v-7.5h1.5v5c0,1.38 1.12,2.5 2.5,2.5s2.5,-1.12 2.5,-2.5L20.5,9c0,-0.69 -0.28,-1.32 -0.73,-1.77zM12,10L6,10L6,5h6v5zM18,10c-0.55,0 -1,-0.45 -1,-1s0.45,-1 1,-1 1,0.45 1,1 -0.45,1 -1,1z"/>
|
||||
</vector>
|
||||
@@ -1,10 +0,0 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="960"
|
||||
android:viewportHeight="960"
|
||||
android:tint="?attr/colorControlNormal">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M160,840L160,200Q160,167 183.5,143.5Q207,120 240,120L480,120Q513,120 536.5,143.5Q560,167 560,200L560,480L600,480Q633,480 656.5,503.5Q680,527 680,560L680,740Q680,757 691.5,768.5Q703,780 720,780Q737,780 748.5,768.5Q760,757 760,740L760,452Q751,457 741,458.5Q731,460 720,460Q678,460 649,431Q620,402 620,360Q620,328 637.5,302.5Q655,277 684,266L600,182L642,140L790,284Q805,299 812.5,319Q820,339 820,360L820,740Q820,782 791,811Q762,840 720,840Q678,840 649,811Q620,782 620,740L620,540Q620,540 620,540Q620,540 620,540L560,540L560,840L160,840ZM240,400L480,400L480,200Q480,200 480,200Q480,200 480,200L240,200Q240,200 240,200Q240,200 240,200L240,400ZM720,400Q737,400 748.5,388.5Q760,377 760,360Q760,343 748.5,331.5Q737,320 720,320Q703,320 691.5,331.5Q680,343 680,360Q680,377 691.5,388.5Q703,400 720,400ZM240,760L480,760L480,480L240,480L240,760ZM480,760L240,760L240,760L480,760Z"/>
|
||||
</vector>
|
||||
@@ -1,10 +0,0 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="48dp"
|
||||
android:height="48dp"
|
||||
android:viewportWidth="960"
|
||||
android:viewportHeight="960"
|
||||
android:tint="?attr/colorControlNormal">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M160,840L160,180Q160,156 178,138Q196,120 220,120L489,120Q513,120 531,138Q549,156 549,180L549,468L614,468Q634.63,468 649.31,482.69Q664,497.37 664,518L664,737Q664,758.68 679.5,773.34Q695,788 717,788Q739,788 754.5,773.34Q770,758.68 770,737L770,442Q759,448 747,451Q735,454 723,454Q683.52,454 656.26,426.74Q629,399.48 629,360Q629,328.39 647,303.19Q665,278 695,270L600,175L636,140L789,293Q803,307 811.5,323.5Q820,340 820,360L820,737Q820,780.26 790.18,810.13Q760.37,840 717.18,840Q674,840 644,810.13Q614,780.26 614,737L614,518Q614,518 614,518Q614,518 614,518L549,518L549,840L160,840ZM220,408L489,408L489,180Q489,180 489,180Q489,180 489,180L220,180Q220,180 220,180Q220,180 220,180L220,408ZM723,404Q741,404 754,391Q767,378 767,360Q767,342 754,329Q741,316 723,316Q705,316 692,329Q679,342 679,360Q679,378 692,391Q705,404 723,404ZM220,780L489,780L489,468L220,468L220,780ZM489,780L220,780L220,780L489,780Z"/>
|
||||
</vector>
|
||||
@@ -1,10 +0,0 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="960"
|
||||
android:viewportHeight="960"
|
||||
android:tint="?attr/colorControlNormal">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M120,840L120,760L200,520L120,280L120,200L628,200L686,40L780,74L734,200L840,200L840,280L760,520L840,760L840,840L120,840ZM440,680L520,680L520,560L640,560L640,480L520,480L520,360L440,360L440,480L320,480L320,560L440,560L440,680ZM204,760L756,760L676,520L756,280L204,280L284,520L204,760ZM480,520L480,520L480,520L480,520L480,520L480,520Z"/>
|
||||
</vector>
|
||||
@@ -50,5 +50,7 @@
|
||||
<string name="use_car_location">Auto GPS verwenden</string>
|
||||
<string name="tomtom">TomTom\t</string>
|
||||
<string name="options">Optionen</string>
|
||||
<string name="tomtom_api_key">TomTom ApiKey</string>
|
||||
<string name="use_car_settings">Verwende Auto Einstellungen</string>
|
||||
|
||||
</resources>
|
||||
|
||||
@@ -37,4 +37,5 @@
|
||||
<string name="tomtom">TomTom\t</string>
|
||||
<string name="options">Options</string>
|
||||
<string name="tomtom_api_key">TomTom ApiKey</string>
|
||||
<string name="use_car_settings">Use car settings</string>
|
||||
</resources>
|
||||
@@ -38,7 +38,6 @@ material3WindowSizeClass = "1.4.0"
|
||||
uiGraphics = "1.10.3"
|
||||
window = "1.5.1"
|
||||
foundationLayout = "1.10.3"
|
||||
foundationLayoutVersion = "1.10.3"
|
||||
datastorePreferences = "1.2.0"
|
||||
datastoreCore = "1.2.0"
|
||||
|
||||
@@ -81,7 +80,6 @@ androidx-app-automotive = { module = "androidx.car.app:app-automotive", version.
|
||||
androidx-compose-ui-graphics = { group = "androidx.compose.ui", name = "ui-graphics", version.ref = "uiGraphics" }
|
||||
androidx-window = { group = "androidx.window", name = "window", version.ref = "window" }
|
||||
androidx-compose-foundation-layout = { group = "androidx.compose.foundation", name = "foundation-layout", version.ref = "foundationLayout" }
|
||||
androidx-foundation-layout = { group = "androidx.compose.foundation", name = "foundation-layout", version.ref = "foundationLayoutVersion" }
|
||||
androidx-datastore-preferences = { module = "androidx.datastore:datastore-preferences", version.ref = "datastorePreferences" }
|
||||
androidx-datastore-core = { group = "androidx.datastore", name = "datastore-core", version.ref = "datastoreCore" }
|
||||
|
||||
|
||||
Reference in New Issue
Block a user