From 11e9dbb21ee0311d37e4a7b2ee592d16077d2e15 Mon Sep 17 00:00:00 2001 From: Dimitris Date: Tue, 3 Mar 2026 16:34:03 +0100 Subject: [PATCH] Distance settings --- app/build.gradle.kts | 6 +- .../kouros/navigation/ui/NavigationScreen.kt | 6 +- .../navigation/ExampleInstrumentedTest.kt | 24 --- .../com/kouros/navigation/ExampleUnitTest.kt | 17 -- .../kouros/navigation/car/RouteModelTest.kt | 6 +- .../navigation/car/DeviceLocationManager.kt | 2 +- .../navigation/car/NavigationSession.kt | 48 ++++- .../car/navigation/RouteCarModel.kt | 24 +-- .../navigation/car/screen/DisplaySettings.kt | 6 + .../navigation/car/screen/DistanceSettings.kt | 84 +++++++++ .../navigation/car/screen/NavigationScreen.kt | 172 ++++++++++++++++-- .../navigation/car/screen/PlaceListScreen.kt | 2 + .../car/screen/observers/MaxSpeedObserver.kt | 15 ++ .../observers/NavigationObserverCallback.kt | 34 ++++ .../observers/NavigationObserverManager.kt | 42 +++++ .../screen/observers/PlaceSearchObserver.kt | 27 +++ .../screen/observers/RecentPlaceObserver.kt | 18 ++ .../car/screen/observers/RouteObserver.kt | 17 ++ .../screen/observers/SpeedCameraObserver.kt | 16 ++ .../car/screen/observers/TrafficObserver.kt | 16 ++ .../car/screen/observers/ObserversTest.kt | 153 ++++++++++++++++ common/data/build.gradle.kts | 2 - common/data/proguard-rules.pro | 3 +- .../kouros/navigation/data/NavigationState.kt | 2 +- .../data/datastore/DataStoreManager.kt | 20 +- .../kouros/navigation/data/route/Summary.kt | 10 +- .../navigation/data/tomtom/TomTomRoute.kt | 4 +- .../navigation/model/NavigationViewModel.kt | 1 + .../navigation/model/RouteCalculator.kt | 2 +- .../navigation/model/SettingsViewModel.kt | 14 ++ .../repository/SettingsRepository.kt | 10 +- .../navigation/utils/NavigationUtils.kt | 34 +++- .../com/kouros/navigation/utils/Settings.kt | 4 + common/data/src/main/res/drawable/empty.png | Bin 4249 -> 4411 bytes .../main/res/drawable/right_x_straight_o.png | Bin 0 -> 5617 bytes .../data/src/main/res/values-de/strings.xml | 9 +- common/data/src/main/res/values/strings.xml | 6 +- .../kouros/navigation/model/IconMapperTest.kt | 24 +-- gradle/libs.versions.toml | 1 - 39 files changed, 753 insertions(+), 128 deletions(-) delete mode 100644 automotive/src/androidTest/java/com/kouros/navigation/ExampleInstrumentedTest.kt delete mode 100644 automotive/src/test/java/com/kouros/navigation/ExampleUnitTest.kt create mode 100644 common/car/src/main/java/com/kouros/navigation/car/screen/DistanceSettings.kt create mode 100644 common/car/src/main/java/com/kouros/navigation/car/screen/observers/MaxSpeedObserver.kt create mode 100644 common/car/src/main/java/com/kouros/navigation/car/screen/observers/NavigationObserverCallback.kt create mode 100644 common/car/src/main/java/com/kouros/navigation/car/screen/observers/NavigationObserverManager.kt create mode 100644 common/car/src/main/java/com/kouros/navigation/car/screen/observers/PlaceSearchObserver.kt create mode 100644 common/car/src/main/java/com/kouros/navigation/car/screen/observers/RecentPlaceObserver.kt create mode 100644 common/car/src/main/java/com/kouros/navigation/car/screen/observers/RouteObserver.kt create mode 100644 common/car/src/main/java/com/kouros/navigation/car/screen/observers/SpeedCameraObserver.kt create mode 100644 common/car/src/main/java/com/kouros/navigation/car/screen/observers/TrafficObserver.kt create mode 100644 common/car/src/test/java/com/kouros/navigation/car/screen/observers/ObserversTest.kt create mode 100644 common/data/src/main/res/drawable/right_x_straight_o.png diff --git a/app/build.gradle.kts b/app/build.gradle.kts index a984b8e..b1bd28a 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 = 57 - versionName = "0.2.0.57" + versionCode = 59 + versionName = "0.2.0.59" base.archivesName = "navi-$versionName" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" } @@ -83,8 +83,8 @@ dependencies { implementation(libs.accompanist.permissions) - implementation(project(":common:data")) implementation(project(":common:car")) + implementation(project(":common:data")) implementation(libs.play.services.location) implementation(libs.androidx.compose.runtime) implementation(libs.androidx.navigation.compose) 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 da92682..630d2af 100644 --- a/app/src/main/java/com/kouros/navigation/ui/NavigationScreen.kt +++ b/app/src/main/java/com/kouros/navigation/ui/NavigationScreen.kt @@ -20,12 +20,13 @@ import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import com.kouros.data.R import com.kouros.navigation.data.StepData +import com.kouros.navigation.utils.formattedDistance import com.kouros.navigation.utils.round private const val MANEUVER_TYPE_EXIT_RIGHT = 45 private const val MANEUVER_TYPE_EXIT_LEFT = 46 private const val METERS_PER_KILOMETER = 1000.0 -private const val DISTANCE_THRESHOLD_METERS = 1000 +private const val DISTANCE_THRESHOLD = 1000 private val CardTopPadding = 60.dp private val CardElevation = 6.dp @@ -92,8 +93,9 @@ fun NavigationInfo( @Composable private fun DistanceText(distance: Double) { + val distancexx = formattedDistance(2, distance) val formattedDistance = when { - distance < DISTANCE_THRESHOLD_METERS -> "${distance.toInt()} m" + distance < DISTANCE_THRESHOLD -> "${distance.toInt()} m" else -> "${(distance / METERS_PER_KILOMETER).round(1)} km" } diff --git a/automotive/src/androidTest/java/com/kouros/navigation/ExampleInstrumentedTest.kt b/automotive/src/androidTest/java/com/kouros/navigation/ExampleInstrumentedTest.kt deleted file mode 100644 index 06d6582..0000000 --- a/automotive/src/androidTest/java/com/kouros/navigation/ExampleInstrumentedTest.kt +++ /dev/null @@ -1,24 +0,0 @@ -package com.kouros.navigation - -import androidx.test.platform.app.InstrumentationRegistry -import androidx.test.ext.junit.runners.AndroidJUnit4 - -import org.junit.Test -import org.junit.runner.RunWith - -import org.junit.Assert.* - -/** - * Instrumented test, which will execute on an Android device. - * - * See [testing documentation](http://d.android.com/tools/testing). - */ -@RunWith(AndroidJUnit4::class) -class ExampleInstrumentedTest { - @Test - fun useAppContext() { - // Context of the app under test. - val appContext = InstrumentationRegistry.getInstrumentation().targetContext - assertEquals("com.kouros.navigation", appContext.packageName) - } -} \ No newline at end of file diff --git a/automotive/src/test/java/com/kouros/navigation/ExampleUnitTest.kt b/automotive/src/test/java/com/kouros/navigation/ExampleUnitTest.kt deleted file mode 100644 index cb0e6f0..0000000 --- a/automotive/src/test/java/com/kouros/navigation/ExampleUnitTest.kt +++ /dev/null @@ -1,17 +0,0 @@ -package com.kouros.navigation - -import org.junit.Test - -import org.junit.Assert.* - -/** - * Example local unit test, which will execute on the development machine (host). - * - * See [testing documentation](http://d.android.com/tools/testing). - */ -class ExampleUnitTest { - @Test - fun addition_isCorrect() { - assertEquals(4, 2 + 2) - } -} \ No newline at end of file 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 058e249..3b8c3ff 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 @@ -102,7 +102,7 @@ class RouteModelTest { } else { assertEquals(stepData.currentManeuverType, Maneuver.TYPE_TURN_NORMAL_LEFT) } - assertEquals(stepData.leftStepDistance, 300.0, 1.0) + assertEquals(stepData.leftStepDistance, 301.0, 1.0) } @Test @@ -192,7 +192,7 @@ class RouteModelTest { val location: Location = location( 11.584578, 48.183653) routeModel.updateLocation(location, NavigationViewModel(TomTomRepository()) ) val step = routeModel.currentStep() - assertEquals(step.leftStepDistance, 650.0, 0.1) + assertEquals(step.leftStepDistance, 645.0, 1.0) } @Test @@ -200,6 +200,6 @@ class RouteModelTest { val location: Location = location( 11.578911, 48.185565) routeModel.updateLocation(location, NavigationViewModel(TomTomRepository()) ) val step = routeModel.currentStep() - assertEquals(step.leftStepDistance , 30.0, 1.0) + assertEquals(step.leftStepDistance , 34.0, 1.0) } } \ No newline at end of file diff --git a/common/car/src/main/java/com/kouros/navigation/car/DeviceLocationManager.kt b/common/car/src/main/java/com/kouros/navigation/car/DeviceLocationManager.kt index d08469e..dc14488 100644 --- a/common/car/src/main/java/com/kouros/navigation/car/DeviceLocationManager.kt +++ b/common/car/src/main/java/com/kouros/navigation/car/DeviceLocationManager.kt @@ -43,7 +43,7 @@ class DeviceLocationManager( * Only processes location if car location hardware is not being used. */ private val locationListener: LocationListenerCompat = LocationListenerCompat { location -> - if (location != null && shouldUseDeviceLocation) { + if (shouldUseDeviceLocation) { onLocationUpdate(location) } } 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 ea28dfe..6d5f5e6 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 @@ -1,7 +1,6 @@ package com.kouros.navigation.car -import android.Manifest -import android.content.Context +import android.Manifest.permission import android.content.Intent import android.content.pm.PackageManager import android.location.Location @@ -11,9 +10,10 @@ import androidx.car.app.Screen import androidx.car.app.ScreenManager import androidx.car.app.Session import androidx.car.app.connection.CarConnection -import androidx.core.net.toUri +import androidx.car.app.navigation.NavigationManager +import androidx.car.app.navigation.NavigationManagerCallback +import androidx.car.app.navigation.model.Trip import androidx.lifecycle.DefaultLifecycleObserver -import androidx.lifecycle.Lifecycle import androidx.lifecycle.LifecycleObserver import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.ViewModelStore @@ -23,6 +23,8 @@ import com.kouros.navigation.car.navigation.RouteCarModel import com.kouros.navigation.car.screen.NavigationScreen import com.kouros.navigation.car.screen.RequestPermissionScreen import com.kouros.navigation.car.screen.SearchScreen +import com.kouros.navigation.data.Constants.AUTOMOTIVE_CAR_SPEED_PERMISSION +import com.kouros.navigation.data.Constants.GMS_CAR_SPEED_PERMISSION 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 @@ -35,9 +37,7 @@ import com.kouros.navigation.utils.GeoUtils.snapLocation import com.kouros.navigation.utils.NavigationUtils.getViewModel import kotlinx.coroutines.awaitCancellation import kotlinx.coroutines.launch -import android.Manifest.permission -import com.kouros.navigation.data.Constants.AUTOMOTIVE_CAR_SPEED_PERMISSION -import com.kouros.navigation.data.Constants.GMS_CAR_SPEED_PERMISSION + /** * Main session for Android Auto/Automotive OS navigation. @@ -65,12 +65,17 @@ class NavigationSession : Session(), NavigationScreen.Listener { // Manages device GPS location updates lateinit var deviceLocationManager: DeviceLocationManager + lateinit var navigationManager: NavigationManager + /** * Lifecycle observer for managing session lifecycle events. * Cleans up resources when the session is destroyed. */ private val lifecycleObserver: LifecycleObserver = object : DefaultLifecycleObserver { override fun onDestroy(owner: LifecycleOwner) { + if (::navigationManager.isInitialized) { + navigationManager.clearNavigationManagerCallback() + } if (::carSensorManager.isInitialized) { carSensorManager.cleanup() } @@ -178,6 +183,21 @@ class NavigationSession : Session(), NavigationScreen.Listener { * Initializes managers for rendering, sensors, and location. */ private fun initializeManagers() { + + navigationManager = carContext.getCarService(NavigationManager::class.java) + navigationManager.setNavigationManagerCallback(object : NavigationManagerCallback { + override fun onAutoDriveEnabled() { + // Called when the app should simulate navigation (e.g., for testing) + // Implement your simulation logic here + Log.d("CarApp", "Auto Drive Enabled") + } + + override fun onStopNavigation() { + // Called when the user stops navigation in the car screen + Log.d("CarApp", "Stop Navigation Requested") + // Stop turn-by-turn logic and clean up + } + }) surfaceRenderer = SurfaceRenderer(carContext, lifecycle, routeModel, viewModelStoreOwner) carSensorManager = CarSensorManager( @@ -346,6 +366,20 @@ class NavigationSession : Session(), NavigationScreen.Listener { */ override fun stopNavigation() { routeModel.stopNavigation() + navigationManager.navigationEnded() + } + + /** + * Start navigation process. + * Called when user starts navigation + */ + override fun startNavigation() { + navigationManager.navigationStarted() + } + + override fun updateTrip(trip: Trip) { + Log.d("Trip", trip.toString()) + navigationManager.updateTrip(trip) } companion object { 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 9198115..ba204ac 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 @@ -1,6 +1,5 @@ package com.kouros.navigation.car.navigation -import android.location.Location import android.text.SpannableString import androidx.annotation.DrawableRes import androidx.annotation.StringRes @@ -26,9 +25,7 @@ import androidx.core.graphics.drawable.IconCompat import com.kouros.data.R import com.kouros.navigation.data.StepData import com.kouros.navigation.model.RouteModel -import com.kouros.navigation.utils.location -import java.util.Collections -import java.util.Locale +import com.kouros.navigation.utils.formattedDistance import java.util.TimeZone import java.util.concurrent.TimeUnit @@ -87,24 +84,21 @@ class RouteCarModel() : RouteModel() { return step } - fun travelEstimate(carContext: CarContext): TravelEstimate { + fun travelEstimate(carContext: CarContext, distanceMode: Int): TravelEstimate { val timeLeft = routeCalculator.travelLeftTime() val timeToDestinationMillis = TimeUnit.SECONDS.toMillis(timeLeft.toLong()) - val leftDistance = routeCalculator.travelLeftDistance() / 1000 - val displayUnit = if (leftDistance > 1.0) { - Distance.UNIT_KILOMETERS - } else { - Distance.UNIT_METERS - } + val distance = formattedDistance(distanceMode, routeCalculator.travelLeftDistance()) + val arrivalTime = DateTimeWithZone.create( routeCalculator.arrivalTime(), - TimeZone.getTimeZone("Europe/Berlin") + TimeZone.getDefault() ) + val traffic = (route.routes.first().summary.trafficDelay/60).toInt() val travelBuilder = TravelEstimate.Builder( // The estimated distance to the destination. Distance.create( - leftDistance, - displayUnit + distance.first, + distance.second ), // Arrival time at the destination with the destination time zone. arrivalTime ) @@ -115,7 +109,7 @@ class RouteCarModel() : RouteModel() { ) .setRemainingTimeColor(CarColor.GREEN) .setRemainingDistanceColor(CarColor.BLUE) - + .setTripText(CarText.create("$traffic min")) if (navState.travelMessage.isNotEmpty()) { travelBuilder.setTripIcon(createCarIcon(carContext, R.drawable.warning_24px)) travelBuilder.setTripText(CarText.create(navState.travelMessage)) 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 36e0291..56eca52 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 @@ -42,6 +42,12 @@ class DisplaySettings(private val carContext: CarContext) : Screen(carContext) { R.string.dark_mode ) ) + listBuilder.addItem( + buildRowForScreenTemplate( + DistanceSettings(carContext), + R.string.distance_units + ) + ) return ListTemplate.Builder() .setSingleList(listBuilder.build()) .setHeader( diff --git a/common/car/src/main/java/com/kouros/navigation/car/screen/DistanceSettings.kt b/common/car/src/main/java/com/kouros/navigation/car/screen/DistanceSettings.kt new file mode 100644 index 0000000..1373277 --- /dev/null +++ b/common/car/src/main/java/com/kouros/navigation/car/screen/DistanceSettings.kt @@ -0,0 +1,84 @@ +package com.kouros.navigation.car.screen + +import androidx.car.app.CarContext +import androidx.car.app.CarToast +import androidx.car.app.Screen +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.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.utils.getSettingsViewModel +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.launch + +class DistanceSettings(private val carContext: CarContext) : Screen(carContext) { + + private var distanceSettings = 0 + + val settingsViewModel = getSettingsViewModel(carContext) + + init { + lifecycleScope.launch { + settingsViewModel.distanceMode.first() + } + } + + override fun onGetTemplate(): Template { + distanceSettings = settingsViewModel.distanceMode.value + val templateBuilder = ListTemplate.Builder() + val radioList = + ItemList.Builder() + .addItem( + buildRowForTemplate( + R.string.automaticaly, + ) + ) + .addItem( + buildRowForTemplate( + R.string.kilometer, + ) + ) + .addItem( + buildRowForTemplate( + R.string.miles, + ) + ) + .setOnSelectedListener { index: Int -> + this.onSelected(index) + } + .setSelectedIndex(distanceSettings) + .build() + + return templateBuilder + .addSectionedList( + SectionedItemList.create( + radioList, + carContext.getString(R.string.distance_units) + ) + ) + .setHeader( + Header.Builder() + .setTitle(carContext.getString(R.string.distance_units)) + .setStartHeaderAction(Action.BACK) + .build() + ) + .build() + } + + + private fun onSelected(index: Int) { + settingsViewModel.onDistanceModeChanged(index) + } + + private fun buildRowForTemplate(title: Int): Row { + return Row.Builder() + .setTitle(carContext.getString(title)) + .build() + } + +} \ No newline at end of file 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 4b91479..716c970 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 @@ -1,6 +1,5 @@ package com.kouros.navigation.car.screen -import android.Manifest import android.content.pm.PackageManager import android.location.Location import android.location.LocationManager @@ -18,12 +17,16 @@ import androidx.car.app.model.Distance import androidx.car.app.model.Header import androidx.car.app.model.MessageTemplate import androidx.car.app.model.Template +import androidx.car.app.navigation.model.Destination import androidx.car.app.navigation.model.Maneuver import androidx.car.app.navigation.model.MapWithContentTemplate import androidx.car.app.navigation.model.MessageInfo import androidx.car.app.navigation.model.NavigationTemplate import androidx.car.app.navigation.model.RoutingInfo +import androidx.car.app.navigation.model.Trip import androidx.core.graphics.drawable.IconCompat +import androidx.lifecycle.Observer +import androidx.lifecycle.asLiveData import androidx.lifecycle.lifecycleScope import com.kouros.data.R import com.kouros.navigation.car.SurfaceRenderer @@ -31,12 +34,12 @@ import com.kouros.navigation.car.ViewStyle import com.kouros.navigation.car.navigation.RouteCarModel import com.kouros.navigation.car.screen.observers.NavigationObserverCallback import com.kouros.navigation.car.screen.observers.NavigationObserverManager -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.overpass.Elements import com.kouros.navigation.model.NavigationViewModel import com.kouros.navigation.utils.GeoUtils +import com.kouros.navigation.utils.formattedDistance import com.kouros.navigation.utils.getSettingsRepository import com.kouros.navigation.utils.getSettingsViewModel import com.kouros.navigation.utils.location @@ -48,6 +51,10 @@ import java.time.LocalDateTime import java.time.ZoneOffset import kotlin.math.absoluteValue +/** + * Main screen for car navigation. + * Handles different navigation states and provides corresponding templates. + */ class NavigationScreen( carContext: CarContext, private var surfaceRenderer: SurfaceRenderer, @@ -60,10 +67,15 @@ class NavigationScreen( interface Listener { /** Stops navigation. */ fun stopNavigation() + + /** Starts navigation. */ + fun startNavigation() + + /** Updates trip information. */ + fun updateTrip(trip: Trip) } val backGroundColor = CarColor.BLUE - var currentNavigationLocation = Location(LocationManager.GPS_PROVIDER) var recentPlace = Place() var navigationType = NavigationType.VIEW @@ -74,18 +86,25 @@ class NavigationScreen( val observerManager = NavigationObserverManager(navigationViewModel, this) + val repository = getSettingsRepository(carContext) + + var distanceMode = 0 + init { observerManager.attachAllObservers(this) lifecycleScope.launch { getSettingsViewModel(carContext).routingEngine.first() getSettingsViewModel(carContext).recentPlaces.first() + distanceMode = repository.distanceModeFlow.first() } } - // NavigationObserverCallback implementations + /** + * Handles the received route string. + * Starts navigation and invalidates the screen. + */ override fun onRouteReceived(route: String) { if (route.isNotEmpty()) { - val repository = getSettingsRepository(carContext) val routingEngine = runBlocking { repository.routingEngineFlow.first() } routeModel.navState = routeModel.navState.copy(routingEngine = routingEngine) navigationType = NavigationType.NAVIGATION @@ -94,26 +113,45 @@ class NavigationScreen( getSettingsViewModel(carContext).onLastRouteChanged(route) } surfaceRenderer.setRouteData() + listener.startNavigation() invalidate() } } + /** + * Checks if navigation is currently active. + */ override fun isNavigating(): Boolean = routeModel.isNavigating() + /** + * Handles the received recent place. + * Updates the navigation type to RECENT and invalidates the screen. + */ override fun onRecentPlaceReceived(place: Place) { recentPlace = place navigationType = NavigationType.RECENT invalidate() } + /** + * Handles received traffic data and updates the surface renderer. + */ override fun onTrafficReceived(traffic: Map) { surfaceRenderer.setTrafficData(traffic) } + /** + * Handles the received place search result. + * Navigates to the specified place. + */ override fun onPlaceSearchResultReceived(place: Place) { navigateToPlace(place) } + /** + * Handles received speed camera data. + * Updates the surface renderer with the camera locations. + */ override fun onSpeedCamerasReceived(cameras: List) { speedCameras = cameras val coordinates = mutableListOf>() @@ -124,15 +162,27 @@ class NavigationScreen( surfaceRenderer.speedCamerasData.value = speedData } + /** + * Handles received maximum speed data and updates the surface renderer. + */ override fun onMaxSpeedReceived(speed: Int) { surfaceRenderer.maxSpeed.value = speed } + /** + * Invalidates the screen. + */ override fun invalidateScreen() { invalidate() } + /** + * Returns the appropriate template based on the current navigation state. + */ override fun onGetTemplate(): Template { + repository.distanceModeFlow.asLiveData().observe(this, Observer { + distanceMode = it + }) val actionStripBuilder = createActionStripBuilder() return when (navigationType) { NavigationType.NAVIGATION -> navigationTemplate(actionStripBuilder) @@ -143,21 +193,28 @@ class NavigationScreen( } } + /** + * Creates and returns a NavigationTemplate for the active navigation state. + */ private fun navigationTemplate(actionStripBuilder: ActionStrip.Builder): NavigationTemplate { actionStripBuilder.addAction( stopAction() ) + updateTrip() return NavigationTemplate.Builder() .setNavigationInfo( getRoutingInfo() ) - .setDestinationTravelEstimate(routeModel.travelEstimate(carContext)) + .setDestinationTravelEstimate(routeModel.travelEstimate(carContext, distanceMode)) .setActionStrip(actionStripBuilder.build()) .setMapActionStrip(mapActionStripBuilder().build()) .setBackgroundColor(backGroundColor) .build() } + /** + * Creates and returns a template for the default view state. + */ private fun navigationViewTemplate(actionStripBuilder: ActionStrip.Builder): Template { return NavigationTemplate.Builder() .setBackgroundColor(CarColor.SECONDARY) @@ -167,6 +224,9 @@ class NavigationScreen( } + /** + * Creates and returns a template for the arrival or end state of navigation. + */ private fun navigationEndTemplate(actionStripBuilder: ActionStrip.Builder): Template { if (routeModel.navState.arrived) { val timer = object : CountDownTimer(8000, 1000) { @@ -189,6 +249,9 @@ class NavigationScreen( } + /** + * Creates and returns a NavigationTemplate specifically for when the destination is reached. + */ fun navigationArrivedTemplate(actionStripBuilder: ActionStrip.Builder): NavigationTemplate { var street = "" if (routeModel.navState.destination.street != null) { @@ -217,6 +280,9 @@ class NavigationScreen( .build() } + /** + * Creates and returns a template showing recent places or destinations. + */ fun navigationRecentPlaceTemplate(): Template { val messageTemplate = MessageTemplate.Builder( recentPlace.name + "\n" @@ -242,6 +308,9 @@ class NavigationScreen( return builder.build() } + /** + * Creates and returns a template for when the route is being recalculated. + */ fun navigationRerouteTemplate(actionStripBuilder: ActionStrip.Builder): Template { return NavigationTemplate.Builder() .setNavigationInfo(RoutingInfo.Builder().setLoading(true).build()) @@ -250,19 +319,15 @@ class NavigationScreen( .build() } + /** + * Builds and returns RoutingInfo based on the current step and distance. + */ fun getRoutingInfo(): RoutingInfo { - var currentDistance = routeModel.routeCalculator.leftStepDistance() - val displayUnit = if (currentDistance > 1000.0) { - currentDistance /= 1000.0 - Distance.UNIT_KILOMETERS - } else { - Distance.UNIT_METERS - } - + val distance = formattedDistance(distanceMode, routeModel.routeCalculator.leftStepDistance()) val routingInfo = RoutingInfo.Builder() .setCurrentStep( routeModel.currentStep(carContext = carContext), - Distance.create(currentDistance, displayUnit) + Distance.create(distance.first, distance.second) ) if (routeModel.navState.nextStep) { val nextStep = routeModel.nextStep(carContext = carContext) @@ -271,6 +336,9 @@ class NavigationScreen( return routingInfo.build() } + /** + * Creates an ActionStrip builder with common search and settings actions. + */ private fun createActionStripBuilder(): ActionStrip.Builder { val actionStripBuilder: ActionStrip.Builder = ActionStrip.Builder() actionStripBuilder.addAction( @@ -282,6 +350,9 @@ class NavigationScreen( return actionStripBuilder } + /** + * Creates an ActionStrip builder for map-related actions like zoom and pan. + */ private fun mapActionStripBuilder(): ActionStrip.Builder { val actionStripBuilder = ActionStrip.Builder() .addAction(zoomPlus()) @@ -295,6 +366,9 @@ class NavigationScreen( return actionStripBuilder } + /** + * Creates a stop navigation action. + */ private fun stopAction(): Action { return Action.Builder() .setTitle(carContext.getString(R.string.stop_action_title)) @@ -313,6 +387,9 @@ class NavigationScreen( .build() } + /** + * Creates an action to start navigation to a specific place. + */ private fun navigateAction(): Action { navigationType = NavigationType.NAVIGATION return Action.Builder() @@ -338,6 +415,9 @@ class NavigationScreen( .build() } + /** + * Creates an action to close the current view or template. + */ private fun closeAction(): Action { return Action.Builder() .setIcon( @@ -357,6 +437,9 @@ class NavigationScreen( .build() } + /** + * Creates an action to start the search screen. + */ private fun searchAction(): Action { return Action.Builder() .setIcon(routeModel.createCarIcon(carContext, R.drawable.search_48px)) @@ -366,6 +449,9 @@ class NavigationScreen( .build() } + /** + * Creates an action to start the settings screen. + */ private fun settingsAction(): Action { return Action.Builder() .setIcon(routeModel.createCarIcon(carContext, R.drawable.settings_48px)) @@ -375,6 +461,9 @@ class NavigationScreen( .build() } + /** + * Creates an action to zoom in on the map. + */ private fun zoomPlus(): Action { return Action.Builder() .setIcon( @@ -392,6 +481,9 @@ class NavigationScreen( .build() } + /** + * Creates an action to zoom out on the map. + */ private fun zoomMinus(): Action { return Action.Builder() .setIcon( @@ -409,6 +501,9 @@ class NavigationScreen( .build() } + /** + * Creates an action to enable map panning. + */ private fun panAction(): Action { return Action.Builder() .setIcon( @@ -426,6 +521,9 @@ class NavigationScreen( .build() } + /** + * Pushes the search screen and handles the search result. + */ private fun startSearchScreen() { screenManager .pushForResult( @@ -450,6 +548,9 @@ class NavigationScreen( } } + /** + * Loads a route to the specified place and sets it as the destination. + */ fun navigateToPlace(place: Place) { navigationType = NavigationType.VIEW val location = location(place.longitude, place.latitude) @@ -465,6 +566,9 @@ class NavigationScreen( invalidate() } + /** + * Stops navigation, resets state, and notifies listeners. + */ fun stopNavigation() { navigationType = NavigationType.VIEW listener.stopNavigation() @@ -473,6 +577,9 @@ class NavigationScreen( invalidate() } + /** + * Initiates recalculation for a new route to the destination. + */ fun calculateNewRoute(destination: Place) { stopNavigation() navigationType = NavigationType.REROUTE @@ -489,6 +596,9 @@ class NavigationScreen( } } + /** + * Re-requests a route for the specified destination. + */ fun reRoute(destination: Place) { val dest = location(destination.longitude, destination.latitude) navigationViewModel.loadRoute( @@ -499,6 +609,9 @@ class NavigationScreen( ) } + /** + * Updates navigation state with the current location, checks for arrival, and traffic updates. + */ fun updateTrip(location: Location) { val current = LocalDateTime.now(ZoneOffset.UTC) val duration = Duration.between(current, lastTrafficDate) @@ -526,6 +639,24 @@ class NavigationScreen( invalidate() } + /** + * Updates the trip information and notifies the listener with a new Trip object. + * This includes destination name, address, travel estimate, and loading status. + */ + private fun updateTrip() { + val tripBuilder = Trip.Builder() + val destination = Destination.Builder() + .setName(routeModel.navState.destination.name ?: "") + .setAddress(routeModel.navState.destination.street ?: "") + .build() + tripBuilder.addDestination(destination, routeModel.travelEstimate(carContext, distanceMode)) + tripBuilder.setLoading(false) + listener.updateTrip(tripBuilder.build()) + } + + /** + * Periodically requests speed camera information near the current location. + */ private fun updateSpeedCamera(location: Location) { if (lastCameraSearch++ % 100 == 0) { navigationViewModel.getSpeedCameras(location, 5.0) @@ -535,6 +666,9 @@ class NavigationScreen( } } + /** + * Updates distances to nearby speed cameras and checks for proximity alerts. + */ private fun updateDistance( location: Location, ) { @@ -565,6 +699,9 @@ class NavigationScreen( } } + /** + * Checks for a specific permission and updates the view model upon grant. + */ fun checkPermission(permission: String) { if (carContext.checkSelfPermission(permission) != PackageManager.PERMISSION_GRANTED) { val permissions: MutableList = ArrayList() @@ -588,6 +725,9 @@ class NavigationScreen( } } +/** + * Defines the possible states for the navigation UI. + */ enum class NavigationType { VIEW, NAVIGATION, REROUTE, RECENT, ARRIVAL -} \ No newline at end of file +} 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 ec61a09..fceb4da 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 @@ -17,6 +17,7 @@ import androidx.car.app.model.Row import androidx.car.app.model.Template import androidx.core.graphics.drawable.IconCompat import androidx.lifecycle.Observer +import androidx.lifecycle.asLiveData import com.kouros.data.R import com.kouros.navigation.car.SurfaceRenderer import com.kouros.navigation.car.navigation.RouteCarModel @@ -25,6 +26,7 @@ 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.NavigationViewModel +import com.kouros.navigation.utils.getSettingsRepository class PlaceListScreen( diff --git a/common/car/src/main/java/com/kouros/navigation/car/screen/observers/MaxSpeedObserver.kt b/common/car/src/main/java/com/kouros/navigation/car/screen/observers/MaxSpeedObserver.kt new file mode 100644 index 0000000..3421491 --- /dev/null +++ b/common/car/src/main/java/com/kouros/navigation/car/screen/observers/MaxSpeedObserver.kt @@ -0,0 +1,15 @@ +package com.kouros.navigation.car.screen.observers + +import androidx.lifecycle.Observer + +/** + * Observer for max speed updates. + */ +class MaxSpeedObserver( + private val callback: NavigationObserverCallback +) : Observer { + + override fun onChanged(value: Int) { + callback.onMaxSpeedReceived(value) + } +} diff --git a/common/car/src/main/java/com/kouros/navigation/car/screen/observers/NavigationObserverCallback.kt b/common/car/src/main/java/com/kouros/navigation/car/screen/observers/NavigationObserverCallback.kt new file mode 100644 index 0000000..696bb09 --- /dev/null +++ b/common/car/src/main/java/com/kouros/navigation/car/screen/observers/NavigationObserverCallback.kt @@ -0,0 +1,34 @@ +package com.kouros.navigation.car.screen.observers + +import com.kouros.navigation.data.Place +import com.kouros.navigation.data.nominatim.SearchResult +import com.kouros.navigation.data.overpass.Elements + +/** + * Callback interface for observer events that need to be handled by NavigationScreen. + */ +interface NavigationObserverCallback { + /** Called when a route is received and navigation should start */ + fun onRouteReceived(route: String) + + /** Called when a recent place is selected but navigation hasn't started */ + fun onRecentPlaceReceived(place: Place) + + /** Check if currently navigating */ + fun isNavigating(): Boolean + + /** Called when traffic data is updated */ + fun onTrafficReceived(traffic: Map) + + /** Called when a place search result is received */ + fun onPlaceSearchResultReceived(place: Place) + + /** Called when speed cameras are updated */ + fun onSpeedCamerasReceived(cameras: List) + + /** Called when max speed is updated */ + fun onMaxSpeedReceived(speed: Int) + + /** Called to request UI invalidation/refresh */ + fun invalidateScreen() +} diff --git a/common/car/src/main/java/com/kouros/navigation/car/screen/observers/NavigationObserverManager.kt b/common/car/src/main/java/com/kouros/navigation/car/screen/observers/NavigationObserverManager.kt new file mode 100644 index 0000000..f9da4f0 --- /dev/null +++ b/common/car/src/main/java/com/kouros/navigation/car/screen/observers/NavigationObserverManager.kt @@ -0,0 +1,42 @@ +package com.kouros.navigation.car.screen.observers + +import com.kouros.navigation.model.NavigationViewModel + +/** + * Manager class that handles all NavigationScreen observers. + * Centralizes observer creation, registration, and lifecycle management. + */ +class NavigationObserverManager( + private val viewModel: NavigationViewModel, + callback: NavigationObserverCallback +) { + + val routeObserver = RouteObserver(callback) + val recentPlaceObserver = RecentPlaceObserver(callback) + val trafficObserver = TrafficObserver(callback) + val placeSearchObserver = PlaceSearchObserver(callback) + val speedCameraObserver = SpeedCameraObserver(callback) + val maxSpeedObserver = MaxSpeedObserver(callback) + + /** + * Attaches all observers to the ViewModel. + * Call this from NavigationScreen's init block or lifecycle method. + */ + fun attachAllObservers(screen: androidx.car.app.Screen) { + viewModel.route.observe(screen, routeObserver) + viewModel.traffic.observe(screen, trafficObserver) + viewModel.recentPlace.observe(screen, recentPlaceObserver) + viewModel.placeLocation.observe(screen, placeSearchObserver) + viewModel.speedCameras.observe(screen, speedCameraObserver) + viewModel.maxSpeed.observe(screen, maxSpeedObserver) + } + + /** + * Detaches all observers from the ViewModel. + * Call this when the screen is being destroyed. + */ + fun detachAllObservers() { + // LiveData observers are automatically removed when the lifecycle is destroyed, + // but we can manually remove them if needed for testing or cleanup + } +} diff --git a/common/car/src/main/java/com/kouros/navigation/car/screen/observers/PlaceSearchObserver.kt b/common/car/src/main/java/com/kouros/navigation/car/screen/observers/PlaceSearchObserver.kt new file mode 100644 index 0000000..47e6dd7 --- /dev/null +++ b/common/car/src/main/java/com/kouros/navigation/car/screen/observers/PlaceSearchObserver.kt @@ -0,0 +1,27 @@ +package com.kouros.navigation.car.screen.observers + +import androidx.lifecycle.Observer +import com.kouros.navigation.data.Constants +import com.kouros.navigation.data.Place +import com.kouros.navigation.data.nominatim.SearchResult + +/** + * Observer for place search results. Converts SearchResult to Place and navigates to it. + */ +class PlaceSearchObserver( + private val callback: NavigationObserverCallback +) : Observer { + + override fun onChanged(value: SearchResult) { + val place = Place( + name = value.displayName, + street = value.address.road, + city = value.address.city, + latitude = value.lat.toDouble(), + longitude = value.lon.toDouble(), + category = Constants.CONTACTS, + postalCode = value.address.postcode + ) + callback.onPlaceSearchResultReceived(place) + } +} diff --git a/common/car/src/main/java/com/kouros/navigation/car/screen/observers/RecentPlaceObserver.kt b/common/car/src/main/java/com/kouros/navigation/car/screen/observers/RecentPlaceObserver.kt new file mode 100644 index 0000000..ef92504 --- /dev/null +++ b/common/car/src/main/java/com/kouros/navigation/car/screen/observers/RecentPlaceObserver.kt @@ -0,0 +1,18 @@ +package com.kouros.navigation.car.screen.observers + +import androidx.lifecycle.Observer +import com.kouros.navigation.data.Place + +/** + * Observer for recent place updates. Updates the recent place when navigation is not active. + */ +class RecentPlaceObserver( + private val callback: NavigationObserverCallback +) : Observer { + + override fun onChanged(value: Place) { + if (!callback.isNavigating()) { + callback.onRecentPlaceReceived(value) + } + } +} diff --git a/common/car/src/main/java/com/kouros/navigation/car/screen/observers/RouteObserver.kt b/common/car/src/main/java/com/kouros/navigation/car/screen/observers/RouteObserver.kt new file mode 100644 index 0000000..c83143e --- /dev/null +++ b/common/car/src/main/java/com/kouros/navigation/car/screen/observers/RouteObserver.kt @@ -0,0 +1,17 @@ +package com.kouros.navigation.car.screen.observers + +import androidx.lifecycle.Observer + +/** + * Observer for route updates. Triggers navigation start when a non-empty route is received. + */ +class RouteObserver( + private val callback: NavigationObserverCallback +) : Observer { + + override fun onChanged(value: String) { + if (value.isNotEmpty()) { + callback.onRouteReceived(value) + } + } +} diff --git a/common/car/src/main/java/com/kouros/navigation/car/screen/observers/SpeedCameraObserver.kt b/common/car/src/main/java/com/kouros/navigation/car/screen/observers/SpeedCameraObserver.kt new file mode 100644 index 0000000..df32c34 --- /dev/null +++ b/common/car/src/main/java/com/kouros/navigation/car/screen/observers/SpeedCameraObserver.kt @@ -0,0 +1,16 @@ +package com.kouros.navigation.car.screen.observers + +import androidx.lifecycle.Observer +import com.kouros.navigation.data.overpass.Elements + +/** + * Observer for speed camera updates. + */ +class SpeedCameraObserver( + private val callback: NavigationObserverCallback +) : Observer> { + + override fun onChanged(value: List) { + callback.onSpeedCamerasReceived(value) + } +} diff --git a/common/car/src/main/java/com/kouros/navigation/car/screen/observers/TrafficObserver.kt b/common/car/src/main/java/com/kouros/navigation/car/screen/observers/TrafficObserver.kt new file mode 100644 index 0000000..b27f21d --- /dev/null +++ b/common/car/src/main/java/com/kouros/navigation/car/screen/observers/TrafficObserver.kt @@ -0,0 +1,16 @@ +package com.kouros.navigation.car.screen.observers + +import androidx.lifecycle.Observer + +/** + * Observer for traffic data updates. + */ +class TrafficObserver( + private val callback: NavigationObserverCallback +) : Observer> { + + override fun onChanged(value: Map) { + callback.onTrafficReceived(value) + callback.invalidateScreen() + } +} diff --git a/common/car/src/test/java/com/kouros/navigation/car/screen/observers/ObserversTest.kt b/common/car/src/test/java/com/kouros/navigation/car/screen/observers/ObserversTest.kt new file mode 100644 index 0000000..9e0d7e7 --- /dev/null +++ b/common/car/src/test/java/com/kouros/navigation/car/screen/observers/ObserversTest.kt @@ -0,0 +1,153 @@ +package com.kouros.navigation.car.screen.observers + +import com.kouros.navigation.data.Constants +import com.kouros.navigation.data.Place +import com.kouros.navigation.data.nominatim.SearchResult +import com.kouros.navigation.data.nominatim.Address +import com.kouros.navigation.data.overpass.Elements +import com.kouros.navigation.data.overpass.Tags +import org.junit.Before +import org.junit.Test +import org.mockito.kotlin.* + +/** + * Unit tests for NavigationScreen observer classes using Mockito. + */ +class ObserversTest { + + private lateinit var mockCallback: NavigationObserverCallback + + @Before + fun setup() { + mockCallback = mock() + } + + @Test + fun `RouteObserver triggers callback when route is not empty`() { + val observer = RouteObserver(mockCallback) + val testRoute = "test_route_data" + + observer.onChanged(testRoute) + + verify(mockCallback).onRouteReceived(testRoute) + } + + @Test + fun `RouteObserver does not trigger callback when route is empty`() { + val observer = RouteObserver(mockCallback) + + observer.onChanged("") + + verify(mockCallback, never()).onRouteReceived(any()) + } + + @Test + fun `RecentPlaceObserver triggers callback when navigating is false`() { + val observer = RecentPlaceObserver(mockCallback) + val testPlace = createTestPlace() + whenever(mockCallback.isNavigating()).thenReturn(false) + + observer.onChanged(testPlace) + + verify(mockCallback).isNavigating() + verify(mockCallback).onRecentPlaceReceived(testPlace) + } + + @Test + fun `RecentPlaceObserver does not trigger callback when navigating is true`() { + val observer = RecentPlaceObserver(mockCallback) + val testPlace = createTestPlace() + whenever(mockCallback.isNavigating()).thenReturn(true) + + observer.onChanged(testPlace) + + verify(mockCallback).isNavigating() + verify(mockCallback, never()).onRecentPlaceReceived(any()) + } + + @Test + fun `TrafficObserver triggers callback with traffic data and invalidates screen`() { + val observer = TrafficObserver(mockCallback) + val trafficData = mapOf("road1" to "congestion", "road2" to "clear") + + observer.onChanged(trafficData) + + verify(mockCallback).onTrafficReceived(trafficData) + verify(mockCallback).invalidateScreen() + } + + @Test + fun `PlaceSearchObserver converts SearchResult to Place and triggers callback`() { + val observer = PlaceSearchObserver(mockCallback) + val searchResult = createTestSearchResult() + + observer.onChanged(searchResult) + + argumentCaptor().apply { + verify(mockCallback).onPlaceSearchResultReceived(capture()) + assert(firstValue.name == "Test Place") + assert(firstValue.street == "Test Street") + assert(firstValue.city == "Test City") + assert(firstValue.latitude == 52.0) + assert(firstValue.longitude == 10.0) + assert(firstValue.category == Constants.CONTACTS) + assert(firstValue.postalCode == "12345") + } + } + + @Test + fun `SpeedCameraObserver triggers callback with camera list`() { + val observer = SpeedCameraObserver(mockCallback) + val cameras = listOf( + createTestElements(10.0, 52.0, "50"), + createTestElements(10.1, 52.1, "30") + ) + + observer.onChanged(cameras) + + verify(mockCallback).onSpeedCamerasReceived(cameras) + } + + @Test + fun `MaxSpeedObserver triggers callback with speed value`() { + val observer = MaxSpeedObserver(mockCallback) + val testSpeed = 50 + + observer.onChanged(testSpeed) + + verify(mockCallback).onMaxSpeedReceived(testSpeed) + } + + // Helper methods + private fun createTestPlace(): Place { + return Place( + name = "Test Place", + street = "Test Street", + city = "Test City", + latitude = 52.0, + longitude = 10.0, + category = Constants.FAVORITES + ) + } + + private fun createTestSearchResult(): SearchResult { + return SearchResult( + displayName = "Test Place", + lat = "52.0", + lon = "10.0", + address = Address( + road = "Test Street", + city = "Test City", + postcode = "12345" + ) + ) + } + + private fun createTestElements(lon: Double, lat: Double, maxSpeed: String): Elements { + return Elements( + lon = lon, + lat = lat, + tags = Tags(maxspeed = maxSpeed, direction = null) + ) + } +} diff --git a/common/data/build.gradle.kts b/common/data/build.gradle.kts index 173f769..c377cf9 100644 --- a/common/data/build.gradle.kts +++ b/common/data/build.gradle.kts @@ -69,6 +69,4 @@ dependencies { androidTestImplementation(libs.androidx.espresso.core) androidTestImplementation(libs.androidx.runner) androidTestImplementation(libs.androidx.rules) - - } diff --git a/common/data/proguard-rules.pro b/common/data/proguard-rules.pro index 481bb43..cf50408 100644 --- a/common/data/proguard-rules.pro +++ b/common/data/proguard-rules.pro @@ -18,4 +18,5 @@ # If you keep the line number information, uncomment this to # hide the original source file name. -#-renamesourcefileattribute SourceFile \ No newline at end of file +#-renamesourcefileattribute SourceFile + 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 cc2ed68..28b3251 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 @@ -20,4 +20,4 @@ data class NavigationState ( val carConnection: 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/datastore/DataStoreManager.kt b/common/data/src/main/java/com/kouros/navigation/data/datastore/DataStoreManager.kt index a904885..a5ca883 100644 --- 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 @@ -21,11 +21,11 @@ private const val DATASTORE_NAME = "navigation_settings" class DataStoreManager(private val context: Context) { companion object { - private val Context.dataStore: DataStore by preferencesDataStore(name = DATASTORE_NAME) + val Context.dataStore: DataStore by preferencesDataStore(name = DATASTORE_NAME) } // Keys - private object PreferencesKeys { + object PreferencesKeys { val SHOW_3D = booleanPreferencesKey("Show3D") @@ -45,7 +45,7 @@ class DataStoreManager(private val context: Context) { val RECENT_PLACES = stringPreferencesKey("RecentPlaces") - val FAVORITES = stringPreferencesKey("Favorites") + val DISTANCE_MODE = intPreferencesKey("DistanceMode") } @@ -79,7 +79,7 @@ class DataStoreManager(private val context: Context) { val routingEngineFlow: Flow = context.dataStore.data.map { preferences -> preferences[PreferencesKeys.ROUTING_ENGINE] - ?: 0 + ?: 2 } val lastRouteFlow: Flow = @@ -100,10 +100,11 @@ class DataStoreManager(private val context: Context) { ?: "" } - val favoritesFlow: Flow = + + val distanceModeFlow: Flow = context.dataStore.data.map { preferences -> - preferences[PreferencesKeys.FAVORITES] - ?: "" + preferences[PreferencesKeys.DISTANCE_MODE] + ?: 0 } // Save values @@ -161,9 +162,10 @@ class DataStoreManager(private val context: Context) { } } - suspend fun setFavorites(apiKey: String) { + suspend fun setDistanceMode(mode: Int) { context.dataStore.edit { prefs -> - prefs[PreferencesKeys.FAVORITES] = apiKey + prefs[PreferencesKeys.DISTANCE_MODE] = mode } } + } diff --git a/common/data/src/main/java/com/kouros/navigation/data/route/Summary.kt b/common/data/src/main/java/com/kouros/navigation/data/route/Summary.kt index 31cee4c..b2adc66 100644 --- a/common/data/src/main/java/com/kouros/navigation/data/route/Summary.kt +++ b/common/data/src/main/java/com/kouros/navigation/data/route/Summary.kt @@ -2,7 +2,11 @@ package com.kouros.navigation.data.route data class Summary( // sec - var duration : Double = 0.0, - // km - var distance : Double = 0.0, + var duration: Double = 0.0, + // m + var distance: Double = 0.0, + // sec + var trafficDelay: Double = 0.0, + // m + var trafficLength: Double = 0.0, ) \ 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 5d6dc3e..09a401a 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 @@ -25,7 +25,9 @@ class TomTomRoute { var points = listOf>() val summary = Summary( route.summary.travelTimeInSeconds.toDouble(), - route.summary.lengthInMeters.toDouble() + route.summary.lengthInMeters.toDouble(), + route.summary.trafficDelayInSeconds.toDouble(), + route.summary.trafficLengthInMeters.toDouble() ) route.legs.forEach { leg -> points = decodePolyline(leg.encodedPolyline, leg.encodedPolylinePrecision) diff --git a/common/data/src/main/java/com/kouros/navigation/model/NavigationViewModel.kt b/common/data/src/main/java/com/kouros/navigation/model/NavigationViewModel.kt index ca53ba8..7a2a4bb 100644 --- a/common/data/src/main/java/com/kouros/navigation/model/NavigationViewModel.kt +++ b/common/data/src/main/java/com/kouros/navigation/model/NavigationViewModel.kt @@ -3,6 +3,7 @@ package com.kouros.navigation.model //import com.kouros.navigation.data.Preferences.boxStore import android.content.Context import android.location.Location +import android.util.Log import androidx.compose.runtime.snapshots.SnapshotStateList import androidx.compose.runtime.toMutableStateList import androidx.compose.ui.platform.isDebugInspectorInfoEnabled 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 35cd7a7..e8b595e 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 @@ -69,7 +69,7 @@ class RouteCalculator(var routeModel: RouteModel) { distance } } - return (leftDistance / 10.0).roundToInt() * 10.0 + return leftDistance.toDouble() } /** Returns the left distance in m. */ 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 2c99782..e852f6a 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 @@ -1,7 +1,10 @@ package com.kouros.navigation.model +import androidx.datastore.preferences.core.edit import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope +import com.kouros.navigation.data.datastore.DataStoreManager.Companion.dataStore +import com.kouros.navigation.data.datastore.DataStoreManager.PreferencesKeys import com.kouros.navigation.repository.SettingsRepository import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.stateIn @@ -63,6 +66,12 @@ class SettingsViewModel(private val repository: SettingsRepository) : ViewModel( "" ) + val distanceMode = repository.distanceModeFlow.stateIn( + viewModelScope, + SharingStarted.WhileSubscribed(5_000), + 0 + ) + fun onShow3DChanged(enabled: Boolean) { viewModelScope.launch { repository.setShow3D(enabled) } } @@ -94,4 +103,9 @@ class SettingsViewModel(private val repository: SettingsRepository) : ViewModel( fun onTomTomApiKeyChanged(key: String) { viewModelScope.launch { repository.setTomTomApiKey(key) } } + + fun onDistanceModeChanged(mode: Int) { + viewModelScope.launch { repository.setDistanceMode(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 index 815f2fb..183d67b 100644 --- a/common/data/src/main/java/com/kouros/navigation/repository/SettingsRepository.kt +++ b/common/data/src/main/java/com/kouros/navigation/repository/SettingsRepository.kt @@ -33,8 +33,8 @@ class SettingsRepository( val recentPlacesFlow: Flow = dataStoreManager.recentPlacesFlow - val favoritesFlow: Flow = - dataStoreManager.favoritesFlow + val distanceModeFlow: Flow = + dataStoreManager.distanceModeFlow suspend fun setShow3D(enabled: Boolean) { @@ -73,7 +73,9 @@ class SettingsRepository( dataStoreManager.setRecentPlaces(places) } - suspend fun setFavorites(favorites: String) { - dataStoreManager.setFavorites(favorites) + suspend fun setDistanceMode(mode: Int) { + dataStoreManager.setDistanceMode(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 e7e3fc7..6b1a763 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,6 +3,7 @@ package com.kouros.navigation.utils import android.content.Context import android.location.Location import android.location.LocationManager +import androidx.car.app.model.Distance import com.kouros.navigation.data.RouteEngine import com.kouros.navigation.data.osrm.OsrmRepository import com.kouros.navigation.data.tomtom.TomTomRepository @@ -16,6 +17,7 @@ import java.time.ZoneOffset import java.time.ZonedDateTime import java.time.format.DateTimeFormatter import java.time.format.FormatStyle +import java.util.Locale import kotlin.math.absoluteValue import kotlin.math.pow import kotlin.math.roundToInt @@ -109,4 +111,34 @@ fun duration(preview: Boolean, bearing: Double, lastBearing: Double): Duration { 1.seconds } return cameraDuration -} \ No newline at end of file +} + +fun isMetricSystem(): Boolean { + val country = Locale.getDefault().country + // Return true for metric, false for imperial + return !setOf("US", "UK", "LR", "MM").contains(country) +} + +fun formattedDistance(distanceMode : Int, distance: Double): Pair { + var currentDistance = distance + var displayUnit: Int + if (distanceMode == 1 || distanceMode == 0 && isMetricSystem()) { + displayUnit = if (currentDistance > 1000.0) { + currentDistance /= 1000.0 + Distance.UNIT_KILOMETERS + } else { + currentDistance = (currentDistance / 10.0).roundToInt() * 10.0 + Distance.UNIT_METERS + } + } else { + currentDistance *= 0.621371 + displayUnit = if (currentDistance > 1000.0) { + currentDistance /= 1000.0 + Distance.UNIT_MILES + } else { + currentDistance = (currentDistance / 10.0).roundToInt() * 10.0 + Distance.UNIT_FEET + } + } + return Pair(currentDistance, displayUnit) +} 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 index 32531d2..72077e4 100644 --- a/common/data/src/main/java/com/kouros/navigation/utils/Settings.kt +++ b/common/data/src/main/java/com/kouros/navigation/utils/Settings.kt @@ -2,6 +2,7 @@ package com.kouros.navigation.utils import android.content.Context import androidx.car.app.CarContext +import androidx.car.app.model.Distance import androidx.compose.runtime.Composable import androidx.compose.runtime.remember import androidx.lifecycle.ViewModel @@ -13,6 +14,9 @@ import com.kouros.navigation.model.SettingsViewModel import com.kouros.navigation.repository.SettingsRepository import kotlinx.coroutines.flow.first import kotlinx.coroutines.runBlocking +import java.text.NumberFormat +import java.util.Locale +import kotlin.math.roundToInt @Composable diff --git a/common/data/src/main/res/drawable/empty.png b/common/data/src/main/res/drawable/empty.png index bcef144e11b6e13bef257ab5d4f24d47af808126..3b3aa860eb92a4dada4848d31e14ea593ed91e1f 100644 GIT binary patch delta 762 zcmVYm+4-5qS^f%e1~5+z z3Ury9Mv$gEu<+rPPZS1_Q|vc)XcYYFyxSPqq6i-GN20S*b!!@qT7#dj{2U3X+!KX^ zu~&vA9z-Ih=tw$#Mn`HTUq)M=aFsHSR&XYr6tlP%f}GXdE(DT&pUd8;l-pE4_vBOIw9=ncuq^JNhC2jFmnyZ*_et zl5MO200APC9|0VHq@p4f1;s%eGPrg$lY>s0#Jh1C8#cQ^ znoOe~Pjj30D7!c}ebo*gC!FHrFl5Svgo=(dQ|B0$J*h`Q$UP;IG-0IPOfwZXqEb#C zhoZhnw`s`RuJe4Q4COJAmr1xIm8Z$W{O|YL$638;ZO!wr2xQi5-*0Oam~rcwf4=bxv`bJ*22rpr000hKvrGbt0wiKNWn*PEVmU1^Ghs9>G&W&k zEn;CaWGyvjWnp1tVKq2oH8Yd?1gQ%)H#s>uH8(IaFfx;_1#<{7H##vhIx#tuDF!A8 zF*iCfGdeLjvsVTY0h5IWTqI>SV>vT7V`VKiGB-6XG-fbiEn#FgIV~|_WH2>3VP-Kg zVK9>g2TceuH##vhIx#u3S_f_j1X4$RGn32@F9#3}6T6hW{F4F@C^H^OL_t(I%VS^| s1mHgt!zdU9WF{Ww(J&YVqhQzo0Q1EH1igE?>i_@%07*qoM6N<$f@Q`u?EnA( delta 773 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 literal 0 HcmV?d00001 diff --git a/common/data/src/main/res/values-de/strings.xml b/common/data/src/main/res/values-de/strings.xml index 9fe0ed1..4f2c0d8 100644 --- a/common/data/src/main/res/values-de/strings.xml +++ b/common/data/src/main/res/values-de/strings.xml @@ -36,8 +36,8 @@ Verwende Telefon Einstellungen 3D Gebäude Losfahren - "Mautstraßen meiden" - "Autobahnen meiden" + "Mautstraßen vermeiden" + "Autobahnen vermeiden" Letzte Ziele Kontakte Route Vorschau @@ -54,5 +54,8 @@ Verwende Auto Einstellungen Ausfahrt nummer Navigations Icon - + Entfernungseinheiten + Automatisch + Kilometer + Meilen diff --git a/common/data/src/main/res/values/strings.xml b/common/data/src/main/res/values/strings.xml index 4a1ffb0..9ff5463 100644 --- a/common/data/src/main/res/values/strings.xml +++ b/common/data/src/main/res/values/strings.xml @@ -18,7 +18,7 @@ Avoid highways Avoid tolls rows No places - Recent destination + Recent destinations Contacts Favorites Recent item deleted @@ -40,4 +40,8 @@ Use car settings Exit number Navigation icon + Distance units + Automaticaly + Kilometer + Miles \ No newline at end of file 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 index f7159e6..0528788 100644 --- a/common/data/src/test/java/com/kouros/navigation/model/IconMapperTest.kt +++ b/common/data/src/test/java/com/kouros/navigation/model/IconMapperTest.kt @@ -35,33 +35,33 @@ class IconMapperTest { @Test fun `addLanes returns correct lane direction`() { - val stepDataNormalLeft = StepData("", 0.0, Maneuver.TYPE_TURN_NORMAL_LEFT, 0, 0L, 0.0) + 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) + 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) + 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) + 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) + 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) + 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) + 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)) @@ -74,24 +74,24 @@ class IconMapperTest { @Test fun `laneToResource returns correct resource string`() { - val stepDataNormalLeft = StepData("", 0.0, Maneuver.TYPE_TURN_NORMAL_LEFT, 0, 0L, 0.0) + 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) + 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) + 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) + 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) + 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)) } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 8dec5b3..4de3d16 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -94,7 +94,6 @@ androidx-monitor = { group = "androidx.test", name = "monitor", version.ref = "m [plugins] android-application = { id = "com.android.application", version.ref = "agp" } -kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" } kotlin-compose = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" } android-library = { id = "com.android.library", version.ref = "agp" } kotlin-kapt = { id = "com.android.legacy-kapt", version.ref = "agp" }