From 9e453dc955498bee2d7ebf7ec5ebe6651a2543fd Mon Sep 17 00:00:00 2001 From: Dimitris Date: Fri, 19 Dec 2025 15:03:32 +0100 Subject: [PATCH] Speed radar --- app/build.gradle.kts | 4 +- app/src/main/AndroidManifest.xml | 2 + .../com/kouros/navigation/ui/MainActivity.kt | 27 ++- automotive/build.gradle.kts | 8 +- .../navigation/car/NavigationSession.kt | 1 + .../kouros/navigation/car/SurfaceRenderer.kt | 13 +- .../com/kouros/navigation/car/map/MapView.kt | 55 ++++-- .../car/navigation/RouteCarModel.kt | 47 ++++- .../navigation/car/screen/CategoriesScreen.kt | 36 ++-- .../navigation/car/screen/CategoryScreen.kt | 84 ++++---- .../navigation/car/screen/NavigationScreen.kt | 179 +++++++++++------- .../navigation/car/screen/PlaceListScreen.kt | 112 +++++------ .../car/screen/RoutePreviewScreen.kt | 69 +++---- .../navigation/car/screen/SearchScreen.kt | 2 +- .../navigation/car/screen/SettingsScreen.kt | 1 - .../com/kouros/navigation/car/UnitTest.kt | 2 + .../navigation/data/overpass/Elements.kt | 3 +- .../navigation/data/overpass/Overpass.kt | 17 +- .../kouros/navigation/data/overpass/Tags.kt | 32 ++-- .../com/kouros/navigation/model/Contacts.kt | 2 +- .../com/kouros/navigation/model/RouteModel.kt | 43 +---- .../com/kouros/navigation/model/ViewModel.kt | 153 +++++++++------ .../com/kouros/navigation/utils/GeoUtils.kt | 6 +- .../navigation/utils/NavigationUtils.kt | 59 ++---- .../src/main/res/drawable/ev_station_24px.xml | 0 .../src/main/res/drawable/ev_station_48px.xml | 10 + .../res/drawable/local_gas_station_24px.xml | 10 + .../res/drawable/local_gas_station_48px.xml | 10 + .../main/res/drawable/local_pharmacy_24px.xml | 10 + .../main/res/drawable/local_pharmacy_48px.xml | 10 + gradle/libs.versions.toml | 2 +- 31 files changed, 597 insertions(+), 412 deletions(-) rename common/{car => data}/src/main/res/drawable/ev_station_24px.xml (100%) create mode 100644 common/data/src/main/res/drawable/ev_station_48px.xml create mode 100644 common/data/src/main/res/drawable/local_gas_station_24px.xml create mode 100644 common/data/src/main/res/drawable/local_gas_station_48px.xml create mode 100644 common/data/src/main/res/drawable/local_pharmacy_24px.xml create mode 100644 common/data/src/main/res/drawable/local_pharmacy_48px.xml diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 78b8d7d..e13da31 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -14,8 +14,8 @@ android { applicationId = "com.kouros.navigation" minSdk = 33 targetSdk = 36 - versionCode = 10 - versionName = "0.1.3.10" + versionCode = 11 + versionName = "0.1.3.11" base.archivesName = "navi-$versionName" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" } diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index ed14074..527c9da 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -6,6 +6,7 @@ + @@ -18,6 +19,7 @@ android:label="@string/app_name" android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" + android:enableOnBackInvokedCallback="true" android:theme="@style/Theme.Navigation"> 300) { + routeModel.updateLocation(location(loc[0], loc[1])) + routeModel.currentStep() + if (routeModel.route.currentManeuverIndex + 1 <= routeModel.route.maneuvers.size) { + nextStepData.value = routeModel.nextStep() + } + println(routeModel.routeState.maneuverType) + } + } + } } \ No newline at end of file diff --git a/automotive/build.gradle.kts b/automotive/build.gradle.kts index fbe1331..a879b46 100644 --- a/automotive/build.gradle.kts +++ b/automotive/build.gradle.kts @@ -1,3 +1,5 @@ +import org.jetbrains.kotlin.gradle.dsl.JvmTarget + plugins { alias(libs.plugins.android.application) alias(libs.plugins.kotlin.android) @@ -32,8 +34,10 @@ android { sourceCompatibility = JavaVersion.VERSION_11 targetCompatibility = JavaVersion.VERSION_11 } - kotlinOptions { - jvmTarget = "11" + kotlin { + compilerOptions { + jvmTarget = JvmTarget.JVM_11 + } } } 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 888f50b..6f413f0 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 @@ -180,6 +180,7 @@ class NavigationSession : Session(), NavigationScreen.Listener { routeModel.stopNavigation() } + companion object { 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 ff0eeca..5e1ff71 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 @@ -37,7 +37,7 @@ import com.kouros.navigation.data.Constants import com.kouros.navigation.data.ObjectBox import com.kouros.navigation.model.RouteModel import com.kouros.navigation.utils.bearing -import com.kouros.navigation.utils.calcTilt +import com.kouros.navigation.utils.calculateTilt import com.kouros.navigation.utils.calculateZoom import com.kouros.navigation.utils.duration import com.kouros.navigation.utils.location @@ -225,7 +225,7 @@ class SurfaceRenderer( } else { cameraPosition.value!!.zoom + 1.0 } - tilt = calcTilt(newZoom, tilt) + tilt = calculateTilt(newZoom, tilt) updateCameraPosition( cameraPosition.value!!.bearing, newZoom, @@ -310,7 +310,7 @@ class SurfaceRenderer( } fun setCategories(location: Location, route: String) { - viewStyle = ViewStyle.SEARCH_VIEW + viewStyle = ViewStyle.AMENITY_VIEW routeData.value = route updateCameraPosition( 0.0, @@ -319,14 +319,13 @@ class SurfaceRenderer( ) } - fun setCategoryLocation(location: Location) { - viewStyle = ViewStyle.SEARCH_VIEW + fun setCategoryLocation(location: Location, category: String) { + viewStyle = ViewStyle.AMENITY_VIEW cameraPosition.postValue( cameraPosition.value!!.copy( target = Position(location.longitude, location.latitude) ) ) - } companion @@ -337,6 +336,6 @@ class SurfaceRenderer( enum class ViewStyle { - VIEW, PREVIEW, PAN_VIEW, SEARCH_VIEW + VIEW, PREVIEW, PAN_VIEW, AMENITY_VIEW } \ No newline at end of file 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 8f1fd6b..28050ec 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 @@ -33,16 +33,18 @@ import com.kouros.navigation.data.RouteColor import com.kouros.navigation.data.SpeedColor import com.kouros.navigation.utils.NavigationUtils.getBooleanKeyValue import com.kouros.navigation.utils.NavigationUtils.getIntKeyValue +import kotlinx.serialization.json.Json +import kotlinx.serialization.json.JsonObject import org.maplibre.compose.camera.CameraPosition import org.maplibre.compose.camera.CameraState import org.maplibre.compose.camera.rememberCameraState import org.maplibre.compose.expressions.dsl.const +import org.maplibre.compose.expressions.dsl.contains import org.maplibre.compose.expressions.dsl.exponential import org.maplibre.compose.expressions.dsl.image import org.maplibre.compose.expressions.dsl.interpolate import org.maplibre.compose.expressions.dsl.zoom import org.maplibre.compose.layers.Anchor -import org.maplibre.compose.layers.CircleLayer import org.maplibre.compose.layers.FillLayer import org.maplibre.compose.layers.LineLayer import org.maplibre.compose.layers.SymbolLayer @@ -58,9 +60,15 @@ import org.maplibre.compose.sources.Source import org.maplibre.compose.sources.getBaseSource import org.maplibre.compose.sources.rememberGeoJsonSource import org.maplibre.compose.style.BaseStyle +import org.maplibre.geojson.FeatureCollection +import org.maplibre.spatialk.geojson.BoundingBox.Companion.serializer import org.maplibre.spatialk.geojson.Feature -import org.maplibre.spatialk.geojson.FeatureCollection +import org.maplibre.spatialk.geojson.GeoJson +import org.maplibre.spatialk.geojson.Geometry import org.maplibre.spatialk.geojson.Position +import org.maplibre.spatialk.geojson.dsl.FeatureBuilder +import org.maplibre.spatialk.geojson.dsl.FeatureCollectionBuilder +import org.maplibre.spatialk.geojson.dsl.buildFeatureCollection @Composable @@ -103,19 +111,22 @@ fun MapLibre( if (!getBooleanKeyValue(context = context, SHOW_THREED_BUILDING)) { BuildingLayer(tiles) } - RouteLayer(route, viewStyle) + if (viewStyle == ViewStyle.AMENITY_VIEW) { + AmenityLayer(route) + } else { + RouteLayer(route) + } } //Puck(cameraState, lastLocation) } } @Composable -fun RouteLayer(routeData: String?, viewStyle: ViewStyle) { +fun RouteLayer(routeData: String?) { if (routeData != null && routeData.isNotEmpty()) { val routes = rememberGeoJsonSource(GeoJsonData.JsonString(routeData)) - if (viewStyle == ViewStyle.VIEW) { LineLayer( - id = "routes-casing$viewStyle", + id = "routes-casing", source = routes, color = const(Color.White), width = @@ -129,7 +140,7 @@ fun RouteLayer(routeData: String?, viewStyle: ViewStyle) { ), ) LineLayer( - id = "routes$viewStyle", + id = "routes", source = routes, color = const(RouteColor), width = @@ -142,21 +153,27 @@ fun RouteLayer(routeData: String?, viewStyle: ViewStyle) { 20 to const(22.dp), ), ) - } else { - SymbolLayer( - id = "my-symbol-layer", - source = routes, - // Convert a drawable resource to a MapLibre image - // drawAsSdf = true allows us to tint the image programmatically - iconImage = image(painterResource(com.kouros.android.cars.carappservice.R.drawable.ev_station_24px), drawAsSdf = true), - // Now we can apply any color we want! - iconColor = const(Color.Red), - iconSize = const(5.0f) - ) - } } } +@Composable +fun AmenityLayer(routeData: String?) { + if (routeData != null && routeData.isNotEmpty()) { + val color = if (routeData.contains(Constants.PHARMACY)) { + const(Color.Red) + } else { + const(Color.Green) + } + val routes = rememberGeoJsonSource(GeoJsonData.JsonString(routeData)) + SymbolLayer( + id = "amenity-layer", + source = routes, + iconImage = image(painterResource(R.drawable.ev_station_48px), drawAsSdf = true), + iconColor = color, + iconSize = const(3.0f), + ) + } +} @Composable fun BuildingLayer(tiles: Source) { Anchor.Replace("building-3d") { 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 44ad275..4001944 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 @@ -18,7 +18,12 @@ package com.kouros.navigation.car.navigation import android.text.SpannableString import androidx.annotation.DrawableRes import androidx.annotation.StringRes +import androidx.car.app.AppManager import androidx.car.app.CarContext +import androidx.car.app.model.Action +import androidx.car.app.model.Action.FLAG_DEFAULT +import androidx.car.app.model.Alert +import androidx.car.app.model.AlertCallback import androidx.car.app.model.CarColor import androidx.car.app.model.CarIcon import androidx.car.app.model.CarText @@ -28,8 +33,7 @@ import androidx.car.app.navigation.model.Maneuver import androidx.car.app.navigation.model.Step import androidx.car.app.navigation.model.TravelEstimate import androidx.core.graphics.drawable.IconCompat -import com.kouros.navigation.data.Constants.NEXT_STEP_THRESHOLD -import com.kouros.navigation.data.ManeuverType +import com.kouros.data.R import com.kouros.navigation.model.RouteModel import java.util.TimeZone import java.util.concurrent.TimeUnit @@ -117,4 +121,43 @@ class RouteCarModel() : RouteModel() { fun createCarIcon(carContext: CarContext, @DrawableRes iconRes: Int): CarIcon { return CarIcon.Builder(IconCompat.createWithResource(carContext, iconRes)).build() } + + fun showSpeedCamera(carContext: CarContext, distance: Double, maxSpeed: String?) { + carContext.getCarService(AppManager::class.java) + .showAlert(createAlert(carContext, distance, maxSpeed)) + } + + fun createAlert(carContext: CarContext, distance: Double, maxSpeed: String?): Alert { + val title = createCarText(carContext,R.string.speed_camera) + val subtitle = CarText.create(maxSpeed!!) + val icon = CarIcon.ALERT + + val dismissAction: Action = createToastAction( + carContext, + R.string.speed_camera, R.string.exit_action_title, + FLAG_DEFAULT + ) + + return Alert.Builder( /* alertId: */0, title, /* durationMillis: */10000) + .setSubtitle(subtitle) + .setIcon(icon) + .addAction(dismissAction).setCallback(object : AlertCallback { + override fun onCancel(reason: Int) { + } + override fun onDismiss() { + } + }).build() + } + + private fun createToastAction( + carContext: CarContext, + @StringRes titleRes: Int, @StringRes toastStringRes: Int, + flags: Int + ): Action { + return Action.Builder() + .setOnClickListener { } + .setTitle(createCarText(carContext,titleRes)) + .setFlags(flags) + .build() + } } diff --git a/common/car/src/main/java/com/kouros/navigation/car/screen/CategoriesScreen.kt b/common/car/src/main/java/com/kouros/navigation/car/screen/CategoriesScreen.kt index 21b2bc4..28ce1d3 100644 --- a/common/car/src/main/java/com/kouros/navigation/car/screen/CategoriesScreen.kt +++ b/common/car/src/main/java/com/kouros/navigation/car/screen/CategoriesScreen.kt @@ -15,7 +15,9 @@ import com.kouros.data.R import com.kouros.navigation.car.SurfaceRenderer import com.kouros.navigation.car.ViewStyle import com.kouros.navigation.data.Category -import com.kouros.navigation.data.Constants +import com.kouros.navigation.data.Constants.CHARGING_STATION +import com.kouros.navigation.data.Constants.FUEL_STATION +import com.kouros.navigation.data.Constants.PHARMACY class CategoriesScreen( private val carContext: CarContext, @@ -24,9 +26,9 @@ class CategoriesScreen( ) : Screen(carContext) { var categories: List = listOf( - Category(id = Constants.FUEL_STATION, name = carContext.getString(R.string.fuel_station)), - Category(id = Constants.PHARMACY, name = carContext.getString(R.string.pharmacy)), - Category(id = Constants.CHARGING_STATION, name = carContext.getString(R.string.charging_station)) + Category(id = FUEL_STATION, name = carContext.getString(R.string.fuel_station)), + Category(id = PHARMACY, name = carContext.getString(R.string.pharmacy)), + Category(id = CHARGING_STATION, name = carContext.getString(R.string.charging_station)) ) override fun onGetTemplate(): Template { @@ -36,13 +38,7 @@ class CategoriesScreen( itemListBuilder.addItem( Row.Builder() .setTitle(it.name) - .setImage(CarIcon.Builder( - IconCompat.createWithResource( - carContext, - com.kouros.android.cars.carappservice.R.drawable.ev_station_24px - ) - ) - .build()) + .setImage(carIcon(carContext,it.id)) .setOnClickListener { screenManager .pushForResult( @@ -64,7 +60,7 @@ class CategoriesScreen( ) } - surfaceRenderer.viewStyle = ViewStyle.SEARCH_VIEW + surfaceRenderer.viewStyle = ViewStyle.AMENITY_VIEW val header = Header.Builder() .setStartHeaderAction(Action.BACK) @@ -76,4 +72,20 @@ class CategoriesScreen( .setSingleList(itemListBuilder.build()) .build() } +} + +fun carIcon(context: CarContext, id: String): CarIcon { + val resId = when (id) { + FUEL_STATION -> R.drawable.local_gas_station_48px + PHARMACY -> R.drawable.local_pharmacy_48px + CHARGING_STATION -> R.drawable.ev_station_48px + else -> {} + } + return CarIcon.Builder( + IconCompat.createWithResource( + context, + resId as Int + ) + ) + .build() } \ No newline at end of file diff --git a/common/car/src/main/java/com/kouros/navigation/car/screen/CategoryScreen.kt b/common/car/src/main/java/com/kouros/navigation/car/screen/CategoryScreen.kt index 22540ca..4966f08 100644 --- a/common/car/src/main/java/com/kouros/navigation/car/screen/CategoryScreen.kt +++ b/common/car/src/main/java/com/kouros/navigation/car/screen/CategoryScreen.kt @@ -3,12 +3,10 @@ package com.kouros.navigation.car.screen import android.location.Location import androidx.annotation.DrawableRes import androidx.car.app.CarContext -import androidx.car.app.CarToast import androidx.car.app.Screen import androidx.car.app.constraints.ConstraintManager import androidx.car.app.model.Action import androidx.car.app.model.ActionStrip -import androidx.car.app.model.CarIcon import androidx.car.app.model.CarText import androidx.car.app.model.Header import androidx.car.app.model.ItemList @@ -17,24 +15,24 @@ import androidx.car.app.model.Row import androidx.car.app.model.Template import androidx.car.app.navigation.model.MapController import androidx.car.app.navigation.model.MapWithContentTemplate -import androidx.car.app.versioning.CarAppApiLevels -import androidx.core.graphics.drawable.IconCompat import androidx.lifecycle.Observer import com.kouros.data.R import com.kouros.navigation.car.SurfaceRenderer import com.kouros.navigation.car.navigation.NavigationMessage +import com.kouros.navigation.data.Constants import com.kouros.navigation.data.NavigationRepository import com.kouros.navigation.data.overpass.Elements import com.kouros.navigation.model.ViewModel import com.kouros.navigation.utils.GeoUtils.createPointCollection import com.kouros.navigation.utils.location +import com.kouros.navigation.utils.round import kotlin.math.min class CategoryScreen( private val carContext: CarContext, private val surfaceRenderer: SurfaceRenderer, location: Location, - category: String, + private val category: String, ) : Screen(carContext) { val viewModel = ViewModel(NavigationRepository()) @@ -52,7 +50,7 @@ class CategoryScreen( coordinates.add(listOf(it.lon!!, it.lat!!)) } if (elements.isNotEmpty()) { - val route = createPointCollection(coordinates) + val route = createPointCollection(coordinates, category) surfaceRenderer.setCategories(loc, route) invalidate() } @@ -65,41 +63,25 @@ class CategoryScreen( override fun onGetTemplate(): Template { - val listBuilder = ItemList.Builder() - if (carContext.getCarAppApiLevel() > CarAppApiLevels.LEVEL_1) { - var index = 0 - val listLimit = min( - 100, - carContext.getCarService(ConstraintManager::class.java) - .getContentLimit( - ConstraintManager.CONTENT_LIMIT_TYPE_LIST - ) - ) - elements.forEach { - if (index++ < listLimit) { + var index = 0 + val listLimit = min( + 50, + carContext.getCarService(ConstraintManager::class.java) + .getContentLimit( + ConstraintManager.CONTENT_LIMIT_TYPE_LIST + ) + ) + elements.forEach { + if (index++ < listLimit) { + if (it.tags.operator != null) { listBuilder.addItem( - Row.Builder() - .setOnClickListener { - val location = location(it.lon!!, it.lat!!) - surfaceRenderer.setCategoryLocation(location) - } - .setTitle(it.tags.operator.toString()) - .setImage( - CarIcon.Builder( - IconCompat.createWithResource( - carContext, - com.kouros.android.cars.carappservice.R.drawable.ev_station_24px - ) - ) - .build() - ) - .addText(it.tags.network.toString()) - .build() + createItem(it, category) ) } } } + val header = Header.Builder() .setStartHeaderAction(Action.BACK) .setTitle(carContext.getString(R.string.charging_station)) @@ -116,11 +98,39 @@ class CategoryScreen( getMapActionStrip() ).build() ) - return builder.build() } - private fun secondText(sText: String): CarText { + private fun createItem(it: Elements, category: String): Row { + var name = "" + if (it.tags.name != null) { + name = it.tags.name.toString() + } + if (name.isEmpty()) { + name = it.tags.operator.toString() + } + val row = Row.Builder() + .setOnClickListener { + val location = location(it.lon!!, it.lat!!) + surfaceRenderer.setCategoryLocation(location, category) + println(it) + } + .setTitle(name) + .setImage(carIcon(carContext, category)) + if (it.distance < 1000) { + row.addText("${(it.distance).toInt()} m") + } else { + row.addText("${(it.distance / 1000).round(1)} km") + } + if (category == Constants.CHARGING_STATION) { + row.addText("${it.tags.socketType2} X Typ 2 ${it.tags.socketType2Output}") + } else { + row.addText(carText("${it.tags.openingHours}")) + } + return row.build() + } + + private fun carText(sText: String): CarText { val secondText = CarText.Builder( "================= " + sText + " ================" 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 95aa63c..3da4d4f 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,7 +1,5 @@ package com.kouros.navigation.car.screen -import android.content.ComponentName -import android.content.Intent import android.location.Location import android.location.LocationManager import android.os.CountDownTimer @@ -9,6 +7,7 @@ import android.os.Handler import androidx.car.app.CarContext import androidx.car.app.Screen import androidx.car.app.model.Action +import androidx.car.app.model.Action.FLAG_DEFAULT import androidx.car.app.model.ActionStrip import androidx.car.app.model.CarColor import androidx.car.app.model.CarIcon @@ -21,18 +20,18 @@ 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.notification.CarPendingIntent -import androidx.car.app.suggestion.model.Suggestion import androidx.core.graphics.drawable.IconCompat import androidx.lifecycle.Observer import com.kouros.data.R -import com.kouros.navigation.car.NavigationCarAppService import com.kouros.navigation.car.SurfaceRenderer import com.kouros.navigation.car.ViewStyle import com.kouros.navigation.car.navigation.RouteCarModel +import com.kouros.navigation.data.Constants import com.kouros.navigation.data.Constants.DESTINATION_ARRIVAL_DISTANCE import com.kouros.navigation.data.NavigationRepository import com.kouros.navigation.data.Place +import com.kouros.navigation.data.nominatim.SearchResult +import com.kouros.navigation.data.overpass.Elements import com.kouros.navigation.model.ViewModel import com.kouros.navigation.utils.location @@ -41,7 +40,6 @@ class NavigationScreen( private var surfaceRenderer: SurfaceRenderer, private var routeModel: RouteCarModel, private var listener: Listener - ) : Screen(carContext) { @@ -52,20 +50,14 @@ class NavigationScreen( } var currentNavigationLocation = Location(LocationManager.GPS_PROVIDER) - - lateinit var recentPlace: Place - - var recentPlaceFound = false - - var recentPlaceActive = true - - var calculateNewRoute = false + var recentPlace = Place() + var navigationType = NavigationType.VIEW val viewModel = ViewModel(NavigationRepository()) val observer = Observer { route -> if (route.isNotEmpty()) { + navigationType = NavigationType.NAVIGATION routeModel.startNavigation(route) surfaceRenderer.setRouteData() - recentPlaceActive = false invalidate() } } @@ -73,26 +65,48 @@ class NavigationScreen( val recentObserver = Observer { lastPlace -> if (!routeModel.isNavigating()) { recentPlace = lastPlace - recentPlaceFound = true + navigationType = NavigationType.RECENT invalidate() } } + val placeObserver = Observer { searchResult -> + val place = Place( + name = searchResult.displayName, + street = searchResult.address.road, + city = searchResult.address.city, + latitude = searchResult.lat.toDouble(), + longitude = searchResult.lon.toDouble(), + category = Constants.CONTACTS, + postalCode = searchResult.address.postcode + ) + navigateToPlace(place) + } + + var lastCameraSearch = 0 + + var speedCameras = listOf() + val speedObserver = Observer> { cameras -> + speedCameras = cameras + println("Speed cameras ${speedCameras.size}") + } + init { viewModel.route.observe(this, observer) viewModel.recentPlace.observe(this, recentObserver) viewModel.loadRecentPlace(location = surfaceRenderer.lastLocation) + viewModel.placeLocation.observe(this, placeObserver) + viewModel.speedCameras.observe(this, speedObserver) } override fun onGetTemplate(): Template { val actionStripBuilder = createActionStripBuilder() - if (calculateNewRoute) { - return navigationRerouteTemplate(actionStripBuilder) - } - return if (routeModel.isNavigating()) { - navigationTemplate(actionStripBuilder) - } else { - navigationEndTemplate(actionStripBuilder) + return when (navigationType) { + NavigationType.NAVIGATION -> navigationTemplate(actionStripBuilder) + NavigationType.RECENT -> navigationRecentPlaceTemplate() + NavigationType.REROUTE -> navigationRerouteTemplate(actionStripBuilder) + NavigationType.ARRIVAL -> navigationEndTemplate(actionStripBuilder) + else -> navigationViewTemplate(actionStripBuilder) } } @@ -111,30 +125,37 @@ class NavigationScreen( .build() } + private fun navigationViewTemplate(actionStripBuilder: ActionStrip.Builder): Template { + return NavigationTemplate.Builder() + .setBackgroundColor(CarColor.SECONDARY) + .setActionStrip(actionStripBuilder.build()) + .setMapActionStrip(mapActionStripBuilder().build()) + .build() + + } + private fun navigationEndTemplate(actionStripBuilder: ActionStrip.Builder): Template { if (routeModel.routeState.arrived) { - val timer = object : CountDownTimer(10000, 10000) { + val timer = object : CountDownTimer(8000, 1000) { override fun onTick(millisUntilFinished: Long) {} override fun onFinish() { routeModel.routeState = routeModel.routeState.copy(arrived = false) + navigationType = NavigationType.VIEW invalidate() } } timer.start() return navigationArrivedTemplate(actionStripBuilder) } else { - return if (recentPlaceFound && recentPlaceActive) { - return recentPlaceTemplate() - } else { - NavigationTemplate.Builder() - .setBackgroundColor(CarColor.SECONDARY) - .setActionStrip(actionStripBuilder.build()) - .setMapActionStrip(mapActionStripBuilder().build()) - .build() - } + return NavigationTemplate.Builder() + .setBackgroundColor(CarColor.SECONDARY) + .setActionStrip(actionStripBuilder.build()) + .setMapActionStrip(mapActionStripBuilder().build()) + .build() } } + fun navigationArrivedTemplate(actionStripBuilder: ActionStrip.Builder): NavigationTemplate { var street = "" if (routeModel.routeState.destination.street != null) { @@ -163,7 +184,7 @@ class NavigationScreen( .build() } - fun recentPlaceTemplate(): Template { + fun navigationRecentPlaceTemplate(): Template { val messageTemplate = MessageTemplate.Builder( recentPlace.name + "\n" + recentPlace.city @@ -266,6 +287,7 @@ class NavigationScreen( } private fun navigateAction(): Action { + navigationType = NavigationType.NAVIGATION return Action.Builder() .setIcon( CarIcon.Builder( @@ -296,9 +318,10 @@ class NavigationScreen( .build() ) .setOnClickListener { - recentPlaceActive = false + navigationType = NavigationType.VIEW invalidate() } + .setFlags(FLAG_DEFAULT) .build() } @@ -368,28 +391,6 @@ class NavigationScreen( .build() } - private fun getSuggestion(title: Int, subtitle: Int, icon: CarIcon): Suggestion { - return Suggestion.Builder() - .setIdentifier("0") - .setTitle(carContext.getString(title)) - .setSubtitle(carContext.getString(subtitle)) - .setIcon(icon) - .setAction( - CarPendingIntent.getCarApp( - carContext, 0, - Intent().setComponent( - ComponentName( - carContext, - NavigationCarAppService::class.java - ) - ), - //.setAction(NavigationSession.EXECUTE_SCRIPT), - 0 - ) - ) - .build() - } - private fun startSearchScreen() { screenManager .pushForResult( @@ -397,26 +398,39 @@ class NavigationScreen( ) { obj: Any? -> if (obj != null) { val place = obj as Place - val location = Location(LocationManager.GPS_PROVIDER) - location.latitude = place.latitude - location.longitude = place.longitude - viewModel.saveRecent(place) - viewModel.loadRoute(carContext, surfaceRenderer.lastLocation, location) - currentNavigationLocation = location - routeModel.routeState.destination = place - invalidate() + if (place.longitude == 0.0) { + viewModel.findAddress( + "${obj.city} ${obj.street}},", + currentNavigationLocation + ) + // result see observer + } else { + navigateToPlace(place) + } } } } + fun navigateToPlace(place: Place) { + navigationType = NavigationType.VIEW + val location = location(place.longitude, place.latitude) + viewModel.saveRecent(place) + currentNavigationLocation = location + viewModel.loadRoute(carContext, surfaceRenderer.lastLocation, location) + routeModel.routeState.destination = place + invalidate() + } + fun stopNavigation() { + navigationType = NavigationType.VIEW listener.stopNavigation() surfaceRenderer.routeData.value = "" + lastCameraSearch = 0 invalidate() } fun calculateNewRoute(destination: Place) { - calculateNewRoute = true + navigationType = NavigationType.REROUTE stopNavigation() invalidate() val mainThreadHandler = Handler(carContext.mainLooper) @@ -424,7 +438,7 @@ class NavigationScreen( object : CountDownTimer(3000, 1000) { override fun onTick(millisUntilFinished: Long) {} override fun onFinish() { - calculateNewRoute = false + navigationType = NavigationType.NAVIGATION reRoute(destination) } }.start() @@ -437,6 +451,12 @@ class NavigationScreen( } fun updateTrip(location: Location) { + if (lastCameraSearch++ % 100 == 0) { + viewModel.getSpeedCameras(location) + } + if (speedCameras.isNotEmpty()) { + updateDistance(location) + } with(routeModel) { updateLocation(location) if (routeState.maneuverType == Maneuver.TYPE_DESTINATION @@ -445,8 +465,33 @@ class NavigationScreen( stopNavigation() routeState = routeState.copy(arrived = true) surfaceRenderer.routeData.value = "" + navigationType = NavigationType.ARRIVAL + invalidate() } } invalidate() } + + private fun updateDistance( + location: Location, + ) { + val updatedCameras = mutableListOf() + speedCameras.forEach { + val plLocation = + location(longitude = it.lon!!, latitude = it.lat!!) + val distance = plLocation.distanceTo(location) + it.distance = distance.toDouble() + updatedCameras.add(it) + } + val sortedList = updatedCameras.sortedWith(compareBy { it.distance }) + val camera = sortedList.first() + if (camera.distance < 100) { + routeModel.showSpeedCamera(carContext, camera.distance, camera.tags.maxspeed) + } + + } } + +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 24447a0..a06fdb3 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 @@ -7,6 +7,7 @@ import android.text.SpannableString import androidx.car.app.CarContext import androidx.car.app.CarToast import androidx.car.app.Screen +import androidx.car.app.constraints.ConstraintManager import androidx.car.app.model.Action import androidx.car.app.model.CarIcon import androidx.car.app.model.Distance @@ -22,10 +23,14 @@ import com.kouros.data.R import com.kouros.navigation.car.SurfaceRenderer import com.kouros.navigation.car.navigation.RouteCarModel import com.kouros.navigation.data.Constants +import com.kouros.navigation.data.Constants.CONTACTS +import com.kouros.navigation.data.Constants.FAVORITES +import com.kouros.navigation.data.Constants.RECENT import com.kouros.navigation.data.Constants.categories import com.kouros.navigation.data.NavigationRepository import com.kouros.navigation.data.Place import com.kouros.navigation.model.ViewModel +import kotlin.math.min class PlaceListScreen( @@ -62,73 +67,74 @@ class PlaceListScreen( } fun loadPlaces() { - if (category == Constants.RECENT) { + if (category == RECENT) { viewModel.loadRecentPlaces(carContext, location) } - if (category == Constants.CONTACTS) { - viewModel.loadContacts(carContext, location) + if (category == CONTACTS) { + viewModel.loadContacts(carContext) } - if (category == Constants.FAVORITES) { + if (category == FAVORITES) { viewModel.loadFavorites(carContext, location) } } + override fun onGetTemplate(): Template { val itemListBuilder = ItemList.Builder() .setNoItemsMessage(carContext.getString(R.string.no_places)) places.forEach { - itemListBuilder.addItem( - Row.Builder() - .addAction( - deleteAction(it) + val row = Row.Builder() + .setImage(contactIcon(it.avatar, it.category)) + .setTitle(it.name!!) + .setOnClickListener { + val place = Place( + 0, + it.name, + it.category, + it.latitude, + it.longitude, + it.postalCode, + it.city, + it.street, + avatar = null ) - .setImage(contactIcon(it.avatar, it.category)) - .setTitle(it.name!!) - .addText(SpannableString(" ").apply { - setSpan( - DistanceSpan.create( - Distance.create( - it.distance.toDouble(), - Distance.UNIT_KILOMETERS - ) - ), 0, 1, Spannable.SPAN_INCLUSIVE_INCLUSIVE - ) - }) - .setOnClickListener { - val place = Place( - 0, - it.name, - it.category, - it.latitude, - it.longitude, - it.postalCode, - it.city, - it.street, - avatar = null - ) - setResult(place) - finish() -// screenManager -// .pushForResult( -// RoutePreviewScreen( -// carContext, -// surfaceRenderer, -// place -// ) -// ) { obj: Any? -> -// if (obj != null) { -// setResult(obj) -// finish() -// } -// } - } - .build() + screenManager + .pushForResult( + RoutePreviewScreen( + carContext, + surfaceRenderer, + place + ) + ) { obj: Any? -> + if (obj != null) { + setResult(obj) + finish() + } + } + } + if (category != CONTACTS) { + row.addText(SpannableString(" ").apply { + setSpan( + DistanceSpan.create( + Distance.create( + it.distance.toDouble(), + Distance.UNIT_KILOMETERS + ) + ), 0, 1, Spannable.SPAN_INCLUSIVE_INCLUSIVE + ) + }) + row.addAction( + deleteAction(it) + ) + } + itemListBuilder.addItem( + row.build() ) } var title = "" - when(category) { - Constants.RECENT -> title = carContext.getString(R.string.recent_destinations) - Constants.CONTACTS -> title = carContext.getString(R.string.contacts) - Constants.FAVORITES -> title = carContext.getString(R.string.favorites) + when (category) { + RECENT -> title = carContext.getString(R.string.recent_destinations) + CONTACTS -> title = carContext.getString(R.string.contacts) + FAVORITES -> title = carContext.getString(R.string.favorites) } val header = Header.Builder() .setStartHeaderAction(Action.BACK) diff --git a/common/car/src/main/java/com/kouros/navigation/car/screen/RoutePreviewScreen.kt b/common/car/src/main/java/com/kouros/navigation/car/screen/RoutePreviewScreen.kt index 576d7bb..f0a8ef0 100644 --- a/common/car/src/main/java/com/kouros/navigation/car/screen/RoutePreviewScreen.kt +++ b/common/car/src/main/java/com/kouros/navigation/car/screen/RoutePreviewScreen.kt @@ -1,18 +1,3 @@ -/* - * Copyright 2024 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ package com.kouros.navigation.car.screen import android.os.CountDownTimer @@ -22,18 +7,23 @@ 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.Action.FLAG_DEFAULT import androidx.car.app.model.Action.FLAG_PRIMARY import androidx.car.app.model.ActionStrip +import androidx.car.app.model.CarColor import androidx.car.app.model.CarIcon import androidx.car.app.model.CarText import androidx.car.app.model.DurationSpan import androidx.car.app.model.Header import androidx.car.app.model.ItemList import androidx.car.app.model.ListTemplate +import androidx.car.app.model.MessageTemplate import androidx.car.app.model.Row import androidx.car.app.model.Template import androidx.car.app.navigation.model.MapController import androidx.car.app.navigation.model.MapWithContentTemplate +import androidx.car.app.navigation.model.NavigationTemplate +import androidx.car.app.navigation.model.RoutingInfo import androidx.core.graphics.drawable.IconCompat import androidx.lifecycle.Observer import com.kouros.data.R @@ -78,24 +68,20 @@ class RoutePreviewScreen( override fun onGetTemplate(): Template { val navigateActionIcon: CarIcon = CarIcon.Builder( IconCompat.createWithResource( - carContext, R.drawable.baseline_assistant_navigation_24 + carContext, R.drawable.navigation_48px ) ).build() val navigateAction = Action.Builder() - .setFlags(FLAG_PRIMARY) + .setFlags(FLAG_DEFAULT) .setIcon(navigateActionIcon) .setOnClickListener { this.onNavigate() } + .build() - val itemListBuilder = ItemList.Builder() - - if (routeModel.isNavigating() && routeModel.route.waypoints.isNotEmpty()) { - itemListBuilder.addItem(createRow(0, navigateAction)) - } - val header = Header.Builder() .setStartHeaderAction(Action.BACK) .setTitle(carContext.getString(R.string.route_preview)) + //.addEndHeaderAction(navigateAction) .addEndHeaderAction( favoriteAction() ) @@ -104,21 +90,30 @@ class RoutePreviewScreen( ) .build() - val timer = object : CountDownTimer(10000, 15000) { + val message = if (routeModel.isNavigating() && routeModel.route.waypoints.isNotEmpty()) { + createRouteText() + } else { + CarText.Builder("Wait") + .build() + } + val messageTemplate = MessageTemplate.Builder( + message + ) + .setHeader(header) + .addAction(navigateAction) + .setLoading(message.toString() == "Wait") + .build() + + val timer = object : CountDownTimer(5000, 1000) { override fun onTick(millisUntilFinished: Long) {} override fun onFinish() { - onNavigate() + //onNavigate() } } timer.start() return MapWithContentTemplate.Builder() - .setContentTemplate( - ListTemplate.Builder() - .setHeader(header) - .setSingleList(itemListBuilder.build()) - .build() - ) + .setContentTemplate(messageTemplate) .setMapController( MapController.Builder().setMapActionStrip( getMapActionStrip() @@ -177,18 +172,8 @@ class RoutePreviewScreen( .build() ) .build() - private fun createRow(index: Int, action: Action): Row { - val route: CarText = createRouteText(index) - return Row.Builder() - .setTitle(route) - .setOnClickListener { onRouteSelected(index) } - .addText("${destination.street!!} ${destination.postalCode} ${destination.city}") - .addAction(action) - .build() - } - - private fun createRouteText(index: Int): CarText { + private fun createRouteText(): CarText { val time = routeModel.route.summary.time val length = BigDecimal(routeModel.route.distance).setScale(1, RoundingMode.HALF_EVEN) val firstRoute = SpannableString(" \u00b7 $length km") diff --git a/common/car/src/main/java/com/kouros/navigation/car/screen/SearchScreen.kt b/common/car/src/main/java/com/kouros/navigation/car/screen/SearchScreen.kt index d1f6544..42e3999 100644 --- a/common/car/src/main/java/com/kouros/navigation/car/screen/SearchScreen.kt +++ b/common/car/src/main/java/com/kouros/navigation/car/screen/SearchScreen.kt @@ -35,7 +35,7 @@ class SearchScreen( var categories: List = listOf( Category(id = Constants.RECENT, name = carContext.getString(R.string.recent_destinations)), - //Category(id = Constants.CONTACTS, name = carContext.getString(R.string.contacts)), + Category(id = Constants.CONTACTS, name = carContext.getString(R.string.contacts)), Category(id = Constants.CATEGORIES, name = carContext.getString(R.string.category_title)), Category(id = Constants.FAVORITES, name = carContext.getString(R.string.favorites)) ) diff --git a/common/car/src/main/java/com/kouros/navigation/car/screen/SettingsScreen.kt b/common/car/src/main/java/com/kouros/navigation/car/screen/SettingsScreen.kt index 46fca1d..644dad8 100644 --- a/common/car/src/main/java/com/kouros/navigation/car/screen/SettingsScreen.kt +++ b/common/car/src/main/java/com/kouros/navigation/car/screen/SettingsScreen.kt @@ -31,7 +31,6 @@ class SettingsScreen( ) : Screen(carContext) { override fun onGetTemplate(): Template { - val listBuilder = ItemList.Builder() listBuilder.addItem( buildRowForTemplate( diff --git a/common/car/src/test/java/com/kouros/navigation/car/UnitTest.kt b/common/car/src/test/java/com/kouros/navigation/car/UnitTest.kt index a0be751..cc2dca3 100644 --- a/common/car/src/test/java/com/kouros/navigation/car/UnitTest.kt +++ b/common/car/src/test/java/com/kouros/navigation/car/UnitTest.kt @@ -24,9 +24,11 @@ class ViewModelTest { fun routeViewModelTest() { val fromLocation = Location(LocationManager.GPS_PROVIDER) + fromLocation.isMock = true fromLocation.latitude = homeLocation.latitude fromLocation.longitude = homeLocation.longitude val toLocation = Location(LocationManager.GPS_PROVIDER) + toLocation.isMock = true toLocation.latitude = home2Location.latitude toLocation.longitude = home2Location.longitude diff --git a/common/data/src/main/java/com/kouros/navigation/data/overpass/Elements.kt b/common/data/src/main/java/com/kouros/navigation/data/overpass/Elements.kt index 3cb354b..50c372c 100644 --- a/common/data/src/main/java/com/kouros/navigation/data/overpass/Elements.kt +++ b/common/data/src/main/java/com/kouros/navigation/data/overpass/Elements.kt @@ -9,6 +9,7 @@ data class Elements ( @SerializedName("id" ) var id : Long? = null, @SerializedName("lat" ) var lat : Double? = null, @SerializedName("lon" ) var lon : Double? = null, - @SerializedName("tags" ) var tags : Tags = Tags() + @SerializedName("tags" ) var tags : Tags = Tags(), + var distance : Double = 0.0 ) \ No newline at end of file diff --git a/common/data/src/main/java/com/kouros/navigation/data/overpass/Overpass.kt b/common/data/src/main/java/com/kouros/navigation/data/overpass/Overpass.kt index 61b47cb..c676cdc 100644 --- a/common/data/src/main/java/com/kouros/navigation/data/overpass/Overpass.kt +++ b/common/data/src/main/java/com/kouros/navigation/data/overpass/Overpass.kt @@ -12,21 +12,22 @@ import java.net.URL class Overpass { val overpassUrl = "https://overpass.kumi.systems/api/interpreter" - fun getAmenities(category: String, location: Location) : List { - val boundingBox = getOverpassBbox(location, 2.0) - val bb = getBoundingBox2(location, 2.0) + fun getAmenities(type: String, category: String, location: Location) : List { + val boundingBox = getOverpassBbox(location, 5.0) val httpURLConnection = URL(overpassUrl).openConnection() as HttpURLConnection httpURLConnection.requestMethod = "POST" httpURLConnection.setRequestProperty( "Accept", "application/json" ) + // node["highway"="speed_camera"] + // node[amenity=$category] httpURLConnection.setDoOutput(true); - // define a query - val test = """ + // define search query + val searchQuery = """ |[out:json]; |( - | node[amenity=$category] + | node[$type=$category] | ($boundingBox); |); |out body; @@ -34,7 +35,7 @@ class Overpass { // Send the JSON we created val outputStreamWriter = OutputStreamWriter(httpURLConnection.outputStream) - outputStreamWriter.write(test) + outputStreamWriter.write(searchQuery) outputStreamWriter.flush() // Check if the connection is successful val responseCode = httpURLConnection.responseCode @@ -43,7 +44,7 @@ class Overpass { .use { it.readText() } // defaults to UTF-8 val gson = GsonBuilder().serializeNulls().create() val overpass = gson.fromJson(response, Amenity::class.java) - println("Overpass: $response") + println("Overpass: $type $response") return overpass.elements } return emptyList() diff --git a/common/data/src/main/java/com/kouros/navigation/data/overpass/Tags.kt b/common/data/src/main/java/com/kouros/navigation/data/overpass/Tags.kt index 7ae9104..064fbdb 100644 --- a/common/data/src/main/java/com/kouros/navigation/data/overpass/Tags.kt +++ b/common/data/src/main/java/com/kouros/navigation/data/overpass/Tags.kt @@ -3,20 +3,22 @@ package com.kouros.navigation.data.overpass import com.google.gson.annotations.SerializedName -data class Tags ( - - @SerializedName("amenity" ) var amenity : String? = null, - @SerializedName("authentication:none" ) var authenticationNone : String? = null, - @SerializedName("capacity" ) var capacity : String? = null, - @SerializedName("motorcar" ) var motorcar : String? = null, - @SerializedName("network" ) var network : String? = null, - @SerializedName("opening_hours" ) var openingHours : String? = null, - @SerializedName("operator" ) var operator : String? = null, - @SerializedName("operator:short" ) var operatorShort : String? = null, - @SerializedName("operator:wikidata" ) var operatorWikidata : String? = null, - @SerializedName("operator:wikipedia" ) var operatorWikipedia : String? = null, - @SerializedName("ref" ) var ref : String? = null, - @SerializedName("socket:type2" ) var socketType2 : String? = null, - @SerializedName("socket:type2:output" ) var socketType2Output : String? = null +data class Tags( + @SerializedName("name") var name: String? = null, + @SerializedName("amenity") var amenity: String? = null, + @SerializedName("authentication:none") var authenticationNone: String? = null, + @SerializedName("capacity") var capacity: String? = null, + @SerializedName("motorcar") var motorcar: String? = null, + @SerializedName("network") var network: String? = null, + @SerializedName("opening_hours") var openingHours: String? = null, + @SerializedName("operator") var operator: String? = null, + @SerializedName("operator:short") var operatorShort: String? = null, + @SerializedName("operator:wikidata") var operatorWikidata: String? = null, + @SerializedName("operator:wikipedia") var operatorWikipedia: String? = null, + @SerializedName("ref") var ref: String? = null, + @SerializedName("socket:type2") var socketType2: String? = null, + @SerializedName("socket:type2:output") var socketType2Output: String? = null, + @SerializedName("maxspeed") var maxspeed: String? = null, + @SerializedName("direction") var direction: String? = null, ) \ No newline at end of file diff --git a/common/data/src/main/java/com/kouros/navigation/model/Contacts.kt b/common/data/src/main/java/com/kouros/navigation/model/Contacts.kt index e2799aa..a506ed8 100644 --- a/common/data/src/main/java/com/kouros/navigation/model/Contacts.kt +++ b/common/data/src/main/java/com/kouros/navigation/model/Contacts.kt @@ -36,7 +36,7 @@ class Contacts(private var context: Context) { if (name.contains("Jola") || name.contains("Dominic") || name.contains("Martha") - || name.contains("Rena") + || name.contains("Groth") || name.contains("David")) { val mimeType: String = getString(getColumnIndex(ContactsContract.Data.MIMETYPE)) if (mimeType == ContactsContract.CommonDataKinds.StructuredPostal.CONTENT_ITEM_TYPE) { diff --git a/common/data/src/main/java/com/kouros/navigation/model/RouteModel.kt b/common/data/src/main/java/com/kouros/navigation/model/RouteModel.kt index 79069f4..9a766b9 100644 --- a/common/data/src/main/java/com/kouros/navigation/model/RouteModel.kt +++ b/common/data/src/main/java/com/kouros/navigation/model/RouteModel.kt @@ -127,6 +127,7 @@ open class RouteModel() { maneuverType = relevantManeuver.type } val maneuverIconPair = maneuverIcon(maneuverType) + routeState.maneuverType = maneuverIconPair.first // Construct and return the final StepData object return StepData( streetName, @@ -138,40 +139,7 @@ open class RouteModel() { ) } - fun currentStepOld(): StepData { - val maneuver = route.currentManeuver() - var text = "" - if (maneuver.streetNames != null && maneuver.streetNames.isNotEmpty()) { - text = maneuver.streetNames[0] - } - val distanceStepLeft = leftStepDistance() - when (distanceStepLeft) { - in 0.0..Constants.NEXT_STEP_THRESHOLD -> { - if (route.currentManeuverIndex < route.maneuvers.size) { - val maneuver = route.nextManeuver() - if (maneuver.streetNames != null && maneuver.streetNames.isNotEmpty()) { - text = maneuver.streetNames[0] - } - } - } - } - val type = if (hasArrived(maneuver.type)) { - maneuver.type - } else { - ManeuverType.None.value - } - var routing: (Pair) = maneuverIcon(type) - when (distanceStepLeft) { - in 0.0..NEXT_STEP_THRESHOLD -> { - if (route.currentManeuverIndex < route.maneuvers.size) { - val maneuver = route.nextManeuver() - val maneuverType = maneuver.type - routing = maneuverIcon(maneuverType) - } - } - } - return StepData(text, distanceStepLeft, routing.first, routing.second, arrivalTime(), travelLeftDistance()) - } + fun nextStep(): StepData { val maneuver = route.nextManeuver() val maneuverType = maneuver.type @@ -181,7 +149,6 @@ open class RouteModel() { when (distanceLeft) { in 0.0..NEXT_STEP_THRESHOLD -> { } - else -> { if (maneuver.streetNames != null && maneuver.streetNames.isNotEmpty()) { text = maneuver.streetNames[0] @@ -319,7 +286,7 @@ open class RouteModel() { currentTurnIcon = R.drawable.ic_roundabout_ccw } } - routeState.maneuverType = type + //routeState.maneuverType = type return Pair(type, currentTurnIcon) } @@ -331,7 +298,7 @@ open class RouteModel() { fun hasArrived(type: Int): Boolean { // return leftStepDistance() < DESTINATION_ARRIVAL_DISTANCE && type == ManeuverType.DestinationRight.value return type == ManeuverType.DestinationRight.value - || routeState.maneuverType == ManeuverType.Destination.value - || routeState.maneuverType == ManeuverType.DestinationLeft.value + || type == ManeuverType.Destination.value + || type == ManeuverType.DestinationLeft.value } } \ No newline at end of file diff --git a/common/data/src/main/java/com/kouros/navigation/model/ViewModel.kt b/common/data/src/main/java/com/kouros/navigation/model/ViewModel.kt index 61dadbd..9635697 100644 --- a/common/data/src/main/java/com/kouros/navigation/model/ViewModel.kt +++ b/common/data/src/main/java/com/kouros/navigation/model/ViewModel.kt @@ -1,7 +1,6 @@ package com.kouros.navigation.model import android.content.Context -import android.location.Geocoder import android.location.Location import androidx.compose.runtime.snapshots.SnapshotStateList import androidx.compose.runtime.toMutableStateList @@ -10,16 +9,15 @@ import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.google.gson.GsonBuilder import com.kouros.navigation.data.Constants -import com.kouros.navigation.data.Locations import com.kouros.navigation.data.NavigationRepository import com.kouros.navigation.data.ObjectBox.boxStore -import com.kouros.navigation.data.overpass.Overpass import com.kouros.navigation.data.Place import com.kouros.navigation.data.Place_ import com.kouros.navigation.data.SearchFilter import com.kouros.navigation.data.nominatim.Search import com.kouros.navigation.data.nominatim.SearchResult import com.kouros.navigation.data.overpass.Elements +import com.kouros.navigation.data.overpass.Overpass import com.kouros.navigation.utils.NavigationUtils import com.kouros.navigation.utils.location import io.objectbox.kotlin.boxFor @@ -31,34 +29,42 @@ import java.time.ZoneOffset class ViewModel(private val repository: NavigationRepository) : ViewModel() { val route: MutableLiveData by lazy { - MutableLiveData() + MutableLiveData() } val previewRoute: MutableLiveData by lazy { - MutableLiveData() + MutableLiveData() } val recentPlace: MutableLiveData by lazy { - MutableLiveData() + MutableLiveData() } val places: MutableLiveData> by lazy { - MutableLiveData>() + MutableLiveData() } val favorites: MutableLiveData> by lazy { - MutableLiveData>() + MutableLiveData() } val searchPlaces: MutableLiveData> by lazy { - MutableLiveData>() + MutableLiveData() } + val placeLocation: MutableLiveData by lazy { + MutableLiveData() + } + val contactAddress: MutableLiveData> by lazy { - MutableLiveData>() + MutableLiveData() } val elements: MutableLiveData> by lazy { - MutableLiveData>() + MutableLiveData() + } + + val speedCameras: MutableLiveData> by lazy { + MutableLiveData() } fun loadRecentPlace(location: Location) { @@ -98,9 +104,15 @@ class ViewModel(private val repository: NavigationRepository) : ViewModel() { query.close() for (place in results) { val plLocation = location(place.longitude, place.latitude) - val distance = - repository.getRouteDistance(location, plLocation, getSearchFilter(context)) - place.distance = distance.toFloat() + if (place.latitude != 0.0) { + val distance = + repository.getRouteDistance( + location, + plLocation, + getSearchFilter(context) + ) + place.distance = distance.toFloat() + } } places.postValue(results) } catch (e: Exception) { @@ -164,52 +176,54 @@ class ViewModel(private val repository: NavigationRepository) : ViewModel() { } } - fun loadContacts(context: Context, currentLocation: Location) { + fun loadContacts(context: Context) { viewModelScope.launch(Dispatchers.IO) { - try { - val geocoder = Geocoder(context) - val contactList = mutableListOf() - val contacts = Contacts(context = context) - val addresses = contacts.retrieveContacts() - for (address in addresses) { - val addressLines = address.address.split("\n") - geocoder.getFromLocationName( - address.address, 5 - ) { - for (adr in it) { - if (addressLines.size > 1) { - val plLocation = location(adr.longitude, adr.latitude) - val distance = - repository.getRouteDistance( - currentLocation, - plLocation, - getSearchFilter(context) - ) - contactList.add( - Place( - id = address.contactId, - name = address.name + " " + addressLines[0] + " " + addressLines[1], - Constants.CONTACTS, - street = addressLines[0], - city = addressLines[1], - latitude = adr.latitude, - longitude = adr.longitude, - avatar = address.avatar, - distance = distance.toFloat() - ) - ) - } - - } - contactAddress.postValue(contactList) - } + val contactList = mutableListOf() + val contacts = Contacts(context = context) + val addresses = contacts.retrieveContacts() + for (address in addresses) { + val addressLines = address.address.split("\n") + if (addressLines.size > 1) { + contactList.add( + Place( + id = address.contactId, + name = address.name + " " + addressLines[0] + " " + addressLines[1], + Constants.CONTACTS, + street = addressLines[0], + city = addressLines[1], + avatar = address.avatar, + longitude = 0.0, + latitude = 0.0, + distance = 0F, + ) + ) } - } catch (e: Exception) { - e.printStackTrace() + contactAddress.postValue(contactList) } } } + + fun findAddress(search: String, location: Location) { + var sortedList: List + viewModelScope.launch(Dispatchers.IO) { + val placesJson = repository.searchPlaces(search, location) + val gson = GsonBuilder().serializeNulls().create() + val places = gson.fromJson(placesJson, Search::class.java) + val distPlaces = mutableListOf() + places.forEach { + val plLocation = + location(longitude = it.lon.toDouble(), latitude = it.lat.toDouble()) + val distance = plLocation.distanceTo(location) + it.distance = distance + distPlaces.add(it) + } + sortedList = distPlaces.sortedWith(compareBy { it.distance }) + if (sortedList.isNotEmpty()) { + placeLocation.postValue(sortedList.first()) + } + } + } fun searchPlaces(search: String, location: Location) { viewModelScope.launch(Dispatchers.IO) { val placesJson = repository.searchPlaces(search, location) @@ -223,7 +237,7 @@ class ViewModel(private val repository: NavigationRepository) : ViewModel() { it.distance = distance distPlaces.add(it) } - val sortedList = distPlaces.sortedWith(compareBy({ it.distance })) + val sortedList = distPlaces.sortedWith(compareBy { it.distance }) searchPlaces.postValue(sortedList) } } @@ -237,8 +251,33 @@ class ViewModel(private val repository: NavigationRepository) : ViewModel() { fun getAmenities(category: String, location: Location) { viewModelScope.launch(Dispatchers.IO) { - val amenities = Overpass().getAmenities(category, location) - elements.postValue(amenities) + val amenities = Overpass().getAmenities("amenity", category, location) + val distAmenities = mutableListOf() + amenities.forEach { + val plLocation = + location(longitude = it.lon!!, latitude = it.lat!!) + val distance = plLocation.distanceTo(location) + it.distance = distance.toDouble() + distAmenities.add(it) + } + val sortedList = distAmenities.sortedWith(compareBy { it.distance }) + elements.postValue(sortedList) + } + } + + fun getSpeedCameras(location: Location) { + viewModelScope.launch(Dispatchers.IO) { + val amenities = Overpass().getAmenities("highway", "speed_camera", location) + val distAmenities = mutableListOf() + amenities.forEach { + val plLocation = + location(longitude = it.lon!!, latitude = it.lat!!) + val distance = plLocation.distanceTo(location) + it.distance = distance.toDouble() + distAmenities.add(it) + } + val sortedList = distAmenities.sortedWith(compareBy { it.distance }) + speedCameras.postValue(sortedList) } } diff --git a/common/data/src/main/java/com/kouros/navigation/utils/GeoUtils.kt b/common/data/src/main/java/com/kouros/navigation/utils/GeoUtils.kt index e032ad7..3565541 100644 --- a/common/data/src/main/java/com/kouros/navigation/utils/GeoUtils.kt +++ b/common/data/src/main/java/com/kouros/navigation/utils/GeoUtils.kt @@ -2,6 +2,8 @@ package com.kouros.navigation.utils import android.location.Location import com.kouros.navigation.data.BoundingBox +import kotlinx.serialization.json.buildJsonObject +import kotlinx.serialization.json.put import org.maplibre.geojson.FeatureCollection import org.maplibre.geojson.Point import org.maplibre.spatialk.geojson.Feature @@ -102,12 +104,12 @@ object GeoUtils { return featureCollection.toJson() } - fun createPointCollection(lineCoordinates: List>): String { + fun createPointCollection(lineCoordinates: List>, category: String): String { val featureCollection = buildFeatureCollection { lineCoordinates.forEach { addFeature { geometry = org.maplibre.spatialk.geojson.Point(it[0], it[1]) - properties = null + properties = buildJsonObject { put("category", category) } } } } 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 be869b8..c206822 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 @@ -4,19 +4,7 @@ import android.content.Context import android.location.Location import android.location.LocationManager import androidx.core.content.edit -import com.kouros.navigation.data.BoundingBox import com.kouros.navigation.data.Constants.SHARED_PREF_KEY -import org.maplibre.geojson.FeatureCollection -import org.maplibre.geojson.Point -import org.maplibre.spatialk.geojson.Feature -import org.maplibre.spatialk.geojson.dsl.addFeature -import org.maplibre.spatialk.geojson.dsl.buildFeatureCollection -import org.maplibre.spatialk.geojson.dsl.buildLineString -import org.maplibre.spatialk.geojson.toJson -import org.maplibre.turf.TurfMeasurement -import org.maplibre.turf.TurfMisc -import java.lang.Math.toDegrees -import java.lang.Math.toRadians import java.time.LocalDateTime import java.time.ZoneId import java.time.ZoneOffset @@ -24,7 +12,6 @@ import java.time.ZonedDateTime import java.time.format.DateTimeFormatter import java.time.format.FormatStyle import kotlin.math.absoluteValue -import kotlin.math.cos import kotlin.math.pow import kotlin.math.roundToInt import kotlin.time.Duration @@ -33,7 +20,7 @@ import kotlin.time.Duration.Companion.seconds object NavigationUtils { - fun getBooleanKeyValue(context: Context, key: String) : Boolean { + fun getBooleanKeyValue(context: Context, key: String): Boolean { return context .getSharedPreferences( SHARED_PREF_KEY, @@ -56,7 +43,7 @@ object NavigationUtils { } } - fun getIntKeyValue(context: Context, key: String) : Int { + fun getIntKeyValue(context: Context, key: String): Int { return context .getSharedPreferences( SHARED_PREF_KEY, @@ -88,9 +75,8 @@ fun calculateZoom(speed: Double?): Double { val zoom = when (speedKmh) { in 0..10 -> 18.0 in 11..30 -> 17.0 - in 21..40 -> 16.0 - in 31..50 -> 15.0 - in 51..60 -> 15.0 + in 31..50 -> 16.0 + in 61..70 -> 15.0 else -> 14 } return zoom.toDouble() @@ -98,39 +84,34 @@ fun calculateZoom(speed: Double?): Double { fun previewZoom(previewDistance: Double): Double { when (previewDistance) { - in 0.0..10.0 -> { - return 13.0 - } - in 10.0..20.0 -> { - return 11.0 - } - in 20.0..30.0 -> { - return 10.0 - } + in 0.0..10.0 -> return 13.0 + in 10.0..20.0 -> return 11.0 + in 20.0..30.0 -> return 10.0 } return 9.0 } - -fun calcTilt(newZoom: Double, tilt: Double): Double = if (newZoom < 13) { - 0.0 -} else { - if (tilt == 0.0) { - 55.0 +fun calculateTilt(newZoom: Double, tilt: Double): Double = + if (newZoom < 13) { + 0.0 } else { - tilt + if (tilt == 0.0) { + 55.0 + } else { + tilt + } } -} -fun bearing(fromLocation: Location, toLocation: Location, oldBearing: Double) : Double { + +fun bearing(fromLocation: Location, toLocation: Location, oldBearing: Double): Double { val distance = fromLocation.distanceTo(toLocation) if (distance < 1.0) { return oldBearing } val bearing = fromLocation.bearingTo(toLocation).toInt().toDouble() - return bearing + return bearing } -fun location(longitude : Double, latitude: Double): Location { +fun location(longitude: Double, latitude: Double): Location { val location = Location(LocationManager.GPS_PROVIDER) location.longitude = longitude location.latitude = latitude @@ -138,7 +119,7 @@ fun location(longitude : Double, latitude: Double): Location { } fun formatDateTime(time: Long): String { - val dateFormatter = DateTimeFormatter.ofLocalizedTime( FormatStyle.SHORT) + val dateFormatter = DateTimeFormatter.ofLocalizedTime(FormatStyle.SHORT) val dateTime = LocalDateTime.ofEpochSecond(time / 1000, 0, ZoneOffset.UTC) val zdt = ZonedDateTime.of(dateTime, ZoneId.of("Europe/Berlin")) return zdt.format(dateFormatter) diff --git a/common/car/src/main/res/drawable/ev_station_24px.xml b/common/data/src/main/res/drawable/ev_station_24px.xml similarity index 100% rename from common/car/src/main/res/drawable/ev_station_24px.xml rename to common/data/src/main/res/drawable/ev_station_24px.xml diff --git a/common/data/src/main/res/drawable/ev_station_48px.xml b/common/data/src/main/res/drawable/ev_station_48px.xml new file mode 100644 index 0000000..4681f9c --- /dev/null +++ b/common/data/src/main/res/drawable/ev_station_48px.xml @@ -0,0 +1,10 @@ + + + diff --git a/common/data/src/main/res/drawable/local_gas_station_24px.xml b/common/data/src/main/res/drawable/local_gas_station_24px.xml new file mode 100644 index 0000000..3566c7d --- /dev/null +++ b/common/data/src/main/res/drawable/local_gas_station_24px.xml @@ -0,0 +1,10 @@ + + + diff --git a/common/data/src/main/res/drawable/local_gas_station_48px.xml b/common/data/src/main/res/drawable/local_gas_station_48px.xml new file mode 100644 index 0000000..8efba2b --- /dev/null +++ b/common/data/src/main/res/drawable/local_gas_station_48px.xml @@ -0,0 +1,10 @@ + + + diff --git a/common/data/src/main/res/drawable/local_pharmacy_24px.xml b/common/data/src/main/res/drawable/local_pharmacy_24px.xml new file mode 100644 index 0000000..17a1640 --- /dev/null +++ b/common/data/src/main/res/drawable/local_pharmacy_24px.xml @@ -0,0 +1,10 @@ + + + diff --git a/common/data/src/main/res/drawable/local_pharmacy_48px.xml b/common/data/src/main/res/drawable/local_pharmacy_48px.xml new file mode 100644 index 0000000..c1ba6ad --- /dev/null +++ b/common/data/src/main/res/drawable/local_pharmacy_48px.xml @@ -0,0 +1,10 @@ + + + diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 60bd242..8e780ee 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -6,7 +6,7 @@ koinAndroid = "4.1.1" koinAndroidxCompose = "4.1.1" koinComposeViewmodel = "4.1.1" koinCore = "4.1.1" -kotlin = "2.2.21" +kotlin = "2.3.0" coreKtx = "1.17.0" junit = "4.13.2" junitVersion = "1.3.0"