Audio guidance

This commit is contained in:
Dimitris
2026-03-04 15:50:21 +01:00
parent 11e9dbb21e
commit e582c1e0dc
37 changed files with 614 additions and 162 deletions

View File

@@ -13,8 +13,8 @@ android {
applicationId = "com.kouros.navigation"
minSdk = 33
targetSdk = 36
versionCode = 59
versionName = "0.2.0.59"
versionCode = 60
versionName = "0.2.0.60"
base.archivesName = "navi-$versionName"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
}

View File

@@ -6,6 +6,8 @@ import android.app.AppOpsManager
import android.location.LocationManager
import android.os.Bundle
import android.os.Process
import android.speech.tts.TextToSpeech
import android.util.Log
import android.widget.Toast
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
@@ -81,13 +83,14 @@ import org.maplibre.compose.location.Location
import org.maplibre.compose.location.rememberDefaultLocationProvider
import org.maplibre.compose.location.rememberUserLocationState
import org.maplibre.spatialk.geojson.Position
import java.util.Locale
import kotlin.time.Duration.Companion.seconds
class MainActivity : ComponentActivity() {
val routeData = MutableLiveData("")
val routeModel = RouteModel()
var tilt = TILT
var tilt = TILT
val useMock = false
val type = SimulationType.SIMULATE
val stepData: MutableLiveData<StepData> by lazy {
@@ -97,29 +100,38 @@ class MainActivity : ComponentActivity() {
MutableLiveData()
}
var lastStepIndex = -1
var lastLocation = location(0.0, 0.0)
val observer = Observer<String> { newRoute ->
if (newRoute.isNotEmpty()) {
val repository = getSettingsRepository(applicationContext)
val routingEngine = runBlocking { repository.routingEngineFlow.first() }
val routingEngine = runBlocking { repository.routingEngineFlow.first() }
routeModel.navState = routeModel.navState.copy(routingEngine = routingEngine)
routeModel.startNavigation(newRoute)
if (routeModel.hasLegs()) {
getSettingsViewModel(applicationContext).onLastRouteChanged(newRoute)
}
routeData.value = routeModel.curRoute.routeGeoJson
if (useMock) {
when (type) {
SimulationType.SIMULATE -> simulate(routeModel, mock)
SimulationType.TEST -> test(applicationContext, routeModel)
SimulationType.GPX -> gpx(
context = applicationContext, mock
)
SimulationType.TEST_SINGLE -> testSingle(applicationContext, routeModel, mock)
}
checkMock()
}
}
lateinit var textToSpeech: TextToSpeech
private fun checkMock() {
if (useMock) {
when (type) {
SimulationType.SIMULATE -> simulate(routeModel, mock)
SimulationType.TEST -> test(applicationContext, routeModel)
SimulationType.GPX -> gpx(
context = applicationContext, mock
)
SimulationType.TEST_SINGLE -> testSingle(applicationContext, routeModel, mock)
}
}
}
val cameraPosition = MutableLiveData(
CameraPosition(
zoom = 15.0, target = Position(latitude = 48.1857475, longitude = 11.5793627)
@@ -135,6 +147,12 @@ class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
textToSpeech = TextToSpeech(applicationContext) { status ->
if (status == TextToSpeech.SUCCESS) {
textToSpeech.language = Locale.getDefault()
}
}
if (useMock) {
checkMockLocationEnabled()
}
@@ -195,8 +213,10 @@ class MainActivity : ComponentActivity() {
updateInterval = 0.5.seconds, desiredAccuracy = DesiredAccuracy.Highest
)
val userLocationState = rememberUserLocationState(locationProvider)
val locationState = locationProvider.location.collectAsState()
updateLocation(locationState.value)
if (!useMock) {
val locationState = locationProvider.location.collectAsState()
updateLocation(locationState.value)
}
val step: StepData? by stepData.observeAsState()
val nextStep: StepData? by nextStepData.observeAsState()
fun closeSheet() {
@@ -210,7 +230,7 @@ class MainActivity : ComponentActivity() {
snackbarHost = {
SnackbarHost(hostState = snackbarHostState)
},
scaffoldState = scaffoldState,
scaffoldState = scaffoldState,
sheetPeekHeight = sheetPeekHeightState.value,
sheetContent = {
SheetContent(step, nextStep) { closeSheet() }
@@ -317,8 +337,9 @@ class MainActivity : ComponentActivity() {
with(routeModel) {
if (isNavigating()) {
updateLocation( currentLocation, navigationViewModel)
updateLocation(currentLocation, navigationViewModel)
stepData.value = currentStep()
//textToSpeech(stepData.value!!.instruction)
if (navState.nextStep) {
nextStepData.value = nextStep()
}
@@ -353,7 +374,24 @@ class MainActivity : ComponentActivity() {
mock.setMockLocation(latitude, longitude, 0F)
}
routeData.value = ""
stepData.value = StepData("", "",0.0, 0, 0, 0, 0.0)
stepData.value = StepData("", "", 0.0, 0, 0, 0, 0.0)
}
fun textToSpeech(text: String) {
val currentStep = routeModel.route.currentStep()
val stepData = routeModel.currentStep()
if (currentStep.index > lastStepIndex && stepData.leftStepDistance < 50) {
lifecycleScope.launch {
try {
val cs: CharSequence = stepData.instruction
textToSpeech.speak(cs, TextToSpeech.QUEUE_FLUSH, null, "1233455")
Log.d("TTS", "speak $cs")
} catch (e: Throwable) {
Log.d("TTS", "speak error", e)
}
}
lastStepIndex = currentStep.index
}
}
fun simulateNavigation() {

View File

@@ -179,9 +179,12 @@ class RouteModelTest {
val curLocation = location(waypoint[0], waypoint[1])
if (routeModel.isNavigating()) {
if (index in 0..routeModel.curRoute.waypoints.size) {
routeModel.updateLocation(curLocation, NavigationViewModel(TomTomRepository()))
//runBlocking { delay(1000) }
val start = System.currentTimeMillis()
routeModel.updateLocation(curLocation, NavigationViewModel(TomTomRepository()))
val stepData = routeModel.currentStep()
val nextData = routeModel.nextStep()
println("${stepData.instruction} ${System.currentTimeMillis() - start}")
// val nextData = routeModel.nextStep()
}
}
}

View File

@@ -4,6 +4,7 @@ import android.Manifest.permission
import android.content.Intent
import android.content.pm.PackageManager
import android.location.Location
import android.speech.tts.TextToSpeech
import android.util.Log
import androidx.car.app.CarContext
import androidx.car.app.Screen
@@ -16,9 +17,12 @@ import androidx.car.app.navigation.model.Trip
import androidx.lifecycle.DefaultLifecycleObserver
import androidx.lifecycle.LifecycleObserver
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelStore
import androidx.lifecycle.ViewModelStoreOwner
import androidx.lifecycle.asLiveData
import androidx.lifecycle.lifecycleScope
import com.kouros.navigation.car.navigation.NavigationUtils
import com.kouros.navigation.car.navigation.RouteCarModel
import com.kouros.navigation.car.screen.NavigationScreen
import com.kouros.navigation.car.screen.RequestPermissionScreen
@@ -35,8 +39,11 @@ import com.kouros.navigation.data.valhalla.ValhallaRepository
import com.kouros.navigation.model.NavigationViewModel
import com.kouros.navigation.utils.GeoUtils.snapLocation
import com.kouros.navigation.utils.NavigationUtils.getViewModel
import com.kouros.navigation.utils.getSettingsRepository
import kotlinx.coroutines.awaitCancellation
import kotlinx.coroutines.launch
import org.maplibre.compose.expressions.dsl.step
import java.util.Locale
/**
@@ -67,6 +74,8 @@ class NavigationSession : Session(), NavigationScreen.Listener {
lateinit var navigationManager: NavigationManager
lateinit var textToSpeechManager: TextToSpeechManager
/**
* Lifecycle observer for managing session lifecycle events.
* Cleans up resources when the session is destroyed.
@@ -82,6 +91,9 @@ class NavigationSession : Session(), NavigationScreen.Listener {
if (::deviceLocationManager.isInitialized) {
deviceLocationManager.stopLocationUpdates()
}
if (::textToSpeechManager.isInitialized) {
textToSpeechManager.cleanup()
}
Log.i(TAG, "NavigationSession destroyed")
}
}
@@ -92,6 +104,10 @@ class NavigationSession : Session(), NavigationScreen.Listener {
// Store for ViewModels to survive configuration changes
lateinit var viewModelStoreOwner: ViewModelStoreOwner
var lastStepIndex = -1
var guidanceAudio = 0
init {
lifecycle.addObserver(lifecycleObserver)
}
@@ -130,6 +146,7 @@ class NavigationSession : Session(), NavigationScreen.Listener {
CarConnection.CONNECTION_TYPE_NATIVE -> {
navigationScreen.checkPermission(AUTOMOTIVE_CAR_SPEED_PERMISSION)
}
CarConnection.CONNECTION_TYPE_PROJECTION -> {
navigationScreen.checkPermission(GMS_CAR_SPEED_PERMISSION)
}
@@ -221,6 +238,12 @@ class NavigationSession : Session(), NavigationScreen.Listener {
)
}
)
textToSpeechManager = TextToSpeechManager(carContext)
val repository = getSettingsRepository(carContext)
repository.guidanceAudioFlow.asLiveData().observe(this, Observer {
guidanceAudio = it
})
}
/**
@@ -319,7 +342,6 @@ class NavigationSession : Session(), NavigationScreen.Listener {
*/
fun updateLocation(location: Location) {
updateBearing(location)
if (routeModel.isNavigating()) {
handleNavigationLocation(location)
} else {
@@ -341,6 +363,9 @@ class NavigationSession : Session(), NavigationScreen.Listener {
* Snaps location to route and checks for deviation requiring reroute.
*/
private fun handleNavigationLocation(location: Location) {
if (guidanceAudio == 1) {
handleGuidanceAudio()
}
navigationScreen.updateTrip(location)
if (routeModel.navState.arrived) return
val snappedLocation = snapLocation(location, routeModel.route.maneuverLocations())
@@ -349,11 +374,9 @@ class NavigationSession : Session(), NavigationScreen.Listener {
distance > MAXIMAL_ROUTE_DEVIATION -> {
navigationScreen.calculateNewRoute(routeModel.navState.destination)
}
distance < MAXIMAL_SNAP_CORRECTION -> {
surfaceRenderer.updateLocation(snappedLocation)
}
else -> {
surfaceRenderer.updateLocation(location)
}
@@ -382,6 +405,21 @@ class NavigationSession : Session(), NavigationScreen.Listener {
navigationManager.updateTrip(trip)
}
/**
* Handle guidance audio
* Called when user wants to hear the step by step instructions
*/
private fun handleGuidanceAudio() {
val currentStep = routeModel.route.currentStep()
val stepData = routeModel.currentStep()
if (currentStep.index > lastStepIndex && stepData.leftStepDistance < 50) {
if (textToSpeechManager.initialized) {
textToSpeechManager.speak(stepData.message)
}
lastStepIndex = currentStep.index
}
}
companion object {
// URI host for deep linking
var uriHost: String = "navigation"

View File

@@ -0,0 +1,61 @@
package com.kouros.navigation.car
import android.media.AudioAttributes
import android.media.AudioFocusRequest
import android.media.AudioManager
import android.speech.tts.TextToSpeech
import android.util.Log
import androidx.car.app.CarContext
import java.util.Locale
class TextToSpeechManager(private val carContext: CarContext) {
var textToSpeech: TextToSpeech
var initialized = false
init {
textToSpeech = TextToSpeech(carContext) { status ->
if (status == TextToSpeech.SUCCESS) {
Log.d("TTS", "Initialization Success")
val audioAttributes =
AudioAttributes.Builder()
.setContentType(AudioAttributes.CONTENT_TYPE_SPEECH)
.setUsage(AudioAttributes.USAGE_ASSISTANCE_NAVIGATION_GUIDANCE)
.build()
val request =
AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK)
.setAudioAttributes(audioAttributes)
.build()
val audioManager: AudioManager =
carContext.getSystemService<AudioManager?>(AudioManager::class.java)!!
// Requesting the audio focus.
if (audioManager.requestAudioFocus(request) == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
textToSpeech.setAudioAttributes(audioAttributes)
}
initialized = true
} else {
Log.d("TTS", "Initialization Failed")
}
}
}
fun speak(text: String) {
try {
val cs: CharSequence = text
textToSpeech.speak(cs, TextToSpeech.QUEUE_FLUSH, null, "1233455")
} catch (e: Throwable) {
Log.d("TTS", "speak error", e)
}
}
/**
* Cleans up manager.
* Should be called when the session is destroyed.
*/
fun cleanup() {
if (initialized) {
textToSpeech.shutdown()
}
}
}

View File

@@ -1,19 +1,27 @@
package com.kouros.navigation.car.navigation
import android.media.AudioAttributes
import android.media.AudioFocusRequest
import android.media.AudioManager
import android.media.MediaPlayer
import android.media.MediaPlayer.OnCompletionListener
import android.speech.tts.TextToSpeech
import android.util.Log
import androidx.annotation.DrawableRes
import androidx.annotation.RawRes
import androidx.annotation.StringRes
import androidx.car.app.CarContext
import androidx.car.app.CarToast
import androidx.car.app.model.Action
import androidx.car.app.model.Alert
import androidx.car.app.model.AlertCallback
import androidx.car.app.model.CarIcon
import androidx.car.app.model.CarText
import androidx.car.app.model.OnClickListener
import androidx.car.app.model.Row
import androidx.core.graphics.drawable.IconCompat
import com.kouros.android.cars.carappservice.R
import com.kouros.navigation.data.Constants.TAG
import java.io.IOException
import java.util.Locale
class NavigationMessage (private var carContext: CarContext) {
class NavigationUtils(private var carContext: CarContext) {
private fun createToastAction(
@StringRes titleRes: Int, @StringRes toastStringRes: Int,
@@ -35,6 +43,7 @@ class NavigationMessage (private var carContext: CarContext) {
)
.show()
}
fun createCarText(@StringRes stringRes: Int): CarText {
return CarText.create(carContext.getString(stringRes))
}
@@ -42,4 +51,19 @@ class NavigationMessage (private var carContext: CarContext) {
fun createCarIcon(@DrawableRes iconRes: Int): CarIcon {
return CarIcon.Builder(IconCompat.createWithResource(carContext, iconRes)).build()
}
fun buildRowForTemplate(title: Int, resource: Int): Row {
return Row.Builder()
.setTitle(carContext.getString(title))
.setImage(
CarIcon.Builder(
IconCompat.createWithResource(
carContext,
resource
)
)
.build()
)
.build()
}
}

View File

@@ -1,6 +1,8 @@
package com.kouros.navigation.car.navigation
import android.speech.tts.TextToSpeech
import android.text.SpannableString
import android.util.Log
import androidx.annotation.DrawableRes
import androidx.annotation.StringRes
import androidx.car.app.AppManager
@@ -26,15 +28,18 @@ import com.kouros.data.R
import com.kouros.navigation.data.StepData
import com.kouros.navigation.model.RouteModel
import com.kouros.navigation.utils.formattedDistance
import java.util.Locale
import java.util.TimeZone
import java.util.concurrent.TimeUnit
/** A class that provides models for the routing demos. */
class RouteCarModel() : RouteModel() {
class RouteCarModel : RouteModel() {
/** Returns the current [Step] with information such as the cue text and images. */
fun currentStep(carContext: CarContext): Step {
val stepData = currentStep()
val currentStepCueWithImage: SpannableString =
createString(stepData.instruction)

View File

@@ -17,7 +17,7 @@ import androidx.car.app.navigation.model.MapWithContentTemplate
import androidx.lifecycle.Observer
import com.kouros.data.R
import com.kouros.navigation.car.SurfaceRenderer
import com.kouros.navigation.car.navigation.NavigationMessage
import com.kouros.navigation.car.navigation.NavigationUtils
import com.kouros.navigation.data.Constants
import com.kouros.navigation.data.Place
import com.kouros.navigation.data.overpass.Elements
@@ -124,7 +124,7 @@ class CategoryScreen(
} else {
row.addText(carText("${it.tags.openingHours}"))
}
val navigationMessage = NavigationMessage(carContext)
val navigationUtils = NavigationUtils(carContext)
row.addAction(
Action.Builder()
.setOnClickListener {
@@ -144,7 +144,7 @@ class CategoryScreen(
)
finish()
}
.setIcon(navigationMessage.createCarIcon(R.drawable.navigation_48px))
.setIcon(navigationUtils.createCarIcon(R.drawable.navigation_48px))
.build())
return row.build()
}
@@ -180,10 +180,10 @@ class CategoryScreen(
@DrawableRes iconRes: Int,
scale: Int
): Action {
val navigationMessage = NavigationMessage(carContext)
val navigationUtils = NavigationUtils(carContext)
return Action.Builder()
.setOnClickListener { surfaceRenderer.handleScale(scale) }
.setIcon(navigationMessage.createCarIcon(iconRes))
.setIcon(navigationUtils.createCarIcon(iconRes))
.build()
}
}

View File

@@ -34,6 +34,7 @@ import com.kouros.navigation.car.ViewStyle
import com.kouros.navigation.car.navigation.RouteCarModel
import com.kouros.navigation.car.screen.observers.NavigationObserverCallback
import com.kouros.navigation.car.screen.observers.NavigationObserverManager
import com.kouros.navigation.car.screen.settings.SettingsScreen
import com.kouros.navigation.data.Constants.DESTINATION_ARRIVAL_DISTANCE
import com.kouros.navigation.data.Place
import com.kouros.navigation.data.overpass.Elements

View File

@@ -16,7 +16,6 @@ import androidx.car.app.model.Header
import androidx.car.app.model.ItemList
import androidx.car.app.model.ListTemplate
import androidx.car.app.model.MessageTemplate
import androidx.car.app.model.OnClickListener
import androidx.car.app.model.Row
import androidx.car.app.model.Template
import androidx.car.app.navigation.model.MapController
@@ -25,12 +24,11 @@ import androidx.core.graphics.drawable.IconCompat
import androidx.lifecycle.Observer
import com.kouros.data.R
import com.kouros.navigation.car.SurfaceRenderer
import com.kouros.navigation.car.navigation.NavigationMessage
import com.kouros.navigation.car.navigation.NavigationUtils
import com.kouros.navigation.car.navigation.RouteCarModel
import com.kouros.navigation.data.Place
import com.kouros.navigation.model.NavigationViewModel
import com.kouros.navigation.utils.getSettingsRepository
import com.kouros.navigation.utils.getSettingsViewModel
import com.kouros.navigation.utils.location
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.runBlocking
@@ -49,7 +47,7 @@ class RoutePreviewScreen(
val routeModel = RouteCarModel()
val navigationMessage = NavigationMessage(carContext)
val navigationUtils = NavigationUtils(carContext)
val observer = Observer<String> { route ->
if (route.isNotEmpty()) {
val repository = getSettingsRepository(carContext)
@@ -228,8 +226,6 @@ class RoutePreviewScreen(
private fun onRouteSelected(index: Int) {
routeModel.navState = routeModel.navState.copy(currentRouteIndex = index)
surfaceRenderer.setPreviewRouteData(routeModel)
//setResult(destination)
//finish()
}
fun getMapActionStrip(): ActionStrip {
@@ -249,7 +245,7 @@ class RoutePreviewScreen(
): Action {
return Action.Builder()
.setOnClickListener { surfaceRenderer.handleScale(-1) }
.setIcon(navigationMessage.createCarIcon(iconRes))
.setIcon(navigationUtils.createCarIcon(iconRes))
.build()
}
}

View File

@@ -0,0 +1,87 @@
package com.kouros.navigation.car.screen.settings
import androidx.car.app.CarContext
import androidx.car.app.Screen
import androidx.car.app.model.Action
import androidx.car.app.model.CarIcon
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.core.graphics.drawable.IconCompat
import androidx.lifecycle.lifecycleScope
import com.kouros.data.R
import com.kouros.navigation.car.navigation.NavigationUtils
import com.kouros.navigation.utils.getSettingsViewModel
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.launch
class AudioSettings(
private val carContext: CarContext,
) :
Screen(carContext) {
private var guidanceAudioSettings = 0
val settingsViewModel = getSettingsViewModel(carContext)
init {
lifecycleScope.launch {
settingsViewModel.guidanceAudio.first()
}
}
override fun onGetTemplate(): Template {
guidanceAudioSettings = settingsViewModel.guidanceAudio.value
val templateBuilder = ListTemplate.Builder()
val radioList =
ItemList.Builder()
.addItem(
NavigationUtils(carContext).buildRowForTemplate(
R.string.muted,
R.drawable.volume_off_24px
)
)
.addItem(
NavigationUtils(carContext).buildRowForTemplate(
R.string.unmuted,
R.drawable.volume_up_24px,
)
)
.addItem(
NavigationUtils(carContext).buildRowForTemplate(
R.string.alerts_only,
R.drawable.warning_24px,
)
)
.setOnSelectedListener { index: Int ->
this.onSelected(index)
}
.setSelectedIndex(guidanceAudioSettings)
.build()
return templateBuilder
.addSectionedList(
SectionedItemList.create(
radioList,
carContext.getString(R.string.audio_settings)
)
)
.setHeader(
Header.Builder()
.setTitle(carContext.getString(R.string.audio_settings))
.setStartHeaderAction(Action.BACK)
.build()
)
.build()
}
private fun onSelected(index: Int) {
settingsViewModel.onGuidanceAudioChanged(index)
}
}

View File

@@ -1,7 +1,6 @@
package com.kouros.navigation.car.screen
package com.kouros.navigation.car.screen.settings
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
@@ -12,6 +11,7 @@ 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.car.navigation.NavigationUtils
import com.kouros.navigation.utils.getSettingsViewModel
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.launch
@@ -34,18 +34,21 @@ class DarkModeSettings(private val carContext: CarContext) : Screen(carContext)
val radioList =
ItemList.Builder()
.addItem(
buildRowForTemplate(
NavigationUtils(carContext).buildRowForTemplate(
R.string.off_action_title,
R.drawable.light_mode_24px
)
)
.addItem(
buildRowForTemplate(
NavigationUtils(carContext).buildRowForTemplate(
R.string.on_action_title,
R.drawable.dark_mode_24px
)
)
.addItem(
buildRowForTemplate(
NavigationUtils(carContext).buildRowForTemplate(
R.string.use_car_settings,
R.drawable.directions_car_24px
)
)
.setOnSelectedListener { index: Int ->
@@ -74,11 +77,4 @@ class DarkModeSettings(private val carContext: CarContext) : Screen(carContext)
private fun onSelected(index: Int) {
settingsViewModel.onDarkModeChanged(index)
}
private fun buildRowForTemplate(title: Int): Row {
return Row.Builder()
.setTitle(carContext.getString(title))
.build()
}
}

View File

@@ -1,4 +1,4 @@
package com.kouros.navigation.car.screen
package com.kouros.navigation.car.screen.settings
import androidx.car.app.CarContext
import androidx.car.app.Screen
@@ -11,6 +11,7 @@ import androidx.car.app.model.Template
import androidx.car.app.model.Toggle
import androidx.lifecycle.lifecycleScope
import com.kouros.data.R
import com.kouros.navigation.car.screen.settings.DistanceSettings
import com.kouros.navigation.utils.getSettingsViewModel
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.launch

View File

@@ -1,7 +1,6 @@
package com.kouros.navigation.car.screen
package com.kouros.navigation.car.screen.settings
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

View File

@@ -1,4 +1,4 @@
package com.kouros.navigation.car.screen
package com.kouros.navigation.car.screen.settings
import androidx.car.app.CarContext
import androidx.car.app.Screen
@@ -11,12 +11,13 @@ import androidx.car.app.model.Template
import androidx.car.app.model.Toggle
import androidx.lifecycle.lifecycleScope
import com.kouros.data.R
import com.kouros.navigation.car.screen.settings.PasswordSettings
import com.kouros.navigation.car.screen.settings.RoutingSettings
import com.kouros.navigation.model.NavigationViewModel
import com.kouros.navigation.utils.getSettingsViewModel
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.launch
class NavigationSettings(
private val carContext: CarContext,
private var navigationViewModel: NavigationViewModel

View File

@@ -1,4 +1,4 @@
package com.kouros.navigation.car.screen
package com.kouros.navigation.car.screen.settings
import androidx.car.app.CarContext
import androidx.car.app.Screen
@@ -10,7 +10,6 @@ import androidx.car.app.model.signin.InputSignInMethod
import androidx.car.app.model.signin.SignInTemplate
import androidx.lifecycle.lifecycleScope
import com.kouros.data.R
import com.kouros.navigation.model.NavigationViewModel
import com.kouros.navigation.utils.getSettingsViewModel
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.launch

View File

@@ -1,7 +1,6 @@
package com.kouros.navigation.car.screen
package com.kouros.navigation.car.screen.settings
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
@@ -56,7 +55,8 @@ class RoutingSettings(private val carContext: CarContext, private var navigation
.build()
return templateBuilder
.addSectionedList(SectionedItemList.create(
.addSectionedList(
SectionedItemList.create(
radioList,
carContext.getString(R.string.routing_engine)
))

View File

@@ -1,19 +1,4 @@
/*
* Copyright 2022 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.
*/
package com.kouros.navigation.car.screen
package com.kouros.navigation.car.screen.settings
import androidx.car.app.CarContext
import androidx.car.app.Screen
@@ -34,6 +19,12 @@ class SettingsScreen(
override fun onGetTemplate(): Template {
val listBuilder = ItemList.Builder()
listBuilder.addItem(
buildRowForTemplate(
AudioSettings(carContext),
R.string.audio_settings
)
)
listBuilder.addItem(
buildRowForTemplate(
DisplaySettings(carContext),
@@ -67,4 +58,4 @@ class SettingsScreen(
.setBrowsable(true)
.build()
}
}
}

View File

@@ -63,6 +63,7 @@ data class StepData (
var leftDistance: Double,
var lane: List<Lane> = listOf(Lane(location(0.0, 0.0), valid = false, indications = emptyList())),
var exitNumber: Int = 0,
var message: String = "",
)

View File

@@ -47,6 +47,8 @@ class DataStoreManager(private val context: Context) {
val DISTANCE_MODE = intPreferencesKey("DistanceMode")
val GUIDANCE_AUDIO = intPreferencesKey("GuidanceAudio")
}
// Read values
@@ -107,6 +109,12 @@ class DataStoreManager(private val context: Context) {
?: 0
}
val guidanceAudioFlow: Flow<Int> =
context.dataStore.data.map { preferences ->
preferences[PreferencesKeys.GUIDANCE_AUDIO]
?: 0
}
// Save values
suspend fun setShow3D(enabled: Boolean) {
context.dataStore.edit { preferences ->
@@ -168,4 +176,10 @@ class DataStoreManager(private val context: Context) {
}
}
suspend fun setGuidanceAudio(mode: Int) {
context.dataStore.edit { prefs ->
prefs[PreferencesKeys.GUIDANCE_AUDIO] = mode
}
}
}

View File

@@ -10,4 +10,5 @@ data class Maneuver(
val location: Location,
val exit: Int = 0,
val street: String = "",
val message: String = ""
)

View File

@@ -9,6 +9,7 @@ import com.kouros.navigation.utils.GeoUtils.calculateSquareRadius
import com.kouros.navigation.utils.getSettingsRepository
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.runBlocking
import java.util.Locale
private const val routeUrl = "https://api.tomtom.com/routing/1/calculateRoute/"
@@ -42,12 +43,14 @@ class TomTomRepository : NavigationRepository() {
}
val repository = getSettingsRepository(context)
val tomtomApiKey = runBlocking { repository.tomTomApiKeyFlow.first() }
val currentLocale = Locale.getDefault()
val language = currentLocale.language + "-" + currentLocale.country
val url =
routeUrl + "${currentLocation.latitude},${currentLocation.longitude}:${location.latitude},${location.longitude}" +
"/json?sectionType=traffic&report=effectiveSettings&routeType=eco" +
"&traffic=true&avoid=unpavedRoads&travelMode=car" +
"&vehicleMaxSpeed=120&vehicleCommercial=false" +
"&instructionsType=text&language=en-GB&sectionType=lanes" +
"&instructionsType=text&language=$language&sectionType=lanes" +
"&routeRepresentation=encodedPolyline" +
"&vehicleEngineType=combustion$filter&key=$tomtomApiKey"
return fetchUrl(

View File

@@ -55,7 +55,8 @@ class TomTomRoute {
location = location(
instruction.point.longitude, instruction.point.latitude
),
street = maneuverStreet
street = maneuverStreet,
message = instruction.message
)
lastPointIndex = instruction.pointIndex

View File

@@ -93,7 +93,8 @@ open class RouteModel {
arrivalTime = routeCalculator.arrivalTime(),
leftDistance = routeCalculator.travelLeftDistance(),
lane = currentLanes(),
exitNumber = exitNumber
exitNumber = exitNumber,
message = currentStep.maneuver.message
)
}
@@ -118,7 +119,8 @@ open class RouteModel {
icon = maneuverIcon,
arrivalTime = routeCalculator.arrivalTime(),
leftDistance = routeCalculator.travelLeftDistance(),
exitNumber = nextStep.maneuver.exit
exitNumber = nextStep.maneuver.exit,
message = nextStep.maneuver.message
)
}

View File

@@ -72,6 +72,12 @@ class SettingsViewModel(private val repository: SettingsRepository) : ViewModel(
0
)
val guidanceAudio = repository.guidanceAudioFlow.stateIn(
viewModelScope,
SharingStarted.WhileSubscribed(5_000),
0
)
fun onShow3DChanged(enabled: Boolean) {
viewModelScope.launch { repository.setShow3D(enabled) }
}
@@ -108,4 +114,8 @@ class SettingsViewModel(private val repository: SettingsRepository) : ViewModel(
viewModelScope.launch { repository.setDistanceMode(mode) }
}
fun onGuidanceAudioChanged(mode: Int) {
viewModelScope.launch { repository.setGuidanceAudio(mode) }
}
}

View File

@@ -36,6 +36,9 @@ class SettingsRepository(
val distanceModeFlow: Flow<Int> =
dataStoreManager.distanceModeFlow
val guidanceAudioFlow: Flow<Int> =
dataStoreManager.guidanceAudioFlow
suspend fun setShow3D(enabled: Boolean) {
dataStoreManager.setShow3D(enabled)
@@ -77,5 +80,8 @@ class SettingsRepository(
dataStoreManager.setDistanceMode(mode)
}
suspend fun setGuidanceAudio(mode: Int) {
dataStoreManager.setGuidanceAudio(mode)
}
}
}

View File

@@ -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="M480,840Q330,840 225,735Q120,630 120,480Q120,330 225,225Q330,120 480,120Q494,120 507.5,121Q521,122 534,124Q493,153 468.5,199.5Q444,246 444,300Q444,390 507,453Q570,516 660,516Q715,516 761,491.5Q807,467 836,426Q838,439 839,452.5Q840,466 840,480Q840,630 735,735Q630,840 480,840ZM480,760Q568,760 638,711.5Q708,663 740,585Q720,590 700,593Q680,596 660,596Q537,596 450.5,509.5Q364,423 364,300Q364,280 367,260Q370,240 375,220Q297,252 248.5,322Q200,392 200,480Q200,596 282,678Q364,760 480,760ZM470,490Q470,490 470,490Q470,490 470,490Q470,490 470,490Q470,490 470,490Q470,490 470,490Q470,490 470,490Q470,490 470,490Q470,490 470,490Q470,490 470,490Q470,490 470,490Q470,490 470,490Q470,490 470,490Z"/>
</vector>

View File

@@ -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,760L240,800Q240,817 228.5,828.5Q217,840 200,840L160,840Q143,840 131.5,828.5Q120,817 120,800L120,480L204,240Q210,222 225.5,211Q241,200 260,200L700,200Q719,200 734.5,211Q750,222 756,240L840,480L840,800Q840,817 828.5,828.5Q817,840 800,840L760,840Q743,840 731.5,828.5Q720,817 720,800L720,760L240,760ZM232,400L728,400L686,280L274,280L232,400ZM200,480L200,480L200,680L200,680L200,480ZM300,640Q325,640 342.5,622.5Q360,605 360,580Q360,555 342.5,537.5Q325,520 300,520Q275,520 257.5,537.5Q240,555 240,580Q240,605 257.5,622.5Q275,640 300,640ZM660,640Q685,640 702.5,622.5Q720,605 720,580Q720,555 702.5,537.5Q685,520 660,520Q635,520 617.5,537.5Q600,555 600,580Q600,605 617.5,622.5Q635,640 660,640ZM200,680L760,680L760,480L200,480L200,680Z"/>
</vector>

View File

@@ -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="M565,565Q600,530 600,480Q600,430 565,395Q530,360 480,360Q430,360 395,395Q360,430 360,480Q360,530 395,565Q430,600 480,600Q530,600 565,565ZM338.5,621.5Q280,563 280,480Q280,397 338.5,338.5Q397,280 480,280Q563,280 621.5,338.5Q680,397 680,480Q680,563 621.5,621.5Q563,680 480,680Q397,680 338.5,621.5ZM200,520L40,520L40,440L200,440L200,520ZM920,520L760,520L760,440L920,440L920,520ZM440,200L440,40L520,40L520,200L440,200ZM440,920L440,760L520,760L520,920L440,920ZM256,310L155,213L212,154L308,254L256,310ZM748,806L651,705L704,650L805,747L748,806ZM650,256L747,155L806,212L706,308L650,256ZM154,748L255,651L310,704L213,805L154,748ZM480,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"/>
</vector>

View File

@@ -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="M792,904L671,783Q646,799 618,810.5Q590,822 560,829L560,747Q574,742 587.5,737Q601,732 613,725L480,592L480,800L280,600L120,600L120,360L248,360L56,168L112,112L848,848L792,904ZM784,672L726,614Q743,583 751.5,549Q760,515 760,479Q760,385 705,311Q650,237 560,211L560,129Q684,157 762,254.5Q840,352 840,479Q840,532 825.5,581Q811,630 784,672ZM650,538L560,448L560,318Q607,340 633.5,384Q660,428 660,480Q660,495 657.5,509.5Q655,524 650,538ZM480,368L376,264L480,160L480,368ZM400,606L400,512L328,440L328,440L200,440L200,520L314,520L400,606ZM364,476L364,476L364,476L364,476L364,476L364,476L364,476Z"/>
</vector>

View File

@@ -0,0 +1,11 @@
<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"
android:autoMirrored="true">
<path
android:fillColor="@android:color/white"
android:pathData="M560,829L560,747Q650,721 705,647Q760,573 760,479Q760,385 705,311Q650,237 560,211L560,129Q684,157 762,254.5Q840,352 840,479Q840,606 762,703.5Q684,801 560,829ZM120,600L120,360L280,360L480,160L480,800L280,600L120,600ZM560,640L560,318Q607,340 633.5,384Q660,428 660,480Q660,531 633.5,574.5Q607,618 560,640ZM400,354L314,440L200,440L200,520L314,520L400,606L400,354ZM300,480L300,480L300,480L300,480L300,480L300,480Z"/>
</vector>

View File

@@ -24,7 +24,7 @@
},
{
"key": "departAt",
"value": "2026-02-06T09:09:59.054Z"
"value": "2026-03-04T14:38:14.253Z"
},
{
"key": "guidanceVersion",
@@ -40,16 +40,20 @@
},
{
"key": "language",
"value": "en-GB"
"value": "de-DE"
},
{
"key": "locations",
"value": "48.18575,11.57939:48.11654,11.59449"
"value": "48.18575,11.57937:48.11648,11.59432"
},
{
"key": "maxAlternatives",
"value": "0"
},
{
"key": "maxPathAlternatives",
"value": "0"
},
{
"key": "routeRepresentation",
"value": "encodedPolyline"
@@ -60,11 +64,11 @@
},
{
"key": "sectionType",
"value": "traffic"
"value": "lanes"
},
{
"key": "sectionType",
"value": "lanes"
"value": "traffic"
},
{
"key": "traffic",
@@ -86,10 +90,6 @@
"key": "vehicleEngineType",
"value": "combustion"
},
{
"key": "vehicleHeading",
"value": "90"
},
{
"key": "vehicleHeight",
"value": "0.00"
@@ -119,28 +119,46 @@
"routes": [
{
"summary": {
"lengthInMeters": 11116,
"travelTimeInSeconds": 1148,
"trafficDelayInSeconds": 0,
"trafficLengthInMeters": 0,
"departureTime": "2026-02-06T10:09:59+01:00",
"arrivalTime": "2026-02-06T10:29:07+01:00"
"lengthInMeters": 11122,
"travelTimeInSeconds": 1483,
"trafficDelayInSeconds": 175,
"trafficLengthInMeters": 2191,
"departureTime": "2026-03-04T15:38:14+01:00",
"arrivalTime": "2026-03-04T16:02:57+01:00"
},
"legs": [
{
"summary": {
"lengthInMeters": 11116,
"travelTimeInSeconds": 1148,
"trafficDelayInSeconds": 0,
"trafficLengthInMeters": 0,
"departureTime": "2026-02-06T10:09:59+01:00",
"arrivalTime": "2026-02-06T10:29:07+01:00"
"lengthInMeters": 11122,
"travelTimeInSeconds": 1483,
"trafficDelayInSeconds": 175,
"trafficLengthInMeters": 2191,
"departureTime": "2026-03-04T15:38:14+01:00",
"arrivalTime": "2026-03-04T16:02:57+01:00"
},
"encodedPolyline": "sfbeHarteAE~DQEy@GQ?wDQFkEH{G?M?sA@kB?_FAeC?o@?[@_@\\Ab@CVAz@CJA@?dBGhAGjAExAGlBEvBKdCKTAfCKLAv@ELA|AGnAGt@ClCKjDQpBIf@BXDPDPBF@ZB?S@SAYAUKi@Go@Cc@BgBBs@Bg@JyAJiAXqBDWNs@TaA\\mAFa@Po@`BwF?YL]FSFSl@iB^kALc@L]Ro@f@yAf@{AFQNe@dAoCdBgEx@qBTi@BITe@L[L_@^}@HSXu@pB}El@eAb@e@f@[h@QZCRAL?j@HRFh@Vf@XLJhAn@lAv@TLtAz@r@`@bAn@pAv@f@X~@j@z@h@vBpA`@VHDFFJFf@X`CzApAh@f@L`@Fz@@f@AXEVEVEhA[h@Yn@e@PQFEJKRWV[PW`@w@t@}AHQN]~BiFP]`AoBh@aADGTa@t@aAt@{@PQJKJGFG@Cd@]XSxDmCf@a@n@o@TY\\g@LQHMJSLUP[f@iAPg@b@yAFODMNi@FS|@qCVaAHUHUn@wBHYh@eBpAkEjBiGfAeDj@yADMFQd@sAf@kAJUh@qAf@eAt@sAn@iALSN[p@kAVc@JOLSj@w@z@}@x@q@pAu@p@_@j@Sl@MLCRCb@E`@?^?L@`ABz@?N@~AFdADJ@rAH`DVpCVrAJd@BfHp@zGl@pAJ|ALnGp@jEh@fBJpAFF?P@N@ZCtC]r@GJCFCLCD?TEVEXGhAYzAg@NGv@]`@QJEvB_AXMVK\\Qb@Qn@QJCNAZC^ENA`@FnBb@xEtA^H^JnCl@z@r@`@Pr@TtBlA~C`Bn@\\xAl@PF`@LrAVlCh@bBLl@BlBJdG\\RDjCHn@?pB?xB?R@`@@GxAC^?ZInBIfCAvC?r@@dD@n@@b@@^D`C?TDxAFbBHdB@VHp@RjAJb@NNH`@VlBFf@PzARhBFd@@LRbBFh@\\nC@FNhAb@lEj@tDPpABTBRZlBTdBXjBn@xEBLDTRpAR~@l@jDj@Qv@IrEP",
"encodedPolyline": "sfbeH_rteAE|DQEy@GQ?wDQFkEH{G?M?sA@kB?_FAeC?o@?[@_@\\Ab@CVAz@CJA@?dBGhAGjAExAGlBEvBKdCKTAfCKLAv@ELA|AGnAGt@ClCKjDQpBIf@BXDPDPBF@ZB?S@SAYAUKi@Go@Cc@BgBBs@Bg@JyAJiAXqBDWNs@TaA\\mAFa@Po@`BwF?YL]FSFSl@iB^kALc@L]Ro@f@yAf@{AFQNe@dAoCdBgEx@qBTi@BITe@L[L_@^}@HSXu@pB}El@eAb@e@f@[h@QZCRAL?j@HRFh@Vf@XLJhAn@lAv@TLtAz@r@`@bAn@pAv@f@X~@j@z@h@vBpA`@VHDFFJFf@X`CzApAh@f@L`@Fz@@f@AXEVEVEhA[h@Yn@e@PQFEJKRWV[PW`@w@t@}AHQN]~BiFP]`AoBh@aADGTa@t@aAt@{@PQJKJGFG@Cd@]XSxDmCf@a@n@o@TY\\g@LQHMJSLUP[f@iAPg@b@yAFODMNi@FS|@qCVaAHUHUn@wBHYh@eBpAkEjBiGfAeDj@yADMFQd@sAf@kAJUh@qAf@eAt@sAn@iALSN[p@kAVc@JOLSj@w@z@}@x@q@pAu@p@_@j@Sl@MLCRCb@E`@?^?L@`ABz@?N@~AFdADJ@rAH`DVpCVrAJd@BfHp@zGl@pAJ|ALnGp@jEh@fBJpAFF?P@N@ZCtC]r@GJCFCLCD?TEVEXGhAYzAg@NGv@]`@QJEvB_AXMVK\\Qb@Qn@QJCNAZC^ENA`@FnBb@xEtA^H^JnCl@z@r@`@Pr@TtBlA~C`Bn@\\xAl@PF`@LrAVlCh@bBLl@BlBJdG\\RDjCHn@?pB?xB?R@`@@GxAC^?ZInBIfCAvC?r@@dD@n@@b@@^D`C?TDxAFbBHdB@VHp@RjAJb@NNH`@VlBFf@PzARhBFd@@LRbBFh@\\nC@FNhAb@lEj@tDPpABTBRZlBTdBXjBn@xEBLDTRpAR~@l@jDj@Qv@I~ER",
"encodedPolylinePrecision": 5
}
],
"sections": [
{
"startPointIndex": 66,
"endPointIndex": 143,
"sectionType": "TRAFFIC",
"simpleCategory": "JAM",
"effectiveSpeedInKmh": 26,
"delayInSeconds": 175,
"magnitudeOfDelay": 1,
"tec": {
"causes": [
{
"mainCauseCode": 1
}
],
"effectCode": 4
},
"eventId": "TTL41056710392017000"
},
{
"lanes": [
{
@@ -331,7 +349,7 @@
"travelTimeInSeconds": 0,
"point": {
"latitude": 48.18554,
"longitude": 11.57937
"longitude": 11.57936
},
"pointIndex": 0,
"instructionType": "LOCATION_DEPARTURE",
@@ -340,10 +358,10 @@
"possibleCombineWithNext": false,
"drivingSide": "RIGHT",
"maneuver": "DEPART",
"message": "Leave from Vogelhartstraße"
"message": "Abfahrt von Vogelhartstraße"
},
{
"routeOffsetInMeters": 72,
"routeOffsetInMeters": 71,
"travelTimeInSeconds": 16,
"point": {
"latitude": 48.18557,
@@ -358,11 +376,11 @@
"possibleCombineWithNext": false,
"drivingSide": "RIGHT",
"maneuver": "TURN_RIGHT",
"message": "Turn right onto Silcherstraße"
"message": "Biegen Sie rechts ab auf Silcherstraße"
},
{
"routeOffsetInMeters": 226,
"travelTimeInSeconds": 60,
"routeOffsetInMeters": 225,
"travelTimeInSeconds": 61,
"point": {
"latitude": 48.18696,
"longitude": 11.57857
@@ -376,11 +394,11 @@
"possibleCombineWithNext": false,
"drivingSide": "RIGHT",
"maneuver": "TURN_RIGHT",
"message": "Turn right onto Schmalkaldener Straße"
"message": "Biegen Sie rechts ab auf Schmalkaldener Straße"
},
{
"routeOffsetInMeters": 658,
"travelTimeInSeconds": 134,
"routeOffsetInMeters": 657,
"travelTimeInSeconds": 135,
"point": {
"latitude": 48.18686,
"longitude": 11.58437
@@ -397,11 +415,11 @@
"possibleCombineWithNext": false,
"drivingSide": "RIGHT",
"maneuver": "TURN_RIGHT",
"message": "Turn right onto Ingolstädter Straße/B13"
"message": "Biegen Sie rechts ab auf Ingolstädter Straße/B13"
},
{
"routeOffsetInMeters": 1720,
"travelTimeInSeconds": 267,
"routeOffsetInMeters": 1719,
"travelTimeInSeconds": 291,
"point": {
"latitude": 48.17733,
"longitude": 11.58503
@@ -418,12 +436,12 @@
"possibleCombineWithNext": true,
"drivingSide": "RIGHT",
"maneuver": "TURN_LEFT",
"message": "Turn left onto Schenkendorfstraße/B2R",
"combinedMessage": "Turn left onto Schenkendorfstraße/B2R then keep left at Schenkendorfstraße/B2R toward Messe / ICM"
"message": "Biegen Sie links ab auf Schenkendorfstraße/B2R",
"combinedMessage": "Biegen Sie links ab auf Schenkendorfstraße/B2R dann bleiben Sie bei Schenkendorfstraße/B2R Richtung Messe / ICM links"
},
{
"routeOffsetInMeters": 2075,
"travelTimeInSeconds": 307,
"routeOffsetInMeters": 2074,
"travelTimeInSeconds": 333,
"point": {
"latitude": 48.17678,
"longitude": 11.58957
@@ -441,12 +459,12 @@
"possibleCombineWithNext": true,
"drivingSide": "RIGHT",
"maneuver": "KEEP_LEFT",
"message": "Keep left at Schenkendorfstraße/B2R toward Messe / ICM",
"combinedMessage": "Keep left at Schenkendorfstraße/B2R toward Messe / ICM then keep left at Schenkendorfstraße/B2R toward Passau"
"message": "Bleiben Sie bei Schenkendorfstraße/B2R Richtung Messe / ICM links",
"combinedMessage": "Bleiben Sie bei Schenkendorfstraße/B2R Richtung Messe / ICM links dann bleiben Sie bei Schenkendorfstraße/B2R Richtung Passau links"
},
{
"routeOffsetInMeters": 2426,
"travelTimeInSeconds": 329,
"routeOffsetInMeters": 2425,
"travelTimeInSeconds": 371,
"point": {
"latitude": 48.17518,
"longitude": 11.59363
@@ -464,11 +482,11 @@
"possibleCombineWithNext": false,
"drivingSide": "RIGHT",
"maneuver": "KEEP_LEFT",
"message": "Keep left at Schenkendorfstraße/B2R toward Passau"
"message": "Bleiben Sie bei Schenkendorfstraße/B2R Richtung Passau links"
},
{
"routeOffsetInMeters": 2781,
"travelTimeInSeconds": 353,
"routeOffsetInMeters": 2780,
"travelTimeInSeconds": 436,
"point": {
"latitude": 48.17329,
"longitude": 11.59747
@@ -484,14 +502,14 @@
"possibleCombineWithNext": false,
"drivingSide": "RIGHT",
"maneuver": "FOLLOW",
"message": "Follow Isarring/B2R toward München-Ost"
"message": "Folgen Sie Isarring/B2R Richtung München-Ost"
},
{
"routeOffsetInMeters": 8433,
"travelTimeInSeconds": 734,
"routeOffsetInMeters": 8431,
"travelTimeInSeconds": 1014,
"point": {
"latitude": 48.13017,
"longitude": 11.61541
"longitude": 11.6154
},
"pointIndex": 266,
"instructionType": "TURN",
@@ -502,11 +520,11 @@
"possibleCombineWithNext": false,
"drivingSide": "RIGHT",
"maneuver": "BEAR_RIGHT",
"message": "Bear right at Ampfingstraße"
"message": "Halten Sie sich rechts bei Ampfingstraße"
},
{
"routeOffsetInMeters": 9495,
"travelTimeInSeconds": 884,
"routeOffsetInMeters": 9494,
"travelTimeInSeconds": 1197,
"point": {
"latitude": 48.12089,
"longitude": 11.61285
@@ -520,12 +538,12 @@
"possibleCombineWithNext": true,
"drivingSide": "RIGHT",
"maneuver": "TURN_RIGHT",
"message": "Turn right onto Anzinger Straße",
"combinedMessage": "Turn right onto Anzinger Straße then keep straight on at Sankt-Martin-Straße"
"message": "Biegen Sie rechts ab auf Anzinger Straße",
"combinedMessage": "Biegen Sie rechts ab auf Anzinger Straße dann fahren Sie geradeaus weiter auf Sankt-Martin-Straße"
},
{
"routeOffsetInMeters": 9991,
"travelTimeInSeconds": 974,
"routeOffsetInMeters": 9990,
"travelTimeInSeconds": 1286,
"point": {
"latitude": 48.12087,
"longitude": 11.60621
@@ -539,11 +557,11 @@
"possibleCombineWithNext": false,
"drivingSide": "RIGHT",
"maneuver": "STRAIGHT",
"message": "Keep straight on at Sankt-Martin-Straße"
"message": "Fahren Sie geradeaus weiter auf Sankt-Martin-Straße"
},
{
"routeOffsetInMeters": 10941,
"travelTimeInSeconds": 1103,
"routeOffsetInMeters": 10940,
"travelTimeInSeconds": 1440,
"point": {
"latitude": 48.11811,
"longitude": 11.59417
@@ -557,15 +575,15 @@
"possibleCombineWithNext": true,
"drivingSide": "RIGHT",
"maneuver": "TURN_LEFT",
"message": "Turn left onto Hohenwaldeckstraße",
"combinedMessage": "Turn left onto Hohenwaldeckstraße then you have arrived at Hohenwaldeckstraße. Your destination is on the left"
"message": "Biegen Sie links ab auf Hohenwaldeckstraße",
"combinedMessage": "Biegen Sie links ab auf Hohenwaldeckstraße dann sie haben Hohenwaldeckstraße erreicht. Ihr Ziel liegt auf der linken Seite"
},
{
"routeOffsetInMeters": 11116,
"travelTimeInSeconds": 1148,
"routeOffsetInMeters": 11122,
"travelTimeInSeconds": 1483,
"point": {
"latitude": 48.11655,
"longitude": 11.59422
"latitude": 48.11649,
"longitude": 11.59421
},
"pointIndex": 338,
"instructionType": "LOCATION_ARRIVAL",
@@ -574,33 +592,33 @@
"possibleCombineWithNext": false,
"drivingSide": "RIGHT",
"maneuver": "ARRIVE_LEFT",
"message": "You have arrived at Hohenwaldeckstraße. Your destination is on the left"
"message": "Sie haben Hohenwaldeckstraße erreicht. Ihr Ziel liegt auf der linken Seite"
}
],
"instructionGroups": [
{
"firstInstructionIndex": 0,
"lastInstructionIndex": 3,
"groupMessage": "Leave from Vogelhartstraße. Take the Ingolstädter Straße/B13",
"groupLengthInMeters": 1720
"groupMessage": "Abfahrt von Vogelhartstraße. Fahren Sie auf die Ingolstädter Straße/B13",
"groupLengthInMeters": 1719
},
{
"firstInstructionIndex": 4,
"lastInstructionIndex": 7,
"groupMessage": "Take the Schenkendorfstraße, Isarring/B2R toward Messe / ICM, Passau, München-Ost",
"groupLengthInMeters": 6713
"groupMessage": "Fahren Sie auf die Schenkendorfstraße, Isarring/B2R in Richtung Messe / ICM, Passau, München-Ost",
"groupLengthInMeters": 6712
},
{
"firstInstructionIndex": 8,
"lastInstructionIndex": 10,
"groupMessage": "Take the Ampfingstraße, Anzinger Straße, Sankt-Martin-Straße",
"groupLengthInMeters": 2508
"groupMessage": "Fahren Sie auf die Ampfingstraße, Anzinger Straße, Sankt-Martin-Straße",
"groupLengthInMeters": 2509
},
{
"firstInstructionIndex": 11,
"lastInstructionIndex": 12,
"groupMessage": "Get to your destination at Hohenwaldeckstraße",
"groupLengthInMeters": 175
"groupMessage": "Fahren Sie zu Ihrem Ziel in der Hohenwaldeckstraße",
"groupLengthInMeters": 182
}
]
}

View File

@@ -58,4 +58,8 @@
<string name="automaticaly">Automatisch</string>
<string name="kilometer">Kilometer</string>
<string name="miles">Meilen</string>
<string name="audio_settings">Töne</string>
<string name="muted">Stummgeschaltet</string>
<string name="unmuted">Ton an</string>
<string name="alerts_only">Nur Alarme</string>
</resources>

View File

@@ -0,0 +1,48 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="speed_camera">Κάμερα ταχύτητας</string>
<string name="exit_action_title">Κλείσιμο</string>
<string name="fuel_station">Πρατήριο καυσίμων</string>
<string name="pharmacy">Φαρμακείο</string>
<string name="charging_station">Σταθμός φόρτισης</string>
<string name="category_title">Κατηγορία</string>
<string name="on_action_title">Ενεργό</string>
<string name="off_action_title">Ανενεργό</string>
<string name="use_telephon_settings">Χρήση ρυθμίσεων τηλεφώνου</string>
<string name="dark_mode">Σκούρα εμφάνιση</string>
<string name="display_settings">Οθόνη</string>
<string name="threed_building">3D κτίρια</string>
<string name="arrived_exclamation_msg">Φτάσατε!</string>
<string name="drive_now">Οδήγηση τώρα</string>
<string name="stop_action_title">Διακοπή</string>
<string name="avoid_highways_row_title">Αποφυγή αυτοκινητοδρόμων</string>
<string name="avoid_tolls_row_title">Αποφυγή διοδίων</string>
<string name="no_places">Δεν βρέθηκαν τοποθεσίες</string>
<string name="recent_destinations">Πρόσφατοι προορισμοί</string>
<string name="contacts">Επαφές</string>
<string name="favorites">Αγαπημένα</string>
<string name="recent_Item_deleted">Το πρόσφατο στοιχείο διαγράφηκε</string>
<string name="route_preview">Προεπισκόπηση διαδρομής</string>
<string name="display">Εμφάνιση</string>
<string name="navigation_settings">Πλοήγηση</string>
<string name="settings_action_title">Ρυθμίσεις</string>
<string name="accept_action_title">Αποδοχή</string>
<string name="reject_action_title">Απόρριψη</string>
<string name="ok_action_title">OK</string>
<string name="search_action_title">Αναζήτηση</string>
<string name="use_car_location">Χρήση τοποθεσίας αυτοκινήτου</string>
<string name="tomtom">TomTom</string>
<string name="options">Επιλογές</string>
<string name="tomtom_api_key">Κλειδί API TomTom</string>
<string name="use_car_settings">Χρήση ρυθμίσεων αυτοκινήτου</string>
<string name="exit_number">Αριθμός εξόδου</string>
<string name="navigation_icon_description">Εικονίδιο πλοήγησης</string>
<string name="distance_units">Μονάδες απόστασης</string>
<string name="automaticaly">Αυτόματα</string>
<string name="kilometer">Χιλιόμετρα</string>
<string name="miles">Μίλια</string>
<string name="audio_settings">Φωνητική καθοδήγηση</string>
<string name="muted">Σίγαση</string>
<string name="unmuted">Ήχος ενεργός</string>
<string name="alerts_only">Μόνο ειδοποιήσεις</string>
</resources>

View File

@@ -0,0 +1,48 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="speed_camera">Fotoradar</string>
<string name="exit_action_title">Odrzuć</string>
<string name="fuel_station">Stacja paliw</string>
<string name="pharmacy">Apteka</string>
<string name="charging_station">Stacja ładowania</string>
<string name="category_title">Kategoria</string>
<string name="on_action_title">Włączone</string>
<string name="off_action_title">Wyłączone</string>
<string name="use_telephon_settings">Użyj ustawień telefonu</string>
<string name="dark_mode">Tryb ciemny</string>
<string name="display_settings">Wyświetlacz</string>
<string name="threed_building">Budynki 3D</string>
<string name="arrived_exclamation_msg">Dotarto do celu!</string>
<string name="drive_now">Jedź teraz</string>
<string name="stop_action_title">Zatrzymaj</string>
<string name="avoid_highways_row_title">Unikaj autostrad</string>
<string name="avoid_tolls_row_title">Unikaj opłat drogowych</string>
<string name="no_places">Brak miejsc</string>
<string name="recent_destinations">Ostatnie cele</string>
<string name="contacts">Kontakty</string>
<string name="favorites">Ulubione</string>
<string name="recent_Item_deleted">Usunięto ostatni element</string>
<string name="route_preview">Podgląd trasy</string>
<string name="display">Wyświetlanie</string>
<string name="navigation_settings">Nawigacja</string>
<string name="settings_action_title">Ustawienia</string>
<string name="accept_action_title">Akceptuj</string>
<string name="reject_action_title">Odrzuć</string>
<string name="ok_action_title">OK</string>
<string name="search_action_title">Szukaj</string>
<string name="use_car_location">Użyj lokalizacji samochodu</string>
<string name="tomtom">TomTom</string>
<string name="options">Opcje</string>
<string name="tomtom_api_key">Klucz API TomTom</string>
<string name="use_car_settings">Użyj ustawień samochodu</string>
<string name="exit_number">Numer zjazdu</string>
<string name="navigation_icon_description">Ikona nawigacji</string>
<string name="distance_units">Jednostki odległości</string>
<string name="automaticaly">Automatycznie</string>
<string name="kilometer">Kilometry</string>
<string name="miles">Mile</string>
<string name="audio_settings">Wskazówki głosowe</string>
<string name="muted">Wyciszony</string>
<string name="unmuted">Dźwięk włączony</string>
<string name="alerts_only">Tylko ostrzeżenia</string>
</resources>

View File

@@ -34,7 +34,7 @@
<string name="osrm" translatable="false">Osrm</string>
<string name="routing_engine" translatable="false">Routing engine</string>
<string name="use_car_location">Use car location</string>
<string name="tomtom">TomTom\t</string>
<string name="tomtom" translatable="false">TomTom\t</string>
<string name="options">Options</string>
<string name="tomtom_api_key">TomTom ApiKey</string>
<string name="use_car_settings">Use car settings</string>
@@ -44,4 +44,8 @@
<string name="automaticaly">Automaticaly</string>
<string name="kilometer">Kilometer</string>
<string name="miles">Miles</string>
<string name="audio_settings">Guidance audio</string>
<string name="muted">Muted</string>
<string name="unmuted">Unmuted</string>
<string name="alerts_only">Alerts only</string>
</resources>

View File

@@ -36,10 +36,11 @@ rootProject.name = "Navigation"
include(
":",
":app",
":automotive",
":common",
":common:data",
":common:car",
":common:data",
)
include(":automotive")
//include(":automotive")