Automotive, CarSession
@@ -17,8 +17,8 @@ android {
|
|||||||
applicationId = "com.kouros.navigation"
|
applicationId = "com.kouros.navigation"
|
||||||
minSdk = 33
|
minSdk = 33
|
||||||
targetSdk = 36
|
targetSdk = 36
|
||||||
versionCode = 90
|
versionCode = 91
|
||||||
versionName = "0.2.3.90"
|
versionName = "0.2.3.91"
|
||||||
base.archivesName = "navi-$versionName"
|
base.archivesName = "navi-$versionName"
|
||||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||||
}
|
}
|
||||||
@@ -101,9 +101,10 @@ dependencies {
|
|||||||
implementation(libs.maplibre.compose)
|
implementation(libs.maplibre.compose)
|
||||||
|
|
||||||
implementation(libs.accompanist.permissions)
|
implementation(libs.accompanist.permissions)
|
||||||
|
|
||||||
implementation(project(":common:car"))
|
implementation(project(":common:car"))
|
||||||
implementation(project(":common:data"))
|
implementation(project(":common:data"))
|
||||||
|
implementation(libs.androidx.car.app)
|
||||||
|
implementation(libs.androidx.app.projected)
|
||||||
implementation(libs.play.services.location)
|
implementation(libs.play.services.location)
|
||||||
implementation(libs.androidx.compose.runtime)
|
implementation(libs.androidx.compose.runtime)
|
||||||
implementation(libs.androidx.navigation.compose)
|
implementation(libs.androidx.navigation.compose)
|
||||||
|
|||||||
@@ -37,7 +37,6 @@ android {
|
|||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation(libs.androidx.app.automotive)
|
implementation(libs.androidx.app.automotive)
|
||||||
implementation(libs.androidx.car.app)
|
|
||||||
implementation(libs.androidx.material3)
|
implementation(libs.androidx.material3)
|
||||||
implementation(libs.androidx.runtime.livedata)
|
implementation(libs.androidx.runtime.livedata)
|
||||||
implementation(project(":common:car"))
|
implementation(project(":common:car"))
|
||||||
|
|||||||
@@ -4,10 +4,10 @@
|
|||||||
android:viewportWidth="960"
|
android:viewportWidth="960"
|
||||||
android:viewportHeight="960"
|
android:viewportHeight="960"
|
||||||
android:tint="#1A7416">
|
android:tint="#1A7416">
|
||||||
<group android:scaleX="0.7888"
|
<group android:scaleX="0.58"
|
||||||
android:scaleY="0.7888"
|
android:scaleY="0.58"
|
||||||
android:translateX="101.376"
|
android:translateX="201.6"
|
||||||
android:translateY="101.376">
|
android:translateY="201.6">
|
||||||
<path
|
<path
|
||||||
android:fillColor="@android:color/white"
|
android:fillColor="@android:color/white"
|
||||||
android:pathData="M319,680L480,607L641,680L656,665L480,240L304,665L319,680ZM480,880Q397,880 324,848.5Q251,817 197,763Q143,709 111.5,636Q80,563 80,480Q80,397 111.5,324Q143,251 197,197Q251,143 324,111.5Q397,80 480,80Q563,80 636,111.5Q709,143 763,197Q817,251 848.5,324Q880,397 880,480Q880,563 848.5,636Q817,709 763,763Q709,817 636,848.5Q563,880 480,880ZM480,800Q614,800 707,707Q800,614 800,480Q800,346 707,253Q614,160 480,160Q346,160 253,253Q160,346 160,480Q160,614 253,707Q346,800 480,800ZM480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Z"/>
|
android:pathData="M319,680L480,607L641,680L656,665L480,240L304,665L319,680ZM480,880Q397,880 324,848.5Q251,817 197,763Q143,709 111.5,636Q80,563 80,480Q80,397 111.5,324Q143,251 197,197Q251,143 324,111.5Q397,80 480,80Q563,80 636,111.5Q709,143 763,197Q817,251 848.5,324Q880,397 880,480Q880,563 848.5,636Q817,709 763,763Q709,817 636,848.5Q563,880 480,880ZM480,800Q614,800 707,707Q800,614 800,480Q800,346 707,253Q614,160 480,160Q346,160 253,253Q160,346 160,480Q160,614 253,707Q346,800 480,800ZM480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Z"/>
|
||||||
|
|||||||
@@ -1,6 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
|
||||||
<background android:drawable="@drawable/ic_launcher_background" />
|
|
||||||
<foreground android:drawable="@drawable/ic_launcher_foreground" />
|
|
||||||
<monochrome android:drawable="@drawable/ic_launcher_foreground" />
|
|
||||||
</adaptive-icon>
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
|
||||||
<background android:drawable="@drawable/ic_launcher_background" />
|
|
||||||
<foreground android:drawable="@drawable/ic_launcher_foreground" />
|
|
||||||
<monochrome android:drawable="@drawable/ic_launcher_foreground" />
|
|
||||||
</adaptive-icon>
|
|
||||||
|
Before Width: | Height: | Size: 2.1 KiB After Width: | Height: | Size: 1.6 KiB |
|
Before Width: | Height: | Size: 3.5 KiB After Width: | Height: | Size: 3.3 KiB |
|
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 2.1 KiB After Width: | Height: | Size: 2.1 KiB |
|
Before Width: | Height: | Size: 2.8 KiB After Width: | Height: | Size: 2.1 KiB |
|
Before Width: | Height: | Size: 4.7 KiB After Width: | Height: | Size: 4.6 KiB |
|
Before Width: | Height: | Size: 4.2 KiB After Width: | Height: | Size: 3.2 KiB |
|
Before Width: | Height: | Size: 7.5 KiB After Width: | Height: | Size: 7.1 KiB |
|
Before Width: | Height: | Size: 5.3 KiB After Width: | Height: | Size: 4.2 KiB |
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 9.8 KiB |
@@ -42,7 +42,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(project(":common:data"))
|
implementation(project(":common:data"))
|
||||||
implementation(libs.androidx.runtime.livedata)
|
implementation(libs.androidx.runtime.livedata)
|
||||||
implementation(libs.androidx.compose.foundation)
|
implementation(libs.androidx.compose.foundation)
|
||||||
|
|||||||
@@ -2,11 +2,11 @@ package com.kouros.navigation.car
|
|||||||
|
|
||||||
import android.location.Location
|
import android.location.Location
|
||||||
import android.location.LocationManager
|
import android.location.LocationManager
|
||||||
import androidx.car.app.navigation.model.Maneuver
|
|
||||||
import androidx.test.platform.app.InstrumentationRegistry
|
import androidx.test.platform.app.InstrumentationRegistry
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||||
import com.kouros.navigation.data.Constants.homeHohenwaldeck
|
import com.kouros.navigation.data.Constants.homeHohenwaldeck
|
||||||
import com.kouros.navigation.data.RouteEngine
|
import com.kouros.navigation.data.RouteEngine
|
||||||
|
import com.kouros.navigation.data.route.ManeuverType
|
||||||
import com.kouros.navigation.data.tomtom.TomTomRepository
|
import com.kouros.navigation.data.tomtom.TomTomRepository
|
||||||
import com.kouros.navigation.model.NavigationViewModel
|
import com.kouros.navigation.model.NavigationViewModel
|
||||||
import com.kouros.navigation.model.RouteModel
|
import com.kouros.navigation.model.RouteModel
|
||||||
@@ -32,6 +32,37 @@ class RouteModelTest {
|
|||||||
val routeModel = RouteModel()
|
val routeModel = RouteModel()
|
||||||
val location = Location(LocationManager.GPS_PROVIDER)
|
val location = Location(LocationManager.GPS_PROVIDER)
|
||||||
|
|
||||||
|
val distance = listOf(
|
||||||
|
1025.5,
|
||||||
|
989.8,
|
||||||
|
963.5,
|
||||||
|
923.7,
|
||||||
|
915.8,
|
||||||
|
914.6,
|
||||||
|
871.0,
|
||||||
|
822.7,
|
||||||
|
769.7,
|
||||||
|
713.8,
|
||||||
|
644.8,
|
||||||
|
577.6,
|
||||||
|
501.7,
|
||||||
|
489.7,
|
||||||
|
452.5,
|
||||||
|
437.4,
|
||||||
|
398.0,
|
||||||
|
390.1,
|
||||||
|
341.3,
|
||||||
|
266.6,
|
||||||
|
219.5,
|
||||||
|
140.7,
|
||||||
|
77.4,
|
||||||
|
55.1,
|
||||||
|
40.0,
|
||||||
|
30.0,
|
||||||
|
19.0,
|
||||||
|
4.0
|
||||||
|
)
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
fun setup() {
|
fun setup() {
|
||||||
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
|
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
|
||||||
@@ -49,8 +80,8 @@ class RouteModelTest {
|
|||||||
@Test
|
@Test
|
||||||
fun checkRoute() {
|
fun checkRoute() {
|
||||||
assertEquals(true, routeModel.isNavigating())
|
assertEquals(true, routeModel.isNavigating())
|
||||||
assertEquals(routeModel.curRoute.summary.distance, 11116.0, 10.0)
|
assertEquals(routeModel.curRoute.summary.distance, 11108.0, 10.0)
|
||||||
assertEquals(routeModel.curRoute.summary.duration, 1581.0, 10.0)
|
assertEquals(routeModel.curRoute.summary.duration, 1094.0, 10.0)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -59,11 +90,11 @@ class RouteModelTest {
|
|||||||
location.longitude = 11.579034
|
location.longitude = 11.579034
|
||||||
routeModel.updateLocation(location, NavigationViewModel(TomTomRepository()))
|
routeModel.updateLocation(location, NavigationViewModel(TomTomRepository()))
|
||||||
val stepData = routeModel.currentStep()
|
val stepData = routeModel.currentStep()
|
||||||
assertEquals(stepData.currentManeuverType, Maneuver.TYPE_TURN_NORMAL_RIGHT)
|
assertEquals(stepData.currentManeuverType, ManeuverType.TYPE_TURN_NORMAL_RIGHT.value)
|
||||||
assertEquals(stepData.instruction, "Silcherstraße")
|
assertEquals(stepData.instruction, "Silcherstraße")
|
||||||
assertEquals(stepData.leftStepDistance, 20.0, 5.0)
|
assertEquals(stepData.leftStepDistance, 20.0, 5.0)
|
||||||
val nextStepData = routeModel.nextStep()
|
val nextStepData = routeModel.nextStep()
|
||||||
assertEquals(nextStepData.currentManeuverType, Maneuver.TYPE_TURN_NORMAL_RIGHT)
|
assertEquals(nextStepData.currentManeuverType, ManeuverType.TYPE_TURN_NORMAL_RIGHT.value)
|
||||||
assertEquals(nextStepData.instruction, "Schmalkaldener Straße")
|
assertEquals(nextStepData.instruction, "Schmalkaldener Straße")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -74,11 +105,11 @@ class RouteModelTest {
|
|||||||
location.longitude = 11.576652
|
location.longitude = 11.576652
|
||||||
routeModel.updateLocation(location, NavigationViewModel(TomTomRepository()))
|
routeModel.updateLocation(location, NavigationViewModel(TomTomRepository()))
|
||||||
val stepData = routeModel.currentStep()
|
val stepData = routeModel.currentStep()
|
||||||
assertEquals(stepData.currentManeuverType, Maneuver.TYPE_TURN_NORMAL_RIGHT)
|
assertEquals(stepData.currentManeuverType, ManeuverType.TYPE_TURN_NORMAL_RIGHT.value)
|
||||||
assertEquals(stepData.instruction, "Schmalkaldener Straße")
|
assertEquals(stepData.instruction, "Schmalkaldener Straße")
|
||||||
assertEquals(stepData.leftStepDistance, 0.0, 1.0)
|
assertEquals(stepData.leftStepDistance, 0.0, 1.0)
|
||||||
val nextStepData = routeModel.nextStep()
|
val nextStepData = routeModel.nextStep()
|
||||||
assertEquals(nextStepData.currentManeuverType, Maneuver.TYPE_TURN_NORMAL_RIGHT)
|
assertEquals(nextStepData.currentManeuverType, ManeuverType.TYPE_TURN_NORMAL_RIGHT.value)
|
||||||
assertEquals(nextStepData.instruction, "Ingolstädter Straße")
|
assertEquals(nextStepData.instruction, "Ingolstädter Straße")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -89,13 +120,13 @@ class RouteModelTest {
|
|||||||
routeModel.updateLocation(location, NavigationViewModel(TomTomRepository()))
|
routeModel.updateLocation(location, NavigationViewModel(TomTomRepository()))
|
||||||
val stepData = routeModel.currentStep()
|
val stepData = routeModel.currentStep()
|
||||||
if (routeModel.navState.nextStep) {
|
if (routeModel.navState.nextStep) {
|
||||||
assertEquals(stepData.currentManeuverType, Maneuver.TYPE_STRAIGHT)
|
assertEquals(stepData.currentManeuverType, ManeuverType.TYPE_STRAIGHT.value)
|
||||||
assertEquals(stepData.instruction, "Ingolstädter Straße")
|
assertEquals(stepData.instruction, "Ingolstädter Straße")
|
||||||
val nextStepData = routeModel.nextStep()
|
val nextStepData = routeModel.nextStep()
|
||||||
assertEquals(nextStepData.currentManeuverType, Maneuver.TYPE_TURN_NORMAL_LEFT)
|
assertEquals(nextStepData.currentManeuverType, ManeuverType.TYPE_TURN_NORMAL_LEFT.value)
|
||||||
assertEquals(nextStepData.instruction, "Schenkendorfstraße")
|
assertEquals(nextStepData.instruction, "Schenkendorfstraße")
|
||||||
} else {
|
} else {
|
||||||
assertEquals(stepData.currentManeuverType, Maneuver.TYPE_TURN_NORMAL_LEFT)
|
assertEquals(stepData.currentManeuverType, ManeuverType.TYPE_TURN_NORMAL_LEFT.value)
|
||||||
}
|
}
|
||||||
assertEquals(stepData.leftStepDistance, 301.0, 1.0)
|
assertEquals(stepData.leftStepDistance, 301.0, 1.0)
|
||||||
}
|
}
|
||||||
@@ -107,14 +138,14 @@ class RouteModelTest {
|
|||||||
location.bearing = 180.0F
|
location.bearing = 180.0F
|
||||||
routeModel.updateLocation(location, NavigationViewModel(TomTomRepository()))
|
routeModel.updateLocation(location, NavigationViewModel(TomTomRepository()))
|
||||||
val stepData = routeModel.currentStep()
|
val stepData = routeModel.currentStep()
|
||||||
assertEquals(stepData.currentManeuverType, Maneuver.TYPE_TURN_NORMAL_LEFT)
|
assertEquals(stepData.currentManeuverType, ManeuverType.TYPE_TURN_NORMAL_LEFT.value)
|
||||||
assertEquals(stepData.instruction, "Schenkendorfstraße")
|
assertEquals(stepData.instruction, "Schenkendorfstraße")
|
||||||
assertEquals(stepData.leftStepDistance, 170.0, 10.0)
|
assertEquals(stepData.leftStepDistance, 170.0, 10.0)
|
||||||
assertEquals(stepData.lane.size, 4)
|
assertEquals(stepData.lane.size, 4)
|
||||||
assertEquals(stepData.lane.first().valid, true)
|
assertEquals(stepData.lane.first().valid, true)
|
||||||
assertEquals(stepData.lane.last().valid, false)
|
assertEquals(stepData.lane.last().valid, false)
|
||||||
val nextStepData = routeModel.nextStep()
|
val nextStepData = routeModel.nextStep()
|
||||||
assertEquals(nextStepData.currentManeuverType, Maneuver.TYPE_KEEP_LEFT)
|
assertEquals(nextStepData.currentManeuverType, ManeuverType.TYPE_KEEP_LEFT.value)
|
||||||
assertEquals(nextStepData.instruction, "Schenkendorfstraße")
|
assertEquals(nextStepData.instruction, "Schenkendorfstraße")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -124,7 +155,7 @@ class RouteModelTest {
|
|||||||
location.longitude = homeHohenwaldeck.longitude
|
location.longitude = homeHohenwaldeck.longitude
|
||||||
routeModel.updateLocation(location, NavigationViewModel(TomTomRepository()))
|
routeModel.updateLocation(location, NavigationViewModel(TomTomRepository()))
|
||||||
val stepData = routeModel.nextStep()
|
val stepData = routeModel.nextStep()
|
||||||
assertEquals(stepData.currentManeuverType, Maneuver.TYPE_DESTINATION_LEFT)
|
assertEquals(stepData.currentManeuverType, ManeuverType.TYPE_DESTINATION_LEFT.value)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -142,11 +173,11 @@ class RouteModelTest {
|
|||||||
if (index in 61..61) {
|
if (index in 61..61) {
|
||||||
routeModel.updateLocation(curLocation, NavigationViewModel(TomTomRepository()))
|
routeModel.updateLocation(curLocation, NavigationViewModel(TomTomRepository()))
|
||||||
val stepData = routeModel.currentStep()
|
val stepData = routeModel.currentStep()
|
||||||
assertEquals(stepData.lane.size, 3)
|
assertEquals(stepData.lane.size, 2)
|
||||||
assertEquals(stepData.lane.first().valid, true)
|
assertEquals(stepData.lane.first().valid, true)
|
||||||
assertEquals(stepData.lane.first().indications.first(), "STRAIGHT")
|
assertEquals(stepData.lane.first().indications.first(), "STRAIGHT")
|
||||||
}
|
}
|
||||||
if (index in 74..74) {
|
if (index in 74..75) {
|
||||||
routeModel.updateLocation(curLocation, NavigationViewModel(TomTomRepository()))
|
routeModel.updateLocation(curLocation, NavigationViewModel(TomTomRepository()))
|
||||||
val stepData = routeModel.currentStep()
|
val stepData = routeModel.currentStep()
|
||||||
assertEquals(stepData.lane.size, 3)
|
assertEquals(stepData.lane.size, 3)
|
||||||
@@ -180,9 +211,9 @@ class RouteModelTest {
|
|||||||
val stepData = routeModel.currentStep()
|
val stepData = routeModel.currentStep()
|
||||||
//println("${stepData.instruction} ${System.currentTimeMillis() - start}")
|
//println("${stepData.instruction} ${System.currentTimeMillis() - start}")
|
||||||
if (stepData.lane.isNotEmpty()) {
|
if (stepData.lane.isNotEmpty()) {
|
||||||
println(stepData.street)
|
// println(stepData.street)
|
||||||
stepData.lane.forEach {
|
stepData.lane.forEach {
|
||||||
println("${it.indications} ${it.valid}")
|
// println("${it.indications} ${it.valid}")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// val nextData = routeModel.nextStep()
|
// val nextData = routeModel.nextStep()
|
||||||
@@ -206,4 +237,19 @@ class RouteModelTest {
|
|||||||
val step = routeModel.currentStep()
|
val step = routeModel.currentStep()
|
||||||
assertEquals(step.leftStepDistance, 26.0, 1.0)
|
assertEquals(step.leftStepDistance, 26.0, 1.0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun leftStepDistance() {
|
||||||
|
for ((index, waypoint) in routeModel.curRoute.waypoints.withIndex()) {
|
||||||
|
val curLocation = location(waypoint[0], waypoint[1])
|
||||||
|
if (routeModel.isNavigating()) {
|
||||||
|
if (index in 16..43) {
|
||||||
|
routeModel.updateLocation(curLocation, NavigationViewModel(TomTomRepository()))
|
||||||
|
val stepData = routeModel.currentStep()
|
||||||
|
assertEquals(stepData.leftStepDistance, distance[index-16], 1.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,10 @@
|
|||||||
|
package com.kouros.navigation.car
|
||||||
|
|
||||||
|
import androidx.car.app.Session
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
|
||||||
|
abstract class CarSession : Session() {
|
||||||
|
|
||||||
|
abstract fun invalidateNavigationScreen()
|
||||||
|
|
||||||
|
}
|
||||||
@@ -17,6 +17,7 @@ package com.kouros.navigation.car
|
|||||||
|
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.content.res.Configuration
|
import android.content.res.Configuration
|
||||||
|
import android.location.Location
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import androidx.car.app.CarContext
|
import androidx.car.app.CarContext
|
||||||
import androidx.car.app.CarToast
|
import androidx.car.app.CarToast
|
||||||
@@ -27,6 +28,9 @@ import androidx.car.app.model.CarIcon
|
|||||||
import androidx.car.app.model.OnClickListener
|
import androidx.car.app.model.OnClickListener
|
||||||
import androidx.car.app.navigation.model.Trip
|
import androidx.car.app.navigation.model.Trip
|
||||||
import androidx.core.graphics.drawable.IconCompat
|
import androidx.core.graphics.drawable.IconCompat
|
||||||
|
import androidx.lifecycle.DefaultLifecycleObserver
|
||||||
|
import androidx.lifecycle.LifecycleObserver
|
||||||
|
import androidx.lifecycle.LifecycleOwner
|
||||||
import androidx.lifecycle.ViewModelStore
|
import androidx.lifecycle.ViewModelStore
|
||||||
import androidx.lifecycle.ViewModelStoreOwner
|
import androidx.lifecycle.ViewModelStoreOwner
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
@@ -34,46 +38,74 @@ import com.kouros.data.R
|
|||||||
import com.kouros.navigation.car.navigation.RouteCarModel
|
import com.kouros.navigation.car.navigation.RouteCarModel
|
||||||
import com.kouros.navigation.car.screen.NavigationListener
|
import com.kouros.navigation.car.screen.NavigationListener
|
||||||
import com.kouros.navigation.car.screen.NavigationScreen
|
import com.kouros.navigation.car.screen.NavigationScreen
|
||||||
|
import com.kouros.navigation.data.Constants
|
||||||
import com.kouros.navigation.data.Place
|
import com.kouros.navigation.data.Place
|
||||||
|
import com.kouros.navigation.data.datastore.DataStoreManager.PreferencesKeys.CAR_LOCATION
|
||||||
|
import com.kouros.navigation.data.datastore.dataStore
|
||||||
|
import com.kouros.navigation.data.tomtom.TomTomRepository
|
||||||
|
import com.kouros.navigation.model.NavigationViewModel
|
||||||
import kotlinx.coroutines.awaitCancellation
|
import kotlinx.coroutines.awaitCancellation
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.flow.flowOf
|
||||||
|
import kotlinx.coroutines.flow.map
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
/** Session class for the Navigation sample app. */
|
/** Session class for the Navigation sample app. */
|
||||||
internal class ClusterSession : Session(), NavigationListener {
|
internal class ClusterSession : CarSession(), NavigationListener {
|
||||||
var mNavigationScreen: NavigationScreen? = null
|
lateinit var mNavigationScreen: NavigationScreen
|
||||||
|
|
||||||
var mNavigationCarSurface: SurfaceRenderer? = null
|
lateinit var surfaceRenderer: SurfaceRenderer
|
||||||
|
|
||||||
var mSettingsAction: Action? = null
|
|
||||||
|
|
||||||
var routeModel = RouteCarModel()
|
var routeModel = RouteCarModel()
|
||||||
|
|
||||||
lateinit var viewModelStoreOwner: ViewModelStoreOwner
|
lateinit var viewModelStoreOwner: ViewModelStoreOwner
|
||||||
|
|
||||||
|
lateinit var navigationViewModel: NavigationViewModel
|
||||||
|
|
||||||
|
lateinit var deviceLocationManager: DeviceLocationManager
|
||||||
|
|
||||||
|
private val lifecycleObserver: LifecycleObserver = object : DefaultLifecycleObserver {
|
||||||
|
override fun onPause(owner: LifecycleOwner) {
|
||||||
|
Log.d(Constants.TAG, "NavigationSession paused")
|
||||||
|
super.onPause(owner)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onResume(owner: LifecycleOwner) {
|
||||||
|
Log.d(Constants.TAG, "NavigationSession resumed")
|
||||||
|
super.onResume(owner)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDestroy(owner: LifecycleOwner) {
|
||||||
|
if (::deviceLocationManager.isInitialized) {
|
||||||
|
deviceLocationManager.stopLocationUpdates()
|
||||||
|
}
|
||||||
|
carContext
|
||||||
|
.stopService(
|
||||||
|
Intent(
|
||||||
|
carContext,
|
||||||
|
NavigationNotificationService::class.java
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
init {
|
||||||
|
lifecycle.addObserver(lifecycleObserver)
|
||||||
|
}
|
||||||
|
|
||||||
override fun onCreateScreen(intent: Intent): Screen {
|
override fun onCreateScreen(intent: Intent): Screen {
|
||||||
Log.i(TAG, "In onCreateScreen()")
|
Log.i(TAG, "In onCreateScreen()")
|
||||||
|
|
||||||
setupViewModelStore()
|
setupViewModelStore()
|
||||||
mSettingsAction =
|
|
||||||
Action.Builder()
|
|
||||||
.setIcon(
|
|
||||||
CarIcon.Builder(
|
|
||||||
IconCompat.createWithResource(
|
|
||||||
carContext, R.drawable.alt_route_48px
|
|
||||||
)
|
|
||||||
)
|
|
||||||
.build()
|
|
||||||
)
|
|
||||||
.setOnClickListener(
|
|
||||||
OnClickListener {})
|
|
||||||
.build()
|
|
||||||
|
|
||||||
mNavigationCarSurface = SurfaceRenderer(carContext, lifecycle, viewModelStoreOwner)
|
surfaceRenderer = SurfaceRenderer(carContext, lifecycle, viewModelStoreOwner, this)
|
||||||
|
navigationViewModel = NavigationViewModel(TomTomRepository())
|
||||||
// mNavigationScreen =
|
mNavigationScreen =
|
||||||
// new NavigationScreen(getCarContext(), mSettingsAction, this, mNavigationCarSurface);
|
NavigationScreen(carContext, surfaceRenderer, this, navigationViewModel)
|
||||||
val action = intent.action
|
val action = intent.action
|
||||||
if (CarContext.ACTION_NAVIGATE == action) {
|
if (CarContext.ACTION_NAVIGATE == action) {
|
||||||
|
Log.i(TAG, "In onCreateScreen() Navigation intent")
|
||||||
CarToast.makeText(
|
CarToast.makeText(
|
||||||
carContext,
|
carContext,
|
||||||
"Navigation intent: " + intent.dataString,
|
"Navigation intent: " + intent.dataString,
|
||||||
@@ -81,8 +113,29 @@ internal class ClusterSession : Session(), NavigationListener {
|
|||||||
)
|
)
|
||||||
.show()
|
.show()
|
||||||
}
|
}
|
||||||
|
initializeManagers()
|
||||||
|
return mNavigationScreen
|
||||||
|
}
|
||||||
|
|
||||||
return mNavigationScreen!!
|
/**
|
||||||
|
* Initializes managers for rendering, sensors, and location.
|
||||||
|
*/
|
||||||
|
private fun initializeManagers() {
|
||||||
|
deviceLocationManager = DeviceLocationManager(
|
||||||
|
carContext = carContext,
|
||||||
|
lifecycleOwner = this,
|
||||||
|
shouldUseCarLocationFlow = flowOf(false),
|
||||||
|
onLocationUpdate = ::updateLocation,
|
||||||
|
onInitialLocation = { location ->
|
||||||
|
|
||||||
|
})
|
||||||
|
deviceLocationManager.startLocationUpdates()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fun updateLocation(location: Location) {
|
||||||
|
Log.d(TAG, "updateLocation $location")
|
||||||
|
surfaceRenderer.updateLocation(location, "")
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCarConfigurationChanged(newConfiguration: Configuration) {
|
override fun onCarConfigurationChanged(newConfiguration: Configuration) {
|
||||||
@@ -125,4 +178,8 @@ internal class ClusterSession : Session(), NavigationListener {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun invalidateNavigationScreen() {
|
||||||
|
mNavigationScreen.invalidate()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ import com.kouros.navigation.data.Constants.TAG
|
|||||||
|
|
||||||
class NavigationCarAppService : CarAppService() {
|
class NavigationCarAppService : CarAppService() {
|
||||||
|
|
||||||
val intentActionNavNotificationOpenApp =
|
val intentActionNavNotificationOpenApp =
|
||||||
"com.kouros.navigation.INTENT_ACTION_NAV_NOTIFICATION_OPEN_APP"
|
"com.kouros.navigation.INTENT_ACTION_NAV_NOTIFICATION_OPEN_APP"
|
||||||
|
|
||||||
val channelId: String = "NavigationSessionChannel"
|
val channelId: String = "NavigationSessionChannel"
|
||||||
@@ -26,7 +26,7 @@ class NavigationCarAppService : CarAppService() {
|
|||||||
@SuppressLint("PrivateResource")
|
@SuppressLint("PrivateResource")
|
||||||
override fun createHostValidator(): HostValidator {
|
override fun createHostValidator(): HostValidator {
|
||||||
|
|
||||||
return HostValidator.ALLOW_ALL_HOSTS_VALIDATOR
|
return HostValidator.ALLOW_ALL_HOSTS_VALIDATOR
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package com.kouros.navigation.car
|
package com.kouros.navigation.car
|
||||||
|
|
||||||
|
import android.Manifest
|
||||||
import android.Manifest.permission
|
import android.Manifest.permission
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.content.pm.PackageManager
|
import android.content.pm.PackageManager
|
||||||
@@ -53,6 +54,8 @@ import com.kouros.navigation.data.tomtom.TomTomRepository
|
|||||||
import com.kouros.navigation.data.valhalla.ValhallaRepository
|
import com.kouros.navigation.data.valhalla.ValhallaRepository
|
||||||
import com.kouros.navigation.model.NavigationViewModel
|
import com.kouros.navigation.model.NavigationViewModel
|
||||||
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 com.kouros.navigation.utils.GeoUtils
|
import com.kouros.navigation.utils.GeoUtils
|
||||||
import com.kouros.navigation.utils.GeoUtils.snapLocation
|
import com.kouros.navigation.utils.GeoUtils.snapLocation
|
||||||
import com.kouros.navigation.utils.NavigationUtils.getViewModel
|
import com.kouros.navigation.utils.NavigationUtils.getViewModel
|
||||||
@@ -65,6 +68,7 @@ import kotlinx.coroutines.launch
|
|||||||
import java.time.Duration
|
import java.time.Duration
|
||||||
import java.time.LocalDateTime
|
import java.time.LocalDateTime
|
||||||
import java.time.ZoneOffset
|
import java.time.ZoneOffset
|
||||||
|
import kotlin.collections.listOf
|
||||||
import kotlin.math.absoluteValue
|
import kotlin.math.absoluteValue
|
||||||
|
|
||||||
|
|
||||||
@@ -74,7 +78,7 @@ import kotlin.math.absoluteValue
|
|||||||
* car hardware sensors, routing engine selection, and screen navigation.
|
* car hardware sensors, routing engine selection, and screen navigation.
|
||||||
* Implements NavigationScreen.Listener for handling navigation events.
|
* Implements NavigationScreen.Listener for handling navigation events.
|
||||||
*/
|
*/
|
||||||
class NavigationSession : Session(), NavigationListener, NavigationObserverCallback {
|
class NavigationSession : CarSession(), NavigationListener, NavigationObserverCallback {
|
||||||
|
|
||||||
// Flag to enable/disable contact access feature
|
// Flag to enable/disable contact access feature
|
||||||
val useContacts = false
|
val useContacts = false
|
||||||
@@ -82,7 +86,7 @@ class NavigationSession : Session(), NavigationListener, NavigationObserverCallb
|
|||||||
// Model for managing route state and navigation logic for Android Auto
|
// Model for managing route state and navigation logic for Android Auto
|
||||||
lateinit var routeModel: RouteCarModel
|
lateinit var routeModel: RouteCarModel
|
||||||
|
|
||||||
var route = ""
|
var route = ""
|
||||||
|
|
||||||
// Main navigation screen displayed to the user
|
// Main navigation screen displayed to the user
|
||||||
lateinit var navigationScreen: NavigationScreen
|
lateinit var navigationScreen: NavigationScreen
|
||||||
@@ -175,24 +179,12 @@ class NavigationSession : Session(), NavigationListener, NavigationObserverCallb
|
|||||||
var lastTrafficDate: LocalDateTime = LocalDateTime.MIN
|
var lastTrafficDate: LocalDateTime = LocalDateTime.MIN
|
||||||
lateinit var observerManager: NavigationObserverManager
|
lateinit var observerManager: NavigationObserverManager
|
||||||
|
|
||||||
val repository = getSettingsRepository(carContext)
|
lateinit var repository: SettingsRepository
|
||||||
|
|
||||||
val settingsViewModel = getSettingsViewModel(carContext)
|
lateinit var settingsViewModel: SettingsViewModel
|
||||||
|
|
||||||
init {
|
init {
|
||||||
lifecycle.addObserver(lifecycleObserver)
|
lifecycle.addObserver(lifecycleObserver)
|
||||||
repository.routingEngineFlow.asLiveData().observe(this, Observer {
|
|
||||||
onRoutingEngineStateUpdated(it)
|
|
||||||
routingEngine = it
|
|
||||||
})
|
|
||||||
|
|
||||||
repository.trafficFlow.asLiveData().observe(this, Observer {
|
|
||||||
showTraffic = it
|
|
||||||
})
|
|
||||||
repository.distanceModeFlow.asLiveData().observe(this, Observer {
|
|
||||||
distanceMode = it
|
|
||||||
})
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -244,10 +236,11 @@ class NavigationSession : Session(), NavigationListener, NavigationObserverCallb
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates the initial screen for the session.
|
* Creates the initial screen for the session.
|
||||||
* Sets up ViewModel store, initializes components, checks permissions,
|
* Sets up ViewModel store, initializes settings, components, checks permissions,
|
||||||
* and returns appropriate starting screen.
|
* and returns appropriate starting screen.
|
||||||
*/
|
*/
|
||||||
override fun onCreateScreen(intent: Intent): Screen {
|
override fun onCreateScreen(intent: Intent): Screen {
|
||||||
|
initializeSettings()
|
||||||
setupViewModelStore()
|
setupViewModelStore()
|
||||||
initializeViewModels()
|
initializeViewModels()
|
||||||
initializeManagers()
|
initializeManagers()
|
||||||
@@ -255,6 +248,26 @@ class NavigationSession : Session(), NavigationListener, NavigationObserverCallb
|
|||||||
return checkPermissionsAndGetScreen()
|
return checkPermissionsAndGetScreen()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Initializes the settings repository and ViewModel.
|
||||||
|
*/
|
||||||
|
private fun initializeSettings() {
|
||||||
|
repository = getSettingsRepository(carContext)
|
||||||
|
settingsViewModel = getSettingsViewModel(carContext)
|
||||||
|
|
||||||
|
repository.routingEngineFlow.asLiveData().observe(this, Observer {
|
||||||
|
onRoutingEngineStateUpdated(it)
|
||||||
|
routingEngine = it
|
||||||
|
})
|
||||||
|
|
||||||
|
repository.trafficFlow.asLiveData().observe(this, Observer {
|
||||||
|
showTraffic = it
|
||||||
|
})
|
||||||
|
repository.distanceModeFlow.asLiveData().observe(this, Observer {
|
||||||
|
distanceMode = it
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets up ViewModelStoreOwner and manages its lifecycle.
|
* Sets up ViewModelStoreOwner and manages its lifecycle.
|
||||||
*/
|
*/
|
||||||
@@ -309,7 +322,7 @@ class NavigationSession : Session(), NavigationListener, NavigationObserverCallb
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
surfaceRenderer = SurfaceRenderer(carContext, lifecycle, viewModelStoreOwner)
|
surfaceRenderer = SurfaceRenderer(carContext, lifecycle, viewModelStoreOwner, this)
|
||||||
|
|
||||||
carSensorManager = CarSensorManager(
|
carSensorManager = CarSensorManager(
|
||||||
carContext = carContext,
|
carContext = carContext,
|
||||||
@@ -335,7 +348,6 @@ class NavigationSession : Session(), NavigationListener, NavigationObserverCallb
|
|||||||
|
|
||||||
|
|
||||||
textToSpeechManager = TextToSpeechManager(carContext)
|
textToSpeechManager = TextToSpeechManager(carContext)
|
||||||
val repository = getSettingsRepository(carContext)
|
|
||||||
repository.guidanceAudioFlow.asLiveData().observe(this, Observer {
|
repository.guidanceAudioFlow.asLiveData().observe(this, Observer {
|
||||||
guidanceAudio = it
|
guidanceAudio = it
|
||||||
})
|
})
|
||||||
@@ -381,6 +393,10 @@ class NavigationSession : Session(), NavigationListener, NavigationObserverCallb
|
|||||||
screenManager.push(navigationScreen)
|
screenManager.push(navigationScreen)
|
||||||
return RequestPermissionScreen(
|
return RequestPermissionScreen(
|
||||||
carContext,
|
carContext,
|
||||||
|
listOf(
|
||||||
|
permission.ACCESS_COARSE_LOCATION,
|
||||||
|
permission.ACCESS_FINE_LOCATION,
|
||||||
|
),
|
||||||
permissionCheckCallback = { screenManager.pop() }
|
permissionCheckCallback = { screenManager.pop() }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -488,7 +504,7 @@ class NavigationSession : Session(), NavigationListener, NavigationObserverCallb
|
|||||||
* Updates the surface renderer with snapped location and street name.
|
* Updates the surface renderer with snapped location and street name.
|
||||||
* Checks if maximal route deviation is exceeded and reroutes if needed.
|
* Checks if maximal route deviation is exceeded and reroutes if needed.
|
||||||
*/
|
*/
|
||||||
private fun snapLocation(location: Location, streetName: String) : Boolean {
|
private fun snapLocation(location: Location, streetName: String): Boolean {
|
||||||
val snappedLocation = snapLocation(location, routeModel.route.maneuverLocations())
|
val snappedLocation = snapLocation(location, routeModel.route.maneuverLocations())
|
||||||
val distance = location.distanceTo(snappedLocation)
|
val distance = location.distanceTo(snappedLocation)
|
||||||
when {
|
when {
|
||||||
@@ -514,7 +530,8 @@ class NavigationSession : Session(), NavigationListener, NavigationObserverCallb
|
|||||||
*/
|
*/
|
||||||
fun updateNavigationScreen() {
|
fun updateNavigationScreen() {
|
||||||
if (routeModel.isNavigating() && routeModel.navState.destination.name.isEmpty()
|
if (routeModel.isNavigating() && routeModel.navState.destination.name.isEmpty()
|
||||||
&& routeModel.navState.destination.street.isEmpty()) {
|
&& routeModel.navState.destination.street.isEmpty()
|
||||||
|
) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
val travelEstimateTrip = routeModel.travelEstimateTrip(carContext, distanceMode)
|
val travelEstimateTrip = routeModel.travelEstimateTrip(carContext, distanceMode)
|
||||||
@@ -728,7 +745,7 @@ class NavigationSession : Session(), NavigationListener, NavigationObserverCallb
|
|||||||
coordinates.add(listOf(it.lon, it.lat))
|
coordinates.add(listOf(it.lon, it.lat))
|
||||||
}
|
}
|
||||||
val speedData = GeoUtils.createPointCollection(coordinates, "radar")
|
val speedData = GeoUtils.createPointCollection(coordinates, "radar")
|
||||||
surfaceRenderer.speedCamerasData.value = speedData
|
surfaceRenderer.speedCameraData.value = speedData
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -755,7 +772,10 @@ class NavigationSession : Session(), NavigationListener, NavigationObserverCallb
|
|||||||
val preview = place.route
|
val preview = place.route
|
||||||
navigationViewModel.previewRoute.value = ""
|
navigationViewModel.previewRoute.value = ""
|
||||||
val location = if (place.stopOver && prevDestination.latitude != 0.0) {
|
val location = if (place.stopOver && prevDestination.latitude != 0.0) {
|
||||||
listOf(location(place.longitude, place.latitude), location(prevDestination.longitude, prevDestination.latitude))
|
listOf(
|
||||||
|
location(place.longitude, place.latitude),
|
||||||
|
location(prevDestination.longitude, prevDestination.latitude)
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
listOf(location(place.longitude, place.latitude))
|
listOf(location(place.longitude, place.latitude))
|
||||||
}
|
}
|
||||||
@@ -853,7 +873,11 @@ class NavigationSession : Session(), NavigationListener, NavigationObserverCallb
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
override fun invalidateNavigationScreen() {
|
||||||
|
navigationScreen.invalidate()
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
// URI host for deep linking
|
// URI host for deep linking
|
||||||
var uriHost: String = "navigation"
|
var uriHost: String = "navigation"
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import android.location.Location
|
|||||||
import android.util.Log
|
import android.util.Log
|
||||||
import androidx.car.app.AppManager
|
import androidx.car.app.AppManager
|
||||||
import androidx.car.app.CarContext
|
import androidx.car.app.CarContext
|
||||||
|
import androidx.car.app.Session
|
||||||
import androidx.car.app.SurfaceCallback
|
import androidx.car.app.SurfaceCallback
|
||||||
import androidx.car.app.SurfaceContainer
|
import androidx.car.app.SurfaceContainer
|
||||||
import androidx.compose.foundation.layout.PaddingValues
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
@@ -32,19 +33,15 @@ import com.kouros.navigation.car.navigation.RouteCarModel
|
|||||||
import com.kouros.navigation.data.Constants.TAG
|
import com.kouros.navigation.data.Constants.TAG
|
||||||
import com.kouros.navigation.data.Constants.TILT
|
import com.kouros.navigation.data.Constants.TILT
|
||||||
import com.kouros.navigation.data.DarkMode
|
import com.kouros.navigation.data.DarkMode
|
||||||
import com.kouros.navigation.data.RouteEngine
|
|
||||||
import com.kouros.navigation.data.ViewStyle
|
import com.kouros.navigation.data.ViewStyle
|
||||||
import com.kouros.navigation.model.BaseStyleModel
|
import com.kouros.navigation.model.BaseStyleModel
|
||||||
import com.kouros.navigation.utils.bearing
|
import com.kouros.navigation.utils.bearing
|
||||||
import com.kouros.navigation.utils.calculateTilt
|
import com.kouros.navigation.utils.calculateTilt
|
||||||
import com.kouros.navigation.utils.calculateZoom
|
import com.kouros.navigation.utils.calculateZoom
|
||||||
import com.kouros.navigation.utils.duration
|
import com.kouros.navigation.utils.duration
|
||||||
import com.kouros.navigation.utils.getSettingsRepository
|
|
||||||
import com.kouros.navigation.utils.location
|
import com.kouros.navigation.utils.location
|
||||||
import com.kouros.navigation.utils.previewZoom
|
import com.kouros.navigation.utils.previewZoom
|
||||||
import com.kouros.navigation.utils.settingsViewModel
|
import com.kouros.navigation.utils.settingsViewModel
|
||||||
import kotlinx.coroutines.flow.first
|
|
||||||
import kotlinx.coroutines.runBlocking
|
|
||||||
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.style.BaseStyle
|
import org.maplibre.compose.style.BaseStyle
|
||||||
@@ -60,8 +57,8 @@ import java.time.LocalDateTime
|
|||||||
class SurfaceRenderer(
|
class SurfaceRenderer(
|
||||||
private var carContext: CarContext,
|
private var carContext: CarContext,
|
||||||
lifecycle: Lifecycle,
|
lifecycle: Lifecycle,
|
||||||
//private var routeModel: RouteCarModel,
|
private var viewModelStoreOwner: ViewModelStoreOwner,
|
||||||
private var viewModelStoreOwner: ViewModelStoreOwner
|
private var navigationSession: CarSession
|
||||||
) : DefaultLifecycleObserver {
|
) : DefaultLifecycleObserver {
|
||||||
|
|
||||||
// Last known location for bearing calculations
|
// Last known location for bearing calculations
|
||||||
@@ -99,7 +96,7 @@ class SurfaceRenderer(
|
|||||||
val trafficData = MutableLiveData(emptyMap<String, String>())
|
val trafficData = MutableLiveData(emptyMap<String, String>())
|
||||||
|
|
||||||
// Speed camera locations as GeoJSON
|
// Speed camera locations as GeoJSON
|
||||||
val speedCamerasData = MutableLiveData("")
|
val speedCameraData = MutableLiveData("")
|
||||||
|
|
||||||
// Current speed in km/h
|
// Current speed in km/h
|
||||||
val speed = MutableLiveData(0F)
|
val speed = MutableLiveData(0F)
|
||||||
@@ -182,7 +179,7 @@ class SurfaceRenderer(
|
|||||||
this.setViewTreeLifecycleOwner(lifecycleOwner)
|
this.setViewTreeLifecycleOwner(lifecycleOwner)
|
||||||
this.setViewTreeSavedStateRegistryOwner(lifecycleOwner)
|
this.setViewTreeSavedStateRegistryOwner(lifecycleOwner)
|
||||||
setContent {
|
setContent {
|
||||||
MapView()
|
MapView()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
presentation = Presentation(carContext, virtualDisplay.display)
|
presentation = Presentation(carContext, virtualDisplay.display)
|
||||||
@@ -227,10 +224,20 @@ class SurfaceRenderer(
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called when user scrolls the map (not currently implemented).
|
* Called when user scrolls the map .
|
||||||
*/
|
*/
|
||||||
override fun onScroll(distanceX: Float, distanceY: Float) {
|
override fun onScroll(distanceX: Float, distanceY: Float) {
|
||||||
synchronized(this@SurfaceRenderer) {
|
synchronized(this@SurfaceRenderer) {
|
||||||
|
viewStyle = ViewStyle.PAN_VIEW
|
||||||
|
if (distanceX != 0.0F) {
|
||||||
|
lastLocation.longitude += (distanceX / 1000) / cameraPosition.value!!.zoom
|
||||||
|
}
|
||||||
|
if (distanceY != 0.0F) {
|
||||||
|
lastLocation.latitude += (distanceY / 1000) / cameraPosition.value!!.zoom
|
||||||
|
}
|
||||||
|
val pos = Position(lastLocation.longitude, lastLocation.latitude)
|
||||||
|
updateCameraPosition( target = pos)
|
||||||
|
navigationSession.invalidateNavigationScreen()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -238,7 +245,9 @@ class SurfaceRenderer(
|
|||||||
* Called when user scales (zooms) the map (not currently implemented).
|
* Called when user scales (zooms) the map (not currently implemented).
|
||||||
*/
|
*/
|
||||||
override fun onScale(focusX: Float, focusY: Float, scaleFactor: Float) {
|
override fun onScale(focusX: Float, focusY: Float, scaleFactor: Float) {
|
||||||
|
synchronized(this@SurfaceRenderer) {
|
||||||
|
Log.d(TAG, "onScale")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -260,7 +269,7 @@ class SurfaceRenderer(
|
|||||||
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 speedCamera: String? by speedCameraData.observeAsState()
|
||||||
val paddingValues = getPaddingValues(height, viewStyle)
|
val paddingValues = getPaddingValues(height, viewStyle)
|
||||||
val cameraState = cameraState(paddingValues, position, tilt)
|
val cameraState = cameraState(paddingValues, position, tilt)
|
||||||
val baseStyle = BaseStyleModel().readStyle(carContext, darkMode, carContext.isDarkMode)
|
val baseStyle = BaseStyleModel().readStyle(carContext, darkMode, carContext.isDarkMode)
|
||||||
@@ -273,7 +282,7 @@ class SurfaceRenderer(
|
|||||||
route,
|
route,
|
||||||
traffic,
|
traffic,
|
||||||
viewStyle,
|
viewStyle,
|
||||||
speedCameras,
|
speedCamera,
|
||||||
showBuildings
|
showBuildings
|
||||||
)
|
)
|
||||||
ShowPosition(cameraState, position, paddingValues, dark)
|
ShowPosition(cameraState, position, paddingValues, dark)
|
||||||
@@ -342,9 +351,9 @@ class SurfaceRenderer(
|
|||||||
viewStyle = ViewStyle.PAN_VIEW
|
viewStyle = ViewStyle.PAN_VIEW
|
||||||
}
|
}
|
||||||
val newZoom = if (zoomSign < 0) {
|
val newZoom = if (zoomSign < 0) {
|
||||||
cameraPosition.value!!.zoom - 1.0
|
cameraPosition.value!!.zoom - 0.2
|
||||||
} else {
|
} else {
|
||||||
cameraPosition.value!!.zoom + 1.0
|
cameraPosition.value!!.zoom + 0.2
|
||||||
}
|
}
|
||||||
if (viewStyle == ViewStyle.VIEW) {
|
if (viewStyle == ViewStyle.VIEW) {
|
||||||
tilt = calculateTilt(newZoom, tilt)
|
tilt = calculateTilt(newZoom, tilt)
|
||||||
@@ -416,7 +425,8 @@ class SurfaceRenderer(
|
|||||||
* Updates camera position with new bearing, zoom, and target.
|
* Updates camera position with new bearing, zoom, and target.
|
||||||
* Posts update to LiveData for UI observation.
|
* Posts update to LiveData for UI observation.
|
||||||
*/
|
*/
|
||||||
fun updateCameraPosition(bearing: Double = 0.0, zoom: Double, target: Position, tilt: Double) {
|
fun updateCameraPosition(bearing: Double = 0.0, zoom: Double = cameraPosition.value!!.zoom ,
|
||||||
|
target: Position, tilt: Double = 0.0) {
|
||||||
synchronized(this) {
|
synchronized(this) {
|
||||||
cameraPosition.postValue(
|
cameraPosition.postValue(
|
||||||
cameraPosition.value!!.copy(
|
cameraPosition.value!!.copy(
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package com.kouros.navigation.car.map
|
package com.kouros.navigation.car.map
|
||||||
|
|
||||||
import android.location.Location
|
import android.location.Location
|
||||||
|
import android.util.Log
|
||||||
import androidx.compose.foundation.Canvas
|
import androidx.compose.foundation.Canvas
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.PaddingValues
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
@@ -52,6 +53,7 @@ import org.maplibre.compose.location.LocationPuck
|
|||||||
import org.maplibre.compose.location.LocationPuckColors
|
import org.maplibre.compose.location.LocationPuckColors
|
||||||
import org.maplibre.compose.location.LocationPuckSizes
|
import org.maplibre.compose.location.LocationPuckSizes
|
||||||
import org.maplibre.compose.location.UserLocationState
|
import org.maplibre.compose.location.UserLocationState
|
||||||
|
import org.maplibre.compose.map.GestureOptions
|
||||||
import org.maplibre.compose.map.MapOptions
|
import org.maplibre.compose.map.MapOptions
|
||||||
import org.maplibre.compose.map.MaplibreMap
|
import org.maplibre.compose.map.MaplibreMap
|
||||||
import org.maplibre.compose.map.OrnamentOptions
|
import org.maplibre.compose.map.OrnamentOptions
|
||||||
@@ -100,7 +102,8 @@ fun MapLibre(
|
|||||||
OrnamentOptions(isScaleBarEnabled = false)
|
OrnamentOptions(isScaleBarEnabled = false)
|
||||||
),
|
),
|
||||||
cameraState = cameraState,
|
cameraState = cameraState,
|
||||||
baseStyle = baseStyle
|
baseStyle = baseStyle,
|
||||||
|
|
||||||
) {
|
) {
|
||||||
getBaseSource(id = "openmaptiles")?.let { tiles ->
|
getBaseSource(id = "openmaptiles")?.let { tiles ->
|
||||||
if (!showBuildings) {
|
if (!showBuildings) {
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ class Simulation {
|
|||||||
longitude = point[0]
|
longitude = point[0]
|
||||||
bearing = curBearing
|
bearing = curBearing
|
||||||
speedAccuracyMetersPerSecond = 1.0f // ~1 m/s
|
speedAccuracyMetersPerSecond = 1.0f // ~1 m/s
|
||||||
speed = 13.0f // ~50 km/h
|
speed = 5.0f
|
||||||
time = System.currentTimeMillis()
|
time = System.currentTimeMillis()
|
||||||
elapsedRealtimeNanos = SystemClock.elapsedRealtimeNanos()
|
elapsedRealtimeNanos = SystemClock.elapsedRealtimeNanos()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,4 +18,5 @@ interface NavigationListener {
|
|||||||
fun navigateToPlace(place: Place)
|
fun navigateToPlace(place: Place)
|
||||||
|
|
||||||
fun recalcRoute(destination: Place)
|
fun recalcRoute(destination: Place)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import android.os.CountDownTimer
|
|||||||
import android.os.Handler
|
import android.os.Handler
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import androidx.car.app.CarContext
|
import androidx.car.app.CarContext
|
||||||
|
import androidx.car.app.CarToast
|
||||||
import androidx.car.app.Screen
|
import androidx.car.app.Screen
|
||||||
import androidx.car.app.model.Action
|
import androidx.car.app.model.Action
|
||||||
import androidx.car.app.model.Action.FLAG_IS_PERSISTENT
|
import androidx.car.app.model.Action.FLAG_IS_PERSISTENT
|
||||||
@@ -20,6 +21,7 @@ import androidx.car.app.navigation.model.Destination
|
|||||||
import androidx.car.app.navigation.model.MapWithContentTemplate
|
import androidx.car.app.navigation.model.MapWithContentTemplate
|
||||||
import androidx.car.app.navigation.model.MessageInfo
|
import androidx.car.app.navigation.model.MessageInfo
|
||||||
import androidx.car.app.navigation.model.NavigationTemplate
|
import androidx.car.app.navigation.model.NavigationTemplate
|
||||||
|
import androidx.car.app.navigation.model.PanModeListener
|
||||||
import androidx.car.app.navigation.model.RoutingInfo
|
import androidx.car.app.navigation.model.RoutingInfo
|
||||||
import androidx.car.app.navigation.model.Step
|
import androidx.car.app.navigation.model.Step
|
||||||
import androidx.car.app.navigation.model.TravelEstimate
|
import androidx.car.app.navigation.model.TravelEstimate
|
||||||
@@ -39,7 +41,6 @@ import com.kouros.navigation.data.ViewStyle
|
|||||||
import com.kouros.navigation.model.NavigationViewModel
|
import com.kouros.navigation.model.NavigationViewModel
|
||||||
import com.kouros.navigation.utils.getSettingsRepository
|
import com.kouros.navigation.utils.getSettingsRepository
|
||||||
import com.kouros.navigation.utils.getSettingsViewModel
|
import com.kouros.navigation.utils.getSettingsViewModel
|
||||||
import com.kouros.navigation.utils.location
|
|
||||||
import kotlinx.coroutines.flow.first
|
import kotlinx.coroutines.flow.first
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
@@ -81,6 +82,8 @@ open class NavigationScreen(
|
|||||||
private lateinit var steps: MutableList<Step>
|
private lateinit var steps: MutableList<Step>
|
||||||
private var junctionImage: CarIcon? = null
|
private var junctionImage: CarIcon? = null
|
||||||
private var backGroundColor = CarColor.BLUE
|
private var backGroundColor = CarColor.BLUE
|
||||||
|
|
||||||
|
private var showAlternativeRoute = false
|
||||||
val observerRecentPlaces = Observer<List<Place>> { newPlaces ->
|
val observerRecentPlaces = Observer<List<Place>> { newPlaces ->
|
||||||
recentPlaces.addAll(newPlaces)
|
recentPlaces.addAll(newPlaces)
|
||||||
if (newPlaces.isNotEmpty() && !tripSuggestionCalled) {
|
if (newPlaces.isNotEmpty() && !tripSuggestionCalled) {
|
||||||
@@ -99,6 +102,9 @@ open class NavigationScreen(
|
|||||||
navigationViewModel.recentPlaces.observe(this, observerRecentPlaces)
|
navigationViewModel.recentPlaces.observe(this, observerRecentPlaces)
|
||||||
tripSuggestion = it
|
tripSuggestion = it
|
||||||
})
|
})
|
||||||
|
repository.alternativeRoutesFlow.asLiveData().observe(this, Observer {
|
||||||
|
showAlternativeRoute = it
|
||||||
|
})
|
||||||
lifecycle.addObserver(object : DefaultLifecycleObserver {
|
lifecycle.addObserver(object : DefaultLifecycleObserver {
|
||||||
override fun onStop(owner: LifecycleOwner) {
|
override fun onStop(owner: LifecycleOwner) {
|
||||||
arrivalTimer?.cancel()
|
arrivalTimer?.cancel()
|
||||||
@@ -145,17 +151,17 @@ open class NavigationScreen(
|
|||||||
.setDestinationTravelEstimate(destinationTravelEstimate)
|
.setDestinationTravelEstimate(destinationTravelEstimate)
|
||||||
.setActionStrip(actionStripBuilder.build())
|
.setActionStrip(actionStripBuilder.build())
|
||||||
.setMapActionStrip(
|
.setMapActionStrip(
|
||||||
mapActionStrip(
|
mapActionStrip(carContext,
|
||||||
surfaceRenderer.viewStyle,
|
surfaceRenderer.viewStyle,
|
||||||
{ zoomPlus() }, { zoomMinus() }, {
|
{ zoomPlus() }, { zoomMinus() }, {
|
||||||
createAction(
|
Action.Builder()
|
||||||
carContext = carContext, R.drawable.ic_pan_24,
|
.setIcon(createCarIcon(carContext, R.drawable.ic_recenter_24))
|
||||||
0,
|
.setFlags(0)
|
||||||
onClickAction = {
|
.setOnClickListener {
|
||||||
surfaceRenderer.setStandardView()
|
surfaceRenderer.setStandardView()
|
||||||
invalidate()
|
invalidate()
|
||||||
}
|
}
|
||||||
)
|
.build()
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
.setBackgroundColor(backGroundColor)
|
.setBackgroundColor(backGroundColor)
|
||||||
@@ -166,24 +172,25 @@ open class NavigationScreen(
|
|||||||
* Creates and returns a template for the default view state.
|
* Creates and returns a template for the default view state.
|
||||||
*/
|
*/
|
||||||
private fun navigationView(actionStripBuilder: ActionStrip.Builder): Template {
|
private fun navigationView(actionStripBuilder: ActionStrip.Builder): Template {
|
||||||
|
val mapActionStrip = mapActionStrip(carContext,
|
||||||
|
surfaceRenderer.viewStyle,
|
||||||
|
{ zoomPlus() }, { zoomMinus() }, {
|
||||||
|
createAction(
|
||||||
|
carContext = carContext, R.drawable.ic_recenter_24,
|
||||||
|
0,
|
||||||
|
onClickAction = {
|
||||||
|
surfaceRenderer.viewStyle = ViewStyle.VIEW
|
||||||
|
invalidate()
|
||||||
|
})
|
||||||
|
})
|
||||||
return NavigationTemplate.Builder()
|
return NavigationTemplate.Builder()
|
||||||
.setBackgroundColor(backGroundColor)
|
.setBackgroundColor(backGroundColor)
|
||||||
.setActionStrip(actionStripBuilder.build())
|
.setActionStrip(actionStripBuilder.build())
|
||||||
.setMapActionStrip(
|
.setMapActionStrip(mapActionStrip)
|
||||||
mapActionStrip(
|
.setPanModeListener { isInPanMode: Boolean ->
|
||||||
surfaceRenderer.viewStyle,
|
Log.d(TAG, "PanMode $isInPanMode")
|
||||||
{ zoomPlus() }, { zoomMinus() }, {
|
}
|
||||||
createAction(
|
|
||||||
carContext = carContext, R.drawable.ic_pan_24,
|
|
||||||
0,
|
|
||||||
onClickAction = {
|
|
||||||
surfaceRenderer.viewStyle = ViewStyle.VIEW
|
|
||||||
invalidate()
|
|
||||||
})
|
|
||||||
})
|
|
||||||
)
|
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -231,11 +238,11 @@ open class NavigationScreen(
|
|||||||
.setBackgroundColor(backGroundColor)
|
.setBackgroundColor(backGroundColor)
|
||||||
.setActionStrip(actionStripBuilder.build())
|
.setActionStrip(actionStripBuilder.build())
|
||||||
.setMapActionStrip(
|
.setMapActionStrip(
|
||||||
mapActionStrip(
|
mapActionStrip(carContext,
|
||||||
surfaceRenderer.viewStyle,
|
surfaceRenderer.viewStyle,
|
||||||
{ zoomPlus() }, { zoomMinus() }, {
|
{ zoomPlus() }, { zoomMinus() }, {
|
||||||
createAction(
|
createAction(
|
||||||
carContext = carContext, R.drawable.ic_pan_24,
|
carContext = carContext, R.drawable.ic_recenter_24,
|
||||||
0,
|
0,
|
||||||
onClickAction = {
|
onClickAction = {
|
||||||
surfaceRenderer.viewStyle = ViewStyle.VIEW
|
surfaceRenderer.viewStyle = ViewStyle.VIEW
|
||||||
@@ -292,7 +299,7 @@ open class NavigationScreen(
|
|||||||
val builder = MapWithContentTemplate.Builder()
|
val builder = MapWithContentTemplate.Builder()
|
||||||
.setContentTemplate(contentTemplate)
|
.setContentTemplate(contentTemplate)
|
||||||
.setActionStrip(
|
.setActionStrip(
|
||||||
mapActionStrip(
|
mapActionStrip(carContext,
|
||||||
ViewStyle.VIEW,
|
ViewStyle.VIEW,
|
||||||
{ settingsAction() },
|
{ settingsAction() },
|
||||||
{
|
{
|
||||||
@@ -304,7 +311,7 @@ open class NavigationScreen(
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
createAction(
|
createAction(
|
||||||
carContext = carContext, R.drawable.ic_zoom_out_24,
|
carContext = carContext, R.drawable.ic_recenter_24,
|
||||||
FLAG_IS_PERSISTENT,
|
FLAG_IS_PERSISTENT,
|
||||||
onClickAction = {
|
onClickAction = {
|
||||||
surfaceRenderer.viewStyle = ViewStyle.VIEW
|
surfaceRenderer.viewStyle = ViewStyle.VIEW
|
||||||
@@ -358,6 +365,7 @@ open class NavigationScreen(
|
|||||||
surfaceRenderer,
|
surfaceRenderer,
|
||||||
place,
|
place,
|
||||||
navigationViewModel,
|
navigationViewModel,
|
||||||
|
showAlternativeRoute
|
||||||
)
|
)
|
||||||
) { obj: Any? ->
|
) { obj: Any? ->
|
||||||
if (obj != null) {
|
if (obj != null) {
|
||||||
|
|||||||
@@ -46,10 +46,15 @@ class PlaceListScreen(
|
|||||||
|
|
||||||
private var routingEngine = 0
|
private var routingEngine = 0
|
||||||
|
|
||||||
|
private var showAlternativeRoute = false
|
||||||
|
|
||||||
init {
|
init {
|
||||||
repository.routingEngineFlow.asLiveData().observe(this, Observer {
|
repository.routingEngineFlow.asLiveData().observe(this, Observer {
|
||||||
routingEngine = it
|
routingEngine = it
|
||||||
})
|
})
|
||||||
|
repository.alternativeRoutesFlow.asLiveData().observe(this, Observer {
|
||||||
|
showAlternativeRoute = it
|
||||||
|
})
|
||||||
lifecycle.addObserver(object : DefaultLifecycleObserver {
|
lifecycle.addObserver(object : DefaultLifecycleObserver {
|
||||||
override fun onStop(owner: LifecycleOwner) {
|
override fun onStop(owner: LifecycleOwner) {
|
||||||
navigationViewModel.recentPlaces.value = emptyList()
|
navigationViewModel.recentPlaces.value = emptyList()
|
||||||
@@ -137,10 +142,11 @@ class PlaceListScreen(
|
|||||||
.pushForResult(
|
.pushForResult(
|
||||||
RoutePreviewScreen(
|
RoutePreviewScreen(
|
||||||
carContext,
|
carContext,
|
||||||
RoutePreviewType.MULTI_ROUTE,
|
if (showAlternativeRoute) RoutePreviewType.MULTI_ROUTE else RoutePreviewType.SINGLE_ROUTE,
|
||||||
surfaceRenderer,
|
surfaceRenderer,
|
||||||
place,
|
place,
|
||||||
navigationViewModel,
|
navigationViewModel,
|
||||||
|
showAlternativeRoute
|
||||||
)
|
)
|
||||||
) { obj: Any? ->
|
) { obj: Any? ->
|
||||||
if (obj != null) {
|
if (obj != null) {
|
||||||
|
|||||||
@@ -16,9 +16,8 @@ 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 permissionCheckCallback: PermissionCheckCallback,
|
val permissions: List<String?> = ArrayList(),
|
||||||
//var mContactsPermissionCheckCallback: LocationPermissionCheckCallback,
|
var permissionCheckCallback: PermissionCheckCallback
|
||||||
val permissions: MutableList<String?> = ArrayList()
|
|
||||||
) : Screen(carContext) {
|
) : Screen(carContext) {
|
||||||
|
|
||||||
/** Callback called when the permission is granted. */
|
/** Callback called when the permission is granted. */
|
||||||
@@ -29,7 +28,7 @@ class RequestPermissionScreen(
|
|||||||
|
|
||||||
override fun onGetTemplate(): Template {
|
override fun onGetTemplate(): Template {
|
||||||
|
|
||||||
var message = ""
|
var message = "This app needs access to location"
|
||||||
if (permissions.contains(permission.ACCESS_FINE_LOCATION))
|
if (permissions.contains(permission.ACCESS_FINE_LOCATION))
|
||||||
message = "This app needs access to location and to car speed"
|
message = "This app needs access to location and to car speed"
|
||||||
if (permissions.contains("android.car.permission.CAR_SPEED"))
|
if (permissions.contains("android.car.permission.CAR_SPEED"))
|
||||||
@@ -83,7 +82,7 @@ fun checkPermission(carContext: CarContext, permission: String) : Boolean {
|
|||||||
screenManager.pop()
|
screenManager.pop()
|
||||||
return@RequestPermissionScreen
|
return@RequestPermissionScreen
|
||||||
},
|
},
|
||||||
permissions
|
permissions = permissions
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -55,6 +55,7 @@ class RoutePreviewScreen(
|
|||||||
private var surfaceRenderer: SurfaceRenderer,
|
private var surfaceRenderer: SurfaceRenderer,
|
||||||
private var destination: Place,
|
private var destination: Place,
|
||||||
private val navigationViewModel: NavigationViewModel,
|
private val navigationViewModel: NavigationViewModel,
|
||||||
|
private var showAlternativeRoute: Boolean
|
||||||
) :
|
) :
|
||||||
Screen(carContext) {
|
Screen(carContext) {
|
||||||
private var isFavorite = false
|
private var isFavorite = false
|
||||||
@@ -71,7 +72,7 @@ class RoutePreviewScreen(
|
|||||||
|
|
||||||
var loading = true
|
var loading = true
|
||||||
|
|
||||||
var showAlternativeRoute = true
|
var flag = FLAG_DEFAULT
|
||||||
|
|
||||||
private val backPressedCallback = object : OnBackPressedCallback(false) {
|
private val backPressedCallback = object : OnBackPressedCallback(false) {
|
||||||
override fun handleOnBackPressed() {
|
override fun handleOnBackPressed() {
|
||||||
@@ -84,7 +85,7 @@ class RoutePreviewScreen(
|
|||||||
routeModel.startNavigation(route)
|
routeModel.startNavigation(route)
|
||||||
surfaceRenderer.setPreviewRouteData(routeModel)
|
surfaceRenderer.setPreviewRouteData(routeModel)
|
||||||
loading = false
|
loading = false
|
||||||
if (routeModel.route.routes.size == 1) {
|
if (routeModel.route.routes.size == 1 && showAlternativeRoute) {
|
||||||
routeType = RoutePreviewType.SINGLE_ROUTE
|
routeType = RoutePreviewType.SINGLE_ROUTE
|
||||||
showAlternativeRoute = false
|
showAlternativeRoute = false
|
||||||
}
|
}
|
||||||
@@ -116,6 +117,10 @@ class RoutePreviewScreen(
|
|||||||
routingEngine = it
|
routingEngine = it
|
||||||
|
|
||||||
})
|
})
|
||||||
|
|
||||||
|
repository.alternativeRoutesFlow.asLiveData().observe(this, Observer {
|
||||||
|
showAlternativeRoute = it
|
||||||
|
})
|
||||||
lifecycleScope.launch {
|
lifecycleScope.launch {
|
||||||
navigationViewModel.loadPreviewRoute(
|
navigationViewModel.loadPreviewRoute(
|
||||||
carContext,
|
carContext,
|
||||||
@@ -178,7 +183,7 @@ class RoutePreviewScreen(
|
|||||||
listContent.build()
|
listContent.build()
|
||||||
} else {
|
} else {
|
||||||
val navigateAction =
|
val navigateAction =
|
||||||
createAction(carContext, R.drawable.navigation_48px, FLAG_DEFAULT,{
|
createAction(carContext, R.drawable.navigation_48px, flag,{
|
||||||
onNavigate(routeModel.navState.currentRouteIndex)
|
onNavigate(routeModel.navState.currentRouteIndex)
|
||||||
})
|
})
|
||||||
val selectRouteAction = createAction(carContext, R.drawable.alt_route_48px, FLAG_IS_PERSISTENT, {
|
val selectRouteAction = createAction(carContext, R.drawable.alt_route_48px, FLAG_IS_PERSISTENT, {
|
||||||
@@ -202,7 +207,7 @@ class RoutePreviewScreen(
|
|||||||
.setContentTemplate(content)
|
.setContentTemplate(content)
|
||||||
.setMapController(
|
.setMapController(
|
||||||
MapController.Builder().setMapActionStrip(
|
MapController.Builder().setMapActionStrip(
|
||||||
mapActionStrip(ViewStyle.PREVIEW, {zoomPlus()}, { zoomMinus()}, {
|
mapActionStrip(carContext, ViewStyle.PREVIEW, {zoomPlus()}, { zoomMinus()}, {
|
||||||
zoomMinus()
|
zoomMinus()
|
||||||
} )).build()
|
} )).build()
|
||||||
|
|
||||||
@@ -225,6 +230,7 @@ class RoutePreviewScreen(
|
|||||||
carContext, R.drawable.ic_zoom_in_24,
|
carContext, R.drawable.ic_zoom_in_24,
|
||||||
FLAG_IS_PERSISTENT,
|
FLAG_IS_PERSISTENT,
|
||||||
onClickAction = {
|
onClickAction = {
|
||||||
|
flag = FLAG_IS_PERSISTENT
|
||||||
surfaceRenderer.handleScale(1)
|
surfaceRenderer.handleScale(1)
|
||||||
invalidate()
|
invalidate()
|
||||||
}
|
}
|
||||||
@@ -239,6 +245,7 @@ class RoutePreviewScreen(
|
|||||||
carContext, R.drawable.ic_zoom_out_24,
|
carContext, R.drawable.ic_zoom_out_24,
|
||||||
FLAG_IS_PERSISTENT,
|
FLAG_IS_PERSISTENT,
|
||||||
onClickAction = {
|
onClickAction = {
|
||||||
|
flag = FLAG_IS_PERSISTENT
|
||||||
surfaceRenderer.handleScale(-1)
|
surfaceRenderer.handleScale(-1)
|
||||||
invalidate()
|
invalidate()
|
||||||
}
|
}
|
||||||
@@ -297,7 +304,7 @@ class RoutePreviewScreen(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun createRow(route: Routes, index: Int): Row {
|
private fun createRow(route: Routes, index: Int): Row {
|
||||||
val navigateAction = createAction(carContext, R.drawable.navigation_48px, FLAG_DEFAULT ) {
|
val navigateAction = createAction(carContext, R.drawable.navigation_48px, flag ) {
|
||||||
this.onNavigate(index)
|
this.onNavigate(index)
|
||||||
}
|
}
|
||||||
val routeText = createRouteText(route)
|
val routeText = createRouteText(route)
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import androidx.car.app.model.CarIcon
|
|||||||
import androidx.car.app.model.Row
|
import androidx.car.app.model.Row
|
||||||
import androidx.core.graphics.createBitmap
|
import androidx.core.graphics.createBitmap
|
||||||
import androidx.core.graphics.drawable.IconCompat
|
import androidx.core.graphics.drawable.IconCompat
|
||||||
|
import com.kouros.data.R
|
||||||
import com.kouros.navigation.data.Constants.CHARGING_STATION
|
import com.kouros.navigation.data.Constants.CHARGING_STATION
|
||||||
import com.kouros.navigation.data.Constants.FUEL_STATION
|
import com.kouros.navigation.data.Constants.FUEL_STATION
|
||||||
import com.kouros.navigation.data.Constants.PHARMACY
|
import com.kouros.navigation.data.Constants.PHARMACY
|
||||||
@@ -82,20 +83,28 @@ fun createActionStripBuilder(action1: () -> Action, action2: () -> Action): Acti
|
|||||||
* Creates an ActionStrip builder for map-related actions like zoom and pan.
|
* Creates an ActionStrip builder for map-related actions like zoom and pan.
|
||||||
*/
|
*/
|
||||||
fun mapActionStrip(
|
fun mapActionStrip(
|
||||||
|
carContext: CarContext,
|
||||||
viewStyle: ViewStyle,
|
viewStyle: ViewStyle,
|
||||||
zoomPlus: () -> Action,
|
zoomPlus: () -> Action,
|
||||||
zoomMinus: () -> Action,
|
zoomMinus: () -> Action,
|
||||||
panAction: () -> Action
|
recenterAction: () -> Action
|
||||||
): ActionStrip {
|
): ActionStrip {
|
||||||
val actionStripBuilder = ActionStrip.Builder()
|
val actionStripBuilder = ActionStrip.Builder()
|
||||||
.addAction(zoomPlus())
|
.addAction(zoomPlus())
|
||||||
.addAction(zoomMinus())
|
.addAction(zoomMinus())
|
||||||
|
actionStripBuilder.addAction(
|
||||||
|
Action.Builder(Action.PAN)
|
||||||
|
.setIcon(createCarIcon(carContext, R.drawable.ic_pan_24))
|
||||||
|
.setFlags(0)
|
||||||
|
.build()
|
||||||
|
)
|
||||||
if (viewStyle == ViewStyle.PAN_VIEW) {
|
if (viewStyle == ViewStyle.PAN_VIEW) {
|
||||||
actionStripBuilder
|
actionStripBuilder
|
||||||
.addAction(
|
.addAction(
|
||||||
panAction()
|
recenterAction()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
return actionStripBuilder.build()
|
return actionStripBuilder.build()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -106,7 +115,7 @@ fun createAction(
|
|||||||
carContext: CarContext,
|
carContext: CarContext,
|
||||||
@DrawableRes iconRes: Int,
|
@DrawableRes iconRes: Int,
|
||||||
flag: Int = FLAG_DEFAULT,
|
flag: Int = FLAG_DEFAULT,
|
||||||
onClickAction: () -> Unit
|
onClickAction: () -> Unit,
|
||||||
): Action {
|
): Action {
|
||||||
return Action.Builder()
|
return Action.Builder()
|
||||||
.setIcon(createCarIcon(carContext, iconRes))
|
.setIcon(createCarIcon(carContext, iconRes))
|
||||||
|
|||||||
@@ -16,17 +16,16 @@ import kotlinx.coroutines.flow.map
|
|||||||
private const val DATASTORE_NAME = "navigation_settings"
|
private const val DATASTORE_NAME = "navigation_settings"
|
||||||
|
|
||||||
|
|
||||||
|
val Context.dataStore: DataStore<Preferences> by preferencesDataStore(name = DATASTORE_NAME)
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Central manager for app settings using DataStore
|
* Central manager for app settings using DataStore
|
||||||
*/
|
*/
|
||||||
class DataStoreManager(private val context: Context) {
|
class DataStoreManager(private val context: Context) {
|
||||||
|
|
||||||
companion object {
|
|
||||||
val Context.dataStore: DataStore<Preferences> by preferencesDataStore(name = DATASTORE_NAME)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Keys
|
// Keys
|
||||||
object PreferencesKeys {
|
companion object PreferencesKeys {
|
||||||
|
|
||||||
val SHOW_3D = booleanPreferencesKey("Show3D")
|
val SHOW_3D = booleanPreferencesKey("Show3D")
|
||||||
|
|
||||||
@@ -66,186 +65,186 @@ class DataStoreManager(private val context: Context) {
|
|||||||
|
|
||||||
val show3DFlow: Flow<Boolean> =
|
val show3DFlow: Flow<Boolean> =
|
||||||
context.dataStore.data.map { preferences ->
|
context.dataStore.data.map { preferences ->
|
||||||
preferences[PreferencesKeys.SHOW_3D] == true
|
preferences[SHOW_3D] == true
|
||||||
}
|
}
|
||||||
val darkModeFlow: Flow<Int> =
|
val darkModeFlow: Flow<Int> =
|
||||||
context.dataStore.data.map { preferences ->
|
context.dataStore.data.map { preferences ->
|
||||||
preferences[PreferencesKeys.DARK_MODE]
|
preferences[DARK_MODE]
|
||||||
?: 0
|
?: 0
|
||||||
}
|
}
|
||||||
|
|
||||||
val avoidMotorwayFlow: Flow<Boolean> =
|
val avoidMotorwayFlow: Flow<Boolean> =
|
||||||
context.dataStore.data.map { preferences ->
|
context.dataStore.data.map { preferences ->
|
||||||
preferences[PreferencesKeys.AVOID_MOTORWAY] == true
|
preferences[AVOID_MOTORWAY] == true
|
||||||
}
|
}
|
||||||
|
|
||||||
val avoidTollwayFlow: Flow<Boolean> =
|
val avoidTollwayFlow: Flow<Boolean> =
|
||||||
context.dataStore.data.map { preferences ->
|
context.dataStore.data.map { preferences ->
|
||||||
preferences[PreferencesKeys.AVOID_TOLLWAY] == true
|
preferences[AVOID_TOLLWAY] == true
|
||||||
}
|
}
|
||||||
|
|
||||||
val avoidFerryFlow: Flow<Boolean> =
|
val avoidFerryFlow: Flow<Boolean> =
|
||||||
context.dataStore.data.map { preferences ->
|
context.dataStore.data.map { preferences ->
|
||||||
preferences[PreferencesKeys.AVOID_FERRY] == true
|
preferences[AVOID_FERRY] == true
|
||||||
}
|
}
|
||||||
|
|
||||||
val useCarLocationFlow: Flow<Boolean> =
|
val useCarLocationFlow: Flow<Boolean> =
|
||||||
context.dataStore.data.map { preferences ->
|
context.dataStore.data.map { preferences ->
|
||||||
preferences[PreferencesKeys.CAR_LOCATION] == true
|
preferences[CAR_LOCATION] == true
|
||||||
}
|
}
|
||||||
|
|
||||||
val routingEngineFlow: Flow<Int> =
|
val routingEngineFlow: Flow<Int> =
|
||||||
context.dataStore.data.map { preferences ->
|
context.dataStore.data.map { preferences ->
|
||||||
preferences[PreferencesKeys.ROUTING_ENGINE]
|
preferences[ROUTING_ENGINE]
|
||||||
?: 2
|
?: 2
|
||||||
}
|
}
|
||||||
|
|
||||||
val lastRouteFlow: Flow<String> =
|
val lastRouteFlow: Flow<String> =
|
||||||
context.dataStore.data.map { preferences ->
|
context.dataStore.data.map { preferences ->
|
||||||
preferences[PreferencesKeys.LAST_ROUTE]
|
preferences[LAST_ROUTE]
|
||||||
?: ""
|
?: ""
|
||||||
}
|
}
|
||||||
|
|
||||||
val tomTomApiKeyFlow: Flow<String> =
|
val tomTomApiKeyFlow: Flow<String> =
|
||||||
context.dataStore.data.map { preferences ->
|
context.dataStore.data.map { preferences ->
|
||||||
preferences[PreferencesKeys.TOMTOM_APIKEY]
|
preferences[TOMTOM_APIKEY]
|
||||||
?: ""
|
?: ""
|
||||||
}
|
}
|
||||||
|
|
||||||
val recentPlacesFlow: Flow<String> =
|
val recentPlacesFlow: Flow<String> =
|
||||||
context.dataStore.data.map { preferences ->
|
context.dataStore.data.map { preferences ->
|
||||||
preferences[PreferencesKeys.RECENT_PLACES]
|
preferences[RECENT_PLACES]
|
||||||
?: ""
|
?: ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
val distanceModeFlow: Flow<Int> =
|
val distanceModeFlow: Flow<Int> =
|
||||||
context.dataStore.data.map { preferences ->
|
context.dataStore.data.map { preferences ->
|
||||||
preferences[PreferencesKeys.DISTANCE_MODE]
|
preferences[DISTANCE_MODE]
|
||||||
?: 0
|
?: 0
|
||||||
}
|
}
|
||||||
|
|
||||||
val guidanceAudioFlow: Flow<Int> =
|
val guidanceAudioFlow: Flow<Int> =
|
||||||
context.dataStore.data.map { preferences ->
|
context.dataStore.data.map { preferences ->
|
||||||
preferences[PreferencesKeys.GUIDANCE_AUDIO]
|
preferences[GUIDANCE_AUDIO]
|
||||||
?: 0
|
?: 0
|
||||||
}
|
}
|
||||||
|
|
||||||
val trafficFlow: Flow<Boolean> =
|
val trafficFlow: Flow<Boolean> =
|
||||||
context.dataStore.data.map { preferences ->
|
context.dataStore.data.map { preferences ->
|
||||||
preferences[PreferencesKeys.TRAFFIC] == true
|
preferences[TRAFFIC] == true
|
||||||
}
|
}
|
||||||
|
|
||||||
val tripSuggestionFlow: Flow<Boolean> =
|
val tripSuggestionFlow: Flow<Boolean> =
|
||||||
context.dataStore.data.map { preferences ->
|
context.dataStore.data.map { preferences ->
|
||||||
preferences[PreferencesKeys.TRIP_SUGGESTION] == true
|
preferences[TRIP_SUGGESTION] == true
|
||||||
}
|
}
|
||||||
|
|
||||||
val engineTypeFlow: Flow<Int> =
|
val engineTypeFlow: Flow<Int> =
|
||||||
context.dataStore.data.map { preferences ->
|
context.dataStore.data.map { preferences ->
|
||||||
preferences[PreferencesKeys.ENGINE_TYPE]
|
preferences[ENGINE_TYPE]
|
||||||
?: EngineType.COMBUSTION.ordinal
|
?: EngineType.COMBUSTION.ordinal
|
||||||
}
|
}
|
||||||
|
|
||||||
val alternativeRoutesFlow: Flow<Boolean> =
|
val alternativeRoutesFlow: Flow<Boolean> =
|
||||||
context.dataStore.data.map { preferences ->
|
context.dataStore.data.map { preferences ->
|
||||||
preferences[PreferencesKeys.ALTERNATIVE_ROUTES] == true
|
preferences[ALTERNATIVE_ROUTES] == true
|
||||||
}
|
}
|
||||||
|
|
||||||
// Save values
|
// Save values
|
||||||
suspend fun setShow3D(enabled: Boolean) {
|
suspend fun setShow3D(enabled: Boolean) {
|
||||||
context.dataStore.edit { preferences ->
|
context.dataStore.edit { preferences ->
|
||||||
preferences[PreferencesKeys.SHOW_3D] = enabled
|
preferences[SHOW_3D] = enabled
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun setDarkMode(mode: Int) {
|
suspend fun setDarkMode(mode: Int) {
|
||||||
context.dataStore.edit { prefs ->
|
context.dataStore.edit { prefs ->
|
||||||
prefs[PreferencesKeys.DARK_MODE] = mode
|
prefs[DARK_MODE] = mode
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun setAvoidMotorway(enabled: Boolean) {
|
suspend fun setAvoidMotorway(enabled: Boolean) {
|
||||||
context.dataStore.edit { preferences ->
|
context.dataStore.edit { preferences ->
|
||||||
preferences[PreferencesKeys.AVOID_MOTORWAY] = enabled
|
preferences[AVOID_MOTORWAY] = enabled
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun setAvoidTollway(enabled: Boolean) {
|
suspend fun setAvoidTollway(enabled: Boolean) {
|
||||||
context.dataStore.edit { preferences ->
|
context.dataStore.edit { preferences ->
|
||||||
preferences[PreferencesKeys.AVOID_TOLLWAY] = enabled
|
preferences[AVOID_TOLLWAY] = enabled
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun setAvoidFerry(enabled: Boolean) {
|
suspend fun setAvoidFerry(enabled: Boolean) {
|
||||||
context.dataStore.edit { preferences ->
|
context.dataStore.edit { preferences ->
|
||||||
preferences[PreferencesKeys.AVOID_FERRY] = enabled
|
preferences[AVOID_FERRY] = enabled
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun setCarLocation(enabled: Boolean) {
|
suspend fun setCarLocation(enabled: Boolean) {
|
||||||
context.dataStore.edit { preferences ->
|
context.dataStore.edit { preferences ->
|
||||||
preferences[PreferencesKeys.CAR_LOCATION] = enabled
|
preferences[CAR_LOCATION] = enabled
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun setRoutingEngine(mode: Int) {
|
suspend fun setRoutingEngine(mode: Int) {
|
||||||
context.dataStore.edit { prefs ->
|
context.dataStore.edit { prefs ->
|
||||||
prefs[PreferencesKeys.ROUTING_ENGINE] = mode
|
prefs[ROUTING_ENGINE] = mode
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun setLastRoute(route: String) {
|
suspend fun setLastRoute(route: String) {
|
||||||
context.dataStore.edit { prefs ->
|
context.dataStore.edit { prefs ->
|
||||||
prefs[PreferencesKeys.LAST_ROUTE] = route
|
prefs[LAST_ROUTE] = route
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun setTomtomApiKey(apiKey: String) {
|
suspend fun setTomtomApiKey(apiKey: String) {
|
||||||
context.dataStore.edit { prefs ->
|
context.dataStore.edit { prefs ->
|
||||||
prefs[PreferencesKeys.TOMTOM_APIKEY] = apiKey
|
prefs[TOMTOM_APIKEY] = apiKey
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun setRecentPlaces(apiKey: String) {
|
suspend fun setRecentPlaces(apiKey: String) {
|
||||||
context.dataStore.edit { prefs ->
|
context.dataStore.edit { prefs ->
|
||||||
prefs[PreferencesKeys.RECENT_PLACES] = apiKey
|
prefs[RECENT_PLACES] = apiKey
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun setDistanceMode(mode: Int) {
|
suspend fun setDistanceMode(mode: Int) {
|
||||||
context.dataStore.edit { prefs ->
|
context.dataStore.edit { prefs ->
|
||||||
prefs[PreferencesKeys.DISTANCE_MODE] = mode
|
prefs[DISTANCE_MODE] = mode
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun setGuidanceAudio(mode: Int) {
|
suspend fun setGuidanceAudio(mode: Int) {
|
||||||
context.dataStore.edit { prefs ->
|
context.dataStore.edit { prefs ->
|
||||||
prefs[PreferencesKeys.GUIDANCE_AUDIO] = mode
|
prefs[GUIDANCE_AUDIO] = mode
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun setTraffic(enabled: Boolean) {
|
suspend fun setTraffic(enabled: Boolean) {
|
||||||
context.dataStore.edit { preferences ->
|
context.dataStore.edit { preferences ->
|
||||||
preferences[PreferencesKeys.TRAFFIC] = enabled
|
preferences[TRAFFIC] = enabled
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun setTripSuggestion(enabled: Boolean) {
|
suspend fun setTripSuggestion(enabled: Boolean) {
|
||||||
context.dataStore.edit { preferences ->
|
context.dataStore.edit { preferences ->
|
||||||
preferences[PreferencesKeys.TRIP_SUGGESTION] = enabled
|
preferences[TRIP_SUGGESTION] = enabled
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun setEngineType(mode: Int) {
|
suspend fun setEngineType(mode: Int) {
|
||||||
context.dataStore.edit { prefs ->
|
context.dataStore.edit { prefs ->
|
||||||
prefs[PreferencesKeys.ENGINE_TYPE] = mode
|
prefs[ENGINE_TYPE] = mode
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun setAlternativeRoutes(enabled: Boolean) {
|
suspend fun setAlternativeRoutes(enabled: Boolean) {
|
||||||
context.dataStore.edit { preferences ->
|
context.dataStore.edit { preferences ->
|
||||||
preferences[PreferencesKeys.ALTERNATIVE_ROUTES] = enabled
|
preferences[ALTERNATIVE_ROUTES] = enabled
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ const val tomtomTrafficUrl = "https://api.tomtom.com/traffic/services/5/incident
|
|||||||
private const val tomtomFields =
|
private const val tomtomFields =
|
||||||
"{incidents{type,geometry{type,coordinates},properties{iconCategory,events{description}}}}"
|
"{incidents{type,geometry{type,coordinates},properties{iconCategory,events{description}}}}"
|
||||||
|
|
||||||
val useLocal = BuildConfig.DEBUG
|
val useLocal = BuildConfig.DEBUG
|
||||||
|
|
||||||
val useLocalTraffic = BuildConfig.DEBUG
|
val useLocalTraffic = BuildConfig.DEBUG
|
||||||
|
|
||||||
|
|||||||
@@ -78,7 +78,6 @@ class TomTomRoute {
|
|||||||
) {
|
) {
|
||||||
val lanes = mutableListOf<Lane>()
|
val lanes = mutableListOf<Lane>()
|
||||||
var startIndex = 0
|
var startIndex = 0
|
||||||
var lastLane: Lane? = null
|
|
||||||
section.lanes?.forEach { itLane ->
|
section.lanes?.forEach { itLane ->
|
||||||
val lane = Lane(
|
val lane = Lane(
|
||||||
location = location(
|
location = location(
|
||||||
@@ -91,13 +90,7 @@ class TomTomRoute {
|
|||||||
endIndex = section.endPointIndex
|
endIndex = section.endPointIndex
|
||||||
)
|
)
|
||||||
startIndex = section.startPointIndex
|
startIndex = section.startPointIndex
|
||||||
if (lastLane == null
|
lanes.add(lane)
|
||||||
|| (!(lastLane.valid && lane.valid
|
|
||||||
&& lastLane.indications == lane.indications))
|
|
||||||
) {
|
|
||||||
lanes.add(lane)
|
|
||||||
}
|
|
||||||
lastLane = lane
|
|
||||||
}
|
}
|
||||||
intersections.add(Intersection(waypoints[startIndex], lanes))
|
intersections.add(Intersection(waypoints[startIndex], lanes))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -76,48 +76,9 @@ open class RouteModel {
|
|||||||
navState = navState.copy(lastLocation = navState.currentLocation)
|
navState = navState.copy(lastLocation = navState.currentLocation)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun nextStep(): StepData {
|
/*
|
||||||
val distanceToNextStep = routeCalculator.leftStepDistance()
|
* Returns the current step
|
||||||
val nextStep = navState.route.nextStep(1)
|
*/
|
||||||
var streetName = nextStep.street
|
|
||||||
var maneuverType = currentStep.maneuver.type
|
|
||||||
if (distanceToNextStep < NEXT_STEP_THRESHOLD) {
|
|
||||||
streetName = nextStep.maneuver.street
|
|
||||||
maneuverType = nextStep.maneuver.type
|
|
||||||
}
|
|
||||||
|
|
||||||
val maneuverIcon = navState.iconMapper.maneuverIcon(maneuverType)
|
|
||||||
// Construct and return the final StepData object
|
|
||||||
return StepData(
|
|
||||||
instruction = streetName,
|
|
||||||
street = "",
|
|
||||||
leftStepDistance = distanceToNextStep,
|
|
||||||
currentManeuverType = maneuverType,
|
|
||||||
icon = maneuverIcon,
|
|
||||||
arrivalTime = routeCalculator.arrivalTime(),
|
|
||||||
leftDistance = routeCalculator.travelLeftDistance(),
|
|
||||||
exitNumber = nextStep.maneuver.exit,
|
|
||||||
message = nextStep.maneuver.message
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun currentLanes(): List<Lane> {
|
|
||||||
var lanes = emptyList<Lane>()
|
|
||||||
if (navState.route.legs().isNotEmpty()) {
|
|
||||||
currentStep.intersection.forEach {
|
|
||||||
if (it.lane.isNotEmpty()) {
|
|
||||||
val distance =
|
|
||||||
navState.lastLocation.distanceTo(location(it.location[0], it.location[1]))
|
|
||||||
if (distance < NEXT_STEP_THRESHOLD) {
|
|
||||||
lanes = it.lane
|
|
||||||
return@forEach
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return lanes
|
|
||||||
}
|
|
||||||
|
|
||||||
fun currentStep(): StepData {
|
fun currentStep(): StepData {
|
||||||
val distanceToNextStep = routeCalculator.leftStepDistance()
|
val distanceToNextStep = routeCalculator.leftStepDistance()
|
||||||
// Determine the maneuver type and corresponding icon
|
// Determine the maneuver type and corresponding icon
|
||||||
@@ -148,6 +109,54 @@ open class RouteModel {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Returns the next step
|
||||||
|
*/
|
||||||
|
fun nextStep(): StepData {
|
||||||
|
val distanceToNextStep = routeCalculator.leftStepDistance()
|
||||||
|
val nextStep = navState.route.nextStep(1)
|
||||||
|
var streetName = nextStep.street
|
||||||
|
var maneuverType = currentStep.maneuver.type
|
||||||
|
if (distanceToNextStep < NEXT_STEP_THRESHOLD) {
|
||||||
|
streetName = nextStep.maneuver.street
|
||||||
|
maneuverType = nextStep.maneuver.type
|
||||||
|
}
|
||||||
|
|
||||||
|
val maneuverIcon = navState.iconMapper.maneuverIcon(maneuverType)
|
||||||
|
// Construct and return the final StepData object
|
||||||
|
return StepData(
|
||||||
|
instruction = streetName,
|
||||||
|
street = "",
|
||||||
|
leftStepDistance = distanceToNextStep,
|
||||||
|
currentManeuverType = maneuverType,
|
||||||
|
icon = maneuverIcon,
|
||||||
|
arrivalTime = routeCalculator.arrivalTime(),
|
||||||
|
leftDistance = routeCalculator.travelLeftDistance(),
|
||||||
|
exitNumber = nextStep.maneuver.exit,
|
||||||
|
message = nextStep.maneuver.message
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Returns the current lanes
|
||||||
|
*/
|
||||||
|
private fun currentLanes(): List<Lane> {
|
||||||
|
var lanes = emptyList<Lane>()
|
||||||
|
if (navState.route.legs().isNotEmpty()) {
|
||||||
|
currentStep.intersection.forEach {
|
||||||
|
if (it.lane.isNotEmpty()) {
|
||||||
|
val distance =
|
||||||
|
navState.lastLocation.distanceTo(location(it.location[0], it.location[1]))
|
||||||
|
if (distance < NEXT_STEP_THRESHOLD) {
|
||||||
|
lanes = it.lane
|
||||||
|
return@forEach
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return lanes
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks for navigating
|
* Checks for navigating
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -1,10 +1,7 @@
|
|||||||
package com.kouros.navigation.model
|
package com.kouros.navigation.model
|
||||||
|
|
||||||
import androidx.datastore.preferences.core.edit
|
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import com.kouros.navigation.data.datastore.DataStoreManager.Companion.dataStore
|
|
||||||
import com.kouros.navigation.data.datastore.DataStoreManager.PreferencesKeys
|
|
||||||
import com.kouros.navigation.repository.SettingsRepository
|
import com.kouros.navigation.repository.SettingsRepository
|
||||||
import kotlinx.coroutines.flow.SharingStarted
|
import kotlinx.coroutines.flow.SharingStarted
|
||||||
import kotlinx.coroutines.flow.stateIn
|
import kotlinx.coroutines.flow.stateIn
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ import kotlin.math.ln
|
|||||||
import kotlin.math.pow
|
import kotlin.math.pow
|
||||||
import kotlin.math.roundToInt
|
import kotlin.math.roundToInt
|
||||||
import kotlin.time.Duration
|
import kotlin.time.Duration
|
||||||
|
import kotlin.time.Duration.Companion.milliseconds
|
||||||
import kotlin.time.Duration.Companion.seconds
|
import kotlin.time.Duration.Companion.seconds
|
||||||
import kotlin.time.DurationUnit
|
import kotlin.time.DurationUnit
|
||||||
import kotlin.time.toDuration
|
import kotlin.time.toDuration
|
||||||
@@ -138,7 +139,7 @@ fun duration(
|
|||||||
lastLocationUpdate: LocalDateTime
|
lastLocationUpdate: LocalDateTime
|
||||||
): Duration {
|
): Duration {
|
||||||
if (preview) {
|
if (preview) {
|
||||||
return 3.seconds
|
return 10.milliseconds
|
||||||
}
|
}
|
||||||
val cameraDuration = if ((lastBearing - bearing).absoluteValue > 20.0) {
|
val cameraDuration = if ((lastBearing - bearing).absoluteValue > 20.0) {
|
||||||
2.seconds
|
2.seconds
|
||||||
|
|||||||
@@ -0,0 +1,10 @@
|
|||||||
|
<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="M240,600L240,640Q240,657 228.5,668.5Q217,680 200,680L160,680Q143,680 131.5,668.5Q120,657 120,640L120,320L204,80Q210,62 225.5,51Q241,40 260,40L700,40Q719,40 734.5,51Q750,62 756,80L840,320L840,640Q840,657 828.5,668.5Q817,680 800,680L760,680Q743,680 731.5,668.5Q720,657 720,640L720,600L240,600ZM232,240L728,240L686,120L274,120L232,240ZM200,320L200,320L200,520L200,520L200,320ZM300,480Q325,480 342.5,462.5Q360,445 360,420Q360,395 342.5,377.5Q325,360 300,360Q275,360 257.5,377.5Q240,395 240,420Q240,445 257.5,462.5Q275,480 300,480ZM660,480Q685,480 702.5,462.5Q720,445 720,420Q720,395 702.5,377.5Q685,360 660,360Q635,360 617.5,377.5Q600,395 600,420Q600,445 617.5,462.5Q635,480 660,480ZM520,920L280,800L440,800L440,720L680,840L520,840L520,920ZM200,520L760,520L760,320L200,320L200,520Z"/>
|
||||||
|
</vector>
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
<!--
|
||||||
|
Copyright 2021 The Android Open Source Project
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24">
|
||||||
|
<path
|
||||||
|
android:fillColor="@android:color/white"
|
||||||
|
android:pathData="M12,2C6.49,2 2,6.49 2,12s4.49,10 10,10 10,-4.49 10,-10S17.51,2 12,2zM12,20c-4.41,0 -8,-3.59 -8,-8s3.59,-8 8,-8 8,3.59 8,8 -3.59,8 -8,8zM15,12c0,1.66 -1.34,3 -3,3s-3,-1.34 -3,-3 1.34,-3 3,-3 3,1.34 3,3z"/>
|
||||||
|
</vector>
|
||||||