Automotive, CarSession

This commit is contained in:
Dimitris
2026-04-03 09:54:43 +02:00
parent 52f8dec2e6
commit 24173412e8
38 changed files with 437 additions and 236 deletions
@@ -16,17 +16,16 @@ import kotlinx.coroutines.flow.map
private const val DATASTORE_NAME = "navigation_settings"
val Context.dataStore: DataStore<Preferences> by preferencesDataStore(name = DATASTORE_NAME)
/**
* Central manager for app settings using DataStore
*/
class DataStoreManager(private val context: Context) {
companion object {
val Context.dataStore: DataStore<Preferences> by preferencesDataStore(name = DATASTORE_NAME)
}
// Keys
object PreferencesKeys {
companion object PreferencesKeys {
val SHOW_3D = booleanPreferencesKey("Show3D")
@@ -66,186 +65,186 @@ class DataStoreManager(private val context: Context) {
val show3DFlow: Flow<Boolean> =
context.dataStore.data.map { preferences ->
preferences[PreferencesKeys.SHOW_3D] == true
preferences[SHOW_3D] == true
}
val darkModeFlow: Flow<Int> =
context.dataStore.data.map { preferences ->
preferences[PreferencesKeys.DARK_MODE]
preferences[DARK_MODE]
?: 0
}
val avoidMotorwayFlow: Flow<Boolean> =
context.dataStore.data.map { preferences ->
preferences[PreferencesKeys.AVOID_MOTORWAY] == true
preferences[AVOID_MOTORWAY] == true
}
val avoidTollwayFlow: Flow<Boolean> =
context.dataStore.data.map { preferences ->
preferences[PreferencesKeys.AVOID_TOLLWAY] == true
preferences[AVOID_TOLLWAY] == true
}
val avoidFerryFlow: Flow<Boolean> =
context.dataStore.data.map { preferences ->
preferences[PreferencesKeys.AVOID_FERRY] == true
preferences[AVOID_FERRY] == true
}
val useCarLocationFlow: Flow<Boolean> =
context.dataStore.data.map { preferences ->
preferences[PreferencesKeys.CAR_LOCATION] == true
preferences[CAR_LOCATION] == true
}
val routingEngineFlow: Flow<Int> =
context.dataStore.data.map { preferences ->
preferences[PreferencesKeys.ROUTING_ENGINE]
preferences[ROUTING_ENGINE]
?: 2
}
val lastRouteFlow: Flow<String> =
context.dataStore.data.map { preferences ->
preferences[PreferencesKeys.LAST_ROUTE]
preferences[LAST_ROUTE]
?: ""
}
val tomTomApiKeyFlow: Flow<String> =
context.dataStore.data.map { preferences ->
preferences[PreferencesKeys.TOMTOM_APIKEY]
preferences[TOMTOM_APIKEY]
?: ""
}
val recentPlacesFlow: Flow<String> =
context.dataStore.data.map { preferences ->
preferences[PreferencesKeys.RECENT_PLACES]
preferences[RECENT_PLACES]
?: ""
}
val distanceModeFlow: Flow<Int> =
context.dataStore.data.map { preferences ->
preferences[PreferencesKeys.DISTANCE_MODE]
preferences[DISTANCE_MODE]
?: 0
}
val guidanceAudioFlow: Flow<Int> =
context.dataStore.data.map { preferences ->
preferences[PreferencesKeys.GUIDANCE_AUDIO]
preferences[GUIDANCE_AUDIO]
?: 0
}
val trafficFlow: Flow<Boolean> =
context.dataStore.data.map { preferences ->
preferences[PreferencesKeys.TRAFFIC] == true
preferences[TRAFFIC] == true
}
val tripSuggestionFlow: Flow<Boolean> =
context.dataStore.data.map { preferences ->
preferences[PreferencesKeys.TRIP_SUGGESTION] == true
preferences[TRIP_SUGGESTION] == true
}
val engineTypeFlow: Flow<Int> =
context.dataStore.data.map { preferences ->
preferences[PreferencesKeys.ENGINE_TYPE]
preferences[ENGINE_TYPE]
?: EngineType.COMBUSTION.ordinal
}
val alternativeRoutesFlow: Flow<Boolean> =
context.dataStore.data.map { preferences ->
preferences[PreferencesKeys.ALTERNATIVE_ROUTES] == true
preferences[ALTERNATIVE_ROUTES] == true
}
// Save values
suspend fun setShow3D(enabled: Boolean) {
context.dataStore.edit { preferences ->
preferences[PreferencesKeys.SHOW_3D] = enabled
preferences[SHOW_3D] = enabled
}
}
suspend fun setDarkMode(mode: Int) {
context.dataStore.edit { prefs ->
prefs[PreferencesKeys.DARK_MODE] = mode
prefs[DARK_MODE] = mode
}
}
suspend fun setAvoidMotorway(enabled: Boolean) {
context.dataStore.edit { preferences ->
preferences[PreferencesKeys.AVOID_MOTORWAY] = enabled
preferences[AVOID_MOTORWAY] = enabled
}
}
suspend fun setAvoidTollway(enabled: Boolean) {
context.dataStore.edit { preferences ->
preferences[PreferencesKeys.AVOID_TOLLWAY] = enabled
preferences[AVOID_TOLLWAY] = enabled
}
}
suspend fun setAvoidFerry(enabled: Boolean) {
context.dataStore.edit { preferences ->
preferences[PreferencesKeys.AVOID_FERRY] = enabled
preferences[AVOID_FERRY] = enabled
}
}
suspend fun setCarLocation(enabled: Boolean) {
context.dataStore.edit { preferences ->
preferences[PreferencesKeys.CAR_LOCATION] = enabled
preferences[CAR_LOCATION] = enabled
}
}
suspend fun setRoutingEngine(mode: Int) {
context.dataStore.edit { prefs ->
prefs[PreferencesKeys.ROUTING_ENGINE] = mode
prefs[ROUTING_ENGINE] = mode
}
}
suspend fun setLastRoute(route: String) {
context.dataStore.edit { prefs ->
prefs[PreferencesKeys.LAST_ROUTE] = route
prefs[LAST_ROUTE] = route
}
}
suspend fun setTomtomApiKey(apiKey: String) {
context.dataStore.edit { prefs ->
prefs[PreferencesKeys.TOMTOM_APIKEY] = apiKey
prefs[TOMTOM_APIKEY] = apiKey
}
}
suspend fun setRecentPlaces(apiKey: String) {
context.dataStore.edit { prefs ->
prefs[PreferencesKeys.RECENT_PLACES] = apiKey
prefs[RECENT_PLACES] = apiKey
}
}
suspend fun setDistanceMode(mode: Int) {
context.dataStore.edit { prefs ->
prefs[PreferencesKeys.DISTANCE_MODE] = mode
prefs[DISTANCE_MODE] = mode
}
}
suspend fun setGuidanceAudio(mode: Int) {
context.dataStore.edit { prefs ->
prefs[PreferencesKeys.GUIDANCE_AUDIO] = mode
prefs[GUIDANCE_AUDIO] = mode
}
}
suspend fun setTraffic(enabled: Boolean) {
context.dataStore.edit { preferences ->
preferences[PreferencesKeys.TRAFFIC] = enabled
preferences[TRAFFIC] = enabled
}
}
suspend fun setTripSuggestion(enabled: Boolean) {
context.dataStore.edit { preferences ->
preferences[PreferencesKeys.TRIP_SUGGESTION] = enabled
preferences[TRIP_SUGGESTION] = enabled
}
}
suspend fun setEngineType(mode: Int) {
context.dataStore.edit { prefs ->
prefs[PreferencesKeys.ENGINE_TYPE] = mode
prefs[ENGINE_TYPE] = mode
}
}
suspend fun setAlternativeRoutes(enabled: Boolean) {
context.dataStore.edit { preferences ->
preferences[PreferencesKeys.ALTERNATIVE_ROUTES] = enabled
preferences[ALTERNATIVE_ROUTES] = enabled
}
}
@@ -21,7 +21,7 @@ const val tomtomTrafficUrl = "https://api.tomtom.com/traffic/services/5/incident
private const val tomtomFields =
"{incidents{type,geometry{type,coordinates},properties{iconCategory,events{description}}}}"
val useLocal = BuildConfig.DEBUG
val useLocal = BuildConfig.DEBUG
val useLocalTraffic = BuildConfig.DEBUG
@@ -78,7 +78,6 @@ class TomTomRoute {
) {
val lanes = mutableListOf<Lane>()
var startIndex = 0
var lastLane: Lane? = null
section.lanes?.forEach { itLane ->
val lane = Lane(
location = location(
@@ -91,13 +90,7 @@ class TomTomRoute {
endIndex = section.endPointIndex
)
startIndex = section.startPointIndex
if (lastLane == null
|| (!(lastLane.valid && lane.valid
&& lastLane.indications == lane.indications))
) {
lanes.add(lane)
}
lastLane = lane
lanes.add(lane)
}
intersections.add(Intersection(waypoints[startIndex], lanes))
}
@@ -76,48 +76,9 @@ open class RouteModel {
navState = navState.copy(lastLocation = navState.currentLocation)
}
fun nextStep(): StepData {
val distanceToNextStep = routeCalculator.leftStepDistance()
val nextStep = navState.route.nextStep(1)
var streetName = nextStep.street
var maneuverType = currentStep.maneuver.type
if (distanceToNextStep < NEXT_STEP_THRESHOLD) {
streetName = nextStep.maneuver.street
maneuverType = nextStep.maneuver.type
}
val maneuverIcon = navState.iconMapper.maneuverIcon(maneuverType)
// Construct and return the final StepData object
return StepData(
instruction = streetName,
street = "",
leftStepDistance = distanceToNextStep,
currentManeuverType = maneuverType,
icon = maneuverIcon,
arrivalTime = routeCalculator.arrivalTime(),
leftDistance = routeCalculator.travelLeftDistance(),
exitNumber = nextStep.maneuver.exit,
message = nextStep.maneuver.message
)
}
private fun currentLanes(): List<Lane> {
var lanes = emptyList<Lane>()
if (navState.route.legs().isNotEmpty()) {
currentStep.intersection.forEach {
if (it.lane.isNotEmpty()) {
val distance =
navState.lastLocation.distanceTo(location(it.location[0], it.location[1]))
if (distance < NEXT_STEP_THRESHOLD) {
lanes = it.lane
return@forEach
}
}
}
}
return lanes
}
/*
* Returns the current step
*/
fun currentStep(): StepData {
val distanceToNextStep = routeCalculator.leftStepDistance()
// Determine the maneuver type and corresponding icon
@@ -148,6 +109,54 @@ open class RouteModel {
)
}
/*
* Returns the next step
*/
fun nextStep(): StepData {
val distanceToNextStep = routeCalculator.leftStepDistance()
val nextStep = navState.route.nextStep(1)
var streetName = nextStep.street
var maneuverType = currentStep.maneuver.type
if (distanceToNextStep < NEXT_STEP_THRESHOLD) {
streetName = nextStep.maneuver.street
maneuverType = nextStep.maneuver.type
}
val maneuverIcon = navState.iconMapper.maneuverIcon(maneuverType)
// Construct and return the final StepData object
return StepData(
instruction = streetName,
street = "",
leftStepDistance = distanceToNextStep,
currentManeuverType = maneuverType,
icon = maneuverIcon,
arrivalTime = routeCalculator.arrivalTime(),
leftDistance = routeCalculator.travelLeftDistance(),
exitNumber = nextStep.maneuver.exit,
message = nextStep.maneuver.message
)
}
/*
* Returns the current lanes
*/
private fun currentLanes(): List<Lane> {
var lanes = emptyList<Lane>()
if (navState.route.legs().isNotEmpty()) {
currentStep.intersection.forEach {
if (it.lane.isNotEmpty()) {
val distance =
navState.lastLocation.distanceTo(location(it.location[0], it.location[1]))
if (distance < NEXT_STEP_THRESHOLD) {
lanes = it.lane
return@forEach
}
}
}
}
return lanes
}
/**
* Checks for navigating
*/
@@ -1,10 +1,7 @@
package com.kouros.navigation.model
import androidx.datastore.preferences.core.edit
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.kouros.navigation.data.datastore.DataStoreManager.Companion.dataStore
import com.kouros.navigation.data.datastore.DataStoreManager.PreferencesKeys
import com.kouros.navigation.repository.SettingsRepository
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.stateIn
@@ -29,6 +29,7 @@ import kotlin.math.ln
import kotlin.math.pow
import kotlin.math.roundToInt
import kotlin.time.Duration
import kotlin.time.Duration.Companion.milliseconds
import kotlin.time.Duration.Companion.seconds
import kotlin.time.DurationUnit
import kotlin.time.toDuration
@@ -138,7 +139,7 @@ fun duration(
lastLocationUpdate: LocalDateTime
): Duration {
if (preview) {
return 3.seconds
return 10.milliseconds
}
val cameraDuration = if ((lastBearing - bearing).absoluteValue > 20.0) {
2.seconds
@@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M240,600L240,640Q240,657 228.5,668.5Q217,680 200,680L160,680Q143,680 131.5,668.5Q120,657 120,640L120,320L204,80Q210,62 225.5,51Q241,40 260,40L700,40Q719,40 734.5,51Q750,62 756,80L840,320L840,640Q840,657 828.5,668.5Q817,680 800,680L760,680Q743,680 731.5,668.5Q720,657 720,640L720,600L240,600ZM232,240L728,240L686,120L274,120L232,240ZM200,320L200,320L200,520L200,520L200,320ZM300,480Q325,480 342.5,462.5Q360,445 360,420Q360,395 342.5,377.5Q325,360 300,360Q275,360 257.5,377.5Q240,395 240,420Q240,445 257.5,462.5Q275,480 300,480ZM660,480Q685,480 702.5,462.5Q720,445 720,420Q720,395 702.5,377.5Q685,360 660,360Q635,360 617.5,377.5Q600,395 600,420Q600,445 617.5,462.5Q635,480 660,480ZM520,920L280,800L440,800L440,720L680,840L520,840L520,920ZM200,520L760,520L760,320L200,320L200,520Z"/>
</vector>
@@ -0,0 +1,25 @@
<!--
Copyright 2021 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="@android:color/white"
android:pathData="M12,2C6.49,2 2,6.49 2,12s4.49,10 10,10 10,-4.49 10,-10S17.51,2 12,2zM12,20c-4.41,0 -8,-3.59 -8,-8s3.59,-8 8,-8 8,3.59 8,8 -3.59,8 -8,8zM15,12c0,1.66 -1.34,3 -3,3s-3,-1.34 -3,-3 1.34,-3 3,-3 3,1.34 3,3z"/>
</vector>