DataStore Manager
This commit is contained in:
@@ -139,7 +139,9 @@ object Constants {
|
||||
const val CAR_LOCATION = "CarLocation"
|
||||
const val ROUTING_ENGINE = "RoutingEngine"
|
||||
|
||||
const val NEXT_STEP_THRESHOLD = 120.0
|
||||
const val LAST_ROUTE = "LastRoute"
|
||||
|
||||
const val NEXT_STEP_THRESHOLD = 500.0
|
||||
|
||||
const val MAXIMAL_SNAP_CORRECTION = 50.0
|
||||
|
||||
@@ -157,3 +159,12 @@ object Constants {
|
||||
enum class RouteEngine {
|
||||
VALHALLA, OSRM, TOMTOM, GRAPHHOPPER
|
||||
}
|
||||
|
||||
enum class NavigationThemeColor(val color: Long) {
|
||||
RED(0xFFD32F2F),
|
||||
ORANGE(0xFFF57C00),
|
||||
YELLOW(0xFFFBC02D),
|
||||
GREEN(0xFF388E3C),
|
||||
BLUE(0xFF1976D2),
|
||||
PURPLE(0xFF7B1FA2)
|
||||
}
|
||||
|
||||
@@ -18,7 +18,11 @@ package com.kouros.navigation.data
|
||||
|
||||
import android.content.Context
|
||||
import android.location.Location
|
||||
import com.google.gson.GsonBuilder
|
||||
import com.kouros.data.R
|
||||
import com.kouros.navigation.data.osrm.OsrmRepository
|
||||
import com.kouros.navigation.data.osrm.OsrmResponse
|
||||
import com.kouros.navigation.data.osrm.OsrmRoute
|
||||
import com.kouros.navigation.model.RouteModel
|
||||
import com.kouros.navigation.utils.GeoUtils.calculateSquareRadius
|
||||
import java.net.Authenticator
|
||||
@@ -50,10 +54,18 @@ abstract class NavigationRepository {
|
||||
searchFilter: SearchFilter,
|
||||
context: Context
|
||||
): Double {
|
||||
val route = getRoute(context, currentLocation, location, carOrientation, searchFilter)
|
||||
val routeModel = RouteModel()
|
||||
routeModel.startNavigation(route, context)
|
||||
return routeModel.curRoute.summary.distance
|
||||
val osrm = OsrmRepository()
|
||||
val route = osrm.getRoute(context, currentLocation, location, carOrientation, searchFilter)
|
||||
val gson = GsonBuilder().serializeNulls().create()
|
||||
val osrmJson = gson.fromJson(route, OsrmResponse::class.java)
|
||||
if (osrmJson.routes.isEmpty()) {
|
||||
return 0.0
|
||||
}
|
||||
return osrmJson.routes.first().distance
|
||||
// return osrmJson.destinations.first().distance?.toDouble() ?: 0.0
|
||||
///val routeModel = RouteModel()
|
||||
//routeModel.startNavigation(route, context)
|
||||
//return routeModel.curRoute.summary.distance
|
||||
}
|
||||
|
||||
fun searchPlaces(search: String, location: Location): String {
|
||||
|
||||
@@ -99,8 +99,8 @@ data class Route(
|
||||
}
|
||||
}
|
||||
|
||||
fun nextStep(steps : Int): Step {
|
||||
val nextIndex = currentStepIndex + steps
|
||||
fun nextStep(add: Int): Step {
|
||||
val nextIndex = currentStepIndex + add
|
||||
return if (isRouteValid() && nextIndex < legs().first().steps.size) {
|
||||
legs().first().steps[nextIndex]
|
||||
} else {
|
||||
|
||||
@@ -0,0 +1,111 @@
|
||||
package com.kouros.navigation.data.datastore
|
||||
|
||||
import android.content.Context
|
||||
import androidx.datastore.core.DataStore
|
||||
import androidx.datastore.preferences.core.Preferences
|
||||
import androidx.datastore.preferences.core.booleanPreferencesKey
|
||||
import androidx.datastore.preferences.core.edit
|
||||
import androidx.datastore.preferences.core.intPreferencesKey
|
||||
import androidx.datastore.preferences.preferencesDataStore
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.flow.map
|
||||
|
||||
private const val DATASTORE_NAME = "navigation_settings"
|
||||
|
||||
|
||||
/**
|
||||
* Central manager for app settings using DataStore
|
||||
*/
|
||||
class DataStoreManager(private val context: Context) {
|
||||
|
||||
companion object {
|
||||
private val Context.dataStore: DataStore<Preferences> by preferencesDataStore(name = DATASTORE_NAME)
|
||||
}
|
||||
|
||||
// Keys
|
||||
private object PreferencesKeys {
|
||||
|
||||
val THREED_BUILDING = booleanPreferencesKey("Show3D")
|
||||
|
||||
val DARK_MODE = intPreferencesKey("DarkMode")
|
||||
|
||||
val AVOID_MOTORWAY = booleanPreferencesKey("AvoidMotorway")
|
||||
|
||||
val AVOID_TOLLWAY = booleanPreferencesKey("AvoidTollway")
|
||||
|
||||
val CAR_LOCATION = booleanPreferencesKey("CarLocation")
|
||||
|
||||
val ROUTING_ENGINE = intPreferencesKey("RoutingEngine")
|
||||
}
|
||||
|
||||
// Read values
|
||||
|
||||
val threeDBuildingFlow: Flow<Boolean> =
|
||||
context.dataStore.data.map { preferences ->
|
||||
preferences[PreferencesKeys.THREED_BUILDING] == true
|
||||
}
|
||||
val darkModeFlow: Flow<Int> =
|
||||
context.dataStore.data.map { preferences ->
|
||||
preferences[PreferencesKeys.DARK_MODE]
|
||||
?: 0
|
||||
}
|
||||
|
||||
val avoidMotorwayFlow: Flow<Boolean> =
|
||||
context.dataStore.data.map { preferences ->
|
||||
preferences[PreferencesKeys.AVOID_MOTORWAY] == true
|
||||
}
|
||||
|
||||
val avoidTollwayFlow: Flow<Boolean> =
|
||||
context.dataStore.data.map { preferences ->
|
||||
preferences[PreferencesKeys.AVOID_TOLLWAY] == true
|
||||
}
|
||||
|
||||
val useCarLocationFlow: Flow<Boolean> =
|
||||
context.dataStore.data.map { preferences ->
|
||||
preferences[PreferencesKeys.CAR_LOCATION] == true
|
||||
}
|
||||
|
||||
val routingEngineFlow: Flow<Int> =
|
||||
context.dataStore.data.map { preferences ->
|
||||
preferences[PreferencesKeys.ROUTING_ENGINE]
|
||||
?: 0
|
||||
}
|
||||
|
||||
// Save values
|
||||
suspend fun setThreedBuilding(enabled: Boolean) {
|
||||
context.dataStore.edit { preferences ->
|
||||
preferences[PreferencesKeys.THREED_BUILDING] = enabled
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun setDarkMode(mode: Int) {
|
||||
context.dataStore.edit { prefs ->
|
||||
prefs[PreferencesKeys.DARK_MODE] = mode
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun setAvoidMotorway(enabled: Boolean) {
|
||||
context.dataStore.edit { preferences ->
|
||||
preferences[PreferencesKeys.AVOID_MOTORWAY] = enabled
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun setAvoidTollway(enabled: Boolean) {
|
||||
context.dataStore.edit { preferences ->
|
||||
preferences[PreferencesKeys.AVOID_TOLLWAY] = enabled
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun setCarLocation(enabled: Boolean) {
|
||||
context.dataStore.edit { preferences ->
|
||||
preferences[PreferencesKeys.CAR_LOCATION] = enabled
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun setRoutingEngine(mode: Int) {
|
||||
context.dataStore.edit { prefs ->
|
||||
prefs[PreferencesKeys.ROUTING_ENGINE] = mode
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,9 @@ import android.location.Location
|
||||
import com.kouros.navigation.data.NavigationRepository
|
||||
import com.kouros.navigation.data.SearchFilter
|
||||
|
||||
private const val routeUrl = "https://kouros-online.de/osrm/route/v1/driving/"
|
||||
//private const val routeUrl = "https://kouros-online.de/osrm/route/v1/driving/"
|
||||
|
||||
private const val routeUrl = "https://router.project-osrm.org/route/v1/driving/"
|
||||
|
||||
class OsrmRepository : NavigationRepository() {
|
||||
override fun getRoute(
|
||||
@@ -23,7 +25,7 @@ class OsrmRepository : NavigationRepository() {
|
||||
if (searchFilter.avoidTollway) {
|
||||
exclude = "$exclude&exclude=toll"
|
||||
}
|
||||
val routeLocation = "${currentLocation.longitude},${currentLocation.latitude};${location.longitude},${location.latitude}?steps=true&alternatives=0"
|
||||
val routeLocation = "${currentLocation.longitude},${currentLocation.latitude};${location.longitude},${location.latitude}?steps=true&alternatives=false"
|
||||
return fetchUrl(routeUrl + routeLocation + exclude, true)
|
||||
}
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ import java.net.URL
|
||||
class Overpass {
|
||||
|
||||
//val overpassUrl = "https://overpass.kumi.systems/api/interpreter"
|
||||
//val overpassUrl = "https://overpass-api.de/api"
|
||||
val overpassUrl = "https://kouros-online.de/overpass/interpreter"
|
||||
|
||||
|
||||
@@ -43,13 +44,13 @@ class Overpass {
|
||||
val boundingBox = getBoundingBox(location.latitude, location.longitude, radius)
|
||||
val httpURLConnection = URL(overpassUrl).openConnection() as HttpURLConnection
|
||||
httpURLConnection.requestMethod = "POST"
|
||||
// node["highway"="speed_camera"]
|
||||
// node[amenity=$category]
|
||||
httpURLConnection.setDoOutput(true);
|
||||
httpURLConnection.setRequestProperty(
|
||||
"Accept",
|
||||
"application/json"
|
||||
)
|
||||
// node["highway"="speed_camera"]
|
||||
// node[amenity=$category]
|
||||
httpURLConnection.setDoOutput(true);
|
||||
// define search query
|
||||
val searchQuery = """
|
||||
|[out:json];
|
||||
@@ -79,7 +80,7 @@ class Overpass {
|
||||
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
|
||||
println("Speed $e")
|
||||
}
|
||||
return emptyList()
|
||||
}
|
||||
|
||||
@@ -10,15 +10,14 @@ import com.kouros.navigation.utils.GeoUtils.calculateSquareRadius
|
||||
|
||||
private const val routeUrl = "https://api.tomtom.com/routing/1/calculateRoute/"
|
||||
|
||||
val tomtomApiKey = "678k5v6940cSXXIS5oD92qIrDgW3RBZ3"
|
||||
const val tomtomApiKey = "678k5v6940cSXXIS5oD92qIrDgW3RBZ3"
|
||||
|
||||
val tomtomTrafficUrl = "https://api.tomtom.com/traffic/services/5/incidentDetails"
|
||||
const val tomtomTrafficUrl = "https://api.tomtom.com/traffic/services/5/incidentDetails"
|
||||
|
||||
|
||||
private val tomtomFields =
|
||||
private const val tomtomFields =
|
||||
"{incidents{type,geometry{type,coordinates},properties{iconCategory,events{description}}}}"
|
||||
|
||||
const val useAsset = false
|
||||
const val useAsset = true
|
||||
|
||||
class TomTomRepository : NavigationRepository() {
|
||||
override fun getRoute(
|
||||
|
||||
@@ -61,9 +61,7 @@ class TomTomRoute {
|
||||
route.sections?.forEach { section ->
|
||||
val lanes = mutableListOf<Lane>()
|
||||
var startIndex = 0
|
||||
if (section.startPointIndex <= instruction.pointIndex - 3
|
||||
&& instruction.pointIndex <= section.endPointIndex
|
||||
) {
|
||||
|
||||
section.lanes?.forEach { itLane ->
|
||||
val lane = Lane(
|
||||
location(
|
||||
@@ -77,7 +75,7 @@ class TomTomRoute {
|
||||
lanes.add(lane)
|
||||
}
|
||||
intersections.add(Intersection(waypoints[startIndex], lanes))
|
||||
}
|
||||
|
||||
}
|
||||
allIntersections.addAll(intersections)
|
||||
stepDistance = route.guidance.instructions[index].routeOffsetInMeters - stepDistance
|
||||
@@ -166,13 +164,23 @@ class TomTomRoute {
|
||||
newType = androidx.car.app.navigation.model.Maneuver.TYPE_TURN_SHARP_RIGHT
|
||||
}
|
||||
|
||||
"ROUNDABOUT_RIGHT" -> {
|
||||
"ROUNDABOUT_RIGHT", "ROUNDABOUT_CROSS" -> {
|
||||
newType = androidx.car.app.navigation.model.Maneuver.TYPE_ROUNDABOUT_ENTER_CCW
|
||||
}
|
||||
|
||||
"ROUNDABOUT_LEFT" -> {
|
||||
newType = androidx.car.app.navigation.model.Maneuver.TYPE_ROUNDABOUT_ENTER_CW
|
||||
}
|
||||
"MAKE_UTURN" -> {
|
||||
newType = androidx.car.app.navigation.model.Maneuver.TYPE_U_TURN_LEFT
|
||||
}
|
||||
"ENTER_MOTORWAY" -> {
|
||||
newType = androidx.car.app.navigation.model.Maneuver.TYPE_MERGE_LEFT
|
||||
}
|
||||
"TAKE_EXIT" -> {
|
||||
newType = androidx.car.app.navigation.model.Maneuver.TYPE_TURN_SHARP_RIGHT
|
||||
|
||||
}
|
||||
}
|
||||
return newType
|
||||
}
|
||||
|
||||
@@ -66,28 +66,17 @@ class IconMapper() {
|
||||
|
||||
currentTurnIcon = R.drawable.ic_roundabout_ccw
|
||||
}
|
||||
}
|
||||
return currentTurnIcon
|
||||
}
|
||||
|
||||
|
||||
fun addLanes(stepData: StepData) : Int {
|
||||
stepData.lane.forEach {
|
||||
if (it.indications.isNotEmpty() && it.valid) {
|
||||
Collections.sort<String>(it.indications)
|
||||
var direction = ""
|
||||
it.indications.forEach { it2 ->
|
||||
direction = if (direction.isEmpty()) {
|
||||
it2.trim()
|
||||
} else {
|
||||
"${direction}_${it2.trim()}"
|
||||
}
|
||||
}
|
||||
val laneDirection = addLanes(direction, stepData)
|
||||
return laneDirection
|
||||
Maneuver.TYPE_U_TURN_LEFT -> {
|
||||
currentTurnIcon = R.drawable.ic_turn_u_turn_left
|
||||
}
|
||||
Maneuver.TYPE_U_TURN_RIGHT -> {
|
||||
currentTurnIcon = R.drawable.ic_turn_u_turn_right
|
||||
}
|
||||
Maneuver.TYPE_MERGE_LEFT -> {
|
||||
currentTurnIcon = R.drawable.ic_turn_merge_symmetrical
|
||||
}
|
||||
}
|
||||
return 0
|
||||
return currentTurnIcon
|
||||
}
|
||||
|
||||
fun addLanes(direction: String, stepData: StepData): Int {
|
||||
@@ -112,6 +101,8 @@ class IconMapper() {
|
||||
"straight" -> {
|
||||
when (stepData.currentManeuverType) {
|
||||
Maneuver.TYPE_STRAIGHT -> LaneDirection.SHAPE_STRAIGHT
|
||||
Maneuver.TYPE_KEEP_LEFT -> LaneDirection.SHAPE_STRAIGHT
|
||||
Maneuver.TYPE_KEEP_RIGHT -> LaneDirection.SHAPE_STRAIGHT
|
||||
else
|
||||
-> LaneDirection.SHAPE_UNKNOWN
|
||||
}
|
||||
@@ -136,7 +127,8 @@ class IconMapper() {
|
||||
|
||||
"left_slight", "slight_left" -> {
|
||||
when (stepData.currentManeuverType) {
|
||||
Maneuver.TYPE_TURN_SLIGHT_LEFT -> LaneDirection.SHAPE_SLIGHT_LEFT
|
||||
Maneuver.TYPE_TURN_NORMAL_LEFT -> LaneDirection.SHAPE_SLIGHT_LEFT
|
||||
Maneuver.TYPE_KEEP_LEFT -> LaneDirection.SHAPE_SLIGHT_LEFT
|
||||
else
|
||||
-> LaneDirection.SHAPE_UNKNOWN
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package com.kouros.navigation.model
|
||||
|
||||
import android.content.Context
|
||||
import android.location.Location
|
||||
import androidx.car.app.CarContext
|
||||
import androidx.compose.runtime.snapshots.SnapshotStateList
|
||||
import androidx.compose.runtime.toMutableStateList
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
@@ -18,21 +19,21 @@ import com.kouros.navigation.data.nominatim.Search
|
||||
import com.kouros.navigation.data.nominatim.SearchResult
|
||||
import com.kouros.navigation.data.overpass.Elements
|
||||
import com.kouros.navigation.data.overpass.Overpass
|
||||
import com.kouros.navigation.data.tomtom.Features
|
||||
import com.kouros.navigation.data.tomtom.Traffic
|
||||
import com.kouros.navigation.data.tomtom.TrafficData
|
||||
import com.kouros.navigation.utils.GeoUtils.createPointCollection
|
||||
import com.kouros.navigation.utils.Levenshtein
|
||||
import com.kouros.navigation.utils.NavigationUtils
|
||||
import com.kouros.navigation.utils.getSettingsRepository
|
||||
import com.kouros.navigation.utils.getSettingsViewModel
|
||||
import com.kouros.navigation.utils.location
|
||||
import io.objectbox.kotlin.boxFor
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.maplibre.geojson.FeatureCollection
|
||||
import java.time.LocalDateTime
|
||||
import java.time.ZoneOffset
|
||||
|
||||
class ViewModel(private val repository: NavigationRepository) : ViewModel() {
|
||||
class NavigationViewModel(private val repository: NavigationRepository) : ViewModel() {
|
||||
|
||||
val route: MutableLiveData<String> by lazy {
|
||||
MutableLiveData()
|
||||
@@ -457,14 +458,9 @@ class ViewModel(private val repository: NavigationRepository) : ViewModel() {
|
||||
}
|
||||
|
||||
fun getSearchFilter(context: Context): SearchFilter {
|
||||
val avoidMotorway = NavigationUtils.getBooleanKeyValue(
|
||||
context = context,
|
||||
Constants.AVOID_MOTORWAY
|
||||
)
|
||||
val avoidTollway = NavigationUtils.getBooleanKeyValue(
|
||||
context = context,
|
||||
Constants.AVOID_TOLLWAY
|
||||
)
|
||||
val repository = getSettingsRepository(context)
|
||||
val avoidMotorway = runBlocking { repository.avoidMotorwayFlow.first() }
|
||||
val avoidTollway = runBlocking { repository.avoidTollwayFlow.first() }
|
||||
return SearchFilter(avoidMotorway, avoidTollway)
|
||||
}
|
||||
|
||||
@@ -90,7 +90,7 @@ class RouteCalculator(var routeModel: RouteModel) {
|
||||
return nowUtcMillis + timeToDestinationMillis
|
||||
}
|
||||
|
||||
fun updateSpeedLimit(location: Location, viewModel: ViewModel) {
|
||||
fun updateSpeedLimit(location: Location, viewModel: NavigationViewModel) {
|
||||
if (routeModel.isNavigating()) {
|
||||
// speed limit
|
||||
val distance = lastSpeedLocation.distanceTo(location)
|
||||
|
||||
@@ -4,15 +4,19 @@ import android.content.Context
|
||||
import android.location.Location
|
||||
import androidx.car.app.navigation.model.Maneuver
|
||||
import com.kouros.navigation.data.Constants.NEXT_STEP_THRESHOLD
|
||||
import com.kouros.navigation.data.Constants.ROUTING_ENGINE
|
||||
import com.kouros.navigation.data.Place
|
||||
import com.kouros.navigation.data.Route
|
||||
import com.kouros.navigation.data.StepData
|
||||
import com.kouros.navigation.data.datastore.DataStoreManager
|
||||
import com.kouros.navigation.data.route.Lane
|
||||
import com.kouros.navigation.data.route.Leg
|
||||
import com.kouros.navigation.data.route.Routes
|
||||
import com.kouros.navigation.utils.NavigationUtils.getIntKeyValue
|
||||
import com.kouros.navigation.repository.SettingsRepository
|
||||
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
|
||||
import kotlin.math.absoluteValue
|
||||
|
||||
open class RouteModel {
|
||||
@@ -20,7 +24,7 @@ open class RouteModel {
|
||||
// Immutable Data Class
|
||||
data class NavigationState(
|
||||
val route: Route = Route.Builder().buildEmpty(),
|
||||
val iconMapper : IconMapper = IconMapper(),
|
||||
val iconMapper: IconMapper = IconMapper(),
|
||||
val navigating: Boolean = false,
|
||||
val arrived: Boolean = false,
|
||||
val travelMessage: String = "",
|
||||
@@ -37,7 +41,7 @@ open class RouteModel {
|
||||
val route: Route
|
||||
get() = navState.route
|
||||
|
||||
val routeCalculator : RouteCalculator = RouteCalculator(this)
|
||||
val routeCalculator: RouteCalculator = RouteCalculator(this)
|
||||
|
||||
val curRoute: Routes
|
||||
get() = navState.route.routes[navState.currentRouteIndex]
|
||||
@@ -46,38 +50,98 @@ open class RouteModel {
|
||||
get() = navState.route.routes[navState.currentRouteIndex].legs.first()
|
||||
|
||||
fun startNavigation(routeString: String, context: Context) {
|
||||
val routeEngine = getIntKeyValue(context = context, ROUTING_ENGINE)
|
||||
val repository = getSettingsRepository(context)
|
||||
val routingEngine = runBlocking { repository.routingEngineFlow.first() }
|
||||
navState = navState.copy(
|
||||
route = Route.Builder()
|
||||
.routeEngine(routeEngine)
|
||||
.routeEngine(routingEngine)
|
||||
.route(routeString)
|
||||
.build()
|
||||
)
|
||||
if (hasLegs()) {
|
||||
navState = navState.copy(navigating = true)
|
||||
//NavigationUtils.setStringKeyValue(context, routeString, LAST_ROUTE)
|
||||
}
|
||||
}
|
||||
|
||||
private fun hasLegs(): Boolean {
|
||||
return navState.route.routes.isNotEmpty() && navState.route.routes[0].legs.isNotEmpty()
|
||||
return navState.route.routes.isNotEmpty() && navState.route.routes[0].legs.isNotEmpty()
|
||||
}
|
||||
|
||||
fun stopNavigation() {
|
||||
fun stopNavigation(context: Context) {
|
||||
navState = navState.copy(
|
||||
route = Route.Builder().buildEmpty(),
|
||||
navigating = false,
|
||||
arrived = false,
|
||||
maneuverType = Maneuver.TYPE_UNKNOWN
|
||||
)
|
||||
//NavigationUtils.setStringKeyValue(context, "", LAST_ROUTE)
|
||||
}
|
||||
|
||||
fun updateLocation(curLocation: Location, viewModel: ViewModel) {
|
||||
fun updateLocation(context: Context, curLocation: Location, viewModel: NavigationViewModel) {
|
||||
navState = navState.copy(currentLocation = curLocation)
|
||||
routeCalculator.findStep(curLocation)
|
||||
routeCalculator.updateSpeedLimit(curLocation, viewModel)
|
||||
val repository = getSettingsRepository(context)
|
||||
val carLocation = runBlocking { repository.carLocationFlow.first() }
|
||||
if (carLocation) {
|
||||
routeCalculator.updateSpeedLimit(curLocation, viewModel)
|
||||
}
|
||||
navState = navState.copy(lastLocation = navState.currentLocation)
|
||||
}
|
||||
|
||||
fun currentStep(): StepData {
|
||||
val distanceToNextStep = routeCalculator.leftStepDistance()
|
||||
// Determine the maneuver type and corresponding icon
|
||||
val currentStep = navState.route.nextStep(0)
|
||||
val streetName = if (distanceToNextStep > NEXT_STEP_THRESHOLD) {
|
||||
currentStep.street
|
||||
} else {
|
||||
currentStep.maneuver.street
|
||||
}
|
||||
val curManeuverType = if (distanceToNextStep > NEXT_STEP_THRESHOLD) {
|
||||
Maneuver.TYPE_STRAIGHT
|
||||
} else {
|
||||
currentStep.maneuver.type
|
||||
}
|
||||
val exitNumber = currentStep.maneuver.exit
|
||||
val maneuverIcon = navState.iconMapper.maneuverIcon(curManeuverType)
|
||||
navState = navState.copy(maneuverType = curManeuverType)
|
||||
// Construct and return the final StepData object
|
||||
return StepData(
|
||||
instruction = streetName,
|
||||
leftStepDistance = distanceToNextStep,
|
||||
currentManeuverType = navState.maneuverType,
|
||||
icon = maneuverIcon,
|
||||
arrivalTime = routeCalculator.arrivalTime(),
|
||||
leftDistance = routeCalculator.travelLeftDistance(),
|
||||
lane = currentLanes(),
|
||||
exitNumber = exitNumber
|
||||
)
|
||||
}
|
||||
|
||||
fun nextStep(): StepData {
|
||||
val distanceToNextStep = routeCalculator.leftStepDistance()
|
||||
val step = navState.route.nextStep(1)
|
||||
val streetName = if (distanceToNextStep < NEXT_STEP_THRESHOLD) {
|
||||
step.maneuver.street
|
||||
} else {
|
||||
step.street
|
||||
}
|
||||
val maneuverType = step.maneuver.type
|
||||
|
||||
val maneuverIcon = navState.iconMapper.maneuverIcon(maneuverType)
|
||||
// Construct and return the final StepData object
|
||||
return StepData(
|
||||
instruction = streetName,
|
||||
leftStepDistance = distanceToNextStep,
|
||||
currentManeuverType = maneuverType,
|
||||
icon = maneuverIcon,
|
||||
arrivalTime = routeCalculator.arrivalTime(),
|
||||
leftDistance = routeCalculator.travelLeftDistance(),
|
||||
exitNumber = step.maneuver.exit
|
||||
)
|
||||
}
|
||||
|
||||
private fun currentLanes(): List<Lane> {
|
||||
var lanes = emptyList<Lane>()
|
||||
if (navState.route.legs().isNotEmpty()) {
|
||||
@@ -87,7 +151,7 @@ open class RouteModel {
|
||||
navState.lastLocation.distanceTo(location(it.location[0], it.location[1]))
|
||||
val sectionBearing =
|
||||
navState.lastLocation.bearingTo(location(it.location[0], it.location[1]))
|
||||
if (distance < 500 && (navState.routeBearing.absoluteValue - sectionBearing.absoluteValue).absoluteValue < 10) {
|
||||
if (distance < NEXT_STEP_THRESHOLD && (navState.routeBearing.absoluteValue - sectionBearing.absoluteValue).absoluteValue < 10) {
|
||||
lanes = it.lane
|
||||
}
|
||||
}
|
||||
@@ -96,61 +160,6 @@ open class RouteModel {
|
||||
return lanes
|
||||
}
|
||||
|
||||
fun currentStep(): StepData {
|
||||
val distanceToNextStep = routeCalculator.leftStepDistance()
|
||||
// Determine the maneuver type and corresponding icon
|
||||
val currentStep = navState.route.nextStep(0)
|
||||
// Safely get the street name from the maneuver
|
||||
val streetName = currentStep.maneuver.street
|
||||
val curManeuverType = currentStep.maneuver.type
|
||||
val exitNumber = currentStep.maneuver.exit
|
||||
val maneuverIcon = navState.iconMapper.maneuverIcon(curManeuverType)
|
||||
navState = navState.copy(maneuverType = curManeuverType)
|
||||
|
||||
val lanes = currentLanes()
|
||||
|
||||
// Construct and return the final StepData object
|
||||
return StepData(
|
||||
streetName,
|
||||
distanceToNextStep,
|
||||
navState.maneuverType,
|
||||
maneuverIcon,
|
||||
routeCalculator.arrivalTime(),
|
||||
routeCalculator.travelLeftDistance(),
|
||||
lanes,
|
||||
exitNumber
|
||||
)
|
||||
}
|
||||
|
||||
fun nextStep(): StepData {
|
||||
val step = navState.route.nextStep(1)
|
||||
val maneuverType = step.maneuver.type
|
||||
val distanceLeft = routeCalculator.leftStepDistance()
|
||||
var text = ""
|
||||
when (distanceLeft) {
|
||||
in 0.0..NEXT_STEP_THRESHOLD -> {
|
||||
}
|
||||
else -> {
|
||||
if (step.street.isNotEmpty()) {
|
||||
text = step.street
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val maneuverIcon = navState.iconMapper.maneuverIcon(maneuverType)
|
||||
// Construct and return the final StepData object
|
||||
return StepData(
|
||||
text,
|
||||
distanceLeft,
|
||||
maneuverType,
|
||||
maneuverIcon,
|
||||
routeCalculator.arrivalTime(),
|
||||
routeCalculator.travelLeftDistance(),
|
||||
listOf(Lane(location(0.0, 0.0), valid = false, indications = emptyList())),
|
||||
step.maneuver.exit
|
||||
)
|
||||
}
|
||||
|
||||
fun isNavigating(): Boolean {
|
||||
return navState.navigating
|
||||
}
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
package com.kouros.navigation.model
|
||||
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.kouros.navigation.repository.SettingsRepository
|
||||
import kotlinx.coroutines.flow.SharingStarted
|
||||
import kotlinx.coroutines.flow.stateIn
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
class SettingsViewModel(private val repository: SettingsRepository) : ViewModel() {
|
||||
|
||||
val threedBuilding = repository.threedBuildingFlow.stateIn(
|
||||
viewModelScope,
|
||||
SharingStarted.WhileSubscribed(5_000),
|
||||
false
|
||||
)
|
||||
|
||||
val darkMode = repository.darkModeFlow.stateIn(
|
||||
viewModelScope,
|
||||
SharingStarted.WhileSubscribed(5_000),
|
||||
0
|
||||
)
|
||||
|
||||
val avoidMotorway = repository.avoidMotorwayFlow.stateIn(
|
||||
viewModelScope,
|
||||
SharingStarted.WhileSubscribed(5_000),
|
||||
false
|
||||
)
|
||||
|
||||
val avoidTollway = repository.avoidTollwayFlow.stateIn(
|
||||
viewModelScope,
|
||||
SharingStarted.WhileSubscribed(5_000),
|
||||
false
|
||||
)
|
||||
|
||||
val carLocation = repository.carLocationFlow.stateIn(
|
||||
viewModelScope,
|
||||
SharingStarted.WhileSubscribed(5_000),
|
||||
false
|
||||
)
|
||||
|
||||
val routingEngine = repository.routingEngineFlow.stateIn(
|
||||
viewModelScope,
|
||||
SharingStarted.WhileSubscribed(5_000),
|
||||
0
|
||||
)
|
||||
|
||||
fun onThreedBuildingChanged(enabled: Boolean) {
|
||||
viewModelScope.launch { repository.setThreedBuilding(enabled) }
|
||||
}
|
||||
|
||||
|
||||
fun onDarkModeChanged(mode: Int) {
|
||||
viewModelScope.launch { repository.setDarkMode(mode) }
|
||||
}
|
||||
|
||||
fun onAvoidMotorway(enabled: Boolean) {
|
||||
viewModelScope.launch { repository.setAvoidMotorway(enabled) }
|
||||
}
|
||||
|
||||
fun onAvoidTollway(enabled: Boolean) {
|
||||
viewModelScope.launch { repository.setAvoidTollway(enabled) }
|
||||
}
|
||||
|
||||
fun onCarLocation(enabled: Boolean) {
|
||||
viewModelScope.launch { repository.setCarLocation(enabled) }
|
||||
}
|
||||
|
||||
fun onRoutingEngineChanged(mode: Int) {
|
||||
viewModelScope.launch { repository.setRoutingEngine(mode) }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
package com.kouros.navigation.repository
|
||||
|
||||
import com.kouros.navigation.data.datastore.DataStoreManager
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
class SettingsRepository(
|
||||
private val dataStoreManager: DataStoreManager
|
||||
) {
|
||||
|
||||
val threedBuildingFlow: Flow<Boolean> =
|
||||
dataStoreManager.threeDBuildingFlow
|
||||
val darkModeFlow: Flow<Int> =
|
||||
dataStoreManager.darkModeFlow
|
||||
|
||||
val avoidMotorwayFlow: Flow<Boolean> =
|
||||
dataStoreManager.avoidMotorwayFlow
|
||||
|
||||
val avoidTollwayFlow: Flow<Boolean> =
|
||||
dataStoreManager.avoidTollwayFlow
|
||||
|
||||
val carLocationFlow: Flow<Boolean> =
|
||||
dataStoreManager.useCarLocationFlow
|
||||
|
||||
val routingEngineFlow: Flow<Int> =
|
||||
dataStoreManager.routingEngineFlow
|
||||
|
||||
|
||||
suspend fun setThreedBuilding(enabled: Boolean) {
|
||||
dataStoreManager.setThreedBuilding(enabled)
|
||||
}
|
||||
|
||||
suspend fun setDarkMode(mode: Int) {
|
||||
dataStoreManager.setDarkMode(mode)
|
||||
}
|
||||
|
||||
suspend fun setAvoidMotorway(enabled: Boolean) {
|
||||
dataStoreManager.setAvoidMotorway(enabled)
|
||||
}
|
||||
|
||||
suspend fun setAvoidTollway(enabled: Boolean) {
|
||||
dataStoreManager.setAvoidTollway(enabled)
|
||||
}
|
||||
|
||||
suspend fun setCarLocation(enabled: Boolean) {
|
||||
dataStoreManager.setCarLocation(enabled)
|
||||
}
|
||||
|
||||
suspend fun setRoutingEngine(mode: Int) {
|
||||
dataStoreManager.setRoutingEngine(mode)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -3,14 +3,20 @@ package com.kouros.navigation.utils
|
||||
import android.content.Context
|
||||
import android.location.Location
|
||||
import android.location.LocationManager
|
||||
import androidx.car.app.CarContext
|
||||
import androidx.core.content.edit
|
||||
import com.kouros.navigation.data.Constants.ROUTING_ENGINE
|
||||
import com.kouros.navigation.data.Constants.SHARED_PREF_KEY
|
||||
import com.kouros.navigation.data.RouteEngine
|
||||
import com.kouros.navigation.data.datastore.DataStoreManager
|
||||
import com.kouros.navigation.data.osrm.OsrmRepository
|
||||
import com.kouros.navigation.data.tomtom.TomTomRepository
|
||||
import com.kouros.navigation.data.valhalla.ValhallaRepository
|
||||
import com.kouros.navigation.model.ViewModel
|
||||
import com.kouros.navigation.model.NavigationViewModel
|
||||
import com.kouros.navigation.model.SettingsViewModel
|
||||
import com.kouros.navigation.repository.SettingsRepository
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import java.time.LocalDateTime
|
||||
import java.time.ZoneId
|
||||
import java.time.ZoneOffset
|
||||
@@ -26,60 +32,15 @@ import kotlin.time.Duration.Companion.seconds
|
||||
|
||||
object NavigationUtils {
|
||||
|
||||
fun getViewModel(context: Context): ViewModel {
|
||||
val routeEngine = getIntKeyValue(context = context, ROUTING_ENGINE)
|
||||
fun getViewModel(context: Context): NavigationViewModel {
|
||||
val repository = getSettingsRepository(context)
|
||||
val routeEngine = runBlocking { repository.routingEngineFlow.first() }
|
||||
return when (routeEngine) {
|
||||
RouteEngine.VALHALLA.ordinal -> ViewModel(ValhallaRepository())
|
||||
RouteEngine.OSRM.ordinal -> ViewModel(OsrmRepository())
|
||||
else -> ViewModel(TomTomRepository())
|
||||
RouteEngine.VALHALLA.ordinal -> NavigationViewModel(ValhallaRepository())
|
||||
RouteEngine.OSRM.ordinal -> NavigationViewModel(OsrmRepository())
|
||||
else -> NavigationViewModel(TomTomRepository())
|
||||
}
|
||||
}
|
||||
|
||||
fun getBooleanKeyValue(context: Context, key: String): Boolean {
|
||||
return context
|
||||
.getSharedPreferences(
|
||||
SHARED_PREF_KEY,
|
||||
Context.MODE_PRIVATE
|
||||
)
|
||||
.getBoolean(key, false)
|
||||
}
|
||||
|
||||
fun setBooleanKeyValue(context: Context, `val`: Boolean, key: String) {
|
||||
context
|
||||
.getSharedPreferences(
|
||||
SHARED_PREF_KEY,
|
||||
Context.MODE_PRIVATE
|
||||
)
|
||||
.edit {
|
||||
putBoolean(
|
||||
key, `val`
|
||||
)
|
||||
apply()
|
||||
}
|
||||
}
|
||||
|
||||
fun getIntKeyValue(context: Context, key: String, default: Int = 0): Int {
|
||||
return context
|
||||
.getSharedPreferences(
|
||||
SHARED_PREF_KEY,
|
||||
Context.MODE_PRIVATE
|
||||
)
|
||||
.getInt(key, default)
|
||||
}
|
||||
|
||||
fun setIntKeyValue(context: Context, `val`: Int, key: String) {
|
||||
context
|
||||
.getSharedPreferences(
|
||||
SHARED_PREF_KEY,
|
||||
Context.MODE_PRIVATE
|
||||
)
|
||||
.edit {
|
||||
putInt(
|
||||
key, `val`
|
||||
)
|
||||
apply()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun calculateZoom(speed: Double?): Double {
|
||||
@@ -90,8 +51,8 @@ fun calculateZoom(speed: Double?): Double {
|
||||
val zoom = when (speedKmh) {
|
||||
in 0..10 -> 18.0
|
||||
in 11..30 -> 17.5
|
||||
in 31..50 -> 17.0
|
||||
in 61..70 -> 16.5
|
||||
in 31..65 -> 17.0
|
||||
in 66..70 -> 16.5
|
||||
else -> 16.0
|
||||
}
|
||||
return zoom
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
package com.kouros.navigation.utils
|
||||
|
||||
import android.content.Context
|
||||
import androidx.car.app.CarContext
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import androidx.lifecycle.ViewModelStoreOwner
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import com.kouros.navigation.data.datastore.DataStoreManager
|
||||
import com.kouros.navigation.model.SettingsViewModel
|
||||
import com.kouros.navigation.repository.SettingsRepository
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.runBlocking
|
||||
|
||||
|
||||
@Composable
|
||||
fun settingsViewModel(context: CarContext, viewModelStoreOwner: ViewModelStoreOwner) : SettingsViewModel {
|
||||
val dataStoreManager = remember { DataStoreManager(context) }
|
||||
val repository = remember { SettingsRepository(dataStoreManager) }
|
||||
val settingsViewModel: SettingsViewModel = viewModel(
|
||||
viewModelStoreOwner,
|
||||
factory = object : ViewModelProvider.Factory {
|
||||
override fun <T : ViewModel> create(modelClass: Class<T>): T {
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
return SettingsViewModel(repository) as T
|
||||
}
|
||||
}
|
||||
)
|
||||
return settingsViewModel
|
||||
}
|
||||
|
||||
fun getSettingsViewModel(context: Context) : SettingsViewModel {
|
||||
val dataStoreManager = DataStoreManager(context)
|
||||
val repository = SettingsRepository(dataStoreManager)
|
||||
return SettingsViewModel( repository)
|
||||
}
|
||||
|
||||
fun getSettingsRepository(context: Context) : SettingsRepository {
|
||||
val dataStore = DataStoreManager(context)
|
||||
return SettingsRepository(dataStore)
|
||||
}
|
||||
|
||||
|
||||
BIN
common/data/src/main/res/drawable/ic_turn_merge_symmetrical.png
Normal file
BIN
common/data/src/main/res/drawable/ic_turn_merge_symmetrical.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 832 B |
BIN
common/data/src/main/res/drawable/ic_turn_sharp_left.png
Normal file
BIN
common/data/src/main/res/drawable/ic_turn_sharp_left.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 593 B |
BIN
common/data/src/main/res/drawable/ic_turn_sharp_right.png
Normal file
BIN
common/data/src/main/res/drawable/ic_turn_sharp_right.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 593 B |
BIN
common/data/src/main/res/drawable/ic_turn_u_turn_left.png
Normal file
BIN
common/data/src/main/res/drawable/ic_turn_u_turn_left.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 829 B |
BIN
common/data/src/main/res/drawable/ic_turn_u_turn_right.png
Normal file
BIN
common/data/src/main/res/drawable/ic_turn_u_turn_right.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 835 B |
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
6668
common/data/src/main/res/raw/tomtom_traffic
Normal file
6668
common/data/src/main/res/raw/tomtom_traffic
Normal file
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user