Distance settings
This commit is contained in:
@@ -13,8 +13,8 @@ android {
|
|||||||
applicationId = "com.kouros.navigation"
|
applicationId = "com.kouros.navigation"
|
||||||
minSdk = 33
|
minSdk = 33
|
||||||
targetSdk = 36
|
targetSdk = 36
|
||||||
versionCode = 57
|
versionCode = 59
|
||||||
versionName = "0.2.0.57"
|
versionName = "0.2.0.59"
|
||||||
base.archivesName = "navi-$versionName"
|
base.archivesName = "navi-$versionName"
|
||||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||||
}
|
}
|
||||||
@@ -83,8 +83,8 @@ dependencies {
|
|||||||
|
|
||||||
implementation(libs.accompanist.permissions)
|
implementation(libs.accompanist.permissions)
|
||||||
|
|
||||||
implementation(project(":common:data"))
|
|
||||||
implementation(project(":common:car"))
|
implementation(project(":common:car"))
|
||||||
|
implementation(project(":common:data"))
|
||||||
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)
|
||||||
|
|||||||
@@ -20,12 +20,13 @@ import androidx.compose.ui.unit.dp
|
|||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
import com.kouros.data.R
|
import com.kouros.data.R
|
||||||
import com.kouros.navigation.data.StepData
|
import com.kouros.navigation.data.StepData
|
||||||
|
import com.kouros.navigation.utils.formattedDistance
|
||||||
import com.kouros.navigation.utils.round
|
import com.kouros.navigation.utils.round
|
||||||
|
|
||||||
private const val MANEUVER_TYPE_EXIT_RIGHT = 45
|
private const val MANEUVER_TYPE_EXIT_RIGHT = 45
|
||||||
private const val MANEUVER_TYPE_EXIT_LEFT = 46
|
private const val MANEUVER_TYPE_EXIT_LEFT = 46
|
||||||
private const val METERS_PER_KILOMETER = 1000.0
|
private const val METERS_PER_KILOMETER = 1000.0
|
||||||
private const val DISTANCE_THRESHOLD_METERS = 1000
|
private const val DISTANCE_THRESHOLD = 1000
|
||||||
|
|
||||||
private val CardTopPadding = 60.dp
|
private val CardTopPadding = 60.dp
|
||||||
private val CardElevation = 6.dp
|
private val CardElevation = 6.dp
|
||||||
@@ -92,8 +93,9 @@ fun NavigationInfo(
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun DistanceText(distance: Double) {
|
private fun DistanceText(distance: Double) {
|
||||||
|
val distancexx = formattedDistance(2, distance)
|
||||||
val formattedDistance = when {
|
val formattedDistance = when {
|
||||||
distance < DISTANCE_THRESHOLD_METERS -> "${distance.toInt()} m"
|
distance < DISTANCE_THRESHOLD -> "${distance.toInt()} m"
|
||||||
else -> "${(distance / METERS_PER_KILOMETER).round(1)} km"
|
else -> "${(distance / METERS_PER_KILOMETER).round(1)} km"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,24 +0,0 @@
|
|||||||
package com.kouros.navigation
|
|
||||||
|
|
||||||
import androidx.test.platform.app.InstrumentationRegistry
|
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
|
||||||
|
|
||||||
import org.junit.Test
|
|
||||||
import org.junit.runner.RunWith
|
|
||||||
|
|
||||||
import org.junit.Assert.*
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Instrumented test, which will execute on an Android device.
|
|
||||||
*
|
|
||||||
* See [testing documentation](http://d.android.com/tools/testing).
|
|
||||||
*/
|
|
||||||
@RunWith(AndroidJUnit4::class)
|
|
||||||
class ExampleInstrumentedTest {
|
|
||||||
@Test
|
|
||||||
fun useAppContext() {
|
|
||||||
// Context of the app under test.
|
|
||||||
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
|
|
||||||
assertEquals("com.kouros.navigation", appContext.packageName)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
package com.kouros.navigation
|
|
||||||
|
|
||||||
import org.junit.Test
|
|
||||||
|
|
||||||
import org.junit.Assert.*
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Example local unit test, which will execute on the development machine (host).
|
|
||||||
*
|
|
||||||
* See [testing documentation](http://d.android.com/tools/testing).
|
|
||||||
*/
|
|
||||||
class ExampleUnitTest {
|
|
||||||
@Test
|
|
||||||
fun addition_isCorrect() {
|
|
||||||
assertEquals(4, 2 + 2)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -102,7 +102,7 @@ class RouteModelTest {
|
|||||||
} else {
|
} else {
|
||||||
assertEquals(stepData.currentManeuverType, Maneuver.TYPE_TURN_NORMAL_LEFT)
|
assertEquals(stepData.currentManeuverType, Maneuver.TYPE_TURN_NORMAL_LEFT)
|
||||||
}
|
}
|
||||||
assertEquals(stepData.leftStepDistance, 300.0, 1.0)
|
assertEquals(stepData.leftStepDistance, 301.0, 1.0)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -192,7 +192,7 @@ class RouteModelTest {
|
|||||||
val location: Location = location( 11.584578, 48.183653)
|
val location: Location = location( 11.584578, 48.183653)
|
||||||
routeModel.updateLocation(location, NavigationViewModel(TomTomRepository()) )
|
routeModel.updateLocation(location, NavigationViewModel(TomTomRepository()) )
|
||||||
val step = routeModel.currentStep()
|
val step = routeModel.currentStep()
|
||||||
assertEquals(step.leftStepDistance, 650.0, 0.1)
|
assertEquals(step.leftStepDistance, 645.0, 1.0)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -200,6 +200,6 @@ class RouteModelTest {
|
|||||||
val location: Location = location( 11.578911, 48.185565)
|
val location: Location = location( 11.578911, 48.185565)
|
||||||
routeModel.updateLocation(location, NavigationViewModel(TomTomRepository()) )
|
routeModel.updateLocation(location, NavigationViewModel(TomTomRepository()) )
|
||||||
val step = routeModel.currentStep()
|
val step = routeModel.currentStep()
|
||||||
assertEquals(step.leftStepDistance , 30.0, 1.0)
|
assertEquals(step.leftStepDistance , 34.0, 1.0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -43,7 +43,7 @@ class DeviceLocationManager(
|
|||||||
* Only processes location if car location hardware is not being used.
|
* Only processes location if car location hardware is not being used.
|
||||||
*/
|
*/
|
||||||
private val locationListener: LocationListenerCompat = LocationListenerCompat { location ->
|
private val locationListener: LocationListenerCompat = LocationListenerCompat { location ->
|
||||||
if (location != null && shouldUseDeviceLocation) {
|
if (shouldUseDeviceLocation) {
|
||||||
onLocationUpdate(location)
|
onLocationUpdate(location)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
package com.kouros.navigation.car
|
package com.kouros.navigation.car
|
||||||
|
|
||||||
import android.Manifest
|
import android.Manifest.permission
|
||||||
import android.content.Context
|
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.content.pm.PackageManager
|
import android.content.pm.PackageManager
|
||||||
import android.location.Location
|
import android.location.Location
|
||||||
@@ -11,9 +10,10 @@ 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.connection.CarConnection
|
||||||
import androidx.core.net.toUri
|
import androidx.car.app.navigation.NavigationManager
|
||||||
|
import androidx.car.app.navigation.NavigationManagerCallback
|
||||||
|
import androidx.car.app.navigation.model.Trip
|
||||||
import androidx.lifecycle.DefaultLifecycleObserver
|
import androidx.lifecycle.DefaultLifecycleObserver
|
||||||
import androidx.lifecycle.Lifecycle
|
|
||||||
import androidx.lifecycle.LifecycleObserver
|
import androidx.lifecycle.LifecycleObserver
|
||||||
import androidx.lifecycle.LifecycleOwner
|
import androidx.lifecycle.LifecycleOwner
|
||||||
import androidx.lifecycle.ViewModelStore
|
import androidx.lifecycle.ViewModelStore
|
||||||
@@ -23,6 +23,8 @@ import com.kouros.navigation.car.navigation.RouteCarModel
|
|||||||
import com.kouros.navigation.car.screen.NavigationScreen
|
import com.kouros.navigation.car.screen.NavigationScreen
|
||||||
import com.kouros.navigation.car.screen.RequestPermissionScreen
|
import com.kouros.navigation.car.screen.RequestPermissionScreen
|
||||||
import com.kouros.navigation.car.screen.SearchScreen
|
import com.kouros.navigation.car.screen.SearchScreen
|
||||||
|
import com.kouros.navigation.data.Constants.AUTOMOTIVE_CAR_SPEED_PERMISSION
|
||||||
|
import com.kouros.navigation.data.Constants.GMS_CAR_SPEED_PERMISSION
|
||||||
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
|
||||||
@@ -35,9 +37,7 @@ import com.kouros.navigation.utils.GeoUtils.snapLocation
|
|||||||
import com.kouros.navigation.utils.NavigationUtils.getViewModel
|
import com.kouros.navigation.utils.NavigationUtils.getViewModel
|
||||||
import kotlinx.coroutines.awaitCancellation
|
import kotlinx.coroutines.awaitCancellation
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import android.Manifest.permission
|
|
||||||
import com.kouros.navigation.data.Constants.AUTOMOTIVE_CAR_SPEED_PERMISSION
|
|
||||||
import com.kouros.navigation.data.Constants.GMS_CAR_SPEED_PERMISSION
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Main session for Android Auto/Automotive OS navigation.
|
* Main session for Android Auto/Automotive OS navigation.
|
||||||
@@ -65,12 +65,17 @@ class NavigationSession : Session(), NavigationScreen.Listener {
|
|||||||
// Manages device GPS location updates
|
// Manages device GPS location updates
|
||||||
lateinit var deviceLocationManager: DeviceLocationManager
|
lateinit var deviceLocationManager: DeviceLocationManager
|
||||||
|
|
||||||
|
lateinit var navigationManager: NavigationManager
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Lifecycle observer for managing session lifecycle events.
|
* Lifecycle observer for managing session lifecycle events.
|
||||||
* Cleans up resources when the session is destroyed.
|
* Cleans up resources when the session is destroyed.
|
||||||
*/
|
*/
|
||||||
private val lifecycleObserver: LifecycleObserver = object : DefaultLifecycleObserver {
|
private val lifecycleObserver: LifecycleObserver = object : DefaultLifecycleObserver {
|
||||||
override fun onDestroy(owner: LifecycleOwner) {
|
override fun onDestroy(owner: LifecycleOwner) {
|
||||||
|
if (::navigationManager.isInitialized) {
|
||||||
|
navigationManager.clearNavigationManagerCallback()
|
||||||
|
}
|
||||||
if (::carSensorManager.isInitialized) {
|
if (::carSensorManager.isInitialized) {
|
||||||
carSensorManager.cleanup()
|
carSensorManager.cleanup()
|
||||||
}
|
}
|
||||||
@@ -178,6 +183,21 @@ class NavigationSession : Session(), NavigationScreen.Listener {
|
|||||||
* Initializes managers for rendering, sensors, and location.
|
* Initializes managers for rendering, sensors, and location.
|
||||||
*/
|
*/
|
||||||
private fun initializeManagers() {
|
private fun initializeManagers() {
|
||||||
|
|
||||||
|
navigationManager = carContext.getCarService(NavigationManager::class.java)
|
||||||
|
navigationManager.setNavigationManagerCallback(object : NavigationManagerCallback {
|
||||||
|
override fun onAutoDriveEnabled() {
|
||||||
|
// Called when the app should simulate navigation (e.g., for testing)
|
||||||
|
// Implement your simulation logic here
|
||||||
|
Log.d("CarApp", "Auto Drive Enabled")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onStopNavigation() {
|
||||||
|
// Called when the user stops navigation in the car screen
|
||||||
|
Log.d("CarApp", "Stop Navigation Requested")
|
||||||
|
// Stop turn-by-turn logic and clean up
|
||||||
|
}
|
||||||
|
})
|
||||||
surfaceRenderer = SurfaceRenderer(carContext, lifecycle, routeModel, viewModelStoreOwner)
|
surfaceRenderer = SurfaceRenderer(carContext, lifecycle, routeModel, viewModelStoreOwner)
|
||||||
|
|
||||||
carSensorManager = CarSensorManager(
|
carSensorManager = CarSensorManager(
|
||||||
@@ -346,6 +366,20 @@ class NavigationSession : Session(), NavigationScreen.Listener {
|
|||||||
*/
|
*/
|
||||||
override fun stopNavigation() {
|
override fun stopNavigation() {
|
||||||
routeModel.stopNavigation()
|
routeModel.stopNavigation()
|
||||||
|
navigationManager.navigationEnded()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Start navigation process.
|
||||||
|
* Called when user starts navigation
|
||||||
|
*/
|
||||||
|
override fun startNavigation() {
|
||||||
|
navigationManager.navigationStarted()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun updateTrip(trip: Trip) {
|
||||||
|
Log.d("Trip", trip.toString())
|
||||||
|
navigationManager.updateTrip(trip)
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
package com.kouros.navigation.car.navigation
|
package com.kouros.navigation.car.navigation
|
||||||
|
|
||||||
import android.location.Location
|
|
||||||
import android.text.SpannableString
|
import android.text.SpannableString
|
||||||
import androidx.annotation.DrawableRes
|
import androidx.annotation.DrawableRes
|
||||||
import androidx.annotation.StringRes
|
import androidx.annotation.StringRes
|
||||||
@@ -26,9 +25,7 @@ import androidx.core.graphics.drawable.IconCompat
|
|||||||
import com.kouros.data.R
|
import com.kouros.data.R
|
||||||
import com.kouros.navigation.data.StepData
|
import com.kouros.navigation.data.StepData
|
||||||
import com.kouros.navigation.model.RouteModel
|
import com.kouros.navigation.model.RouteModel
|
||||||
import com.kouros.navigation.utils.location
|
import com.kouros.navigation.utils.formattedDistance
|
||||||
import java.util.Collections
|
|
||||||
import java.util.Locale
|
|
||||||
import java.util.TimeZone
|
import java.util.TimeZone
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
|
|
||||||
@@ -87,24 +84,21 @@ class RouteCarModel() : RouteModel() {
|
|||||||
return step
|
return step
|
||||||
}
|
}
|
||||||
|
|
||||||
fun travelEstimate(carContext: CarContext): TravelEstimate {
|
fun travelEstimate(carContext: CarContext, distanceMode: Int): TravelEstimate {
|
||||||
val timeLeft = routeCalculator.travelLeftTime()
|
val timeLeft = routeCalculator.travelLeftTime()
|
||||||
val timeToDestinationMillis =
|
val timeToDestinationMillis =
|
||||||
TimeUnit.SECONDS.toMillis(timeLeft.toLong())
|
TimeUnit.SECONDS.toMillis(timeLeft.toLong())
|
||||||
val leftDistance = routeCalculator.travelLeftDistance() / 1000
|
val distance = formattedDistance(distanceMode, routeCalculator.travelLeftDistance())
|
||||||
val displayUnit = if (leftDistance > 1.0) {
|
|
||||||
Distance.UNIT_KILOMETERS
|
|
||||||
} else {
|
|
||||||
Distance.UNIT_METERS
|
|
||||||
}
|
|
||||||
val arrivalTime = DateTimeWithZone.create(
|
val arrivalTime = DateTimeWithZone.create(
|
||||||
routeCalculator.arrivalTime(),
|
routeCalculator.arrivalTime(),
|
||||||
TimeZone.getTimeZone("Europe/Berlin")
|
TimeZone.getDefault()
|
||||||
)
|
)
|
||||||
|
val traffic = (route.routes.first().summary.trafficDelay/60).toInt()
|
||||||
val travelBuilder = TravelEstimate.Builder( // The estimated distance to the destination.
|
val travelBuilder = TravelEstimate.Builder( // The estimated distance to the destination.
|
||||||
Distance.create(
|
Distance.create(
|
||||||
leftDistance,
|
distance.first,
|
||||||
displayUnit
|
distance.second
|
||||||
), // Arrival time at the destination with the destination time zone.
|
), // Arrival time at the destination with the destination time zone.
|
||||||
arrivalTime
|
arrivalTime
|
||||||
)
|
)
|
||||||
@@ -115,7 +109,7 @@ class RouteCarModel() : RouteModel() {
|
|||||||
)
|
)
|
||||||
.setRemainingTimeColor(CarColor.GREEN)
|
.setRemainingTimeColor(CarColor.GREEN)
|
||||||
.setRemainingDistanceColor(CarColor.BLUE)
|
.setRemainingDistanceColor(CarColor.BLUE)
|
||||||
|
.setTripText(CarText.create("$traffic min"))
|
||||||
if (navState.travelMessage.isNotEmpty()) {
|
if (navState.travelMessage.isNotEmpty()) {
|
||||||
travelBuilder.setTripIcon(createCarIcon(carContext, R.drawable.warning_24px))
|
travelBuilder.setTripIcon(createCarIcon(carContext, R.drawable.warning_24px))
|
||||||
travelBuilder.setTripText(CarText.create(navState.travelMessage))
|
travelBuilder.setTripText(CarText.create(navState.travelMessage))
|
||||||
|
|||||||
@@ -42,6 +42,12 @@ class DisplaySettings(private val carContext: CarContext) : Screen(carContext) {
|
|||||||
R.string.dark_mode
|
R.string.dark_mode
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
listBuilder.addItem(
|
||||||
|
buildRowForScreenTemplate(
|
||||||
|
DistanceSettings(carContext),
|
||||||
|
R.string.distance_units
|
||||||
|
)
|
||||||
|
)
|
||||||
return ListTemplate.Builder()
|
return ListTemplate.Builder()
|
||||||
.setSingleList(listBuilder.build())
|
.setSingleList(listBuilder.build())
|
||||||
.setHeader(
|
.setHeader(
|
||||||
|
|||||||
@@ -0,0 +1,84 @@
|
|||||||
|
package com.kouros.navigation.car.screen
|
||||||
|
|
||||||
|
import androidx.car.app.CarContext
|
||||||
|
import androidx.car.app.CarToast
|
||||||
|
import androidx.car.app.Screen
|
||||||
|
import androidx.car.app.model.Action
|
||||||
|
import androidx.car.app.model.Header
|
||||||
|
import androidx.car.app.model.ItemList
|
||||||
|
import androidx.car.app.model.ListTemplate
|
||||||
|
import androidx.car.app.model.Row
|
||||||
|
import androidx.car.app.model.SectionedItemList
|
||||||
|
import androidx.car.app.model.Template
|
||||||
|
import androidx.lifecycle.lifecycleScope
|
||||||
|
import com.kouros.data.R
|
||||||
|
import com.kouros.navigation.utils.getSettingsViewModel
|
||||||
|
import kotlinx.coroutines.flow.first
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
|
class DistanceSettings(private val carContext: CarContext) : Screen(carContext) {
|
||||||
|
|
||||||
|
private var distanceSettings = 0
|
||||||
|
|
||||||
|
val settingsViewModel = getSettingsViewModel(carContext)
|
||||||
|
|
||||||
|
init {
|
||||||
|
lifecycleScope.launch {
|
||||||
|
settingsViewModel.distanceMode.first()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onGetTemplate(): Template {
|
||||||
|
distanceSettings = settingsViewModel.distanceMode.value
|
||||||
|
val templateBuilder = ListTemplate.Builder()
|
||||||
|
val radioList =
|
||||||
|
ItemList.Builder()
|
||||||
|
.addItem(
|
||||||
|
buildRowForTemplate(
|
||||||
|
R.string.automaticaly,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.addItem(
|
||||||
|
buildRowForTemplate(
|
||||||
|
R.string.kilometer,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.addItem(
|
||||||
|
buildRowForTemplate(
|
||||||
|
R.string.miles,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.setOnSelectedListener { index: Int ->
|
||||||
|
this.onSelected(index)
|
||||||
|
}
|
||||||
|
.setSelectedIndex(distanceSettings)
|
||||||
|
.build()
|
||||||
|
|
||||||
|
return templateBuilder
|
||||||
|
.addSectionedList(
|
||||||
|
SectionedItemList.create(
|
||||||
|
radioList,
|
||||||
|
carContext.getString(R.string.distance_units)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.setHeader(
|
||||||
|
Header.Builder()
|
||||||
|
.setTitle(carContext.getString(R.string.distance_units))
|
||||||
|
.setStartHeaderAction(Action.BACK)
|
||||||
|
.build()
|
||||||
|
)
|
||||||
|
.build()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private fun onSelected(index: Int) {
|
||||||
|
settingsViewModel.onDistanceModeChanged(index)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun buildRowForTemplate(title: Int): Row {
|
||||||
|
return Row.Builder()
|
||||||
|
.setTitle(carContext.getString(title))
|
||||||
|
.build()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,6 +1,5 @@
|
|||||||
package com.kouros.navigation.car.screen
|
package com.kouros.navigation.car.screen
|
||||||
|
|
||||||
import android.Manifest
|
|
||||||
import android.content.pm.PackageManager
|
import android.content.pm.PackageManager
|
||||||
import android.location.Location
|
import android.location.Location
|
||||||
import android.location.LocationManager
|
import android.location.LocationManager
|
||||||
@@ -18,12 +17,16 @@ import androidx.car.app.model.Distance
|
|||||||
import androidx.car.app.model.Header
|
import androidx.car.app.model.Header
|
||||||
import androidx.car.app.model.MessageTemplate
|
import androidx.car.app.model.MessageTemplate
|
||||||
import androidx.car.app.model.Template
|
import androidx.car.app.model.Template
|
||||||
|
import androidx.car.app.navigation.model.Destination
|
||||||
import androidx.car.app.navigation.model.Maneuver
|
import androidx.car.app.navigation.model.Maneuver
|
||||||
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.RoutingInfo
|
import androidx.car.app.navigation.model.RoutingInfo
|
||||||
|
import androidx.car.app.navigation.model.Trip
|
||||||
import androidx.core.graphics.drawable.IconCompat
|
import androidx.core.graphics.drawable.IconCompat
|
||||||
|
import androidx.lifecycle.Observer
|
||||||
|
import androidx.lifecycle.asLiveData
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
import com.kouros.data.R
|
import com.kouros.data.R
|
||||||
import com.kouros.navigation.car.SurfaceRenderer
|
import com.kouros.navigation.car.SurfaceRenderer
|
||||||
@@ -31,12 +34,12 @@ import com.kouros.navigation.car.ViewStyle
|
|||||||
import com.kouros.navigation.car.navigation.RouteCarModel
|
import com.kouros.navigation.car.navigation.RouteCarModel
|
||||||
import com.kouros.navigation.car.screen.observers.NavigationObserverCallback
|
import com.kouros.navigation.car.screen.observers.NavigationObserverCallback
|
||||||
import com.kouros.navigation.car.screen.observers.NavigationObserverManager
|
import com.kouros.navigation.car.screen.observers.NavigationObserverManager
|
||||||
import com.kouros.navigation.data.Constants
|
|
||||||
import com.kouros.navigation.data.Constants.DESTINATION_ARRIVAL_DISTANCE
|
import com.kouros.navigation.data.Constants.DESTINATION_ARRIVAL_DISTANCE
|
||||||
import com.kouros.navigation.data.Place
|
import com.kouros.navigation.data.Place
|
||||||
import com.kouros.navigation.data.overpass.Elements
|
import com.kouros.navigation.data.overpass.Elements
|
||||||
import com.kouros.navigation.model.NavigationViewModel
|
import com.kouros.navigation.model.NavigationViewModel
|
||||||
import com.kouros.navigation.utils.GeoUtils
|
import com.kouros.navigation.utils.GeoUtils
|
||||||
|
import com.kouros.navigation.utils.formattedDistance
|
||||||
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 com.kouros.navigation.utils.location
|
||||||
@@ -48,6 +51,10 @@ import java.time.LocalDateTime
|
|||||||
import java.time.ZoneOffset
|
import java.time.ZoneOffset
|
||||||
import kotlin.math.absoluteValue
|
import kotlin.math.absoluteValue
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Main screen for car navigation.
|
||||||
|
* Handles different navigation states and provides corresponding templates.
|
||||||
|
*/
|
||||||
class NavigationScreen(
|
class NavigationScreen(
|
||||||
carContext: CarContext,
|
carContext: CarContext,
|
||||||
private var surfaceRenderer: SurfaceRenderer,
|
private var surfaceRenderer: SurfaceRenderer,
|
||||||
@@ -60,10 +67,15 @@ class NavigationScreen(
|
|||||||
interface Listener {
|
interface Listener {
|
||||||
/** Stops navigation. */
|
/** Stops navigation. */
|
||||||
fun stopNavigation()
|
fun stopNavigation()
|
||||||
|
|
||||||
|
/** Starts navigation. */
|
||||||
|
fun startNavigation()
|
||||||
|
|
||||||
|
/** Updates trip information. */
|
||||||
|
fun updateTrip(trip: Trip)
|
||||||
}
|
}
|
||||||
|
|
||||||
val backGroundColor = CarColor.BLUE
|
val backGroundColor = CarColor.BLUE
|
||||||
|
|
||||||
var currentNavigationLocation = Location(LocationManager.GPS_PROVIDER)
|
var currentNavigationLocation = Location(LocationManager.GPS_PROVIDER)
|
||||||
var recentPlace = Place()
|
var recentPlace = Place()
|
||||||
var navigationType = NavigationType.VIEW
|
var navigationType = NavigationType.VIEW
|
||||||
@@ -74,18 +86,25 @@ class NavigationScreen(
|
|||||||
|
|
||||||
val observerManager = NavigationObserverManager(navigationViewModel, this)
|
val observerManager = NavigationObserverManager(navigationViewModel, this)
|
||||||
|
|
||||||
|
val repository = getSettingsRepository(carContext)
|
||||||
|
|
||||||
|
var distanceMode = 0
|
||||||
|
|
||||||
init {
|
init {
|
||||||
observerManager.attachAllObservers(this)
|
observerManager.attachAllObservers(this)
|
||||||
lifecycleScope.launch {
|
lifecycleScope.launch {
|
||||||
getSettingsViewModel(carContext).routingEngine.first()
|
getSettingsViewModel(carContext).routingEngine.first()
|
||||||
getSettingsViewModel(carContext).recentPlaces.first()
|
getSettingsViewModel(carContext).recentPlaces.first()
|
||||||
|
distanceMode = repository.distanceModeFlow.first()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// NavigationObserverCallback implementations
|
/**
|
||||||
|
* Handles the received route string.
|
||||||
|
* Starts navigation and invalidates the screen.
|
||||||
|
*/
|
||||||
override fun onRouteReceived(route: String) {
|
override fun onRouteReceived(route: String) {
|
||||||
if (route.isNotEmpty()) {
|
if (route.isNotEmpty()) {
|
||||||
val repository = getSettingsRepository(carContext)
|
|
||||||
val routingEngine = runBlocking { repository.routingEngineFlow.first() }
|
val routingEngine = runBlocking { repository.routingEngineFlow.first() }
|
||||||
routeModel.navState = routeModel.navState.copy(routingEngine = routingEngine)
|
routeModel.navState = routeModel.navState.copy(routingEngine = routingEngine)
|
||||||
navigationType = NavigationType.NAVIGATION
|
navigationType = NavigationType.NAVIGATION
|
||||||
@@ -94,26 +113,45 @@ class NavigationScreen(
|
|||||||
getSettingsViewModel(carContext).onLastRouteChanged(route)
|
getSettingsViewModel(carContext).onLastRouteChanged(route)
|
||||||
}
|
}
|
||||||
surfaceRenderer.setRouteData()
|
surfaceRenderer.setRouteData()
|
||||||
|
listener.startNavigation()
|
||||||
invalidate()
|
invalidate()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if navigation is currently active.
|
||||||
|
*/
|
||||||
override fun isNavigating(): Boolean = routeModel.isNavigating()
|
override fun isNavigating(): Boolean = routeModel.isNavigating()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles the received recent place.
|
||||||
|
* Updates the navigation type to RECENT and invalidates the screen.
|
||||||
|
*/
|
||||||
override fun onRecentPlaceReceived(place: Place) {
|
override fun onRecentPlaceReceived(place: Place) {
|
||||||
recentPlace = place
|
recentPlace = place
|
||||||
navigationType = NavigationType.RECENT
|
navigationType = NavigationType.RECENT
|
||||||
invalidate()
|
invalidate()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles received traffic data and updates the surface renderer.
|
||||||
|
*/
|
||||||
override fun onTrafficReceived(traffic: Map<String, String>) {
|
override fun onTrafficReceived(traffic: Map<String, String>) {
|
||||||
surfaceRenderer.setTrafficData(traffic)
|
surfaceRenderer.setTrafficData(traffic)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles the received place search result.
|
||||||
|
* Navigates to the specified place.
|
||||||
|
*/
|
||||||
override fun onPlaceSearchResultReceived(place: Place) {
|
override fun onPlaceSearchResultReceived(place: Place) {
|
||||||
navigateToPlace(place)
|
navigateToPlace(place)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles received speed camera data.
|
||||||
|
* Updates the surface renderer with the camera locations.
|
||||||
|
*/
|
||||||
override fun onSpeedCamerasReceived(cameras: List<Elements>) {
|
override fun onSpeedCamerasReceived(cameras: List<Elements>) {
|
||||||
speedCameras = cameras
|
speedCameras = cameras
|
||||||
val coordinates = mutableListOf<List<Double>>()
|
val coordinates = mutableListOf<List<Double>>()
|
||||||
@@ -124,15 +162,27 @@ class NavigationScreen(
|
|||||||
surfaceRenderer.speedCamerasData.value = speedData
|
surfaceRenderer.speedCamerasData.value = speedData
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles received maximum speed data and updates the surface renderer.
|
||||||
|
*/
|
||||||
override fun onMaxSpeedReceived(speed: Int) {
|
override fun onMaxSpeedReceived(speed: Int) {
|
||||||
surfaceRenderer.maxSpeed.value = speed
|
surfaceRenderer.maxSpeed.value = speed
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invalidates the screen.
|
||||||
|
*/
|
||||||
override fun invalidateScreen() {
|
override fun invalidateScreen() {
|
||||||
invalidate()
|
invalidate()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the appropriate template based on the current navigation state.
|
||||||
|
*/
|
||||||
override fun onGetTemplate(): Template {
|
override fun onGetTemplate(): Template {
|
||||||
|
repository.distanceModeFlow.asLiveData().observe(this, Observer {
|
||||||
|
distanceMode = it
|
||||||
|
})
|
||||||
val actionStripBuilder = createActionStripBuilder()
|
val actionStripBuilder = createActionStripBuilder()
|
||||||
return when (navigationType) {
|
return when (navigationType) {
|
||||||
NavigationType.NAVIGATION -> navigationTemplate(actionStripBuilder)
|
NavigationType.NAVIGATION -> navigationTemplate(actionStripBuilder)
|
||||||
@@ -143,21 +193,28 @@ class NavigationScreen(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates and returns a NavigationTemplate for the active navigation state.
|
||||||
|
*/
|
||||||
private fun navigationTemplate(actionStripBuilder: ActionStrip.Builder): NavigationTemplate {
|
private fun navigationTemplate(actionStripBuilder: ActionStrip.Builder): NavigationTemplate {
|
||||||
actionStripBuilder.addAction(
|
actionStripBuilder.addAction(
|
||||||
stopAction()
|
stopAction()
|
||||||
)
|
)
|
||||||
|
updateTrip()
|
||||||
return NavigationTemplate.Builder()
|
return NavigationTemplate.Builder()
|
||||||
.setNavigationInfo(
|
.setNavigationInfo(
|
||||||
getRoutingInfo()
|
getRoutingInfo()
|
||||||
)
|
)
|
||||||
.setDestinationTravelEstimate(routeModel.travelEstimate(carContext))
|
.setDestinationTravelEstimate(routeModel.travelEstimate(carContext, distanceMode))
|
||||||
.setActionStrip(actionStripBuilder.build())
|
.setActionStrip(actionStripBuilder.build())
|
||||||
.setMapActionStrip(mapActionStripBuilder().build())
|
.setMapActionStrip(mapActionStripBuilder().build())
|
||||||
.setBackgroundColor(backGroundColor)
|
.setBackgroundColor(backGroundColor)
|
||||||
.build()
|
.build()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates and returns a template for the default view state.
|
||||||
|
*/
|
||||||
private fun navigationViewTemplate(actionStripBuilder: ActionStrip.Builder): Template {
|
private fun navigationViewTemplate(actionStripBuilder: ActionStrip.Builder): Template {
|
||||||
return NavigationTemplate.Builder()
|
return NavigationTemplate.Builder()
|
||||||
.setBackgroundColor(CarColor.SECONDARY)
|
.setBackgroundColor(CarColor.SECONDARY)
|
||||||
@@ -167,6 +224,9 @@ class NavigationScreen(
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates and returns a template for the arrival or end state of navigation.
|
||||||
|
*/
|
||||||
private fun navigationEndTemplate(actionStripBuilder: ActionStrip.Builder): Template {
|
private fun navigationEndTemplate(actionStripBuilder: ActionStrip.Builder): Template {
|
||||||
if (routeModel.navState.arrived) {
|
if (routeModel.navState.arrived) {
|
||||||
val timer = object : CountDownTimer(8000, 1000) {
|
val timer = object : CountDownTimer(8000, 1000) {
|
||||||
@@ -189,6 +249,9 @@ class NavigationScreen(
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates and returns a NavigationTemplate specifically for when the destination is reached.
|
||||||
|
*/
|
||||||
fun navigationArrivedTemplate(actionStripBuilder: ActionStrip.Builder): NavigationTemplate {
|
fun navigationArrivedTemplate(actionStripBuilder: ActionStrip.Builder): NavigationTemplate {
|
||||||
var street = ""
|
var street = ""
|
||||||
if (routeModel.navState.destination.street != null) {
|
if (routeModel.navState.destination.street != null) {
|
||||||
@@ -217,6 +280,9 @@ class NavigationScreen(
|
|||||||
.build()
|
.build()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates and returns a template showing recent places or destinations.
|
||||||
|
*/
|
||||||
fun navigationRecentPlaceTemplate(): Template {
|
fun navigationRecentPlaceTemplate(): Template {
|
||||||
val messageTemplate = MessageTemplate.Builder(
|
val messageTemplate = MessageTemplate.Builder(
|
||||||
recentPlace.name + "\n"
|
recentPlace.name + "\n"
|
||||||
@@ -242,6 +308,9 @@ class NavigationScreen(
|
|||||||
return builder.build()
|
return builder.build()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates and returns a template for when the route is being recalculated.
|
||||||
|
*/
|
||||||
fun navigationRerouteTemplate(actionStripBuilder: ActionStrip.Builder): Template {
|
fun navigationRerouteTemplate(actionStripBuilder: ActionStrip.Builder): Template {
|
||||||
return NavigationTemplate.Builder()
|
return NavigationTemplate.Builder()
|
||||||
.setNavigationInfo(RoutingInfo.Builder().setLoading(true).build())
|
.setNavigationInfo(RoutingInfo.Builder().setLoading(true).build())
|
||||||
@@ -250,19 +319,15 @@ class NavigationScreen(
|
|||||||
.build()
|
.build()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Builds and returns RoutingInfo based on the current step and distance.
|
||||||
|
*/
|
||||||
fun getRoutingInfo(): RoutingInfo {
|
fun getRoutingInfo(): RoutingInfo {
|
||||||
var currentDistance = routeModel.routeCalculator.leftStepDistance()
|
val distance = formattedDistance(distanceMode, routeModel.routeCalculator.leftStepDistance())
|
||||||
val displayUnit = if (currentDistance > 1000.0) {
|
|
||||||
currentDistance /= 1000.0
|
|
||||||
Distance.UNIT_KILOMETERS
|
|
||||||
} else {
|
|
||||||
Distance.UNIT_METERS
|
|
||||||
}
|
|
||||||
|
|
||||||
val routingInfo = RoutingInfo.Builder()
|
val routingInfo = RoutingInfo.Builder()
|
||||||
.setCurrentStep(
|
.setCurrentStep(
|
||||||
routeModel.currentStep(carContext = carContext),
|
routeModel.currentStep(carContext = carContext),
|
||||||
Distance.create(currentDistance, displayUnit)
|
Distance.create(distance.first, distance.second)
|
||||||
)
|
)
|
||||||
if (routeModel.navState.nextStep) {
|
if (routeModel.navState.nextStep) {
|
||||||
val nextStep = routeModel.nextStep(carContext = carContext)
|
val nextStep = routeModel.nextStep(carContext = carContext)
|
||||||
@@ -271,6 +336,9 @@ class NavigationScreen(
|
|||||||
return routingInfo.build()
|
return routingInfo.build()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates an ActionStrip builder with common search and settings actions.
|
||||||
|
*/
|
||||||
private fun createActionStripBuilder(): ActionStrip.Builder {
|
private fun createActionStripBuilder(): ActionStrip.Builder {
|
||||||
val actionStripBuilder: ActionStrip.Builder = ActionStrip.Builder()
|
val actionStripBuilder: ActionStrip.Builder = ActionStrip.Builder()
|
||||||
actionStripBuilder.addAction(
|
actionStripBuilder.addAction(
|
||||||
@@ -282,6 +350,9 @@ class NavigationScreen(
|
|||||||
return actionStripBuilder
|
return actionStripBuilder
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates an ActionStrip builder for map-related actions like zoom and pan.
|
||||||
|
*/
|
||||||
private fun mapActionStripBuilder(): ActionStrip.Builder {
|
private fun mapActionStripBuilder(): ActionStrip.Builder {
|
||||||
val actionStripBuilder = ActionStrip.Builder()
|
val actionStripBuilder = ActionStrip.Builder()
|
||||||
.addAction(zoomPlus())
|
.addAction(zoomPlus())
|
||||||
@@ -295,6 +366,9 @@ class NavigationScreen(
|
|||||||
return actionStripBuilder
|
return actionStripBuilder
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a stop navigation action.
|
||||||
|
*/
|
||||||
private fun stopAction(): Action {
|
private fun stopAction(): Action {
|
||||||
return Action.Builder()
|
return Action.Builder()
|
||||||
.setTitle(carContext.getString(R.string.stop_action_title))
|
.setTitle(carContext.getString(R.string.stop_action_title))
|
||||||
@@ -313,6 +387,9 @@ class NavigationScreen(
|
|||||||
.build()
|
.build()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates an action to start navigation to a specific place.
|
||||||
|
*/
|
||||||
private fun navigateAction(): Action {
|
private fun navigateAction(): Action {
|
||||||
navigationType = NavigationType.NAVIGATION
|
navigationType = NavigationType.NAVIGATION
|
||||||
return Action.Builder()
|
return Action.Builder()
|
||||||
@@ -338,6 +415,9 @@ class NavigationScreen(
|
|||||||
.build()
|
.build()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates an action to close the current view or template.
|
||||||
|
*/
|
||||||
private fun closeAction(): Action {
|
private fun closeAction(): Action {
|
||||||
return Action.Builder()
|
return Action.Builder()
|
||||||
.setIcon(
|
.setIcon(
|
||||||
@@ -357,6 +437,9 @@ class NavigationScreen(
|
|||||||
.build()
|
.build()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates an action to start the search screen.
|
||||||
|
*/
|
||||||
private fun searchAction(): Action {
|
private fun searchAction(): Action {
|
||||||
return Action.Builder()
|
return Action.Builder()
|
||||||
.setIcon(routeModel.createCarIcon(carContext, R.drawable.search_48px))
|
.setIcon(routeModel.createCarIcon(carContext, R.drawable.search_48px))
|
||||||
@@ -366,6 +449,9 @@ class NavigationScreen(
|
|||||||
.build()
|
.build()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates an action to start the settings screen.
|
||||||
|
*/
|
||||||
private fun settingsAction(): Action {
|
private fun settingsAction(): Action {
|
||||||
return Action.Builder()
|
return Action.Builder()
|
||||||
.setIcon(routeModel.createCarIcon(carContext, R.drawable.settings_48px))
|
.setIcon(routeModel.createCarIcon(carContext, R.drawable.settings_48px))
|
||||||
@@ -375,6 +461,9 @@ class NavigationScreen(
|
|||||||
.build()
|
.build()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates an action to zoom in on the map.
|
||||||
|
*/
|
||||||
private fun zoomPlus(): Action {
|
private fun zoomPlus(): Action {
|
||||||
return Action.Builder()
|
return Action.Builder()
|
||||||
.setIcon(
|
.setIcon(
|
||||||
@@ -392,6 +481,9 @@ class NavigationScreen(
|
|||||||
.build()
|
.build()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates an action to zoom out on the map.
|
||||||
|
*/
|
||||||
private fun zoomMinus(): Action {
|
private fun zoomMinus(): Action {
|
||||||
return Action.Builder()
|
return Action.Builder()
|
||||||
.setIcon(
|
.setIcon(
|
||||||
@@ -409,6 +501,9 @@ class NavigationScreen(
|
|||||||
.build()
|
.build()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates an action to enable map panning.
|
||||||
|
*/
|
||||||
private fun panAction(): Action {
|
private fun panAction(): Action {
|
||||||
return Action.Builder()
|
return Action.Builder()
|
||||||
.setIcon(
|
.setIcon(
|
||||||
@@ -426,6 +521,9 @@ class NavigationScreen(
|
|||||||
.build()
|
.build()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pushes the search screen and handles the search result.
|
||||||
|
*/
|
||||||
private fun startSearchScreen() {
|
private fun startSearchScreen() {
|
||||||
screenManager
|
screenManager
|
||||||
.pushForResult(
|
.pushForResult(
|
||||||
@@ -450,6 +548,9 @@ class NavigationScreen(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads a route to the specified place and sets it as the destination.
|
||||||
|
*/
|
||||||
fun navigateToPlace(place: Place) {
|
fun navigateToPlace(place: Place) {
|
||||||
navigationType = NavigationType.VIEW
|
navigationType = NavigationType.VIEW
|
||||||
val location = location(place.longitude, place.latitude)
|
val location = location(place.longitude, place.latitude)
|
||||||
@@ -465,6 +566,9 @@ class NavigationScreen(
|
|||||||
invalidate()
|
invalidate()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stops navigation, resets state, and notifies listeners.
|
||||||
|
*/
|
||||||
fun stopNavigation() {
|
fun stopNavigation() {
|
||||||
navigationType = NavigationType.VIEW
|
navigationType = NavigationType.VIEW
|
||||||
listener.stopNavigation()
|
listener.stopNavigation()
|
||||||
@@ -473,6 +577,9 @@ class NavigationScreen(
|
|||||||
invalidate()
|
invalidate()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initiates recalculation for a new route to the destination.
|
||||||
|
*/
|
||||||
fun calculateNewRoute(destination: Place) {
|
fun calculateNewRoute(destination: Place) {
|
||||||
stopNavigation()
|
stopNavigation()
|
||||||
navigationType = NavigationType.REROUTE
|
navigationType = NavigationType.REROUTE
|
||||||
@@ -489,6 +596,9 @@ class NavigationScreen(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Re-requests a route for the specified destination.
|
||||||
|
*/
|
||||||
fun reRoute(destination: Place) {
|
fun reRoute(destination: Place) {
|
||||||
val dest = location(destination.longitude, destination.latitude)
|
val dest = location(destination.longitude, destination.latitude)
|
||||||
navigationViewModel.loadRoute(
|
navigationViewModel.loadRoute(
|
||||||
@@ -499,6 +609,9 @@ class NavigationScreen(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates navigation state with the current location, checks for arrival, and traffic updates.
|
||||||
|
*/
|
||||||
fun updateTrip(location: Location) {
|
fun updateTrip(location: Location) {
|
||||||
val current = LocalDateTime.now(ZoneOffset.UTC)
|
val current = LocalDateTime.now(ZoneOffset.UTC)
|
||||||
val duration = Duration.between(current, lastTrafficDate)
|
val duration = Duration.between(current, lastTrafficDate)
|
||||||
@@ -526,6 +639,24 @@ class NavigationScreen(
|
|||||||
invalidate()
|
invalidate()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the trip information and notifies the listener with a new Trip object.
|
||||||
|
* This includes destination name, address, travel estimate, and loading status.
|
||||||
|
*/
|
||||||
|
private fun updateTrip() {
|
||||||
|
val tripBuilder = Trip.Builder()
|
||||||
|
val destination = Destination.Builder()
|
||||||
|
.setName(routeModel.navState.destination.name ?: "")
|
||||||
|
.setAddress(routeModel.navState.destination.street ?: "")
|
||||||
|
.build()
|
||||||
|
tripBuilder.addDestination(destination, routeModel.travelEstimate(carContext, distanceMode))
|
||||||
|
tripBuilder.setLoading(false)
|
||||||
|
listener.updateTrip(tripBuilder.build())
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Periodically requests speed camera information near the current location.
|
||||||
|
*/
|
||||||
private fun updateSpeedCamera(location: Location) {
|
private fun updateSpeedCamera(location: Location) {
|
||||||
if (lastCameraSearch++ % 100 == 0) {
|
if (lastCameraSearch++ % 100 == 0) {
|
||||||
navigationViewModel.getSpeedCameras(location, 5.0)
|
navigationViewModel.getSpeedCameras(location, 5.0)
|
||||||
@@ -535,6 +666,9 @@ class NavigationScreen(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates distances to nearby speed cameras and checks for proximity alerts.
|
||||||
|
*/
|
||||||
private fun updateDistance(
|
private fun updateDistance(
|
||||||
location: Location,
|
location: Location,
|
||||||
) {
|
) {
|
||||||
@@ -565,6 +699,9 @@ class NavigationScreen(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks for a specific permission and updates the view model upon grant.
|
||||||
|
*/
|
||||||
fun checkPermission(permission: String) {
|
fun checkPermission(permission: String) {
|
||||||
if (carContext.checkSelfPermission(permission) != PackageManager.PERMISSION_GRANTED) {
|
if (carContext.checkSelfPermission(permission) != PackageManager.PERMISSION_GRANTED) {
|
||||||
val permissions: MutableList<String?> = ArrayList()
|
val permissions: MutableList<String?> = ArrayList()
|
||||||
@@ -588,6 +725,9 @@ class NavigationScreen(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Defines the possible states for the navigation UI.
|
||||||
|
*/
|
||||||
enum class NavigationType {
|
enum class NavigationType {
|
||||||
VIEW, NAVIGATION, REROUTE, RECENT, ARRIVAL
|
VIEW, NAVIGATION, REROUTE, RECENT, ARRIVAL
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import androidx.car.app.model.Row
|
|||||||
import androidx.car.app.model.Template
|
import androidx.car.app.model.Template
|
||||||
import androidx.core.graphics.drawable.IconCompat
|
import androidx.core.graphics.drawable.IconCompat
|
||||||
import androidx.lifecycle.Observer
|
import androidx.lifecycle.Observer
|
||||||
|
import androidx.lifecycle.asLiveData
|
||||||
import com.kouros.data.R
|
import com.kouros.data.R
|
||||||
import com.kouros.navigation.car.SurfaceRenderer
|
import com.kouros.navigation.car.SurfaceRenderer
|
||||||
import com.kouros.navigation.car.navigation.RouteCarModel
|
import com.kouros.navigation.car.navigation.RouteCarModel
|
||||||
@@ -25,6 +26,7 @@ import com.kouros.navigation.data.Constants.FAVORITES
|
|||||||
import com.kouros.navigation.data.Constants.RECENT
|
import com.kouros.navigation.data.Constants.RECENT
|
||||||
import com.kouros.navigation.data.Place
|
import com.kouros.navigation.data.Place
|
||||||
import com.kouros.navigation.model.NavigationViewModel
|
import com.kouros.navigation.model.NavigationViewModel
|
||||||
|
import com.kouros.navigation.utils.getSettingsRepository
|
||||||
|
|
||||||
|
|
||||||
class PlaceListScreen(
|
class PlaceListScreen(
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
package com.kouros.navigation.car.screen.observers
|
||||||
|
|
||||||
|
import androidx.lifecycle.Observer
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Observer for max speed updates.
|
||||||
|
*/
|
||||||
|
class MaxSpeedObserver(
|
||||||
|
private val callback: NavigationObserverCallback
|
||||||
|
) : Observer<Int> {
|
||||||
|
|
||||||
|
override fun onChanged(value: Int) {
|
||||||
|
callback.onMaxSpeedReceived(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
package com.kouros.navigation.car.screen.observers
|
||||||
|
|
||||||
|
import com.kouros.navigation.data.Place
|
||||||
|
import com.kouros.navigation.data.nominatim.SearchResult
|
||||||
|
import com.kouros.navigation.data.overpass.Elements
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Callback interface for observer events that need to be handled by NavigationScreen.
|
||||||
|
*/
|
||||||
|
interface NavigationObserverCallback {
|
||||||
|
/** Called when a route is received and navigation should start */
|
||||||
|
fun onRouteReceived(route: String)
|
||||||
|
|
||||||
|
/** Called when a recent place is selected but navigation hasn't started */
|
||||||
|
fun onRecentPlaceReceived(place: Place)
|
||||||
|
|
||||||
|
/** Check if currently navigating */
|
||||||
|
fun isNavigating(): Boolean
|
||||||
|
|
||||||
|
/** Called when traffic data is updated */
|
||||||
|
fun onTrafficReceived(traffic: Map<String, String>)
|
||||||
|
|
||||||
|
/** Called when a place search result is received */
|
||||||
|
fun onPlaceSearchResultReceived(place: Place)
|
||||||
|
|
||||||
|
/** Called when speed cameras are updated */
|
||||||
|
fun onSpeedCamerasReceived(cameras: List<Elements>)
|
||||||
|
|
||||||
|
/** Called when max speed is updated */
|
||||||
|
fun onMaxSpeedReceived(speed: Int)
|
||||||
|
|
||||||
|
/** Called to request UI invalidation/refresh */
|
||||||
|
fun invalidateScreen()
|
||||||
|
}
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
package com.kouros.navigation.car.screen.observers
|
||||||
|
|
||||||
|
import com.kouros.navigation.model.NavigationViewModel
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Manager class that handles all NavigationScreen observers.
|
||||||
|
* Centralizes observer creation, registration, and lifecycle management.
|
||||||
|
*/
|
||||||
|
class NavigationObserverManager(
|
||||||
|
private val viewModel: NavigationViewModel,
|
||||||
|
callback: NavigationObserverCallback
|
||||||
|
) {
|
||||||
|
|
||||||
|
val routeObserver = RouteObserver(callback)
|
||||||
|
val recentPlaceObserver = RecentPlaceObserver(callback)
|
||||||
|
val trafficObserver = TrafficObserver(callback)
|
||||||
|
val placeSearchObserver = PlaceSearchObserver(callback)
|
||||||
|
val speedCameraObserver = SpeedCameraObserver(callback)
|
||||||
|
val maxSpeedObserver = MaxSpeedObserver(callback)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Attaches all observers to the ViewModel.
|
||||||
|
* Call this from NavigationScreen's init block or lifecycle method.
|
||||||
|
*/
|
||||||
|
fun attachAllObservers(screen: androidx.car.app.Screen) {
|
||||||
|
viewModel.route.observe(screen, routeObserver)
|
||||||
|
viewModel.traffic.observe(screen, trafficObserver)
|
||||||
|
viewModel.recentPlace.observe(screen, recentPlaceObserver)
|
||||||
|
viewModel.placeLocation.observe(screen, placeSearchObserver)
|
||||||
|
viewModel.speedCameras.observe(screen, speedCameraObserver)
|
||||||
|
viewModel.maxSpeed.observe(screen, maxSpeedObserver)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Detaches all observers from the ViewModel.
|
||||||
|
* Call this when the screen is being destroyed.
|
||||||
|
*/
|
||||||
|
fun detachAllObservers() {
|
||||||
|
// LiveData observers are automatically removed when the lifecycle is destroyed,
|
||||||
|
// but we can manually remove them if needed for testing or cleanup
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
package com.kouros.navigation.car.screen.observers
|
||||||
|
|
||||||
|
import androidx.lifecycle.Observer
|
||||||
|
import com.kouros.navigation.data.Constants
|
||||||
|
import com.kouros.navigation.data.Place
|
||||||
|
import com.kouros.navigation.data.nominatim.SearchResult
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Observer for place search results. Converts SearchResult to Place and navigates to it.
|
||||||
|
*/
|
||||||
|
class PlaceSearchObserver(
|
||||||
|
private val callback: NavigationObserverCallback
|
||||||
|
) : Observer<SearchResult> {
|
||||||
|
|
||||||
|
override fun onChanged(value: SearchResult) {
|
||||||
|
val place = Place(
|
||||||
|
name = value.displayName,
|
||||||
|
street = value.address.road,
|
||||||
|
city = value.address.city,
|
||||||
|
latitude = value.lat.toDouble(),
|
||||||
|
longitude = value.lon.toDouble(),
|
||||||
|
category = Constants.CONTACTS,
|
||||||
|
postalCode = value.address.postcode
|
||||||
|
)
|
||||||
|
callback.onPlaceSearchResultReceived(place)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
package com.kouros.navigation.car.screen.observers
|
||||||
|
|
||||||
|
import androidx.lifecycle.Observer
|
||||||
|
import com.kouros.navigation.data.Place
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Observer for recent place updates. Updates the recent place when navigation is not active.
|
||||||
|
*/
|
||||||
|
class RecentPlaceObserver(
|
||||||
|
private val callback: NavigationObserverCallback
|
||||||
|
) : Observer<Place> {
|
||||||
|
|
||||||
|
override fun onChanged(value: Place) {
|
||||||
|
if (!callback.isNavigating()) {
|
||||||
|
callback.onRecentPlaceReceived(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
package com.kouros.navigation.car.screen.observers
|
||||||
|
|
||||||
|
import androidx.lifecycle.Observer
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Observer for route updates. Triggers navigation start when a non-empty route is received.
|
||||||
|
*/
|
||||||
|
class RouteObserver(
|
||||||
|
private val callback: NavigationObserverCallback
|
||||||
|
) : Observer<String> {
|
||||||
|
|
||||||
|
override fun onChanged(value: String) {
|
||||||
|
if (value.isNotEmpty()) {
|
||||||
|
callback.onRouteReceived(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
package com.kouros.navigation.car.screen.observers
|
||||||
|
|
||||||
|
import androidx.lifecycle.Observer
|
||||||
|
import com.kouros.navigation.data.overpass.Elements
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Observer for speed camera updates.
|
||||||
|
*/
|
||||||
|
class SpeedCameraObserver(
|
||||||
|
private val callback: NavigationObserverCallback
|
||||||
|
) : Observer<List<Elements>> {
|
||||||
|
|
||||||
|
override fun onChanged(value: List<Elements>) {
|
||||||
|
callback.onSpeedCamerasReceived(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
package com.kouros.navigation.car.screen.observers
|
||||||
|
|
||||||
|
import androidx.lifecycle.Observer
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Observer for traffic data updates.
|
||||||
|
*/
|
||||||
|
class TrafficObserver(
|
||||||
|
private val callback: NavigationObserverCallback
|
||||||
|
) : Observer<Map<String, String>> {
|
||||||
|
|
||||||
|
override fun onChanged(value: Map<String, String>) {
|
||||||
|
callback.onTrafficReceived(value)
|
||||||
|
callback.invalidateScreen()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,153 @@
|
|||||||
|
package com.kouros.navigation.car.screen.observers
|
||||||
|
|
||||||
|
import com.kouros.navigation.data.Constants
|
||||||
|
import com.kouros.navigation.data.Place
|
||||||
|
import com.kouros.navigation.data.nominatim.SearchResult
|
||||||
|
import com.kouros.navigation.data.nominatim.Address
|
||||||
|
import com.kouros.navigation.data.overpass.Elements
|
||||||
|
import com.kouros.navigation.data.overpass.Tags
|
||||||
|
import org.junit.Before
|
||||||
|
import org.junit.Test
|
||||||
|
import org.mockito.kotlin.*
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unit tests for NavigationScreen observer classes using Mockito.
|
||||||
|
*/
|
||||||
|
class ObserversTest {
|
||||||
|
|
||||||
|
private lateinit var mockCallback: NavigationObserverCallback
|
||||||
|
|
||||||
|
@Before
|
||||||
|
fun setup() {
|
||||||
|
mockCallback = mock()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `RouteObserver triggers callback when route is not empty`() {
|
||||||
|
val observer = RouteObserver(mockCallback)
|
||||||
|
val testRoute = "test_route_data"
|
||||||
|
|
||||||
|
observer.onChanged(testRoute)
|
||||||
|
|
||||||
|
verify(mockCallback).onRouteReceived(testRoute)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `RouteObserver does not trigger callback when route is empty`() {
|
||||||
|
val observer = RouteObserver(mockCallback)
|
||||||
|
|
||||||
|
observer.onChanged("")
|
||||||
|
|
||||||
|
verify(mockCallback, never()).onRouteReceived(any())
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `RecentPlaceObserver triggers callback when navigating is false`() {
|
||||||
|
val observer = RecentPlaceObserver(mockCallback)
|
||||||
|
val testPlace = createTestPlace()
|
||||||
|
whenever(mockCallback.isNavigating()).thenReturn(false)
|
||||||
|
|
||||||
|
observer.onChanged(testPlace)
|
||||||
|
|
||||||
|
verify(mockCallback).isNavigating()
|
||||||
|
verify(mockCallback).onRecentPlaceReceived(testPlace)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `RecentPlaceObserver does not trigger callback when navigating is true`() {
|
||||||
|
val observer = RecentPlaceObserver(mockCallback)
|
||||||
|
val testPlace = createTestPlace()
|
||||||
|
whenever(mockCallback.isNavigating()).thenReturn(true)
|
||||||
|
|
||||||
|
observer.onChanged(testPlace)
|
||||||
|
|
||||||
|
verify(mockCallback).isNavigating()
|
||||||
|
verify(mockCallback, never()).onRecentPlaceReceived(any())
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `TrafficObserver triggers callback with traffic data and invalidates screen`() {
|
||||||
|
val observer = TrafficObserver(mockCallback)
|
||||||
|
val trafficData = mapOf("road1" to "congestion", "road2" to "clear")
|
||||||
|
|
||||||
|
observer.onChanged(trafficData)
|
||||||
|
|
||||||
|
verify(mockCallback).onTrafficReceived(trafficData)
|
||||||
|
verify(mockCallback).invalidateScreen()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `PlaceSearchObserver converts SearchResult to Place and triggers callback`() {
|
||||||
|
val observer = PlaceSearchObserver(mockCallback)
|
||||||
|
val searchResult = createTestSearchResult()
|
||||||
|
|
||||||
|
observer.onChanged(searchResult)
|
||||||
|
|
||||||
|
argumentCaptor<Place>().apply {
|
||||||
|
verify(mockCallback).onPlaceSearchResultReceived(capture())
|
||||||
|
assert(firstValue.name == "Test Place")
|
||||||
|
assert(firstValue.street == "Test Street")
|
||||||
|
assert(firstValue.city == "Test City")
|
||||||
|
assert(firstValue.latitude == 52.0)
|
||||||
|
assert(firstValue.longitude == 10.0)
|
||||||
|
assert(firstValue.category == Constants.CONTACTS)
|
||||||
|
assert(firstValue.postalCode == "12345")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `SpeedCameraObserver triggers callback with camera list`() {
|
||||||
|
val observer = SpeedCameraObserver(mockCallback)
|
||||||
|
val cameras = listOf(
|
||||||
|
createTestElements(10.0, 52.0, "50"),
|
||||||
|
createTestElements(10.1, 52.1, "30")
|
||||||
|
)
|
||||||
|
|
||||||
|
observer.onChanged(cameras)
|
||||||
|
|
||||||
|
verify(mockCallback).onSpeedCamerasReceived(cameras)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `MaxSpeedObserver triggers callback with speed value`() {
|
||||||
|
val observer = MaxSpeedObserver(mockCallback)
|
||||||
|
val testSpeed = 50
|
||||||
|
|
||||||
|
observer.onChanged(testSpeed)
|
||||||
|
|
||||||
|
verify(mockCallback).onMaxSpeedReceived(testSpeed)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper methods
|
||||||
|
private fun createTestPlace(): Place {
|
||||||
|
return Place(
|
||||||
|
name = "Test Place",
|
||||||
|
street = "Test Street",
|
||||||
|
city = "Test City",
|
||||||
|
latitude = 52.0,
|
||||||
|
longitude = 10.0,
|
||||||
|
category = Constants.FAVORITES
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun createTestSearchResult(): SearchResult {
|
||||||
|
return SearchResult(
|
||||||
|
displayName = "Test Place",
|
||||||
|
lat = "52.0",
|
||||||
|
lon = "10.0",
|
||||||
|
address = Address(
|
||||||
|
road = "Test Street",
|
||||||
|
city = "Test City",
|
||||||
|
postcode = "12345"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun createTestElements(lon: Double, lat: Double, maxSpeed: String): Elements {
|
||||||
|
return Elements(
|
||||||
|
lon = lon,
|
||||||
|
lat = lat,
|
||||||
|
tags = Tags(maxspeed = maxSpeed, direction = null)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -69,6 +69,4 @@ dependencies {
|
|||||||
androidTestImplementation(libs.androidx.espresso.core)
|
androidTestImplementation(libs.androidx.espresso.core)
|
||||||
androidTestImplementation(libs.androidx.runner)
|
androidTestImplementation(libs.androidx.runner)
|
||||||
androidTestImplementation(libs.androidx.rules)
|
androidTestImplementation(libs.androidx.rules)
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
3
common/data/proguard-rules.pro
vendored
3
common/data/proguard-rules.pro
vendored
@@ -18,4 +18,5 @@
|
|||||||
|
|
||||||
# If you keep the line number information, uncomment this to
|
# If you keep the line number information, uncomment this to
|
||||||
# hide the original source file name.
|
# hide the original source file name.
|
||||||
#-renamesourcefileattribute SourceFile
|
#-renamesourcefileattribute SourceFile
|
||||||
|
|
||||||
|
|||||||
@@ -20,4 +20,4 @@ data class NavigationState (
|
|||||||
val carConnection: Int = 0,
|
val carConnection: Int = 0,
|
||||||
val routingEngine: Int = 0,
|
val routingEngine: Int = 0,
|
||||||
val nextStep: Boolean = false,
|
val nextStep: Boolean = false,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -21,11 +21,11 @@ private const val DATASTORE_NAME = "navigation_settings"
|
|||||||
class DataStoreManager(private val context: Context) {
|
class DataStoreManager(private val context: Context) {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private val Context.dataStore: DataStore<Preferences> by preferencesDataStore(name = DATASTORE_NAME)
|
val Context.dataStore: DataStore<Preferences> by preferencesDataStore(name = DATASTORE_NAME)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Keys
|
// Keys
|
||||||
private object PreferencesKeys {
|
object PreferencesKeys {
|
||||||
|
|
||||||
val SHOW_3D = booleanPreferencesKey("Show3D")
|
val SHOW_3D = booleanPreferencesKey("Show3D")
|
||||||
|
|
||||||
@@ -45,7 +45,7 @@ class DataStoreManager(private val context: Context) {
|
|||||||
|
|
||||||
val RECENT_PLACES = stringPreferencesKey("RecentPlaces")
|
val RECENT_PLACES = stringPreferencesKey("RecentPlaces")
|
||||||
|
|
||||||
val FAVORITES = stringPreferencesKey("Favorites")
|
val DISTANCE_MODE = intPreferencesKey("DistanceMode")
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -79,7 +79,7 @@ class DataStoreManager(private val context: Context) {
|
|||||||
val routingEngineFlow: Flow<Int> =
|
val routingEngineFlow: Flow<Int> =
|
||||||
context.dataStore.data.map { preferences ->
|
context.dataStore.data.map { preferences ->
|
||||||
preferences[PreferencesKeys.ROUTING_ENGINE]
|
preferences[PreferencesKeys.ROUTING_ENGINE]
|
||||||
?: 0
|
?: 2
|
||||||
}
|
}
|
||||||
|
|
||||||
val lastRouteFlow: Flow<String> =
|
val lastRouteFlow: Flow<String> =
|
||||||
@@ -100,10 +100,11 @@ class DataStoreManager(private val context: Context) {
|
|||||||
?: ""
|
?: ""
|
||||||
}
|
}
|
||||||
|
|
||||||
val favoritesFlow: Flow<String> =
|
|
||||||
|
val distanceModeFlow: Flow<Int> =
|
||||||
context.dataStore.data.map { preferences ->
|
context.dataStore.data.map { preferences ->
|
||||||
preferences[PreferencesKeys.FAVORITES]
|
preferences[PreferencesKeys.DISTANCE_MODE]
|
||||||
?: ""
|
?: 0
|
||||||
}
|
}
|
||||||
|
|
||||||
// Save values
|
// Save values
|
||||||
@@ -161,9 +162,10 @@ class DataStoreManager(private val context: Context) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun setFavorites(apiKey: String) {
|
suspend fun setDistanceMode(mode: Int) {
|
||||||
context.dataStore.edit { prefs ->
|
context.dataStore.edit { prefs ->
|
||||||
prefs[PreferencesKeys.FAVORITES] = apiKey
|
prefs[PreferencesKeys.DISTANCE_MODE] = mode
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,11 @@ package com.kouros.navigation.data.route
|
|||||||
|
|
||||||
data class Summary(
|
data class Summary(
|
||||||
// sec
|
// sec
|
||||||
var duration : Double = 0.0,
|
var duration: Double = 0.0,
|
||||||
// km
|
// m
|
||||||
var distance : Double = 0.0,
|
var distance: Double = 0.0,
|
||||||
|
// sec
|
||||||
|
var trafficDelay: Double = 0.0,
|
||||||
|
// m
|
||||||
|
var trafficLength: Double = 0.0,
|
||||||
)
|
)
|
||||||
@@ -25,7 +25,9 @@ class TomTomRoute {
|
|||||||
var points = listOf<List<Double>>()
|
var points = listOf<List<Double>>()
|
||||||
val summary = Summary(
|
val summary = Summary(
|
||||||
route.summary.travelTimeInSeconds.toDouble(),
|
route.summary.travelTimeInSeconds.toDouble(),
|
||||||
route.summary.lengthInMeters.toDouble()
|
route.summary.lengthInMeters.toDouble(),
|
||||||
|
route.summary.trafficDelayInSeconds.toDouble(),
|
||||||
|
route.summary.trafficLengthInMeters.toDouble()
|
||||||
)
|
)
|
||||||
route.legs.forEach { leg ->
|
route.legs.forEach { leg ->
|
||||||
points = decodePolyline(leg.encodedPolyline, leg.encodedPolylinePrecision)
|
points = decodePolyline(leg.encodedPolyline, leg.encodedPolylinePrecision)
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package com.kouros.navigation.model
|
|||||||
//import com.kouros.navigation.data.Preferences.boxStore
|
//import com.kouros.navigation.data.Preferences.boxStore
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.location.Location
|
import android.location.Location
|
||||||
|
import android.util.Log
|
||||||
import androidx.compose.runtime.snapshots.SnapshotStateList
|
import androidx.compose.runtime.snapshots.SnapshotStateList
|
||||||
import androidx.compose.runtime.toMutableStateList
|
import androidx.compose.runtime.toMutableStateList
|
||||||
import androidx.compose.ui.platform.isDebugInspectorInfoEnabled
|
import androidx.compose.ui.platform.isDebugInspectorInfoEnabled
|
||||||
|
|||||||
@@ -69,7 +69,7 @@ class RouteCalculator(var routeModel: RouteModel) {
|
|||||||
distance
|
distance
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return (leftDistance / 10.0).roundToInt() * 10.0
|
return leftDistance.toDouble()
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Returns the left distance in m. */
|
/** Returns the left distance in m. */
|
||||||
|
|||||||
@@ -1,7 +1,10 @@
|
|||||||
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
|
||||||
@@ -63,6 +66,12 @@ class SettingsViewModel(private val repository: SettingsRepository) : ViewModel(
|
|||||||
""
|
""
|
||||||
)
|
)
|
||||||
|
|
||||||
|
val distanceMode = repository.distanceModeFlow.stateIn(
|
||||||
|
viewModelScope,
|
||||||
|
SharingStarted.WhileSubscribed(5_000),
|
||||||
|
0
|
||||||
|
)
|
||||||
|
|
||||||
fun onShow3DChanged(enabled: Boolean) {
|
fun onShow3DChanged(enabled: Boolean) {
|
||||||
viewModelScope.launch { repository.setShow3D(enabled) }
|
viewModelScope.launch { repository.setShow3D(enabled) }
|
||||||
}
|
}
|
||||||
@@ -94,4 +103,9 @@ class SettingsViewModel(private val repository: SettingsRepository) : ViewModel(
|
|||||||
fun onTomTomApiKeyChanged(key: String) {
|
fun onTomTomApiKeyChanged(key: String) {
|
||||||
viewModelScope.launch { repository.setTomTomApiKey(key) }
|
viewModelScope.launch { repository.setTomTomApiKey(key) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun onDistanceModeChanged(mode: Int) {
|
||||||
|
viewModelScope.launch { repository.setDistanceMode(mode) }
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,8 +33,8 @@ class SettingsRepository(
|
|||||||
val recentPlacesFlow: Flow<String> =
|
val recentPlacesFlow: Flow<String> =
|
||||||
dataStoreManager.recentPlacesFlow
|
dataStoreManager.recentPlacesFlow
|
||||||
|
|
||||||
val favoritesFlow: Flow<String> =
|
val distanceModeFlow: Flow<Int> =
|
||||||
dataStoreManager.favoritesFlow
|
dataStoreManager.distanceModeFlow
|
||||||
|
|
||||||
|
|
||||||
suspend fun setShow3D(enabled: Boolean) {
|
suspend fun setShow3D(enabled: Boolean) {
|
||||||
@@ -73,7 +73,9 @@ class SettingsRepository(
|
|||||||
dataStoreManager.setRecentPlaces(places)
|
dataStoreManager.setRecentPlaces(places)
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun setFavorites(favorites: String) {
|
suspend fun setDistanceMode(mode: Int) {
|
||||||
dataStoreManager.setFavorites(favorites)
|
dataStoreManager.setDistanceMode(mode)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -3,6 +3,7 @@ package com.kouros.navigation.utils
|
|||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.location.Location
|
import android.location.Location
|
||||||
import android.location.LocationManager
|
import android.location.LocationManager
|
||||||
|
import androidx.car.app.model.Distance
|
||||||
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
|
||||||
@@ -16,6 +17,7 @@ import java.time.ZoneOffset
|
|||||||
import java.time.ZonedDateTime
|
import java.time.ZonedDateTime
|
||||||
import java.time.format.DateTimeFormatter
|
import java.time.format.DateTimeFormatter
|
||||||
import java.time.format.FormatStyle
|
import java.time.format.FormatStyle
|
||||||
|
import java.util.Locale
|
||||||
import kotlin.math.absoluteValue
|
import kotlin.math.absoluteValue
|
||||||
import kotlin.math.pow
|
import kotlin.math.pow
|
||||||
import kotlin.math.roundToInt
|
import kotlin.math.roundToInt
|
||||||
@@ -109,4 +111,34 @@ fun duration(preview: Boolean, bearing: Double, lastBearing: Double): Duration {
|
|||||||
1.seconds
|
1.seconds
|
||||||
}
|
}
|
||||||
return cameraDuration
|
return cameraDuration
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun isMetricSystem(): Boolean {
|
||||||
|
val country = Locale.getDefault().country
|
||||||
|
// Return true for metric, false for imperial
|
||||||
|
return !setOf("US", "UK", "LR", "MM").contains(country)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun formattedDistance(distanceMode : Int, distance: Double): Pair<Double, Int> {
|
||||||
|
var currentDistance = distance
|
||||||
|
var displayUnit: Int
|
||||||
|
if (distanceMode == 1 || distanceMode == 0 && isMetricSystem()) {
|
||||||
|
displayUnit = if (currentDistance > 1000.0) {
|
||||||
|
currentDistance /= 1000.0
|
||||||
|
Distance.UNIT_KILOMETERS
|
||||||
|
} else {
|
||||||
|
currentDistance = (currentDistance / 10.0).roundToInt() * 10.0
|
||||||
|
Distance.UNIT_METERS
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
currentDistance *= 0.621371
|
||||||
|
displayUnit = if (currentDistance > 1000.0) {
|
||||||
|
currentDistance /= 1000.0
|
||||||
|
Distance.UNIT_MILES
|
||||||
|
} else {
|
||||||
|
currentDistance = (currentDistance / 10.0).roundToInt() * 10.0
|
||||||
|
Distance.UNIT_FEET
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Pair(currentDistance, displayUnit)
|
||||||
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package com.kouros.navigation.utils
|
|||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import androidx.car.app.CarContext
|
import androidx.car.app.CarContext
|
||||||
|
import androidx.car.app.model.Distance
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
@@ -13,6 +14,9 @@ import com.kouros.navigation.model.SettingsViewModel
|
|||||||
import com.kouros.navigation.repository.SettingsRepository
|
import com.kouros.navigation.repository.SettingsRepository
|
||||||
import kotlinx.coroutines.flow.first
|
import kotlinx.coroutines.flow.first
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
|
import java.text.NumberFormat
|
||||||
|
import java.util.Locale
|
||||||
|
import kotlin.math.roundToInt
|
||||||
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 4.1 KiB After Width: | Height: | Size: 4.3 KiB |
BIN
common/data/src/main/res/drawable/right_x_straight_o.png
Normal file
BIN
common/data/src/main/res/drawable/right_x_straight_o.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 5.5 KiB |
@@ -36,8 +36,8 @@
|
|||||||
<string name="use_telephon_settings">Verwende Telefon Einstellungen</string>
|
<string name="use_telephon_settings">Verwende Telefon Einstellungen</string>
|
||||||
<string name="threed_building">3D Gebäude</string>
|
<string name="threed_building">3D Gebäude</string>
|
||||||
<string name="drive_now">Losfahren</string>
|
<string name="drive_now">Losfahren</string>
|
||||||
<string name="avoid_tolls_row_title" msgid="5194057244144831024">"Mautstraßen meiden"</string>
|
<string name="avoid_tolls_row_title" msgid="5194057244144831024">"Mautstraßen vermeiden"</string>
|
||||||
<string name="avoid_highways_row_title" msgid="4711913426200490304">"Autobahnen meiden"</string>
|
<string name="avoid_highways_row_title" msgid="4711913426200490304">"Autobahnen vermeiden"</string>
|
||||||
<string name="recent_destinations">Letzte Ziele</string>
|
<string name="recent_destinations">Letzte Ziele</string>
|
||||||
<string name="contacts">Kontakte</string>
|
<string name="contacts">Kontakte</string>
|
||||||
<string name="route_preview">Route Vorschau</string>
|
<string name="route_preview">Route Vorschau</string>
|
||||||
@@ -54,5 +54,8 @@
|
|||||||
<string name="use_car_settings">Verwende Auto Einstellungen</string>
|
<string name="use_car_settings">Verwende Auto Einstellungen</string>
|
||||||
<string name="exit_number">Ausfahrt nummer</string>
|
<string name="exit_number">Ausfahrt nummer</string>
|
||||||
<string name="navigation_icon_description">Navigations Icon</string>
|
<string name="navigation_icon_description">Navigations Icon</string>
|
||||||
|
<string name="distance_units">Entfernungseinheiten</string>
|
||||||
|
<string name="automaticaly">Automatisch</string>
|
||||||
|
<string name="kilometer">Kilometer</string>
|
||||||
|
<string name="miles">Meilen</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -18,7 +18,7 @@
|
|||||||
<string name="avoid_highways_row_title">Avoid highways</string>
|
<string name="avoid_highways_row_title">Avoid highways</string>
|
||||||
<string name="avoid_tolls_row_title">Avoid tolls rows</string>
|
<string name="avoid_tolls_row_title">Avoid tolls rows</string>
|
||||||
<string name="no_places">No places</string>
|
<string name="no_places">No places</string>
|
||||||
<string name="recent_destinations">Recent destination</string>
|
<string name="recent_destinations">Recent destinations</string>
|
||||||
<string name="contacts">Contacts</string>
|
<string name="contacts">Contacts</string>
|
||||||
<string name="favorites">Favorites</string>
|
<string name="favorites">Favorites</string>
|
||||||
<string name="recent_Item_deleted">Recent item deleted</string>
|
<string name="recent_Item_deleted">Recent item deleted</string>
|
||||||
@@ -40,4 +40,8 @@
|
|||||||
<string name="use_car_settings">Use car settings</string>
|
<string name="use_car_settings">Use car settings</string>
|
||||||
<string name="exit_number">Exit number</string>
|
<string name="exit_number">Exit number</string>
|
||||||
<string name="navigation_icon_description">Navigation icon</string>
|
<string name="navigation_icon_description">Navigation icon</string>
|
||||||
|
<string name="distance_units">Distance units</string>
|
||||||
|
<string name="automaticaly">Automaticaly</string>
|
||||||
|
<string name="kilometer">Kilometer</string>
|
||||||
|
<string name="miles">Miles</string>
|
||||||
</resources>
|
</resources>
|
||||||
@@ -35,33 +35,33 @@ class IconMapperTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `addLanes returns correct lane direction`() {
|
fun `addLanes returns correct lane direction`() {
|
||||||
val stepDataNormalLeft = StepData("", 0.0, Maneuver.TYPE_TURN_NORMAL_LEFT, 0, 0L, 0.0)
|
val stepDataNormalLeft = StepData("", "", 0.0, Maneuver.TYPE_TURN_NORMAL_LEFT, 0, 0L, 0.0)
|
||||||
assertEquals(LaneDirection.SHAPE_NORMAL_LEFT, iconMapper.addLanes("left_straight", stepDataNormalLeft))
|
assertEquals(LaneDirection.SHAPE_NORMAL_LEFT, iconMapper.addLanes("left_straight", stepDataNormalLeft))
|
||||||
assertEquals(LaneDirection.SHAPE_NORMAL_LEFT, iconMapper.addLanes("left", stepDataNormalLeft))
|
assertEquals(LaneDirection.SHAPE_NORMAL_LEFT, iconMapper.addLanes("left", stepDataNormalLeft))
|
||||||
assertEquals(LaneDirection.SHAPE_SLIGHT_LEFT, iconMapper.addLanes("left_slight", stepDataNormalLeft))
|
assertEquals(LaneDirection.SHAPE_SLIGHT_LEFT, iconMapper.addLanes("left_slight", stepDataNormalLeft))
|
||||||
assertEquals(LaneDirection.SHAPE_SLIGHT_LEFT, iconMapper.addLanes("slight_left", stepDataNormalLeft))
|
assertEquals(LaneDirection.SHAPE_SLIGHT_LEFT, iconMapper.addLanes("slight_left", stepDataNormalLeft))
|
||||||
|
|
||||||
val stepDataStraight = StepData("", 0.0, Maneuver.TYPE_STRAIGHT, 0, 0L, 0.0)
|
val stepDataStraight = StepData("", "", 0.0, Maneuver.TYPE_STRAIGHT, 0, 0L, 0.0)
|
||||||
assertEquals(LaneDirection.SHAPE_STRAIGHT, iconMapper.addLanes("left_straight", stepDataStraight))
|
assertEquals(LaneDirection.SHAPE_STRAIGHT, iconMapper.addLanes("left_straight", stepDataStraight))
|
||||||
assertEquals(LaneDirection.SHAPE_STRAIGHT, iconMapper.addLanes("straight", stepDataStraight))
|
assertEquals(LaneDirection.SHAPE_STRAIGHT, iconMapper.addLanes("straight", stepDataStraight))
|
||||||
assertEquals(LaneDirection.SHAPE_STRAIGHT, iconMapper.addLanes("right_straight", stepDataStraight))
|
assertEquals(LaneDirection.SHAPE_STRAIGHT, iconMapper.addLanes("right_straight", stepDataStraight))
|
||||||
|
|
||||||
val stepDataKeepLeft = StepData("", 0.0, Maneuver.TYPE_KEEP_LEFT, 0, 0L, 0.0)
|
val stepDataKeepLeft = StepData("", "", 0.0, Maneuver.TYPE_KEEP_LEFT, 0, 0L, 0.0)
|
||||||
assertEquals(LaneDirection.SHAPE_STRAIGHT, iconMapper.addLanes("straight", stepDataKeepLeft))
|
assertEquals(LaneDirection.SHAPE_STRAIGHT, iconMapper.addLanes("straight", stepDataKeepLeft))
|
||||||
assertEquals(LaneDirection.SHAPE_SLIGHT_LEFT, iconMapper.addLanes("left_slight", stepDataKeepLeft))
|
assertEquals(LaneDirection.SHAPE_SLIGHT_LEFT, iconMapper.addLanes("left_slight", stepDataKeepLeft))
|
||||||
|
|
||||||
val stepDataKeepRight = StepData("", 0.0, Maneuver.TYPE_KEEP_RIGHT, 0, 0L, 0.0)
|
val stepDataKeepRight = StepData("", "", 0.0, Maneuver.TYPE_KEEP_RIGHT, 0, 0L, 0.0)
|
||||||
assertEquals(LaneDirection.SHAPE_STRAIGHT, iconMapper.addLanes("straight", stepDataKeepRight))
|
assertEquals(LaneDirection.SHAPE_STRAIGHT, iconMapper.addLanes("straight", stepDataKeepRight))
|
||||||
|
|
||||||
val stepDataNormalRight = StepData("", 0.0, Maneuver.TYPE_TURN_NORMAL_RIGHT, 0, 0L, 0.0)
|
val stepDataNormalRight = StepData("", "", 0.0, Maneuver.TYPE_TURN_NORMAL_RIGHT, 0, 0L, 0.0)
|
||||||
assertEquals(LaneDirection.SHAPE_NORMAL_RIGHT, iconMapper.addLanes("right", stepDataNormalRight))
|
assertEquals(LaneDirection.SHAPE_NORMAL_RIGHT, iconMapper.addLanes("right", stepDataNormalRight))
|
||||||
assertEquals(LaneDirection.SHAPE_NORMAL_RIGHT, iconMapper.addLanes("right_straight", stepDataNormalRight))
|
assertEquals(LaneDirection.SHAPE_NORMAL_RIGHT, iconMapper.addLanes("right_straight", stepDataNormalRight))
|
||||||
|
|
||||||
val stepDataSlightRight = StepData("", 0.0, Maneuver.TYPE_TURN_SLIGHT_RIGHT, 0, 0L, 0.0)
|
val stepDataSlightRight = StepData("", "", 0.0, Maneuver.TYPE_TURN_SLIGHT_RIGHT, 0, 0L, 0.0)
|
||||||
assertEquals(LaneDirection.SHAPE_NORMAL_RIGHT, iconMapper.addLanes("right_slight", stepDataSlightRight))
|
assertEquals(LaneDirection.SHAPE_NORMAL_RIGHT, iconMapper.addLanes("right_slight", stepDataSlightRight))
|
||||||
assertEquals(LaneDirection.SHAPE_NORMAL_RIGHT, iconMapper.addLanes("slight_right", stepDataSlightRight))
|
assertEquals(LaneDirection.SHAPE_NORMAL_RIGHT, iconMapper.addLanes("slight_right", stepDataSlightRight))
|
||||||
|
|
||||||
val stepDataUnknown = StepData("", 0.0, Maneuver.TYPE_UNKNOWN, 0, 0L, 0.0)
|
val stepDataUnknown = StepData("", "", 0.0, Maneuver.TYPE_UNKNOWN, 0, 0L, 0.0)
|
||||||
assertEquals(LaneDirection.SHAPE_UNKNOWN, iconMapper.addLanes("left_straight", stepDataUnknown))
|
assertEquals(LaneDirection.SHAPE_UNKNOWN, iconMapper.addLanes("left_straight", stepDataUnknown))
|
||||||
assertEquals(LaneDirection.SHAPE_UNKNOWN, iconMapper.addLanes("left", stepDataUnknown))
|
assertEquals(LaneDirection.SHAPE_UNKNOWN, iconMapper.addLanes("left", stepDataUnknown))
|
||||||
assertEquals(LaneDirection.SHAPE_UNKNOWN, iconMapper.addLanes("straight", stepDataUnknown))
|
assertEquals(LaneDirection.SHAPE_UNKNOWN, iconMapper.addLanes("straight", stepDataUnknown))
|
||||||
@@ -74,24 +74,24 @@ class IconMapperTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `laneToResource returns correct resource string`() {
|
fun `laneToResource returns correct resource string`() {
|
||||||
val stepDataNormalLeft = StepData("", 0.0, Maneuver.TYPE_TURN_NORMAL_LEFT, 0, 0L, 0.0)
|
val stepDataNormalLeft = StepData("", "", 0.0, Maneuver.TYPE_TURN_NORMAL_LEFT, 0, 0L, 0.0)
|
||||||
assertEquals("left_o_straight_x", iconMapper.laneToResource(listOf("left", "straight"), stepDataNormalLeft))
|
assertEquals("left_o_straight_x", iconMapper.laneToResource(listOf("left", "straight"), stepDataNormalLeft))
|
||||||
assertEquals("left_o", iconMapper.laneToResource(listOf("left"), stepDataNormalLeft))
|
assertEquals("left_o", iconMapper.laneToResource(listOf("left"), stepDataNormalLeft))
|
||||||
assertEquals("slight_left_o", iconMapper.laneToResource(listOf("slight_left"), stepDataNormalLeft))
|
assertEquals("slight_left_o", iconMapper.laneToResource(listOf("slight_left"), stepDataNormalLeft))
|
||||||
|
|
||||||
val stepDataStraight = StepData("", 0.0, Maneuver.TYPE_STRAIGHT, 0, 0L, 0.0)
|
val stepDataStraight = StepData("", "", 0.0, Maneuver.TYPE_STRAIGHT, 0, 0L, 0.0)
|
||||||
assertEquals("left_x_straight_o", iconMapper.laneToResource(listOf("left", "straight"), stepDataStraight))
|
assertEquals("left_x_straight_o", iconMapper.laneToResource(listOf("left", "straight"), stepDataStraight))
|
||||||
assertEquals("straight_o", iconMapper.laneToResource(listOf("straight"), stepDataStraight))
|
assertEquals("straight_o", iconMapper.laneToResource(listOf("straight"), stepDataStraight))
|
||||||
assertEquals("right_x_straight_o", iconMapper.laneToResource(listOf("right_straight"), stepDataStraight))
|
assertEquals("right_x_straight_o", iconMapper.laneToResource(listOf("right_straight"), stepDataStraight))
|
||||||
|
|
||||||
val stepDataNormalRight = StepData("", 0.0, Maneuver.TYPE_TURN_NORMAL_RIGHT, 0, 0L, 0.0)
|
val stepDataNormalRight = StepData("", "", 0.0, Maneuver.TYPE_TURN_NORMAL_RIGHT, 0, 0L, 0.0)
|
||||||
assertEquals("right_x_straight_x", iconMapper.laneToResource(listOf("right_straight"), stepDataNormalRight))
|
assertEquals("right_x_straight_x", iconMapper.laneToResource(listOf("right_straight"), stepDataNormalRight))
|
||||||
assertEquals("right_o", iconMapper.laneToResource(listOf("right"), stepDataNormalRight))
|
assertEquals("right_o", iconMapper.laneToResource(listOf("right"), stepDataNormalRight))
|
||||||
|
|
||||||
val stepDataSlightRight = StepData("", 0.0, Maneuver.TYPE_TURN_SLIGHT_RIGHT, 0, 0L, 0.0)
|
val stepDataSlightRight = StepData("", "", 0.0, Maneuver.TYPE_TURN_SLIGHT_RIGHT, 0, 0L, 0.0)
|
||||||
assertEquals("right_o_straight_o", iconMapper.laneToResource(listOf("right_straight"), stepDataSlightRight))
|
assertEquals("right_o_straight_o", iconMapper.laneToResource(listOf("right_straight"), stepDataSlightRight))
|
||||||
|
|
||||||
val stepDataUnknown = StepData("", 0.0, Maneuver.TYPE_UNKNOWN, 0, 0L, 0.0)
|
val stepDataUnknown = StepData("", "", 0.0, Maneuver.TYPE_UNKNOWN, 0, 0L, 0.0)
|
||||||
assertEquals("left_x_straight_x", iconMapper.laneToResource(listOf("left", "straight"), stepDataUnknown))
|
assertEquals("left_x_straight_x", iconMapper.laneToResource(listOf("left", "straight"), stepDataUnknown))
|
||||||
assertEquals("", iconMapper.laneToResource(listOf("invalid"), stepDataUnknown))
|
assertEquals("", iconMapper.laneToResource(listOf("invalid"), stepDataUnknown))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -94,7 +94,6 @@ androidx-monitor = { group = "androidx.test", name = "monitor", version.ref = "m
|
|||||||
|
|
||||||
[plugins]
|
[plugins]
|
||||||
android-application = { id = "com.android.application", version.ref = "agp" }
|
android-application = { id = "com.android.application", version.ref = "agp" }
|
||||||
kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
|
|
||||||
kotlin-compose = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }
|
kotlin-compose = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }
|
||||||
android-library = { id = "com.android.library", version.ref = "agp" }
|
android-library = { id = "com.android.library", version.ref = "agp" }
|
||||||
kotlin-kapt = { id = "com.android.legacy-kapt", version.ref = "agp" }
|
kotlin-kapt = { id = "com.android.legacy-kapt", version.ref = "agp" }
|
||||||
|
|||||||
Reference in New Issue
Block a user