Speed radar
This commit is contained in:
@@ -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"
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
|
||||
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
|
||||
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
|
||||
<!-- <uses-permission android:name="android.permission.READ_CONTACTS"/>-->
|
||||
<uses-permission android:name="android.permission.ACCESS_MOCK_LOCATION"
|
||||
tools:ignore="MockLocation" />
|
||||
|
||||
@@ -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">
|
||||
|
||||
<meta-data
|
||||
|
||||
@@ -46,6 +46,7 @@ import com.google.android.gms.location.FusedLocationProviderClient
|
||||
import com.google.android.gms.location.LocationServices
|
||||
import com.kouros.data.R
|
||||
import com.kouros.navigation.data.Constants
|
||||
import com.kouros.navigation.data.Constants.DESTINATION_ARRIVAL_DISTANCE
|
||||
import com.kouros.navigation.data.Constants.homeLocation
|
||||
import com.kouros.navigation.data.NavigationRepository
|
||||
import com.kouros.navigation.data.StepData
|
||||
@@ -87,8 +88,8 @@ class MainActivity : ComponentActivity() {
|
||||
if (newRoute.isNotEmpty()) {
|
||||
routeModel.startNavigation(newRoute)
|
||||
routeData.value = routeModel.route.routeGeoJson
|
||||
//mock.setMockLocation(homeLocation.latitude, homeLocation.longitude)
|
||||
simulate()
|
||||
//test()
|
||||
}
|
||||
}
|
||||
val cameraPosition = MutableLiveData(
|
||||
@@ -237,10 +238,6 @@ class MainActivity : ComponentActivity() {
|
||||
&& lastLocation.latitude != location.position.latitude
|
||||
&& lastLocation.longitude != location.position.longitude
|
||||
) {
|
||||
if (lastLocation.latitude != 0.0 && !overpass) {
|
||||
//viewModel.getAmenities(Constants.CHARGING_STATION, lastLocation)
|
||||
//overpass = true
|
||||
}
|
||||
val currentLocation = location(location.position.longitude, location.position.latitude)
|
||||
with(routeModel) {
|
||||
if (isNavigating()) {
|
||||
@@ -249,6 +246,13 @@ class MainActivity : ComponentActivity() {
|
||||
if (route.currentManeuverIndex + 1 <= route.maneuvers.size) {
|
||||
nextStepData.value = nextStep()
|
||||
}
|
||||
if (routeState.maneuverType == 39
|
||||
&& leftStepDistance() < DESTINATION_ARRIVAL_DISTANCE
|
||||
) {
|
||||
stopNavigation()
|
||||
routeState = routeState.copy(arrived = true)
|
||||
routeData.value = ""
|
||||
}
|
||||
}
|
||||
}
|
||||
val bearing = bearing(lastLocation, currentLocation, cameraPosition.value!!.bearing)
|
||||
@@ -312,4 +316,17 @@ class MainActivity : ComponentActivity() {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun test() {
|
||||
for ((index, loc) in routeModel.route.waypoints.withIndex()) {
|
||||
if (index > 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -180,6 +180,7 @@ class NavigationSession : Session(), NavigationScreen.Listener {
|
||||
routeModel.stopNavigation()
|
||||
}
|
||||
|
||||
|
||||
companion object {
|
||||
var uriHost: String = "navigation"
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
}
|
||||
@@ -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") {
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
@@ -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 + " ================"
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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))
|
||||
)
|
||||
|
||||
@@ -31,7 +31,6 @@ class SettingsScreen(
|
||||
) : Screen(carContext) {
|
||||
|
||||
override fun onGetTemplate(): Template {
|
||||
|
||||
val listBuilder = ItemList.Builder()
|
||||
listBuilder.addItem(
|
||||
buildRowForTemplate(
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
)
|
||||
@@ -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<Elements> {
|
||||
val boundingBox = getOverpassBbox(location, 2.0)
|
||||
val bb = getBoundingBox2(location, 2.0)
|
||||
fun getAmenities(type: String, category: String, location: Location) : List<Elements> {
|
||||
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()
|
||||
|
||||
@@ -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,
|
||||
|
||||
)
|
||||
@@ -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) {
|
||||
|
||||
@@ -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<Int, Int>) = 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
|
||||
}
|
||||
}
|
||||
@@ -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<String> by lazy {
|
||||
MutableLiveData<String>()
|
||||
MutableLiveData()
|
||||
}
|
||||
|
||||
val previewRoute: MutableLiveData<String> by lazy {
|
||||
MutableLiveData<String>()
|
||||
MutableLiveData()
|
||||
}
|
||||
|
||||
val recentPlace: MutableLiveData<Place> by lazy {
|
||||
MutableLiveData<Place>()
|
||||
MutableLiveData()
|
||||
}
|
||||
val places: MutableLiveData<List<Place>> by lazy {
|
||||
MutableLiveData<List<Place>>()
|
||||
MutableLiveData()
|
||||
}
|
||||
|
||||
val favorites: MutableLiveData<List<Place>> by lazy {
|
||||
MutableLiveData<List<Place>>()
|
||||
MutableLiveData()
|
||||
}
|
||||
|
||||
val searchPlaces: MutableLiveData<List<SearchResult>> by lazy {
|
||||
MutableLiveData<List<SearchResult>>()
|
||||
MutableLiveData()
|
||||
}
|
||||
|
||||
val placeLocation: MutableLiveData<SearchResult> by lazy {
|
||||
MutableLiveData()
|
||||
}
|
||||
|
||||
val contactAddress: MutableLiveData<List<Place>> by lazy {
|
||||
MutableLiveData<List<Place>>()
|
||||
MutableLiveData()
|
||||
}
|
||||
|
||||
val elements: MutableLiveData<List<Elements>> by lazy {
|
||||
MutableLiveData<List<Elements>>()
|
||||
MutableLiveData()
|
||||
}
|
||||
|
||||
val speedCameras: MutableLiveData<List<Elements>> 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<Place>()
|
||||
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<Place>()
|
||||
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<SearchResult>
|
||||
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<SearchResult>()
|
||||
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<Elements>()
|
||||
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<Elements>()
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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<List<Double>>): String {
|
||||
fun createPointCollection(lineCoordinates: List<List<Double>>, 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) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
10
common/data/src/main/res/drawable/ev_station_48px.xml
Normal file
10
common/data/src/main/res/drawable/ev_station_48px.xml
Normal file
@@ -0,0 +1,10 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="48dp"
|
||||
android:height="48dp"
|
||||
android:viewportWidth="960"
|
||||
android:viewportHeight="960"
|
||||
android:tint="?attr/colorControlNormal">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M220,408L489,408L489,180Q489,180 489,180Q489,180 489,180L220,180Q220,180 220,180Q220,180 220,180L220,408ZM160,840L160,180Q160,156 178,138Q196,120 220,120L489,120Q513,120 531,138Q549,156 549,180L549,468L614,468Q634.71,468 649.36,482.64Q664,497.29 664,518L664,737Q664,759 681.5,773.5Q699,788 722,788Q745,788 765,773.5Q785,759 785,737L785,350L770,350Q757.25,350 748.63,341.37Q740,332.75 740,320L740,230L760,230L760,180L790,180L790,230L830,230L830,180L860,180L860,230L880,230L880,320Q880,332.75 871.38,341.37Q862.75,350 850,350L835,350L835,736.69Q835,780 801,810Q767,840 721.82,840Q677.66,840 645.83,810Q614,780 614,737L614,518Q614,518 614,518Q614,518 614,518L549,518L549,840L160,840ZM337,746L425,606L372,606L372,501L285,641L337,641L337,746Z"/>
|
||||
</vector>
|
||||
10
common/data/src/main/res/drawable/local_gas_station_24px.xml
Normal file
10
common/data/src/main/res/drawable/local_gas_station_24px.xml
Normal file
@@ -0,0 +1,10 @@
|
||||
<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="M160,840L160,200Q160,167 183.5,143.5Q207,120 240,120L480,120Q513,120 536.5,143.5Q560,167 560,200L560,480L600,480Q633,480 656.5,503.5Q680,527 680,560L680,740Q680,757 691.5,768.5Q703,780 720,780Q737,780 748.5,768.5Q760,757 760,740L760,452Q751,457 741,458.5Q731,460 720,460Q678,460 649,431Q620,402 620,360Q620,328 637.5,302.5Q655,277 684,266L600,182L642,140L790,284Q805,299 812.5,319Q820,339 820,360L820,740Q820,782 791,811Q762,840 720,840Q678,840 649,811Q620,782 620,740L620,540Q620,540 620,540Q620,540 620,540L560,540L560,840L160,840ZM240,400L480,400L480,200Q480,200 480,200Q480,200 480,200L240,200Q240,200 240,200Q240,200 240,200L240,400ZM720,400Q737,400 748.5,388.5Q760,377 760,360Q760,343 748.5,331.5Q737,320 720,320Q703,320 691.5,331.5Q680,343 680,360Q680,377 691.5,388.5Q703,400 720,400ZM240,760L480,760L480,480L240,480L240,760ZM480,760L240,760L240,760L480,760Z"/>
|
||||
</vector>
|
||||
10
common/data/src/main/res/drawable/local_gas_station_48px.xml
Normal file
10
common/data/src/main/res/drawable/local_gas_station_48px.xml
Normal file
@@ -0,0 +1,10 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="48dp"
|
||||
android:height="48dp"
|
||||
android:viewportWidth="960"
|
||||
android:viewportHeight="960"
|
||||
android:tint="?attr/colorControlNormal">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M160,840L160,180Q160,156 178,138Q196,120 220,120L489,120Q513,120 531,138Q549,156 549,180L549,468L614,468Q634.63,468 649.31,482.69Q664,497.37 664,518L664,737Q664,758.68 679.5,773.34Q695,788 717,788Q739,788 754.5,773.34Q770,758.68 770,737L770,442Q759,448 747,451Q735,454 723,454Q683.52,454 656.26,426.74Q629,399.48 629,360Q629,328.39 647,303.19Q665,278 695,270L600,175L636,140L789,293Q803,307 811.5,323.5Q820,340 820,360L820,737Q820,780.26 790.18,810.13Q760.37,840 717.18,840Q674,840 644,810.13Q614,780.26 614,737L614,518Q614,518 614,518Q614,518 614,518L549,518L549,840L160,840ZM220,408L489,408L489,180Q489,180 489,180Q489,180 489,180L220,180Q220,180 220,180Q220,180 220,180L220,408ZM723,404Q741,404 754,391Q767,378 767,360Q767,342 754,329Q741,316 723,316Q705,316 692,329Q679,342 679,360Q679,378 692,391Q705,404 723,404Z"/>
|
||||
</vector>
|
||||
10
common/data/src/main/res/drawable/local_pharmacy_24px.xml
Normal file
10
common/data/src/main/res/drawable/local_pharmacy_24px.xml
Normal file
@@ -0,0 +1,10 @@
|
||||
<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="M120,840L120,760L200,520L120,280L120,200L628,200L686,40L780,74L734,200L840,200L840,280L760,520L840,760L840,840L120,840ZM440,680L520,680L520,560L640,560L640,480L520,480L520,360L440,360L440,480L320,480L320,560L440,560L440,680ZM204,760L756,760L676,520L756,280L204,280L284,520L204,760ZM480,520L480,520L480,520L480,520L480,520L480,520Z"/>
|
||||
</vector>
|
||||
10
common/data/src/main/res/drawable/local_pharmacy_48px.xml
Normal file
10
common/data/src/main/res/drawable/local_pharmacy_48px.xml
Normal file
@@ -0,0 +1,10 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="48dp"
|
||||
android:height="48dp"
|
||||
android:viewportWidth="960"
|
||||
android:viewportHeight="960"
|
||||
android:tint="?attr/colorControlNormal">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M120,840L120,780L207,525L120,270L120,210L647,210L709,40L777,67L725,210L840,210L840,270L752,525L840,780L840,840L120,840ZM452,679L512,679L512,555L636,555L636,495L512,495L512,371L452,371L452,495L328,495L328,555L452,555L452,679Z"/>
|
||||
</vector>
|
||||
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user