From e1af3e19fa4f2c8ed62bb527ff5b5e3d9dcdc28c Mon Sep 17 00:00:00 2001 From: Dimitris Date: Mon, 2 Mar 2026 17:11:19 +0100 Subject: [PATCH] Test, Lanes --- app/build.gradle.kts | 4 +- .../com/kouros/navigation/ui/MainActivity.kt | 46 +++----- .../java/com/kouros/navigation/ui/MapView.kt | 3 +- .../kouros/navigation/ui/NavigationScreen.kt | 21 ++-- .../kouros/navigation/ui/NavigationSheet.kt | 6 +- .../com/kouros/navigation/ui/theme/Color.kt | 8 -- .../kouros/navigation/car/RouteModelTest.kt | 79 ++++++++++++-- .../navigation/car/NavigationSession.kt | 4 +- .../kouros/navigation/car/SurfaceRenderer.kt | 22 +++- .../com/kouros/navigation/car/map/MapView.kt | 43 +++++++- .../car/navigation/RouteCarModel.kt | 19 ++-- .../navigation/car/screen/DarkModeSettings.kt | 5 +- .../navigation/car/screen/DisplaySettings.kt | 5 +- .../navigation/car/screen/NavigationScreen.kt | 15 +-- .../car/screen/NavigationSettings.kt | 20 ++-- .../navigation/car/screen/PasswordSettings.kt | 5 +- .../navigation/car/screen/RoutingSettings.kt | 5 +- .../java/com/kouros/navigation/data/Data.kt | 13 +-- .../kouros/navigation/data/NavigationState.kt | 4 +- .../navigation/data/tomtom/TomTomRoute.kt | 44 ++++---- .../data/valhalla/ValhallaRepository.kt | 4 +- .../com/kouros/navigation/model/IconMapper.kt | 57 +++++----- .../navigation/model/RouteCalculator.kt | 12 ++- .../com/kouros/navigation/model/RouteModel.kt | 10 +- .../navigation/model/SettingsViewModel.kt | 6 ++ common/data/src/main/res/drawable/empty.png | Bin 0 -> 4249 bytes .../src/main/res/drawable/left_o_right_x.png | Bin 883 -> 0 bytes .../src/main/res/drawable/slight_left_o.png | Bin 0 -> 4624 bytes .../src/main/res/drawable/slight_left_x.png | Bin 0 -> 5150 bytes .../kouros/navigation/model/IconMapperTest.kt | 98 ++++++++++++++++++ 30 files changed, 378 insertions(+), 180 deletions(-) create mode 100644 common/data/src/main/res/drawable/empty.png delete mode 100644 common/data/src/main/res/drawable/left_o_right_x.png create mode 100644 common/data/src/main/res/drawable/slight_left_o.png create mode 100644 common/data/src/main/res/drawable/slight_left_x.png create mode 100644 common/data/src/test/java/com/kouros/navigation/model/IconMapperTest.kt diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 624852a..a984b8e 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -13,8 +13,8 @@ android { applicationId = "com.kouros.navigation" minSdk = 33 targetSdk = 36 - versionCode = 56 - versionName = "0.2.0.56" + versionCode = 57 + versionName = "0.2.0.57" base.archivesName = "navi-$versionName" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" } 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 50e338a..03ce677 100644 --- a/app/src/main/java/com/kouros/navigation/ui/MainActivity.kt +++ b/app/src/main/java/com/kouros/navigation/ui/MainActivity.kt @@ -11,6 +11,7 @@ import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.activity.enableEdgeToEdge import androidx.annotation.RequiresPermission +import androidx.compose.foundation.gestures.Orientation import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth @@ -49,7 +50,9 @@ 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.TILT import com.kouros.navigation.data.Constants.homeVogelhart import com.kouros.navigation.data.StepData import com.kouros.navigation.model.BaseStyleModel @@ -84,10 +87,9 @@ import kotlin.time.Duration.Companion.seconds class MainActivity : ComponentActivity() { val routeData = MutableLiveData("") val routeModel = RouteModel() - var tilt = 50.0 + var tilt = TILT val useMock = false val type = SimulationType.SIMULATE - val stepData: MutableLiveData by lazy { MutableLiveData() } @@ -148,9 +150,8 @@ class MainActivity : ComponentActivity() { } } lifecycleScope.launch { - getSettingsViewModel(applicationContext).routingEngine.collect { - - } + getSettingsViewModel(applicationContext).routingEngine.first() + getSettingsViewModel(applicationContext).recentPlaces.first() } enableEdgeToEdge() setContent { @@ -169,21 +170,11 @@ class MainActivity : ComponentActivity() { permissions = permissions, requiredPermissions = listOf(permissions.first()), onGranted = { - App() - // auto navigate - if (useMock) { -// navigationViewModel.loadRoute( -// applicationContext, -// homeVogelhart, -// homeHohenwaldeck, -// 0F -// ) - } + Application() }, ) } - @OptIn(ExperimentalMaterial3Api::class) @Composable fun StartScreen( @@ -193,7 +184,6 @@ class MainActivity : ComponentActivity() { val appViewModel: AppViewModel = appViewModel() val darkMode by appViewModel.darkMode.collectAsState() - val baseStyle = BaseStyleModel().readStyle(applicationContext, darkMode, darkMode == 1) val scaffoldState = rememberBottomSheetScaffoldState() val snackbarHostState = remember { SnackbarHostState() } @@ -207,10 +197,6 @@ class MainActivity : ComponentActivity() { val userLocationState = rememberUserLocationState(locationProvider) val locationState = locationProvider.location.collectAsState() updateLocation(locationState.value) - var latitude by remember { mutableDoubleStateOf(0.0) } - if (locationState.value != null) { - latitude = locationState.value!!.position.latitude - } val step: StepData? by stepData.observeAsState() val nextStep: StepData? by nextStepData.observeAsState() fun closeSheet() { @@ -227,7 +213,7 @@ class MainActivity : ComponentActivity() { scaffoldState = scaffoldState, sheetPeekHeight = sheetPeekHeightState.value, sheetContent = { - SheetContent(latitude, step, nextStep) { closeSheet() } + SheetContent(step, nextStep) { closeSheet() } }, ) { innerPadding -> Box( @@ -255,7 +241,7 @@ class MainActivity : ComponentActivity() { } @Composable - fun App() { + fun Application() { val appViewModel: AppViewModel = appViewModel() val lastRoute by appViewModel.lastRoute.collectAsState() if (lastRoute.isNotEmpty()) { @@ -286,12 +272,12 @@ class MainActivity : ComponentActivity() { @Composable fun SheetContent( - locationState: Double, step: StepData?, nextStep: StepData?, closeSheet: () -> Unit + step: StepData?, nextStep: StepData?, closeSheet: () -> Unit ) { if (!routeModel.isNavigating()) { SearchSheet(applicationContext, navigationViewModel, lastLocation) { closeSheet() } } else { - if (step != null && nextStep != null) { + if (step != null) { NavigationSheet( applicationContext, routeModel, @@ -301,8 +287,6 @@ class MainActivity : ComponentActivity() { { simulateNavigation() }) } } - // For recomposition! - Text("$locationState", fontSize = 12.sp) } fun updateLocation(location: Location?) { @@ -314,7 +298,7 @@ class MainActivity : ComponentActivity() { if (routeModel.isNavigating()) { val snapedLocation = snapLocation(currentLocation, routeModel.route.maneuverLocations()) - updateLocationInternal(snapedLocation, location) + updateLocationInternal(currentLocation, location) } else { updateLocationInternal(currentLocation, location) } @@ -335,7 +319,9 @@ class MainActivity : ComponentActivity() { if (isNavigating()) { updateLocation( currentLocation, navigationViewModel) stepData.value = currentStep() - nextStepData.value = nextStep() + if (navState.nextStep) { + nextStepData.value = nextStep() + } if (navState.maneuverType in 39..42 && routeCalculator.leftStepDistance() < DESTINATION_ARRIVAL_DISTANCE) { // stopNavigation() navState = navState.copy(arrived = true) @@ -367,7 +353,7 @@ class MainActivity : ComponentActivity() { mock.setMockLocation(latitude, longitude, 0F) } routeData.value = "" - stepData.value = StepData("", 0.0, 0, 0, 0, 0.0) + stepData.value = StepData("", "",0.0, 0, 0, 0, 0.0) } fun simulateNavigation() { 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 6c42ee3..dfaf07c 100644 --- a/app/src/main/java/com/kouros/navigation/ui/MapView.kt +++ b/app/src/main/java/com/kouros/navigation/ui/MapView.kt @@ -60,6 +60,7 @@ fun MapView( val appViewModel: AppViewModel = appViewModel() val showBuildings by appViewModel.show3D.collectAsState() + val darkMode by appViewModel.darkMode.collectAsState() Column { NavigationInfo(step, nextStep) @@ -87,7 +88,7 @@ fun MapView( duration = 1.seconds ) } - NavigationImage(paddingValues, width, height / 6) + NavigationImage(paddingValues, width, height / 6, "", darkMode) } } } 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 3a0ea84..da92682 100644 --- a/app/src/main/java/com/kouros/navigation/ui/NavigationScreen.kt +++ b/app/src/main/java/com/kouros/navigation/ui/NavigationScreen.kt @@ -52,12 +52,19 @@ fun NavigationInfo( modifier = Modifier.padding(CardPadding), horizontalAlignment = Alignment.Start ) { - Icon( - painter = painterResource(currentStep.icon), - contentDescription = stringResource(id = R.string.navigation_icon_description), - modifier = Modifier.size(IconSize), - tint = MaterialTheme.colorScheme.primary - ) + Row( + verticalAlignment = Alignment.CenterVertically, + modifier = Modifier.padding(top = ElementSpacing) + ) { + Icon( + painter = painterResource(currentStep.icon), + contentDescription = stringResource(id = R.string.navigation_icon_description), + modifier = Modifier.size(IconSize), + tint = MaterialTheme.colorScheme.primary + ) + Spacer(modifier = Modifier.padding(horizontal = SpacerWidth)) + DistanceText(distance = currentStep.leftStepDistance) + } if (currentStep.isExitManeuver) { Text( @@ -72,8 +79,6 @@ fun NavigationInfo( verticalAlignment = Alignment.CenterVertically, modifier = Modifier.padding(top = ElementSpacing) ) { - DistanceText(distance = currentStep.leftStepDistance) - Spacer(modifier = Modifier.padding(horizontal = SpacerWidth)) Text( text = currentStep.instruction, fontSize = PrimaryTextSize, 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 3fb73bb..d8c1529 100755 --- a/app/src/main/java/com/kouros/navigation/ui/NavigationSheet.kt +++ b/app/src/main/java/com/kouros/navigation/ui/NavigationSheet.kt @@ -27,16 +27,12 @@ fun NavigationSheet( applicationContext: Context, routeModel: RouteModel, step: StepData, - nextStep: StepData, + nextStep: StepData?, stopNavigation: () -> Unit, simulateNavigation: () -> Unit, ) { val distance = (step.leftDistance / 1000).round(1) - if (step.lane.isNotEmpty()) { - // routeModel.navState.iconMapper.addLanes( step) - } - Column { FlowRow(horizontalArrangement = Arrangement.SpaceEvenly) { Text(formatDateTime(step.arrivalTime), fontSize = 22.sp) diff --git a/app/src/main/java/com/kouros/navigation/ui/theme/Color.kt b/app/src/main/java/com/kouros/navigation/ui/theme/Color.kt index 0dbc89c..e347284 100644 --- a/app/src/main/java/com/kouros/navigation/ui/theme/Color.kt +++ b/app/src/main/java/com/kouros/navigation/ui/theme/Color.kt @@ -2,14 +2,6 @@ package com.kouros.navigation.ui.theme import androidx.compose.ui.graphics.Color -//val Purple80 = Color(0xFFD0BCFF) -//val PurpleGrey80 = Color(0xFFCCC2DC) -//val Pink80 = Color(0xFFEFB8C8) -// -//val Purple40 = Color(0xFF6650a4) -//val PurpleGrey40 = Color(0xFF625b71) -//val Pink40 = Color(0xFF7D5260) - val md_theme_light_primary = Color(0xFF825500) val md_theme_light_onPrimary = Color(0xFFFFFFFF) val md_theme_light_primaryContainer = Color(0xFFFFDDB3) diff --git a/common/car/src/androidTest/java/com/kouros/navigation/car/RouteModelTest.kt b/common/car/src/androidTest/java/com/kouros/navigation/car/RouteModelTest.kt index 41b23a5..058e249 100644 --- a/common/car/src/androidTest/java/com/kouros/navigation/car/RouteModelTest.kt +++ b/common/car/src/androidTest/java/com/kouros/navigation/car/RouteModelTest.kt @@ -25,6 +25,7 @@ import org.junit.runner.RunWith import org.junit.Assert.* import org.junit.Before +import org.maplibre.compose.expressions.dsl.step import kotlin.collections.forEach /** @@ -65,7 +66,7 @@ class RouteModelTest { val stepData = routeModel.currentStep() assertEquals(stepData.currentManeuverType, Maneuver.TYPE_TURN_NORMAL_RIGHT) assertEquals(stepData.instruction, "Silcherstraße") - assertEquals(stepData.leftStepDistance, 70.0, 1.0) + assertEquals(stepData.leftStepDistance, 30.0, 1.0) val nextStepData = routeModel.nextStep() assertEquals(nextStepData.currentManeuverType, Maneuver.TYPE_TURN_NORMAL_RIGHT) assertEquals(nextStepData.instruction, "Schmalkaldener Straße") @@ -92,12 +93,16 @@ class RouteModelTest { location.longitude = 11.585125 routeModel.updateLocation(location, NavigationViewModel(TomTomRepository())) val stepData = routeModel.currentStep() - assertEquals(stepData.currentManeuverType, Maneuver.TYPE_STRAIGHT) - assertEquals(stepData.instruction, "Ingolstädter Straße") - assertEquals(stepData.leftStepDistance, 350.0, 1.0) - val nextStepData = routeModel.nextStep() - assertEquals(nextStepData.currentManeuverType, Maneuver.TYPE_TURN_NORMAL_LEFT) - assertEquals(nextStepData.instruction, "Schenkendorfstraße") + if (routeModel.navState.nextStep) { + assertEquals(stepData.currentManeuverType, Maneuver.TYPE_STRAIGHT) + assertEquals(stepData.instruction, "Ingolstädter Straße") + val nextStepData = routeModel.nextStep() + assertEquals(nextStepData.currentManeuverType, Maneuver.TYPE_TURN_NORMAL_LEFT) + assertEquals(nextStepData.instruction, "Schenkendorfstraße") + } else { + assertEquals(stepData.currentManeuverType, Maneuver.TYPE_TURN_NORMAL_LEFT) + } + assertEquals(stepData.leftStepDistance, 300.0, 1.0) } @Test @@ -109,7 +114,7 @@ class RouteModelTest { val stepData = routeModel.currentStep() assertEquals(stepData.currentManeuverType, Maneuver.TYPE_TURN_NORMAL_LEFT) assertEquals(stepData.instruction, "Schenkendorfstraße") - assertEquals(stepData.leftStepDistance, 240.0, 1.0) + assertEquals(stepData.leftStepDistance, 170.0, 1.0) assertEquals(stepData.lane.size, 4) assertEquals(stepData.lane.first().valid, true) assertEquals(stepData.lane.last().valid, false) @@ -127,6 +132,47 @@ class RouteModelTest { assertEquals(stepData.currentManeuverType, Maneuver.TYPE_DESTINATION_LEFT) } + @Test + fun checkLanes() { + for ((index, waypoint) in routeModel.curRoute.waypoints.withIndex()) { + val curLocation = location(waypoint[0], waypoint[1]) + if (routeModel.isNavigating()) { + if (index in 42..45) { + routeModel.updateLocation(curLocation, NavigationViewModel(TomTomRepository())) + val stepData = routeModel.currentStep() + assertEquals(stepData.lane.size, 4) + assertEquals(stepData.lane.first().valid, true) + assertEquals(stepData.lane.first().indications.first(), "SLIGHT_LEFT") + } + if (index in 61..61) { + routeModel.updateLocation(curLocation, NavigationViewModel(TomTomRepository())) + val stepData = routeModel.currentStep() + assertEquals(stepData.lane.size, 2) + assertEquals(stepData.lane.first().valid, true) + assertEquals(stepData.lane.first().indications.first(), "STRAIGHT") + } + if (index in 74..74) { + routeModel.updateLocation(curLocation, NavigationViewModel(TomTomRepository())) + val stepData = routeModel.currentStep() + assertEquals(stepData.lane.size, 3) + assertEquals(stepData.lane.first().valid, true) + assertEquals(stepData.lane.first().indications.first(), "SLIGHT_LEFT") + assertEquals(stepData.lane[1].valid, true) + assertEquals(stepData.lane[1].indications.first(), "SLIGHT_LEFT") + } + if (index in 265..265) { + routeModel.updateLocation(curLocation, NavigationViewModel(TomTomRepository())) + val stepData = routeModel.currentStep() + assertEquals(stepData.lane.size, 2) + assertEquals(stepData.lane.first().valid, false) + assertEquals(stepData.lane.first().indications.first(), "STRAIGHT") + assertEquals(stepData.lane[1].valid, true) + assertEquals(stepData.lane[1].indications.first(), "SLIGHT_RIGHT") + } + } + } + } + @Test fun simulate() { for ((index, waypoint) in routeModel.curRoute.waypoints.withIndex()) { @@ -136,9 +182,24 @@ class RouteModelTest { routeModel.updateLocation(curLocation, NavigationViewModel(TomTomRepository())) val stepData = routeModel.currentStep() val nextData = routeModel.nextStep() - println("${stepData.leftStepDistance} : ${stepData.instruction} ${stepData.currentManeuverType} : ${nextData.instruction} ${nextData.currentManeuverType}") } } } } + + @Test + fun `leftStepDistance Inglolstädter `() { + val location: Location = location( 11.584578, 48.183653) + routeModel.updateLocation(location, NavigationViewModel(TomTomRepository()) ) + val step = routeModel.currentStep() + assertEquals(step.leftStepDistance, 650.0, 0.1) + } + + @Test + fun `leftStepDistance Vogelhart `() { + val location: Location = location( 11.578911, 48.185565) + routeModel.updateLocation(location, NavigationViewModel(TomTomRepository()) ) + val step = routeModel.currentStep() + assertEquals(step.leftStepDistance , 30.0, 1.0) + } } \ 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 ba74564..ea28dfe 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 @@ -151,6 +151,7 @@ class NavigationSession : Session(), NavigationScreen.Listener { viewModelStoreOwner = object : ViewModelStoreOwner { override val viewModelStore = ViewModelStore() } + lifecycleScope.launch { try { awaitCancellation() @@ -320,8 +321,6 @@ class NavigationSession : Session(), NavigationScreen.Listener { * Snaps location to route and checks for deviation requiring reroute. */ private fun handleNavigationLocation(location: Location) { - routeModel.navState = - routeModel.navState.copy(travelMessage = "${location.latitude} ${location.longitude}") navigationScreen.updateTrip(location) if (routeModel.navState.arrived) return val snappedLocation = snapLocation(location, routeModel.route.maneuverLocations()) @@ -349,7 +348,6 @@ class NavigationSession : Session(), NavigationScreen.Listener { routeModel.stopNavigation() } - companion object { // URI host for deep linking var uriHost: String = "navigation" 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 429e736..6464b53 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 @@ -29,6 +29,7 @@ import com.kouros.navigation.car.map.MapLibre import com.kouros.navigation.car.map.cameraState import com.kouros.navigation.car.map.getPaddingValues import com.kouros.navigation.car.navigation.RouteCarModel +import com.kouros.navigation.data.Constants.TILT import com.kouros.navigation.data.Constants.homeVogelhart import com.kouros.navigation.data.RouteEngine import com.kouros.navigation.model.BaseStyleModel @@ -104,6 +105,9 @@ class SurfaceRenderer( // Speed limit for current road val maxSpeed = MutableLiveData(0) + // Current street name + val street = MutableLiveData("") + // Current view mode (navigation, preview, etc.) var viewStyle = ViewStyle.VIEW @@ -116,8 +120,8 @@ class SurfaceRenderer( // Compose view for rendering the map lateinit var mapView: ComposeView - // Camera tilt angle (default 55 degrees for navigation) - var tilt = 55.0 + // Camera tilt angle (default 60 degrees for navigation) + var tilt = TILT // Map base style (day/night) val style: MutableLiveData by lazy { @@ -267,7 +271,7 @@ class SurfaceRenderer( speedCameras, showBuildings ) - ShowPosition(cameraState, position, paddingValues) + ShowPosition(cameraState, position, paddingValues, darkMode) } /** @@ -278,12 +282,14 @@ class SurfaceRenderer( fun ShowPosition( cameraState: CameraState, position: CameraPosition?, - paddingValues: PaddingValues + paddingValues: PaddingValues, + darkMode: Int ) { val cameraDuration = duration(viewStyle == ViewStyle.PREVIEW, position!!.bearing, lastBearing) val currentSpeed: Float? by speed.observeAsState() val speed: Int? by maxSpeed.observeAsState() + val streetName: String? by street.observeAsState() if (viewStyle == ViewStyle.VIEW || viewStyle == ViewStyle.PAN_VIEW) { DrawNavigationImages( paddingValues, @@ -291,7 +297,8 @@ class SurfaceRenderer( speed!!, width, height, - 0.0 + streetName, + darkMode ) } LaunchedEffect(position, viewStyle) { @@ -345,6 +352,11 @@ class SurfaceRenderer( */ fun updateLocation(location: Location) { synchronized(this) { + if (routeModel.isNavigating()) { + street.value = routeModel.currentStep().street + } else { + street.value = "" + } if (viewStyle == ViewStyle.VIEW || viewStyle == ViewStyle.PAN_VIEW) { val bearing = if (carOrientation == 999F) { if (location.hasBearing()) { 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 f8d1ac6..e4d0eef 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 @@ -2,6 +2,7 @@ package com.kouros.navigation.car.map import android.location.Location import androidx.compose.foundation.Canvas +import androidx.compose.foundation.isSystemInDarkTheme import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.fillMaxSize @@ -29,7 +30,6 @@ import com.kouros.navigation.data.Constants import com.kouros.navigation.data.NavigationColor import com.kouros.navigation.data.RouteColor import com.kouros.navigation.data.SpeedColor -import com.kouros.navigation.model.RouteModel import org.maplibre.compose.camera.CameraPosition import org.maplibre.compose.camera.CameraState import org.maplibre.compose.camera.rememberCameraState @@ -292,9 +292,10 @@ fun DrawNavigationImages( maxSpeed: Int, width: Int, height: Int, - lat: Double? + streetName: String?, + darkMode: Int, ) { - NavigationImage(padding, width, height) + NavigationImage(padding, width, height, streetName, darkMode) if (speed != null) { CurrentSpeed(width, height, speed, maxSpeed) } @@ -305,9 +306,27 @@ fun DrawNavigationImages( } @Composable -fun NavigationImage(padding: PaddingValues, width: Int, height: Int) { +fun NavigationImage( + padding: PaddingValues, + width: Int, + height: Int, + streetName: String?, + darkMode: Int +) { + val imageSize = (height / 8) val navigationColor = remember { NavigationColor } + + val textMeasurerStreet = rememberTextMeasurer() + val street = streetName.toString() + val styleStreet = TextStyle( + fontSize = 14.sp, + color = if (darkMode == 1) Color.Yellow else Color.Red, + ) + val textLayoutStreet = remember(street) { + textMeasurerStreet.measure(street, styleStreet) + } + Box(contentAlignment = Alignment.Center, modifier = Modifier.padding(padding)) { Canvas( modifier = Modifier @@ -325,6 +344,22 @@ fun NavigationImage(padding: PaddingValues, width: Int, height: Int) { .size(imageSize.dp, imageSize.dp) .scale(scaleX = 1f, scaleY = 0.7f), ) + Canvas( + modifier = Modifier + .size(imageSize.dp + 100.dp, imageSize.dp + 80.dp) + ) { + if (!streetName.isNullOrEmpty()) { + drawText( + textMeasurer = textMeasurerStreet, + text = streetName, + style = styleStreet, + topLeft = Offset( + x = center.x - textLayoutStreet.size.width / 2, + y = center.y + textLayoutStreet.size.height, + ) + ) + } + } } } diff --git a/common/car/src/main/java/com/kouros/navigation/car/navigation/RouteCarModel.kt b/common/car/src/main/java/com/kouros/navigation/car/navigation/RouteCarModel.kt index 94d68be..9198115 100644 --- a/common/car/src/main/java/com/kouros/navigation/car/navigation/RouteCarModel.kt +++ b/common/car/src/main/java/com/kouros/navigation/car/navigation/RouteCarModel.kt @@ -50,15 +50,19 @@ class RouteCarModel() : RouteModel() { } val step = Step.Builder(currentStepCueWithImage) - .setManeuver( - maneuver.build() - ) + if (navState.destination.street != null) { step.setRoad(navState.destination.street!!) } if (stepData.lane.isNotEmpty()) { - addLanes(carContext, step, stepData) + val lanesAdded = addLanes(carContext, step, stepData) + if (lanesAdded) { + maneuver.setIcon(createCarIcon(carContext, R.drawable.empty)) + } } + step.setManeuver( + maneuver.build() + ) return step.build() } @@ -119,10 +123,10 @@ class RouteCarModel() : RouteModel() { return travelBuilder.build() } - fun addLanes(carContext: CarContext, step: Step.Builder, stepData: StepData) { + fun addLanes(carContext: CarContext, step: Step.Builder, stepData: StepData) : Boolean { var laneImageAdded = false stepData.lane.forEach { - if (it.indications.isNotEmpty() && it.valid) { + if (it.indications.isNotEmpty()) { val sorted = it.indications.sorted() var direction = "" sorted.forEach { it2 -> @@ -140,12 +144,13 @@ class RouteCarModel() : RouteModel() { } val laneType = Lane.Builder() - .addDirection(LaneDirection.create(laneDirection, false)) + .addDirection(LaneDirection.create(laneDirection, true)) .build() step.addLane(laneType) } } } + return laneImageAdded } fun createString( 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 3e552ae..1c07be4 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 @@ -13,6 +13,7 @@ import androidx.car.app.model.Template import androidx.lifecycle.lifecycleScope import com.kouros.data.R import com.kouros.navigation.utils.getSettingsViewModel +import kotlinx.coroutines.flow.first import kotlinx.coroutines.launch class DarkModeSettings(private val carContext: CarContext) : Screen(carContext) { @@ -23,9 +24,7 @@ class DarkModeSettings(private val carContext: CarContext) : Screen(carContext) init { lifecycleScope.launch { - settingsViewModel.darkMode.collect { - invalidate() - } + settingsViewModel.darkMode.first() } } 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 e9e20dc..36e0291 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 @@ -12,6 +12,7 @@ import androidx.car.app.model.Toggle import androidx.lifecycle.lifecycleScope import com.kouros.data.R import com.kouros.navigation.utils.getSettingsViewModel +import kotlinx.coroutines.flow.first import kotlinx.coroutines.launch class DisplaySettings(private val carContext: CarContext) : Screen(carContext) { @@ -22,9 +23,7 @@ class DisplaySettings(private val carContext: CarContext) : Screen(carContext) { init { lifecycleScope.launch { - settingsViewModel.show3D.collect { - invalidate() - } + settingsViewModel.show3D.first() } } 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 cec17b9..4b91479 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 @@ -77,8 +77,8 @@ class NavigationScreen( init { observerManager.attachAllObservers(this) lifecycleScope.launch { - getSettingsViewModel(carContext).routingEngine.collect { - } + getSettingsViewModel(carContext).routingEngine.first() + getSettingsViewModel(carContext).recentPlaces.first() } } @@ -258,14 +258,17 @@ class NavigationScreen( } else { Distance.UNIT_METERS } - val nextStep = routeModel.nextStep(carContext = carContext) - return RoutingInfo.Builder() + + val routingInfo = RoutingInfo.Builder() .setCurrentStep( routeModel.currentStep(carContext = carContext), Distance.create(currentDistance, displayUnit) ) - .setNextStep(nextStep) - .build() + if (routeModel.navState.nextStep) { + val nextStep = routeModel.nextStep(carContext = carContext) + routingInfo.setNextStep(nextStep) + } + return routingInfo.build() } private fun createActionStripBuilder(): ActionStrip.Builder { 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 165bb23..ec90a2f 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 @@ -13,6 +13,7 @@ import androidx.lifecycle.lifecycleScope import com.kouros.data.R import com.kouros.navigation.model.NavigationViewModel import com.kouros.navigation.utils.getSettingsViewModel +import kotlinx.coroutines.flow.first import kotlinx.coroutines.launch @@ -32,13 +33,9 @@ class NavigationSettings( init { lifecycleScope.launch { - settingsViewModel.avoidTollway.collect { - settingsViewModel.avoidMotorway.collect { - settingsViewModel.carLocation.collect { - invalidate() - } - } - } + settingsViewModel.avoidTollway.first() + settingsViewModel.avoidMotorway.first() + settingsViewModel.carLocation.first() } } @@ -53,7 +50,12 @@ class NavigationSettings( settingsViewModel.onAvoidMotorway(checked) motorWayToggleState = !motorWayToggleState }.setChecked(motorWayToggleState).build() - listBuilder.addItem(buildRowForTemplate(R.string.avoid_highways_row_title, highwayToggle)) + listBuilder.addItem( + buildRowForTemplate( + R.string.avoid_highways_row_title, + highwayToggle + ) + ) // Tollway val tollwayToggle: Toggle = @@ -92,7 +94,7 @@ class NavigationSettings( .setSingleList(listBuilder.build()) .setHeader( Header.Builder() - .setTitle(carContext.getString(R.string.display_settings)) + .setTitle(carContext.getString(R.string.navigation_settings)) .setStartHeaderAction(Action.BACK) .build() ) diff --git a/common/car/src/main/java/com/kouros/navigation/car/screen/PasswordSettings.kt b/common/car/src/main/java/com/kouros/navigation/car/screen/PasswordSettings.kt index 0808856..993c755 100644 --- a/common/car/src/main/java/com/kouros/navigation/car/screen/PasswordSettings.kt +++ b/common/car/src/main/java/com/kouros/navigation/car/screen/PasswordSettings.kt @@ -12,6 +12,7 @@ import androidx.lifecycle.lifecycleScope import com.kouros.data.R import com.kouros.navigation.model.NavigationViewModel import com.kouros.navigation.utils.getSettingsViewModel +import kotlinx.coroutines.flow.first import kotlinx.coroutines.launch class PasswordSettings( @@ -24,9 +25,7 @@ class PasswordSettings( init { lifecycleScope.launch { - settingsViewModel.tomTomApiKey.collect { - invalidate() - } + settingsViewModel.tomTomApiKey.first() } } override fun onGetTemplate(): Template { 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 7f2fb98..76d9920 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 @@ -15,6 +15,7 @@ import com.kouros.data.R import com.kouros.navigation.data.RouteEngine import com.kouros.navigation.model.NavigationViewModel import com.kouros.navigation.utils.getSettingsViewModel +import kotlinx.coroutines.flow.first import kotlinx.coroutines.launch class RoutingSettings(private val carContext: CarContext, private var navigationViewModel: NavigationViewModel) : Screen(carContext) { @@ -24,9 +25,7 @@ class RoutingSettings(private val carContext: CarContext, private var navigation init { lifecycleScope.launch { - settingsViewModel.routingEngine.collect { - invalidate() - } + settingsViewModel.routingEngine.first() } } 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 46bc8f3..753c23b 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 @@ -55,17 +55,12 @@ data class ContactData( data class StepData ( var instruction: String, - + var street: String, var leftStepDistance: Double, - var currentManeuverType: Int, - var icon: Int, - var arrivalTime : Long, - var leftDistance: Double, - var lane: List = listOf(Lane(location(0.0, 0.0), valid = false, indications = emptyList())), var exitNumber: Int = 0, ) @@ -76,7 +71,7 @@ data class Locations ( var lat : Double, var lon : Double, var street : String = "", - val search_filter: String, + val searchFilter: String, ) data class SearchFilter( @@ -123,7 +118,7 @@ object Constants { val homeVogelhart = location(11.5793748, 48.185749) val homeHohenwaldeck = location( 11.594322, 48.1164817) - const val NEXT_STEP_THRESHOLD = 300.0 + const val NEXT_STEP_THRESHOLD = 1000.0 const val MAXIMAL_SNAP_CORRECTION = 50.0 @@ -138,6 +133,8 @@ object Constants { const val GMS_CAR_SPEED_PERMISSION = "com.google.android.gms.permission.CAR_SPEED" const val AUTOMOTIVE_CAR_SPEED_PERMISSION = "android.car.permission.CAR_SPEED" + + const val TILT = 60.0 } diff --git a/common/data/src/main/java/com/kouros/navigation/data/NavigationState.kt b/common/data/src/main/java/com/kouros/navigation/data/NavigationState.kt index 483c399..cc2ed68 100644 --- a/common/data/src/main/java/com/kouros/navigation/data/NavigationState.kt +++ b/common/data/src/main/java/com/kouros/navigation/data/NavigationState.kt @@ -18,6 +18,6 @@ data class NavigationState ( val currentRouteIndex: Int = 0, val destination: Place = Place(), val carConnection: Int = 0, - var routingEngine: Int = 0, - + val routingEngine: Int = 0, + val nextStep: Boolean = false, ) \ No newline at end of file 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 d68b87e..5d6dc3e 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 @@ -36,8 +36,8 @@ class TomTomRoute { val allIntersections = mutableListOf() val steps = mutableListOf() var lastPointIndex = 0 - for (index in 1..< route.guidance.instructions.size) { - val lastInstruction = route.guidance.instructions[index-1] + for (index in 1.. val lanes = mutableListOf() var startIndex = 0 - - section.lanes?.forEach { itLane -> - val lane = Lane( - location( - waypoints[section.startPointIndex][0], - waypoints[section.startPointIndex][1] - ), - itLane.directions.first() == itLane.follow, - itLane.directions - ) - startIndex = section.startPointIndex - lanes.add(lane) - } - intersections.add(Intersection(waypoints[startIndex], lanes)) + section.lanes?.forEach { itLane -> + val lane = Lane( + location( + waypoints[section.startPointIndex][0], + waypoints[section.startPointIndex][1] + ), + itLane.directions.first() == itLane.follow, + itLane.directions + ) + startIndex = section.startPointIndex + lanes.add(lane) + } + intersections.add(Intersection(waypoints[startIndex], lanes)) } allIntersections.addAll(intersections) @@ -140,6 +139,7 @@ class TomTomRoute { "BEAR_RIGHT" -> { newType = androidx.car.app.navigation.model.Maneuver.TYPE_TURN_SLIGHT_RIGHT } + "BEAR_LEFT" -> { newType = androidx.car.app.navigation.model.Maneuver.TYPE_TURN_SLIGHT_LEFT } @@ -171,12 +171,15 @@ class TomTomRoute { "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 @@ -187,10 +190,11 @@ class TomTomRoute { } private fun exitNumber( - instruction: Instruction + instruction: Instruction ): Int { - return if ( instruction.exitNumber == null - || instruction.exitNumber.isEmpty()) { + return if (instruction.exitNumber == null + || instruction.exitNumber.isEmpty() + ) { 0 } else { instruction.exitNumber.toInt() diff --git a/common/data/src/main/java/com/kouros/navigation/data/valhalla/ValhallaRepository.kt b/common/data/src/main/java/com/kouros/navigation/data/valhalla/ValhallaRepository.kt index ae26cb4..b8e11bb 100644 --- a/common/data/src/main/java/com/kouros/navigation/data/valhalla/ValhallaRepository.kt +++ b/common/data/src/main/java/com/kouros/navigation/data/valhalla/ValhallaRepository.kt @@ -33,9 +33,9 @@ class ValhallaRepository : NavigationRepository() { Locations( lat = currentLocation.latitude, lon = currentLocation.longitude, - search_filter = exclude + searchFilter = exclude ), - Locations(lat = location.latitude, lon = location.longitude, search_filter = exclude) + Locations(lat = location.latitude, lon = location.longitude, searchFilter = exclude) ) val valhallaLocation = ValhallaLocation( locations = vLocation, 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 96cafa4..12e95c4 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 @@ -15,7 +15,6 @@ import com.kouros.data.R import com.kouros.navigation.data.StepData import java.util.Collections import java.util.Locale -import kotlin.collections.forEach class IconMapper() { @@ -148,22 +147,14 @@ class IconMapper() { return laneDirection } - fun createCarIcon(iconCompat: IconCompat): CarIcon { - return CarIcon.Builder(iconCompat).build() - } - - fun createCarIconx(carContext: Context, @DrawableRes iconRes: Int): CarIcon { - return CarIcon.Builder(IconCompat.createWithResource(carContext, iconRes)).build() - } - fun createLaneIcon(context: Context, stepData: StepData): IconCompat { val bitmaps = mutableListOf() - stepData.lane.forEach { - if (it.indications.isNotEmpty()) { - Collections.sort(it.indications) - val resource = laneToResource(it.indications, stepData) + stepData.lane.forEach { lane -> + if (lane.indications.isNotEmpty()) { + Collections.sort(lane.indications) + val resource = laneToResource(lane.indications, stepData) if (resource.isNotEmpty()) { - val id = resourceId(resource); + val id = resourceId(resource) val bitMap = BitmapFactory.decodeResource(context.resources, id) bitmaps.add(bitMap) } @@ -182,30 +173,30 @@ class IconMapper() { return bitmaps.first() } val bmOverlay = createBitmap( - bitmaps.first().getWidth() * (bitmaps.size * 1.5).toInt(), - bitmaps.first().getHeight(), - bitmaps.first().getConfig()!! + bitmaps.first().width * (bitmaps.size * 1.5).toInt(), + bitmaps.first().height, + bitmaps.first().config!! ) val canvas = Canvas(bmOverlay) canvas.drawBitmap(bitmaps.first(), matrix, null) var i = 0 - bitmaps.forEach { + bitmaps.forEach { bitmap -> if (i > 0) { matrix.setTranslate(i * 45F, 0F) - canvas.drawBitmap(it, matrix, null) + canvas.drawBitmap(bitmap, matrix, null) } i++ } return bmOverlay } - private fun laneToResource(directions: List, stepData: StepData): String { + fun laneToResource(directions: List, stepData: StepData): String { var direction = "" - directions.forEach { + directions.forEach { indication -> direction = if (direction.isEmpty()) { - it.trim() + indication.trim() } else { - "${direction}_${it.trim()}" + "${direction}_${indication.trim()}" } } direction = direction.lowercase() @@ -232,8 +223,10 @@ class IconMapper() { "right" -> if (stepData.currentManeuverType == Maneuver.TYPE_TURN_NORMAL_RIGHT) "${direction}_o" else "${direction}_x" "left" -> if (stepData.currentManeuverType == Maneuver.TYPE_TURN_NORMAL_LEFT) "${direction}_o" else "${direction}_x" "straight" -> if (stepData.currentManeuverType == Maneuver.TYPE_STRAIGHT) "${direction}_o" else "${direction}_x" - "right_slight", "slight_right" -> if (stepData.currentManeuverType == Maneuver.TYPE_TURN_SLIGHT_RIGHT) "${direction}_o" else "${direction}_x" - "left_slight", "slight_left" -> if (stepData.currentManeuverType == Maneuver.TYPE_TURN_SLIGHT_LEFT) "${direction}_o" else "${direction}_x" + "right_slight", "slight_right" -> if (stepData.currentManeuverType == Maneuver.TYPE_TURN_SLIGHT_RIGHT + || stepData.currentManeuverType == Maneuver.TYPE_TURN_NORMAL_RIGHT) "slight_right_o" else "slight_right_x" + "left_slight", "slight_left" -> if (stepData.currentManeuverType == Maneuver.TYPE_TURN_SLIGHT_LEFT + || stepData.currentManeuverType == Maneuver.TYPE_TURN_NORMAL_LEFT) "slight_left_o" else "slight_left_x" else -> { "" } @@ -246,22 +239,22 @@ class IconMapper() { return when (variableName) { "left_x" -> R.drawable.left_x "left_o" -> R.drawable.left_o - "left_o_right_x" -> R.drawable.left_o_right_x + "left_o_straight_x" -> R.drawable.left_o_straight_x + "left_x_straight_o" -> R.drawable.left_x_straight_o + "slight_left_x" -> R.drawable.slight_left_x + "slight_left_o" -> R.drawable.slight_left_o "right_x" -> R.drawable.right_x "right_o" -> R.drawable.right_o "slight_right_x" -> R.drawable.slight_right_x "slight_right_o" -> R.drawable.slight_right_o - "slight_left_x" -> R.drawable.left_x "straight_x" -> R.drawable.straight_x "right_o_straight_x" -> R.drawable.right_o_straight_x "right_x_straight_x" -> R.drawable.right_x_straight_x - "right_x_straight_o" -> R.drawable.right_x_straight_x + "right_x_straight_o" -> R.drawable.right_x_straight_o "straight_o" -> R.drawable.straight_o - "left_o_straight_x" -> R.drawable.left_o_straight_x - "left_x_straight_o" -> R.drawable.left_x_straight_o else -> { - R.drawable.left_x + R.drawable.ic_zoom_out_24 } } } -} \ No newline at end of file +} 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 65ca5f4..35cd7a7 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 @@ -26,9 +26,6 @@ class RouteCalculator(var routeModel: RouteModel) { routeModel.navState.route.currentStepIndex = step.index step.waypointIndex = wayIndex step.wayPointLocation = location(waypoint[0], waypoint[1]) - //routeModel.navState = routeModel.navState.copy( - // routeBearing = routeModel.navState.lastLocation.bearingTo(location) - // ) } } } @@ -64,8 +61,13 @@ class RouteCalculator(var routeModel: RouteModel) { val loc1 = location(step.maneuver.waypoints[i][0], step.maneuver.waypoints[i][1]) val loc2 = location(step.maneuver.waypoints[i + 1][0], step.maneuver.waypoints[i + 1][1]) + val locationDistance = loc1.distanceTo(routeModel.navState.lastLocation) val distance = loc1.distanceTo(loc2) - leftDistance += distance + leftDistance += if (locationDistance < distance) { + locationDistance + } else { + distance + } } return (leftDistance / 10.0).roundToInt() * 10.0 } @@ -73,10 +75,12 @@ class RouteCalculator(var routeModel: RouteModel) { /** Returns the left distance in m. */ fun travelLeftDistance(): Double { var leftDistance = 0.0 + // distance for next steps for (i in routeModel.route.currentStepIndex + 1.. NEXT_STEP_THRESHOLD) { - streetName = currentStep.street - curManeuverType = Maneuver.TYPE_STRAIGHT + if (navState.nextStep) { + if (distanceToNextStep > NEXT_STEP_THRESHOLD) { + streetName = currentStep.street + curManeuverType = Maneuver.TYPE_STRAIGHT + } } val exitNumber = currentStep.maneuver.exit val maneuverIcon = navState.iconMapper.maneuverIcon(curManeuverType) @@ -84,6 +86,7 @@ open class RouteModel { // Construct and return the final StepData object return StepData( instruction = streetName, + street = currentStep.street, leftStepDistance = distanceToNextStep, currentManeuverType = navState.maneuverType, icon = maneuverIcon, @@ -109,6 +112,7 @@ open class RouteModel { // Construct and return the final StepData object return StepData( instruction = streetName, + street = "", leftStepDistance = distanceToNextStep, currentManeuverType = maneuverType, icon = maneuverIcon, 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 index 22024ef..2c99782 100644 --- a/common/data/src/main/java/com/kouros/navigation/model/SettingsViewModel.kt +++ b/common/data/src/main/java/com/kouros/navigation/model/SettingsViewModel.kt @@ -57,6 +57,12 @@ class SettingsViewModel(private val repository: SettingsRepository) : ViewModel( "" ) + val recentPlaces = repository.recentPlacesFlow.stateIn( + viewModelScope, + SharingStarted.WhileSubscribed(5_000), + "" + ) + fun onShow3DChanged(enabled: Boolean) { viewModelScope.launch { repository.setShow3D(enabled) } } diff --git a/common/data/src/main/res/drawable/empty.png b/common/data/src/main/res/drawable/empty.png new file mode 100644 index 0000000000000000000000000000000000000000..bcef144e11b6e13bef257ab5d4f24d47af808126 GIT binary patch literal 4249 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 literal 0 HcmV?d00001 diff --git a/common/data/src/main/res/drawable/slight_left_x.png b/common/data/src/main/res/drawable/slight_left_x.png new file mode 100644 index 0000000000000000000000000000000000000000..f968d594ad350f69eb873886c4214fcb7c07e336 GIT binary patch literal 5150 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 TomdTa3r`*p=AZqSFBbj_!LW@S literal 0 HcmV?d00001 diff --git a/common/data/src/test/java/com/kouros/navigation/model/IconMapperTest.kt b/common/data/src/test/java/com/kouros/navigation/model/IconMapperTest.kt new file mode 100644 index 0000000..f7159e6 --- /dev/null +++ b/common/data/src/test/java/com/kouros/navigation/model/IconMapperTest.kt @@ -0,0 +1,98 @@ +package com.kouros.navigation.model + +import androidx.car.app.navigation.model.LaneDirection +import androidx.car.app.navigation.model.Maneuver +import com.kouros.data.R +import com.kouros.navigation.data.StepData +import org.junit.Assert.assertEquals +import org.junit.Test + +class IconMapperTest { + + private val iconMapper = IconMapper() + + @Test + fun `maneuverIcon returns correct icon for each maneuver type`() { + assertEquals(R.drawable.ic_turn_name_change, iconMapper.maneuverIcon(Maneuver.TYPE_STRAIGHT)) + assertEquals(R.drawable.ic_turn_destination, iconMapper.maneuverIcon(Maneuver.TYPE_DESTINATION)) + assertEquals(R.drawable.ic_turn_destination, iconMapper.maneuverIcon(Maneuver.TYPE_DESTINATION_RIGHT)) + assertEquals(R.drawable.ic_turn_destination, iconMapper.maneuverIcon(Maneuver.TYPE_DESTINATION_LEFT)) + assertEquals(R.drawable.ic_turn_destination, iconMapper.maneuverIcon(Maneuver.TYPE_DESTINATION_STRAIGHT)) + assertEquals(R.drawable.ic_turn_normal_right, iconMapper.maneuverIcon(Maneuver.TYPE_TURN_NORMAL_RIGHT)) + assertEquals(R.drawable.ic_turn_normal_left, iconMapper.maneuverIcon(Maneuver.TYPE_TURN_NORMAL_LEFT)) + assertEquals(R.drawable.ic_turn_slight_right, iconMapper.maneuverIcon(Maneuver.TYPE_OFF_RAMP_SLIGHT_RIGHT)) + assertEquals(R.drawable.ic_turn_slight_right, iconMapper.maneuverIcon(Maneuver.TYPE_TURN_SLIGHT_RIGHT)) + assertEquals(R.drawable.ic_turn_name_change, iconMapper.maneuverIcon(Maneuver.TYPE_KEEP_RIGHT)) + assertEquals(R.drawable.ic_turn_name_change, iconMapper.maneuverIcon(Maneuver.TYPE_KEEP_LEFT)) + assertEquals(R.drawable.ic_roundabout_ccw, iconMapper.maneuverIcon(Maneuver.TYPE_ROUNDABOUT_ENTER_CCW)) + assertEquals(R.drawable.ic_roundabout_ccw, iconMapper.maneuverIcon(Maneuver.TYPE_ROUNDABOUT_EXIT_CCW)) + assertEquals(R.drawable.ic_turn_u_turn_left, iconMapper.maneuverIcon(Maneuver.TYPE_U_TURN_LEFT)) + assertEquals(R.drawable.ic_turn_u_turn_right, iconMapper.maneuverIcon(Maneuver.TYPE_U_TURN_RIGHT)) + assertEquals(R.drawable.ic_turn_merge_symmetrical, iconMapper.maneuverIcon(Maneuver.TYPE_MERGE_LEFT)) + assertEquals(R.drawable.ic_turn_name_change, iconMapper.maneuverIcon(Maneuver.TYPE_UNKNOWN)) + assertEquals(R.drawable.ic_turn_name_change, iconMapper.maneuverIcon(Maneuver.TYPE_DEPART)) + } + + @Test + fun `addLanes returns correct lane direction`() { + val stepDataNormalLeft = StepData("", 0.0, Maneuver.TYPE_TURN_NORMAL_LEFT, 0, 0L, 0.0) + assertEquals(LaneDirection.SHAPE_NORMAL_LEFT, iconMapper.addLanes("left_straight", stepDataNormalLeft)) + assertEquals(LaneDirection.SHAPE_NORMAL_LEFT, iconMapper.addLanes("left", stepDataNormalLeft)) + assertEquals(LaneDirection.SHAPE_SLIGHT_LEFT, iconMapper.addLanes("left_slight", stepDataNormalLeft)) + assertEquals(LaneDirection.SHAPE_SLIGHT_LEFT, iconMapper.addLanes("slight_left", stepDataNormalLeft)) + + val stepDataStraight = StepData("", 0.0, Maneuver.TYPE_STRAIGHT, 0, 0L, 0.0) + assertEquals(LaneDirection.SHAPE_STRAIGHT, iconMapper.addLanes("left_straight", stepDataStraight)) + assertEquals(LaneDirection.SHAPE_STRAIGHT, iconMapper.addLanes("straight", stepDataStraight)) + assertEquals(LaneDirection.SHAPE_STRAIGHT, iconMapper.addLanes("right_straight", stepDataStraight)) + + val stepDataKeepLeft = StepData("", 0.0, Maneuver.TYPE_KEEP_LEFT, 0, 0L, 0.0) + assertEquals(LaneDirection.SHAPE_STRAIGHT, iconMapper.addLanes("straight", stepDataKeepLeft)) + assertEquals(LaneDirection.SHAPE_SLIGHT_LEFT, iconMapper.addLanes("left_slight", stepDataKeepLeft)) + + val stepDataKeepRight = StepData("", 0.0, Maneuver.TYPE_KEEP_RIGHT, 0, 0L, 0.0) + assertEquals(LaneDirection.SHAPE_STRAIGHT, iconMapper.addLanes("straight", stepDataKeepRight)) + + val stepDataNormalRight = StepData("", 0.0, Maneuver.TYPE_TURN_NORMAL_RIGHT, 0, 0L, 0.0) + assertEquals(LaneDirection.SHAPE_NORMAL_RIGHT, iconMapper.addLanes("right", stepDataNormalRight)) + assertEquals(LaneDirection.SHAPE_NORMAL_RIGHT, iconMapper.addLanes("right_straight", stepDataNormalRight)) + + val stepDataSlightRight = StepData("", 0.0, Maneuver.TYPE_TURN_SLIGHT_RIGHT, 0, 0L, 0.0) + assertEquals(LaneDirection.SHAPE_NORMAL_RIGHT, iconMapper.addLanes("right_slight", stepDataSlightRight)) + assertEquals(LaneDirection.SHAPE_NORMAL_RIGHT, iconMapper.addLanes("slight_right", stepDataSlightRight)) + + val stepDataUnknown = StepData("", 0.0, Maneuver.TYPE_UNKNOWN, 0, 0L, 0.0) + assertEquals(LaneDirection.SHAPE_UNKNOWN, iconMapper.addLanes("left_straight", stepDataUnknown)) + assertEquals(LaneDirection.SHAPE_UNKNOWN, iconMapper.addLanes("left", stepDataUnknown)) + assertEquals(LaneDirection.SHAPE_UNKNOWN, iconMapper.addLanes("straight", stepDataUnknown)) + assertEquals(LaneDirection.SHAPE_UNKNOWN, iconMapper.addLanes("right", stepDataUnknown)) + assertEquals(LaneDirection.SHAPE_UNKNOWN, iconMapper.addLanes("right_straight", stepDataUnknown)) + assertEquals(LaneDirection.SHAPE_UNKNOWN, iconMapper.addLanes("left_slight", stepDataUnknown)) + assertEquals(LaneDirection.SHAPE_UNKNOWN, iconMapper.addLanes("right_slight", stepDataUnknown)) + assertEquals(LaneDirection.SHAPE_UNKNOWN, iconMapper.addLanes("invalid_direction", stepDataNormalLeft)) + } + + @Test + fun `laneToResource returns correct resource string`() { + val stepDataNormalLeft = StepData("", 0.0, Maneuver.TYPE_TURN_NORMAL_LEFT, 0, 0L, 0.0) + assertEquals("left_o_straight_x", iconMapper.laneToResource(listOf("left", "straight"), stepDataNormalLeft)) + assertEquals("left_o", iconMapper.laneToResource(listOf("left"), stepDataNormalLeft)) + assertEquals("slight_left_o", iconMapper.laneToResource(listOf("slight_left"), stepDataNormalLeft)) + + val stepDataStraight = StepData("", 0.0, Maneuver.TYPE_STRAIGHT, 0, 0L, 0.0) + assertEquals("left_x_straight_o", iconMapper.laneToResource(listOf("left", "straight"), stepDataStraight)) + assertEquals("straight_o", iconMapper.laneToResource(listOf("straight"), stepDataStraight)) + assertEquals("right_x_straight_o", iconMapper.laneToResource(listOf("right_straight"), stepDataStraight)) + + val stepDataNormalRight = StepData("", 0.0, Maneuver.TYPE_TURN_NORMAL_RIGHT, 0, 0L, 0.0) + assertEquals("right_x_straight_x", iconMapper.laneToResource(listOf("right_straight"), stepDataNormalRight)) + assertEquals("right_o", iconMapper.laneToResource(listOf("right"), stepDataNormalRight)) + + val stepDataSlightRight = StepData("", 0.0, Maneuver.TYPE_TURN_SLIGHT_RIGHT, 0, 0L, 0.0) + assertEquals("right_o_straight_o", iconMapper.laneToResource(listOf("right_straight"), stepDataSlightRight)) + + val stepDataUnknown = StepData("", 0.0, Maneuver.TYPE_UNKNOWN, 0, 0L, 0.0) + assertEquals("left_x_straight_x", iconMapper.laneToResource(listOf("left", "straight"), stepDataUnknown)) + assertEquals("", iconMapper.laneToResource(listOf("invalid"), stepDataUnknown)) + } +}