DataStore Manager
This commit is contained in:
@@ -5,6 +5,7 @@ import org.jetbrains.kotlin.gradle.dsl.JvmTarget
|
||||
plugins {
|
||||
alias(libs.plugins.android.library)
|
||||
alias(libs.plugins.kotlin.android)
|
||||
alias(libs.plugins.kotlin.compose)
|
||||
kotlin("plugin.serialization") version "2.2.21"
|
||||
kotlin("kapt")
|
||||
}
|
||||
@@ -19,6 +20,10 @@ android {
|
||||
consumerProguardFiles("consumer-rules.pro")
|
||||
}
|
||||
|
||||
buildFeatures {
|
||||
compose = true
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
isMinifyEnabled = false
|
||||
@@ -40,6 +45,10 @@ android {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
val composeBom = platform(libs.androidx.compose.bom)
|
||||
implementation(composeBom)
|
||||
androidTestImplementation(composeBom)
|
||||
|
||||
implementation(libs.androidx.core.ktx)
|
||||
implementation(libs.androidx.appcompat)
|
||||
implementation(libs.material)
|
||||
@@ -49,6 +58,7 @@ dependencies {
|
||||
implementation(libs.koin.compose.viewmodel)
|
||||
implementation(libs.androidx.car.app)
|
||||
implementation(libs.android.sdk.turf)
|
||||
implementation(libs.androidx.compose.runtime)
|
||||
|
||||
|
||||
// objectbox
|
||||
@@ -56,6 +66,8 @@ dependencies {
|
||||
implementation(libs.androidx.material3)
|
||||
annotationProcessor(libs.objectbox.processor)
|
||||
|
||||
implementation(libs.androidx.datastore.preferences)
|
||||
|
||||
implementation(libs.kotlinx.serialization.json)
|
||||
implementation(libs.maplibre.compose)
|
||||
implementation("androidx.compose.material:material-icons-extended:1.7.8")
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
+9
-13
@@ -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)
|
||||
}
|
||||
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 832 B |
Binary file not shown.
|
After Width: | Height: | Size: 593 B |
Binary file not shown.
|
After Width: | Height: | Size: 593 B |
Binary file not shown.
|
After Width: | Height: | Size: 829 B |
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
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