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

@@ -14,8 +14,8 @@ android {
applicationId = "com.kouros.navigation" applicationId = "com.kouros.navigation"
minSdk = 33 minSdk = 33
targetSdk = 36 targetSdk = 36
versionCode = 38 versionCode = 42
versionName = "0.2.0.38" versionName = "0.2.0.42"
base.archivesName = "navi-$versionName" base.archivesName = "navi-$versionName"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
} }

View File

@@ -4,7 +4,7 @@ import android.app.Application
import android.content.Context import android.content.Context
import com.kouros.navigation.data.ObjectBox import com.kouros.navigation.data.ObjectBox
import com.kouros.navigation.di.appModule import com.kouros.navigation.di.appModule
import com.kouros.navigation.model.ViewModel import com.kouros.navigation.model.NavigationViewModel
import com.kouros.navigation.utils.NavigationUtils.getViewModel import com.kouros.navigation.utils.NavigationUtils.getViewModel
import org.koin.android.ext.koin.androidContext import org.koin.android.ext.koin.androidContext
import org.koin.android.ext.koin.androidLogger import org.koin.android.ext.koin.androidLogger
@@ -15,7 +15,7 @@ class MainApplication : Application() {
override fun onCreate() { override fun onCreate() {
super.onCreate() super.onCreate()
ObjectBox.init(this); ObjectBox.init(this)
appContext = applicationContext appContext = applicationContext
navigationViewModel = getViewModel(appContext!!) navigationViewModel = getViewModel(appContext!!)
startKoin { startKoin {
@@ -30,6 +30,6 @@ class MainApplication : Application() {
var appContext: Context? = null var appContext: Context? = null
private set private set
lateinit var navigationViewModel : ViewModel lateinit var navigationViewModel : NavigationViewModel
} }
} }

View File

@@ -1,18 +1,19 @@
package com.kouros.navigation.di package com.kouros.navigation.di
import com.kouros.navigation.data.NavigationRepository
import com.kouros.navigation.data.osrm.OsrmRepository import com.kouros.navigation.data.osrm.OsrmRepository
import com.kouros.navigation.data.tomtom.TomTomRepository import com.kouros.navigation.data.tomtom.TomTomRepository
import com.kouros.navigation.data.valhalla.ValhallaRepository import com.kouros.navigation.data.valhalla.ValhallaRepository
import com.kouros.navigation.model.BaseStyleModel import com.kouros.navigation.model.NavigationViewModel
import com.kouros.navigation.model.ViewModel import com.kouros.navigation.model.SettingsViewModel
import com.kouros.navigation.repository.SettingsRepository
import org.koin.core.module.dsl.singleOf import org.koin.core.module.dsl.singleOf
import org.koin.core.module.dsl.viewModel
import org.koin.core.module.dsl.viewModelOf import org.koin.core.module.dsl.viewModelOf
import org.koin.dsl.module import org.koin.dsl.module
import kotlin.math.sin
val appModule = module { val appModule = module {
viewModelOf(::ViewModel) viewModelOf(::NavigationViewModel)
viewModelOf(::SettingsViewModel)
singleOf(::ValhallaRepository) singleOf(::ValhallaRepository)
singleOf(::OsrmRepository) singleOf(::OsrmRepository)
singleOf(::TomTomRepository) singleOf(::TomTomRepository)

View File

@@ -43,26 +43,27 @@ import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import androidx.lifecycle.MutableLiveData import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.Observer import androidx.lifecycle.Observer
import androidx.lifecycle.lifecycleScope
import androidx.navigation.NavController import androidx.navigation.NavController
import androidx.navigation.NavHostController import androidx.navigation.NavHostController
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController
import com.google.android.gms.location.FusedLocationProviderClient import com.google.android.gms.location.FusedLocationProviderClient
import com.google.android.gms.location.LocationServices import com.google.android.gms.location.LocationServices
import com.kouros.data.R import com.kouros.data.R
import com.kouros.navigation.MainApplication.Companion.navigationViewModel import com.kouros.navigation.MainApplication.Companion.navigationViewModel
import com.kouros.navigation.data.Constants
import com.kouros.navigation.data.Constants.DESTINATION_ARRIVAL_DISTANCE import com.kouros.navigation.data.Constants.DESTINATION_ARRIVAL_DISTANCE
import com.kouros.navigation.data.Constants.homeVogelhart import com.kouros.navigation.data.Constants.homeVogelhart
import com.kouros.navigation.data.StepData import com.kouros.navigation.data.StepData
import com.kouros.navigation.data.datastore.DataStoreManager
import com.kouros.navigation.model.BaseStyleModel import com.kouros.navigation.model.BaseStyleModel
import com.kouros.navigation.model.MockLocation import com.kouros.navigation.model.MockLocation
import com.kouros.navigation.model.RouteModel import com.kouros.navigation.model.RouteModel
import com.kouros.navigation.repository.SettingsRepository
import com.kouros.navigation.ui.app.AppViewModel
import com.kouros.navigation.ui.app.appViewModel
import com.kouros.navigation.ui.navigation.AppNavGraph
import com.kouros.navigation.ui.theme.NavigationTheme import com.kouros.navigation.ui.theme.NavigationTheme
import com.kouros.navigation.utils.NavigationUtils.getIntKeyValue
import com.kouros.navigation.utils.bearing import com.kouros.navigation.utils.bearing
import com.kouros.navigation.utils.calculateZoom import com.kouros.navigation.utils.getSettingsViewModel
import com.kouros.navigation.utils.location import com.kouros.navigation.utils.location
import io.ticofab.androidgpxparser.parser.GPXParser import io.ticofab.androidgpxparser.parser.GPXParser
import io.ticofab.androidgpxparser.parser.domain.Gpx import io.ticofab.androidgpxparser.parser.domain.Gpx
@@ -70,7 +71,9 @@ import io.ticofab.androidgpxparser.parser.domain.TrackSegment
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import org.joda.time.DateTime import org.joda.time.DateTime
import org.maplibre.compose.camera.CameraPosition import org.maplibre.compose.camera.CameraPosition
import org.maplibre.compose.location.DesiredAccuracy import org.maplibre.compose.location.DesiredAccuracy
@@ -83,12 +86,13 @@ import kotlin.time.Duration.Companion.seconds
class MainActivity : ComponentActivity() { class MainActivity : ComponentActivity() {
val routeData = MutableLiveData("") val routeData = MutableLiveData("")
val routeModel = RouteModel() val routeModel = RouteModel()
var tilt = 50.0 var tilt = 50.0
val useMock = false val useMock = true
val type = 3 // simulate 2 test 3 gpx 4 testSingle val type = 3 // 1 simulate 2 test 3 gpx 4 testSingle
var currentIndex = 0 var currentIndex = 0
val stepData: MutableLiveData<StepData> by lazy { val stepData: MutableLiveData<StepData> by lazy {
@@ -134,8 +138,7 @@ class MainActivity : ComponentActivity() {
@RequiresPermission(allOf = [Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.ACCESS_COARSE_LOCATION]) @RequiresPermission(allOf = [Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.ACCESS_COARSE_LOCATION])
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
val darkModeSettings = getIntKeyValue(applicationContext, Constants.DARK_MODE_SETTINGS)
baseStyle = BaseStyleModel().readStyle(applicationContext, darkModeSettings, false)
if (useMock) { if (useMock) {
checkMockLocationEnabled() checkMockLocationEnabled()
} }
@@ -150,6 +153,11 @@ class MainActivity : ComponentActivity() {
navigationViewModel.route.observe(this, observer) navigationViewModel.route.observe(this, observer)
} }
} }
lifecycleScope.launch {
getSettingsViewModel(applicationContext).routingEngine.collect {
}
}
enableEdgeToEdge() enableEdgeToEdge()
setContent { setContent {
CheckPermissionScreen() CheckPermissionScreen()
@@ -186,9 +194,13 @@ class MainActivity : ComponentActivity() {
@Composable @Composable
fun StartScreen( fun StartScreen(
navController: NavHostController navController: NavHostController
) { ) {
val appViewModel: AppViewModel = appViewModel()
val darkMode by appViewModel.darkMode.collectAsState()
baseStyle = BaseStyleModel().readStyle(applicationContext, darkMode, darkMode == 1)
val scaffoldState = rememberBottomSheetScaffoldState() val scaffoldState = rememberBottomSheetScaffoldState()
val snackbarHostState = remember { SnackbarHostState() } val snackbarHostState = remember { SnackbarHostState() }
val scope = rememberCoroutineScope() val scope = rememberCoroutineScope()
@@ -212,7 +224,7 @@ class MainActivity : ComponentActivity() {
sheetPeekHeightState.value = 128.dp sheetPeekHeightState.value = 128.dp
} }
} }
NavigationTheme { NavigationTheme (useDarkTheme = darkMode == 1) {
BottomSheetScaffold( BottomSheetScaffold(
snackbarHost = { snackbarHost = {
SnackbarHost(hostState = snackbarHostState) SnackbarHost(hostState = snackbarHostState)
@@ -249,13 +261,12 @@ class MainActivity : ComponentActivity() {
@Composable @Composable
fun App() { fun App() {
val navController = rememberNavController() //val lastRoute = NavigationUtils.getStringKeyValue(applicationContext, Constants.LAST_ROUTE)
NavHost(navController = navController, startDestination = "startScreen") { //if (lastRoute!!.isNotEmpty()) {
composable("startScreen") { StartScreen(navController) } // routeModel.startNavigation(lastRoute, applicationContext)
composable("settings") { SettingsScreen(navController) { navController.popBackStack() } } // routeData.value = routeModel.curRoute.routeGeoJson
composable("display_settings") { DisplayScreenSettings(applicationContext) { navController.popBackStack() } } //}
composable("nav_settings") { NavigationScreenSettings(applicationContext) { navController.popBackStack() } } AppNavGraph(applicationContext = applicationContext, this)
}
} }
@Composable @Composable
@@ -285,14 +296,16 @@ class MainActivity : ComponentActivity() {
if (!routeModel.isNavigating()) { if (!routeModel.isNavigating()) {
SearchSheet(applicationContext, navigationViewModel, lastLocation) { closeSheet() } SearchSheet(applicationContext, navigationViewModel, lastLocation) { closeSheet() }
} else { } else {
if (step != null && nextStep != null) {
NavigationSheet( NavigationSheet(
applicationContext, applicationContext,
routeModel, routeModel,
step!!, step,
nextStep!!, nextStep,
{ stopNavigation { closeSheet() } }, { stopNavigation { closeSheet() } },
{ simulateNavigation() }) { simulateNavigation() })
} }
}
// For recomposition! // For recomposition!
Text("$locationState", fontSize = 12.sp) Text("$locationState", fontSize = 12.sp)
} }
@@ -303,17 +316,18 @@ class MainActivity : ComponentActivity() {
val bearing = bearing(lastLocation, currentLocation, cameraPosition.value!!.bearing) val bearing = bearing(lastLocation, currentLocation, cameraPosition.value!!.bearing)
with(routeModel) { with(routeModel) {
if (isNavigating()) { if (isNavigating()) {
updateLocation(currentLocation, navigationViewModel) updateLocation(applicationContext,currentLocation, navigationViewModel)
stepData.value = currentStep() stepData.value = currentStep()
nextStepData.value = nextStep() nextStepData.value = nextStep()
if (navState.maneuverType in 39..42 && routeCalculator.leftStepDistance() < DESTINATION_ARRIVAL_DISTANCE) { if (navState.maneuverType in 39..42 && routeCalculator.leftStepDistance() < DESTINATION_ARRIVAL_DISTANCE) {
// stopNavigation() // stopNavigation()
navState.copy(arrived = true) navState = navState.copy(arrived = true)
routeData.value = "" routeData.value = ""
} }
} }
} }
val zoom = calculateZoom(location.speed) //val zoom = calculateZoom(location.speed)
val zoom = 16.0
cameraPosition.postValue( cameraPosition.postValue(
cameraPosition.value!!.copy( cameraPosition.value!!.copy(
zoom = zoom, target = location.position, bearing = bearing zoom = zoom, target = location.position, bearing = bearing
@@ -331,7 +345,7 @@ class MainActivity : ComponentActivity() {
val latitude = routeModel.curRoute.waypoints[0][1] val latitude = routeModel.curRoute.waypoints[0][1]
val longitude = routeModel.curRoute.waypoints[0][0] val longitude = routeModel.curRoute.waypoints[0][0]
closeSheet() closeSheet()
routeModel.stopNavigation() routeModel.stopNavigation(applicationContext)
if (useMock) { if (useMock) {
mock.setMockLocation(latitude, longitude) mock.setMockLocation(latitude, longitude)
} }
@@ -370,7 +384,7 @@ class MainActivity : ComponentActivity() {
val deviation = 0.0 val deviation = 0.0
if (index in 0..routeModel.curRoute.waypoints.size) { if (index in 0..routeModel.curRoute.waypoints.size) {
mock.setMockLocation(waypoint[1], waypoint[0]) mock.setMockLocation(waypoint[1], waypoint[0])
Thread.sleep(500) Thread.sleep(1000)
} }
} }
} }
@@ -381,7 +395,7 @@ class MainActivity : ComponentActivity() {
for ((index, step) in routeModel.curLeg.steps.withIndex()) { for ((index, step) in routeModel.curLeg.steps.withIndex()) {
//if (index in 3..3) { //if (index in 3..3) {
for ((windex, waypoint) in step.maneuver.waypoints.withIndex()) { for ((windex, waypoint) in step.maneuver.waypoints.withIndex()) {
routeModel.updateLocation( routeModel.updateLocation(applicationContext,
location(waypoint[0], waypoint[1]), navigationViewModel location(waypoint[0], waypoint[1]), navigationViewModel
) )
val step = routeModel.currentStep() val step = routeModel.currentStep()
@@ -402,7 +416,7 @@ class MainActivity : ComponentActivity() {
if (1 == 1) { if (1 == 1) {
mock.setMockLocation(latitude, longitude) mock.setMockLocation(latitude, longitude)
} else { } else {
routeModel.updateLocation( routeModel.updateLocation(applicationContext,
location(longitude, latitude), navigationViewModel location(longitude, latitude), navigationViewModel
) )
} }

View File

@@ -5,18 +5,24 @@ import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.livedata.observeAsState import androidx.compose.runtime.livedata.observeAsState
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.lifecycle.MutableLiveData import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.window.layout.WindowMetricsCalculator import androidx.window.layout.WindowMetricsCalculator
import com.kouros.navigation.car.ViewStyle import com.kouros.navigation.car.ViewStyle
import com.kouros.navigation.car.map.MapLibre import com.kouros.navigation.car.map.MapLibre
import com.kouros.navigation.car.map.NavigationImage import com.kouros.navigation.car.map.NavigationImage
import com.kouros.navigation.car.map.rememberBaseStyle import com.kouros.navigation.car.map.rememberBaseStyle
import com.kouros.navigation.data.StepData import com.kouros.navigation.data.StepData
import com.kouros.navigation.data.tomtom.TrafficData import com.kouros.navigation.ui.app.AppViewModel
import com.kouros.navigation.ui.app.appViewModel
import org.maplibre.compose.camera.CameraPosition import org.maplibre.compose.camera.CameraPosition
import org.maplibre.compose.camera.rememberCameraState import org.maplibre.compose.camera.rememberCameraState
import org.maplibre.compose.location.LocationTrackingEffect import org.maplibre.compose.location.LocationTrackingEffect
@@ -58,6 +64,10 @@ fun MapView(
) )
val rememberBaseStyle = rememberBaseStyle(baseStyle) val rememberBaseStyle = rememberBaseStyle(baseStyle)
val appViewModel: AppViewModel = appViewModel()
val showBuildings by appViewModel.threedBuilding.collectAsState()
Column { Column {
NavigationInfo(step, nextStep) NavigationInfo(step, nextStep)
Box(contentAlignment = Alignment.Center) { Box(contentAlignment = Alignment.Center) {
@@ -67,7 +77,9 @@ fun MapView(
rememberBaseStyle, rememberBaseStyle,
route, route,
emptyMap(), emptyMap(),
ViewStyle.VIEW ViewStyle.VIEW,
speedCameras = "",
showBuildings
) )
LocationTrackingEffect( LocationTrackingEffect(
locationState = userLocationState, locationState = userLocationState,
@@ -87,6 +99,3 @@ fun MapView(
} }
} }
} }

View File

@@ -2,10 +2,15 @@ package com.kouros.navigation.ui
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.size
import androidx.compose.material3.Card import androidx.compose.material3.Card
import androidx.compose.material3.CardDefaults
import androidx.compose.material3.ElevatedCard
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
@@ -17,39 +22,43 @@ import com.kouros.data.R
import com.kouros.navigation.data.StepData import com.kouros.navigation.data.StepData
import com.kouros.navigation.utils.round import com.kouros.navigation.utils.round
@Composable @Composable
fun NavigationInfo(step: StepData?, nextStep: StepData?) { fun NavigationInfo(step: StepData?, nextStep: StepData?) {
if (step != null && step.instruction.isNotEmpty()) { if (step != null && step.instruction.isNotEmpty()) {
Card(modifier = Modifier.padding(top = 60.dp)) { ElevatedCard(
Column() { elevation = CardDefaults.cardElevation(
Row { defaultElevation = 6.dp
), modifier = Modifier
.padding(top = 60.dp)
.fillMaxWidth()
) {
Column {
Icon( Icon(
painter = painterResource(step.icon), painter = painterResource(step.icon),
contentDescription = stringResource(id = R.string.accept_action_title), contentDescription = stringResource(id = R.string.accept_action_title),
modifier = Modifier.size(48.dp, 48.dp), modifier = Modifier.size(48.dp, 48.dp),
) )
if (step.currentManeuverType == 46 if (step.currentManeuverType == 46
|| step.currentManeuverType == 45) { || step.currentManeuverType == 45
Text(text ="Exit ${step.exitNumber}", fontSize = 20.sp) ) {
Text(text = "Exit ${step.exitNumber}", fontSize = 18.sp)
} }
Column { Row {
if (step.leftStepDistance < 1000) { if (step.leftStepDistance < 1000) {
Text(text = "${step.leftStepDistance.toInt()} m", fontSize = 25.sp) Text(text = "${step.leftStepDistance.toInt()} m", fontSize = 24.sp, color = MaterialTheme.colorScheme.primary)
} else { } else {
Text( Text(
text = "${(step.leftStepDistance / 1000).round(1)} km", text = "${(step.leftStepDistance / 1000).round(1)} km",
fontSize = 25.sp fontSize = 24.sp,
color = MaterialTheme.colorScheme.primary
) )
} }
Text(text = step.instruction, fontSize = 20.sp) Spacer(
} modifier = Modifier.padding(5.dp)
if (nextStep != null && step.icon != nextStep.icon) {
Icon(
painter = painterResource(nextStep.icon),
contentDescription = stringResource(id = R.string.accept_action_title),
modifier = Modifier.size(48.dp, 48.dp),
) )
} Text(text = step.instruction, fontSize = 24.sp, color = MaterialTheme.colorScheme.primary)
} }
} }
} }

View File

@@ -34,7 +34,7 @@ fun NavigationSheet(
val distance = (step.leftDistance / 1000).round(1) val distance = (step.leftDistance / 1000).round(1)
if (step.lane.isNotEmpty()) { if (step.lane.isNotEmpty()) {
routeModel.navState.iconMapper.addLanes( step) // routeModel.navState.iconMapper.addLanes( step)
} }
Column { Column {

View File

@@ -4,10 +4,8 @@ import android.content.Context
import android.location.Location import android.location.Location
import androidx.compose.foundation.clickable import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.size
import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyColumn
@@ -29,25 +27,21 @@ import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.painterResource
import androidx.compose.ui.semantics.isTraversalGroup
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.semantics.traversalIndex
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import com.kouros.data.R import com.kouros.data.R
import com.kouros.navigation.data.Place import com.kouros.navigation.data.Place
import com.kouros.navigation.data.PlaceColor import com.kouros.navigation.data.PlaceColor
import com.kouros.navigation.data.nominatim.SearchResult import com.kouros.navigation.data.nominatim.SearchResult
import com.kouros.navigation.model.ViewModel import com.kouros.navigation.model.NavigationViewModel
import com.kouros.navigation.utils.location import com.kouros.navigation.utils.location
@Composable @Composable
fun SearchSheet( fun SearchSheet(
applicationContext: Context, applicationContext: Context,
viewModel: ViewModel, viewModel: NavigationViewModel,
location: Location, location: Location,
closeSheet: () -> Unit closeSheet: () -> Unit
) { ) {
@@ -88,7 +82,7 @@ fun SearchSheet(
@Composable @Composable
fun Home( fun Home(
applicationContext: Context, applicationContext: Context,
viewModel: ViewModel, viewModel: NavigationViewModel,
location: Location, location: Location,
closeSheet: () -> Unit closeSheet: () -> Unit
) { ) {
@@ -125,7 +119,7 @@ fun SearchBar(
searchPlaces: List<Place>, searchPlaces: List<Place>,
searchResults: List<SearchResult>, searchResults: List<SearchResult>,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
viewModel: ViewModel, viewModel: NavigationViewModel,
context: Context, context: Context,
location: Location, location: Location,
closeSheet: () -> Unit closeSheet: () -> Unit
@@ -166,14 +160,14 @@ fun SearchBar(
} }
} }
private fun searchPlaces(viewModel: ViewModel, location: Location, it: String) { private fun searchPlaces(viewModel: NavigationViewModel, location: Location, it: String) {
viewModel.searchPlaces(it, location) viewModel.searchPlaces(it, location)
} }
@Composable @Composable
private fun SearchPlaces( private fun SearchPlaces(
searchResults: List<SearchResult>, searchResults: List<SearchResult>,
viewModel: ViewModel, viewModel: NavigationViewModel,
context: Context, context: Context,
location: Location, location: Location,
closeSheet: () -> Unit closeSheet: () -> Unit
@@ -222,7 +216,7 @@ private fun SearchPlaces(
@Composable @Composable
private fun RecentPlaces( private fun RecentPlaces(
recentPlaces: List<Place>, recentPlaces: List<Place>,
viewModel: ViewModel, viewModel: NavigationViewModel,
context: Context, context: Context,
location: Location, location: Location,
closeSheet: () -> Unit closeSheet: () -> Unit

View File

@@ -6,6 +6,7 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.size
import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.Button
import androidx.compose.material3.CenterAlignedTopAppBar import androidx.compose.material3.CenterAlignedTopAppBar
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
@@ -19,7 +20,6 @@ import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.navigation.NavHostController import androidx.navigation.NavHostController
import com.alorma.compose.settings.ui.SettingsMenuLink
import com.kouros.data.R import com.kouros.data.R
@OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterial3ExpressiveApi::class) @OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterial3ExpressiveApi::class)
@@ -53,34 +53,16 @@ fun SettingsScreen(navController: NavHostController, navigateBack: () -> Unit) {
.verticalScroll(scrollState) .verticalScroll(scrollState)
.padding(top = padding.calculateTopPadding()), .padding(top = padding.calculateTopPadding()),
) { ) {
SettingsMenuLink( Column(modifier = Modifier.padding(16.dp)) {
title = { Text(text = stringResource(R.string.display_settings)) },
modifier = Modifier, Button(onClick = { navController.navigate("display_settings") }) {
enabled = true, Text(stringResource(R.string.display_settings))
onClick = { navController.navigate("display_settings")}, }
icon = { Button(onClick = { navController.navigate("nav_settings") }) {
Icon( Text(stringResource(R.string.navigation_settings))
painter = painterResource(R.drawable.ic_place_white_24dp),
contentDescription = stringResource(id = R.string.display_settings),
modifier = Modifier.size(48.dp, 48.dp),
)
} }
)
SettingsMenuLink(
title = { Text(text = stringResource(R.string.navigation_settings)) },
modifier = Modifier,
enabled = true,
onClick = { navController.navigate("nav_settings")},
icon = {
Icon(
painter = painterResource(R.drawable.navigation_24px),
contentDescription = stringResource(id = R.string.navigation_settings),
modifier = Modifier.size(48.dp, 48.dp),
)
} }
)
} }
} }
} }

View File

@@ -0,0 +1,27 @@
package com.kouros.navigation.ui.app
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.kouros.navigation.repository.SettingsRepository
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.stateIn
class AppViewModel(
settingsRepository: SettingsRepository
) : ViewModel() {
val darkMode = settingsRepository.darkModeFlow
.stateIn(
viewModelScope,
SharingStarted.Eagerly,
0
)
val threedBuilding = settingsRepository.threedBuildingFlow
.stateIn(
viewModelScope,
SharingStarted.Eagerly,
false
)
}

View File

@@ -0,0 +1,29 @@
package com.kouros.navigation.ui.app
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.platform.LocalContext
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.viewmodel.compose.viewModel
import com.kouros.navigation.data.datastore.DataStoreManager
import com.kouros.navigation.repository.SettingsRepository
@Composable
fun appViewModel(): AppViewModel {
val context = LocalContext.current
val dataStoreManager = remember { DataStoreManager(context) }
val repository = remember { SettingsRepository(dataStoreManager) }
return viewModel(
factory = object : ViewModelProvider.Factory {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
@Suppress("UNCHECKED_CAST")
return AppViewModel(repository) as T
}
}
)
}

View File

@@ -0,0 +1,59 @@
package com.kouros.navigation.ui.components
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.selection.selectable
import androidx.compose.foundation.selection.selectableGroup
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.RadioButton
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.semantics.Role
import androidx.compose.ui.unit.dp
@Composable
fun RadioButtonSingleSelection(
modifier: Modifier = Modifier,
selectedOption: Int,
radioOptions: List<String>,
onClick: (Int) -> Unit,
) {
Column(modifier.selectableGroup()) {
for ((index, text) in radioOptions.withIndex()) {
Row(
Modifier
.fillMaxWidth()
.height(56.dp)
.selectable(
selected = (index == selectedOption),
onClick = {
onClick(index)
},
role = Role.RadioButton
)
.padding(horizontal = 16.dp),
verticalAlignment = Alignment.CenterVertically
) {
RadioButton(
selected = (index == selectedOption),
onClick = null
)
Text(
text = text,
style = MaterialTheme.typography.bodyLarge,
modifier = Modifier.padding(start = 16.dp)
)
}
}
}
}

View File

@@ -0,0 +1,17 @@
package com.kouros.navigation.ui.components
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
@Composable
fun SectionTitle(title: String) {
Text(
text = title,
style = MaterialTheme.typography.titleLarge,
modifier = Modifier.padding(top = 24.dp, bottom = 8.dp)
)
}

View File

@@ -0,0 +1,29 @@
package com.kouros.navigation.ui.components
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
@Composable
fun SettingItem(
title: String,
value: String? = null
) {
Row(
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 12.dp),
horizontalArrangement = Arrangement.SpaceBetween
) {
Text(text = title)
value?.let {
Text(text = it, color = MaterialTheme.colorScheme.primary)
}
}
}

View File

@@ -0,0 +1,37 @@
package com.kouros.navigation.ui.components
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Switch
import androidx.compose.material3.SwitchDefaults
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
@Composable
fun SettingSwitch(
title: String,
checked: Boolean,
onCheckedChange: (Boolean) -> Unit
) {
Row(
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 12.dp),
horizontalArrangement = Arrangement.SpaceBetween
) {
Text(text = title)
Switch(
checked = checked,
onCheckedChange = onCheckedChange,
colors = SwitchDefaults.colors(
checkedThumbColor = Color.White,
uncheckedThumbColor = Color.White
)
)
}
}

View File

@@ -0,0 +1,52 @@
package com.kouros.navigation.ui.components
import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
import com.kouros.navigation.data.NavigationThemeColor
@Composable
fun ThemeColorPicker(
selectedColor: Long,
onColorSelected: (Long) -> Unit
) {
Row(
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 8.dp),
horizontalArrangement = Arrangement.SpaceBetween
) {
NavigationThemeColor.entries.forEach { themeColor ->
val isSelected = selectedColor == themeColor.color
Box(
modifier = Modifier
.size(36.dp)
.clip(CircleShape)
.background(Color(themeColor.color))
.border(
width = if (isSelected) 3.dp else 0.dp,
color = if (isSelected) MaterialTheme.colorScheme.onSurface else Color.Transparent,
shape = CircleShape
)
.clickable {
onColorSelected(themeColor.color)
}
)
}
}
}

View File

@@ -0,0 +1,34 @@
package com.kouros.navigation.ui.navigation
import android.content.Context
import androidx.compose.runtime.Composable
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController
import com.kouros.navigation.ui.MainActivity
import com.kouros.navigation.ui.settings.NavigationScreenSettings
import com.kouros.navigation.ui.SettingsScreen
import com.kouros.navigation.ui.settings.DisplaySettings
import com.kouros.navigation.ui.settings.SettingsRoute
@Composable
fun AppNavGraph(applicationContext: Context, mainActivity: MainActivity) {
val navController = rememberNavController()
NavHost(navController = navController, startDestination = "startScreen") {
composable("startScreen") { mainActivity.StartScreen(navController) }
composable("settings") { SettingsScreen(navController) { navController.popBackStack() } }
composable("settingsxx") {
SettingsRoute("display_settings", navController) { navController.popBackStack() }
}
// old
//composable("display_settings") { DisplaySettings(applicationContext) { navController.popBackStack() } }
//composable("nav_settings") { NavigationScreenSettings(applicationContext) { navController.popBackStack() } }
// new
composable("display_settings") { SettingsRoute("display_settings", navController) { navController.popBackStack() } }
composable("nav_settings") { SettingsRoute("nav_settings", navController) { navController.popBackStack() } }
}
}

View File

@@ -1,4 +1,4 @@
package com.kouros.navigation.ui package com.kouros.navigation.ui.settings
import android.content.Context import android.content.Context
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
@@ -32,13 +32,11 @@ import com.alorma.compose.settings.ui.SettingsRadioButton
import com.alorma.compose.settings.ui.base.internal.LocalSettingsTileColors import com.alorma.compose.settings.ui.base.internal.LocalSettingsTileColors
import com.alorma.compose.settings.ui.base.internal.SettingsTileDefaults import com.alorma.compose.settings.ui.base.internal.SettingsTileDefaults
import com.kouros.data.R import com.kouros.data.R
import com.kouros.navigation.data.Constants.DARK_MODE_SETTINGS import com.kouros.navigation.utils.getSettingsViewModel
import com.kouros.navigation.data.Constants.SHOW_THREED_BUILDING
import com.kouros.navigation.utils.NavigationUtils
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
fun DisplayScreenSettings(context: Context, navigateBack: () -> Unit) { fun DisplaySettings(context: Context, navigateBack: () -> Unit) {
Scaffold( Scaffold(
topBar = { topBar = {
CenterAlignedTopAppBar( CenterAlignedTopAppBar(
@@ -75,13 +73,11 @@ fun DisplayScreenSettings(context: Context, navigateBack: () -> Unit) {
@Composable @Composable
private fun DisplaySettings(context: Context) { private fun DisplaySettings(context: Context) {
val settingsViewModel = getSettingsViewModel(context)
Section(title = "Anzeige") { Section(title = "Anzeige") {
val state = remember { val state = remember {
mutableStateOf( mutableStateOf(
NavigationUtils.getBooleanKeyValue( settingsViewModel.threedBuilding.value
context,
SHOW_THREED_BUILDING
)
) )
} }
SettingsCheckbox( SettingsCheckbox(
@@ -89,17 +85,14 @@ private fun DisplaySettings(context: Context) {
title = { Text(text = stringResource(R.string.threed_building)) }, title = { Text(text = stringResource(R.string.threed_building)) },
onCheckedChange = { onCheckedChange = {
state.value = it state.value = it
NavigationUtils.setBooleanKeyValue(context, it, SHOW_THREED_BUILDING) settingsViewModel.onThreedBuildingChanged(it)
}, },
) )
} }
Section(title = "Dunkles Design") { Section(title = "Dunkles Design") {
val state = remember { val state = remember {
mutableIntStateOf( mutableIntStateOf(
NavigationUtils.getIntKeyValue( settingsViewModel.darkMode.value
context,
DARK_MODE_SETTINGS
)
) )
} }
DarkModeData(context).darkDesign.forEach { sampleItem -> DarkModeData(context).darkDesign.forEach { sampleItem ->
@@ -108,7 +101,7 @@ private fun DisplaySettings(context: Context) {
title = { Text(text = sampleItem.title) }, title = { Text(text = sampleItem.title) },
onClick = { onClick = {
state.intValue = sampleItem.key state.intValue = sampleItem.key
NavigationUtils.setIntKeyValue(context, state.intValue, DARK_MODE_SETTINGS) settingsViewModel.onDarkModeChanged(state.intValue)
}, },
) )
} }

View File

@@ -1,4 +1,4 @@
package com.kouros.navigation.ui package com.kouros.navigation.ui.settings
import android.content.Context import android.content.Context
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
@@ -25,10 +25,10 @@ import com.alorma.compose.settings.ui.SettingsCheckbox
import com.alorma.compose.settings.ui.SettingsRadioButton import com.alorma.compose.settings.ui.SettingsRadioButton
import com.kouros.data.R import com.kouros.data.R
import com.kouros.navigation.data.Constants import com.kouros.navigation.data.Constants
import com.kouros.navigation.data.Constants.DARK_MODE_SETTINGS
import com.kouros.navigation.data.Constants.ROUTING_ENGINE import com.kouros.navigation.data.Constants.ROUTING_ENGINE
import com.kouros.navigation.data.RouteEngine import com.kouros.navigation.data.RouteEngine
import com.kouros.navigation.utils.NavigationUtils import com.kouros.navigation.utils.NavigationUtils
import com.kouros.navigation.utils.getSettingsViewModel
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
@@ -68,13 +68,11 @@ fun NavigationScreenSettings(context: Context, navigateBack: () -> Unit) {
@Composable @Composable
private fun NavigationSettings(context: Context) { private fun NavigationSettings(context: Context) {
val settingsViewModel = getSettingsViewModel(context)
Section(title = stringResource(id = R.string.options)) { Section(title = stringResource(id = R.string.options)) {
val avoidMotorwayState = remember { val avoidMotorwayState = remember {
mutableStateOf( mutableStateOf(
NavigationUtils.getBooleanKeyValue( settingsViewModel.avoidMotorway.value
context,
Constants.AVOID_MOTORWAY
)
) )
} }
SettingsCheckbox( SettingsCheckbox(
@@ -82,16 +80,13 @@ private fun NavigationSettings(context: Context) {
title = { Text(text = stringResource(id = R.string.avoid_highways_row_title)) }, title = { Text(text = stringResource(id = R.string.avoid_highways_row_title)) },
onCheckedChange = { onCheckedChange = {
avoidMotorwayState.value = it avoidMotorwayState.value = it
NavigationUtils.setBooleanKeyValue(context, it, Constants.AVOID_MOTORWAY) settingsViewModel.onAvoidMotorway(it)
}, },
) )
val avoidTollwayState = remember { val avoidTollwayState = remember {
mutableStateOf( mutableStateOf(
NavigationUtils.getBooleanKeyValue( settingsViewModel.avoidTollway.value
context,
Constants.AVOID_TOLLWAY
)
) )
} }
SettingsCheckbox( SettingsCheckbox(
@@ -99,16 +94,13 @@ private fun NavigationSettings(context: Context) {
title = { Text(text = stringResource(id = R.string.avoid_tolls_row_title)) }, title = { Text(text = stringResource(id = R.string.avoid_tolls_row_title)) },
onCheckedChange = { onCheckedChange = {
avoidTollwayState.value = it avoidTollwayState.value = it
NavigationUtils.setBooleanKeyValue(context, it, Constants.AVOID_TOLLWAY) settingsViewModel.onAvoidTollway(it)
}, },
) )
val carLocationState = remember { val carLocationState = remember {
mutableStateOf( mutableStateOf(
NavigationUtils.getBooleanKeyValue( settingsViewModel.carLocation.value
context,
Constants.CAR_LOCATION
)
) )
} }
SettingsCheckbox( SettingsCheckbox(
@@ -116,7 +108,7 @@ private fun NavigationSettings(context: Context) {
title = { Text(text = stringResource(id = R.string.use_car_location)) }, title = { Text(text = stringResource(id = R.string.use_car_location)) },
onCheckedChange = { onCheckedChange = {
carLocationState.value = it carLocationState.value = it
NavigationUtils.setBooleanKeyValue(context, it, Constants.CAR_LOCATION) settingsViewModel.onCarLocation(it)
}, },
) )
} }
@@ -124,10 +116,7 @@ private fun NavigationSettings(context: Context) {
Section(title = stringResource(id = R.string.routing_engine)) { Section(title = stringResource(id = R.string.routing_engine)) {
val state = remember { val state = remember {
mutableIntStateOf( mutableIntStateOf(
NavigationUtils.getIntKeyValue( settingsViewModel.routingEngine.value
context,
ROUTING_ENGINE
)
) )
} }
RoutingEngineData.engines.forEach { sampleItem -> RoutingEngineData.engines.forEach { sampleItem ->
@@ -136,7 +125,7 @@ private fun NavigationSettings(context: Context) {
title = { Text(text = sampleItem.title) }, title = { Text(text = sampleItem.title) },
onClick = { onClick = {
state.intValue = sampleItem.key state.intValue = sampleItem.key
NavigationUtils.setIntKeyValue(context, state.intValue, ROUTING_ENGINE) settingsViewModel.onRoutingEngineChanged(state.intValue)
}, },
) )
} }

View File

@@ -0,0 +1,37 @@
package com.kouros.navigation.ui.settings
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.platform.LocalContext
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavHostController
import com.kouros.navigation.data.datastore.DataStoreManager
import com.kouros.navigation.model.SettingsViewModel
import com.kouros.navigation.repository.SettingsRepository
@Composable
fun SettingsRoute(route: String, navController: NavHostController, function: () -> Boolean) {
val context = LocalContext.current
val dataStoreManager = remember { DataStoreManager(context) }
val repository = remember { SettingsRepository(dataStoreManager) }
val viewModel: SettingsViewModel = viewModel(
factory = object : ViewModelProvider.Factory {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
@Suppress("UNCHECKED_CAST")
return SettingsViewModel(repository) as T
}
}
)
if (route == "display_settings") {
SettingsScreen(viewModel = viewModel)
}
if (route == "nav_settings") {
SettingsScreen(viewModel = viewModel)
}
///DisplaySettings(context, viewModel, navController.popBackStack())
}

View File

@@ -0,0 +1,151 @@
package com.kouros.navigation.ui.settings
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.CenterAlignedTopAppBar
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import com.kouros.data.R
import com.kouros.navigation.model.SettingsViewModel
import com.kouros.navigation.ui.components.RadioButtonSingleSelection
import com.kouros.navigation.ui.components.SectionTitle
import com.kouros.navigation.ui.components.SettingSwitch
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun SettingsScreen(viewModel: SettingsViewModel) {
val darkMode by viewModel.darkMode.collectAsState()
val threedBuilding by viewModel.threedBuilding.collectAsState()
val avoidMotorway by viewModel.avoidMotorway.collectAsState()
val avoidTollway by viewModel.avoidTollway.collectAsState()
val carLocation by viewModel.carLocation.collectAsState()
val routingEngine by viewModel.routingEngine.collectAsState()
Scaffold(
topBar = {
CenterAlignedTopAppBar(
title = {
Text(
stringResource(id = R.string.display_settings),
)
},
navigationIcon = {
// IconButton(onClick = navigateBack) {
// Icon(
// painter = painterResource(R.drawable.arrow_back_24px),
// contentDescription = stringResource(id = R.string.accept_action_title),
// modifier = Modifier.size(48.dp, 48.dp),
// )
// }
},
)
},
)
{ padding ->
val scrollState = rememberScrollState()
Column(
modifier = Modifier
.fillMaxSize()
.padding(top = 20.dp)
.verticalScroll(scrollState)
) {
Text(
text = stringResource(R.string.settings_action_title),
style = MaterialTheme.typography.headlineMedium
)
Spacer(modifier = Modifier.height(24.dp))
// Appearance
SectionTitle(stringResource(R.string.threed_building))
SettingSwitch(
title = stringResource(R.string.threed_building),
checked = threedBuilding,
onCheckedChange = viewModel::onThreedBuildingChanged
)
SectionTitle(stringResource(R.string.dark_mode))
val radioOptions = listOf(
stringResource(R.string.off_action_title),
stringResource(R.string.on_action_title),
stringResource(R.string.use_telephon_settings)
)
RadioButtonSingleSelection(
modifier = Modifier.padding(),
selectedOption = darkMode,
radioOptions = radioOptions,
onClick = viewModel::onDarkModeChanged
)
// Appearance
SectionTitle(stringResource(R.string.navigation_settings))
SettingSwitch(
title = stringResource(R.string.avoid_highways_row_title),
checked = avoidMotorway,
onCheckedChange = viewModel::onAvoidMotorway
)
SettingSwitch(
title = stringResource(R.string.avoid_tolls_row_title),
checked = avoidTollway,
onCheckedChange = viewModel::onAvoidTollway
)
SettingSwitch(
title = stringResource(R.string.use_car_location),
checked = carLocation,
onCheckedChange = viewModel::onCarLocation
)
SectionTitle(stringResource(R.string.routing_engine))
val routingEngineOptions = listOf(
stringResource(R.string.valhalla),
stringResource(R.string.osrm),
stringResource(R.string.tomtom)
)
RadioButtonSingleSelection(
modifier = Modifier.padding(),
selectedOption = routingEngine,
radioOptions = routingEngineOptions,
onClick = viewModel::onRoutingEngineChanged
)
}
}
}

View File

@@ -102,7 +102,7 @@ fun NavigationTheme(
} }
MaterialTheme( MaterialTheme(
colorScheme = colorScheme, colorScheme = if (useDarkTheme) darkColorScheme() else colorScheme,
typography = typography, typography = typography,
content = content, content = content,
shapes = shapes, shapes = shapes,

View File

@@ -56,6 +56,7 @@ dependencies {
implementation(libs.androidx.material3) implementation(libs.androidx.material3)
implementation(libs.androidx.compose.ui.text) implementation(libs.androidx.compose.ui.text)
implementation(libs.play.services.location) implementation(libs.play.services.location)
implementation(libs.androidx.datastore.core)
androidTestImplementation(libs.androidx.junit) androidTestImplementation(libs.androidx.junit)
testImplementation(libs.junit) testImplementation(libs.junit)
} }

View File

@@ -2,6 +2,7 @@ package com.kouros.navigation.car
import android.Manifest import android.Manifest
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.app.Application
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.content.pm.PackageManager import android.content.pm.PackageManager
@@ -26,6 +27,9 @@ import androidx.lifecycle.DefaultLifecycleObserver
import androidx.lifecycle.Lifecycle import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleObserver import androidx.lifecycle.LifecycleObserver
import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.ViewModelStore
import androidx.lifecycle.ViewModelStoreOwner
import androidx.lifecycle.lifecycleScope
import com.kouros.navigation.car.navigation.RouteCarModel import com.kouros.navigation.car.navigation.RouteCarModel
import com.kouros.navigation.car.screen.NavigationScreen import com.kouros.navigation.car.screen.NavigationScreen
import com.kouros.navigation.car.screen.RequestPermissionScreen import com.kouros.navigation.car.screen.RequestPermissionScreen
@@ -35,13 +39,20 @@ import com.kouros.navigation.data.Constants.MAXIMAL_ROUTE_DEVIATION
import com.kouros.navigation.data.Constants.MAXIMAL_SNAP_CORRECTION import com.kouros.navigation.data.Constants.MAXIMAL_SNAP_CORRECTION
import com.kouros.navigation.data.Constants.TAG import com.kouros.navigation.data.Constants.TAG
import com.kouros.navigation.data.RouteEngine 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.osrm.OsrmRepository
import com.kouros.navigation.data.tomtom.TomTomRepository import com.kouros.navigation.data.tomtom.TomTomRepository
import com.kouros.navigation.data.valhalla.ValhallaRepository import com.kouros.navigation.data.valhalla.ValhallaRepository
import com.kouros.navigation.model.ViewModel import com.kouros.navigation.model.NavigationViewModel
import com.kouros.navigation.utils.GeoUtils.snapLocation import com.kouros.navigation.utils.GeoUtils.snapLocation
import com.kouros.navigation.utils.NavigationUtils.getBooleanKeyValue
import com.kouros.navigation.utils.NavigationUtils.getViewModel import com.kouros.navigation.utils.NavigationUtils.getViewModel
import com.kouros.navigation.utils.getSettingsRepository
import com.kouros.navigation.utils.getSettingsViewModel
import kotlinx.coroutines.awaitCancellation
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
class NavigationSession : Session(), NavigationScreen.Listener { class NavigationSession : Session(), NavigationScreen.Listener {
@@ -54,7 +65,8 @@ class NavigationSession : Session(), NavigationScreen.Listener {
lateinit var surfaceRenderer: SurfaceRenderer lateinit var surfaceRenderer: SurfaceRenderer
var mLocationListener: LocationListenerCompat = LocationListenerCompat { location: Location? -> var mLocationListener: LocationListenerCompat = LocationListenerCompat { location: Location? ->
val useCarLocation = getBooleanKeyValue(carContext, CAR_LOCATION) val repository = getSettingsRepository(carContext)
val useCarLocation = runBlocking { repository.carLocationFlow.first() }
if (!useCarLocation) { if (!useCarLocation) {
updateLocation(location!!) updateLocation(location!!)
} }
@@ -75,7 +87,8 @@ class NavigationSession : Session(), NavigationScreen.Listener {
override fun onDestroy(owner: LifecycleOwner) { override fun onDestroy(owner: LifecycleOwner) {
val carInfo = carContext.getCarService(CarHardwareManager::class.java).carInfo val carInfo = carContext.getCarService(CarHardwareManager::class.java).carInfo
val useCarLocation = getBooleanKeyValue(carContext, CAR_LOCATION) val repository = getSettingsRepository(carContext)
val useCarLocation = runBlocking { repository.carLocationFlow.first() }
if (useCarLocation) { if (useCarLocation) {
val carSensors = carContext.getCarService(CarHardwareManager::class.java).carSensors val carSensors = carContext.getCarService(CarHardwareManager::class.java).carSensors
carSensors.removeCarHardwareLocationListener(carLocationListener) carSensors.removeCarHardwareLocationListener(carLocationListener)
@@ -88,8 +101,9 @@ class NavigationSession : Session(), NavigationScreen.Listener {
} }
} }
lateinit var navigationViewModel: ViewModel lateinit var navigationViewModel: NavigationViewModel
lateinit var viewModelStoreOwner : ViewModelStoreOwner
val carLocationListener: OnCarDataAvailableListener<CarHardwareLocation?> = val carLocationListener: OnCarDataAvailableListener<CarHardwareLocation?> =
OnCarDataAvailableListener { data -> OnCarDataAvailableListener { data ->
if (data.location.status == CarValue.STATUS_SUCCESS) { if (data.location.status == CarValue.STATUS_SUCCESS) {
@@ -123,22 +137,39 @@ class NavigationSession : Session(), NavigationScreen.Listener {
fun onRoutingEngineStateUpdated(routeEngine : Int) { fun onRoutingEngineStateUpdated(routeEngine : Int) {
navigationViewModel = when (routeEngine) { navigationViewModel = when (routeEngine) {
RouteEngine.VALHALLA.ordinal -> ViewModel(ValhallaRepository()) RouteEngine.VALHALLA.ordinal -> NavigationViewModel(ValhallaRepository())
RouteEngine.OSRM.ordinal -> ViewModel(OsrmRepository()) RouteEngine.OSRM.ordinal -> NavigationViewModel(OsrmRepository())
else -> ViewModel(TomTomRepository()) else -> NavigationViewModel(TomTomRepository())
} }
} }
override fun onCreateScreen(intent: Intent): Screen { override fun onCreateScreen(intent: Intent): Screen {
viewModelStoreOwner = object : ViewModelStoreOwner {
override val viewModelStore = ViewModelStore()
}
lifecycleScope.launch {
try {
awaitCancellation()
} finally {
viewModelStoreOwner.viewModelStore.clear()
}
}
// lifecycleScope.launch {
//}
navigationViewModel = getViewModel(carContext) navigationViewModel = getViewModel(carContext)
navigationViewModel.routingEngine.observe(this, ::onRoutingEngineStateUpdated) navigationViewModel.routingEngine.observe(this, ::onRoutingEngineStateUpdated)
routeModel = RouteCarModel() routeModel = RouteCarModel()
surfaceRenderer = SurfaceRenderer(carContext, lifecycle, routeModel) surfaceRenderer = SurfaceRenderer(carContext, lifecycle, routeModel, viewModelStoreOwner)
navigationScreen = navigationScreen =
NavigationScreen(carContext, surfaceRenderer, routeModel, this, navigationViewModel) NavigationScreen(carContext, surfaceRenderer, routeModel, this, navigationViewModel)
@@ -175,7 +206,8 @@ class NavigationSession : Session(), NavigationScreen.Listener {
fun addSensors() { fun addSensors() {
val carInfo = carContext.getCarService(CarHardwareManager::class.java).carInfo val carInfo = carContext.getCarService(CarHardwareManager::class.java).carInfo
val useCarLocation = getBooleanKeyValue(carContext, CAR_LOCATION) val repository = getSettingsRepository(carContext)
val useCarLocation = runBlocking { repository.carLocationFlow.first() }
if (useCarLocation) { if (useCarLocation) {
val carSensors = carContext.getCarService(CarHardwareManager::class.java).carSensors val carSensors = carContext.getCarService(CarHardwareManager::class.java).carSensors
carSensors.addCompassListener(CarSensors.UPDATE_RATE_NORMAL, carSensors.addCompassListener(CarSensors.UPDATE_RATE_NORMAL,
@@ -269,8 +301,8 @@ class NavigationSession : Session(), NavigationScreen.Listener {
} }
} }
override fun stopNavigation() { override fun stopNavigation(context: CarContext) {
routeModel.stopNavigation() routeModel.stopNavigation(context)
} }

View File

@@ -14,6 +14,7 @@ import androidx.car.app.connection.CarConnection
import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.livedata.observeAsState import androidx.compose.runtime.livedata.observeAsState
import androidx.compose.ui.platform.ComposeView import androidx.compose.ui.platform.ComposeView
@@ -21,6 +22,7 @@ import androidx.lifecycle.DefaultLifecycleObserver
import androidx.lifecycle.Lifecycle import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.MutableLiveData import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModelStoreOwner
import androidx.lifecycle.setViewTreeLifecycleOwner import androidx.lifecycle.setViewTreeLifecycleOwner
import androidx.savedstate.setViewTreeSavedStateRegistryOwner import androidx.savedstate.setViewTreeSavedStateRegistryOwner
import com.kouros.navigation.car.map.DrawNavigationImages import com.kouros.navigation.car.map.DrawNavigationImages
@@ -29,21 +31,23 @@ import com.kouros.navigation.car.map.cameraState
import com.kouros.navigation.car.map.getPaddingValues import com.kouros.navigation.car.map.getPaddingValues
import com.kouros.navigation.car.map.rememberBaseStyle import com.kouros.navigation.car.map.rememberBaseStyle
import com.kouros.navigation.car.navigation.RouteCarModel import com.kouros.navigation.car.navigation.RouteCarModel
import com.kouros.navigation.data.Constants
import com.kouros.navigation.data.Constants.ROUTING_ENGINE import com.kouros.navigation.data.Constants.ROUTING_ENGINE
import com.kouros.navigation.data.Constants.homeVogelhart import com.kouros.navigation.data.Constants.homeVogelhart
import com.kouros.navigation.data.ObjectBox import com.kouros.navigation.data.ObjectBox
import com.kouros.navigation.data.RouteEngine import com.kouros.navigation.data.RouteEngine
import com.kouros.navigation.data.tomtom.TrafficData
import com.kouros.navigation.model.BaseStyleModel import com.kouros.navigation.model.BaseStyleModel
import com.kouros.navigation.model.RouteModel import com.kouros.navigation.model.RouteModel
import com.kouros.navigation.utils.NavigationUtils.getIntKeyValue
import com.kouros.navigation.utils.bearing import com.kouros.navigation.utils.bearing
import com.kouros.navigation.utils.calculateTilt import com.kouros.navigation.utils.calculateTilt
import com.kouros.navigation.utils.calculateZoom import com.kouros.navigation.utils.calculateZoom
import com.kouros.navigation.utils.duration import com.kouros.navigation.utils.duration
import com.kouros.navigation.utils.getSettingsRepository
import com.kouros.navigation.utils.getSettingsViewModel
import com.kouros.navigation.utils.location import com.kouros.navigation.utils.location
import com.kouros.navigation.utils.previewZoom import com.kouros.navigation.utils.previewZoom
import com.kouros.navigation.utils.settingsViewModel
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.runBlocking
import org.maplibre.compose.camera.CameraPosition import org.maplibre.compose.camera.CameraPosition
import org.maplibre.compose.camera.CameraState import org.maplibre.compose.camera.CameraState
import org.maplibre.compose.style.BaseStyle import org.maplibre.compose.style.BaseStyle
@@ -52,7 +56,8 @@ import org.maplibre.spatialk.geojson.Position
class SurfaceRenderer( class SurfaceRenderer(
private var carContext: CarContext, lifecycle: Lifecycle, private var carContext: CarContext, lifecycle: Lifecycle,
private var routeModel: RouteCarModel private var routeModel: RouteCarModel,
private var viewModelStoreOwner: ViewModelStoreOwner
) : DefaultLifecycleObserver { ) : DefaultLifecycleObserver {
var lastLocation = location(0.0, 0.0) var lastLocation = location(0.0, 0.0)
@@ -187,9 +192,12 @@ class SurfaceRenderer(
@Composable @Composable
fun MapView() { fun MapView() {
val darkMode = getIntKeyValue(carContext, Constants.DARK_MODE_SETTINGS)
val baseStyle = BaseStyleModel().readStyle(carContext, darkMode, carContext.isDarkMode)
//val appViewModel: AppViewModel = appViewModel(viewModelStoreOwner)
//val darkMode by appViewModel.darkMode.collectAsState()
val darkMode = settingsViewModel(carContext, viewModelStoreOwner).darkMode.collectAsState().value
val baseStyle = BaseStyleModel().readStyle(carContext, darkMode, carContext.isDarkMode)
val position: CameraPosition? by cameraPosition.observeAsState() val position: CameraPosition? by cameraPosition.observeAsState()
val route: String? by routeData.observeAsState() val route: String? by routeData.observeAsState()
val traffic: Map<String, String> ? by trafficData.observeAsState() val traffic: Map<String, String> ? by trafficData.observeAsState()
@@ -197,7 +205,17 @@ class SurfaceRenderer(
val paddingValues = getPaddingValues(height, viewStyle) val paddingValues = getPaddingValues(height, viewStyle)
val cameraState = cameraState(paddingValues, position, tilt) val cameraState = cameraState(paddingValues, position, tilt)
val rememberBaseStyle = rememberBaseStyle(baseStyle) val rememberBaseStyle = rememberBaseStyle(baseStyle)
MapLibre(carContext, cameraState, rememberBaseStyle, route, traffic, viewStyle, speedCameras)
MapLibre(
carContext,
cameraState,
rememberBaseStyle,
route,
traffic,
viewStyle,
speedCameras,
false
)
ShowPosition(cameraState, position, paddingValues) ShowPosition(cameraState, position, paddingValues)
} }
@@ -339,7 +357,8 @@ class SurfaceRenderer(
} }
fun updateCarLocation(location: Location) { fun updateCarLocation(location: Location) {
val routingEngine = getIntKeyValue(carContext, ROUTING_ENGINE) val repository = getSettingsRepository(carContext)
val routingEngine = runBlocking { repository.routingEngineFlow.first() }
if (routingEngine == RouteEngine.OSRM.ordinal) { if (routingEngine == RouteEngine.OSRM.ordinal) {
updateLocation(location) updateLocation(location)
} }

View File

@@ -26,15 +26,18 @@ import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.rememberTextMeasurer import androidx.compose.ui.text.rememberTextMeasurer
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import com.kouros.data.R import com.kouros.data.R
import com.kouros.navigation.car.ViewStyle import com.kouros.navigation.car.ViewStyle
import com.kouros.navigation.data.Constants import com.kouros.navigation.data.Constants
import com.kouros.navigation.data.Constants.SHOW_THREED_BUILDING
import com.kouros.navigation.data.NavigationColor import com.kouros.navigation.data.NavigationColor
import com.kouros.navigation.data.RouteColor import com.kouros.navigation.data.RouteColor
import com.kouros.navigation.data.SpeedColor import com.kouros.navigation.data.SpeedColor
import com.kouros.navigation.data.datastore.DataStoreManager
import com.kouros.navigation.model.RouteModel import com.kouros.navigation.model.RouteModel
import com.kouros.navigation.utils.NavigationUtils.getBooleanKeyValue import com.kouros.navigation.model.SettingsViewModel
import com.kouros.navigation.repository.SettingsRepository
import org.maplibre.compose.camera.CameraPosition import org.maplibre.compose.camera.CameraPosition
import org.maplibre.compose.camera.CameraState import org.maplibre.compose.camera.CameraState
import org.maplibre.compose.camera.rememberCameraState import org.maplibre.compose.camera.rememberCameraState
@@ -92,7 +95,8 @@ fun MapLibre(
route: String?, route: String?,
traffic: Map<String, String>?, traffic: Map<String, String>?,
viewStyle: ViewStyle, viewStyle: ViewStyle,
speedCameras: String? = "" speedCameras: String? = "",
showBuildings: Boolean
) { ) {
MaplibreMap( MaplibreMap(
options = MapOptions( options = MapOptions(
@@ -103,7 +107,7 @@ fun MapLibre(
baseStyle = baseStyle baseStyle = baseStyle
) { ) {
getBaseSource(id = "openmaptiles")?.let { tiles -> getBaseSource(id = "openmaptiles")?.let { tiles ->
if (!getBooleanKeyValue(context = context, SHOW_THREED_BUILDING)) { if (!showBuildings) {
BuildingLayer(tiles) BuildingLayer(tiles)
} }
if (viewStyle == ViewStyle.AMENITY_VIEW) { if (viewStyle == ViewStyle.AMENITY_VIEW) {
@@ -553,3 +557,4 @@ fun PuckState(cameraState: CameraState, userLocationState: UserLocationState) {
) )
} }

View File

@@ -1,6 +1,5 @@
package com.kouros.navigation.car.screen package com.kouros.navigation.car.screen
import android.location.Location
import androidx.car.app.CarContext import androidx.car.app.CarContext
import androidx.car.app.Screen import androidx.car.app.Screen
import androidx.car.app.model.Action import androidx.car.app.model.Action
@@ -14,17 +13,16 @@ import androidx.core.graphics.drawable.IconCompat
import com.kouros.data.R import com.kouros.data.R
import com.kouros.navigation.car.SurfaceRenderer import com.kouros.navigation.car.SurfaceRenderer
import com.kouros.navigation.car.ViewStyle import com.kouros.navigation.car.ViewStyle
import com.kouros.navigation.car.navigation.RouteCarModel
import com.kouros.navigation.data.Category import com.kouros.navigation.data.Category
import com.kouros.navigation.data.Constants.CHARGING_STATION import com.kouros.navigation.data.Constants.CHARGING_STATION
import com.kouros.navigation.data.Constants.FUEL_STATION import com.kouros.navigation.data.Constants.FUEL_STATION
import com.kouros.navigation.data.Constants.PHARMACY import com.kouros.navigation.data.Constants.PHARMACY
import com.kouros.navigation.model.ViewModel import com.kouros.navigation.model.NavigationViewModel
class CategoriesScreen( class CategoriesScreen(
private val carContext: CarContext, private val carContext: CarContext,
private val surfaceRenderer: SurfaceRenderer, private val surfaceRenderer: SurfaceRenderer,
private val viewModel: ViewModel, private val navigationViewModel: NavigationViewModel,
) : Screen(carContext) { ) : Screen(carContext) {
var categories: List<Category> = listOf( var categories: List<Category> = listOf(
@@ -48,7 +46,7 @@ class CategoriesScreen(
carContext, carContext,
surfaceRenderer, surfaceRenderer,
it.id, it.id,
viewModel navigationViewModel
) )
) { obj: Any? -> ) { obj: Any? ->
if (obj != null) { if (obj != null) {

View File

@@ -1,6 +1,5 @@
package com.kouros.navigation.car.screen package com.kouros.navigation.car.screen
import android.location.Location
import androidx.annotation.DrawableRes import androidx.annotation.DrawableRes
import androidx.car.app.CarContext import androidx.car.app.CarContext
import androidx.car.app.Screen import androidx.car.app.Screen
@@ -19,11 +18,10 @@ import androidx.lifecycle.Observer
import com.kouros.data.R import com.kouros.data.R
import com.kouros.navigation.car.SurfaceRenderer import com.kouros.navigation.car.SurfaceRenderer
import com.kouros.navigation.car.navigation.NavigationMessage import com.kouros.navigation.car.navigation.NavigationMessage
import com.kouros.navigation.car.navigation.RouteCarModel
import com.kouros.navigation.data.Constants import com.kouros.navigation.data.Constants
import com.kouros.navigation.data.Place import com.kouros.navigation.data.Place
import com.kouros.navigation.data.overpass.Elements import com.kouros.navigation.data.overpass.Elements
import com.kouros.navigation.model.ViewModel import com.kouros.navigation.model.NavigationViewModel
import com.kouros.navigation.utils.GeoUtils.createPointCollection import com.kouros.navigation.utils.GeoUtils.createPointCollection
import com.kouros.navigation.utils.location import com.kouros.navigation.utils.location
import com.kouros.navigation.utils.round import com.kouros.navigation.utils.round
@@ -33,7 +31,7 @@ class CategoryScreen(
private val carContext: CarContext, private val carContext: CarContext,
private val surfaceRenderer: SurfaceRenderer, private val surfaceRenderer: SurfaceRenderer,
private val category: String, private val category: String,
private val viewModel: ViewModel, private val navigationViewModel: NavigationViewModel,
) : Screen(carContext) { ) : Screen(carContext) {
var elements = listOf<Elements>() var elements = listOf<Elements>()
@@ -57,8 +55,8 @@ class CategoryScreen(
} }
init { init {
viewModel.elements.observe(this, observer) navigationViewModel.elements.observe(this, observer)
viewModel.getAmenities(category, surfaceRenderer.lastLocation) navigationViewModel.getAmenities(category, surfaceRenderer.lastLocation)
} }
@@ -130,7 +128,7 @@ class CategoryScreen(
row.addAction( row.addAction(
Action.Builder() Action.Builder()
.setOnClickListener { .setOnClickListener {
viewModel.loadRoute( navigationViewModel.loadRoute(
carContext, carContext,
currentLocation = surfaceRenderer.lastLocation, currentLocation = surfaceRenderer.lastLocation,
location(it.lon!!, it.lat!!), location(it.lon!!, it.lat!!),

View File

@@ -10,23 +10,27 @@ import androidx.car.app.model.ListTemplate
import androidx.car.app.model.Row import androidx.car.app.model.Row
import androidx.car.app.model.SectionedItemList import androidx.car.app.model.SectionedItemList
import androidx.car.app.model.Template import androidx.car.app.model.Template
import androidx.lifecycle.lifecycleScope
import com.kouros.data.R import com.kouros.data.R
import com.kouros.navigation.data.Constants.DARK_MODE_SETTINGS import com.kouros.navigation.utils.getSettingsViewModel
import com.kouros.navigation.data.Constants.SHOW_THREED_BUILDING import kotlinx.coroutines.launch
import com.kouros.navigation.utils.NavigationUtils.getBooleanKeyValue
import com.kouros.navigation.utils.NavigationUtils.getIntKeyValue
import com.kouros.navigation.utils.NavigationUtils.setBooleanKeyValue
import com.kouros.navigation.utils.NavigationUtils.setIntKeyValue
class DarkModeSettings(private val carContext: CarContext) : Screen(carContext) { class DarkModeSettings(private val carContext: CarContext) : Screen(carContext) {
private var darkModeSettings = 0 private var darkModeSettings = 0
val settingsViewModel = getSettingsViewModel(carContext)
init { init {
darkModeSettings = getIntKeyValue(carContext, DARK_MODE_SETTINGS) lifecycleScope.launch {
settingsViewModel.darkMode.collect {
invalidate()
}
}
} }
override fun onGetTemplate(): Template { override fun onGetTemplate(): Template {
darkModeSettings = settingsViewModel.darkMode.value
val templateBuilder = ListTemplate.Builder() val templateBuilder = ListTemplate.Builder()
val radioList = val radioList =
ItemList.Builder() ItemList.Builder()
@@ -52,10 +56,12 @@ class DarkModeSettings(private val carContext: CarContext) : Screen(carContext)
.build() .build()
return templateBuilder return templateBuilder
.addSectionedList(SectionedItemList.create( .addSectionedList(
SectionedItemList.create(
radioList, radioList,
carContext.getString(R.string.dark_mode) carContext.getString(R.string.dark_mode)
)) )
)
.setHeader( .setHeader(
Header.Builder() Header.Builder()
.setTitle(carContext.getString(R.string.dark_mode)) .setTitle(carContext.getString(R.string.dark_mode))
@@ -67,7 +73,7 @@ class DarkModeSettings(private val carContext: CarContext) : Screen(carContext)
private fun onSelected(index: Int) { private fun onSelected(index: Int) {
setIntKeyValue(carContext, index, DARK_MODE_SETTINGS) settingsViewModel.onDarkModeChanged(index)
CarToast.makeText( CarToast.makeText(
carContext, carContext,
(carContext (carContext

View File

@@ -6,33 +6,34 @@ import androidx.car.app.model.Action
import androidx.car.app.model.Header import androidx.car.app.model.Header
import androidx.car.app.model.ItemList import androidx.car.app.model.ItemList
import androidx.car.app.model.ListTemplate import androidx.car.app.model.ListTemplate
import androidx.car.app.model.OnClickListener
import androidx.car.app.model.Row import androidx.car.app.model.Row
import androidx.car.app.model.Template import androidx.car.app.model.Template
import androidx.car.app.model.Toggle import androidx.car.app.model.Toggle
import androidx.lifecycle.lifecycleScope
import com.kouros.data.R import com.kouros.data.R
import com.kouros.navigation.data.Constants.SHOW_THREED_BUILDING import com.kouros.navigation.utils.getSettingsViewModel
import com.kouros.navigation.utils.NavigationUtils.getBooleanKeyValue import kotlinx.coroutines.launch
import com.kouros.navigation.utils.NavigationUtils.setBooleanKeyValue
class DisplaySettings(private val carContext: CarContext) : Screen(carContext) { class DisplaySettings(private val carContext: CarContext) : Screen(carContext) {
private var buildingToggleState = false private var buildingToggleState = false
val settingsViewModel = getSettingsViewModel(carContext)
init { init {
buildingToggleState = getBooleanKeyValue(carContext, SHOW_THREED_BUILDING) lifecycleScope.launch {
settingsViewModel.threedBuilding.collect {
invalidate()
}
}
} }
override fun onGetTemplate(): Template { override fun onGetTemplate(): Template {
buildingToggleState = settingsViewModel.threedBuilding.value
val listBuilder = ItemList.Builder() val listBuilder = ItemList.Builder()
val buildingToggle: Toggle = val buildingToggle: Toggle =
Toggle.Builder { checked: Boolean -> Toggle.Builder { checked: Boolean ->
if (checked) { settingsViewModel.onThreedBuildingChanged(checked)
setBooleanKeyValue(carContext, true, SHOW_THREED_BUILDING)
} else {
setBooleanKeyValue(carContext, false, SHOW_THREED_BUILDING)
}
buildingToggleState = !buildingToggleState buildingToggleState = !buildingToggleState
}.setChecked(buildingToggleState).build() }.setChecked(buildingToggleState).build()
listBuilder.addItem(buildRowForTemplate(R.string.threed_building, buildingToggle)) listBuilder.addItem(buildRowForTemplate(R.string.threed_building, buildingToggle))

View File

@@ -22,6 +22,7 @@ import androidx.car.app.navigation.model.NavigationTemplate
import androidx.car.app.navigation.model.RoutingInfo import androidx.car.app.navigation.model.RoutingInfo
import androidx.core.graphics.drawable.IconCompat import androidx.core.graphics.drawable.IconCompat
import androidx.lifecycle.Observer import androidx.lifecycle.Observer
import androidx.lifecycle.lifecycleScope
import com.kouros.data.R import com.kouros.data.R
import com.kouros.navigation.car.SurfaceRenderer import com.kouros.navigation.car.SurfaceRenderer
import com.kouros.navigation.car.ViewStyle import com.kouros.navigation.car.ViewStyle
@@ -29,11 +30,18 @@ import com.kouros.navigation.car.navigation.RouteCarModel
import com.kouros.navigation.data.Constants import com.kouros.navigation.data.Constants
import com.kouros.navigation.data.Constants.DESTINATION_ARRIVAL_DISTANCE import com.kouros.navigation.data.Constants.DESTINATION_ARRIVAL_DISTANCE
import com.kouros.navigation.data.Place import com.kouros.navigation.data.Place
import com.kouros.navigation.data.datastore.DataStoreManager
import com.kouros.navigation.data.nominatim.SearchResult import com.kouros.navigation.data.nominatim.SearchResult
import com.kouros.navigation.data.overpass.Elements import com.kouros.navigation.data.overpass.Elements
import com.kouros.navigation.model.ViewModel import com.kouros.navigation.model.NavigationViewModel
import com.kouros.navigation.repository.SettingsRepository
import com.kouros.navigation.utils.GeoUtils import com.kouros.navigation.utils.GeoUtils
import com.kouros.navigation.utils.getSettingsViewModel
import com.kouros.navigation.utils.location import com.kouros.navigation.utils.location
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import java.time.Duration
import java.time.LocalDateTime import java.time.LocalDateTime
import java.time.ZoneOffset import java.time.ZoneOffset
import kotlin.math.absoluteValue import kotlin.math.absoluteValue
@@ -43,21 +51,21 @@ class NavigationScreen(
private var surfaceRenderer: SurfaceRenderer, private var surfaceRenderer: SurfaceRenderer,
private var routeModel: RouteCarModel, private var routeModel: RouteCarModel,
private var listener: Listener, private var listener: Listener,
private val viewModel: ViewModel private val navigationViewModel: NavigationViewModel
) : ) :
Screen(carContext) { Screen(carContext) {
/** A listener for navigation start and stop signals. */ /** A listener for navigation start and stop signals. */
interface Listener { interface Listener {
/** Stops navigation. */ /** Stops navigation. */
fun stopNavigation() fun stopNavigation(context: CarContext)
} }
var currentNavigationLocation = Location(LocationManager.GPS_PROVIDER) var currentNavigationLocation = Location(LocationManager.GPS_PROVIDER)
var recentPlace = Place() var recentPlace = Place()
var navigationType = NavigationType.VIEW var navigationType = NavigationType.VIEW
var lastTrafficDate = LocalDateTime.of(1960, 6, 21, 0, 0) var lastTrafficDate: LocalDateTime? = LocalDateTime.of(1960, 6, 21, 0, 0)
val observer = Observer<String> { route -> val observer = Observer<String> { route ->
if (route.isNotEmpty()) { if (route.isNotEmpty()) {
navigationType = NavigationType.NAVIGATION navigationType = NavigationType.NAVIGATION
@@ -99,7 +107,7 @@ class NavigationScreen(
speedCameras = cameras speedCameras = cameras
val coordinates = mutableListOf<List<Double>>() val coordinates = mutableListOf<List<Double>>()
cameras.forEach { cameras.forEach {
coordinates.add(listOf(it.lon!!, it.lat!!)) coordinates.add(listOf(it.lon, it.lat))
} }
val speedData = GeoUtils.createPointCollection(coordinates, "radar") val speedData = GeoUtils.createPointCollection(coordinates, "radar")
surfaceRenderer.speedCamerasData.value = speedData surfaceRenderer.speedCamerasData.value = speedData
@@ -107,11 +115,15 @@ class NavigationScreen(
init { init {
viewModel.route.observe(this, observer) navigationViewModel.route.observe(this, observer)
viewModel.traffic.observe(this, trafficObserver); navigationViewModel.traffic.observe(this, trafficObserver);
viewModel.recentPlace.observe(this, recentObserver) navigationViewModel.recentPlace.observe(this, recentObserver)
viewModel.placeLocation.observe(this, placeObserver) navigationViewModel.placeLocation.observe(this, placeObserver)
viewModel.speedCameras.observe(this, speedObserver) navigationViewModel.speedCameras.observe(this, speedObserver)
lifecycleScope.launch {
getSettingsViewModel(carContext).routingEngine.collect {
}
}
} }
override fun onGetTemplate(): Template { override fun onGetTemplate(): Template {
@@ -306,7 +318,7 @@ class NavigationScreen(
) )
.setOnClickListener { .setOnClickListener {
val navigateTo = location(recentPlace.longitude, recentPlace.latitude) val navigateTo = location(recentPlace.longitude, recentPlace.latitude)
viewModel.loadRoute( navigationViewModel.loadRoute(
carContext, carContext,
surfaceRenderer.lastLocation, surfaceRenderer.lastLocation,
navigateTo, navigateTo,
@@ -349,7 +361,7 @@ class NavigationScreen(
return Action.Builder() return Action.Builder()
.setIcon(routeModel.createCarIcon(carContext, R.drawable.settings_48px)) .setIcon(routeModel.createCarIcon(carContext, R.drawable.settings_48px))
.setOnClickListener { .setOnClickListener {
screenManager.push(SettingsScreen(carContext, viewModel)) screenManager.push(SettingsScreen(carContext, navigationViewModel))
} }
.build() .build()
} }
@@ -411,13 +423,13 @@ class NavigationScreen(
SearchScreen( SearchScreen(
carContext, carContext,
surfaceRenderer, surfaceRenderer,
viewModel navigationViewModel
) )
) { obj: Any? -> ) { obj: Any? ->
if (obj != null) { if (obj != null) {
val place = obj as Place val place = obj as Place
if (place.longitude == 0.0) { if (place.longitude == 0.0) {
viewModel.findAddress( navigationViewModel.findAddress(
"${obj.city} ${obj.street}},", "${obj.city} ${obj.street}},",
currentNavigationLocation currentNavigationLocation
) )
@@ -432,9 +444,9 @@ class NavigationScreen(
fun navigateToPlace(place: Place) { fun navigateToPlace(place: Place) {
navigationType = NavigationType.VIEW navigationType = NavigationType.VIEW
val location = location(place.longitude, place.latitude) val location = location(place.longitude, place.latitude)
viewModel.saveRecent(place) navigationViewModel.saveRecent(place)
currentNavigationLocation = location currentNavigationLocation = location
viewModel.loadRoute( navigationViewModel.loadRoute(
carContext, carContext,
surfaceRenderer.lastLocation, surfaceRenderer.lastLocation,
location, location,
@@ -446,7 +458,7 @@ class NavigationScreen(
fun stopNavigation() { fun stopNavigation() {
navigationType = NavigationType.VIEW navigationType = NavigationType.VIEW
listener.stopNavigation() listener.stopNavigation(carContext)
surfaceRenderer.routeData.value = "" surfaceRenderer.routeData.value = ""
lastCameraSearch = 0 lastCameraSearch = 0
invalidate() invalidate()
@@ -470,7 +482,7 @@ class NavigationScreen(
fun reRoute(destination: Place) { fun reRoute(destination: Place) {
val dest = location(destination.longitude, destination.latitude) val dest = location(destination.longitude, destination.latitude)
viewModel.loadRoute( navigationViewModel.loadRoute(
carContext, carContext,
surfaceRenderer.lastLocation, surfaceRenderer.lastLocation,
dest, dest,
@@ -480,14 +492,14 @@ class NavigationScreen(
fun updateTrip(location: Location) { fun updateTrip(location: Location) {
val current = LocalDateTime.now(ZoneOffset.UTC) val current = LocalDateTime.now(ZoneOffset.UTC)
val duration = java.time.Duration.between(current, lastTrafficDate) val duration = Duration.between(current, lastTrafficDate)
if (duration.abs().seconds > 360) { if (duration.abs().seconds > 360) {
lastTrafficDate = current lastTrafficDate = current
viewModel.loadTraffic(carContext, location, surfaceRenderer.carOrientation) navigationViewModel.loadTraffic(carContext, location, surfaceRenderer.carOrientation)
} }
updateSpeedCamera(location) updateSpeedCamera(location)
with(routeModel) { with(routeModel) {
updateLocation(location, viewModel) updateLocation(carContext,location, navigationViewModel)
if ((navState.maneuverType == Maneuver.TYPE_DESTINATION if ((navState.maneuverType == Maneuver.TYPE_DESTINATION
|| navState.maneuverType == Maneuver.TYPE_DESTINATION_LEFT || navState.maneuverType == Maneuver.TYPE_DESTINATION_LEFT
|| navState.maneuverType == Maneuver.TYPE_DESTINATION_RIGHT || navState.maneuverType == Maneuver.TYPE_DESTINATION_RIGHT
@@ -506,7 +518,7 @@ class NavigationScreen(
private fun updateSpeedCamera(location: Location) { private fun updateSpeedCamera(location: Location) {
if (lastCameraSearch++ % 100 == 0) { if (lastCameraSearch++ % 100 == 0) {
viewModel.getSpeedCameras(location, 5.0) navigationViewModel.getSpeedCameras(location, 5.0)
} }
if (speedCameras.isNotEmpty()) { if (speedCameras.isNotEmpty()) {
updateDistance(location) updateDistance(location)

View File

@@ -9,16 +9,14 @@ import androidx.car.app.model.ListTemplate
import androidx.car.app.model.Row import androidx.car.app.model.Row
import androidx.car.app.model.Template import androidx.car.app.model.Template
import androidx.car.app.model.Toggle import androidx.car.app.model.Toggle
import androidx.lifecycle.lifecycleScope
import com.kouros.data.R import com.kouros.data.R
import com.kouros.navigation.data.Constants.AVOID_MOTORWAY import com.kouros.navigation.model.NavigationViewModel
import com.kouros.navigation.data.Constants.AVOID_TOLLWAY import com.kouros.navigation.utils.getSettingsViewModel
import com.kouros.navigation.data.Constants.CAR_LOCATION import kotlinx.coroutines.launch
import com.kouros.navigation.model.ViewModel
import com.kouros.navigation.utils.NavigationUtils.getBooleanKeyValue
import com.kouros.navigation.utils.NavigationUtils.setBooleanKeyValue
class NavigationSettings(private val carContext: CarContext, private var viewModel: ViewModel) : class NavigationSettings(private val carContext: CarContext, private var navigationViewModel: NavigationViewModel) :
Screen(carContext) { Screen(carContext) {
private var motorWayToggleState = false private var motorWayToggleState = false
@@ -27,25 +25,25 @@ class NavigationSettings(private val carContext: CarContext, private var viewMod
private var carLocationToggleState = false private var carLocationToggleState = false
val settingsViewModel = getSettingsViewModel(carContext)
init { init {
motorWayToggleState = getBooleanKeyValue(carContext, AVOID_MOTORWAY) lifecycleScope.launch {
settingsViewModel.avoidTollway.collect {
tollWayToggleState = getBooleanKeyValue(carContext, AVOID_MOTORWAY) invalidate()
}
carLocationToggleState = getBooleanKeyValue(carContext, CAR_LOCATION) }
} }
override fun onGetTemplate(): Template { override fun onGetTemplate(): Template {
motorWayToggleState = settingsViewModel.avoidMotorway.value
tollWayToggleState = settingsViewModel.avoidTollway.value
carLocationToggleState = settingsViewModel.carLocation.value
val listBuilder = ItemList.Builder() val listBuilder = ItemList.Builder()
val highwayToggle: Toggle = val highwayToggle: Toggle =
Toggle.Builder { checked: Boolean -> Toggle.Builder { checked: Boolean ->
if (checked) { settingsViewModel.onAvoidMotorway(checked)
setBooleanKeyValue(carContext, true, AVOID_MOTORWAY)
} else {
setBooleanKeyValue(carContext, false, AVOID_MOTORWAY)
}
motorWayToggleState = !motorWayToggleState motorWayToggleState = !motorWayToggleState
}.setChecked(motorWayToggleState).build() }.setChecked(motorWayToggleState).build()
listBuilder.addItem(buildRowForTemplate(R.string.avoid_highways_row_title, highwayToggle)) listBuilder.addItem(buildRowForTemplate(R.string.avoid_highways_row_title, highwayToggle))
@@ -53,22 +51,14 @@ class NavigationSettings(private val carContext: CarContext, private var viewMod
// Tollway // Tollway
val tollwayToggle: Toggle = val tollwayToggle: Toggle =
Toggle.Builder { checked: Boolean -> Toggle.Builder { checked: Boolean ->
if (checked) { settingsViewModel.onAvoidTollway(checked)
setBooleanKeyValue(carContext, true, AVOID_TOLLWAY)
} else {
setBooleanKeyValue(carContext, false, AVOID_TOLLWAY)
}
tollWayToggleState = !tollWayToggleState tollWayToggleState = !tollWayToggleState
}.setChecked(tollWayToggleState).build() }.setChecked(tollWayToggleState).build()
listBuilder.addItem(buildRowForTemplate(R.string.avoid_tolls_row_title, tollwayToggle)) listBuilder.addItem(buildRowForTemplate(R.string.avoid_tolls_row_title, tollwayToggle))
val carLocationToggle: Toggle = val carLocationToggle: Toggle =
Toggle.Builder { checked: Boolean -> Toggle.Builder { checked: Boolean ->
if (checked) { settingsViewModel.onCarLocation(checked)
setBooleanKeyValue(carContext, true, CAR_LOCATION)
} else {
setBooleanKeyValue(carContext, false, CAR_LOCATION)
}
carLocationToggleState = !carLocationToggleState carLocationToggleState = !carLocationToggleState
}.setChecked(carLocationToggleState).build() }.setChecked(carLocationToggleState).build()
@@ -81,7 +71,7 @@ class NavigationSettings(private val carContext: CarContext, private var viewMod
listBuilder.addItem( listBuilder.addItem(
buildRowForScreenTemplate( buildRowForScreenTemplate(
RoutingSettings(carContext, viewModel), RoutingSettings(carContext, navigationViewModel),
R.string.routing_engine R.string.routing_engine
) )
) )

View File

@@ -1,6 +1,5 @@
package com.kouros.navigation.car.screen package com.kouros.navigation.car.screen
import android.location.Location
import android.net.Uri import android.net.Uri
import android.text.Spannable import android.text.Spannable
import android.text.SpannableString import android.text.SpannableString
@@ -25,14 +24,14 @@ import com.kouros.navigation.data.Constants.CONTACTS
import com.kouros.navigation.data.Constants.FAVORITES import com.kouros.navigation.data.Constants.FAVORITES
import com.kouros.navigation.data.Constants.RECENT import com.kouros.navigation.data.Constants.RECENT
import com.kouros.navigation.data.Place import com.kouros.navigation.data.Place
import com.kouros.navigation.model.ViewModel import com.kouros.navigation.model.NavigationViewModel
class PlaceListScreen( class PlaceListScreen(
private val carContext: CarContext, private val carContext: CarContext,
private val surfaceRenderer: SurfaceRenderer, private val surfaceRenderer: SurfaceRenderer,
private val category: String, private val category: String,
private val viewModel: ViewModel private val navigationViewModel: NavigationViewModel
) : Screen(carContext) { ) : Screen(carContext) {
var places = listOf<Place>() var places = listOf<Place>()
@@ -49,30 +48,30 @@ class PlaceListScreen(
init { init {
if (category == RECENT) { if (category == RECENT) {
viewModel.places.observe(this, observer) navigationViewModel.places.observe(this, observer)
} }
if (category == CONTACTS) { if (category == CONTACTS) {
viewModel.contactAddress.observe(this, observerAddress) navigationViewModel.contactAddress.observe(this, observerAddress)
} }
if (category == FAVORITES) { if (category == FAVORITES) {
viewModel.favorites.observe(this, observer) navigationViewModel.favorites.observe(this, observer)
} }
loadPlaces() loadPlaces()
} }
fun loadPlaces() { fun loadPlaces() {
if (category == RECENT) { if (category == RECENT) {
viewModel.loadRecentPlaces( navigationViewModel.loadRecentPlaces(
carContext, carContext,
surfaceRenderer.lastLocation, surfaceRenderer.lastLocation,
surfaceRenderer.carOrientation surfaceRenderer.carOrientation
) )
} }
if (category == CONTACTS) { if (category == CONTACTS) {
viewModel.loadContacts(carContext) navigationViewModel.loadContacts(carContext)
} }
if (category == FAVORITES) { if (category == FAVORITES) {
viewModel.loadFavorites( navigationViewModel.loadFavorites(
carContext, carContext,
surfaceRenderer.lastLocation, surfaceRenderer.lastLocation,
surfaceRenderer.carOrientation surfaceRenderer.carOrientation
@@ -110,7 +109,7 @@ class PlaceListScreen(
carContext, carContext,
surfaceRenderer, surfaceRenderer,
place, place,
viewModel navigationViewModel
) )
) { obj: Any? -> ) { obj: Any? ->
if (obj != null) { if (obj != null) {
@@ -163,7 +162,7 @@ class PlaceListScreen(
) )
) )
.setOnClickListener { .setOnClickListener {
viewModel.deletePlace(place) navigationViewModel.deletePlace(place)
CarToast.makeText( CarToast.makeText(
carContext, carContext,
R.string.recent_Item_deleted, CarToast.LENGTH_LONG R.string.recent_Item_deleted, CarToast.LENGTH_LONG

View File

@@ -6,7 +6,6 @@ import androidx.annotation.DrawableRes
import androidx.car.app.CarContext import androidx.car.app.CarContext
import androidx.car.app.CarToast import androidx.car.app.CarToast
import androidx.car.app.Screen import androidx.car.app.Screen
import androidx.car.app.constraints.ConstraintManager
import androidx.car.app.model.Action import androidx.car.app.model.Action
import androidx.car.app.model.Action.FLAG_DEFAULT import androidx.car.app.model.Action.FLAG_DEFAULT
import androidx.car.app.model.ActionStrip import androidx.car.app.model.ActionStrip
@@ -29,7 +28,7 @@ import com.kouros.navigation.car.SurfaceRenderer
import com.kouros.navigation.car.navigation.NavigationMessage import com.kouros.navigation.car.navigation.NavigationMessage
import com.kouros.navigation.car.navigation.RouteCarModel import com.kouros.navigation.car.navigation.RouteCarModel
import com.kouros.navigation.data.Place import com.kouros.navigation.data.Place
import com.kouros.navigation.model.ViewModel import com.kouros.navigation.model.NavigationViewModel
import com.kouros.navigation.utils.location import com.kouros.navigation.utils.location
import java.math.BigDecimal import java.math.BigDecimal
import java.math.RoundingMode import java.math.RoundingMode
@@ -39,7 +38,7 @@ class RoutePreviewScreen(
carContext: CarContext, carContext: CarContext,
private var surfaceRenderer: SurfaceRenderer, private var surfaceRenderer: SurfaceRenderer,
private var destination: Place, private var destination: Place,
private val viewModel: ViewModel private val navigationViewModel: NavigationViewModel
) : ) :
Screen(carContext) { Screen(carContext) {
private var isFavorite = false private var isFavorite = false
@@ -56,9 +55,9 @@ class RoutePreviewScreen(
} }
init { init {
viewModel.previewRoute.observe(this, observer) navigationViewModel.previewRoute.observe(this, observer)
val location = location(destination.longitude, destination.latitude) val location = location(destination.longitude, destination.latitude)
viewModel.loadPreviewRoute( navigationViewModel.loadPreviewRoute(
carContext, carContext,
surfaceRenderer.lastLocation, surfaceRenderer.lastLocation,
location, location,
@@ -164,7 +163,7 @@ class RoutePreviewScreen(
CarToast.LENGTH_SHORT CarToast.LENGTH_SHORT
) )
.show() .show()
viewModel.saveFavorite(destination) navigationViewModel.saveFavorite(destination)
invalidate() invalidate()
} }
.build() .build()
@@ -172,7 +171,7 @@ class RoutePreviewScreen(
private fun deleteFavoriteAction(): Action = Action.Builder() private fun deleteFavoriteAction(): Action = Action.Builder()
.setOnClickListener { .setOnClickListener {
if (isFavorite) { if (isFavorite) {
viewModel.deleteFavorite(destination) navigationViewModel.deleteFavorite(destination)
} }
isFavorite = !isFavorite isFavorite = !isFavorite
finish() finish()

View File

@@ -10,28 +10,28 @@ import androidx.car.app.model.ListTemplate
import androidx.car.app.model.Row import androidx.car.app.model.Row
import androidx.car.app.model.SectionedItemList import androidx.car.app.model.SectionedItemList
import androidx.car.app.model.Template import androidx.car.app.model.Template
import androidx.car.app.model.Toggle import androidx.lifecycle.lifecycleScope
import com.kouros.data.R import com.kouros.data.R
import com.kouros.navigation.data.Constants.AVOID_MOTORWAY
import com.kouros.navigation.data.Constants.CAR_LOCATION
import com.kouros.navigation.data.Constants.ROUTING_ENGINE
import com.kouros.navigation.data.RouteEngine import com.kouros.navigation.data.RouteEngine
import com.kouros.navigation.model.ViewModel import com.kouros.navigation.model.NavigationViewModel
import com.kouros.navigation.utils.NavigationUtils.getBooleanKeyValue import com.kouros.navigation.utils.getSettingsViewModel
import com.kouros.navigation.utils.NavigationUtils.getIntKeyValue import kotlinx.coroutines.launch
import com.kouros.navigation.utils.NavigationUtils.setBooleanKeyValue
import com.kouros.navigation.utils.NavigationUtils.setIntKeyValue
class RoutingSettings(private val carContext: CarContext, private var viewModel: ViewModel) : Screen(carContext) { class RoutingSettings(private val carContext: CarContext, private var navigationViewModel: NavigationViewModel) : Screen(carContext) {
private var routingEngine = RouteEngine.OSRM.ordinal private var routingEngine = RouteEngine.OSRM.ordinal
val settingsViewModel = getSettingsViewModel(carContext)
init { init {
routingEngine = getIntKeyValue(carContext, ROUTING_ENGINE) lifecycleScope.launch {
settingsViewModel.routingEngine.collect {
invalidate()
}
}
} }
override fun onGetTemplate(): Template { override fun onGetTemplate(): Template {
routingEngine = settingsViewModel.routingEngine.value
val templateBuilder = ListTemplate.Builder() val templateBuilder = ListTemplate.Builder()
val radioList = val radioList =
ItemList.Builder() ItemList.Builder()
@@ -71,8 +71,8 @@ class RoutingSettings(private val carContext: CarContext, private var viewModel:
} }
private fun onSelected(index: Int) { private fun onSelected(index: Int) {
setIntKeyValue(carContext, index, ROUTING_ENGINE) settingsViewModel.onRoutingEngineChanged(index)
viewModel.routingEngine.value = index navigationViewModel.routingEngine.value = index
CarToast.makeText( CarToast.makeText(
carContext, carContext,
(carContext (carContext

View File

@@ -1,7 +1,6 @@
package com.kouros.navigation.car.screen package com.kouros.navigation.car.screen
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.location.Location
import androidx.car.app.CarContext import androidx.car.app.CarContext
import androidx.car.app.Screen import androidx.car.app.Screen
import androidx.car.app.model.Action import androidx.car.app.model.Action
@@ -16,18 +15,17 @@ import androidx.lifecycle.Observer
import com.kouros.data.R import com.kouros.data.R
import com.kouros.navigation.car.SurfaceRenderer import com.kouros.navigation.car.SurfaceRenderer
import com.kouros.navigation.car.ViewStyle import com.kouros.navigation.car.ViewStyle
import com.kouros.navigation.car.navigation.RouteCarModel
import com.kouros.navigation.data.Category import com.kouros.navigation.data.Category
import com.kouros.navigation.data.Constants import com.kouros.navigation.data.Constants
import com.kouros.navigation.data.Place import com.kouros.navigation.data.Place
import com.kouros.navigation.data.nominatim.SearchResult import com.kouros.navigation.data.nominatim.SearchResult
import com.kouros.navigation.model.ViewModel import com.kouros.navigation.model.NavigationViewModel
class SearchScreen( class SearchScreen(
carContext: CarContext, carContext: CarContext,
private var surfaceRenderer: SurfaceRenderer, private var surfaceRenderer: SurfaceRenderer,
private val viewModel: ViewModel, private val navigationViewModel: NavigationViewModel,
) : Screen(carContext) { ) : Screen(carContext) {
var isSearchComplete: Boolean = false var isSearchComplete: Boolean = false
@@ -47,7 +45,7 @@ class SearchScreen(
} }
init { init {
viewModel.searchPlaces.observe(this, observer) navigationViewModel.searchPlaces.observe(this, observer)
} }
override fun onGetTemplate(): Template { override fun onGetTemplate(): Template {
@@ -71,7 +69,7 @@ class SearchScreen(
CategoriesScreen( CategoriesScreen(
carContext, carContext,
surfaceRenderer, surfaceRenderer,
viewModel navigationViewModel
) )
) { obj: Any? -> ) { obj: Any? ->
surfaceRenderer.viewStyle = ViewStyle.VIEW surfaceRenderer.viewStyle = ViewStyle.VIEW
@@ -87,7 +85,7 @@ class SearchScreen(
carContext, carContext,
surfaceRenderer, surfaceRenderer,
it.id, it.id,
viewModel navigationViewModel
) )
) { obj: Any? -> ) { obj: Any? ->
if (obj != null) { if (obj != null) {
@@ -115,7 +113,7 @@ class SearchScreen(
object : SearchCallback { object : SearchCallback {
override fun onSearchSubmitted(searchTerm: String) { override fun onSearchSubmitted(searchTerm: String) {
isSearchComplete = true isSearchComplete = true
viewModel.searchPlaces(searchTerm, surfaceRenderer.lastLocation) navigationViewModel.searchPlaces(searchTerm, surfaceRenderer.lastLocation)
} }
}) })
.setHeaderAction(Action.BACK) .setHeaderAction(Action.BACK)

View File

@@ -24,12 +24,12 @@ import androidx.car.app.model.ListTemplate
import androidx.car.app.model.Row import androidx.car.app.model.Row
import androidx.car.app.model.Template import androidx.car.app.model.Template
import com.kouros.data.R import com.kouros.data.R
import com.kouros.navigation.model.ViewModel import com.kouros.navigation.model.NavigationViewModel
/** A screen demonstrating selectable lists. */ /** A screen demonstrating selectable lists. */
class SettingsScreen( class SettingsScreen(
carContext: CarContext, carContext: CarContext,
private var viewModel: ViewModel, private var navigationViewModel: NavigationViewModel,
) : Screen(carContext) { ) : Screen(carContext) {
override fun onGetTemplate(): Template { override fun onGetTemplate(): Template {
@@ -42,7 +42,7 @@ class SettingsScreen(
) )
listBuilder.addItem( listBuilder.addItem(
buildRowForTemplate( buildRowForTemplate(
NavigationSettings(carContext, viewModel), NavigationSettings(carContext, navigationViewModel),
R.string.navigation_settings R.string.navigation_settings
) )
) )

View File

@@ -2,7 +2,7 @@ package com.kouros.navigation.car
import com.kouros.navigation.data.valhalla.ValhallaRepository import com.kouros.navigation.data.valhalla.ValhallaRepository
import com.kouros.navigation.model.RouteModel import com.kouros.navigation.model.RouteModel
import com.kouros.navigation.model.ViewModel import com.kouros.navigation.model.NavigationViewModel
import org.junit.Test import org.junit.Test
/** /**
@@ -12,7 +12,7 @@ import org.junit.Test
class ViewModelTest { class ViewModelTest {
val repo = ValhallaRepository() val repo = ValhallaRepository()
val viewModel = ViewModel(repo) val navigationViewModel = NavigationViewModel(repo)
val model = RouteModel() val model = RouteModel()
@Test @Test

View File

@@ -5,6 +5,7 @@ import org.jetbrains.kotlin.gradle.dsl.JvmTarget
plugins { plugins {
alias(libs.plugins.android.library) alias(libs.plugins.android.library)
alias(libs.plugins.kotlin.android) alias(libs.plugins.kotlin.android)
alias(libs.plugins.kotlin.compose)
kotlin("plugin.serialization") version "2.2.21" kotlin("plugin.serialization") version "2.2.21"
kotlin("kapt") kotlin("kapt")
} }
@@ -19,6 +20,10 @@ android {
consumerProguardFiles("consumer-rules.pro") consumerProguardFiles("consumer-rules.pro")
} }
buildFeatures {
compose = true
}
buildTypes { buildTypes {
release { release {
isMinifyEnabled = false isMinifyEnabled = false
@@ -40,6 +45,10 @@ android {
} }
dependencies { dependencies {
val composeBom = platform(libs.androidx.compose.bom)
implementation(composeBom)
androidTestImplementation(composeBom)
implementation(libs.androidx.core.ktx) implementation(libs.androidx.core.ktx)
implementation(libs.androidx.appcompat) implementation(libs.androidx.appcompat)
implementation(libs.material) implementation(libs.material)
@@ -49,6 +58,7 @@ dependencies {
implementation(libs.koin.compose.viewmodel) implementation(libs.koin.compose.viewmodel)
implementation(libs.androidx.car.app) implementation(libs.androidx.car.app)
implementation(libs.android.sdk.turf) implementation(libs.android.sdk.turf)
implementation(libs.androidx.compose.runtime)
// objectbox // objectbox
@@ -56,6 +66,8 @@ dependencies {
implementation(libs.androidx.material3) implementation(libs.androidx.material3)
annotationProcessor(libs.objectbox.processor) annotationProcessor(libs.objectbox.processor)
implementation(libs.androidx.datastore.preferences)
implementation(libs.kotlinx.serialization.json) implementation(libs.kotlinx.serialization.json)
implementation(libs.maplibre.compose) implementation(libs.maplibre.compose)
implementation("androidx.compose.material:material-icons-extended:1.7.8") 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 CAR_LOCATION = "CarLocation"
const val ROUTING_ENGINE = "RoutingEngine" 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 const val MAXIMAL_SNAP_CORRECTION = 50.0
@@ -157,3 +159,12 @@ object Constants {
enum class RouteEngine { enum class RouteEngine {
VALHALLA, OSRM, TOMTOM, GRAPHHOPPER 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.content.Context
import android.location.Location import android.location.Location
import com.google.gson.GsonBuilder
import com.kouros.data.R 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.model.RouteModel
import com.kouros.navigation.utils.GeoUtils.calculateSquareRadius import com.kouros.navigation.utils.GeoUtils.calculateSquareRadius
import java.net.Authenticator import java.net.Authenticator
@@ -50,10 +54,18 @@ abstract class NavigationRepository {
searchFilter: SearchFilter, searchFilter: SearchFilter,
context: Context context: Context
): Double { ): Double {
val route = getRoute(context, currentLocation, location, carOrientation, searchFilter) val osrm = OsrmRepository()
val routeModel = RouteModel() val route = osrm.getRoute(context, currentLocation, location, carOrientation, searchFilter)
routeModel.startNavigation(route, context) val gson = GsonBuilder().serializeNulls().create()
return routeModel.curRoute.summary.distance 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 { fun searchPlaces(search: String, location: Location): String {

View File

@@ -99,8 +99,8 @@ data class Route(
} }
} }
fun nextStep(steps : Int): Step { fun nextStep(add: Int): Step {
val nextIndex = currentStepIndex + steps val nextIndex = currentStepIndex + add
return if (isRouteValid() && nextIndex < legs().first().steps.size) { return if (isRouteValid() && nextIndex < legs().first().steps.size) {
legs().first().steps[nextIndex] legs().first().steps[nextIndex]
} else { } 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.NavigationRepository
import com.kouros.navigation.data.SearchFilter 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() { class OsrmRepository : NavigationRepository() {
override fun getRoute( override fun getRoute(
@@ -23,7 +25,7 @@ class OsrmRepository : NavigationRepository() {
if (searchFilter.avoidTollway) { if (searchFilter.avoidTollway) {
exclude = "$exclude&exclude=toll" 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) return fetchUrl(routeUrl + routeLocation + exclude, true)
} }

View File

@@ -10,6 +10,7 @@ import java.net.URL
class Overpass { class Overpass {
//val overpassUrl = "https://overpass.kumi.systems/api/interpreter" //val overpassUrl = "https://overpass.kumi.systems/api/interpreter"
//val overpassUrl = "https://overpass-api.de/api"
val overpassUrl = "https://kouros-online.de/overpass/interpreter" val overpassUrl = "https://kouros-online.de/overpass/interpreter"
@@ -43,13 +44,13 @@ class Overpass {
val boundingBox = getBoundingBox(location.latitude, location.longitude, radius) val boundingBox = getBoundingBox(location.latitude, location.longitude, radius)
val httpURLConnection = URL(overpassUrl).openConnection() as HttpURLConnection val httpURLConnection = URL(overpassUrl).openConnection() as HttpURLConnection
httpURLConnection.requestMethod = "POST" httpURLConnection.requestMethod = "POST"
// node["highway"="speed_camera"]
// node[amenity=$category]
httpURLConnection.setDoOutput(true);
httpURLConnection.setRequestProperty( httpURLConnection.setRequestProperty(
"Accept", "Accept",
"application/json" "application/json"
) )
// node["highway"="speed_camera"]
// node[amenity=$category]
httpURLConnection.setDoOutput(true);
// define search query // define search query
val searchQuery = """ val searchQuery = """
|[out:json]; |[out:json];
@@ -79,7 +80,7 @@ class Overpass {
} }
} catch (e: Exception) { } catch (e: Exception) {
println("Speed $e")
} }
return emptyList() 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/" 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 const val tomtomFields =
private val tomtomFields =
"{incidents{type,geometry{type,coordinates},properties{iconCategory,events{description}}}}" "{incidents{type,geometry{type,coordinates},properties{iconCategory,events{description}}}}"
const val useAsset = false const val useAsset = true
class TomTomRepository : NavigationRepository() { class TomTomRepository : NavigationRepository() {
override fun getRoute( override fun getRoute(

View File

@@ -61,9 +61,7 @@ class TomTomRoute {
route.sections?.forEach { section -> route.sections?.forEach { section ->
val lanes = mutableListOf<Lane>() val lanes = mutableListOf<Lane>()
var startIndex = 0 var startIndex = 0
if (section.startPointIndex <= instruction.pointIndex - 3
&& instruction.pointIndex <= section.endPointIndex
) {
section.lanes?.forEach { itLane -> section.lanes?.forEach { itLane ->
val lane = Lane( val lane = Lane(
location( location(
@@ -77,7 +75,7 @@ class TomTomRoute {
lanes.add(lane) lanes.add(lane)
} }
intersections.add(Intersection(waypoints[startIndex], lanes)) intersections.add(Intersection(waypoints[startIndex], lanes))
}
} }
allIntersections.addAll(intersections) allIntersections.addAll(intersections)
stepDistance = route.guidance.instructions[index].routeOffsetInMeters - stepDistance stepDistance = route.guidance.instructions[index].routeOffsetInMeters - stepDistance
@@ -166,13 +164,23 @@ class TomTomRoute {
newType = androidx.car.app.navigation.model.Maneuver.TYPE_TURN_SHARP_RIGHT 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 newType = androidx.car.app.navigation.model.Maneuver.TYPE_ROUNDABOUT_ENTER_CCW
} }
"ROUNDABOUT_LEFT" -> { "ROUNDABOUT_LEFT" -> {
newType = androidx.car.app.navigation.model.Maneuver.TYPE_ROUNDABOUT_ENTER_CW 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 return newType
} }

View File

@@ -66,30 +66,19 @@ class IconMapper() {
currentTurnIcon = R.drawable.ic_roundabout_ccw currentTurnIcon = R.drawable.ic_roundabout_ccw
} }
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 currentTurnIcon 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
}
}
return 0
}
fun addLanes(direction: String, stepData: StepData): Int { fun addLanes(direction: String, stepData: StepData): Int {
val laneDirection = when (direction.lowercase(Locale.getDefault())) { val laneDirection = when (direction.lowercase(Locale.getDefault())) {
"left_straight" -> { "left_straight" -> {
@@ -112,6 +101,8 @@ class IconMapper() {
"straight" -> { "straight" -> {
when (stepData.currentManeuverType) { when (stepData.currentManeuverType) {
Maneuver.TYPE_STRAIGHT -> LaneDirection.SHAPE_STRAIGHT Maneuver.TYPE_STRAIGHT -> LaneDirection.SHAPE_STRAIGHT
Maneuver.TYPE_KEEP_LEFT -> LaneDirection.SHAPE_STRAIGHT
Maneuver.TYPE_KEEP_RIGHT -> LaneDirection.SHAPE_STRAIGHT
else else
-> LaneDirection.SHAPE_UNKNOWN -> LaneDirection.SHAPE_UNKNOWN
} }
@@ -136,7 +127,8 @@ class IconMapper() {
"left_slight", "slight_left" -> { "left_slight", "slight_left" -> {
when (stepData.currentManeuverType) { 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 else
-> LaneDirection.SHAPE_UNKNOWN -> LaneDirection.SHAPE_UNKNOWN
} }

View File

@@ -2,6 +2,7 @@ package com.kouros.navigation.model
import android.content.Context import android.content.Context
import android.location.Location import android.location.Location
import androidx.car.app.CarContext
import androidx.compose.runtime.snapshots.SnapshotStateList import androidx.compose.runtime.snapshots.SnapshotStateList
import androidx.compose.runtime.toMutableStateList import androidx.compose.runtime.toMutableStateList
import androidx.lifecycle.MutableLiveData 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.nominatim.SearchResult
import com.kouros.navigation.data.overpass.Elements import com.kouros.navigation.data.overpass.Elements
import com.kouros.navigation.data.overpass.Overpass 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.Levenshtein
import com.kouros.navigation.utils.NavigationUtils 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 com.kouros.navigation.utils.location
import io.objectbox.kotlin.boxFor import io.objectbox.kotlin.boxFor
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import org.maplibre.geojson.FeatureCollection import org.maplibre.geojson.FeatureCollection
import java.time.LocalDateTime import java.time.LocalDateTime
import java.time.ZoneOffset import java.time.ZoneOffset
class ViewModel(private val repository: NavigationRepository) : ViewModel() { class NavigationViewModel(private val repository: NavigationRepository) : ViewModel() {
val route: MutableLiveData<String> by lazy { val route: MutableLiveData<String> by lazy {
MutableLiveData() MutableLiveData()
@@ -457,14 +458,9 @@ class ViewModel(private val repository: NavigationRepository) : ViewModel() {
} }
fun getSearchFilter(context: Context): SearchFilter { fun getSearchFilter(context: Context): SearchFilter {
val avoidMotorway = NavigationUtils.getBooleanKeyValue( val repository = getSettingsRepository(context)
context = context, val avoidMotorway = runBlocking { repository.avoidMotorwayFlow.first() }
Constants.AVOID_MOTORWAY val avoidTollway = runBlocking { repository.avoidTollwayFlow.first() }
)
val avoidTollway = NavigationUtils.getBooleanKeyValue(
context = context,
Constants.AVOID_TOLLWAY
)
return SearchFilter(avoidMotorway, avoidTollway) return SearchFilter(avoidMotorway, avoidTollway)
} }

View File

@@ -90,7 +90,7 @@ class RouteCalculator(var routeModel: RouteModel) {
return nowUtcMillis + timeToDestinationMillis return nowUtcMillis + timeToDestinationMillis
} }
fun updateSpeedLimit(location: Location, viewModel: ViewModel) { fun updateSpeedLimit(location: Location, viewModel: NavigationViewModel) {
if (routeModel.isNavigating()) { if (routeModel.isNavigating()) {
// speed limit // speed limit
val distance = lastSpeedLocation.distanceTo(location) val distance = lastSpeedLocation.distanceTo(location)

View File

@@ -4,15 +4,19 @@ import android.content.Context
import android.location.Location import android.location.Location
import androidx.car.app.navigation.model.Maneuver import androidx.car.app.navigation.model.Maneuver
import com.kouros.navigation.data.Constants.NEXT_STEP_THRESHOLD 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.Place
import com.kouros.navigation.data.Route import com.kouros.navigation.data.Route
import com.kouros.navigation.data.StepData 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.Lane
import com.kouros.navigation.data.route.Leg import com.kouros.navigation.data.route.Leg
import com.kouros.navigation.data.route.Routes 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 com.kouros.navigation.utils.location
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.runBlocking
import kotlin.math.absoluteValue import kotlin.math.absoluteValue
open class RouteModel { open class RouteModel {
@@ -46,15 +50,17 @@ open class RouteModel {
get() = navState.route.routes[navState.currentRouteIndex].legs.first() get() = navState.route.routes[navState.currentRouteIndex].legs.first()
fun startNavigation(routeString: String, context: Context) { 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( navState = navState.copy(
route = Route.Builder() route = Route.Builder()
.routeEngine(routeEngine) .routeEngine(routingEngine)
.route(routeString) .route(routeString)
.build() .build()
) )
if (hasLegs()) { if (hasLegs()) {
navState = navState.copy(navigating = true) navState = navState.copy(navigating = true)
//NavigationUtils.setStringKeyValue(context, routeString, LAST_ROUTE)
} }
} }
@@ -62,22 +68,80 @@ open class RouteModel {
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( navState = navState.copy(
route = Route.Builder().buildEmpty(), route = Route.Builder().buildEmpty(),
navigating = false, navigating = false,
arrived = false, arrived = false,
maneuverType = Maneuver.TYPE_UNKNOWN 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) navState = navState.copy(currentLocation = curLocation)
routeCalculator.findStep(curLocation) routeCalculator.findStep(curLocation)
val repository = getSettingsRepository(context)
val carLocation = runBlocking { repository.carLocationFlow.first() }
if (carLocation) {
routeCalculator.updateSpeedLimit(curLocation, viewModel) routeCalculator.updateSpeedLimit(curLocation, viewModel)
}
navState = navState.copy(lastLocation = navState.currentLocation) 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> { private fun currentLanes(): List<Lane> {
var lanes = emptyList<Lane>() var lanes = emptyList<Lane>()
if (navState.route.legs().isNotEmpty()) { if (navState.route.legs().isNotEmpty()) {
@@ -87,7 +151,7 @@ open class RouteModel {
navState.lastLocation.distanceTo(location(it.location[0], it.location[1])) navState.lastLocation.distanceTo(location(it.location[0], it.location[1]))
val sectionBearing = val sectionBearing =
navState.lastLocation.bearingTo(location(it.location[0], it.location[1])) 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 lanes = it.lane
} }
} }
@@ -96,61 +160,6 @@ open class RouteModel {
return lanes 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 { fun isNavigating(): Boolean {
return navState.navigating 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.content.Context
import android.location.Location import android.location.Location
import android.location.LocationManager import android.location.LocationManager
import androidx.car.app.CarContext
import androidx.core.content.edit import androidx.core.content.edit
import com.kouros.navigation.data.Constants.ROUTING_ENGINE import com.kouros.navigation.data.Constants.ROUTING_ENGINE
import com.kouros.navigation.data.Constants.SHARED_PREF_KEY import com.kouros.navigation.data.Constants.SHARED_PREF_KEY
import com.kouros.navigation.data.RouteEngine 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.osrm.OsrmRepository
import com.kouros.navigation.data.tomtom.TomTomRepository import com.kouros.navigation.data.tomtom.TomTomRepository
import com.kouros.navigation.data.valhalla.ValhallaRepository 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.LocalDateTime
import java.time.ZoneId import java.time.ZoneId
import java.time.ZoneOffset import java.time.ZoneOffset
@@ -26,58 +32,13 @@ import kotlin.time.Duration.Companion.seconds
object NavigationUtils { object NavigationUtils {
fun getViewModel(context: Context): ViewModel { fun getViewModel(context: Context): NavigationViewModel {
val routeEngine = getIntKeyValue(context = context, ROUTING_ENGINE) val repository = getSettingsRepository(context)
val routeEngine = runBlocking { repository.routingEngineFlow.first() }
return when (routeEngine) { return when (routeEngine) {
RouteEngine.VALHALLA.ordinal -> ViewModel(ValhallaRepository()) RouteEngine.VALHALLA.ordinal -> NavigationViewModel(ValhallaRepository())
RouteEngine.OSRM.ordinal -> ViewModel(OsrmRepository()) RouteEngine.OSRM.ordinal -> NavigationViewModel(OsrmRepository())
else -> ViewModel(TomTomRepository()) 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()
} }
} }
} }
@@ -90,8 +51,8 @@ fun calculateZoom(speed: Double?): Double {
val zoom = when (speedKmh) { val zoom = when (speedKmh) {
in 0..10 -> 18.0 in 0..10 -> 18.0
in 11..30 -> 17.5 in 11..30 -> 17.5
in 31..50 -> 17.0 in 31..65 -> 17.0
in 61..70 -> 16.5 in 66..70 -> 16.5
else -> 16.0 else -> 16.0
} }
return zoom 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

View File

@@ -13,33 +13,34 @@ junitVersion = "1.3.0"
espressoCore = "3.7.0" espressoCore = "3.7.0"
kotlinxSerializationJson = "1.10.0" kotlinxSerializationJson = "1.10.0"
lifecycleRuntimeKtx = "2.10.0" lifecycleRuntimeKtx = "2.10.0"
composeBom = "2026.01.01" composeBom = "2026.02.00"
appcompat = "1.7.1" appcompat = "1.7.1"
material = "1.13.0" material = "1.13.0"
carApp = "1.7.0" carApp = "1.7.0"
androidx-car = "1.7.0" androidx-car = "1.7.0"
objectboxKotlin = "5.1.0" objectboxKotlin = "5.2.0"
objectboxProcessor = "5.0.1" objectboxProcessor = "5.2.0"
ui = "1.10.0" ui = "1.10.0"
material3 = "1.4.0" material3 = "1.4.0"
runtimeLivedata = "1.10.2" runtimeLivedata = "1.10.3"
foundation = "1.10.0" foundation = "1.10.3"
maplibre-composeMaterial3 = "0.12.2" maplibre-composeMaterial3 = "0.12.2"
maplibre-compose = "0.12.1" maplibre-compose = "0.12.1"
playServicesLocation = "21.3.0" playServicesLocation = "21.3.0"
runtime = "1.10.2" runtime = "1.10.3"
accompanist = "0.37.3" accompanist = "0.37.3"
uiVersion = "1.10.2" uiVersion = "1.10.3"
uiText = "1.10.2" uiText = "1.10.3"
navigationCompose = "2.9.7" navigationCompose = "2.9.7"
uiToolingPreview = "1.10.2" uiToolingPreview = "1.10.3"
uiTooling = "1.10.2" uiTooling = "1.10.3"
material3WindowSizeClass = "1.4.0" material3WindowSizeClass = "1.4.0"
uiGraphics = "1.10.2" uiGraphics = "1.10.3"
window = "1.5.1" window = "1.5.1"
foundationLayout = "1.10.2" foundationLayout = "1.10.3"
foundationLayoutVersion = "1.10.2" foundationLayoutVersion = "1.10.3"
datastorePreferences = "1.2.0"
datastoreCore = "1.2.0"
[libraries] [libraries]
android-sdk-turf = { module = "org.maplibre.gl:android-sdk-turf", version.ref = "androidSdkTurf" } android-sdk-turf = { module = "org.maplibre.gl:android-sdk-turf", version.ref = "androidSdkTurf" }
@@ -81,6 +82,8 @@ androidx-compose-ui-graphics = { group = "androidx.compose.ui", name = "ui-graph
androidx-window = { group = "androidx.window", name = "window", version.ref = "window" } androidx-window = { group = "androidx.window", name = "window", version.ref = "window" }
androidx-compose-foundation-layout = { group = "androidx.compose.foundation", name = "foundation-layout", version.ref = "foundationLayout" } androidx-compose-foundation-layout = { group = "androidx.compose.foundation", name = "foundation-layout", version.ref = "foundationLayout" }
androidx-foundation-layout = { group = "androidx.compose.foundation", name = "foundation-layout", version.ref = "foundationLayoutVersion" } androidx-foundation-layout = { group = "androidx.compose.foundation", name = "foundation-layout", version.ref = "foundationLayoutVersion" }
androidx-datastore-preferences = { module = "androidx.datastore:datastore-preferences", version.ref = "datastorePreferences" }
androidx-datastore-core = { group = "androidx.datastore", name = "datastore-core", version.ref = "datastoreCore" }
[plugins] [plugins]
android-application = { id = "com.android.application", version.ref = "agp" } android-application = { id = "com.android.application", version.ref = "agp" }