diff --git a/app/build.gradle.kts b/app/build.gradle.kts index b1bd28a..d3c625c 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -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" } diff --git a/app/src/main/java/com/kouros/navigation/ui/MainActivity.kt b/app/src/main/java/com/kouros/navigation/ui/MainActivity.kt index 03ce677..7a80189 100644 --- a/app/src/main/java/com/kouros/navigation/ui/MainActivity.kt +++ b/app/src/main/java/com/kouros/navigation/ui/MainActivity.kt @@ -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 by lazy { @@ -97,29 +100,38 @@ class MainActivity : ComponentActivity() { MutableLiveData() } + var lastStepIndex = -1 var lastLocation = location(0.0, 0.0) val observer = Observer { 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() { diff --git a/common/car/src/androidTest/java/com/kouros/navigation/car/RouteModelTest.kt b/common/car/src/androidTest/java/com/kouros/navigation/car/RouteModelTest.kt index 3b8c3ff..833db23 100644 --- a/common/car/src/androidTest/java/com/kouros/navigation/car/RouteModelTest.kt +++ b/common/car/src/androidTest/java/com/kouros/navigation/car/RouteModelTest.kt @@ -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() } } } diff --git a/common/car/src/main/java/com/kouros/navigation/car/NavigationSession.kt b/common/car/src/main/java/com/kouros/navigation/car/NavigationSession.kt index 6d5f5e6..f47f95e 100644 --- a/common/car/src/main/java/com/kouros/navigation/car/NavigationSession.kt +++ b/common/car/src/main/java/com/kouros/navigation/car/NavigationSession.kt @@ -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" diff --git a/common/car/src/main/java/com/kouros/navigation/car/TextToSpeechManager.kt b/common/car/src/main/java/com/kouros/navigation/car/TextToSpeechManager.kt new file mode 100644 index 0000000..f535a3f --- /dev/null +++ b/common/car/src/main/java/com/kouros/navigation/car/TextToSpeechManager.kt @@ -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::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() + } + } +} \ No newline at end of file diff --git a/common/car/src/main/java/com/kouros/navigation/car/navigation/NavigationMessage.kt b/common/car/src/main/java/com/kouros/navigation/car/navigation/NavigationUtils.kt similarity index 56% rename from common/car/src/main/java/com/kouros/navigation/car/navigation/NavigationMessage.kt rename to common/car/src/main/java/com/kouros/navigation/car/navigation/NavigationUtils.kt index d39e1c9..69ff6b2 100644 --- a/common/car/src/main/java/com/kouros/navigation/car/navigation/NavigationMessage.kt +++ b/common/car/src/main/java/com/kouros/navigation/car/navigation/NavigationUtils.kt @@ -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() + } } \ No newline at end of file diff --git a/common/car/src/main/java/com/kouros/navigation/car/navigation/RouteCarModel.kt b/common/car/src/main/java/com/kouros/navigation/car/navigation/RouteCarModel.kt index ba204ac..4c7115c 100644 --- a/common/car/src/main/java/com/kouros/navigation/car/navigation/RouteCarModel.kt +++ b/common/car/src/main/java/com/kouros/navigation/car/navigation/RouteCarModel.kt @@ -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) diff --git a/common/car/src/main/java/com/kouros/navigation/car/screen/CategoryScreen.kt b/common/car/src/main/java/com/kouros/navigation/car/screen/CategoryScreen.kt index a5dcdb8..0ee7514 100644 --- a/common/car/src/main/java/com/kouros/navigation/car/screen/CategoryScreen.kt +++ b/common/car/src/main/java/com/kouros/navigation/car/screen/CategoryScreen.kt @@ -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() } } diff --git a/common/car/src/main/java/com/kouros/navigation/car/screen/NavigationScreen.kt b/common/car/src/main/java/com/kouros/navigation/car/screen/NavigationScreen.kt index 716c970..ff5522e 100644 --- a/common/car/src/main/java/com/kouros/navigation/car/screen/NavigationScreen.kt +++ b/common/car/src/main/java/com/kouros/navigation/car/screen/NavigationScreen.kt @@ -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 diff --git a/common/car/src/main/java/com/kouros/navigation/car/screen/RoutePreviewScreen.kt b/common/car/src/main/java/com/kouros/navigation/car/screen/RoutePreviewScreen.kt index ab3de4d..3c7cc3b 100644 --- a/common/car/src/main/java/com/kouros/navigation/car/screen/RoutePreviewScreen.kt +++ b/common/car/src/main/java/com/kouros/navigation/car/screen/RoutePreviewScreen.kt @@ -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 { 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() } } diff --git a/common/car/src/main/java/com/kouros/navigation/car/screen/settings/AudioSettings.kt b/common/car/src/main/java/com/kouros/navigation/car/screen/settings/AudioSettings.kt new file mode 100644 index 0000000..f91f8d8 --- /dev/null +++ b/common/car/src/main/java/com/kouros/navigation/car/screen/settings/AudioSettings.kt @@ -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) + } + +} diff --git a/common/car/src/main/java/com/kouros/navigation/car/screen/DarkModeSettings.kt b/common/car/src/main/java/com/kouros/navigation/car/screen/settings/DarkModeSettings.kt similarity index 82% rename from common/car/src/main/java/com/kouros/navigation/car/screen/DarkModeSettings.kt rename to common/car/src/main/java/com/kouros/navigation/car/screen/settings/DarkModeSettings.kt index 1c07be4..9d1bf90 100644 --- a/common/car/src/main/java/com/kouros/navigation/car/screen/DarkModeSettings.kt +++ b/common/car/src/main/java/com/kouros/navigation/car/screen/settings/DarkModeSettings.kt @@ -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() - } - } \ No newline at end of file diff --git a/common/car/src/main/java/com/kouros/navigation/car/screen/DisplaySettings.kt b/common/car/src/main/java/com/kouros/navigation/car/screen/settings/DisplaySettings.kt similarity index 95% rename from common/car/src/main/java/com/kouros/navigation/car/screen/DisplaySettings.kt rename to common/car/src/main/java/com/kouros/navigation/car/screen/settings/DisplaySettings.kt index 56eca52..977a805 100644 --- a/common/car/src/main/java/com/kouros/navigation/car/screen/DisplaySettings.kt +++ b/common/car/src/main/java/com/kouros/navigation/car/screen/settings/DisplaySettings.kt @@ -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 diff --git a/common/car/src/main/java/com/kouros/navigation/car/screen/DistanceSettings.kt b/common/car/src/main/java/com/kouros/navigation/car/screen/settings/DistanceSettings.kt similarity index 97% rename from common/car/src/main/java/com/kouros/navigation/car/screen/DistanceSettings.kt rename to common/car/src/main/java/com/kouros/navigation/car/screen/settings/DistanceSettings.kt index 1373277..0e7b773 100644 --- a/common/car/src/main/java/com/kouros/navigation/car/screen/DistanceSettings.kt +++ b/common/car/src/main/java/com/kouros/navigation/car/screen/settings/DistanceSettings.kt @@ -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 diff --git a/common/car/src/main/java/com/kouros/navigation/car/screen/NavigationSettings.kt b/common/car/src/main/java/com/kouros/navigation/car/screen/settings/NavigationSettings.kt similarity index 95% rename from common/car/src/main/java/com/kouros/navigation/car/screen/NavigationSettings.kt rename to common/car/src/main/java/com/kouros/navigation/car/screen/settings/NavigationSettings.kt index ec90a2f..84c63c6 100644 --- a/common/car/src/main/java/com/kouros/navigation/car/screen/NavigationSettings.kt +++ b/common/car/src/main/java/com/kouros/navigation/car/screen/settings/NavigationSettings.kt @@ -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 diff --git a/common/car/src/main/java/com/kouros/navigation/car/screen/PasswordSettings.kt b/common/car/src/main/java/com/kouros/navigation/car/screen/settings/PasswordSettings.kt similarity index 95% rename from common/car/src/main/java/com/kouros/navigation/car/screen/PasswordSettings.kt rename to common/car/src/main/java/com/kouros/navigation/car/screen/settings/PasswordSettings.kt index 993c755..df45771 100644 --- a/common/car/src/main/java/com/kouros/navigation/car/screen/PasswordSettings.kt +++ b/common/car/src/main/java/com/kouros/navigation/car/screen/settings/PasswordSettings.kt @@ -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 diff --git a/common/car/src/main/java/com/kouros/navigation/car/screen/RoutingSettings.kt b/common/car/src/main/java/com/kouros/navigation/car/screen/settings/RoutingSettings.kt similarity index 95% rename from common/car/src/main/java/com/kouros/navigation/car/screen/RoutingSettings.kt rename to common/car/src/main/java/com/kouros/navigation/car/screen/settings/RoutingSettings.kt index 76d9920..d9a0141 100644 --- a/common/car/src/main/java/com/kouros/navigation/car/screen/RoutingSettings.kt +++ b/common/car/src/main/java/com/kouros/navigation/car/screen/settings/RoutingSettings.kt @@ -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) )) diff --git a/common/car/src/main/java/com/kouros/navigation/car/screen/SettingsScreen.kt b/common/car/src/main/java/com/kouros/navigation/car/screen/settings/SettingsScreen.kt similarity index 71% rename from common/car/src/main/java/com/kouros/navigation/car/screen/SettingsScreen.kt rename to common/car/src/main/java/com/kouros/navigation/car/screen/settings/SettingsScreen.kt index 742e104..0396546 100644 --- a/common/car/src/main/java/com/kouros/navigation/car/screen/SettingsScreen.kt +++ b/common/car/src/main/java/com/kouros/navigation/car/screen/settings/SettingsScreen.kt @@ -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() } -} +} \ No newline at end of file diff --git a/common/data/src/main/java/com/kouros/navigation/data/Data.kt b/common/data/src/main/java/com/kouros/navigation/data/Data.kt index 753c23b..067a1a0 100644 --- a/common/data/src/main/java/com/kouros/navigation/data/Data.kt +++ b/common/data/src/main/java/com/kouros/navigation/data/Data.kt @@ -63,6 +63,7 @@ data class StepData ( var leftDistance: Double, var lane: List = listOf(Lane(location(0.0, 0.0), valid = false, indications = emptyList())), var exitNumber: Int = 0, + var message: String = "", ) diff --git a/common/data/src/main/java/com/kouros/navigation/data/datastore/DataStoreManager.kt b/common/data/src/main/java/com/kouros/navigation/data/datastore/DataStoreManager.kt index a5ca883..d48cc51 100644 --- a/common/data/src/main/java/com/kouros/navigation/data/datastore/DataStoreManager.kt +++ b/common/data/src/main/java/com/kouros/navigation/data/datastore/DataStoreManager.kt @@ -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 = + 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 + } + } + } diff --git a/common/data/src/main/java/com/kouros/navigation/data/route/Maneuver.kt b/common/data/src/main/java/com/kouros/navigation/data/route/Maneuver.kt index 4a2ad02..8502f27 100644 --- a/common/data/src/main/java/com/kouros/navigation/data/route/Maneuver.kt +++ b/common/data/src/main/java/com/kouros/navigation/data/route/Maneuver.kt @@ -10,4 +10,5 @@ data class Maneuver( val location: Location, val exit: Int = 0, val street: String = "", + val message: String = "" ) diff --git a/common/data/src/main/java/com/kouros/navigation/data/tomtom/TomTomRepository.kt b/common/data/src/main/java/com/kouros/navigation/data/tomtom/TomTomRepository.kt index 7ea1064..600459a 100644 --- a/common/data/src/main/java/com/kouros/navigation/data/tomtom/TomTomRepository.kt +++ b/common/data/src/main/java/com/kouros/navigation/data/tomtom/TomTomRepository.kt @@ -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§ionType=lanes" + + "&instructionsType=text&language=$language§ionType=lanes" + "&routeRepresentation=encodedPolyline" + "&vehicleEngineType=combustion$filter&key=$tomtomApiKey" return fetchUrl( diff --git a/common/data/src/main/java/com/kouros/navigation/data/tomtom/TomTomRoute.kt b/common/data/src/main/java/com/kouros/navigation/data/tomtom/TomTomRoute.kt index 09a401a..d980502 100644 --- a/common/data/src/main/java/com/kouros/navigation/data/tomtom/TomTomRoute.kt +++ b/common/data/src/main/java/com/kouros/navigation/data/tomtom/TomTomRoute.kt @@ -55,7 +55,8 @@ class TomTomRoute { location = location( instruction.point.longitude, instruction.point.latitude ), - street = maneuverStreet + street = maneuverStreet, + message = instruction.message ) lastPointIndex = instruction.pointIndex diff --git a/common/data/src/main/java/com/kouros/navigation/model/RouteModel.kt b/common/data/src/main/java/com/kouros/navigation/model/RouteModel.kt index ec5c5b1..0af684b 100644 --- a/common/data/src/main/java/com/kouros/navigation/model/RouteModel.kt +++ b/common/data/src/main/java/com/kouros/navigation/model/RouteModel.kt @@ -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 ) } diff --git a/common/data/src/main/java/com/kouros/navigation/model/SettingsViewModel.kt b/common/data/src/main/java/com/kouros/navigation/model/SettingsViewModel.kt index e852f6a..c587700 100644 --- a/common/data/src/main/java/com/kouros/navigation/model/SettingsViewModel.kt +++ b/common/data/src/main/java/com/kouros/navigation/model/SettingsViewModel.kt @@ -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) } + } + } diff --git a/common/data/src/main/java/com/kouros/navigation/repository/SettingsRepository.kt b/common/data/src/main/java/com/kouros/navigation/repository/SettingsRepository.kt index 183d67b..24eb41b 100644 --- a/common/data/src/main/java/com/kouros/navigation/repository/SettingsRepository.kt +++ b/common/data/src/main/java/com/kouros/navigation/repository/SettingsRepository.kt @@ -36,6 +36,9 @@ class SettingsRepository( val distanceModeFlow: Flow = dataStoreManager.distanceModeFlow + val guidanceAudioFlow: Flow = + 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) + } -} \ No newline at end of file +} diff --git a/common/data/src/main/res/drawable/dark_mode_24px.xml b/common/data/src/main/res/drawable/dark_mode_24px.xml new file mode 100644 index 0000000..b578c20 --- /dev/null +++ b/common/data/src/main/res/drawable/dark_mode_24px.xml @@ -0,0 +1,10 @@ + + + diff --git a/common/data/src/main/res/drawable/directions_car_24px.xml b/common/data/src/main/res/drawable/directions_car_24px.xml new file mode 100644 index 0000000..379343e --- /dev/null +++ b/common/data/src/main/res/drawable/directions_car_24px.xml @@ -0,0 +1,10 @@ + + + diff --git a/common/data/src/main/res/drawable/light_mode_24px.xml b/common/data/src/main/res/drawable/light_mode_24px.xml new file mode 100644 index 0000000..1af7fa4 --- /dev/null +++ b/common/data/src/main/res/drawable/light_mode_24px.xml @@ -0,0 +1,10 @@ + + + diff --git a/common/data/src/main/res/drawable/volume_off_24px.xml b/common/data/src/main/res/drawable/volume_off_24px.xml new file mode 100644 index 0000000..629bf61 --- /dev/null +++ b/common/data/src/main/res/drawable/volume_off_24px.xml @@ -0,0 +1,10 @@ + + + diff --git a/common/data/src/main/res/drawable/volume_up_24px.xml b/common/data/src/main/res/drawable/volume_up_24px.xml new file mode 100644 index 0000000..bc9c5c8 --- /dev/null +++ b/common/data/src/main/res/drawable/volume_up_24px.xml @@ -0,0 +1,11 @@ + + + diff --git a/common/data/src/main/res/raw/tomom_routing.json b/common/data/src/main/res/raw/tomom_routing.json index cae2b56..3e93a2b 100644 --- a/common/data/src/main/res/raw/tomom_routing.json +++ b/common/data/src/main/res/raw/tomom_routing.json @@ -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 } ] } diff --git a/common/data/src/main/res/values-de/strings.xml b/common/data/src/main/res/values-de/strings.xml index 4f2c0d8..8dcf179 100644 --- a/common/data/src/main/res/values-de/strings.xml +++ b/common/data/src/main/res/values-de/strings.xml @@ -58,4 +58,8 @@ Automatisch Kilometer Meilen + Töne + Stummgeschaltet + Ton an + Nur Alarme diff --git a/common/data/src/main/res/values-el/strings.xml b/common/data/src/main/res/values-el/strings.xml new file mode 100644 index 0000000..8b5e992 --- /dev/null +++ b/common/data/src/main/res/values-el/strings.xml @@ -0,0 +1,48 @@ + + + Κάμερα ταχύτητας + Κλείσιμο + Πρατήριο καυσίμων + Φαρμακείο + Σταθμός φόρτισης + Κατηγορία + Ενεργό + Ανενεργό + Χρήση ρυθμίσεων τηλεφώνου + Σκούρα εμφάνιση + Οθόνη + 3D κτίρια + Φτάσατε! + Οδήγηση τώρα + Διακοπή + Αποφυγή αυτοκινητοδρόμων + Αποφυγή διοδίων + Δεν βρέθηκαν τοποθεσίες + Πρόσφατοι προορισμοί + Επαφές + Αγαπημένα + Το πρόσφατο στοιχείο διαγράφηκε + Προεπισκόπηση διαδρομής + Εμφάνιση + Πλοήγηση + Ρυθμίσεις + Αποδοχή + Απόρριψη + OK + Αναζήτηση + Χρήση τοποθεσίας αυτοκινήτου + TomTom + Επιλογές + Κλειδί API TomTom + Χρήση ρυθμίσεων αυτοκινήτου + Αριθμός εξόδου + Εικονίδιο πλοήγησης + Μονάδες απόστασης + Αυτόματα + Χιλιόμετρα + Μίλια + Φωνητική καθοδήγηση + Σίγαση + Ήχος ενεργός + Μόνο ειδοποιήσεις + \ No newline at end of file diff --git a/common/data/src/main/res/values-pl/strings.xml b/common/data/src/main/res/values-pl/strings.xml new file mode 100644 index 0000000..ec1a92c --- /dev/null +++ b/common/data/src/main/res/values-pl/strings.xml @@ -0,0 +1,48 @@ + + + Fotoradar + Odrzuć + Stacja paliw + Apteka + Stacja ładowania + Kategoria + Włączone + Wyłączone + Użyj ustawień telefonu + Tryb ciemny + Wyświetlacz + Budynki 3D + Dotarto do celu! + Jedź teraz + Zatrzymaj + Unikaj autostrad + Unikaj opłat drogowych + Brak miejsc + Ostatnie cele + Kontakty + Ulubione + Usunięto ostatni element + Podgląd trasy + Wyświetlanie + Nawigacja + Ustawienia + Akceptuj + Odrzuć + OK + Szukaj + Użyj lokalizacji samochodu + TomTom + Opcje + Klucz API TomTom + Użyj ustawień samochodu + Numer zjazdu + Ikona nawigacji + Jednostki odległości + Automatycznie + Kilometry + Mile + Wskazówki głosowe + Wyciszony + Dźwięk włączony + Tylko ostrzeżenia + \ No newline at end of file diff --git a/common/data/src/main/res/values/strings.xml b/common/data/src/main/res/values/strings.xml index 9ff5463..4c78f68 100644 --- a/common/data/src/main/res/values/strings.xml +++ b/common/data/src/main/res/values/strings.xml @@ -34,7 +34,7 @@ Osrm Routing engine Use car location - TomTom\t + TomTom\t Options TomTom ApiKey Use car settings @@ -44,4 +44,8 @@ Automaticaly Kilometer Miles + Guidance audio + Muted + Unmuted + Alerts only \ No newline at end of file diff --git a/settings.gradle.kts b/settings.gradle.kts index 5dee68a..2b7fead 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -36,10 +36,11 @@ rootProject.name = "Navigation" include( ":", ":app", + ":automotive", ":common", - ":common:data", ":common:car", + ":common:data", ) -include(":automotive") +//include(":automotive")