Automotive, CarSession
This commit is contained in:
+36
-37
@@ -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>
|
||||
Reference in New Issue
Block a user