Speed radar

This commit is contained in:
Dimitris
2025-12-19 15:03:32 +01:00
parent b9030dbc50
commit 9e453dc955
31 changed files with 597 additions and 412 deletions

View File

@@ -180,6 +180,7 @@ class NavigationSession : Session(), NavigationScreen.Listener {
routeModel.stopNavigation()
}
companion object {
var uriHost: String = "navigation"

View File

@@ -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
}

View File

@@ -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") {

View File

@@ -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?>(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()
}
}

View File

@@ -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<Category> = 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()
}

View File

@@ -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 + " ================"

View File

@@ -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<String> { route ->
if (route.isNotEmpty()) {
navigationType = NavigationType.NAVIGATION
routeModel.startNavigation(route)
surfaceRenderer.setRouteData()
recentPlaceActive = false
invalidate()
}
}
@@ -73,26 +65,48 @@ class NavigationScreen(
val recentObserver = Observer<Place> { lastPlace ->
if (!routeModel.isNavigating()) {
recentPlace = lastPlace
recentPlaceFound = true
navigationType = NavigationType.RECENT
invalidate()
}
}
val placeObserver = Observer<SearchResult> { 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<Elements>()
val speedObserver = Observer<List<Elements>> { 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<Elements>()
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
}

View File

@@ -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)

View File

@@ -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")

View File

@@ -35,7 +35,7 @@ class SearchScreen(
var categories: List<Category> = 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))
)

View File

@@ -31,7 +31,6 @@ class SettingsScreen(
) : Screen(carContext) {
override fun onGetTemplate(): Template {
val listBuilder = ItemList.Builder()
listBuilder.addItem(
buildRowForTemplate(

View File

@@ -1,10 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M340,760L440,600L380,600L380,480L280,640L340,640L340,760ZM240,400L480,400L480,200Q480,200 480,200Q480,200 480,200L240,200Q240,200 240,200Q240,200 240,200L240,400ZM240,760L480,760L480,480L240,480L240,760ZM160,840L160,200Q160,167 183.5,143.5Q207,120 240,120L480,120Q513,120 536.5,143.5Q560,167 560,200L560,480L610,480Q639,480 659.5,500.5Q680,521 680,550L680,735Q680,752 694,766Q708,780 725,780Q743,780 756.5,766Q770,752 770,735L770,360L760,360Q743,360 731.5,348.5Q720,337 720,320L720,240L740,240L740,180L780,180L780,240L820,240L820,180L860,180L860,240L880,240L880,320Q880,337 868.5,348.5Q857,360 840,360L830,360L830,735Q830,777 799.5,808.5Q769,840 725,840Q682,840 651,808.5Q620,777 620,735L620,550Q620,545 617.5,542.5Q615,540 610,540L560,540L560,840L160,840ZM480,760L240,760L240,760L480,760Z"/>
</vector>

View File

@@ -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