DataStore Manager

This commit is contained in:
Dimitris
2026-02-20 07:20:04 +01:00
parent 65ff41995d
commit ebd97cf1c9
65 changed files with 7975 additions and 13716 deletions

View File

@@ -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")

View File

@@ -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)
}

View File

@@ -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 {

View File

@@ -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 {

View File

@@ -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
}
}
}

View File

@@ -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)
}

View File

@@ -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()
}

View File

@@ -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(

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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)
}

View File

@@ -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)

View File

@@ -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
}

View File

@@ -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) }
}
}

View File

@@ -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)
}
}

View File

@@ -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

View File

@@ -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