From ebd97cf1c9d82e5bfdae8163f97581e1bf8d9b80 Mon Sep 17 00:00:00 2001 From: Dimitris Date: Fri, 20 Feb 2026 07:20:04 +0100 Subject: [PATCH] DataStore Manager --- app/build.gradle.kts | 4 +- .../com/kouros/navigation/MainApplication.kt | 6 +- .../com/kouros/navigation/di/appModule.kt | 11 +- .../com/kouros/navigation/ui/MainActivity.kt | 80 +- .../java/com/kouros/navigation/ui/MapView.kt | 21 +- .../kouros/navigation/ui/NavigationScreen.kt | 63 +- .../kouros/navigation/ui/NavigationSheet.kt | 2 +- .../com/kouros/navigation/ui/SearchSheet.kt | 20 +- .../kouros/navigation/ui/SettingsScreen.kt | 34 +- .../kouros/navigation/ui/app/AppViewModel.kt | 27 + .../navigation/ui/app/AppViewModelProvider.kt | 29 + .../components/RadioButtonSingleSelection.kt | 59 + .../navigation/ui/components/SectionTitle.kt | 17 + .../navigation/ui/components/SettingItem.kt | 29 + .../navigation/ui/components/SettingSwitch.kt | 37 + .../ui/components/ThemeColorPicker.kt | 52 + .../navigation/ui/navigation/AppNavGraph.kt | 34 + .../DisplaySettings.kt} | 23 +- .../NavigationScreenSettings.kt | 33 +- .../navigation/ui/settings/SettingsRoute.kt | 37 + .../navigation/ui/settings/SettingsScreen.kt | 151 + .../com/kouros/navigation/ui/theme/Theme.kt | 2 +- common/car/build.gradle.kts | 1 + .../navigation/car/NavigationSession.kt | 56 +- .../kouros/navigation/car/SurfaceRenderer.kt | 35 +- .../com/kouros/navigation/car/map/MapView.kt | 13 +- .../navigation/car/screen/CategoriesScreen.kt | 8 +- .../navigation/car/screen/CategoryScreen.kt | 12 +- .../navigation/car/screen/DarkModeSettings.kt | 30 +- .../navigation/car/screen/DisplaySettings.kt | 21 +- .../navigation/car/screen/NavigationScreen.kt | 56 +- .../car/screen/NavigationSettings.kt | 48 +- .../navigation/car/screen/PlaceListScreen.kt | 21 +- .../car/screen/RoutePreviewScreen.kt | 13 +- .../navigation/car/screen/RoutingSettings.kt | 28 +- .../navigation/car/screen/SearchScreen.kt | 14 +- .../navigation/car/screen/SettingsScreen.kt | 6 +- .../com/kouros/navigation/car/UnitTest.kt | 4 +- common/data/build.gradle.kts | 12 + .../java/com/kouros/navigation/data/Data.kt | 13 +- .../navigation/data/NavigationRepository.kt | 20 +- .../java/com/kouros/navigation/data/Route.kt | 4 +- .../data/datastore/DataStoreManager.kt | 111 + .../navigation/data/osrm/OsrmRepository.kt | 6 +- .../navigation/data/overpass/Overpass.kt | 9 +- .../data/tomtom/TomTomRepository.kt | 9 +- .../navigation/data/tomtom/TomTomRoute.kt | 18 +- .../com/kouros/navigation/model/IconMapper.kt | 34 +- .../{ViewModel.kt => NavigationViewModel.kt} | 22 +- .../navigation/model/RouteCalculator.kt | 2 +- .../com/kouros/navigation/model/RouteModel.kt | 141 +- .../navigation/model/SettingsViewModel.kt | 72 + .../repository/SettingsRepository.kt | 52 + .../navigation/utils/NavigationUtils.kt | 69 +- .../com/kouros/navigation/utils/Settings.kt | 45 + .../drawable/ic_turn_merge_symmetrical.png | Bin 0 -> 832 bytes .../main/res/drawable/ic_turn_sharp_left.png | Bin 0 -> 593 bytes .../main/res/drawable/ic_turn_sharp_right.png | Bin 0 -> 593 bytes .../main/res/drawable/ic_turn_u_turn_left.png | Bin 0 -> 829 bytes .../res/drawable/ic_turn_u_turn_right.png | Bin 0 -> 835 bytes common/data/src/main/res/raw/osrm_hv.json | 1313 --- common/data/src/main/res/raw/osrm_vh.json | 4706 ----------- common/data/src/main/res/raw/tomtom_traffic | 6668 +++++++++++++++ .../data/src/main/res/raw/tomtom_traffic.json | 7197 ----------------- gradle/libs.versions.toml | 31 +- 65 files changed, 7975 insertions(+), 13716 deletions(-) create mode 100644 app/src/main/java/com/kouros/navigation/ui/app/AppViewModel.kt create mode 100644 app/src/main/java/com/kouros/navigation/ui/app/AppViewModelProvider.kt create mode 100644 app/src/main/java/com/kouros/navigation/ui/components/RadioButtonSingleSelection.kt create mode 100644 app/src/main/java/com/kouros/navigation/ui/components/SectionTitle.kt create mode 100644 app/src/main/java/com/kouros/navigation/ui/components/SettingItem.kt create mode 100644 app/src/main/java/com/kouros/navigation/ui/components/SettingSwitch.kt create mode 100644 app/src/main/java/com/kouros/navigation/ui/components/ThemeColorPicker.kt create mode 100644 app/src/main/java/com/kouros/navigation/ui/navigation/AppNavGraph.kt rename app/src/main/java/com/kouros/navigation/ui/{DisplayScreenSettings.kt => settings/DisplaySettings.kt} (86%) rename app/src/main/java/com/kouros/navigation/ui/{ => settings}/NavigationScreenSettings.kt (82%) create mode 100644 app/src/main/java/com/kouros/navigation/ui/settings/SettingsRoute.kt create mode 100644 app/src/main/java/com/kouros/navigation/ui/settings/SettingsScreen.kt create mode 100644 common/data/src/main/java/com/kouros/navigation/data/datastore/DataStoreManager.kt rename common/data/src/main/java/com/kouros/navigation/model/{ViewModel.kt => NavigationViewModel.kt} (96%) create mode 100644 common/data/src/main/java/com/kouros/navigation/model/SettingsViewModel.kt create mode 100644 common/data/src/main/java/com/kouros/navigation/repository/SettingsRepository.kt create mode 100644 common/data/src/main/java/com/kouros/navigation/utils/Settings.kt create mode 100644 common/data/src/main/res/drawable/ic_turn_merge_symmetrical.png create mode 100644 common/data/src/main/res/drawable/ic_turn_sharp_left.png create mode 100644 common/data/src/main/res/drawable/ic_turn_sharp_right.png create mode 100644 common/data/src/main/res/drawable/ic_turn_u_turn_left.png create mode 100644 common/data/src/main/res/drawable/ic_turn_u_turn_right.png delete mode 100644 common/data/src/main/res/raw/osrm_hv.json delete mode 100644 common/data/src/main/res/raw/osrm_vh.json create mode 100644 common/data/src/main/res/raw/tomtom_traffic delete mode 100644 common/data/src/main/res/raw/tomtom_traffic.json diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 6ec8ca3..f4665ed 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -14,8 +14,8 @@ android { applicationId = "com.kouros.navigation" minSdk = 33 targetSdk = 36 - versionCode = 38 - versionName = "0.2.0.38" + versionCode = 42 + versionName = "0.2.0.42" base.archivesName = "navi-$versionName" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" } diff --git a/app/src/main/java/com/kouros/navigation/MainApplication.kt b/app/src/main/java/com/kouros/navigation/MainApplication.kt index bad399b..ac8694c 100644 --- a/app/src/main/java/com/kouros/navigation/MainApplication.kt +++ b/app/src/main/java/com/kouros/navigation/MainApplication.kt @@ -4,7 +4,7 @@ import android.app.Application import android.content.Context import com.kouros.navigation.data.ObjectBox 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 org.koin.android.ext.koin.androidContext import org.koin.android.ext.koin.androidLogger @@ -15,7 +15,7 @@ class MainApplication : Application() { override fun onCreate() { super.onCreate() - ObjectBox.init(this); + ObjectBox.init(this) appContext = applicationContext navigationViewModel = getViewModel(appContext!!) startKoin { @@ -30,6 +30,6 @@ class MainApplication : Application() { var appContext: Context? = null private set - lateinit var navigationViewModel : ViewModel + lateinit var navigationViewModel : NavigationViewModel } } \ No newline at end of file diff --git a/app/src/main/java/com/kouros/navigation/di/appModule.kt b/app/src/main/java/com/kouros/navigation/di/appModule.kt index f8047cb..ebb9c09 100644 --- a/app/src/main/java/com/kouros/navigation/di/appModule.kt +++ b/app/src/main/java/com/kouros/navigation/di/appModule.kt @@ -1,18 +1,19 @@ package com.kouros.navigation.di -import com.kouros.navigation.data.NavigationRepository import com.kouros.navigation.data.osrm.OsrmRepository import com.kouros.navigation.data.tomtom.TomTomRepository import com.kouros.navigation.data.valhalla.ValhallaRepository -import com.kouros.navigation.model.BaseStyleModel -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 org.koin.core.module.dsl.singleOf +import org.koin.core.module.dsl.viewModel import org.koin.core.module.dsl.viewModelOf import org.koin.dsl.module -import kotlin.math.sin val appModule = module { - viewModelOf(::ViewModel) + viewModelOf(::NavigationViewModel) + viewModelOf(::SettingsViewModel) singleOf(::ValhallaRepository) singleOf(::OsrmRepository) singleOf(::TomTomRepository) diff --git a/app/src/main/java/com/kouros/navigation/ui/MainActivity.kt b/app/src/main/java/com/kouros/navigation/ui/MainActivity.kt index 7ba1186..2a97729 100644 --- a/app/src/main/java/com/kouros/navigation/ui/MainActivity.kt +++ b/app/src/main/java/com/kouros/navigation/ui/MainActivity.kt @@ -43,26 +43,27 @@ import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.lifecycle.MutableLiveData import androidx.lifecycle.Observer +import androidx.lifecycle.lifecycleScope import androidx.navigation.NavController 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.LocationServices import com.kouros.data.R 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.homeVogelhart import com.kouros.navigation.data.StepData +import com.kouros.navigation.data.datastore.DataStoreManager import com.kouros.navigation.model.BaseStyleModel import com.kouros.navigation.model.MockLocation 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.utils.NavigationUtils.getIntKeyValue 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 io.ticofab.androidgpxparser.parser.GPXParser 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.Dispatchers import kotlinx.coroutines.delay +import kotlinx.coroutines.flow.first import kotlinx.coroutines.launch +import kotlinx.coroutines.runBlocking import org.joda.time.DateTime import org.maplibre.compose.camera.CameraPosition import org.maplibre.compose.location.DesiredAccuracy @@ -83,12 +86,13 @@ import kotlin.time.Duration.Companion.seconds class MainActivity : ComponentActivity() { + val routeData = MutableLiveData("") val routeModel = RouteModel() var tilt = 50.0 - val useMock = false - val type = 3 // simulate 2 test 3 gpx 4 testSingle + val useMock = true + val type = 3 // 1 simulate 2 test 3 gpx 4 testSingle var currentIndex = 0 val stepData: MutableLiveData by lazy { @@ -134,8 +138,7 @@ class MainActivity : ComponentActivity() { @RequiresPermission(allOf = [Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.ACCESS_COARSE_LOCATION]) override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - val darkModeSettings = getIntKeyValue(applicationContext, Constants.DARK_MODE_SETTINGS) - baseStyle = BaseStyleModel().readStyle(applicationContext, darkModeSettings, false) + if (useMock) { checkMockLocationEnabled() } @@ -150,6 +153,11 @@ class MainActivity : ComponentActivity() { navigationViewModel.route.observe(this, observer) } } + lifecycleScope.launch { + getSettingsViewModel(applicationContext).routingEngine.collect { + + } + } enableEdgeToEdge() setContent { CheckPermissionScreen() @@ -186,9 +194,13 @@ class MainActivity : ComponentActivity() { @Composable fun StartScreen( navController: NavHostController - ) { + val appViewModel: AppViewModel = appViewModel() + val darkMode by appViewModel.darkMode.collectAsState() + + baseStyle = BaseStyleModel().readStyle(applicationContext, darkMode, darkMode == 1) + val scaffoldState = rememberBottomSheetScaffoldState() val snackbarHostState = remember { SnackbarHostState() } val scope = rememberCoroutineScope() @@ -212,7 +224,7 @@ class MainActivity : ComponentActivity() { sheetPeekHeightState.value = 128.dp } } - NavigationTheme { + NavigationTheme (useDarkTheme = darkMode == 1) { BottomSheetScaffold( snackbarHost = { SnackbarHost(hostState = snackbarHostState) @@ -249,13 +261,12 @@ class MainActivity : ComponentActivity() { @Composable fun App() { - val navController = rememberNavController() - NavHost(navController = navController, startDestination = "startScreen") { - composable("startScreen") { StartScreen(navController) } - composable("settings") { SettingsScreen(navController) { navController.popBackStack() } } - composable("display_settings") { DisplayScreenSettings(applicationContext) { navController.popBackStack() } } - composable("nav_settings") { NavigationScreenSettings(applicationContext) { navController.popBackStack() } } - } + //val lastRoute = NavigationUtils.getStringKeyValue(applicationContext, Constants.LAST_ROUTE) + //if (lastRoute!!.isNotEmpty()) { + // routeModel.startNavigation(lastRoute, applicationContext) + // routeData.value = routeModel.curRoute.routeGeoJson + //} + AppNavGraph(applicationContext = applicationContext, this) } @Composable @@ -285,13 +296,15 @@ class MainActivity : ComponentActivity() { if (!routeModel.isNavigating()) { SearchSheet(applicationContext, navigationViewModel, lastLocation) { closeSheet() } } else { - NavigationSheet( - applicationContext, - routeModel, - step!!, - nextStep!!, - { stopNavigation { closeSheet() } }, - { simulateNavigation() }) + if (step != null && nextStep != null) { + NavigationSheet( + applicationContext, + routeModel, + step, + nextStep, + { stopNavigation { closeSheet() } }, + { simulateNavigation() }) + } } // For recomposition! Text("$locationState", fontSize = 12.sp) @@ -303,17 +316,18 @@ class MainActivity : ComponentActivity() { val bearing = bearing(lastLocation, currentLocation, cameraPosition.value!!.bearing) with(routeModel) { if (isNavigating()) { - updateLocation(currentLocation, navigationViewModel) + updateLocation(applicationContext,currentLocation, navigationViewModel) stepData.value = currentStep() nextStepData.value = nextStep() if (navState.maneuverType in 39..42 && routeCalculator.leftStepDistance() < DESTINATION_ARRIVAL_DISTANCE) { // stopNavigation() - navState.copy(arrived = true) + navState = navState.copy(arrived = true) routeData.value = "" } } } - val zoom = calculateZoom(location.speed) + //val zoom = calculateZoom(location.speed) + val zoom = 16.0 cameraPosition.postValue( cameraPosition.value!!.copy( zoom = zoom, target = location.position, bearing = bearing @@ -331,7 +345,7 @@ class MainActivity : ComponentActivity() { val latitude = routeModel.curRoute.waypoints[0][1] val longitude = routeModel.curRoute.waypoints[0][0] closeSheet() - routeModel.stopNavigation() + routeModel.stopNavigation(applicationContext) if (useMock) { mock.setMockLocation(latitude, longitude) } @@ -370,7 +384,7 @@ class MainActivity : ComponentActivity() { val deviation = 0.0 if (index in 0..routeModel.curRoute.waypoints.size) { 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()) { //if (index in 3..3) { for ((windex, waypoint) in step.maneuver.waypoints.withIndex()) { - routeModel.updateLocation( + routeModel.updateLocation(applicationContext, location(waypoint[0], waypoint[1]), navigationViewModel ) val step = routeModel.currentStep() @@ -402,7 +416,7 @@ class MainActivity : ComponentActivity() { if (1 == 1) { mock.setMockLocation(latitude, longitude) } else { - routeModel.updateLocation( + routeModel.updateLocation(applicationContext, location(longitude, latitude), navigationViewModel ) } diff --git a/app/src/main/java/com/kouros/navigation/ui/MapView.kt b/app/src/main/java/com/kouros/navigation/ui/MapView.kt index 6b79535..822d3fc 100644 --- a/app/src/main/java/com/kouros/navigation/ui/MapView.kt +++ b/app/src/main/java/com/kouros/navigation/ui/MapView.kt @@ -5,18 +5,24 @@ import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.PaddingValues import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.livedata.observeAsState +import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.unit.dp import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel +import androidx.lifecycle.ViewModelProvider +import androidx.lifecycle.viewmodel.compose.viewModel import androidx.window.layout.WindowMetricsCalculator import com.kouros.navigation.car.ViewStyle import com.kouros.navigation.car.map.MapLibre import com.kouros.navigation.car.map.NavigationImage import com.kouros.navigation.car.map.rememberBaseStyle 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.rememberCameraState import org.maplibre.compose.location.LocationTrackingEffect @@ -57,7 +63,11 @@ fun MapView( ) ) - val rememberBaseStyle = rememberBaseStyle( baseStyle) + val rememberBaseStyle = rememberBaseStyle(baseStyle) + + val appViewModel: AppViewModel = appViewModel() + val showBuildings by appViewModel.threedBuilding.collectAsState() + Column { NavigationInfo(step, nextStep) Box(contentAlignment = Alignment.Center) { @@ -67,7 +77,9 @@ fun MapView( rememberBaseStyle, route, emptyMap(), - ViewStyle.VIEW + ViewStyle.VIEW, + speedCameras = "", + showBuildings ) LocationTrackingEffect( locationState = userLocationState, @@ -87,6 +99,3 @@ fun MapView( } } } - - - diff --git a/app/src/main/java/com/kouros/navigation/ui/NavigationScreen.kt b/app/src/main/java/com/kouros/navigation/ui/NavigationScreen.kt index d67894e..abeb3c7 100644 --- a/app/src/main/java/com/kouros/navigation/ui/NavigationScreen.kt +++ b/app/src/main/java/com/kouros/navigation/ui/NavigationScreen.kt @@ -2,10 +2,15 @@ package com.kouros.navigation.ui import androidx.compose.foundation.layout.Column 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.size import androidx.compose.material3.Card +import androidx.compose.material3.CardDefaults +import androidx.compose.material3.ElevatedCard import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier @@ -17,39 +22,43 @@ import com.kouros.data.R import com.kouros.navigation.data.StepData import com.kouros.navigation.utils.round + @Composable fun NavigationInfo(step: StepData?, nextStep: StepData?) { if (step != null && step.instruction.isNotEmpty()) { - Card(modifier = Modifier.padding(top = 60.dp)) { - Column() { + ElevatedCard( + elevation = CardDefaults.cardElevation( + defaultElevation = 6.dp + + ), modifier = Modifier + .padding(top = 60.dp) + .fillMaxWidth() + ) { + Column { + Icon( + painter = painterResource(step.icon), + contentDescription = stringResource(id = R.string.accept_action_title), + modifier = Modifier.size(48.dp, 48.dp), + ) + if (step.currentManeuverType == 46 + || step.currentManeuverType == 45 + ) { + Text(text = "Exit ${step.exitNumber}", fontSize = 18.sp) + } Row { - Icon( - painter = painterResource(step.icon), - contentDescription = stringResource(id = R.string.accept_action_title), - modifier = Modifier.size(48.dp, 48.dp), - ) - if (step.currentManeuverType == 46 - || step.currentManeuverType == 45) { - Text(text ="Exit ${step.exitNumber}", fontSize = 20.sp) - } - Column { - if (step.leftStepDistance < 1000) { - Text(text = "${step.leftStepDistance.toInt()} m", fontSize = 25.sp) - } else { - Text( - text = "${(step.leftStepDistance / 1000).round(1)} km", - fontSize = 25.sp - ) - } - Text(text = step.instruction, fontSize = 20.sp) - } - 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), + if (step.leftStepDistance < 1000) { + Text(text = "${step.leftStepDistance.toInt()} m", fontSize = 24.sp, color = MaterialTheme.colorScheme.primary) + } else { + Text( + text = "${(step.leftStepDistance / 1000).round(1)} km", + fontSize = 24.sp, + color = MaterialTheme.colorScheme.primary ) } + Spacer( + modifier = Modifier.padding(5.dp) + ) + Text(text = step.instruction, fontSize = 24.sp, color = MaterialTheme.colorScheme.primary) } } } diff --git a/app/src/main/java/com/kouros/navigation/ui/NavigationSheet.kt b/app/src/main/java/com/kouros/navigation/ui/NavigationSheet.kt index 798925a..193981a 100755 --- a/app/src/main/java/com/kouros/navigation/ui/NavigationSheet.kt +++ b/app/src/main/java/com/kouros/navigation/ui/NavigationSheet.kt @@ -34,7 +34,7 @@ fun NavigationSheet( val distance = (step.leftDistance / 1000).round(1) if (step.lane.isNotEmpty()) { - routeModel.navState.iconMapper.addLanes( step) + // routeModel.navState.iconMapper.addLanes( step) } Column { diff --git a/app/src/main/java/com/kouros/navigation/ui/SearchSheet.kt b/app/src/main/java/com/kouros/navigation/ui/SearchSheet.kt index e0e137b..a6a50ed 100644 --- a/app/src/main/java/com/kouros/navigation/ui/SearchSheet.kt +++ b/app/src/main/java/com/kouros/navigation/ui/SearchSheet.kt @@ -4,10 +4,8 @@ import android.content.Context import android.location.Location import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.size import androidx.compose.foundation.lazy.LazyColumn @@ -29,25 +27,21 @@ import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue -import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color 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 com.kouros.data.R import com.kouros.navigation.data.Place import com.kouros.navigation.data.PlaceColor 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 @Composable fun SearchSheet( applicationContext: Context, - viewModel: ViewModel, + viewModel: NavigationViewModel, location: Location, closeSheet: () -> Unit ) { @@ -88,7 +82,7 @@ fun SearchSheet( @Composable fun Home( applicationContext: Context, - viewModel: ViewModel, + viewModel: NavigationViewModel, location: Location, closeSheet: () -> Unit ) { @@ -125,7 +119,7 @@ fun SearchBar( searchPlaces: List, searchResults: List, modifier: Modifier = Modifier, - viewModel: ViewModel, + viewModel: NavigationViewModel, context: Context, location: Location, 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) } @Composable private fun SearchPlaces( searchResults: List, - viewModel: ViewModel, + viewModel: NavigationViewModel, context: Context, location: Location, closeSheet: () -> Unit @@ -222,7 +216,7 @@ private fun SearchPlaces( @Composable private fun RecentPlaces( recentPlaces: List, - viewModel: ViewModel, + viewModel: NavigationViewModel, context: Context, location: Location, closeSheet: () -> Unit diff --git a/app/src/main/java/com/kouros/navigation/ui/SettingsScreen.kt b/app/src/main/java/com/kouros/navigation/ui/SettingsScreen.kt index 33b151c..926bd73 100644 --- a/app/src/main/java/com/kouros/navigation/ui/SettingsScreen.kt +++ b/app/src/main/java/com/kouros/navigation/ui/SettingsScreen.kt @@ -6,6 +6,7 @@ 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.Button import androidx.compose.material3.CenterAlignedTopAppBar import androidx.compose.material3.ExperimentalMaterial3Api 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.unit.dp import androidx.navigation.NavHostController -import com.alorma.compose.settings.ui.SettingsMenuLink import com.kouros.data.R @OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterial3ExpressiveApi::class) @@ -53,34 +53,16 @@ fun SettingsScreen(navController: NavHostController, navigateBack: () -> Unit) { .verticalScroll(scrollState) .padding(top = padding.calculateTopPadding()), ) { - SettingsMenuLink( - title = { Text(text = stringResource(R.string.display_settings)) }, - modifier = Modifier, - enabled = true, - onClick = { navController.navigate("display_settings")}, - icon = { - Icon( - painter = painterResource(R.drawable.ic_place_white_24dp), - contentDescription = stringResource(id = R.string.display_settings), - modifier = Modifier.size(48.dp, 48.dp), - ) + Column(modifier = Modifier.padding(16.dp)) { + + Button(onClick = { navController.navigate("display_settings") }) { + Text(stringResource(R.string.display_settings)) } - ) - 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), - ) + Button(onClick = { navController.navigate("nav_settings") }) { + Text(stringResource(R.string.navigation_settings)) } - ) + } } } } - diff --git a/app/src/main/java/com/kouros/navigation/ui/app/AppViewModel.kt b/app/src/main/java/com/kouros/navigation/ui/app/AppViewModel.kt new file mode 100644 index 0000000..8f30888 --- /dev/null +++ b/app/src/main/java/com/kouros/navigation/ui/app/AppViewModel.kt @@ -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 + ) +} \ No newline at end of file diff --git a/app/src/main/java/com/kouros/navigation/ui/app/AppViewModelProvider.kt b/app/src/main/java/com/kouros/navigation/ui/app/AppViewModelProvider.kt new file mode 100644 index 0000000..29dcff5 --- /dev/null +++ b/app/src/main/java/com/kouros/navigation/ui/app/AppViewModelProvider.kt @@ -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 create(modelClass: Class): T { + @Suppress("UNCHECKED_CAST") + return AppViewModel(repository) as T + } + } + ) +} \ No newline at end of file diff --git a/app/src/main/java/com/kouros/navigation/ui/components/RadioButtonSingleSelection.kt b/app/src/main/java/com/kouros/navigation/ui/components/RadioButtonSingleSelection.kt new file mode 100644 index 0000000..0c1e8ab --- /dev/null +++ b/app/src/main/java/com/kouros/navigation/ui/components/RadioButtonSingleSelection.kt @@ -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, + 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) + ) + } + } + } +} + diff --git a/app/src/main/java/com/kouros/navigation/ui/components/SectionTitle.kt b/app/src/main/java/com/kouros/navigation/ui/components/SectionTitle.kt new file mode 100644 index 0000000..eccdec7 --- /dev/null +++ b/app/src/main/java/com/kouros/navigation/ui/components/SectionTitle.kt @@ -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) + ) +} \ No newline at end of file diff --git a/app/src/main/java/com/kouros/navigation/ui/components/SettingItem.kt b/app/src/main/java/com/kouros/navigation/ui/components/SettingItem.kt new file mode 100644 index 0000000..ba8edec --- /dev/null +++ b/app/src/main/java/com/kouros/navigation/ui/components/SettingItem.kt @@ -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) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/kouros/navigation/ui/components/SettingSwitch.kt b/app/src/main/java/com/kouros/navigation/ui/components/SettingSwitch.kt new file mode 100644 index 0000000..56e75ca --- /dev/null +++ b/app/src/main/java/com/kouros/navigation/ui/components/SettingSwitch.kt @@ -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 + ) + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/kouros/navigation/ui/components/ThemeColorPicker.kt b/app/src/main/java/com/kouros/navigation/ui/components/ThemeColorPicker.kt new file mode 100644 index 0000000..0ca8354 --- /dev/null +++ b/app/src/main/java/com/kouros/navigation/ui/components/ThemeColorPicker.kt @@ -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) + } + ) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/kouros/navigation/ui/navigation/AppNavGraph.kt b/app/src/main/java/com/kouros/navigation/ui/navigation/AppNavGraph.kt new file mode 100644 index 0000000..7657e69 --- /dev/null +++ b/app/src/main/java/com/kouros/navigation/ui/navigation/AppNavGraph.kt @@ -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() } } + + } +} diff --git a/app/src/main/java/com/kouros/navigation/ui/DisplayScreenSettings.kt b/app/src/main/java/com/kouros/navigation/ui/settings/DisplaySettings.kt similarity index 86% rename from app/src/main/java/com/kouros/navigation/ui/DisplayScreenSettings.kt rename to app/src/main/java/com/kouros/navigation/ui/settings/DisplaySettings.kt index 7aaa75f..9e6bd30 100644 --- a/app/src/main/java/com/kouros/navigation/ui/DisplayScreenSettings.kt +++ b/app/src/main/java/com/kouros/navigation/ui/settings/DisplaySettings.kt @@ -1,4 +1,4 @@ -package com.kouros.navigation.ui +package com.kouros.navigation.ui.settings import android.content.Context 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.SettingsTileDefaults import com.kouros.data.R -import com.kouros.navigation.data.Constants.DARK_MODE_SETTINGS -import com.kouros.navigation.data.Constants.SHOW_THREED_BUILDING -import com.kouros.navigation.utils.NavigationUtils +import com.kouros.navigation.utils.getSettingsViewModel @OptIn(ExperimentalMaterial3Api::class) @Composable -fun DisplayScreenSettings(context: Context, navigateBack: () -> Unit) { +fun DisplaySettings(context: Context, navigateBack: () -> Unit) { Scaffold( topBar = { CenterAlignedTopAppBar( @@ -75,13 +73,11 @@ fun DisplayScreenSettings(context: Context, navigateBack: () -> Unit) { @Composable private fun DisplaySettings(context: Context) { + val settingsViewModel = getSettingsViewModel(context) Section(title = "Anzeige") { val state = remember { mutableStateOf( - NavigationUtils.getBooleanKeyValue( - context, - SHOW_THREED_BUILDING - ) + settingsViewModel.threedBuilding.value ) } SettingsCheckbox( @@ -89,17 +85,14 @@ private fun DisplaySettings(context: Context) { title = { Text(text = stringResource(R.string.threed_building)) }, onCheckedChange = { state.value = it - NavigationUtils.setBooleanKeyValue(context, it, SHOW_THREED_BUILDING) + settingsViewModel.onThreedBuildingChanged(it) }, ) } Section(title = "Dunkles Design") { val state = remember { mutableIntStateOf( - NavigationUtils.getIntKeyValue( - context, - DARK_MODE_SETTINGS - ) + settingsViewModel.darkMode.value ) } DarkModeData(context).darkDesign.forEach { sampleItem -> @@ -108,7 +101,7 @@ private fun DisplaySettings(context: Context) { title = { Text(text = sampleItem.title) }, onClick = { state.intValue = sampleItem.key - NavigationUtils.setIntKeyValue(context, state.intValue, DARK_MODE_SETTINGS) + settingsViewModel.onDarkModeChanged(state.intValue) }, ) } diff --git a/app/src/main/java/com/kouros/navigation/ui/NavigationScreenSettings.kt b/app/src/main/java/com/kouros/navigation/ui/settings/NavigationScreenSettings.kt similarity index 82% rename from app/src/main/java/com/kouros/navigation/ui/NavigationScreenSettings.kt rename to app/src/main/java/com/kouros/navigation/ui/settings/NavigationScreenSettings.kt index f2ec4cb..7867917 100644 --- a/app/src/main/java/com/kouros/navigation/ui/NavigationScreenSettings.kt +++ b/app/src/main/java/com/kouros/navigation/ui/settings/NavigationScreenSettings.kt @@ -1,4 +1,4 @@ -package com.kouros.navigation.ui +package com.kouros.navigation.ui.settings import android.content.Context 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.kouros.data.R 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.RouteEngine import com.kouros.navigation.utils.NavigationUtils +import com.kouros.navigation.utils.getSettingsViewModel @OptIn(ExperimentalMaterial3Api::class) @Composable @@ -68,13 +68,11 @@ fun NavigationScreenSettings(context: Context, navigateBack: () -> Unit) { @Composable private fun NavigationSettings(context: Context) { + val settingsViewModel = getSettingsViewModel(context) Section(title = stringResource(id = R.string.options)) { val avoidMotorwayState = remember { mutableStateOf( - NavigationUtils.getBooleanKeyValue( - context, - Constants.AVOID_MOTORWAY - ) + settingsViewModel.avoidMotorway.value ) } SettingsCheckbox( @@ -82,16 +80,13 @@ private fun NavigationSettings(context: Context) { title = { Text(text = stringResource(id = R.string.avoid_highways_row_title)) }, onCheckedChange = { avoidMotorwayState.value = it - NavigationUtils.setBooleanKeyValue(context, it, Constants.AVOID_MOTORWAY) + settingsViewModel.onAvoidMotorway(it) }, ) val avoidTollwayState = remember { mutableStateOf( - NavigationUtils.getBooleanKeyValue( - context, - Constants.AVOID_TOLLWAY - ) + settingsViewModel.avoidTollway.value ) } SettingsCheckbox( @@ -99,16 +94,13 @@ private fun NavigationSettings(context: Context) { title = { Text(text = stringResource(id = R.string.avoid_tolls_row_title)) }, onCheckedChange = { avoidTollwayState.value = it - NavigationUtils.setBooleanKeyValue(context, it, Constants.AVOID_TOLLWAY) + settingsViewModel.onAvoidTollway(it) }, ) val carLocationState = remember { mutableStateOf( - NavigationUtils.getBooleanKeyValue( - context, - Constants.CAR_LOCATION - ) + settingsViewModel.carLocation.value ) } SettingsCheckbox( @@ -116,7 +108,7 @@ private fun NavigationSettings(context: Context) { title = { Text(text = stringResource(id = R.string.use_car_location)) }, onCheckedChange = { 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)) { val state = remember { mutableIntStateOf( - NavigationUtils.getIntKeyValue( - context, - ROUTING_ENGINE - ) + settingsViewModel.routingEngine.value ) } RoutingEngineData.engines.forEach { sampleItem -> @@ -136,7 +125,7 @@ private fun NavigationSettings(context: Context) { title = { Text(text = sampleItem.title) }, onClick = { state.intValue = sampleItem.key - NavigationUtils.setIntKeyValue(context, state.intValue, ROUTING_ENGINE) + settingsViewModel.onRoutingEngineChanged(state.intValue) }, ) } diff --git a/app/src/main/java/com/kouros/navigation/ui/settings/SettingsRoute.kt b/app/src/main/java/com/kouros/navigation/ui/settings/SettingsRoute.kt new file mode 100644 index 0000000..c3fc435 --- /dev/null +++ b/app/src/main/java/com/kouros/navigation/ui/settings/SettingsRoute.kt @@ -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 create(modelClass: Class): 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()) +} diff --git a/app/src/main/java/com/kouros/navigation/ui/settings/SettingsScreen.kt b/app/src/main/java/com/kouros/navigation/ui/settings/SettingsScreen.kt new file mode 100644 index 0000000..6f26d1d --- /dev/null +++ b/app/src/main/java/com/kouros/navigation/ui/settings/SettingsScreen.kt @@ -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 + ) + } + } +} + + + + + + + + + + + + + + diff --git a/app/src/main/java/com/kouros/navigation/ui/theme/Theme.kt b/app/src/main/java/com/kouros/navigation/ui/theme/Theme.kt index 20a2cfe..1319608 100644 --- a/app/src/main/java/com/kouros/navigation/ui/theme/Theme.kt +++ b/app/src/main/java/com/kouros/navigation/ui/theme/Theme.kt @@ -102,7 +102,7 @@ fun NavigationTheme( } MaterialTheme( - colorScheme = colorScheme, + colorScheme = if (useDarkTheme) darkColorScheme() else colorScheme, typography = typography, content = content, shapes = shapes, diff --git a/common/car/build.gradle.kts b/common/car/build.gradle.kts index 655b4c7..c865cac 100644 --- a/common/car/build.gradle.kts +++ b/common/car/build.gradle.kts @@ -56,6 +56,7 @@ dependencies { implementation(libs.androidx.material3) implementation(libs.androidx.compose.ui.text) implementation(libs.play.services.location) + implementation(libs.androidx.datastore.core) androidTestImplementation(libs.androidx.junit) testImplementation(libs.junit) } \ No newline at end of file diff --git a/common/car/src/main/java/com/kouros/navigation/car/NavigationSession.kt b/common/car/src/main/java/com/kouros/navigation/car/NavigationSession.kt index 7930831..7a09590 100644 --- a/common/car/src/main/java/com/kouros/navigation/car/NavigationSession.kt +++ b/common/car/src/main/java/com/kouros/navigation/car/NavigationSession.kt @@ -2,6 +2,7 @@ package com.kouros.navigation.car import android.Manifest import android.annotation.SuppressLint +import android.app.Application import android.content.Context import android.content.Intent import android.content.pm.PackageManager @@ -26,6 +27,9 @@ import androidx.lifecycle.DefaultLifecycleObserver import androidx.lifecycle.Lifecycle import androidx.lifecycle.LifecycleObserver 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.screen.NavigationScreen 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.TAG import com.kouros.navigation.data.RouteEngine +import com.kouros.navigation.data.datastore.DataStoreManager import com.kouros.navigation.data.osrm.OsrmRepository import com.kouros.navigation.data.tomtom.TomTomRepository import com.kouros.navigation.data.valhalla.ValhallaRepository -import com.kouros.navigation.model.ViewModel +import com.kouros.navigation.model.NavigationViewModel import com.kouros.navigation.utils.GeoUtils.snapLocation -import com.kouros.navigation.utils.NavigationUtils.getBooleanKeyValue 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 { @@ -54,7 +65,8 @@ class NavigationSession : Session(), NavigationScreen.Listener { lateinit var surfaceRenderer: SurfaceRenderer var mLocationListener: LocationListenerCompat = LocationListenerCompat { location: Location? -> - val useCarLocation = getBooleanKeyValue(carContext, CAR_LOCATION) + val repository = getSettingsRepository(carContext) + val useCarLocation = runBlocking { repository.carLocationFlow.first() } if (!useCarLocation) { updateLocation(location!!) } @@ -75,7 +87,8 @@ class NavigationSession : Session(), NavigationScreen.Listener { override fun onDestroy(owner: LifecycleOwner) { 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) { val carSensors = carContext.getCarService(CarHardwareManager::class.java).carSensors 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 = OnCarDataAvailableListener { data -> if (data.location.status == CarValue.STATUS_SUCCESS) { @@ -123,22 +137,39 @@ class NavigationSession : Session(), NavigationScreen.Listener { fun onRoutingEngineStateUpdated(routeEngine : Int) { navigationViewModel = when (routeEngine) { - RouteEngine.VALHALLA.ordinal -> ViewModel(ValhallaRepository()) - RouteEngine.OSRM.ordinal -> ViewModel(OsrmRepository()) - else -> ViewModel(TomTomRepository()) + RouteEngine.VALHALLA.ordinal -> NavigationViewModel(ValhallaRepository()) + RouteEngine.OSRM.ordinal -> NavigationViewModel(OsrmRepository()) + else -> NavigationViewModel(TomTomRepository()) } } 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.routingEngine.observe(this, ::onRoutingEngineStateUpdated) routeModel = RouteCarModel() - surfaceRenderer = SurfaceRenderer(carContext, lifecycle, routeModel) + surfaceRenderer = SurfaceRenderer(carContext, lifecycle, routeModel, viewModelStoreOwner) navigationScreen = NavigationScreen(carContext, surfaceRenderer, routeModel, this, navigationViewModel) @@ -175,7 +206,8 @@ class NavigationSession : Session(), NavigationScreen.Listener { fun addSensors() { 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) { val carSensors = carContext.getCarService(CarHardwareManager::class.java).carSensors carSensors.addCompassListener(CarSensors.UPDATE_RATE_NORMAL, @@ -269,8 +301,8 @@ class NavigationSession : Session(), NavigationScreen.Listener { } } - override fun stopNavigation() { - routeModel.stopNavigation() + override fun stopNavigation(context: CarContext) { + routeModel.stopNavigation(context) } diff --git a/common/car/src/main/java/com/kouros/navigation/car/SurfaceRenderer.kt b/common/car/src/main/java/com/kouros/navigation/car/SurfaceRenderer.kt index 6dd882e..806021a 100644 --- a/common/car/src/main/java/com/kouros/navigation/car/SurfaceRenderer.kt +++ b/common/car/src/main/java/com/kouros/navigation/car/SurfaceRenderer.kt @@ -14,6 +14,7 @@ import androidx.car.app.connection.CarConnection import androidx.compose.foundation.layout.PaddingValues import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.livedata.observeAsState import androidx.compose.ui.platform.ComposeView @@ -21,6 +22,7 @@ import androidx.lifecycle.DefaultLifecycleObserver import androidx.lifecycle.Lifecycle import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModelStoreOwner import androidx.lifecycle.setViewTreeLifecycleOwner import androidx.savedstate.setViewTreeSavedStateRegistryOwner 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.rememberBaseStyle 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.homeVogelhart import com.kouros.navigation.data.ObjectBox import com.kouros.navigation.data.RouteEngine -import com.kouros.navigation.data.tomtom.TrafficData import com.kouros.navigation.model.BaseStyleModel import com.kouros.navigation.model.RouteModel -import com.kouros.navigation.utils.NavigationUtils.getIntKeyValue import com.kouros.navigation.utils.bearing import com.kouros.navigation.utils.calculateTilt import com.kouros.navigation.utils.calculateZoom 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.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.CameraState import org.maplibre.compose.style.BaseStyle @@ -52,7 +56,8 @@ import org.maplibre.spatialk.geojson.Position class SurfaceRenderer( private var carContext: CarContext, lifecycle: Lifecycle, - private var routeModel: RouteCarModel + private var routeModel: RouteCarModel, + private var viewModelStoreOwner: ViewModelStoreOwner ) : DefaultLifecycleObserver { var lastLocation = location(0.0, 0.0) @@ -187,9 +192,12 @@ class SurfaceRenderer( @Composable 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 route: String? by routeData.observeAsState() val traffic: Map ? by trafficData.observeAsState() @@ -197,7 +205,17 @@ class SurfaceRenderer( val paddingValues = getPaddingValues(height, viewStyle) val cameraState = cameraState(paddingValues, position, tilt) 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) } @@ -339,7 +357,8 @@ class SurfaceRenderer( } 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) { updateLocation(location) } diff --git a/common/car/src/main/java/com/kouros/navigation/car/map/MapView.kt b/common/car/src/main/java/com/kouros/navigation/car/map/MapView.kt index 6340255..96c97cb 100644 --- a/common/car/src/main/java/com/kouros/navigation/car/map/MapView.kt +++ b/common/car/src/main/java/com/kouros/navigation/car/map/MapView.kt @@ -26,15 +26,18 @@ import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.rememberTextMeasurer import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp +import androidx.lifecycle.ViewModel +import androidx.lifecycle.ViewModelProvider import com.kouros.data.R import com.kouros.navigation.car.ViewStyle 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.RouteColor import com.kouros.navigation.data.SpeedColor +import com.kouros.navigation.data.datastore.DataStoreManager 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.CameraState import org.maplibre.compose.camera.rememberCameraState @@ -92,7 +95,8 @@ fun MapLibre( route: String?, traffic: Map?, viewStyle: ViewStyle, - speedCameras: String? = "" + speedCameras: String? = "", + showBuildings: Boolean ) { MaplibreMap( options = MapOptions( @@ -103,7 +107,7 @@ fun MapLibre( baseStyle = baseStyle ) { getBaseSource(id = "openmaptiles")?.let { tiles -> - if (!getBooleanKeyValue(context = context, SHOW_THREED_BUILDING)) { + if (!showBuildings) { BuildingLayer(tiles) } if (viewStyle == ViewStyle.AMENITY_VIEW) { @@ -553,3 +557,4 @@ fun PuckState(cameraState: CameraState, userLocationState: UserLocationState) { ) } + diff --git a/common/car/src/main/java/com/kouros/navigation/car/screen/CategoriesScreen.kt b/common/car/src/main/java/com/kouros/navigation/car/screen/CategoriesScreen.kt index eab6cd5..4bd3281 100644 --- a/common/car/src/main/java/com/kouros/navigation/car/screen/CategoriesScreen.kt +++ b/common/car/src/main/java/com/kouros/navigation/car/screen/CategoriesScreen.kt @@ -1,6 +1,5 @@ package com.kouros.navigation.car.screen -import android.location.Location import androidx.car.app.CarContext import androidx.car.app.Screen import androidx.car.app.model.Action @@ -14,17 +13,16 @@ import androidx.core.graphics.drawable.IconCompat import com.kouros.data.R import com.kouros.navigation.car.SurfaceRenderer import com.kouros.navigation.car.ViewStyle -import com.kouros.navigation.car.navigation.RouteCarModel import com.kouros.navigation.data.Category import com.kouros.navigation.data.Constants.CHARGING_STATION import com.kouros.navigation.data.Constants.FUEL_STATION import com.kouros.navigation.data.Constants.PHARMACY -import com.kouros.navigation.model.ViewModel +import com.kouros.navigation.model.NavigationViewModel class CategoriesScreen( private val carContext: CarContext, private val surfaceRenderer: SurfaceRenderer, - private val viewModel: ViewModel, + private val navigationViewModel: NavigationViewModel, ) : Screen(carContext) { var categories: List = listOf( @@ -48,7 +46,7 @@ class CategoriesScreen( carContext, surfaceRenderer, it.id, - viewModel + navigationViewModel ) ) { obj: Any? -> if (obj != null) { diff --git a/common/car/src/main/java/com/kouros/navigation/car/screen/CategoryScreen.kt b/common/car/src/main/java/com/kouros/navigation/car/screen/CategoryScreen.kt index 53bcd12..a5dcdb8 100644 --- a/common/car/src/main/java/com/kouros/navigation/car/screen/CategoryScreen.kt +++ b/common/car/src/main/java/com/kouros/navigation/car/screen/CategoryScreen.kt @@ -1,6 +1,5 @@ package com.kouros.navigation.car.screen -import android.location.Location import androidx.annotation.DrawableRes import androidx.car.app.CarContext import androidx.car.app.Screen @@ -19,11 +18,10 @@ import androidx.lifecycle.Observer import com.kouros.data.R import com.kouros.navigation.car.SurfaceRenderer 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.Place 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.location import com.kouros.navigation.utils.round @@ -33,7 +31,7 @@ class CategoryScreen( private val carContext: CarContext, private val surfaceRenderer: SurfaceRenderer, private val category: String, - private val viewModel: ViewModel, + private val navigationViewModel: NavigationViewModel, ) : Screen(carContext) { var elements = listOf() @@ -57,8 +55,8 @@ class CategoryScreen( } init { - viewModel.elements.observe(this, observer) - viewModel.getAmenities(category, surfaceRenderer.lastLocation) + navigationViewModel.elements.observe(this, observer) + navigationViewModel.getAmenities(category, surfaceRenderer.lastLocation) } @@ -130,7 +128,7 @@ class CategoryScreen( row.addAction( Action.Builder() .setOnClickListener { - viewModel.loadRoute( + navigationViewModel.loadRoute( carContext, currentLocation = surfaceRenderer.lastLocation, location(it.lon!!, it.lat!!), diff --git a/common/car/src/main/java/com/kouros/navigation/car/screen/DarkModeSettings.kt b/common/car/src/main/java/com/kouros/navigation/car/screen/DarkModeSettings.kt index 2e95394..e46122f 100644 --- a/common/car/src/main/java/com/kouros/navigation/car/screen/DarkModeSettings.kt +++ b/common/car/src/main/java/com/kouros/navigation/car/screen/DarkModeSettings.kt @@ -10,23 +10,27 @@ import androidx.car.app.model.ListTemplate import androidx.car.app.model.Row import androidx.car.app.model.SectionedItemList import androidx.car.app.model.Template +import androidx.lifecycle.lifecycleScope import com.kouros.data.R -import com.kouros.navigation.data.Constants.DARK_MODE_SETTINGS -import com.kouros.navigation.data.Constants.SHOW_THREED_BUILDING -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 +import com.kouros.navigation.utils.getSettingsViewModel +import kotlinx.coroutines.launch class DarkModeSettings(private val carContext: CarContext) : Screen(carContext) { private var darkModeSettings = 0 + val settingsViewModel = getSettingsViewModel(carContext) + init { - darkModeSettings = getIntKeyValue(carContext, DARK_MODE_SETTINGS) + lifecycleScope.launch { + settingsViewModel.darkMode.collect { + invalidate() + } + } } override fun onGetTemplate(): Template { + darkModeSettings = settingsViewModel.darkMode.value val templateBuilder = ListTemplate.Builder() val radioList = ItemList.Builder() @@ -52,10 +56,12 @@ class DarkModeSettings(private val carContext: CarContext) : Screen(carContext) .build() return templateBuilder - .addSectionedList(SectionedItemList.create( - radioList, - carContext.getString(R.string.dark_mode) - )) + .addSectionedList( + SectionedItemList.create( + radioList, + carContext.getString(R.string.dark_mode) + ) + ) .setHeader( Header.Builder() .setTitle(carContext.getString(R.string.dark_mode)) @@ -67,7 +73,7 @@ class DarkModeSettings(private val carContext: CarContext) : Screen(carContext) private fun onSelected(index: Int) { - setIntKeyValue(carContext, index, DARK_MODE_SETTINGS) + settingsViewModel.onDarkModeChanged(index) CarToast.makeText( carContext, (carContext diff --git a/common/car/src/main/java/com/kouros/navigation/car/screen/DisplaySettings.kt b/common/car/src/main/java/com/kouros/navigation/car/screen/DisplaySettings.kt index c6966de..6d0fde0 100644 --- a/common/car/src/main/java/com/kouros/navigation/car/screen/DisplaySettings.kt +++ b/common/car/src/main/java/com/kouros/navigation/car/screen/DisplaySettings.kt @@ -6,33 +6,34 @@ import androidx.car.app.model.Action import androidx.car.app.model.Header import androidx.car.app.model.ItemList import androidx.car.app.model.ListTemplate -import androidx.car.app.model.OnClickListener import androidx.car.app.model.Row import androidx.car.app.model.Template import androidx.car.app.model.Toggle +import androidx.lifecycle.lifecycleScope import com.kouros.data.R -import com.kouros.navigation.data.Constants.SHOW_THREED_BUILDING -import com.kouros.navigation.utils.NavigationUtils.getBooleanKeyValue -import com.kouros.navigation.utils.NavigationUtils.setBooleanKeyValue +import com.kouros.navigation.utils.getSettingsViewModel +import kotlinx.coroutines.launch class DisplaySettings(private val carContext: CarContext) : Screen(carContext) { private var buildingToggleState = false + val settingsViewModel = getSettingsViewModel(carContext) init { - buildingToggleState = getBooleanKeyValue(carContext, SHOW_THREED_BUILDING) + lifecycleScope.launch { + settingsViewModel.threedBuilding.collect { + invalidate() + } + } } override fun onGetTemplate(): Template { + buildingToggleState = settingsViewModel.threedBuilding.value val listBuilder = ItemList.Builder() val buildingToggle: Toggle = Toggle.Builder { checked: Boolean -> - if (checked) { - setBooleanKeyValue(carContext, true, SHOW_THREED_BUILDING) - } else { - setBooleanKeyValue(carContext, false, SHOW_THREED_BUILDING) - } + settingsViewModel.onThreedBuildingChanged(checked) buildingToggleState = !buildingToggleState }.setChecked(buildingToggleState).build() listBuilder.addItem(buildRowForTemplate(R.string.threed_building, buildingToggle)) diff --git a/common/car/src/main/java/com/kouros/navigation/car/screen/NavigationScreen.kt b/common/car/src/main/java/com/kouros/navigation/car/screen/NavigationScreen.kt index afc2220..e23c390 100644 --- a/common/car/src/main/java/com/kouros/navigation/car/screen/NavigationScreen.kt +++ b/common/car/src/main/java/com/kouros/navigation/car/screen/NavigationScreen.kt @@ -22,6 +22,7 @@ import androidx.car.app.navigation.model.NavigationTemplate import androidx.car.app.navigation.model.RoutingInfo import androidx.core.graphics.drawable.IconCompat import androidx.lifecycle.Observer +import androidx.lifecycle.lifecycleScope import com.kouros.data.R import com.kouros.navigation.car.SurfaceRenderer 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.DESTINATION_ARRIVAL_DISTANCE 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.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.getSettingsViewModel 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.ZoneOffset import kotlin.math.absoluteValue @@ -43,21 +51,21 @@ class NavigationScreen( private var surfaceRenderer: SurfaceRenderer, private var routeModel: RouteCarModel, private var listener: Listener, - private val viewModel: ViewModel + private val navigationViewModel: NavigationViewModel ) : Screen(carContext) { /** A listener for navigation start and stop signals. */ interface Listener { /** Stops navigation. */ - fun stopNavigation() + fun stopNavigation(context: CarContext) } var currentNavigationLocation = Location(LocationManager.GPS_PROVIDER) var recentPlace = Place() 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 { route -> if (route.isNotEmpty()) { navigationType = NavigationType.NAVIGATION @@ -99,7 +107,7 @@ class NavigationScreen( speedCameras = cameras val coordinates = mutableListOf>() cameras.forEach { - coordinates.add(listOf(it.lon!!, it.lat!!)) + coordinates.add(listOf(it.lon, it.lat)) } val speedData = GeoUtils.createPointCollection(coordinates, "radar") surfaceRenderer.speedCamerasData.value = speedData @@ -107,11 +115,15 @@ class NavigationScreen( init { - viewModel.route.observe(this, observer) - viewModel.traffic.observe(this, trafficObserver); - viewModel.recentPlace.observe(this, recentObserver) - viewModel.placeLocation.observe(this, placeObserver) - viewModel.speedCameras.observe(this, speedObserver) + navigationViewModel.route.observe(this, observer) + navigationViewModel.traffic.observe(this, trafficObserver); + navigationViewModel.recentPlace.observe(this, recentObserver) + navigationViewModel.placeLocation.observe(this, placeObserver) + navigationViewModel.speedCameras.observe(this, speedObserver) + lifecycleScope.launch { + getSettingsViewModel(carContext).routingEngine.collect { + } + } } override fun onGetTemplate(): Template { @@ -306,7 +318,7 @@ class NavigationScreen( ) .setOnClickListener { val navigateTo = location(recentPlace.longitude, recentPlace.latitude) - viewModel.loadRoute( + navigationViewModel.loadRoute( carContext, surfaceRenderer.lastLocation, navigateTo, @@ -349,7 +361,7 @@ class NavigationScreen( return Action.Builder() .setIcon(routeModel.createCarIcon(carContext, R.drawable.settings_48px)) .setOnClickListener { - screenManager.push(SettingsScreen(carContext, viewModel)) + screenManager.push(SettingsScreen(carContext, navigationViewModel)) } .build() } @@ -411,13 +423,13 @@ class NavigationScreen( SearchScreen( carContext, surfaceRenderer, - viewModel + navigationViewModel ) ) { obj: Any? -> if (obj != null) { val place = obj as Place if (place.longitude == 0.0) { - viewModel.findAddress( + navigationViewModel.findAddress( "${obj.city} ${obj.street}},", currentNavigationLocation ) @@ -432,9 +444,9 @@ class NavigationScreen( fun navigateToPlace(place: Place) { navigationType = NavigationType.VIEW val location = location(place.longitude, place.latitude) - viewModel.saveRecent(place) + navigationViewModel.saveRecent(place) currentNavigationLocation = location - viewModel.loadRoute( + navigationViewModel.loadRoute( carContext, surfaceRenderer.lastLocation, location, @@ -446,7 +458,7 @@ class NavigationScreen( fun stopNavigation() { navigationType = NavigationType.VIEW - listener.stopNavigation() + listener.stopNavigation(carContext) surfaceRenderer.routeData.value = "" lastCameraSearch = 0 invalidate() @@ -470,7 +482,7 @@ class NavigationScreen( fun reRoute(destination: Place) { val dest = location(destination.longitude, destination.latitude) - viewModel.loadRoute( + navigationViewModel.loadRoute( carContext, surfaceRenderer.lastLocation, dest, @@ -480,14 +492,14 @@ class NavigationScreen( fun updateTrip(location: Location) { 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) { lastTrafficDate = current - viewModel.loadTraffic(carContext, location, surfaceRenderer.carOrientation) + navigationViewModel.loadTraffic(carContext, location, surfaceRenderer.carOrientation) } updateSpeedCamera(location) with(routeModel) { - updateLocation(location, viewModel) + updateLocation(carContext,location, navigationViewModel) if ((navState.maneuverType == Maneuver.TYPE_DESTINATION || navState.maneuverType == Maneuver.TYPE_DESTINATION_LEFT || navState.maneuverType == Maneuver.TYPE_DESTINATION_RIGHT @@ -506,7 +518,7 @@ class NavigationScreen( private fun updateSpeedCamera(location: Location) { if (lastCameraSearch++ % 100 == 0) { - viewModel.getSpeedCameras(location, 5.0) + navigationViewModel.getSpeedCameras(location, 5.0) } if (speedCameras.isNotEmpty()) { updateDistance(location) diff --git a/common/car/src/main/java/com/kouros/navigation/car/screen/NavigationSettings.kt b/common/car/src/main/java/com/kouros/navigation/car/screen/NavigationSettings.kt index f1ea209..2e036fb 100644 --- a/common/car/src/main/java/com/kouros/navigation/car/screen/NavigationSettings.kt +++ b/common/car/src/main/java/com/kouros/navigation/car/screen/NavigationSettings.kt @@ -9,16 +9,14 @@ import androidx.car.app.model.ListTemplate import androidx.car.app.model.Row import androidx.car.app.model.Template import androidx.car.app.model.Toggle +import androidx.lifecycle.lifecycleScope import com.kouros.data.R -import com.kouros.navigation.data.Constants.AVOID_MOTORWAY -import com.kouros.navigation.data.Constants.AVOID_TOLLWAY -import com.kouros.navigation.data.Constants.CAR_LOCATION -import com.kouros.navigation.model.ViewModel -import com.kouros.navigation.utils.NavigationUtils.getBooleanKeyValue -import com.kouros.navigation.utils.NavigationUtils.setBooleanKeyValue +import com.kouros.navigation.model.NavigationViewModel +import com.kouros.navigation.utils.getSettingsViewModel +import kotlinx.coroutines.launch -class NavigationSettings(private val carContext: CarContext, private var viewModel: ViewModel) : +class NavigationSettings(private val carContext: CarContext, private var navigationViewModel: NavigationViewModel) : Screen(carContext) { private var motorWayToggleState = false @@ -27,25 +25,25 @@ class NavigationSettings(private val carContext: CarContext, private var viewMod private var carLocationToggleState = false + val settingsViewModel = getSettingsViewModel(carContext) init { - motorWayToggleState = getBooleanKeyValue(carContext, AVOID_MOTORWAY) - - tollWayToggleState = getBooleanKeyValue(carContext, AVOID_MOTORWAY) - - carLocationToggleState = getBooleanKeyValue(carContext, CAR_LOCATION) - + lifecycleScope.launch { + settingsViewModel.avoidTollway.collect { + invalidate() + } + } } override fun onGetTemplate(): Template { + motorWayToggleState = settingsViewModel.avoidMotorway.value + tollWayToggleState = settingsViewModel.avoidTollway.value + carLocationToggleState = settingsViewModel.carLocation.value + val listBuilder = ItemList.Builder() val highwayToggle: Toggle = Toggle.Builder { checked: Boolean -> - if (checked) { - setBooleanKeyValue(carContext, true, AVOID_MOTORWAY) - } else { - setBooleanKeyValue(carContext, false, AVOID_MOTORWAY) - } + settingsViewModel.onAvoidMotorway(checked) motorWayToggleState = !motorWayToggleState }.setChecked(motorWayToggleState).build() listBuilder.addItem(buildRowForTemplate(R.string.avoid_highways_row_title, highwayToggle)) @@ -53,22 +51,14 @@ class NavigationSettings(private val carContext: CarContext, private var viewMod // Tollway val tollwayToggle: Toggle = Toggle.Builder { checked: Boolean -> - if (checked) { - setBooleanKeyValue(carContext, true, AVOID_TOLLWAY) - } else { - setBooleanKeyValue(carContext, false, AVOID_TOLLWAY) - } + settingsViewModel.onAvoidTollway(checked) tollWayToggleState = !tollWayToggleState }.setChecked(tollWayToggleState).build() listBuilder.addItem(buildRowForTemplate(R.string.avoid_tolls_row_title, tollwayToggle)) val carLocationToggle: Toggle = Toggle.Builder { checked: Boolean -> - if (checked) { - setBooleanKeyValue(carContext, true, CAR_LOCATION) - } else { - setBooleanKeyValue(carContext, false, CAR_LOCATION) - } + settingsViewModel.onCarLocation(checked) carLocationToggleState = !carLocationToggleState }.setChecked(carLocationToggleState).build() @@ -81,7 +71,7 @@ class NavigationSettings(private val carContext: CarContext, private var viewMod listBuilder.addItem( buildRowForScreenTemplate( - RoutingSettings(carContext, viewModel), + RoutingSettings(carContext, navigationViewModel), R.string.routing_engine ) ) diff --git a/common/car/src/main/java/com/kouros/navigation/car/screen/PlaceListScreen.kt b/common/car/src/main/java/com/kouros/navigation/car/screen/PlaceListScreen.kt index 3965270..ee4c544 100644 --- a/common/car/src/main/java/com/kouros/navigation/car/screen/PlaceListScreen.kt +++ b/common/car/src/main/java/com/kouros/navigation/car/screen/PlaceListScreen.kt @@ -1,6 +1,5 @@ package com.kouros.navigation.car.screen -import android.location.Location import android.net.Uri import android.text.Spannable 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.RECENT import com.kouros.navigation.data.Place -import com.kouros.navigation.model.ViewModel +import com.kouros.navigation.model.NavigationViewModel class PlaceListScreen( private val carContext: CarContext, private val surfaceRenderer: SurfaceRenderer, private val category: String, - private val viewModel: ViewModel + private val navigationViewModel: NavigationViewModel ) : Screen(carContext) { var places = listOf() @@ -49,30 +48,30 @@ class PlaceListScreen( init { if (category == RECENT) { - viewModel.places.observe(this, observer) + navigationViewModel.places.observe(this, observer) } if (category == CONTACTS) { - viewModel.contactAddress.observe(this, observerAddress) + navigationViewModel.contactAddress.observe(this, observerAddress) } if (category == FAVORITES) { - viewModel.favorites.observe(this, observer) + navigationViewModel.favorites.observe(this, observer) } loadPlaces() } fun loadPlaces() { if (category == RECENT) { - viewModel.loadRecentPlaces( + navigationViewModel.loadRecentPlaces( carContext, surfaceRenderer.lastLocation, surfaceRenderer.carOrientation ) } if (category == CONTACTS) { - viewModel.loadContacts(carContext) + navigationViewModel.loadContacts(carContext) } if (category == FAVORITES) { - viewModel.loadFavorites( + navigationViewModel.loadFavorites( carContext, surfaceRenderer.lastLocation, surfaceRenderer.carOrientation @@ -110,7 +109,7 @@ class PlaceListScreen( carContext, surfaceRenderer, place, - viewModel + navigationViewModel ) ) { obj: Any? -> if (obj != null) { @@ -163,7 +162,7 @@ class PlaceListScreen( ) ) .setOnClickListener { - viewModel.deletePlace(place) + navigationViewModel.deletePlace(place) CarToast.makeText( carContext, R.string.recent_Item_deleted, CarToast.LENGTH_LONG diff --git a/common/car/src/main/java/com/kouros/navigation/car/screen/RoutePreviewScreen.kt b/common/car/src/main/java/com/kouros/navigation/car/screen/RoutePreviewScreen.kt index 822dc52..be05f27 100644 --- a/common/car/src/main/java/com/kouros/navigation/car/screen/RoutePreviewScreen.kt +++ b/common/car/src/main/java/com/kouros/navigation/car/screen/RoutePreviewScreen.kt @@ -6,7 +6,6 @@ import androidx.annotation.DrawableRes import androidx.car.app.CarContext import androidx.car.app.CarToast import androidx.car.app.Screen -import androidx.car.app.constraints.ConstraintManager import androidx.car.app.model.Action import androidx.car.app.model.Action.FLAG_DEFAULT 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.RouteCarModel 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 java.math.BigDecimal import java.math.RoundingMode @@ -39,7 +38,7 @@ class RoutePreviewScreen( carContext: CarContext, private var surfaceRenderer: SurfaceRenderer, private var destination: Place, - private val viewModel: ViewModel + private val navigationViewModel: NavigationViewModel ) : Screen(carContext) { private var isFavorite = false @@ -56,9 +55,9 @@ class RoutePreviewScreen( } init { - viewModel.previewRoute.observe(this, observer) + navigationViewModel.previewRoute.observe(this, observer) val location = location(destination.longitude, destination.latitude) - viewModel.loadPreviewRoute( + navigationViewModel.loadPreviewRoute( carContext, surfaceRenderer.lastLocation, location, @@ -164,7 +163,7 @@ class RoutePreviewScreen( CarToast.LENGTH_SHORT ) .show() - viewModel.saveFavorite(destination) + navigationViewModel.saveFavorite(destination) invalidate() } .build() @@ -172,7 +171,7 @@ class RoutePreviewScreen( private fun deleteFavoriteAction(): Action = Action.Builder() .setOnClickListener { if (isFavorite) { - viewModel.deleteFavorite(destination) + navigationViewModel.deleteFavorite(destination) } isFavorite = !isFavorite finish() diff --git a/common/car/src/main/java/com/kouros/navigation/car/screen/RoutingSettings.kt b/common/car/src/main/java/com/kouros/navigation/car/screen/RoutingSettings.kt index f39ac10..f139963 100644 --- a/common/car/src/main/java/com/kouros/navigation/car/screen/RoutingSettings.kt +++ b/common/car/src/main/java/com/kouros/navigation/car/screen/RoutingSettings.kt @@ -10,28 +10,28 @@ import androidx.car.app.model.ListTemplate import androidx.car.app.model.Row import androidx.car.app.model.SectionedItemList import androidx.car.app.model.Template -import androidx.car.app.model.Toggle +import androidx.lifecycle.lifecycleScope 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.model.ViewModel -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 +import com.kouros.navigation.model.NavigationViewModel +import com.kouros.navigation.utils.getSettingsViewModel +import kotlinx.coroutines.launch -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 + val settingsViewModel = getSettingsViewModel(carContext) init { - routingEngine = getIntKeyValue(carContext, ROUTING_ENGINE) - + lifecycleScope.launch { + settingsViewModel.routingEngine.collect { + invalidate() + } + } } override fun onGetTemplate(): Template { + routingEngine = settingsViewModel.routingEngine.value val templateBuilder = ListTemplate.Builder() val radioList = ItemList.Builder() @@ -71,8 +71,8 @@ class RoutingSettings(private val carContext: CarContext, private var viewModel: } private fun onSelected(index: Int) { - setIntKeyValue(carContext, index, ROUTING_ENGINE) - viewModel.routingEngine.value = index + settingsViewModel.onRoutingEngineChanged(index) + navigationViewModel.routingEngine.value = index CarToast.makeText( carContext, (carContext diff --git a/common/car/src/main/java/com/kouros/navigation/car/screen/SearchScreen.kt b/common/car/src/main/java/com/kouros/navigation/car/screen/SearchScreen.kt index 4b6ffc9..5c60089 100644 --- a/common/car/src/main/java/com/kouros/navigation/car/screen/SearchScreen.kt +++ b/common/car/src/main/java/com/kouros/navigation/car/screen/SearchScreen.kt @@ -1,7 +1,6 @@ package com.kouros.navigation.car.screen import android.annotation.SuppressLint -import android.location.Location import androidx.car.app.CarContext import androidx.car.app.Screen import androidx.car.app.model.Action @@ -16,18 +15,17 @@ import androidx.lifecycle.Observer import com.kouros.data.R import com.kouros.navigation.car.SurfaceRenderer import com.kouros.navigation.car.ViewStyle -import com.kouros.navigation.car.navigation.RouteCarModel import com.kouros.navigation.data.Category import com.kouros.navigation.data.Constants import com.kouros.navigation.data.Place import com.kouros.navigation.data.nominatim.SearchResult -import com.kouros.navigation.model.ViewModel +import com.kouros.navigation.model.NavigationViewModel class SearchScreen( carContext: CarContext, private var surfaceRenderer: SurfaceRenderer, - private val viewModel: ViewModel, + private val navigationViewModel: NavigationViewModel, ) : Screen(carContext) { var isSearchComplete: Boolean = false @@ -47,7 +45,7 @@ class SearchScreen( } init { - viewModel.searchPlaces.observe(this, observer) + navigationViewModel.searchPlaces.observe(this, observer) } override fun onGetTemplate(): Template { @@ -71,7 +69,7 @@ class SearchScreen( CategoriesScreen( carContext, surfaceRenderer, - viewModel + navigationViewModel ) ) { obj: Any? -> surfaceRenderer.viewStyle = ViewStyle.VIEW @@ -87,7 +85,7 @@ class SearchScreen( carContext, surfaceRenderer, it.id, - viewModel + navigationViewModel ) ) { obj: Any? -> if (obj != null) { @@ -115,7 +113,7 @@ class SearchScreen( object : SearchCallback { override fun onSearchSubmitted(searchTerm: String) { isSearchComplete = true - viewModel.searchPlaces(searchTerm, surfaceRenderer.lastLocation) + navigationViewModel.searchPlaces(searchTerm, surfaceRenderer.lastLocation) } }) .setHeaderAction(Action.BACK) diff --git a/common/car/src/main/java/com/kouros/navigation/car/screen/SettingsScreen.kt b/common/car/src/main/java/com/kouros/navigation/car/screen/SettingsScreen.kt index 7c2be97..742e104 100644 --- a/common/car/src/main/java/com/kouros/navigation/car/screen/SettingsScreen.kt +++ b/common/car/src/main/java/com/kouros/navigation/car/screen/SettingsScreen.kt @@ -24,12 +24,12 @@ import androidx.car.app.model.ListTemplate import androidx.car.app.model.Row import androidx.car.app.model.Template import com.kouros.data.R -import com.kouros.navigation.model.ViewModel +import com.kouros.navigation.model.NavigationViewModel /** A screen demonstrating selectable lists. */ class SettingsScreen( carContext: CarContext, - private var viewModel: ViewModel, + private var navigationViewModel: NavigationViewModel, ) : Screen(carContext) { override fun onGetTemplate(): Template { @@ -42,7 +42,7 @@ class SettingsScreen( ) listBuilder.addItem( buildRowForTemplate( - NavigationSettings(carContext, viewModel), + NavigationSettings(carContext, navigationViewModel), R.string.navigation_settings ) ) diff --git a/common/car/src/test/java/com/kouros/navigation/car/UnitTest.kt b/common/car/src/test/java/com/kouros/navigation/car/UnitTest.kt index 0775d3f..8020d3b 100644 --- a/common/car/src/test/java/com/kouros/navigation/car/UnitTest.kt +++ b/common/car/src/test/java/com/kouros/navigation/car/UnitTest.kt @@ -2,7 +2,7 @@ package com.kouros.navigation.car import com.kouros.navigation.data.valhalla.ValhallaRepository import com.kouros.navigation.model.RouteModel -import com.kouros.navigation.model.ViewModel +import com.kouros.navigation.model.NavigationViewModel import org.junit.Test /** @@ -12,7 +12,7 @@ import org.junit.Test class ViewModelTest { val repo = ValhallaRepository() - val viewModel = ViewModel(repo) + val navigationViewModel = NavigationViewModel(repo) val model = RouteModel() @Test diff --git a/common/data/build.gradle.kts b/common/data/build.gradle.kts index 0954e5b..a6b9b65 100644 --- a/common/data/build.gradle.kts +++ b/common/data/build.gradle.kts @@ -5,6 +5,7 @@ import org.jetbrains.kotlin.gradle.dsl.JvmTarget plugins { alias(libs.plugins.android.library) alias(libs.plugins.kotlin.android) + alias(libs.plugins.kotlin.compose) kotlin("plugin.serialization") version "2.2.21" kotlin("kapt") } @@ -19,6 +20,10 @@ android { consumerProguardFiles("consumer-rules.pro") } + buildFeatures { + compose = true + } + buildTypes { release { isMinifyEnabled = false @@ -40,6 +45,10 @@ android { } dependencies { + val composeBom = platform(libs.androidx.compose.bom) + implementation(composeBom) + androidTestImplementation(composeBom) + implementation(libs.androidx.core.ktx) implementation(libs.androidx.appcompat) implementation(libs.material) @@ -49,6 +58,7 @@ dependencies { implementation(libs.koin.compose.viewmodel) implementation(libs.androidx.car.app) implementation(libs.android.sdk.turf) + implementation(libs.androidx.compose.runtime) // objectbox @@ -56,6 +66,8 @@ dependencies { implementation(libs.androidx.material3) annotationProcessor(libs.objectbox.processor) + implementation(libs.androidx.datastore.preferences) + implementation(libs.kotlinx.serialization.json) implementation(libs.maplibre.compose) implementation("androidx.compose.material:material-icons-extended:1.7.8") diff --git a/common/data/src/main/java/com/kouros/navigation/data/Data.kt b/common/data/src/main/java/com/kouros/navigation/data/Data.kt index f445df8..7c2ba06 100644 --- a/common/data/src/main/java/com/kouros/navigation/data/Data.kt +++ b/common/data/src/main/java/com/kouros/navigation/data/Data.kt @@ -139,7 +139,9 @@ object Constants { const val CAR_LOCATION = "CarLocation" const val ROUTING_ENGINE = "RoutingEngine" - const val NEXT_STEP_THRESHOLD = 120.0 + const val LAST_ROUTE = "LastRoute" + + const val NEXT_STEP_THRESHOLD = 500.0 const val MAXIMAL_SNAP_CORRECTION = 50.0 @@ -157,3 +159,12 @@ object Constants { enum class RouteEngine { VALHALLA, OSRM, TOMTOM, GRAPHHOPPER } + +enum class NavigationThemeColor(val color: Long) { + RED(0xFFD32F2F), + ORANGE(0xFFF57C00), + YELLOW(0xFFFBC02D), + GREEN(0xFF388E3C), + BLUE(0xFF1976D2), + PURPLE(0xFF7B1FA2) +} diff --git a/common/data/src/main/java/com/kouros/navigation/data/NavigationRepository.kt b/common/data/src/main/java/com/kouros/navigation/data/NavigationRepository.kt index e00f061..7d9ed5e 100644 --- a/common/data/src/main/java/com/kouros/navigation/data/NavigationRepository.kt +++ b/common/data/src/main/java/com/kouros/navigation/data/NavigationRepository.kt @@ -18,7 +18,11 @@ package com.kouros.navigation.data import android.content.Context import android.location.Location +import com.google.gson.GsonBuilder import com.kouros.data.R +import com.kouros.navigation.data.osrm.OsrmRepository +import com.kouros.navigation.data.osrm.OsrmResponse +import com.kouros.navigation.data.osrm.OsrmRoute import com.kouros.navigation.model.RouteModel import com.kouros.navigation.utils.GeoUtils.calculateSquareRadius import java.net.Authenticator @@ -50,10 +54,18 @@ abstract class NavigationRepository { searchFilter: SearchFilter, context: Context ): Double { - val route = getRoute(context, currentLocation, location, carOrientation, searchFilter) - val routeModel = RouteModel() - routeModel.startNavigation(route, context) - return routeModel.curRoute.summary.distance + val osrm = OsrmRepository() + val route = osrm.getRoute(context, currentLocation, location, carOrientation, searchFilter) + val gson = GsonBuilder().serializeNulls().create() + val osrmJson = gson.fromJson(route, OsrmResponse::class.java) + if (osrmJson.routes.isEmpty()) { + return 0.0 + } + return osrmJson.routes.first().distance + // return osrmJson.destinations.first().distance?.toDouble() ?: 0.0 + ///val routeModel = RouteModel() + //routeModel.startNavigation(route, context) + //return routeModel.curRoute.summary.distance } fun searchPlaces(search: String, location: Location): String { diff --git a/common/data/src/main/java/com/kouros/navigation/data/Route.kt b/common/data/src/main/java/com/kouros/navigation/data/Route.kt index 8879603..69887da 100644 --- a/common/data/src/main/java/com/kouros/navigation/data/Route.kt +++ b/common/data/src/main/java/com/kouros/navigation/data/Route.kt @@ -99,8 +99,8 @@ data class Route( } } - fun nextStep(steps : Int): Step { - val nextIndex = currentStepIndex + steps + fun nextStep(add: Int): Step { + val nextIndex = currentStepIndex + add return if (isRouteValid() && nextIndex < legs().first().steps.size) { legs().first().steps[nextIndex] } else { diff --git a/common/data/src/main/java/com/kouros/navigation/data/datastore/DataStoreManager.kt b/common/data/src/main/java/com/kouros/navigation/data/datastore/DataStoreManager.kt new file mode 100644 index 0000000..1ae65ba --- /dev/null +++ b/common/data/src/main/java/com/kouros/navigation/data/datastore/DataStoreManager.kt @@ -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 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 = + context.dataStore.data.map { preferences -> + preferences[PreferencesKeys.THREED_BUILDING] == true + } + val darkModeFlow: Flow = + context.dataStore.data.map { preferences -> + preferences[PreferencesKeys.DARK_MODE] + ?: 0 + } + + val avoidMotorwayFlow: Flow = + context.dataStore.data.map { preferences -> + preferences[PreferencesKeys.AVOID_MOTORWAY] == true + } + + val avoidTollwayFlow: Flow = + context.dataStore.data.map { preferences -> + preferences[PreferencesKeys.AVOID_TOLLWAY] == true + } + + val useCarLocationFlow: Flow = + context.dataStore.data.map { preferences -> + preferences[PreferencesKeys.CAR_LOCATION] == true + } + + val routingEngineFlow: Flow = + 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 + } + } +} diff --git a/common/data/src/main/java/com/kouros/navigation/data/osrm/OsrmRepository.kt b/common/data/src/main/java/com/kouros/navigation/data/osrm/OsrmRepository.kt index 5eaac6b..9ef717e 100644 --- a/common/data/src/main/java/com/kouros/navigation/data/osrm/OsrmRepository.kt +++ b/common/data/src/main/java/com/kouros/navigation/data/osrm/OsrmRepository.kt @@ -5,7 +5,9 @@ import android.location.Location import com.kouros.navigation.data.NavigationRepository import com.kouros.navigation.data.SearchFilter -private const val routeUrl = "https://kouros-online.de/osrm/route/v1/driving/" +//private const val routeUrl = "https://kouros-online.de/osrm/route/v1/driving/" + +private const val routeUrl = "https://router.project-osrm.org/route/v1/driving/" class OsrmRepository : NavigationRepository() { override fun getRoute( @@ -23,7 +25,7 @@ class OsrmRepository : NavigationRepository() { if (searchFilter.avoidTollway) { exclude = "$exclude&exclude=toll" } - val routeLocation = "${currentLocation.longitude},${currentLocation.latitude};${location.longitude},${location.latitude}?steps=true&alternatives=0" + val routeLocation = "${currentLocation.longitude},${currentLocation.latitude};${location.longitude},${location.latitude}?steps=true&alternatives=false" return fetchUrl(routeUrl + routeLocation + exclude, true) } diff --git a/common/data/src/main/java/com/kouros/navigation/data/overpass/Overpass.kt b/common/data/src/main/java/com/kouros/navigation/data/overpass/Overpass.kt index 59012a0..7190ea2 100644 --- a/common/data/src/main/java/com/kouros/navigation/data/overpass/Overpass.kt +++ b/common/data/src/main/java/com/kouros/navigation/data/overpass/Overpass.kt @@ -10,6 +10,7 @@ import java.net.URL class Overpass { //val overpassUrl = "https://overpass.kumi.systems/api/interpreter" + //val overpassUrl = "https://overpass-api.de/api" val overpassUrl = "https://kouros-online.de/overpass/interpreter" @@ -43,13 +44,13 @@ class Overpass { val boundingBox = getBoundingBox(location.latitude, location.longitude, radius) val httpURLConnection = URL(overpassUrl).openConnection() as HttpURLConnection httpURLConnection.requestMethod = "POST" + // node["highway"="speed_camera"] + // node[amenity=$category] + httpURLConnection.setDoOutput(true); httpURLConnection.setRequestProperty( "Accept", "application/json" ) - // node["highway"="speed_camera"] - // node[amenity=$category] - httpURLConnection.setDoOutput(true); // define search query val searchQuery = """ |[out:json]; @@ -79,7 +80,7 @@ class Overpass { } } catch (e: Exception) { - + println("Speed $e") } return emptyList() } diff --git a/common/data/src/main/java/com/kouros/navigation/data/tomtom/TomTomRepository.kt b/common/data/src/main/java/com/kouros/navigation/data/tomtom/TomTomRepository.kt index 0501d6c..c29f449 100644 --- a/common/data/src/main/java/com/kouros/navigation/data/tomtom/TomTomRepository.kt +++ b/common/data/src/main/java/com/kouros/navigation/data/tomtom/TomTomRepository.kt @@ -10,15 +10,14 @@ import com.kouros.navigation.utils.GeoUtils.calculateSquareRadius private const val routeUrl = "https://api.tomtom.com/routing/1/calculateRoute/" -val tomtomApiKey = "678k5v6940cSXXIS5oD92qIrDgW3RBZ3" +const val tomtomApiKey = "678k5v6940cSXXIS5oD92qIrDgW3RBZ3" -val tomtomTrafficUrl = "https://api.tomtom.com/traffic/services/5/incidentDetails" +const val tomtomTrafficUrl = "https://api.tomtom.com/traffic/services/5/incidentDetails" - -private val tomtomFields = +private const val tomtomFields = "{incidents{type,geometry{type,coordinates},properties{iconCategory,events{description}}}}" -const val useAsset = false +const val useAsset = true class TomTomRepository : NavigationRepository() { override fun getRoute( diff --git a/common/data/src/main/java/com/kouros/navigation/data/tomtom/TomTomRoute.kt b/common/data/src/main/java/com/kouros/navigation/data/tomtom/TomTomRoute.kt index 3646081..d68b87e 100644 --- a/common/data/src/main/java/com/kouros/navigation/data/tomtom/TomTomRoute.kt +++ b/common/data/src/main/java/com/kouros/navigation/data/tomtom/TomTomRoute.kt @@ -61,9 +61,7 @@ class TomTomRoute { route.sections?.forEach { section -> val lanes = mutableListOf() var startIndex = 0 - if (section.startPointIndex <= instruction.pointIndex - 3 - && instruction.pointIndex <= section.endPointIndex - ) { + section.lanes?.forEach { itLane -> val lane = Lane( location( @@ -77,7 +75,7 @@ class TomTomRoute { lanes.add(lane) } intersections.add(Intersection(waypoints[startIndex], lanes)) - } + } allIntersections.addAll(intersections) stepDistance = route.guidance.instructions[index].routeOffsetInMeters - stepDistance @@ -166,13 +164,23 @@ class TomTomRoute { newType = androidx.car.app.navigation.model.Maneuver.TYPE_TURN_SHARP_RIGHT } - "ROUNDABOUT_RIGHT" -> { + "ROUNDABOUT_RIGHT", "ROUNDABOUT_CROSS" -> { newType = androidx.car.app.navigation.model.Maneuver.TYPE_ROUNDABOUT_ENTER_CCW } "ROUNDABOUT_LEFT" -> { newType = androidx.car.app.navigation.model.Maneuver.TYPE_ROUNDABOUT_ENTER_CW } + "MAKE_UTURN" -> { + newType = androidx.car.app.navigation.model.Maneuver.TYPE_U_TURN_LEFT + } + "ENTER_MOTORWAY" -> { + newType = androidx.car.app.navigation.model.Maneuver.TYPE_MERGE_LEFT + } + "TAKE_EXIT" -> { + newType = androidx.car.app.navigation.model.Maneuver.TYPE_TURN_SHARP_RIGHT + + } } return newType } diff --git a/common/data/src/main/java/com/kouros/navigation/model/IconMapper.kt b/common/data/src/main/java/com/kouros/navigation/model/IconMapper.kt index 250b911..96cafa4 100644 --- a/common/data/src/main/java/com/kouros/navigation/model/IconMapper.kt +++ b/common/data/src/main/java/com/kouros/navigation/model/IconMapper.kt @@ -66,28 +66,17 @@ class IconMapper() { currentTurnIcon = R.drawable.ic_roundabout_ccw } - } - return currentTurnIcon - } - - - fun addLanes(stepData: StepData) : Int { - stepData.lane.forEach { - if (it.indications.isNotEmpty() && it.valid) { - Collections.sort(it.indications) - var direction = "" - it.indications.forEach { it2 -> - direction = if (direction.isEmpty()) { - it2.trim() - } else { - "${direction}_${it2.trim()}" - } - } - val laneDirection = addLanes(direction, stepData) - return laneDirection + Maneuver.TYPE_U_TURN_LEFT -> { + currentTurnIcon = R.drawable.ic_turn_u_turn_left + } + Maneuver.TYPE_U_TURN_RIGHT -> { + currentTurnIcon = R.drawable.ic_turn_u_turn_right + } + Maneuver.TYPE_MERGE_LEFT -> { + currentTurnIcon = R.drawable.ic_turn_merge_symmetrical } } - return 0 + return currentTurnIcon } fun addLanes(direction: String, stepData: StepData): Int { @@ -112,6 +101,8 @@ class IconMapper() { "straight" -> { when (stepData.currentManeuverType) { Maneuver.TYPE_STRAIGHT -> LaneDirection.SHAPE_STRAIGHT + Maneuver.TYPE_KEEP_LEFT -> LaneDirection.SHAPE_STRAIGHT + Maneuver.TYPE_KEEP_RIGHT -> LaneDirection.SHAPE_STRAIGHT else -> LaneDirection.SHAPE_UNKNOWN } @@ -136,7 +127,8 @@ class IconMapper() { "left_slight", "slight_left" -> { when (stepData.currentManeuverType) { - Maneuver.TYPE_TURN_SLIGHT_LEFT -> LaneDirection.SHAPE_SLIGHT_LEFT + Maneuver.TYPE_TURN_NORMAL_LEFT -> LaneDirection.SHAPE_SLIGHT_LEFT + Maneuver.TYPE_KEEP_LEFT -> LaneDirection.SHAPE_SLIGHT_LEFT else -> LaneDirection.SHAPE_UNKNOWN } diff --git a/common/data/src/main/java/com/kouros/navigation/model/ViewModel.kt b/common/data/src/main/java/com/kouros/navigation/model/NavigationViewModel.kt similarity index 96% rename from common/data/src/main/java/com/kouros/navigation/model/ViewModel.kt rename to common/data/src/main/java/com/kouros/navigation/model/NavigationViewModel.kt index 92582e9..5dfa7e9 100644 --- a/common/data/src/main/java/com/kouros/navigation/model/ViewModel.kt +++ b/common/data/src/main/java/com/kouros/navigation/model/NavigationViewModel.kt @@ -2,6 +2,7 @@ package com.kouros.navigation.model import android.content.Context import android.location.Location +import androidx.car.app.CarContext import androidx.compose.runtime.snapshots.SnapshotStateList import androidx.compose.runtime.toMutableStateList import androidx.lifecycle.MutableLiveData @@ -18,21 +19,21 @@ import com.kouros.navigation.data.nominatim.Search import com.kouros.navigation.data.nominatim.SearchResult import com.kouros.navigation.data.overpass.Elements import com.kouros.navigation.data.overpass.Overpass -import com.kouros.navigation.data.tomtom.Features -import com.kouros.navigation.data.tomtom.Traffic -import com.kouros.navigation.data.tomtom.TrafficData -import com.kouros.navigation.utils.GeoUtils.createPointCollection import com.kouros.navigation.utils.Levenshtein import com.kouros.navigation.utils.NavigationUtils +import com.kouros.navigation.utils.getSettingsRepository +import com.kouros.navigation.utils.getSettingsViewModel import com.kouros.navigation.utils.location import io.objectbox.kotlin.boxFor import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.first import kotlinx.coroutines.launch +import kotlinx.coroutines.runBlocking import org.maplibre.geojson.FeatureCollection import java.time.LocalDateTime import java.time.ZoneOffset -class ViewModel(private val repository: NavigationRepository) : ViewModel() { +class NavigationViewModel(private val repository: NavigationRepository) : ViewModel() { val route: MutableLiveData by lazy { MutableLiveData() @@ -457,14 +458,9 @@ class ViewModel(private val repository: NavigationRepository) : ViewModel() { } fun getSearchFilter(context: Context): SearchFilter { - val avoidMotorway = NavigationUtils.getBooleanKeyValue( - context = context, - Constants.AVOID_MOTORWAY - ) - val avoidTollway = NavigationUtils.getBooleanKeyValue( - context = context, - Constants.AVOID_TOLLWAY - ) + val repository = getSettingsRepository(context) + val avoidMotorway = runBlocking { repository.avoidMotorwayFlow.first() } + val avoidTollway = runBlocking { repository.avoidTollwayFlow.first() } return SearchFilter(avoidMotorway, avoidTollway) } diff --git a/common/data/src/main/java/com/kouros/navigation/model/RouteCalculator.kt b/common/data/src/main/java/com/kouros/navigation/model/RouteCalculator.kt index b7efa2a..625c5ef 100644 --- a/common/data/src/main/java/com/kouros/navigation/model/RouteCalculator.kt +++ b/common/data/src/main/java/com/kouros/navigation/model/RouteCalculator.kt @@ -90,7 +90,7 @@ class RouteCalculator(var routeModel: RouteModel) { return nowUtcMillis + timeToDestinationMillis } - fun updateSpeedLimit(location: Location, viewModel: ViewModel) { + fun updateSpeedLimit(location: Location, viewModel: NavigationViewModel) { if (routeModel.isNavigating()) { // speed limit val distance = lastSpeedLocation.distanceTo(location) diff --git a/common/data/src/main/java/com/kouros/navigation/model/RouteModel.kt b/common/data/src/main/java/com/kouros/navigation/model/RouteModel.kt index a479d2e..7945c00 100644 --- a/common/data/src/main/java/com/kouros/navigation/model/RouteModel.kt +++ b/common/data/src/main/java/com/kouros/navigation/model/RouteModel.kt @@ -4,15 +4,19 @@ import android.content.Context import android.location.Location import androidx.car.app.navigation.model.Maneuver import com.kouros.navigation.data.Constants.NEXT_STEP_THRESHOLD -import com.kouros.navigation.data.Constants.ROUTING_ENGINE import com.kouros.navigation.data.Place import com.kouros.navigation.data.Route import com.kouros.navigation.data.StepData +import com.kouros.navigation.data.datastore.DataStoreManager import com.kouros.navigation.data.route.Lane import com.kouros.navigation.data.route.Leg import com.kouros.navigation.data.route.Routes -import com.kouros.navigation.utils.NavigationUtils.getIntKeyValue +import com.kouros.navigation.repository.SettingsRepository +import com.kouros.navigation.utils.getSettingsRepository +import com.kouros.navigation.utils.getSettingsViewModel import com.kouros.navigation.utils.location +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.runBlocking import kotlin.math.absoluteValue open class RouteModel { @@ -20,7 +24,7 @@ open class RouteModel { // Immutable Data Class data class NavigationState( val route: Route = Route.Builder().buildEmpty(), - val iconMapper : IconMapper = IconMapper(), + val iconMapper: IconMapper = IconMapper(), val navigating: Boolean = false, val arrived: Boolean = false, val travelMessage: String = "", @@ -37,7 +41,7 @@ open class RouteModel { val route: Route get() = navState.route - val routeCalculator : RouteCalculator = RouteCalculator(this) + val routeCalculator: RouteCalculator = RouteCalculator(this) val curRoute: Routes get() = navState.route.routes[navState.currentRouteIndex] @@ -46,38 +50,98 @@ open class RouteModel { get() = navState.route.routes[navState.currentRouteIndex].legs.first() fun startNavigation(routeString: String, context: Context) { - val routeEngine = getIntKeyValue(context = context, ROUTING_ENGINE) + val repository = getSettingsRepository(context) + val routingEngine = runBlocking { repository.routingEngineFlow.first() } navState = navState.copy( route = Route.Builder() - .routeEngine(routeEngine) + .routeEngine(routingEngine) .route(routeString) .build() ) if (hasLegs()) { navState = navState.copy(navigating = true) + //NavigationUtils.setStringKeyValue(context, routeString, LAST_ROUTE) } } private fun hasLegs(): Boolean { - return navState.route.routes.isNotEmpty() && navState.route.routes[0].legs.isNotEmpty() + return navState.route.routes.isNotEmpty() && navState.route.routes[0].legs.isNotEmpty() } - fun stopNavigation() { + fun stopNavigation(context: Context) { navState = navState.copy( route = Route.Builder().buildEmpty(), navigating = false, arrived = false, maneuverType = Maneuver.TYPE_UNKNOWN ) + //NavigationUtils.setStringKeyValue(context, "", LAST_ROUTE) } - fun updateLocation(curLocation: Location, viewModel: ViewModel) { + fun updateLocation(context: Context, curLocation: Location, viewModel: NavigationViewModel) { navState = navState.copy(currentLocation = curLocation) routeCalculator.findStep(curLocation) - routeCalculator.updateSpeedLimit(curLocation, viewModel) + val repository = getSettingsRepository(context) + val carLocation = runBlocking { repository.carLocationFlow.first() } + if (carLocation) { + routeCalculator.updateSpeedLimit(curLocation, viewModel) + } navState = navState.copy(lastLocation = navState.currentLocation) } + fun currentStep(): StepData { + val distanceToNextStep = routeCalculator.leftStepDistance() + // Determine the maneuver type and corresponding icon + val currentStep = navState.route.nextStep(0) + val streetName = if (distanceToNextStep > NEXT_STEP_THRESHOLD) { + currentStep.street + } else { + currentStep.maneuver.street + } + val curManeuverType = if (distanceToNextStep > NEXT_STEP_THRESHOLD) { + Maneuver.TYPE_STRAIGHT + } else { + currentStep.maneuver.type + } + val exitNumber = currentStep.maneuver.exit + val maneuverIcon = navState.iconMapper.maneuverIcon(curManeuverType) + navState = navState.copy(maneuverType = curManeuverType) + // Construct and return the final StepData object + return StepData( + instruction = streetName, + leftStepDistance = distanceToNextStep, + currentManeuverType = navState.maneuverType, + icon = maneuverIcon, + arrivalTime = routeCalculator.arrivalTime(), + leftDistance = routeCalculator.travelLeftDistance(), + lane = currentLanes(), + exitNumber = exitNumber + ) + } + + fun nextStep(): StepData { + val distanceToNextStep = routeCalculator.leftStepDistance() + val step = navState.route.nextStep(1) + val streetName = if (distanceToNextStep < NEXT_STEP_THRESHOLD) { + step.maneuver.street + } else { + step.street + } + val maneuverType = step.maneuver.type + + val maneuverIcon = navState.iconMapper.maneuverIcon(maneuverType) + // Construct and return the final StepData object + return StepData( + instruction = streetName, + leftStepDistance = distanceToNextStep, + currentManeuverType = maneuverType, + icon = maneuverIcon, + arrivalTime = routeCalculator.arrivalTime(), + leftDistance = routeCalculator.travelLeftDistance(), + exitNumber = step.maneuver.exit + ) + } + private fun currentLanes(): List { var lanes = emptyList() if (navState.route.legs().isNotEmpty()) { @@ -87,7 +151,7 @@ open class RouteModel { navState.lastLocation.distanceTo(location(it.location[0], it.location[1])) val sectionBearing = navState.lastLocation.bearingTo(location(it.location[0], it.location[1])) - if (distance < 500 && (navState.routeBearing.absoluteValue - sectionBearing.absoluteValue).absoluteValue < 10) { + if (distance < NEXT_STEP_THRESHOLD && (navState.routeBearing.absoluteValue - sectionBearing.absoluteValue).absoluteValue < 10) { lanes = it.lane } } @@ -96,61 +160,6 @@ open class RouteModel { return lanes } - fun currentStep(): StepData { - val distanceToNextStep = routeCalculator.leftStepDistance() - // Determine the maneuver type and corresponding icon - val currentStep = navState.route.nextStep(0) - // Safely get the street name from the maneuver - val streetName = currentStep.maneuver.street - val curManeuverType = currentStep.maneuver.type - val exitNumber = currentStep.maneuver.exit - val maneuverIcon = navState.iconMapper.maneuverIcon(curManeuverType) - navState = navState.copy(maneuverType = curManeuverType) - - val lanes = currentLanes() - - // Construct and return the final StepData object - return StepData( - streetName, - distanceToNextStep, - navState.maneuverType, - maneuverIcon, - routeCalculator.arrivalTime(), - routeCalculator.travelLeftDistance(), - lanes, - exitNumber - ) - } - - fun nextStep(): StepData { - val step = navState.route.nextStep(1) - val maneuverType = step.maneuver.type - val distanceLeft = routeCalculator.leftStepDistance() - var text = "" - when (distanceLeft) { - in 0.0..NEXT_STEP_THRESHOLD -> { - } - else -> { - if (step.street.isNotEmpty()) { - text = step.street - } - } - } - - val maneuverIcon = navState.iconMapper.maneuverIcon(maneuverType) - // Construct and return the final StepData object - return StepData( - text, - distanceLeft, - maneuverType, - maneuverIcon, - routeCalculator.arrivalTime(), - routeCalculator.travelLeftDistance(), - listOf(Lane(location(0.0, 0.0), valid = false, indications = emptyList())), - step.maneuver.exit - ) - } - fun isNavigating(): Boolean { return navState.navigating } diff --git a/common/data/src/main/java/com/kouros/navigation/model/SettingsViewModel.kt b/common/data/src/main/java/com/kouros/navigation/model/SettingsViewModel.kt new file mode 100644 index 0000000..dbb5286 --- /dev/null +++ b/common/data/src/main/java/com/kouros/navigation/model/SettingsViewModel.kt @@ -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) } + } +} diff --git a/common/data/src/main/java/com/kouros/navigation/repository/SettingsRepository.kt b/common/data/src/main/java/com/kouros/navigation/repository/SettingsRepository.kt new file mode 100644 index 0000000..234ff08 --- /dev/null +++ b/common/data/src/main/java/com/kouros/navigation/repository/SettingsRepository.kt @@ -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 = + dataStoreManager.threeDBuildingFlow + val darkModeFlow: Flow = + dataStoreManager.darkModeFlow + + val avoidMotorwayFlow: Flow = + dataStoreManager.avoidMotorwayFlow + + val avoidTollwayFlow: Flow = + dataStoreManager.avoidTollwayFlow + + val carLocationFlow: Flow = + dataStoreManager.useCarLocationFlow + + val routingEngineFlow: Flow = + 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) + } + +} \ No newline at end of file diff --git a/common/data/src/main/java/com/kouros/navigation/utils/NavigationUtils.kt b/common/data/src/main/java/com/kouros/navigation/utils/NavigationUtils.kt index bce75b9..e381e02 100644 --- a/common/data/src/main/java/com/kouros/navigation/utils/NavigationUtils.kt +++ b/common/data/src/main/java/com/kouros/navigation/utils/NavigationUtils.kt @@ -3,14 +3,20 @@ package com.kouros.navigation.utils import android.content.Context import android.location.Location import android.location.LocationManager +import androidx.car.app.CarContext import androidx.core.content.edit import com.kouros.navigation.data.Constants.ROUTING_ENGINE import com.kouros.navigation.data.Constants.SHARED_PREF_KEY import com.kouros.navigation.data.RouteEngine +import com.kouros.navigation.data.datastore.DataStoreManager import com.kouros.navigation.data.osrm.OsrmRepository import com.kouros.navigation.data.tomtom.TomTomRepository import com.kouros.navigation.data.valhalla.ValhallaRepository -import com.kouros.navigation.model.ViewModel +import com.kouros.navigation.model.NavigationViewModel +import com.kouros.navigation.model.SettingsViewModel +import com.kouros.navigation.repository.SettingsRepository +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.runBlocking import java.time.LocalDateTime import java.time.ZoneId import java.time.ZoneOffset @@ -26,60 +32,15 @@ import kotlin.time.Duration.Companion.seconds object NavigationUtils { - fun getViewModel(context: Context): ViewModel { - val routeEngine = getIntKeyValue(context = context, ROUTING_ENGINE) + fun getViewModel(context: Context): NavigationViewModel { + val repository = getSettingsRepository(context) + val routeEngine = runBlocking { repository.routingEngineFlow.first() } return when (routeEngine) { - RouteEngine.VALHALLA.ordinal -> ViewModel(ValhallaRepository()) - RouteEngine.OSRM.ordinal -> ViewModel(OsrmRepository()) - else -> ViewModel(TomTomRepository()) + RouteEngine.VALHALLA.ordinal -> NavigationViewModel(ValhallaRepository()) + RouteEngine.OSRM.ordinal -> NavigationViewModel(OsrmRepository()) + else -> NavigationViewModel(TomTomRepository()) } } - - fun getBooleanKeyValue(context: Context, key: String): Boolean { - return context - .getSharedPreferences( - SHARED_PREF_KEY, - Context.MODE_PRIVATE - ) - .getBoolean(key, false) - } - - fun setBooleanKeyValue(context: Context, `val`: Boolean, key: String) { - context - .getSharedPreferences( - SHARED_PREF_KEY, - Context.MODE_PRIVATE - ) - .edit { - putBoolean( - key, `val` - ) - apply() - } - } - - fun getIntKeyValue(context: Context, key: String, default: Int = 0): Int { - return context - .getSharedPreferences( - SHARED_PREF_KEY, - Context.MODE_PRIVATE - ) - .getInt(key, default) - } - - fun setIntKeyValue(context: Context, `val`: Int, key: String) { - context - .getSharedPreferences( - SHARED_PREF_KEY, - Context.MODE_PRIVATE - ) - .edit { - putInt( - key, `val` - ) - apply() - } - } } fun calculateZoom(speed: Double?): Double { @@ -90,8 +51,8 @@ fun calculateZoom(speed: Double?): Double { val zoom = when (speedKmh) { in 0..10 -> 18.0 in 11..30 -> 17.5 - in 31..50 -> 17.0 - in 61..70 -> 16.5 + in 31..65 -> 17.0 + in 66..70 -> 16.5 else -> 16.0 } return zoom diff --git a/common/data/src/main/java/com/kouros/navigation/utils/Settings.kt b/common/data/src/main/java/com/kouros/navigation/utils/Settings.kt new file mode 100644 index 0000000..32531d2 --- /dev/null +++ b/common/data/src/main/java/com/kouros/navigation/utils/Settings.kt @@ -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 create(modelClass: Class): 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) +} + + diff --git a/common/data/src/main/res/drawable/ic_turn_merge_symmetrical.png b/common/data/src/main/res/drawable/ic_turn_merge_symmetrical.png new file mode 100644 index 0000000000000000000000000000000000000000..5925a8c676ea50389b00f3c00ca4971f0537c594 GIT binary patch literal 832 zcmeAS@N?(olHy`uVBq!ia0y~yU<5K58911MRQ8&P5Fq6j;1l8sr2j(zkb#V#qNGu^ zW{<^yCQ6kA`2{ol7h`1M=4EH*<7N}!Wo6`HG5f`-%PzyjAs+ki9|HqZyQhm|NCo4Y z3p)du0~n4T*l$+;pLMdJU_~3l?W$J^rPreV%igM5u$J9y!P)j*^Ffd|Ue2H8#jRDV2UWnkD4@AsWqW^cnyhJ7sjall{#f*U&T z_p$tCVYoAMvp568ffxG^c8fD4oc@>0s-qPZvSo!uiN~2mi(Fm1yBK`D&dG(CXj+zf zom;ZR({u79HPz*QCudGE(aa3GwB*SfN7u=df|oz>@tOq|Dta|#4n&o&7vFth6O9bN zyp6}jnEq!4>I%Gw*TawR>rQxaUwgu<`OaOtZ>9&EZrLjocrnZg|IN;_ zj_W&S;-@FGy}OHUWakP61PcNp7L7zJD|_SiZeiT$Qj{r|Mi0RxSj zTF;Xo#V?qj!4>*s%eAQNwOe6CxMsZZ@81m$oZ1H%f8Kk~q>=c)`2fSk272NJ4eg9y zq!i?NA6#eXvX@MVV|g-}@gB3vM|S2tYyp$ruvbVv_{yNqyh-*!X~T5pJ$xtf8kifx znPnI@Nk3qCaFv0*;SHMwLxt%9Mupvsd<=2C1q>&$8n_$GnPV6>Nj_kCaFjv4L5FPz z`;X7xpEEM+CrB9}%QP}B zV`OIbVP<2S!TiU263-r}4v_fg*FfSRk$5K)&(~db_KleieGJa}21@%Sby}W$F#oE&%F%>FMGaQo;E4PPZpZpn%K8cRZ^Ylnn$w_j~>0P;%D) zyXVE~*$3pf%Jvz2P1amGCCCd-{LWUYlNa66%T>{^zg&F5U)zfej~VET|Fb##7Up66 zdx_;9^A&C3EnC?uQkn9Z;$|$!)n|Gpd}G@;hR2*$(gt%6+-`WtvX_5`?}OR{8BO=t z&!~N{OE7VK&zvS)A#X5i!FR^ZoO}2sd@r~&G;`K48O&JF&%h@9gC)VhQJ#@U?L**! z&n&fKA8Z=?ndYg#*xc}(vqmnU?uEsIOoxXJ%NSdkXR(PeRQz9|Z@L#Dz?UHvfT98< zuzX-b)eRm|K=t<8Bzs*U;g;zz3H|7>Gk_fcmMnL=a;+5b^B$;K&cPu za@WQG9CKmaZ!EOeLG<@IkKYYHI~#2H{wzPwxRQxOp@9KT)M%K>pLS(k<;1!Q5ncap asrTl6$i8*cN)?z27(8A5T-G@yGywoQKQp>ji@%<)Kku%6pn|+Y=iY$-K8-aEH9xQX+xhJ$gZ+~EH~-f^;ZAwJo$17X z*4;d8-fo+=HKFeU}Jqi)!<7IN5V-4i&7SgW9{d-3+Bo`Fta{jE0b{GbOlSBopl}K z`U7WT8639%T=Cy%$Lw06nr$;#q~EZd-|XMiz1iWoenMHp-#7aYe9{JnI{iTV#D(wA ze~;!(Fs@Qz0$QsOpKy|eVS;`@k^iMt|D_n}-(E1}WiSvpvf+dK-H(b9Q}uY}?2+f^ z`2DwG!+r87p!O5LKQ@pQqpOdb;|#taD0e0s!3Ddbj`p literal 0 HcmV?d00001 diff --git a/common/data/src/main/res/drawable/ic_turn_u_turn_right.png b/common/data/src/main/res/drawable/ic_turn_u_turn_right.png new file mode 100644 index 0000000000000000000000000000000000000000..9f26af1b7fa8a44018f4a1334dc7db106c40e05d GIT binary patch literal 835 zcmeAS@N?(olHy`uVBq!ia0y~yU<5K58911MRQ8&P5Flk4;1l8sr2hi}gaomO1xdTo zN`RV$OM?7@8U8c!GcmGp3b3Eakt!T9C^dm_;0!y7XH zIEp*|U!TnO>3&r9+O5~3UhlNLsek%En+xAWz7SvL4> zyY=tqLoJ5YQ?uXfPus(EVAWTl-{JxiHV&KX{;=GA$H1G{l(+uy-^Y#4CLYK8853?d z?7Juv&2aK=gD1n5b8J0Al6T8lEOxLTi9P-$tKop*hRRnQ1$;SiR!lBt>lvi}IX;xS zVFOg*%YMU$eTOgm4t6tn#)~Bl=?R~&ew6!=_jd>PjHd!OY<%h^Yy9hl_L#AJP^p+% z$KR2&Z<#&kyXDL|R!lmAtiaHx8~E_ErhevTnbnLroAntO9x*bQ2>saZe4K^hfP=uH z59@a+Sv_e#%=l930K=#MTnrp8jC&m98n~V?t{{s7X%{)bO%BBnxZap4kU>AdX~8