Diverse Änderungen
This commit is contained in:
@@ -14,8 +14,8 @@ android {
|
|||||||
applicationId = "com.kouros.navigation"
|
applicationId = "com.kouros.navigation"
|
||||||
minSdk = 33
|
minSdk = 33
|
||||||
targetSdk = 36
|
targetSdk = 36
|
||||||
versionCode = 45
|
versionCode = 49
|
||||||
versionName = "0.2.0.45"
|
versionName = "0.2.0.49"
|
||||||
base.archivesName = "navi-$versionName"
|
base.archivesName = "navi-$versionName"
|
||||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||||
}
|
}
|
||||||
@@ -82,6 +82,7 @@ dependencies {
|
|||||||
implementation(libs.koin.androidx.compose)
|
implementation(libs.koin.androidx.compose)
|
||||||
implementation(libs.maplibre.compose)
|
implementation(libs.maplibre.compose)
|
||||||
//implementation(libs.maplibre.composeMaterial3)
|
//implementation(libs.maplibre.composeMaterial3)
|
||||||
|
implementation(libs.androidx.app.projected)
|
||||||
implementation(libs.accompanist.permissions)
|
implementation(libs.accompanist.permissions)
|
||||||
|
|
||||||
implementation(project(":common:data"))
|
implementation(project(":common:data"))
|
||||||
@@ -97,7 +98,7 @@ dependencies {
|
|||||||
implementation("com.github.ticofab:android-gpx-parser:2.3.1")
|
implementation("com.github.ticofab:android-gpx-parser:2.3.1")
|
||||||
implementation(libs.androidx.navigation.compose)
|
implementation(libs.androidx.navigation.compose)
|
||||||
implementation(libs.kotlinx.serialization.json)
|
implementation(libs.kotlinx.serialization.json)
|
||||||
implementation(libs.androidx.foundation.layout)
|
implementation(libs.androidx.compose.foundation.layout)
|
||||||
testImplementation(libs.junit)
|
testImplementation(libs.junit)
|
||||||
androidTestImplementation(libs.androidx.junit)
|
androidTestImplementation(libs.androidx.junit)
|
||||||
androidTestImplementation(libs.androidx.espresso.core)
|
androidTestImplementation(libs.androidx.espresso.core)
|
||||||
|
|||||||
@@ -7,11 +7,16 @@ import android.os.SystemClock
|
|||||||
class MockLocation (private var locationManager: LocationManager) {
|
class MockLocation (private var locationManager: LocationManager) {
|
||||||
|
|
||||||
var curSpeed = 0F
|
var curSpeed = 0F
|
||||||
fun setMockLocation(latitude: Double, longitude: Double) {
|
fun setMockLocation(latitude: Double, longitude: Double, bearing : Float) {
|
||||||
try {
|
try {
|
||||||
// Set mock location for all providers
|
// Set mock location for all providers
|
||||||
setMockLocationForProvider(LocationManager.GPS_PROVIDER, latitude, longitude)
|
setMockLocationForProvider(LocationManager.GPS_PROVIDER, latitude, longitude, bearing)
|
||||||
setMockLocationForProvider(LocationManager.NETWORK_PROVIDER, latitude, longitude)
|
setMockLocationForProvider(
|
||||||
|
LocationManager.NETWORK_PROVIDER,
|
||||||
|
latitude,
|
||||||
|
longitude,
|
||||||
|
bearing
|
||||||
|
)
|
||||||
} catch (e: NumberFormatException) {
|
} catch (e: NumberFormatException) {
|
||||||
} catch (e: SecurityException) {
|
} catch (e: SecurityException) {
|
||||||
} catch (e: Exception) {
|
} 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 {
|
try {
|
||||||
// Check if provider exists
|
// Check if provider exists
|
||||||
if (!locationManager.allProviders.contains(provider)) {
|
if (!locationManager.allProviders.contains(provider)) {
|
||||||
@@ -52,10 +62,10 @@ class MockLocation (private var locationManager: LocationManager) {
|
|||||||
this.speed = 0f
|
this.speed = 0f
|
||||||
this.time = System.currentTimeMillis()
|
this.time = System.currentTimeMillis()
|
||||||
this.elapsedRealtimeNanos = SystemClock.elapsedRealtimeNanos()
|
this.elapsedRealtimeNanos = SystemClock.elapsedRealtimeNanos()
|
||||||
|
|
||||||
this.bearingAccuracyDegrees = 0.0f
|
this.bearingAccuracyDegrees = 0.0f
|
||||||
this.verticalAccuracyMeters = 0.0f
|
this.verticalAccuracyMeters = 0.0f
|
||||||
this.speedAccuracyMetersPerSecond = 0.0f
|
this.speedAccuracyMetersPerSecond = 0.0f
|
||||||
|
this.bearing = bearing
|
||||||
}
|
}
|
||||||
// Set the mock location
|
// Set the mock location
|
||||||
locationManager.setTestProviderLocation(provider, mockLocation)
|
locationManager.setTestProviderLocation(provider, mockLocation)
|
||||||
@@ -79,6 +89,7 @@ class MockLocation (private var locationManager: LocationManager) {
|
|||||||
this.bearingAccuracyDegrees = 0.0f
|
this.bearingAccuracyDegrees = 0.0f
|
||||||
this.verticalAccuracyMeters = 0.0f
|
this.verticalAccuracyMeters = 0.0f
|
||||||
this.speedAccuracyMetersPerSecond = 0.0f
|
this.speedAccuracyMetersPerSecond = 0.0f
|
||||||
|
this.bearing = bearing
|
||||||
}
|
}
|
||||||
locationManager.setTestProviderLocation(provider, mockLocation)
|
locationManager.setTestProviderLocation(provider, mockLocation)
|
||||||
} catch (ex: Exception) {
|
} catch (ex: Exception) {
|
||||||
|
|||||||
@@ -77,19 +77,16 @@ import org.maplibre.compose.location.DesiredAccuracy
|
|||||||
import org.maplibre.compose.location.Location
|
import org.maplibre.compose.location.Location
|
||||||
import org.maplibre.compose.location.rememberDefaultLocationProvider
|
import org.maplibre.compose.location.rememberDefaultLocationProvider
|
||||||
import org.maplibre.compose.location.rememberUserLocationState
|
import org.maplibre.compose.location.rememberUserLocationState
|
||||||
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() {
|
||||||
|
|
||||||
val routeData = MutableLiveData("")
|
val routeData = MutableLiveData("")
|
||||||
|
|
||||||
val routeModel = RouteModel()
|
val routeModel = RouteModel()
|
||||||
var tilt = 50.0
|
var tilt = 50.0
|
||||||
val useMock = false
|
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 {
|
val stepData: MutableLiveData<StepData> by lazy {
|
||||||
MutableLiveData()
|
MutableLiveData()
|
||||||
@@ -125,7 +122,6 @@ class MainActivity : ComponentActivity() {
|
|||||||
private lateinit var fusedLocationClient: FusedLocationProviderClient
|
private lateinit var fusedLocationClient: FusedLocationProviderClient
|
||||||
private lateinit var mock: MockLocation
|
private lateinit var mock: MockLocation
|
||||||
private var loadRecentPlaces = false
|
private var loadRecentPlaces = false
|
||||||
lateinit var baseStyle: BaseStyle.Json
|
|
||||||
|
|
||||||
@RequiresPermission(allOf = [Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.ACCESS_COARSE_LOCATION])
|
@RequiresPermission(allOf = [Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.ACCESS_COARSE_LOCATION])
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
@@ -140,7 +136,7 @@ class MainActivity : ComponentActivity() {
|
|||||||
if (useMock) {
|
if (useMock) {
|
||||||
mock = MockLocation(locationManager)
|
mock = MockLocation(locationManager)
|
||||||
mock.setMockLocation(
|
mock.setMockLocation(
|
||||||
homeVogelhart.latitude, homeVogelhart.longitude
|
homeVogelhart.latitude, homeVogelhart.longitude, 0F
|
||||||
)
|
)
|
||||||
navigationViewModel.route.observe(this, observer)
|
navigationViewModel.route.observe(this, observer)
|
||||||
}
|
}
|
||||||
@@ -190,8 +186,9 @@ class MainActivity : ComponentActivity() {
|
|||||||
|
|
||||||
val appViewModel: AppViewModel = appViewModel()
|
val appViewModel: AppViewModel = appViewModel()
|
||||||
val darkMode by appViewModel.darkMode.collectAsState()
|
val darkMode by appViewModel.darkMode.collectAsState()
|
||||||
|
|
||||||
val sheetPeekHeight = 250.dp
|
val sheetPeekHeight = 250.dp
|
||||||
baseStyle = BaseStyleModel().readStyle(applicationContext, darkMode, darkMode == 1)
|
val baseStyle = BaseStyleModel().readStyle(applicationContext, darkMode, darkMode == 1)
|
||||||
val scaffoldState = rememberBottomSheetScaffoldState()
|
val scaffoldState = rememberBottomSheetScaffoldState()
|
||||||
val snackbarHostState = remember { SnackbarHostState() }
|
val snackbarHostState = remember { SnackbarHostState() }
|
||||||
val scope = rememberCoroutineScope()
|
val scope = rememberCoroutineScope()
|
||||||
@@ -240,7 +237,7 @@ class MainActivity : ComponentActivity() {
|
|||||||
cameraPosition,
|
cameraPosition,
|
||||||
routeData,
|
routeData,
|
||||||
tilt,
|
tilt,
|
||||||
baseStyle
|
baseStyle,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
if (!routeModel.isNavigating()) {
|
if (!routeModel.isNavigating()) {
|
||||||
@@ -304,6 +301,9 @@ class MainActivity : ComponentActivity() {
|
|||||||
fun updateLocation(location: Location?) {
|
fun updateLocation(location: Location?) {
|
||||||
if (location != null && lastLocation.latitude != location.position.latitude && lastLocation.longitude != location.position.longitude) {
|
if (location != null && lastLocation.latitude != location.position.latitude && lastLocation.longitude != location.position.longitude) {
|
||||||
val currentLocation = location(location.position.longitude, location.position.latitude)
|
val currentLocation = location(location.position.longitude, location.position.latitude)
|
||||||
|
if (location.bearing != null) {
|
||||||
|
currentLocation.bearing = location.bearing!!.toFloat()
|
||||||
|
}
|
||||||
if (routeModel.isNavigating()) {
|
if (routeModel.isNavigating()) {
|
||||||
val snapedLocation =
|
val snapedLocation =
|
||||||
snapLocation(currentLocation, routeModel.route.maneuverLocations())
|
snapLocation(currentLocation, routeModel.route.maneuverLocations())
|
||||||
@@ -315,7 +315,15 @@ class MainActivity : ComponentActivity() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun updateLocationInternal(currentLocation: android.location.Location, location: Location?) {
|
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) {
|
with(routeModel) {
|
||||||
if (isNavigating()) {
|
if (isNavigating()) {
|
||||||
updateLocation(applicationContext, currentLocation, navigationViewModel)
|
updateLocation(applicationContext, currentLocation, navigationViewModel)
|
||||||
@@ -348,7 +356,7 @@ class MainActivity : ComponentActivity() {
|
|||||||
closeSheet()
|
closeSheet()
|
||||||
routeModel.stopNavigation(applicationContext)
|
routeModel.stopNavigation(applicationContext)
|
||||||
if (useMock) {
|
if (useMock) {
|
||||||
mock.setMockLocation(latitude, longitude)
|
mock.setMockLocation(latitude, longitude, 0F)
|
||||||
}
|
}
|
||||||
routeData.value = ""
|
routeData.value = ""
|
||||||
stepData.value = StepData("", 0.0, 0, 0, 0, 0.0)
|
stepData.value = StepData("", 0.0, 0, 0, 0, 0.0)
|
||||||
@@ -380,14 +388,18 @@ class MainActivity : ComponentActivity() {
|
|||||||
|
|
||||||
fun simulate() {
|
fun simulate() {
|
||||||
CoroutineScope(Dispatchers.IO).launch {
|
CoroutineScope(Dispatchers.IO).launch {
|
||||||
|
var lastLocation = location(0.0, 0.0)
|
||||||
for ((index, waypoint) in routeModel.curRoute.waypoints.withIndex()) {
|
for ((index, waypoint) in routeModel.curRoute.waypoints.withIndex()) {
|
||||||
|
val curLocation = location(waypoint[0], waypoint[1])
|
||||||
if (routeModel.isNavigating()) {
|
if (routeModel.isNavigating()) {
|
||||||
val deviation = 0.0
|
val deviation = 0.0
|
||||||
if (index in 0..routeModel.curRoute.waypoints.size) {
|
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)
|
Thread.sleep(1000)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
lastLocation = curLocation
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -416,7 +428,7 @@ class MainActivity : ComponentActivity() {
|
|||||||
|
|
||||||
fun testSingleUpdate(latitude: Double, longitude: Double) {
|
fun testSingleUpdate(latitude: Double, longitude: Double) {
|
||||||
if (1 == 1) {
|
if (1 == 1) {
|
||||||
mock.setMockLocation(latitude, longitude)
|
mock.setMockLocation(latitude, longitude, 0F)
|
||||||
} else {
|
} else {
|
||||||
routeModel.updateLocation(
|
routeModel.updateLocation(
|
||||||
applicationContext,
|
applicationContext,
|
||||||
@@ -425,12 +437,12 @@ class MainActivity : ComponentActivity() {
|
|||||||
}
|
}
|
||||||
val step = routeModel.currentStep()
|
val step = routeModel.currentStep()
|
||||||
val nextStep = routeModel.nextStep()
|
val nextStep = routeModel.nextStep()
|
||||||
println("Step: ${step.instruction} ${step.leftStepDistance} ${nextStep.currentManeuverType}")
|
|
||||||
Thread.sleep(1_000)
|
Thread.sleep(1_000)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun gpx(context: Context) {
|
fun gpx(context: Context) {
|
||||||
CoroutineScope(Dispatchers.IO).launch {
|
CoroutineScope(Dispatchers.IO).launch {
|
||||||
|
var lastLocation = location(0.0, 0.0)
|
||||||
val parser = GPXParser()
|
val parser = GPXParser()
|
||||||
val input = context.resources.openRawResource(R.raw.vh)
|
val input = context.resources.openRawResource(R.raw.vh)
|
||||||
val parsedGpx: Gpx? = parser.parse(input) // consider using a background thread
|
val parsedGpx: Gpx? = parser.parse(input) // consider using a background thread
|
||||||
@@ -441,18 +453,23 @@ class MainActivity : ComponentActivity() {
|
|||||||
segments!!.forEach { seg ->
|
segments!!.forEach { seg ->
|
||||||
var lastTime = DateTime.now()
|
var lastTime = DateTime.now()
|
||||||
seg!!.trackPoints.forEach { p ->
|
seg!!.trackPoints.forEach { p ->
|
||||||
|
val curLocation = location(p.longitude, p.latitude)
|
||||||
val ext = p.extensions
|
val ext = p.extensions
|
||||||
val speed: Double?
|
val speed: Double?
|
||||||
if (ext != null) {
|
if (ext != null) {
|
||||||
speed = ext.speed
|
speed = ext.speed
|
||||||
mock.curSpeed = speed.toFloat()
|
mock.curSpeed = speed.toFloat()
|
||||||
}
|
}
|
||||||
|
|
||||||
val duration = p.time.millis - lastTime.millis
|
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) {
|
if (duration > 0) {
|
||||||
delay(duration / 5)
|
delay(duration / 5)
|
||||||
}
|
}
|
||||||
lastTime = p.time
|
lastTime = p.time
|
||||||
|
lastLocation = curLocation
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,26 +4,21 @@ import android.content.Context
|
|||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.PaddingValues
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.collectAsState
|
import androidx.compose.runtime.collectAsState
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.livedata.observeAsState
|
import androidx.compose.runtime.livedata.observeAsState
|
||||||
import androidx.compose.runtime.remember
|
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.lifecycle.MutableLiveData
|
import androidx.lifecycle.MutableLiveData
|
||||||
import androidx.lifecycle.ViewModel
|
|
||||||
import androidx.lifecycle.ViewModelProvider
|
|
||||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
|
||||||
import androidx.window.layout.WindowMetricsCalculator
|
import androidx.window.layout.WindowMetricsCalculator
|
||||||
import com.kouros.navigation.car.ViewStyle
|
import com.kouros.navigation.car.ViewStyle
|
||||||
import com.kouros.navigation.car.map.MapLibre
|
import com.kouros.navigation.car.map.MapLibre
|
||||||
import com.kouros.navigation.car.map.NavigationImage
|
import com.kouros.navigation.car.map.NavigationImage
|
||||||
import com.kouros.navigation.car.map.rememberBaseStyle
|
|
||||||
import com.kouros.navigation.data.StepData
|
import com.kouros.navigation.data.StepData
|
||||||
import com.kouros.navigation.ui.app.AppViewModel
|
import com.kouros.navigation.ui.app.AppViewModel
|
||||||
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.CameraPosition
|
||||||
import org.maplibre.compose.camera.rememberCameraState
|
import org.maplibre.compose.camera.rememberCameraState
|
||||||
import org.maplibre.compose.location.LocationTrackingEffect
|
import org.maplibre.compose.location.LocationTrackingEffect
|
||||||
@@ -64,8 +59,6 @@ fun MapView(
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
val rememberBaseStyle = rememberBaseStyle(baseStyle)
|
|
||||||
|
|
||||||
val appViewModel: AppViewModel = appViewModel()
|
val appViewModel: AppViewModel = appViewModel()
|
||||||
val showBuildings by appViewModel.show3D.collectAsState()
|
val showBuildings by appViewModel.show3D.collectAsState()
|
||||||
|
|
||||||
@@ -73,9 +66,8 @@ fun MapView(
|
|||||||
NavigationInfo(step, nextStep)
|
NavigationInfo(step, nextStep)
|
||||||
Box(contentAlignment = Alignment.Center) {
|
Box(contentAlignment = Alignment.Center) {
|
||||||
MapLibre(
|
MapLibre(
|
||||||
applicationContext,
|
|
||||||
cameraState,
|
cameraState,
|
||||||
rememberBaseStyle,
|
baseStyle,
|
||||||
route,
|
route,
|
||||||
emptyMap(),
|
emptyMap(),
|
||||||
ViewStyle.VIEW,
|
ViewStyle.VIEW,
|
||||||
|
|||||||
@@ -8,9 +8,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.car.permission.CAR_SPEED"/>
|
||||||
<uses-permission android:name="androidx.car.app.ACCESS_SURFACE" />
|
<uses-permission android:name="androidx.car.app.ACCESS_SURFACE" />
|
||||||
|
|
||||||
<!-- Various required feature settings for an automotive app. -->
|
|
||||||
<uses-feature
|
<uses-feature
|
||||||
android:name="android.hardware.type.automotive"
|
android:name="android.hardware.type.automotive"
|
||||||
android:required="true" />
|
android:required="true" />
|
||||||
|
|||||||
@@ -46,7 +46,6 @@ dependencies {
|
|||||||
implementation(libs.androidx.lifecycle.runtime.ktx)
|
implementation(libs.androidx.lifecycle.runtime.ktx)
|
||||||
implementation(libs.androidx.ui)
|
implementation(libs.androidx.ui)
|
||||||
implementation(libs.maplibre.compose)
|
implementation(libs.maplibre.compose)
|
||||||
implementation(libs.androidx.app.projected)
|
|
||||||
//implementation(libs.maplibre.composeMaterial3)
|
//implementation(libs.maplibre.composeMaterial3)
|
||||||
|
|
||||||
implementation(project(":common:data"))
|
implementation(project(":common:data"))
|
||||||
|
|||||||
@@ -49,6 +49,7 @@
|
|||||||
<category android:name="androidx.car.app.category.WEATHER" />
|
<category android:name="androidx.car.app.category.WEATHER" />
|
||||||
<category android:name="androidx.car.app.category.POI"/>
|
<category android:name="androidx.car.app.category.POI"/>
|
||||||
<category android:name="androidx.car.app.category.NAVIGATION"/>
|
<category android:name="androidx.car.app.category.NAVIGATION"/>
|
||||||
|
<category android:name="androidx.car.app.category.FEATURE_CLUSTER"/>
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</service>
|
</service>
|
||||||
</application>
|
</application>
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ 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
|
||||||
import androidx.car.app.Session
|
import androidx.car.app.Session
|
||||||
|
import androidx.car.app.connection.CarConnection
|
||||||
import androidx.car.app.hardware.CarHardwareManager
|
import androidx.car.app.hardware.CarHardwareManager
|
||||||
import androidx.car.app.hardware.common.CarValue
|
import androidx.car.app.hardware.common.CarValue
|
||||||
import androidx.car.app.hardware.common.OnCarDataAvailableListener
|
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_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.data.RouteEngine
|
import com.kouros.navigation.data.RouteEngine
|
||||||
import com.kouros.navigation.data.osrm.OsrmRepository
|
import com.kouros.navigation.data.osrm.OsrmRepository
|
||||||
import com.kouros.navigation.data.tomtom.TomTomRepository
|
import com.kouros.navigation.data.tomtom.TomTomRepository
|
||||||
@@ -48,7 +50,7 @@ import kotlinx.coroutines.awaitCancellation
|
|||||||
import kotlinx.coroutines.flow.first
|
import kotlinx.coroutines.flow.first
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
|
import android.Manifest.permission
|
||||||
|
|
||||||
class NavigationSession : Session(), NavigationScreen.Listener {
|
class NavigationSession : Session(), NavigationScreen.Listener {
|
||||||
|
|
||||||
@@ -82,14 +84,7 @@ class NavigationSession : Session(), NavigationScreen.Listener {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onDestroy(owner: LifecycleOwner) {
|
override fun onDestroy(owner: LifecycleOwner) {
|
||||||
val carInfo = carContext.getCarService(CarHardwareManager::class.java).carInfo
|
removeSensors()
|
||||||
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)
|
|
||||||
Log.i(TAG, "In onDestroy()")
|
Log.i(TAG, "In onDestroy()")
|
||||||
val locationManager =
|
val locationManager =
|
||||||
carContext.getSystemService(Context.LOCATION_SERVICE) as 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 {
|
override fun onCreateScreen(intent: Intent): Screen {
|
||||||
|
|
||||||
@@ -153,26 +166,23 @@ class NavigationSession : Session(), NavigationScreen.Listener {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// lifecycleScope.launch {
|
|
||||||
|
|
||||||
|
|
||||||
//}
|
|
||||||
|
|
||||||
|
|
||||||
navigationViewModel = getViewModel(carContext)
|
navigationViewModel = getViewModel(carContext)
|
||||||
|
|
||||||
navigationViewModel.routingEngine.observe(this, ::onRoutingEngineStateUpdated)
|
navigationViewModel.routingEngine.observe(this, ::onRoutingEngineStateUpdated)
|
||||||
|
|
||||||
|
navigationViewModel.permissionGranted.observe(this, ::onPermissionGranted)
|
||||||
|
|
||||||
routeModel = RouteCarModel()
|
routeModel = RouteCarModel()
|
||||||
|
|
||||||
|
CarConnection(carContext).type.observe(this, ::onConnectionStateUpdated)
|
||||||
|
|
||||||
surfaceRenderer = SurfaceRenderer(carContext, lifecycle, routeModel, viewModelStoreOwner)
|
surfaceRenderer = SurfaceRenderer(carContext, lifecycle, routeModel, viewModelStoreOwner)
|
||||||
|
|
||||||
navigationScreen =
|
navigationScreen =
|
||||||
NavigationScreen(carContext, surfaceRenderer, routeModel, this, navigationViewModel)
|
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
|
== PackageManager.PERMISSION_GRANTED
|
||||||
&& carContext.checkSelfPermission("com.google.android.gms.permission.CAR_SPEED") == PackageManager.PERMISSION_GRANTED
|
|
||||||
&& !useContacts
|
&& !useContacts
|
||||||
|| (useContacts && carContext.checkSelfPermission(Manifest.permission.READ_CONTACTS)
|
|| (useContacts && carContext.checkSelfPermission(Manifest.permission.READ_CONTACTS)
|
||||||
== PackageManager.PERMISSION_GRANTED)
|
== PackageManager.PERMISSION_GRANTED)
|
||||||
@@ -180,27 +190,24 @@ class NavigationSession : Session(), NavigationScreen.Listener {
|
|||||||
requestLocationUpdates()
|
requestLocationUpdates()
|
||||||
} else {
|
} else {
|
||||||
// If we do not have the location permission, show the request permission screen.
|
// 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 =
|
val screenManager =
|
||||||
carContext.getCarService(ScreenManager::class.java)
|
carContext.getCarService(ScreenManager::class.java)
|
||||||
screenManager
|
screenManager
|
||||||
.push(navigationScreen)
|
.push(navigationScreen)
|
||||||
return RequestPermissionScreen(
|
return RequestPermissionScreen(
|
||||||
carContext,
|
carContext,
|
||||||
mLocationPermissionCheckCallback = {
|
permissionCheckCallback = {
|
||||||
screenManager.pop()
|
screenManager.pop()
|
||||||
},
|
},
|
||||||
mContactsPermissionCheckCallback = {
|
|
||||||
screenManager.pop()
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
addSensors()
|
|
||||||
|
|
||||||
return navigationScreen
|
return navigationScreen
|
||||||
}
|
}
|
||||||
|
|
||||||
fun addSensors() {
|
fun addSensors(connectionState: Int) {
|
||||||
val carInfo = carContext.getCarService(CarHardwareManager::class.java).carInfo
|
val carInfo = carContext.getCarService(CarHardwareManager::class.java).carInfo
|
||||||
val repository = getSettingsRepository(carContext)
|
val repository = getSettingsRepository(carContext)
|
||||||
val useCarLocation = runBlocking { repository.carLocationFlow.first() }
|
val useCarLocation = runBlocking { repository.carLocationFlow.first() }
|
||||||
@@ -215,7 +222,24 @@ class NavigationSession : Session(), NavigationScreen.Listener {
|
|||||||
carLocationListener
|
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) {
|
override fun onNewIntent(intent: Intent) {
|
||||||
@@ -277,6 +301,9 @@ class NavigationSession : Session(), NavigationScreen.Listener {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun updateLocation(location: Location) {
|
fun updateLocation(location: Location) {
|
||||||
|
if (location.hasBearing()) {
|
||||||
|
routeModel.navState = routeModel.navState.copy(routeBearing = location.bearing)
|
||||||
|
}
|
||||||
if (routeModel.isNavigating()) {
|
if (routeModel.isNavigating()) {
|
||||||
navigationScreen.updateTrip(location)
|
navigationScreen.updateTrip(location)
|
||||||
if (!routeModel.navState.arrived) {
|
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.MapLibre
|
||||||
import com.kouros.navigation.car.map.cameraState
|
import com.kouros.navigation.car.map.cameraState
|
||||||
import com.kouros.navigation.car.map.getPaddingValues
|
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.car.navigation.RouteCarModel
|
||||||
import com.kouros.navigation.data.Constants.homeVogelhart
|
import com.kouros.navigation.data.Constants.homeVogelhart
|
||||||
import com.kouros.navigation.data.ObjectBox
|
import com.kouros.navigation.data.ObjectBox
|
||||||
@@ -79,6 +78,7 @@ class SurfaceRenderer(
|
|||||||
val trafficData = MutableLiveData(emptyMap<String, String>())
|
val trafficData = MutableLiveData(emptyMap<String, String>())
|
||||||
val speedCamerasData = MutableLiveData("")
|
val speedCamerasData = MutableLiveData("")
|
||||||
val speed = MutableLiveData(0F)
|
val speed = MutableLiveData(0F)
|
||||||
|
|
||||||
val maxSpeed = MutableLiveData(0)
|
val maxSpeed = MutableLiveData(0)
|
||||||
var viewStyle = ViewStyle.VIEW
|
var viewStyle = ViewStyle.VIEW
|
||||||
lateinit var centerLocation: Location
|
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) {
|
fun onBaseStyleStateUpdated(style: BaseStyle) {
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -190,29 +182,24 @@ class SurfaceRenderer(
|
|||||||
@Composable
|
@Composable
|
||||||
fun MapView() {
|
fun MapView() {
|
||||||
|
|
||||||
|
|
||||||
//val appViewModel: AppViewModel = appViewModel(viewModelStoreOwner)
|
|
||||||
//val darkMode by appViewModel.darkMode.collectAsState()
|
|
||||||
val darkMode = settingsViewModel(carContext, viewModelStoreOwner).darkMode.collectAsState().value
|
val darkMode = settingsViewModel(carContext, viewModelStoreOwner).darkMode.collectAsState().value
|
||||||
|
val showBuildings = settingsViewModel(carContext, viewModelStoreOwner).show3D.collectAsState().value
|
||||||
val baseStyle = BaseStyleModel().readStyle(carContext, darkMode, carContext.isDarkMode)
|
|
||||||
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 traffic: Map<String, String> ? by trafficData.observeAsState()
|
val traffic: Map<String, String> ? by trafficData.observeAsState()
|
||||||
val speedCameras: String? by speedCamerasData.observeAsState()
|
val speedCameras: String? by speedCamerasData.observeAsState()
|
||||||
val paddingValues = getPaddingValues(height, viewStyle)
|
val paddingValues = getPaddingValues(height, viewStyle)
|
||||||
val cameraState = cameraState(paddingValues, position, tilt)
|
val cameraState = cameraState(paddingValues, position, tilt)
|
||||||
val rememberBaseStyle = rememberBaseStyle(baseStyle)
|
val baseStyle = BaseStyleModel().readStyle(carContext, darkMode, carContext.isDarkMode)
|
||||||
|
|
||||||
MapLibre(
|
MapLibre(
|
||||||
carContext,
|
|
||||||
cameraState,
|
cameraState,
|
||||||
rememberBaseStyle,
|
baseStyle,
|
||||||
route,
|
route,
|
||||||
traffic,
|
traffic,
|
||||||
viewStyle,
|
viewStyle,
|
||||||
speedCameras,
|
speedCameras,
|
||||||
false
|
showBuildings
|
||||||
)
|
)
|
||||||
ShowPosition(cameraState, position, paddingValues)
|
ShowPosition(cameraState, position, paddingValues)
|
||||||
}
|
}
|
||||||
@@ -226,12 +213,12 @@ class SurfaceRenderer(
|
|||||||
val cameraDuration =
|
val cameraDuration =
|
||||||
duration(viewStyle == ViewStyle.PREVIEW, position!!.bearing, lastBearing)
|
duration(viewStyle == ViewStyle.PREVIEW, position!!.bearing, lastBearing)
|
||||||
val currentSpeed: Float? by speed.observeAsState()
|
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) {
|
if (viewStyle == ViewStyle.VIEW || viewStyle == ViewStyle.PAN_VIEW) {
|
||||||
DrawNavigationImages(
|
DrawNavigationImages(
|
||||||
paddingValues,
|
paddingValues,
|
||||||
currentSpeed,
|
currentSpeed,
|
||||||
maxSpeed!!,
|
speed!!,
|
||||||
width,
|
width,
|
||||||
height
|
height
|
||||||
)
|
)
|
||||||
@@ -251,7 +238,6 @@ class SurfaceRenderer(
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onCreate(owner: LifecycleOwner) {
|
override fun onCreate(owner: LifecycleOwner) {
|
||||||
CarConnection(carContext).type.observe(owner, ::onConnectionStateUpdated)
|
|
||||||
style.observe(owner, :: onBaseStyleStateUpdated)
|
style.observe(owner, :: onBaseStyleStateUpdated)
|
||||||
Log.i(TAG, "SurfaceRenderer created")
|
Log.i(TAG, "SurfaceRenderer created")
|
||||||
carContext.getCarService(AppManager::class.java)
|
carContext.getCarService(AppManager::class.java)
|
||||||
@@ -281,15 +267,19 @@ class SurfaceRenderer(
|
|||||||
fun updateLocation(location: Location) {
|
fun updateLocation(location: Location) {
|
||||||
synchronized(this) {
|
synchronized(this) {
|
||||||
if (viewStyle == ViewStyle.VIEW || viewStyle == ViewStyle.PAN_VIEW) {
|
if (viewStyle == ViewStyle.VIEW || viewStyle == ViewStyle.PAN_VIEW) {
|
||||||
val bearing = if (carOrientation == 999F)
|
val bearing = if (carOrientation == 999F) {
|
||||||
bearing(
|
if (location.hasBearing()) {
|
||||||
lastLocation,
|
location.bearing.toDouble()
|
||||||
location,
|
} else {
|
||||||
cameraPosition.value!!.bearing
|
bearing(
|
||||||
) else {
|
lastLocation,
|
||||||
|
location,
|
||||||
|
cameraPosition.value!!.bearing
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
carOrientation.toDouble()
|
carOrientation.toDouble()
|
||||||
}
|
}
|
||||||
println("Bearing $bearing")
|
|
||||||
val zoom = if (viewStyle == ViewStyle.VIEW) {
|
val zoom = if (viewStyle == ViewStyle.VIEW) {
|
||||||
calculateZoom(location.speed.toDouble())
|
calculateZoom(location.speed.toDouble())
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
package com.kouros.navigation.car.map
|
package com.kouros.navigation.car.map
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import android.location.Location
|
import android.location.Location
|
||||||
import androidx.compose.foundation.Canvas
|
import androidx.compose.foundation.Canvas
|
||||||
import androidx.compose.foundation.layout.Box
|
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.foundation.layout.size
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.getValue
|
|
||||||
import androidx.compose.runtime.mutableStateOf
|
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
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.text.rememberTextMeasurer
|
||||||
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.lifecycle.ViewModel
|
|
||||||
import androidx.lifecycle.ViewModelProvider
|
|
||||||
import com.kouros.data.R
|
import com.kouros.data.R
|
||||||
import com.kouros.navigation.car.ViewStyle
|
import com.kouros.navigation.car.ViewStyle
|
||||||
import com.kouros.navigation.data.Constants
|
import com.kouros.navigation.data.Constants
|
||||||
import com.kouros.navigation.data.NavigationColor
|
import com.kouros.navigation.data.NavigationColor
|
||||||
import com.kouros.navigation.data.RouteColor
|
import com.kouros.navigation.data.RouteColor
|
||||||
import com.kouros.navigation.data.SpeedColor
|
import com.kouros.navigation.data.SpeedColor
|
||||||
import com.kouros.navigation.data.datastore.DataStoreManager
|
|
||||||
import com.kouros.navigation.model.RouteModel
|
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.CameraPosition
|
||||||
import org.maplibre.compose.camera.CameraState
|
import org.maplibre.compose.camera.CameraState
|
||||||
import org.maplibre.compose.camera.rememberCameraState
|
import org.maplibre.compose.camera.rememberCameraState
|
||||||
@@ -89,7 +81,6 @@ fun cameraState(
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun MapLibre(
|
fun MapLibre(
|
||||||
context: Context,
|
|
||||||
cameraState: CameraState,
|
cameraState: CameraState,
|
||||||
baseStyle: BaseStyle.Json,
|
baseStyle: BaseStyle.Json,
|
||||||
route: String?,
|
route: String?,
|
||||||
@@ -229,15 +220,15 @@ fun trafficColor(key: String): Expression<ColorValue> {
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun AmenityLayer(routeData: String?) {
|
fun AmenityLayer(routeData: String?) {
|
||||||
if (routeData != null && routeData.isNotEmpty()) {
|
if (!routeData.isNullOrEmpty()) {
|
||||||
var color = const(Color.Red)
|
var color = const(Color.Red)
|
||||||
var img = image(painterResource(R.drawable.local_pharmacy_48px), drawAsSdf = true)
|
var img = image(painterResource(R.drawable.local_pharmacy_48px), drawAsSdf = true)
|
||||||
if (routeData.contains(Constants.CHARGING_STATION)) {
|
if (routeData.contains(Constants.CHARGING_STATION)) {
|
||||||
color = const(Color.Green)
|
color = const(Color(0xFF054603))
|
||||||
img = image(painterResource(R.drawable.ev_station_48px), drawAsSdf = true)
|
img = image(painterResource(R.drawable.ev_station_48px), drawAsSdf = true)
|
||||||
} else if (routeData.contains(Constants.FUEL_STATION)) {
|
} else if (routeData.contains(Constants.FUEL_STATION)) {
|
||||||
color = const(Color.Black)
|
color = const(Color.Blue)
|
||||||
img = image(painterResource(R.drawable.local_gas_station_48px), drawAsSdf = true)
|
img = image(painterResource(R.drawable.local_gas_station_24), drawAsSdf = true)
|
||||||
}
|
}
|
||||||
val routes = rememberGeoJsonSource(GeoJsonData.JsonString(routeData))
|
val routes = rememberGeoJsonSource(GeoJsonData.JsonString(routeData))
|
||||||
SymbolLayer(
|
SymbolLayer(
|
||||||
@@ -261,7 +252,7 @@ fun AmenityLayer(routeData: String?) {
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun SpeedCameraLayer(speedCameras: String?) {
|
fun SpeedCameraLayer(speedCameras: String?) {
|
||||||
if (speedCameras != null && speedCameras.isNotEmpty()) {
|
if (!speedCameras.isNullOrEmpty()) {
|
||||||
val color = const(Color.Red)
|
val color = const(Color.Red)
|
||||||
val cameraSource = rememberGeoJsonSource(GeoJsonData.JsonString(speedCameras))
|
val cameraSource = rememberGeoJsonSource(GeoJsonData.JsonString(speedCameras))
|
||||||
SymbolLayer(
|
SymbolLayer(
|
||||||
@@ -306,7 +297,7 @@ fun DrawNavigationImages(
|
|||||||
if (speed != null) {
|
if (speed != null) {
|
||||||
CurrentSpeed(width, height, speed, maxSpeed)
|
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)
|
MaxSpeed(width, height, maxSpeed)
|
||||||
}
|
}
|
||||||
//DebugInfo(width, height, routeModel)
|
//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 {
|
fun getPaddingValues(height: Int, viewStyle: ViewStyle): PaddingValues {
|
||||||
return when (viewStyle) {
|
return when (viewStyle) {
|
||||||
ViewStyle.VIEW, ViewStyle.PAN_VIEW -> PaddingValues(
|
ViewStyle.VIEW, ViewStyle.PAN_VIEW -> PaddingValues(
|
||||||
|
|||||||
@@ -76,7 +76,7 @@ class CategoriesScreen(
|
|||||||
|
|
||||||
fun carIcon(context: CarContext, id: String): CarIcon {
|
fun carIcon(context: CarContext, id: String): CarIcon {
|
||||||
val resId = when (id) {
|
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
|
PHARMACY -> R.drawable.local_pharmacy_48px
|
||||||
CHARGING_STATION -> R.drawable.ev_station_48px
|
CHARGING_STATION -> R.drawable.ev_station_48px
|
||||||
else -> {}
|
else -> {}
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ class DarkModeSettings(private val carContext: CarContext) : Screen(carContext)
|
|||||||
)
|
)
|
||||||
.addItem(
|
.addItem(
|
||||||
buildRowForTemplate(
|
buildRowForTemplate(
|
||||||
R.string.use_telephon_settings,
|
R.string.use_car_settings,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
.setOnSelectedListener { index: Int ->
|
.setOnSelectedListener { index: Int ->
|
||||||
@@ -74,14 +74,6 @@ class DarkModeSettings(private val carContext: CarContext) : Screen(carContext)
|
|||||||
|
|
||||||
private fun onSelected(index: Int) {
|
private fun onSelected(index: Int) {
|
||||||
settingsViewModel.onDarkModeChanged(index)
|
settingsViewModel.onDarkModeChanged(index)
|
||||||
CarToast.makeText(
|
|
||||||
carContext,
|
|
||||||
(carContext
|
|
||||||
.getString(R.string.display_settings)
|
|
||||||
+ ":"
|
|
||||||
+ " " + index), CarToast.LENGTH_LONG
|
|
||||||
)
|
|
||||||
.show()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun buildRowForTemplate(title: Int): Row {
|
private fun buildRowForTemplate(title: Int): Row {
|
||||||
|
|||||||
@@ -1,11 +1,14 @@
|
|||||||
package com.kouros.navigation.car.screen
|
package com.kouros.navigation.car.screen
|
||||||
|
|
||||||
|
import android.Manifest
|
||||||
|
import android.content.pm.PackageManager
|
||||||
import android.location.Location
|
import android.location.Location
|
||||||
import android.location.LocationManager
|
import android.location.LocationManager
|
||||||
import android.os.CountDownTimer
|
import android.os.CountDownTimer
|
||||||
import android.os.Handler
|
import android.os.Handler
|
||||||
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.model.Action
|
import androidx.car.app.model.Action
|
||||||
import androidx.car.app.model.Action.FLAG_DEFAULT
|
import androidx.car.app.model.Action.FLAG_DEFAULT
|
||||||
import androidx.car.app.model.ActionStrip
|
import androidx.car.app.model.ActionStrip
|
||||||
@@ -80,7 +83,7 @@ class NavigationScreen(
|
|||||||
invalidate()
|
invalidate()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
val trafficObserver = Observer<Map<String, String> > { traffic ->
|
val trafficObserver = Observer<Map<String, String>> { traffic ->
|
||||||
surfaceRenderer.setTrafficData(traffic)
|
surfaceRenderer.setTrafficData(traffic)
|
||||||
invalidate()
|
invalidate()
|
||||||
}
|
}
|
||||||
@@ -111,6 +114,9 @@ class NavigationScreen(
|
|||||||
surfaceRenderer.speedCamerasData.value = speedData
|
surfaceRenderer.speedCamerasData.value = speedData
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val maxSpeedObserver = Observer<Int> { speed ->
|
||||||
|
surfaceRenderer.maxSpeed.value = speed
|
||||||
|
}
|
||||||
|
|
||||||
init {
|
init {
|
||||||
navigationViewModel.route.observe(this, observer)
|
navigationViewModel.route.observe(this, observer)
|
||||||
@@ -118,6 +124,7 @@ class NavigationScreen(
|
|||||||
navigationViewModel.recentPlace.observe(this, recentObserver)
|
navigationViewModel.recentPlace.observe(this, recentObserver)
|
||||||
navigationViewModel.placeLocation.observe(this, placeObserver)
|
navigationViewModel.placeLocation.observe(this, placeObserver)
|
||||||
navigationViewModel.speedCameras.observe(this, speedObserver)
|
navigationViewModel.speedCameras.observe(this, speedObserver)
|
||||||
|
navigationViewModel.maxSpeed.observe(this, maxSpeedObserver)
|
||||||
lifecycleScope.launch {
|
lifecycleScope.launch {
|
||||||
getSettingsViewModel(carContext).routingEngine.collect {
|
getSettingsViewModel(carContext).routingEngine.collect {
|
||||||
}
|
}
|
||||||
@@ -497,7 +504,7 @@ class NavigationScreen(
|
|||||||
}
|
}
|
||||||
updateSpeedCamera(location)
|
updateSpeedCamera(location)
|
||||||
with(routeModel) {
|
with(routeModel) {
|
||||||
updateLocation(carContext,location, navigationViewModel)
|
updateLocation(carContext, location, navigationViewModel)
|
||||||
if ((navState.maneuverType == Maneuver.TYPE_DESTINATION
|
if ((navState.maneuverType == Maneuver.TYPE_DESTINATION
|
||||||
|| navState.maneuverType == Maneuver.TYPE_DESTINATION_LEFT
|
|| navState.maneuverType == Maneuver.TYPE_DESTINATION_LEFT
|
||||||
|| navState.maneuverType == Maneuver.TYPE_DESTINATION_RIGHT
|
|| navState.maneuverType == Maneuver.TYPE_DESTINATION_RIGHT
|
||||||
@@ -540,7 +547,7 @@ class NavigationScreen(
|
|||||||
val bearingSpeedCamera = if (camera.tags.direction != null) {
|
val bearingSpeedCamera = if (camera.tags.direction != null) {
|
||||||
try {
|
try {
|
||||||
camera.tags.direction!!.toFloat()
|
camera.tags.direction!!.toFloat()
|
||||||
} catch ( e: Exception) {
|
} catch (e: Exception) {
|
||||||
0F
|
0F
|
||||||
}
|
}
|
||||||
} else {
|
} 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 {
|
enum class NavigationType {
|
||||||
|
|||||||
@@ -16,7 +16,10 @@ import com.kouros.navigation.utils.getSettingsViewModel
|
|||||||
import kotlinx.coroutines.launch
|
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) {
|
Screen(carContext) {
|
||||||
|
|
||||||
private var motorWayToggleState = false
|
private var motorWayToggleState = false
|
||||||
@@ -79,6 +82,12 @@ class NavigationSettings(private val carContext: CarContext, private var navigat
|
|||||||
R.string.routing_engine
|
R.string.routing_engine
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
.addItem(
|
||||||
|
buildRowForScreenTemplate(
|
||||||
|
PasswordSettings(carContext, navigationViewModel),
|
||||||
|
R.string.tomtom_api_key
|
||||||
|
)
|
||||||
|
)
|
||||||
return ListTemplate.Builder()
|
return ListTemplate.Builder()
|
||||||
.setSingleList(listBuilder.build())
|
.setSingleList(listBuilder.build())
|
||||||
.setHeader(
|
.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. */
|
/** Screen for asking the user to grant location permission. */
|
||||||
class RequestPermissionScreen(
|
class RequestPermissionScreen(
|
||||||
carContext: CarContext,
|
carContext: CarContext,
|
||||||
var mLocationPermissionCheckCallback: LocationPermissionCheckCallback,
|
var permissionCheckCallback: PermissionCheckCallback,
|
||||||
var mContactsPermissionCheckCallback: LocationPermissionCheckCallback,
|
//var mContactsPermissionCheckCallback: LocationPermissionCheckCallback,
|
||||||
|
val permissions: MutableList<String?> = ArrayList()
|
||||||
) : Screen(carContext) {
|
) : Screen(carContext) {
|
||||||
/** Callback called when the location permission is granted. */
|
|
||||||
fun interface LocationPermissionCheckCallback {
|
/** Callback called when the permission is granted. */
|
||||||
/** Callback called when the location permission is granted. */
|
fun interface PermissionCheckCallback {
|
||||||
|
/** Callback called when the permission is granted. */
|
||||||
fun onPermissionGranted()
|
fun onPermissionGranted()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onGetTemplate(): Template {
|
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 {
|
val listener: OnClickListener = ParkedOnlyOnClickListener.create {
|
||||||
carContext.requestPermissions(
|
carContext.requestPermissions(
|
||||||
@@ -41,8 +45,8 @@ class RequestPermissionScreen(
|
|||||||
CarToast.LENGTH_LONG
|
CarToast.LENGTH_LONG
|
||||||
).show()
|
).show()
|
||||||
if (!approved!!.isEmpty()) {
|
if (!approved!!.isEmpty()) {
|
||||||
mLocationPermissionCheckCallback.onPermissionGranted()
|
permissionCheckCallback.onPermissionGranted()
|
||||||
mContactsPermissionCheckCallback.onPermissionGranted()
|
//mContactsPermissionCheckCallback.onPermissionGranted()
|
||||||
finish()
|
finish()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -79,7 +79,7 @@ class RoutePreviewScreen(
|
|||||||
|
|
||||||
val itemListBuilder = ItemList.Builder()
|
val itemListBuilder = ItemList.Builder()
|
||||||
var i = 0
|
var i = 0
|
||||||
routeModel.route.routes.forEach { it ->
|
routeModel.route.routes.forEach { _ ->
|
||||||
itemListBuilder.addItem(createRow(i++, navigateAction))
|
itemListBuilder.addItem(createRow(i++, navigateAction))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -95,7 +95,7 @@ class RoutePreviewScreen(
|
|||||||
.build()
|
.build()
|
||||||
|
|
||||||
val message =
|
val message =
|
||||||
if (routeModel.isNavigating() && routeModel.curRoute.waypoints!!.isNotEmpty()) {
|
if (routeModel.isNavigating() && routeModel.curRoute.waypoints.isNotEmpty()) {
|
||||||
createRouteText(0)
|
createRouteText(0)
|
||||||
} else {
|
} else {
|
||||||
CarText.Builder("Wait")
|
CarText.Builder("Wait")
|
||||||
@@ -190,7 +190,7 @@ class RoutePreviewScreen(
|
|||||||
private fun createRouteText(index: Int): CarText {
|
private fun createRouteText(index: Int): CarText {
|
||||||
val time = routeModel.route.routes[index].summary.duration
|
val time = routeModel.route.routes[index].summary.duration
|
||||||
val length =
|
val length =
|
||||||
BigDecimal(routeModel.route.routes[index].summary.distance).setScale(
|
BigDecimal((routeModel.route.routes[index].summary.distance) / 1000).setScale(
|
||||||
1,
|
1,
|
||||||
RoundingMode.HALF_EVEN
|
RoundingMode.HALF_EVEN
|
||||||
)
|
)
|
||||||
@@ -207,7 +207,7 @@ class RoutePreviewScreen(
|
|||||||
val titleText = "$index"
|
val titleText = "$index"
|
||||||
return Row.Builder()
|
return Row.Builder()
|
||||||
.setTitle(route)
|
.setTitle(route)
|
||||||
.setOnClickListener(OnClickListener { onRouteSelected(index) })
|
.setOnClickListener { onRouteSelected(index) }
|
||||||
.addText(titleText)
|
.addText(titleText)
|
||||||
.addAction(action)
|
.addAction(action)
|
||||||
.build()
|
.build()
|
||||||
|
|||||||
@@ -73,14 +73,6 @@ class RoutingSettings(private val carContext: CarContext, private var navigation
|
|||||||
private fun onSelected(index: Int) {
|
private fun onSelected(index: Int) {
|
||||||
settingsViewModel.onRoutingEngineChanged(index)
|
settingsViewModel.onRoutingEngineChanged(index)
|
||||||
navigationViewModel.routingEngine.value = 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 {
|
private fun buildRowForTemplate(title: Int): Row {
|
||||||
|
|||||||
@@ -124,7 +124,7 @@ object Constants {
|
|||||||
val homeVogelhart = location(11.5793748, 48.185749)
|
val homeVogelhart = location(11.5793748, 48.185749)
|
||||||
val homeHohenwaldeck = location( 11.594322, 48.1164817)
|
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
|
const val MAXIMAL_SNAP_CORRECTION = 50.0
|
||||||
|
|
||||||
|
|||||||
@@ -33,59 +33,87 @@ import org.maplibre.geojson.FeatureCollection
|
|||||||
import java.time.LocalDateTime
|
import java.time.LocalDateTime
|
||||||
import java.time.ZoneOffset
|
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() {
|
class NavigationViewModel(private val repository: NavigationRepository) : ViewModel() {
|
||||||
|
|
||||||
|
/** LiveData containing the calculated route JSON string */
|
||||||
val route: MutableLiveData<String> by lazy {
|
val route: MutableLiveData<String> by lazy {
|
||||||
MutableLiveData()
|
MutableLiveData()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** LiveData containing categorized traffic incidents map */
|
||||||
val traffic: MutableLiveData<Map<String, String>> by lazy {
|
val traffic: MutableLiveData<Map<String, String>> by lazy {
|
||||||
MutableLiveData()
|
MutableLiveData()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** LiveData containing a preview route JSON string for route preview screens */
|
||||||
val previewRoute: MutableLiveData<String> by lazy {
|
val previewRoute: MutableLiveData<String> by lazy {
|
||||||
MutableLiveData()
|
MutableLiveData()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** LiveData containing the most recent place used for navigation */
|
||||||
val recentPlace: MutableLiveData<Place> by lazy {
|
val recentPlace: MutableLiveData<Place> by lazy {
|
||||||
MutableLiveData()
|
MutableLiveData()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** LiveData containing list of recent navigation destinations */
|
||||||
val places: MutableLiveData<List<Place>> by lazy {
|
val places: MutableLiveData<List<Place>> by lazy {
|
||||||
MutableLiveData()
|
MutableLiveData()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** LiveData containing list of favorite saved places */
|
||||||
val favorites: MutableLiveData<List<Place>> by lazy {
|
val favorites: MutableLiveData<List<Place>> by lazy {
|
||||||
MutableLiveData()
|
MutableLiveData()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** LiveData containing search results from Nominatim geocoding */
|
||||||
val searchPlaces: MutableLiveData<List<SearchResult>> by lazy {
|
val searchPlaces: MutableLiveData<List<SearchResult>> by lazy {
|
||||||
MutableLiveData()
|
MutableLiveData()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** LiveData containing the best matching place from address search */
|
||||||
val placeLocation: MutableLiveData<SearchResult> by lazy {
|
val placeLocation: MutableLiveData<SearchResult> by lazy {
|
||||||
MutableLiveData()
|
MutableLiveData()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** LiveData containing contacts with addresses */
|
||||||
val contactAddress: MutableLiveData<List<Place>> by lazy {
|
val contactAddress: MutableLiveData<List<Place>> by lazy {
|
||||||
MutableLiveData()
|
MutableLiveData()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** LiveData containing POI elements from Overpass API */
|
||||||
val elements: MutableLiveData<List<Elements>> by lazy {
|
val elements: MutableLiveData<List<Elements>> by lazy {
|
||||||
MutableLiveData()
|
MutableLiveData()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** LiveData containing speed camera locations */
|
||||||
val speedCameras: MutableLiveData<List<Elements>> by lazy {
|
val speedCameras: MutableLiveData<List<Elements>> by lazy {
|
||||||
MutableLiveData()
|
MutableLiveData()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** LiveData containing current road speed limit */
|
||||||
val maxSpeed: MutableLiveData<Int> by lazy {
|
val maxSpeed: MutableLiveData<Int> by lazy {
|
||||||
MutableLiveData()
|
MutableLiveData()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** LiveData containing selected routing engine index */
|
||||||
val routingEngine: MutableLiveData<Int> by lazy {
|
val routingEngine: MutableLiveData<Int> by lazy {
|
||||||
MutableLiveData()
|
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) {
|
fun loadRecentPlace(location: Location, carOrientation: Float, context: Context) {
|
||||||
viewModelScope.launch(Dispatchers.IO) {
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
try {
|
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) {
|
fun loadRecentPlaces(context: Context, location: Location, carOrientation: Float) {
|
||||||
viewModelScope.launch(Dispatchers.IO) {
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
try {
|
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) {
|
fun loadFavorites(context: Context, location: Location, carOrientation: Float) {
|
||||||
viewModelScope.launch(Dispatchers.IO) {
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
try {
|
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(
|
fun loadRoute(
|
||||||
context: Context,
|
context: Context,
|
||||||
currentLocation: Location,
|
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) {
|
fun loadTraffic(context: Context, currentLocation: Location, carOrientation: Float) {
|
||||||
viewModelScope.launch(Dispatchers.IO) {
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
try {
|
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> {
|
private fun rebuildTraffic(data: String): Map<String, String> {
|
||||||
val featureCollection = FeatureCollection.fromJson(data)
|
val featureCollection = FeatureCollection.fromJson(data)
|
||||||
val incidents = mutableMapOf<String, String>()
|
val incidents = mutableMapOf<String, String>()
|
||||||
@@ -238,6 +287,10 @@ class NavigationViewModel(private val repository: NavigationRepository) : ViewMo
|
|||||||
return incidents
|
return incidents
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculates a preview route for route preview screen.
|
||||||
|
* Posts the route JSON to previewRoute LiveData.
|
||||||
|
*/
|
||||||
fun loadPreviewRoute(
|
fun loadPreviewRoute(
|
||||||
context: Context,
|
context: Context,
|
||||||
currentLocation: Location,
|
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) {
|
fun loadContacts(context: Context) {
|
||||||
viewModelScope.launch(Dispatchers.IO) {
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
val contactList = mutableListOf<Place>()
|
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) {
|
fun findAddress(search: String, location: Location) {
|
||||||
var sortedList: List<SearchResult>
|
var sortedList: List<SearchResult>
|
||||||
viewModelScope.launch(Dispatchers.IO) {
|
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) {
|
fun searchPlaces(search: String, location: Location) {
|
||||||
viewModelScope.launch(Dispatchers.IO) {
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
val placesJson = repository.searchPlaces(search, location)
|
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 {
|
fun reverseAddress(location: Location): String {
|
||||||
val address = repository.reverseAddress(location)
|
val address = repository.reverseAddress(location)
|
||||||
val gson = GsonBuilder().serializeNulls().create()
|
val gson = GsonBuilder().serializeNulls().create()
|
||||||
@@ -337,6 +405,10 @@ class NavigationViewModel(private val repository: NavigationRepository) : ViewMo
|
|||||||
return place.address.road
|
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) {
|
fun getAmenities(category: String, location: Location) {
|
||||||
viewModelScope.launch(Dispatchers.IO) {
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
val amenities = Overpass().getAmenities("amenity", category, location, 5.0)
|
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) {
|
fun getSpeedCameras(location: Location, radius: Double) {
|
||||||
viewModelScope.launch(Dispatchers.IO) {
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
val amenities = Overpass().getAmenities("highway", "speed_camera", location, radius)
|
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) {
|
fun getMaxSpeed(location: Location, street: String) {
|
||||||
viewModelScope.launch(Dispatchers.IO) {
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
val levenshtein = Levenshtein()
|
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) {
|
fun saveFavorite(place: Place) {
|
||||||
place.category = Constants.FAVORITES
|
place.category = Constants.FAVORITES
|
||||||
savePlace(place)
|
savePlace(place)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Saves a place to recent destinations in ObjectBox.
|
||||||
|
* Skips fuel stations, charging stations, and pharmacies.
|
||||||
|
*/
|
||||||
fun saveRecent(place: Place) {
|
fun saveRecent(place: Place) {
|
||||||
if (place.category == Constants.FUEL_STATION
|
if (place.category == Constants.FUEL_STATION
|
||||||
|| place.category == Constants.CHARGING_STATION
|
|| place.category == Constants.CHARGING_STATION
|
||||||
@@ -403,6 +490,10 @@ class NavigationViewModel(private val repository: NavigationRepository) : ViewMo
|
|||||||
savePlace(place)
|
savePlace(place)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Saves a place to ObjectBox, removing existing duplicates first.
|
||||||
|
* Updates the timestamp to current time.
|
||||||
|
*/
|
||||||
private fun savePlace(place: Place) {
|
private fun savePlace(place: Place) {
|
||||||
viewModelScope.launch(Dispatchers.IO) {
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
try {
|
try {
|
||||||
@@ -426,16 +517,25 @@ class NavigationViewModel(private val repository: NavigationRepository) : ViewMo
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deletes a place from favorites in ObjectBox.
|
||||||
|
*/
|
||||||
fun deleteFavorite(place: Place) {
|
fun deleteFavorite(place: Place) {
|
||||||
place.category = Constants.FAVORITES
|
place.category = Constants.FAVORITES
|
||||||
deletePlace(place)
|
deletePlace(place)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deletes a place from recent destinations in ObjectBox.
|
||||||
|
*/
|
||||||
fun deleteRecent(place: Place) {
|
fun deleteRecent(place: Place) {
|
||||||
place.category = Constants.RECENT
|
place.category = Constants.RECENT
|
||||||
deletePlace(place)
|
deletePlace(place)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deletes a place from ObjectBox matching name and category.
|
||||||
|
*/
|
||||||
fun deletePlace(place: Place) {
|
fun deletePlace(place: Place) {
|
||||||
viewModelScope.launch(Dispatchers.IO) {
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
try {
|
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 {
|
fun getSearchFilter(context: Context): SearchFilter {
|
||||||
val repository = getSettingsRepository(context)
|
val repository = getSettingsRepository(context)
|
||||||
val avoidMotorway = runBlocking { repository.avoidMotorwayFlow.first() }
|
val avoidMotorway = runBlocking { repository.avoidMotorwayFlow.first() }
|
||||||
@@ -463,7 +567,10 @@ class NavigationViewModel(private val repository: NavigationRepository) : ViewMo
|
|||||||
return SearchFilter(avoidMotorway, avoidTollway)
|
return SearchFilter(avoidMotorway, avoidTollway)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads recent places with calculated distances for Compose state.
|
||||||
|
* @return SnapshotStateList of recent places with distances
|
||||||
|
*/
|
||||||
fun loadPlaces2(
|
fun loadPlaces2(
|
||||||
context: Context,
|
context: Context,
|
||||||
location: Location,
|
location: Location,
|
||||||
@@ -495,6 +602,10 @@ class NavigationViewModel(private val repository: NavigationRepository) : ViewMo
|
|||||||
return results.toMutableStateList()
|
return results.toMutableStateList()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads recent places as Compose SnapshotStateList.
|
||||||
|
* @return SnapshotStateList of recent places
|
||||||
|
*/
|
||||||
fun loadRecentPlace(): SnapshotStateList<Place?> {
|
fun loadRecentPlace(): SnapshotStateList<Place?> {
|
||||||
val results = listOf<Place>()
|
val results = listOf<Place>()
|
||||||
try {
|
try {
|
||||||
@@ -511,5 +622,4 @@ class NavigationViewModel(private val repository: NavigationRepository) : ViewMo
|
|||||||
}
|
}
|
||||||
return results.toMutableStateList()
|
return results.toMutableStateList()
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -26,9 +26,9 @@ class RouteCalculator(var routeModel: RouteModel) {
|
|||||||
routeModel.navState.route.currentStepIndex = step.index
|
routeModel.navState.route.currentStepIndex = step.index
|
||||||
step.waypointIndex = wayIndex
|
step.waypointIndex = wayIndex
|
||||||
step.wayPointLocation = location(waypoint[0], waypoint[1])
|
step.wayPointLocation = location(waypoint[0], waypoint[1])
|
||||||
routeModel.navState = routeModel.navState.copy(
|
//routeModel.navState = routeModel.navState.copy(
|
||||||
routeBearing = routeModel.navState.lastLocation.bearingTo(location)
|
// routeBearing = routeModel.navState.lastLocation.bearingTo(location)
|
||||||
)
|
// )
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ package com.kouros.navigation.model
|
|||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.location.Location
|
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 androidx.car.app.navigation.model.Maneuver
|
||||||
import com.kouros.navigation.data.Constants.NEXT_STEP_THRESHOLD
|
import com.kouros.navigation.data.Constants.NEXT_STEP_THRESHOLD
|
||||||
import com.kouros.navigation.data.Place
|
import com.kouros.navigation.data.Place
|
||||||
@@ -33,7 +35,8 @@ open class RouteModel {
|
|||||||
val currentLocation: Location = location(0.0, 0.0),
|
val currentLocation: Location = location(0.0, 0.0),
|
||||||
val routeBearing: Float = 0F,
|
val routeBearing: Float = 0F,
|
||||||
val currentRouteIndex: Int = 0,
|
val currentRouteIndex: Int = 0,
|
||||||
val destination: Place = Place()
|
val destination: Place = Place(),
|
||||||
|
val carConnection: Int = 0,
|
||||||
)
|
)
|
||||||
|
|
||||||
var navState = NavigationState()
|
var navState = NavigationState()
|
||||||
@@ -81,9 +84,8 @@ open class RouteModel {
|
|||||||
fun updateLocation(context: Context, curLocation: Location, viewModel: NavigationViewModel) {
|
fun updateLocation(context: Context, curLocation: Location, viewModel: NavigationViewModel) {
|
||||||
navState = navState.copy(currentLocation = curLocation)
|
navState = navState.copy(currentLocation = curLocation)
|
||||||
routeCalculator.findStep(curLocation)
|
routeCalculator.findStep(curLocation)
|
||||||
val repository = getSettingsRepository(context)
|
if (navState.carConnection == CONNECTION_TYPE_PROJECTION
|
||||||
val carLocation = runBlocking { repository.carLocationFlow.first() }
|
|| navState.carConnection == CONNECTION_TYPE_NATIVE) {
|
||||||
if (carLocation) {
|
|
||||||
routeCalculator.updateSpeedLimit(curLocation, viewModel)
|
routeCalculator.updateSpeedLimit(curLocation, viewModel)
|
||||||
}
|
}
|
||||||
navState = navState.copy(lastLocation = navState.currentLocation)
|
navState = navState.copy(lastLocation = navState.currentLocation)
|
||||||
|
|||||||
@@ -27,6 +27,9 @@ object GeoUtils {
|
|||||||
val point = pointFeature.geometry() as Point
|
val point = pointFeature.geometry() as Point
|
||||||
newLocation.latitude = point.latitude()
|
newLocation.latitude = point.latitude()
|
||||||
newLocation.longitude = point.longitude()
|
newLocation.longitude = point.longitude()
|
||||||
|
if (location.hasBearing()) {
|
||||||
|
newLocation.bearing = location.bearing
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return newLocation
|
return newLocation
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ fun calculateZoom(speed: Double?): Double {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun previewZoom(previewDistance: Double): Double {
|
fun previewZoom(previewDistance: Double): Double {
|
||||||
when (previewDistance) {
|
when (previewDistance / 1000) {
|
||||||
in 0.0..10.0 -> return 13.5
|
in 0.0..10.0 -> return 13.5
|
||||||
in 10.0..20.0 -> return 11.5
|
in 10.0..20.0 -> return 11.5
|
||||||
in 20.0..30.0 -> return 10.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="use_car_location">Auto GPS verwenden</string>
|
||||||
<string name="tomtom">TomTom\t</string>
|
<string name="tomtom">TomTom\t</string>
|
||||||
<string name="options">Optionen</string>
|
<string name="options">Optionen</string>
|
||||||
|
<string name="tomtom_api_key">TomTom ApiKey</string>
|
||||||
|
<string name="use_car_settings">Verwende Auto Einstellungen</string>
|
||||||
|
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -37,4 +37,5 @@
|
|||||||
<string name="tomtom">TomTom\t</string>
|
<string name="tomtom">TomTom\t</string>
|
||||||
<string name="options">Options</string>
|
<string name="options">Options</string>
|
||||||
<string name="tomtom_api_key">TomTom ApiKey</string>
|
<string name="tomtom_api_key">TomTom ApiKey</string>
|
||||||
|
<string name="use_car_settings">Use car settings</string>
|
||||||
</resources>
|
</resources>
|
||||||
@@ -38,7 +38,6 @@ material3WindowSizeClass = "1.4.0"
|
|||||||
uiGraphics = "1.10.3"
|
uiGraphics = "1.10.3"
|
||||||
window = "1.5.1"
|
window = "1.5.1"
|
||||||
foundationLayout = "1.10.3"
|
foundationLayout = "1.10.3"
|
||||||
foundationLayoutVersion = "1.10.3"
|
|
||||||
datastorePreferences = "1.2.0"
|
datastorePreferences = "1.2.0"
|
||||||
datastoreCore = "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-compose-ui-graphics = { group = "androidx.compose.ui", name = "ui-graphics", version.ref = "uiGraphics" }
|
||||||
androidx-window = { group = "androidx.window", name = "window", version.ref = "window" }
|
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-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-preferences = { module = "androidx.datastore:datastore-preferences", version.ref = "datastorePreferences" }
|
||||||
androidx-datastore-core = { group = "androidx.datastore", name = "datastore-core", version.ref = "datastoreCore" }
|
androidx-datastore-core = { group = "androidx.datastore", name = "datastore-core", version.ref = "datastoreCore" }
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user