Compare commits
3 Commits
45c8bb5ccc
...
fdf2ee9f48
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fdf2ee9f48 | ||
|
|
1eab4f1aa3 | ||
|
|
9f356bd728 |
@@ -14,8 +14,8 @@ android {
|
|||||||
applicationId = "com.kouros.navigation"
|
applicationId = "com.kouros.navigation"
|
||||||
minSdk = 33
|
minSdk = 33
|
||||||
targetSdk = 36
|
targetSdk = 36
|
||||||
versionCode = 15
|
versionCode = 18
|
||||||
versionName = "0.1.3.15"
|
versionName = "0.1.3.18"
|
||||||
base.archivesName = "navi-$versionName"
|
base.archivesName = "navi-$versionName"
|
||||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
|
<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_FINE_LOCATION"/>
|
||||||
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
|
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
|
||||||
<!-- <uses-permission android:name="android.permission.READ_CONTACTS"/>-->
|
<!-- <uses-permission android:name="android.permission.READ_CONTACTS"/>-->
|
||||||
<uses-permission android:name="android.permission.ACCESS_MOCK_LOCATION"
|
<uses-permission android:name="android.permission.ACCESS_MOCK_LOCATION"
|
||||||
tools:ignore="MockLocation" />
|
tools:ignore="MockLocation" />
|
||||||
|
|
||||||
@@ -20,6 +20,7 @@
|
|||||||
android:roundIcon="@mipmap/ic_launcher_round"
|
android:roundIcon="@mipmap/ic_launcher_round"
|
||||||
android:supportsRtl="true"
|
android:supportsRtl="true"
|
||||||
android:enableOnBackInvokedCallback="true"
|
android:enableOnBackInvokedCallback="true"
|
||||||
|
android:usesCleartextTraffic="true"
|
||||||
android:theme="@style/Theme.Navigation">
|
android:theme="@style/Theme.Navigation">
|
||||||
|
|
||||||
<meta-data
|
<meta-data
|
||||||
|
|||||||
@@ -2,25 +2,14 @@ package com.kouros.navigation
|
|||||||
|
|
||||||
import android.app.Application
|
import android.app.Application
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import com.kouros.navigation.data.Constants.DARK_MODE_SETTINGS
|
|
||||||
import com.kouros.navigation.data.Constants.ROUTE_ENGINE
|
|
||||||
import com.kouros.navigation.data.Constants.SHOW_THREED_BUILDING
|
|
||||||
import com.kouros.navigation.data.NavigationRepository
|
|
||||||
import com.kouros.navigation.data.ObjectBox
|
import com.kouros.navigation.data.ObjectBox
|
||||||
import com.kouros.navigation.data.RouteEngine
|
|
||||||
import com.kouros.navigation.data.osrm.OsrmRepository
|
|
||||||
import com.kouros.navigation.data.valhalla.ValhallaRepository
|
|
||||||
import com.kouros.navigation.di.appModule
|
import com.kouros.navigation.di.appModule
|
||||||
import com.kouros.navigation.model.ViewModel
|
import com.kouros.navigation.model.ViewModel
|
||||||
import com.kouros.navigation.utils.NavigationUtils.getBooleanKeyValue
|
|
||||||
import com.kouros.navigation.utils.NavigationUtils.getIntKeyValue
|
|
||||||
import com.kouros.navigation.utils.NavigationUtils.getRouteEngine
|
import com.kouros.navigation.utils.NavigationUtils.getRouteEngine
|
||||||
import com.kouros.navigation.utils.NavigationUtils.setIntKeyValue
|
|
||||||
import org.koin.android.ext.koin.androidContext
|
import org.koin.android.ext.koin.androidContext
|
||||||
import org.koin.android.ext.koin.androidLogger
|
import org.koin.android.ext.koin.androidLogger
|
||||||
import org.koin.core.context.startKoin
|
import org.koin.core.context.startKoin
|
||||||
import org.koin.core.logger.Level
|
import org.koin.core.logger.Level
|
||||||
import org.maplibre.compose.expressions.dsl.switch
|
|
||||||
|
|
||||||
class MainApplication : Application() {
|
class MainApplication : Application() {
|
||||||
|
|
||||||
@@ -28,7 +17,6 @@ class MainApplication : Application() {
|
|||||||
super.onCreate()
|
super.onCreate()
|
||||||
ObjectBox.init(this);
|
ObjectBox.init(this);
|
||||||
appContext = applicationContext
|
appContext = applicationContext
|
||||||
setIntKeyValue(appContext!!, RouteEngine.VALHALLA.ordinal, ROUTE_ENGINE)
|
|
||||||
navigationViewModel = getRouteEngine(appContext!!)
|
navigationViewModel = getRouteEngine(appContext!!)
|
||||||
startKoin {
|
startKoin {
|
||||||
androidLogger(Level.DEBUG)
|
androidLogger(Level.DEBUG)
|
||||||
@@ -42,8 +30,6 @@ class MainApplication : Application() {
|
|||||||
var appContext: Context? = null
|
var appContext: Context? = null
|
||||||
private set
|
private set
|
||||||
|
|
||||||
var useContacts = false
|
|
||||||
|
|
||||||
lateinit var navigationViewModel : ViewModel
|
lateinit var navigationViewModel : ViewModel
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -3,13 +3,15 @@ package com.kouros.navigation.di
|
|||||||
import com.kouros.navigation.data.NavigationRepository
|
import com.kouros.navigation.data.NavigationRepository
|
||||||
import com.kouros.navigation.data.osrm.OsrmRepository
|
import com.kouros.navigation.data.osrm.OsrmRepository
|
||||||
import com.kouros.navigation.data.valhalla.ValhallaRepository
|
import com.kouros.navigation.data.valhalla.ValhallaRepository
|
||||||
|
import com.kouros.navigation.model.BaseStyleModel
|
||||||
import com.kouros.navigation.model.ViewModel
|
import com.kouros.navigation.model.ViewModel
|
||||||
import org.koin.core.module.dsl.singleOf
|
import org.koin.core.module.dsl.singleOf
|
||||||
import org.koin.core.module.dsl.viewModelOf
|
import org.koin.core.module.dsl.viewModelOf
|
||||||
import org.koin.dsl.module
|
import org.koin.dsl.module
|
||||||
|
import kotlin.math.sin
|
||||||
|
|
||||||
val appModule = module {
|
val appModule = module {
|
||||||
viewModelOf(::ViewModel)
|
viewModelOf(::ViewModel)
|
||||||
singleOf(::ValhallaRepository)
|
singleOf(::ValhallaRepository)
|
||||||
singleOf(::OsrmRepository)
|
singleOf(::OsrmRepository)
|
||||||
}
|
}
|
||||||
@@ -39,20 +39,21 @@ import androidx.lifecycle.Observer
|
|||||||
import com.google.android.gms.location.FusedLocationProviderClient
|
import com.google.android.gms.location.FusedLocationProviderClient
|
||||||
import com.google.android.gms.location.LocationServices
|
import com.google.android.gms.location.LocationServices
|
||||||
import com.kouros.navigation.MainApplication.Companion.navigationViewModel
|
import com.kouros.navigation.MainApplication.Companion.navigationViewModel
|
||||||
|
import com.kouros.navigation.data.Constants
|
||||||
import com.kouros.navigation.data.Constants.DESTINATION_ARRIVAL_DISTANCE
|
import com.kouros.navigation.data.Constants.DESTINATION_ARRIVAL_DISTANCE
|
||||||
import com.kouros.navigation.data.Constants.homeLocation
|
import com.kouros.navigation.data.Constants.homeLocation
|
||||||
import com.kouros.navigation.data.StepData
|
import com.kouros.navigation.data.StepData
|
||||||
|
import com.kouros.navigation.model.BaseStyleModel
|
||||||
import com.kouros.navigation.model.MockLocation
|
import com.kouros.navigation.model.MockLocation
|
||||||
import com.kouros.navigation.model.RouteModel
|
import com.kouros.navigation.model.RouteModel
|
||||||
import com.kouros.navigation.ui.theme.NavigationTheme
|
import com.kouros.navigation.ui.theme.NavigationTheme
|
||||||
|
import com.kouros.navigation.utils.GeoUtils.snapLocation
|
||||||
|
import com.kouros.navigation.utils.NavigationUtils.getIntKeyValue
|
||||||
import com.kouros.navigation.utils.bearing
|
import com.kouros.navigation.utils.bearing
|
||||||
import com.kouros.navigation.utils.calculateZoom
|
import com.kouros.navigation.utils.calculateZoom
|
||||||
import com.kouros.navigation.utils.location
|
import com.kouros.navigation.utils.location
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.DelicateCoroutinesApi
|
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.GlobalScope
|
|
||||||
import kotlinx.coroutines.async
|
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import org.maplibre.compose.camera.CameraPosition
|
import org.maplibre.compose.camera.CameraPosition
|
||||||
@@ -60,6 +61,8 @@ import org.maplibre.compose.location.DesiredAccuracy
|
|||||||
import org.maplibre.compose.location.Location
|
import org.maplibre.compose.location.Location
|
||||||
import org.maplibre.compose.location.rememberDefaultLocationProvider
|
import org.maplibre.compose.location.rememberDefaultLocationProvider
|
||||||
import org.maplibre.compose.location.rememberUserLocationState
|
import org.maplibre.compose.location.rememberUserLocationState
|
||||||
|
import org.maplibre.compose.style.BaseStyle
|
||||||
|
import org.maplibre.geojson.Point
|
||||||
import org.maplibre.spatialk.geojson.Position
|
import org.maplibre.spatialk.geojson.Position
|
||||||
import kotlin.time.Duration.Companion.seconds
|
import kotlin.time.Duration.Companion.seconds
|
||||||
|
|
||||||
@@ -101,6 +104,8 @@ class MainActivity : ComponentActivity() {
|
|||||||
|
|
||||||
private var overpass = false
|
private var overpass = false
|
||||||
|
|
||||||
|
lateinit var baseStyle: BaseStyle.Json
|
||||||
|
|
||||||
init {
|
init {
|
||||||
navigationViewModel.route.observe(this, observer)
|
navigationViewModel.route.observe(this, observer)
|
||||||
}
|
}
|
||||||
@@ -108,13 +113,15 @@ class MainActivity : ComponentActivity() {
|
|||||||
@RequiresPermission(allOf = [Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.ACCESS_COARSE_LOCATION])
|
@RequiresPermission(allOf = [Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.ACCESS_COARSE_LOCATION])
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
val darkModeSettings = getIntKeyValue(applicationContext, Constants.DARK_MODE_SETTINGS)
|
||||||
|
baseStyle = BaseStyleModel().readStyle(applicationContext, darkModeSettings, false)
|
||||||
if (useMock) {
|
if (useMock) {
|
||||||
checkMockLocationEnabled()
|
checkMockLocationEnabled()
|
||||||
}
|
}
|
||||||
locationManager = getSystemService(LOCATION_SERVICE) as LocationManager
|
locationManager = getSystemService(LOCATION_SERVICE) as LocationManager
|
||||||
fusedLocationClient = LocationServices.getFusedLocationProviderClient(this)
|
fusedLocationClient = LocationServices.getFusedLocationProviderClient(this)
|
||||||
fusedLocationClient.lastLocation
|
fusedLocationClient.lastLocation
|
||||||
.addOnSuccessListener { location : android.location.Location? ->
|
.addOnSuccessListener { location: android.location.Location? ->
|
||||||
if (useMock) {
|
if (useMock) {
|
||||||
mock = MockLocation(locationManager)
|
mock = MockLocation(locationManager)
|
||||||
mock.setMockLocation(
|
mock.setMockLocation(
|
||||||
@@ -196,7 +203,8 @@ class MainActivity : ComponentActivity() {
|
|||||||
step,
|
step,
|
||||||
cameraPosition,
|
cameraPosition,
|
||||||
routeData,
|
routeData,
|
||||||
tilt
|
tilt,
|
||||||
|
baseStyle
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -229,6 +237,7 @@ class MainActivity : ComponentActivity() {
|
|||||||
&& lastLocation.longitude != location.position.longitude
|
&& lastLocation.longitude != location.position.longitude
|
||||||
) {
|
) {
|
||||||
val currentLocation = location(location.position.longitude, location.position.latitude)
|
val currentLocation = location(location.position.longitude, location.position.latitude)
|
||||||
|
val bearing = bearing(lastLocation, currentLocation, cameraPosition.value!!.bearing)
|
||||||
with(routeModel) {
|
with(routeModel) {
|
||||||
if (isNavigating()) {
|
if (isNavigating()) {
|
||||||
updateLocation(currentLocation, navigationViewModel)
|
updateLocation(currentLocation, navigationViewModel)
|
||||||
@@ -245,7 +254,6 @@ class MainActivity : ComponentActivity() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
val bearing = bearing(lastLocation, currentLocation, cameraPosition.value!!.bearing)
|
|
||||||
val zoom = calculateZoom(location.speed)
|
val zoom = calculateZoom(location.speed)
|
||||||
cameraPosition.postValue(
|
cameraPosition.postValue(
|
||||||
cameraPosition.value!!.copy(
|
cameraPosition.value!!.copy(
|
||||||
@@ -299,12 +307,11 @@ class MainActivity : ComponentActivity() {
|
|||||||
|
|
||||||
fun simulate() {
|
fun simulate() {
|
||||||
CoroutineScope(Dispatchers.IO).launch {
|
CoroutineScope(Dispatchers.IO).launch {
|
||||||
for ((index, step) in routeModel.legs.steps.withIndex()) {
|
|
||||||
for ((windex, waypoint) in step.maneuver.waypoints.withIndex()) {
|
|
||||||
if (routeModel.isNavigating()) {
|
if (routeModel.isNavigating()) {
|
||||||
mock.setMockLocation(waypoint[1], waypoint[0])
|
for ((index, waypoint) in routeModel.route.waypoints!!.withIndex()) {
|
||||||
delay(800L) //
|
var deviation = 0.0
|
||||||
}
|
mock.setMockLocation(waypoint[1] + deviation, waypoint[0])
|
||||||
|
delay(500L) //
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,17 +7,14 @@ import androidx.compose.foundation.layout.PaddingValues
|
|||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.livedata.observeAsState
|
import androidx.compose.runtime.livedata.observeAsState
|
||||||
import androidx.compose.runtime.mutableStateOf
|
|
||||||
import androidx.compose.runtime.remember
|
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.lifecycle.MutableLiveData
|
import androidx.lifecycle.MutableLiveData
|
||||||
import androidx.window.layout.WindowMetricsCalculator
|
import androidx.window.layout.WindowMetricsCalculator
|
||||||
import com.kouros.navigation.car.ViewStyle
|
import com.kouros.navigation.car.ViewStyle
|
||||||
import com.kouros.navigation.car.map.DarkMode
|
|
||||||
import com.kouros.navigation.car.map.MapLibre
|
import com.kouros.navigation.car.map.MapLibre
|
||||||
import com.kouros.navigation.car.map.NavigationImage
|
import com.kouros.navigation.car.map.NavigationImage
|
||||||
import com.kouros.navigation.data.Constants
|
import com.kouros.navigation.car.map.rememberBaseStyle
|
||||||
import com.kouros.navigation.data.StepData
|
import com.kouros.navigation.data.StepData
|
||||||
import org.maplibre.compose.camera.CameraPosition
|
import org.maplibre.compose.camera.CameraPosition
|
||||||
import org.maplibre.compose.camera.rememberCameraState
|
import org.maplibre.compose.camera.rememberCameraState
|
||||||
@@ -34,10 +31,12 @@ fun MapView(
|
|||||||
step: StepData?,
|
step: StepData?,
|
||||||
cameraPosition: MutableLiveData<CameraPosition>,
|
cameraPosition: MutableLiveData<CameraPosition>,
|
||||||
routeData: MutableLiveData<String>,
|
routeData: MutableLiveData<String>,
|
||||||
tilt: Double
|
tilt: Double,
|
||||||
|
baseStyle: BaseStyle.Json,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
val metrics = WindowMetricsCalculator.getOrCreate().computeCurrentWindowMetrics(applicationContext)
|
val metrics =
|
||||||
|
WindowMetricsCalculator.getOrCreate().computeCurrentWindowMetrics(applicationContext)
|
||||||
val width = metrics.bounds.width()
|
val width = metrics.bounds.width()
|
||||||
val height = metrics.bounds.height()
|
val height = metrics.bounds.height()
|
||||||
val paddingValues = PaddingValues(start = 0.dp, top = 350.dp)
|
val paddingValues = PaddingValues(start = 0.dp, top = 350.dp)
|
||||||
@@ -55,17 +54,15 @@ fun MapView(
|
|||||||
zoom = 15.0,
|
zoom = 15.0,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
val baseStyle = remember {
|
|
||||||
mutableStateOf(BaseStyle.Uri(Constants.STYLE))
|
val rememberBaseStyle = rememberBaseStyle( baseStyle)
|
||||||
}
|
|
||||||
DarkMode(applicationContext, baseStyle)
|
|
||||||
Column {
|
Column {
|
||||||
NavigationInfo(step)
|
NavigationInfo(step)
|
||||||
Box(contentAlignment = Alignment.Center) {
|
Box(contentAlignment = Alignment.Center) {
|
||||||
MapLibre(
|
MapLibre(
|
||||||
applicationContext,
|
applicationContext,
|
||||||
cameraState,
|
cameraState,
|
||||||
baseStyle,
|
rememberBaseStyle,
|
||||||
route,
|
route,
|
||||||
ViewStyle.VIEW
|
ViewStyle.VIEW
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -46,6 +46,7 @@ dependencies {
|
|||||||
implementation(libs.androidx.lifecycle.runtime.ktx)
|
implementation(libs.androidx.lifecycle.runtime.ktx)
|
||||||
implementation(libs.androidx.ui)
|
implementation(libs.androidx.ui)
|
||||||
implementation(libs.maplibre.compose)
|
implementation(libs.maplibre.compose)
|
||||||
|
implementation(libs.androidx.app.projected)
|
||||||
//implementation(libs.maplibre.composeMaterial3)
|
//implementation(libs.maplibre.composeMaterial3)
|
||||||
|
|
||||||
implementation(project(":common:data"))
|
implementation(project(":common:data"))
|
||||||
|
|||||||
@@ -32,8 +32,11 @@
|
|||||||
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
|
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
|
||||||
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
|
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
|
||||||
<uses-permission android:name="androidx.car.app.ACCESS_SURFACE" />
|
<uses-permission android:name="androidx.car.app.ACCESS_SURFACE" />
|
||||||
|
<uses-permission android:name="com.google.android.gms.permission.CAR_SPEED"/>
|
||||||
|
<uses-permission android:name="android.car.permission.READ_CAR_DISPLAY_UNITS"/>
|
||||||
|
|
||||||
<application android:requestLegacyExternalStorage="true">
|
<application android:requestLegacyExternalStorage="true"
|
||||||
|
android:usesCleartextTraffic="true">
|
||||||
<meta-data
|
<meta-data
|
||||||
android:name="androidx.car.app.minCarApiLevel"
|
android:name="androidx.car.app.minCarApiLevel"
|
||||||
android:value="1" />
|
android:value="1" />
|
||||||
|
|||||||
@@ -38,9 +38,4 @@ class NavigationCarAppService : CarAppService() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public interface LocationCallback {
|
|
||||||
|
|
||||||
fun onLocationChanged(location: Location) {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import android.annotation.SuppressLint
|
|||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.content.pm.PackageManager
|
import android.content.pm.PackageManager
|
||||||
|
import android.content.res.Configuration
|
||||||
import android.location.Location
|
import android.location.Location
|
||||||
import android.location.LocationManager
|
import android.location.LocationManager
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
@@ -12,6 +13,12 @@ import androidx.car.app.CarContext
|
|||||||
import androidx.car.app.Screen
|
import androidx.car.app.Screen
|
||||||
import androidx.car.app.ScreenManager
|
import androidx.car.app.ScreenManager
|
||||||
import androidx.car.app.Session
|
import androidx.car.app.Session
|
||||||
|
import androidx.car.app.hardware.CarHardwareManager
|
||||||
|
import androidx.car.app.hardware.common.CarValue
|
||||||
|
import androidx.car.app.hardware.common.OnCarDataAvailableListener
|
||||||
|
import androidx.car.app.hardware.info.CarHardwareLocation
|
||||||
|
import androidx.car.app.hardware.info.CarSensors
|
||||||
|
import androidx.car.app.hardware.info.Speed
|
||||||
import androidx.core.location.LocationListenerCompat
|
import androidx.core.location.LocationListenerCompat
|
||||||
import androidx.core.net.toUri
|
import androidx.core.net.toUri
|
||||||
import androidx.lifecycle.DefaultLifecycleObserver
|
import androidx.lifecycle.DefaultLifecycleObserver
|
||||||
@@ -22,17 +29,19 @@ import com.kouros.navigation.car.navigation.RouteCarModel
|
|||||||
import com.kouros.navigation.car.screen.NavigationScreen
|
import com.kouros.navigation.car.screen.NavigationScreen
|
||||||
import com.kouros.navigation.car.screen.RequestPermissionScreen
|
import com.kouros.navigation.car.screen.RequestPermissionScreen
|
||||||
import com.kouros.navigation.car.screen.SearchScreen
|
import com.kouros.navigation.car.screen.SearchScreen
|
||||||
|
import com.kouros.navigation.data.Constants
|
||||||
import com.kouros.navigation.data.Constants.MAXIMAL_ROUTE_DEVIATION
|
import com.kouros.navigation.data.Constants.MAXIMAL_ROUTE_DEVIATION
|
||||||
import com.kouros.navigation.data.Constants.MAXIMAL_SNAP_CORRECTION
|
import com.kouros.navigation.data.Constants.MAXIMAL_SNAP_CORRECTION
|
||||||
import com.kouros.navigation.data.Constants.ROUTE_ENGINE
|
import com.kouros.navigation.data.Constants.ROUTING_ENGINE
|
||||||
import com.kouros.navigation.data.Constants.TAG
|
import com.kouros.navigation.data.Constants.TAG
|
||||||
import com.kouros.navigation.data.NavigationRepository
|
import com.kouros.navigation.data.RouteEngine
|
||||||
import com.kouros.navigation.data.osrm.OsrmRepository
|
import com.kouros.navigation.data.osrm.OsrmRepository
|
||||||
import com.kouros.navigation.data.valhalla.ValhallaRepository
|
import com.kouros.navigation.data.valhalla.ValhallaRepository
|
||||||
|
import com.kouros.navigation.model.BaseStyleModel
|
||||||
import com.kouros.navigation.model.ViewModel
|
import com.kouros.navigation.model.ViewModel
|
||||||
import com.kouros.navigation.utils.GeoUtils.snapLocation
|
|
||||||
import com.kouros.navigation.utils.NavigationUtils.getIntKeyValue
|
import com.kouros.navigation.utils.NavigationUtils.getIntKeyValue
|
||||||
import com.kouros.navigation.utils.NavigationUtils.getRouteEngine
|
import com.kouros.navigation.utils.NavigationUtils.getRouteEngine
|
||||||
|
import org.maplibre.compose.style.BaseStyle
|
||||||
|
|
||||||
class NavigationSession : Session(), NavigationScreen.Listener {
|
class NavigationSession : Session(), NavigationScreen.Listener {
|
||||||
|
|
||||||
@@ -45,8 +54,11 @@ class NavigationSession : Session(), NavigationScreen.Listener {
|
|||||||
lateinit var surfaceRenderer: SurfaceRenderer
|
lateinit var surfaceRenderer: SurfaceRenderer
|
||||||
|
|
||||||
var mLocationListener: LocationListenerCompat = LocationListenerCompat { location: Location? ->
|
var mLocationListener: LocationListenerCompat = LocationListenerCompat { location: Location? ->
|
||||||
|
val routingEngine = getIntKeyValue(carContext, ROUTING_ENGINE)
|
||||||
|
if (routingEngine == RouteEngine.VALHALLA.ordinal) {
|
||||||
updateLocation(location!!)
|
updateLocation(location!!)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private val mLifeCycleObserver: LifecycleObserver = object : DefaultLifecycleObserver {
|
private val mLifeCycleObserver: LifecycleObserver = object : DefaultLifecycleObserver {
|
||||||
override fun onCreate(owner: LifecycleOwner) {
|
override fun onCreate(owner: LifecycleOwner) {
|
||||||
@@ -66,6 +78,10 @@ class NavigationSession : Session(), NavigationScreen.Listener {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onDestroy(owner: LifecycleOwner) {
|
override fun onDestroy(owner: LifecycleOwner) {
|
||||||
|
val carSensors = carContext.getCarService(CarHardwareManager::class.java).carSensors
|
||||||
|
val carInfo = carContext.getCarService(CarHardwareManager::class.java).carInfo
|
||||||
|
carSensors.removeCarHardwareLocationListener(carLocationListener)
|
||||||
|
carInfo.removeSpeedListener(carSpeedListener)
|
||||||
Log.i(TAG, "In onDestroy()")
|
Log.i(TAG, "In onDestroy()")
|
||||||
val locationManager =
|
val locationManager =
|
||||||
carContext.getSystemService(Context.LOCATION_SERVICE) as LocationManager
|
carContext.getSystemService(Context.LOCATION_SERVICE) as LocationManager
|
||||||
@@ -73,24 +89,55 @@ class NavigationSession : Session(), NavigationScreen.Listener {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
lateinit var navigationViewModel : ViewModel
|
lateinit var navigationViewModel: ViewModel
|
||||||
|
|
||||||
|
lateinit var baseStyle: BaseStyle.Json
|
||||||
|
|
||||||
|
val carLocationListener: OnCarDataAvailableListener<CarHardwareLocation?> =
|
||||||
|
OnCarDataAvailableListener { data ->
|
||||||
|
if (data.location.status == CarValue.STATUS_SUCCESS) {
|
||||||
|
val location = data.location.value
|
||||||
|
surfaceRenderer.updateCarLocation(location!!)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val carSpeedListener = OnCarDataAvailableListener<Speed> { data ->
|
||||||
|
if (data.displaySpeedMetersPerSecond.status == CarValue.STATUS_SUCCESS) {
|
||||||
|
val speed = data.displaySpeedMetersPerSecond.value
|
||||||
|
surfaceRenderer.updateCarSpeed(speed!!)
|
||||||
|
}
|
||||||
|
}
|
||||||
init {
|
init {
|
||||||
val lifecycle: Lifecycle = lifecycle
|
val lifecycle: Lifecycle = lifecycle
|
||||||
lifecycle.addObserver(mLifeCycleObserver)
|
lifecycle.addObserver(mLifeCycleObserver)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun onRoutingEngineStateUpdated(routeEngine : Int) {
|
||||||
|
navigationViewModel = when (routeEngine) {
|
||||||
|
RouteEngine.VALHALLA.ordinal -> ViewModel(ValhallaRepository())
|
||||||
|
else -> ViewModel(OsrmRepository())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun onCreateScreen(intent: Intent): Screen {
|
override fun onCreateScreen(intent: Intent): Screen {
|
||||||
|
|
||||||
navigationViewModel = getRouteEngine(carContext)
|
navigationViewModel = getRouteEngine(carContext)
|
||||||
|
|
||||||
|
navigationViewModel.routingEngine.observe(this, ::onRoutingEngineStateUpdated)
|
||||||
routeModel = RouteCarModel()
|
routeModel = RouteCarModel()
|
||||||
|
|
||||||
surfaceRenderer = SurfaceRenderer(carContext, lifecycle, routeModel)
|
val darkMode = getIntKeyValue(carContext, Constants.DARK_MODE_SETTINGS)
|
||||||
|
baseStyle = BaseStyleModel().readStyle(carContext, darkMode, carContext.isDarkMode)
|
||||||
|
|
||||||
navigationScreen = NavigationScreen(carContext, surfaceRenderer, routeModel, this, navigationViewModel)
|
surfaceRenderer = SurfaceRenderer(carContext, lifecycle, routeModel, baseStyle)
|
||||||
|
|
||||||
|
navigationScreen =
|
||||||
|
NavigationScreen(carContext, surfaceRenderer, routeModel, this, navigationViewModel)
|
||||||
|
|
||||||
if (carContext.checkSelfPermission(Manifest.permission.ACCESS_FINE_LOCATION)
|
if (carContext.checkSelfPermission(Manifest.permission.ACCESS_FINE_LOCATION)
|
||||||
== PackageManager.PERMISSION_GRANTED && !useContacts
|
== PackageManager.PERMISSION_GRANTED
|
||||||
|
&& carContext.checkSelfPermission("com.google.android.gms.permission.CAR_SPEED") == PackageManager.PERMISSION_GRANTED
|
||||||
|
&& !useContacts
|
||||||
|| (useContacts && carContext.checkSelfPermission(Manifest.permission.READ_CONTACTS)
|
|| (useContacts && carContext.checkSelfPermission(Manifest.permission.READ_CONTACTS)
|
||||||
== PackageManager.PERMISSION_GRANTED)
|
== PackageManager.PERMISSION_GRANTED)
|
||||||
) {
|
) {
|
||||||
@@ -111,9 +158,23 @@ class NavigationSession : Session(), NavigationScreen.Listener {
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
addSensors()
|
||||||
|
|
||||||
return navigationScreen
|
return navigationScreen
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun addSensors() {
|
||||||
|
val carSensors = carContext.getCarService(CarHardwareManager::class.java).carSensors
|
||||||
|
val carInfo = carContext.getCarService(CarHardwareManager::class.java).carInfo
|
||||||
|
carSensors.addCarHardwareLocationListener(
|
||||||
|
CarSensors.UPDATE_RATE_NORMAL,
|
||||||
|
carContext.mainExecutor,
|
||||||
|
carLocationListener
|
||||||
|
)
|
||||||
|
carInfo.addSpeedListener(carContext.mainExecutor, carSpeedListener)
|
||||||
|
}
|
||||||
|
|
||||||
override fun onNewIntent(intent: Intent) {
|
override fun onNewIntent(intent: Intent) {
|
||||||
val screenManager = carContext.getCarService(ScreenManager::class.java)
|
val screenManager = carContext.getCarService(ScreenManager::class.java)
|
||||||
if ((CarContext.ACTION_NAVIGATE == intent.action)) {
|
if ((CarContext.ACTION_NAVIGATE == intent.action)) {
|
||||||
@@ -151,12 +212,18 @@ class NavigationSession : Session(), NavigationScreen.Listener {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onCarConfigurationChanged(newConfiguration: Configuration) {
|
||||||
|
println("Configuration: ${newConfiguration.isNightModeActive}")
|
||||||
|
super.onCarConfigurationChanged(newConfiguration)
|
||||||
|
}
|
||||||
|
|
||||||
@SuppressLint("MissingPermission")
|
@SuppressLint("MissingPermission")
|
||||||
fun requestLocationUpdates() {
|
fun requestLocationUpdates() {
|
||||||
val locationManager =
|
val locationManager =
|
||||||
carContext.getSystemService(Context.LOCATION_SERVICE) as LocationManager
|
carContext.getSystemService(Context.LOCATION_SERVICE) as LocationManager
|
||||||
val location = locationManager.getLastKnownLocation(LocationManager.GPS_PROVIDER)
|
val location = locationManager.getLastKnownLocation(LocationManager.GPS_PROVIDER)
|
||||||
if (location != null) {
|
if (location != null) {
|
||||||
|
navigationViewModel.loadRecentPlace(location = location, carContext)
|
||||||
updateLocation(location)
|
updateLocation(location)
|
||||||
locationManager.requestLocationUpdates(
|
locationManager.requestLocationUpdates(
|
||||||
LocationManager.GPS_PROVIDER,
|
LocationManager.GPS_PROVIDER,
|
||||||
@@ -169,15 +236,15 @@ class NavigationSession : Session(), NavigationScreen.Listener {
|
|||||||
|
|
||||||
fun updateLocation(location: Location) {
|
fun updateLocation(location: Location) {
|
||||||
if (routeModel.isNavigating()) {
|
if (routeModel.isNavigating()) {
|
||||||
val snapedLocation = snapLocation(location, routeModel.route.maneuverLocations())
|
navigationScreen.updateTrip(location)
|
||||||
val distance = location.distanceTo(snapedLocation)
|
val wayPointLocation = routeModel.route.currentStep().wayPointLocation
|
||||||
|
val distance = location.distanceTo(wayPointLocation)
|
||||||
if (distance > MAXIMAL_ROUTE_DEVIATION) {
|
if (distance > MAXIMAL_ROUTE_DEVIATION) {
|
||||||
navigationScreen.calculateNewRoute(routeModel.routeState.destination)
|
navigationScreen.calculateNewRoute(routeModel.routeState.destination)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
navigationScreen.updateTrip(location)
|
|
||||||
if (distance < MAXIMAL_SNAP_CORRECTION) {
|
if (distance < MAXIMAL_SNAP_CORRECTION) {
|
||||||
surfaceRenderer.updateLocation(snapedLocation)
|
surfaceRenderer.updateLocation(wayPointLocation)
|
||||||
} else {
|
} else {
|
||||||
surfaceRenderer.updateLocation(location)
|
surfaceRenderer.updateLocation(location)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,8 +5,6 @@ import android.graphics.Rect
|
|||||||
import android.hardware.display.DisplayManager
|
import android.hardware.display.DisplayManager
|
||||||
import android.hardware.display.VirtualDisplay
|
import android.hardware.display.VirtualDisplay
|
||||||
import android.location.Location
|
import android.location.Location
|
||||||
import android.os.CountDownTimer
|
|
||||||
import android.os.Handler
|
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import androidx.car.app.AppManager
|
import androidx.car.app.AppManager
|
||||||
import androidx.car.app.CarContext
|
import androidx.car.app.CarContext
|
||||||
@@ -18,8 +16,6 @@ import androidx.compose.runtime.Composable
|
|||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.livedata.observeAsState
|
import androidx.compose.runtime.livedata.observeAsState
|
||||||
import androidx.compose.runtime.mutableStateOf
|
|
||||||
import androidx.compose.runtime.remember
|
|
||||||
import androidx.compose.ui.platform.ComposeView
|
import androidx.compose.ui.platform.ComposeView
|
||||||
import androidx.lifecycle.DefaultLifecycleObserver
|
import androidx.lifecycle.DefaultLifecycleObserver
|
||||||
import androidx.lifecycle.Lifecycle
|
import androidx.lifecycle.Lifecycle
|
||||||
@@ -27,16 +23,18 @@ import androidx.lifecycle.LifecycleOwner
|
|||||||
import androidx.lifecycle.MutableLiveData
|
import androidx.lifecycle.MutableLiveData
|
||||||
import androidx.lifecycle.setViewTreeLifecycleOwner
|
import androidx.lifecycle.setViewTreeLifecycleOwner
|
||||||
import androidx.savedstate.setViewTreeSavedStateRegistryOwner
|
import androidx.savedstate.setViewTreeSavedStateRegistryOwner
|
||||||
import com.kouros.navigation.car.map.DarkMode
|
|
||||||
import com.kouros.navigation.car.map.DrawNavigationImages
|
import com.kouros.navigation.car.map.DrawNavigationImages
|
||||||
import com.kouros.navigation.car.map.MapLibre
|
import com.kouros.navigation.car.map.MapLibre
|
||||||
import com.kouros.navigation.car.map.cameraState
|
import com.kouros.navigation.car.map.cameraState
|
||||||
import com.kouros.navigation.car.map.getPaddingValues
|
import com.kouros.navigation.car.map.getPaddingValues
|
||||||
|
import com.kouros.navigation.car.map.rememberBaseStyle
|
||||||
import com.kouros.navigation.car.navigation.RouteCarModel
|
import com.kouros.navigation.car.navigation.RouteCarModel
|
||||||
import com.kouros.navigation.data.Constants
|
import com.kouros.navigation.data.Constants.ROUTING_ENGINE
|
||||||
import com.kouros.navigation.data.Constants.homeLocation
|
import com.kouros.navigation.data.Constants.homeLocation
|
||||||
import com.kouros.navigation.data.ObjectBox
|
import com.kouros.navigation.data.ObjectBox
|
||||||
|
import com.kouros.navigation.data.RouteEngine
|
||||||
import com.kouros.navigation.model.RouteModel
|
import com.kouros.navigation.model.RouteModel
|
||||||
|
import com.kouros.navigation.utils.NavigationUtils.getIntKeyValue
|
||||||
import com.kouros.navigation.utils.bearing
|
import com.kouros.navigation.utils.bearing
|
||||||
import com.kouros.navigation.utils.calculateTilt
|
import com.kouros.navigation.utils.calculateTilt
|
||||||
import com.kouros.navigation.utils.calculateZoom
|
import com.kouros.navigation.utils.calculateZoom
|
||||||
@@ -51,7 +49,8 @@ import org.maplibre.spatialk.geojson.Position
|
|||||||
|
|
||||||
class SurfaceRenderer(
|
class SurfaceRenderer(
|
||||||
private var carContext: CarContext, lifecycle: Lifecycle,
|
private var carContext: CarContext, lifecycle: Lifecycle,
|
||||||
private var routeModel: RouteCarModel
|
private var routeModel: RouteCarModel,
|
||||||
|
private var baseStyle: BaseStyle.Json
|
||||||
) : DefaultLifecycleObserver {
|
) : DefaultLifecycleObserver {
|
||||||
|
|
||||||
var lastLocation = location(0.0, 0.0)
|
var lastLocation = location(0.0, 0.0)
|
||||||
@@ -163,7 +162,9 @@ class SurfaceRenderer(
|
|||||||
|
|
||||||
fun onConnectionStateUpdated(connectionState: Int) {
|
fun onConnectionStateUpdated(connectionState: Int) {
|
||||||
when (connectionState) {
|
when (connectionState) {
|
||||||
CarConnection.CONNECTION_TYPE_NATIVE -> ObjectBox.init(carContext)
|
CarConnection.CONNECTION_TYPE_NOT_CONNECTED -> "Not connected to a head unit"
|
||||||
|
CarConnection.CONNECTION_TYPE_PROJECTION -> "Connected to Android Auto"
|
||||||
|
CarConnection.CONNECTION_TYPE_NATIVE -> ObjectBox.init(carContext) // Automotive OS
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -174,12 +175,8 @@ class SurfaceRenderer(
|
|||||||
val speedCameras: String? by speedCamerasData.observeAsState()
|
val speedCameras: String? by speedCamerasData.observeAsState()
|
||||||
val paddingValues = getPaddingValues(height, viewStyle)
|
val paddingValues = getPaddingValues(height, viewStyle)
|
||||||
val cameraState = cameraState(paddingValues, position, tilt)
|
val cameraState = cameraState(paddingValues, position, tilt)
|
||||||
|
val rememberBaseStyle = rememberBaseStyle(baseStyle)
|
||||||
val baseStyle = remember {
|
MapLibre(carContext, cameraState, rememberBaseStyle, route, viewStyle, speedCameras)
|
||||||
mutableStateOf(BaseStyle.Uri(Constants.STYLE))
|
|
||||||
}
|
|
||||||
DarkMode(carContext, baseStyle)
|
|
||||||
MapLibre(carContext, cameraState, baseStyle, route, viewStyle, speedCameras)
|
|
||||||
ShowPosition(cameraState, position, paddingValues)
|
ShowPosition(cameraState, position, paddingValues)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -192,7 +189,7 @@ class SurfaceRenderer(
|
|||||||
val cameraDuration =
|
val cameraDuration =
|
||||||
duration(viewStyle == ViewStyle.PREVIEW, position!!.bearing, lastBearing)
|
duration(viewStyle == ViewStyle.PREVIEW, position!!.bearing, lastBearing)
|
||||||
val currentSpeed: Float? by speed.observeAsState()
|
val currentSpeed: Float? by speed.observeAsState()
|
||||||
if (viewStyle == ViewStyle.VIEW) {
|
if (viewStyle == ViewStyle.VIEW || viewStyle == ViewStyle.PAN_VIEW) {
|
||||||
DrawNavigationImages(
|
DrawNavigationImages(
|
||||||
paddingValues,
|
paddingValues,
|
||||||
currentSpeed,
|
currentSpeed,
|
||||||
@@ -262,35 +259,12 @@ class SurfaceRenderer(
|
|||||||
)
|
)
|
||||||
lastBearing = cameraPosition.value!!.bearing
|
lastBearing = cameraPosition.value!!.bearing
|
||||||
lastLocation = location
|
lastLocation = location
|
||||||
speed.value = location.speed
|
|
||||||
if (!countDownTimerActive) {
|
|
||||||
countDownTimerActive = true
|
|
||||||
val mainThreadHandler = Handler(carContext.mainLooper)
|
|
||||||
val lastLocationTimer = lastLocation
|
|
||||||
checkUpdate(mainThreadHandler, lastLocationTimer)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
private fun checkUpdate(
|
|
||||||
mainThreadHandler: Handler,
|
|
||||||
lastLocationTimer: Location
|
|
||||||
) {
|
|
||||||
mainThreadHandler.post {
|
|
||||||
object : CountDownTimer(3000, 1000) {
|
|
||||||
override fun onTick(millisUntilFinished: Long) {}
|
|
||||||
override fun onFinish() {
|
|
||||||
countDownTimerActive = false
|
|
||||||
if (lastLocation.time - lastLocationTimer.time < 1500) {
|
|
||||||
speed.postValue(0F)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}.start()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun updateCameraPosition(bearing: Double, zoom: Double, target: Position) {
|
private fun updateCameraPosition(bearing: Double, zoom: Double, target: Position) {
|
||||||
|
synchronized(this) {
|
||||||
cameraPosition.postValue(
|
cameraPosition.postValue(
|
||||||
cameraPosition.value!!.copy(
|
cameraPosition.value!!.copy(
|
||||||
bearing = bearing,
|
bearing = bearing,
|
||||||
@@ -301,6 +275,7 @@ class SurfaceRenderer(
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun setRouteData() {
|
fun setRouteData() {
|
||||||
routeData.value = routeModel.route.routeGeoJson
|
routeData.value = routeModel.route.routeGeoJson
|
||||||
@@ -322,6 +297,7 @@ class SurfaceRenderer(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun setCategories(location: Location, route: String) {
|
fun setCategories(location: Location, route: String) {
|
||||||
|
synchronized(this) {
|
||||||
viewStyle = ViewStyle.AMENITY_VIEW
|
viewStyle = ViewStyle.AMENITY_VIEW
|
||||||
routeData.value = route
|
routeData.value = route
|
||||||
updateCameraPosition(
|
updateCameraPosition(
|
||||||
@@ -330,6 +306,18 @@ class SurfaceRenderer(
|
|||||||
target = Position(location.longitude, location.latitude)
|
target = Position(location.longitude, location.latitude)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun updateCarLocation(location: Location) {
|
||||||
|
val routingEngine = getIntKeyValue(carContext, ROUTING_ENGINE)
|
||||||
|
if (routingEngine == RouteEngine.OSRM.ordinal) {
|
||||||
|
updateLocation(location)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun updateCarSpeed(newSpeed: Float) {
|
||||||
|
speed.value = newSpeed
|
||||||
|
}
|
||||||
|
|
||||||
fun setCategoryLocation(location: Location, category: String) {
|
fun setCategoryLocation(location: Location, category: String) {
|
||||||
viewStyle = ViewStyle.AMENITY_VIEW
|
viewStyle = ViewStyle.AMENITY_VIEW
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
package com.kouros.navigation.car.map
|
package com.kouros.navigation.car.map
|
||||||
|
|
||||||
import android.location.Location
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
import android.location.Location
|
||||||
import androidx.compose.foundation.Canvas
|
import androidx.compose.foundation.Canvas
|
||||||
import androidx.compose.foundation.isSystemInDarkTheme
|
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.PaddingValues
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
@@ -12,13 +11,17 @@ import androidx.compose.foundation.layout.size
|
|||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.MutableState
|
import androidx.compose.runtime.MutableState
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.rememberUpdatedState
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.draw.scale
|
import androidx.compose.ui.draw.scale
|
||||||
import androidx.compose.ui.geometry.Offset
|
import androidx.compose.ui.geometry.Offset
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.graphics.drawscope.scale
|
import androidx.compose.ui.graphics.drawscope.scale
|
||||||
|
import androidx.compose.ui.platform.LocalDensity
|
||||||
import androidx.compose.ui.res.painterResource
|
import androidx.compose.ui.res.painterResource
|
||||||
import androidx.compose.ui.text.TextStyle
|
import androidx.compose.ui.text.TextStyle
|
||||||
import androidx.compose.ui.text.drawText
|
import androidx.compose.ui.text.drawText
|
||||||
@@ -33,9 +36,9 @@ import com.kouros.navigation.data.Constants.SHOW_THREED_BUILDING
|
|||||||
import com.kouros.navigation.data.NavigationColor
|
import com.kouros.navigation.data.NavigationColor
|
||||||
import com.kouros.navigation.data.RouteColor
|
import com.kouros.navigation.data.RouteColor
|
||||||
import com.kouros.navigation.data.SpeedColor
|
import com.kouros.navigation.data.SpeedColor
|
||||||
|
import com.kouros.navigation.model.BaseStyleModel
|
||||||
import com.kouros.navigation.utils.NavigationUtils.getBooleanKeyValue
|
import com.kouros.navigation.utils.NavigationUtils.getBooleanKeyValue
|
||||||
import com.kouros.navigation.utils.NavigationUtils.getIntKeyValue
|
import com.kouros.navigation.utils.NavigationUtils.getIntKeyValue
|
||||||
import com.kouros.navigation.utils.location
|
|
||||||
import org.maplibre.compose.camera.CameraPosition
|
import org.maplibre.compose.camera.CameraPosition
|
||||||
import org.maplibre.compose.camera.CameraState
|
import org.maplibre.compose.camera.CameraState
|
||||||
import org.maplibre.compose.camera.rememberCameraState
|
import org.maplibre.compose.camera.rememberCameraState
|
||||||
@@ -87,18 +90,19 @@ fun cameraState(
|
|||||||
fun MapLibre(
|
fun MapLibre(
|
||||||
context: Context,
|
context: Context,
|
||||||
cameraState: CameraState,
|
cameraState: CameraState,
|
||||||
baseStyle: MutableState<BaseStyle.Uri>,
|
baseStyle: BaseStyle.Json,
|
||||||
route: String?,
|
route: String?,
|
||||||
viewStyle: ViewStyle,
|
viewStyle: ViewStyle,
|
||||||
speedCameras: String? = ""
|
speedCameras: String? = ""
|
||||||
) {
|
) {
|
||||||
|
|
||||||
MaplibreMap(
|
MaplibreMap(
|
||||||
options = MapOptions(
|
options = MapOptions(
|
||||||
ornamentOptions =
|
ornamentOptions =
|
||||||
OrnamentOptions(isScaleBarEnabled = false)
|
OrnamentOptions(isScaleBarEnabled = false)
|
||||||
),
|
),
|
||||||
cameraState = cameraState,
|
cameraState = cameraState,
|
||||||
baseStyle = baseStyle.value
|
baseStyle = baseStyle
|
||||||
) {
|
) {
|
||||||
getBaseSource(id = "openmaptiles")?.let { tiles ->
|
getBaseSource(id = "openmaptiles")?.let { tiles ->
|
||||||
if (!getBooleanKeyValue(context = context, SHOW_THREED_BUILDING)) {
|
if (!getBooleanKeyValue(context = context, SHOW_THREED_BUILDING)) {
|
||||||
@@ -115,6 +119,7 @@ fun MapLibre(
|
|||||||
//Puck(cameraState, lastLocation)
|
//Puck(cameraState, lastLocation)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun RouteLayer(routeData: String?) {
|
fun RouteLayer(routeData: String?) {
|
||||||
if (routeData != null && routeData.isNotEmpty()) {
|
if (routeData != null && routeData.isNotEmpty()) {
|
||||||
@@ -172,12 +177,12 @@ fun AmenityLayer(routeData: String?) {
|
|||||||
@Composable
|
@Composable
|
||||||
fun SpeedCameraLayer(speedCameras: String?) {
|
fun SpeedCameraLayer(speedCameras: String?) {
|
||||||
if (speedCameras != null && speedCameras.isNotEmpty()) {
|
if (speedCameras != null && speedCameras.isNotEmpty()) {
|
||||||
val color = const(Color.DarkGray)
|
val color = const(Color.Red)
|
||||||
val cameraSource = rememberGeoJsonSource(GeoJsonData.JsonString(speedCameras))
|
val cameraSource = rememberGeoJsonSource(GeoJsonData.JsonString(speedCameras))
|
||||||
SymbolLayer(
|
SymbolLayer(
|
||||||
id = "speed-camera-layer",
|
id = "speed-camera-layer",
|
||||||
source = cameraSource,
|
source = cameraSource,
|
||||||
iconImage = image(painterResource(R.drawable.speed_camera_48px), drawAsSdf = true),
|
iconImage = image(painterResource(R.drawable.speed_camera_24px), drawAsSdf = true),
|
||||||
iconColor = color,
|
iconColor = color,
|
||||||
iconSize =
|
iconSize =
|
||||||
interpolate(
|
interpolate(
|
||||||
@@ -191,6 +196,7 @@ fun SpeedCameraLayer(speedCameras: String?) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun BuildingLayer(tiles: Source) {
|
fun BuildingLayer(tiles: Source) {
|
||||||
Anchor.Replace("building-3d") {
|
Anchor.Replace("building-3d") {
|
||||||
@@ -367,25 +373,21 @@ private fun MaxSpeed(
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun DarkMode(context: Context, baseStyle: MutableState<BaseStyle.Uri>) {
|
fun rememberBaseStyle( baseStyle : BaseStyle.Json): BaseStyle.Json {
|
||||||
val darkMode = getIntKeyValue(context, Constants.DARK_MODE_SETTINGS)
|
val rememberBaseStyle by remember() {
|
||||||
if (darkMode == 0) {
|
mutableStateOf(baseStyle)
|
||||||
baseStyle.value = BaseStyle.Uri(Constants.STYLE)
|
|
||||||
}
|
|
||||||
if (darkMode == 1) {
|
|
||||||
baseStyle.value = BaseStyle.Uri(Constants.STYLE_DARK)
|
|
||||||
}
|
|
||||||
if (darkMode == 2) {
|
|
||||||
baseStyle.value =
|
|
||||||
(if (isSystemInDarkTheme()) BaseStyle.Uri(Constants.STYLE_DARK) else BaseStyle.Uri(
|
|
||||||
Constants.STYLE
|
|
||||||
))
|
|
||||||
}
|
}
|
||||||
|
return rememberBaseStyle
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
fun getPaddingValues(height: Int, viewStyle: ViewStyle): PaddingValues {
|
fun getPaddingValues(height: Int, viewStyle: ViewStyle): PaddingValues {
|
||||||
return when (viewStyle) {
|
return when (viewStyle) {
|
||||||
ViewStyle.VIEW -> PaddingValues(start = 50.dp, top = distanceFromTop(height).dp)
|
ViewStyle.VIEW, ViewStyle.PAN_VIEW -> PaddingValues(
|
||||||
|
start = 50.dp,
|
||||||
|
top = distanceFromTop(height).dp
|
||||||
|
)
|
||||||
|
|
||||||
ViewStyle.PREVIEW -> PaddingValues(start = 150.dp, bottom = 0.dp)
|
ViewStyle.PREVIEW -> PaddingValues(start = 150.dp, bottom = 0.dp)
|
||||||
else -> PaddingValues(start = 250.dp, bottom = 0.dp)
|
else -> PaddingValues(start = 250.dp, bottom = 0.dp)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,6 +29,8 @@ import androidx.car.app.model.CarIcon
|
|||||||
import androidx.car.app.model.CarText
|
import androidx.car.app.model.CarText
|
||||||
import androidx.car.app.model.DateTimeWithZone
|
import androidx.car.app.model.DateTimeWithZone
|
||||||
import androidx.car.app.model.Distance
|
import androidx.car.app.model.Distance
|
||||||
|
import androidx.car.app.navigation.model.Lane
|
||||||
|
import androidx.car.app.navigation.model.LaneDirection
|
||||||
import androidx.car.app.navigation.model.Maneuver
|
import androidx.car.app.navigation.model.Maneuver
|
||||||
import androidx.car.app.navigation.model.Step
|
import androidx.car.app.navigation.model.Step
|
||||||
import androidx.car.app.navigation.model.TravelEstimate
|
import androidx.car.app.navigation.model.TravelEstimate
|
||||||
@@ -46,6 +48,10 @@ class RouteCarModel() : RouteModel() {
|
|||||||
val stepData = currentStep()
|
val stepData = currentStep()
|
||||||
val currentStepCueWithImage: SpannableString =
|
val currentStepCueWithImage: SpannableString =
|
||||||
createString(stepData.instruction)
|
createString(stepData.instruction)
|
||||||
|
val straightNormal =
|
||||||
|
Lane.Builder()
|
||||||
|
.addDirection(LaneDirection.create(LaneDirection.SHAPE_STRAIGHT, false))
|
||||||
|
.build()
|
||||||
val step =
|
val step =
|
||||||
Step.Builder(currentStepCueWithImage)
|
Step.Builder(currentStepCueWithImage)
|
||||||
.setManeuver(
|
.setManeuver(
|
||||||
@@ -54,12 +60,17 @@ class RouteCarModel() : RouteModel() {
|
|||||||
.build()
|
.build()
|
||||||
)
|
)
|
||||||
.setRoad(routeState.destination.street!!)
|
.setRoad(routeState.destination.street!!)
|
||||||
.build()
|
stepData.lane.forEach {
|
||||||
return step
|
if (it.indications.isNotEmpty() ) {
|
||||||
|
step.setLanesImage(createCarIcon(createLaneIcon(carContext, stepData)))
|
||||||
|
step.addLane(straightNormal)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return step.build()
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Returns the next [Step] with information such as the cue text and images. */
|
/** Returns the next [Step] with information such as the cue text and images. */
|
||||||
fun nextStep(carContext: CarContext): Step? {
|
fun nextStep(carContext: CarContext): Step {
|
||||||
val stepData = nextStep()
|
val stepData = nextStep()
|
||||||
val currentStepCueWithImage: SpannableString =
|
val currentStepCueWithImage: SpannableString =
|
||||||
createString(stepData.instruction)
|
createString(stepData.instruction)
|
||||||
@@ -125,6 +136,10 @@ class RouteCarModel() : RouteModel() {
|
|||||||
return CarIcon.Builder(IconCompat.createWithResource(carContext, iconRes)).build()
|
return CarIcon.Builder(IconCompat.createWithResource(carContext, iconRes)).build()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun createCarIcon(iconCompat: IconCompat): CarIcon {
|
||||||
|
return CarIcon.Builder(iconCompat).build()
|
||||||
|
}
|
||||||
|
|
||||||
fun showSpeedCamera(carContext: CarContext, distance: Double, maxSpeed: String?) {
|
fun showSpeedCamera(carContext: CarContext, distance: Double, maxSpeed: String?) {
|
||||||
carContext.getCarService<AppManager?>(AppManager::class.java)
|
carContext.getCarService<AppManager?>(AppManager::class.java)
|
||||||
.showAlert(createAlert(carContext, distance, maxSpeed))
|
.showAlert(createAlert(carContext, distance, maxSpeed))
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ import androidx.car.app.model.Action.FLAG_DEFAULT
|
|||||||
import androidx.car.app.model.ActionStrip
|
import androidx.car.app.model.ActionStrip
|
||||||
import androidx.car.app.model.CarColor
|
import androidx.car.app.model.CarColor
|
||||||
import androidx.car.app.model.CarIcon
|
import androidx.car.app.model.CarIcon
|
||||||
import androidx.car.app.model.CarText
|
|
||||||
import androidx.car.app.model.Distance
|
import androidx.car.app.model.Distance
|
||||||
import androidx.car.app.model.Header
|
import androidx.car.app.model.Header
|
||||||
import androidx.car.app.model.MessageTemplate
|
import androidx.car.app.model.MessageTemplate
|
||||||
@@ -29,13 +28,11 @@ import com.kouros.navigation.car.ViewStyle
|
|||||||
import com.kouros.navigation.car.navigation.RouteCarModel
|
import com.kouros.navigation.car.navigation.RouteCarModel
|
||||||
import com.kouros.navigation.data.Constants
|
import com.kouros.navigation.data.Constants
|
||||||
import com.kouros.navigation.data.Constants.DESTINATION_ARRIVAL_DISTANCE
|
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.Place
|
||||||
import com.kouros.navigation.data.nominatim.SearchResult
|
import com.kouros.navigation.data.nominatim.SearchResult
|
||||||
import com.kouros.navigation.data.overpass.Elements
|
import com.kouros.navigation.data.overpass.Elements
|
||||||
import com.kouros.navigation.model.ViewModel
|
import com.kouros.navigation.model.ViewModel
|
||||||
import com.kouros.navigation.utils.GeoUtils
|
import com.kouros.navigation.utils.GeoUtils
|
||||||
import com.kouros.navigation.utils.bearing
|
|
||||||
import com.kouros.navigation.utils.location
|
import com.kouros.navigation.utils.location
|
||||||
import kotlin.math.absoluteValue
|
import kotlin.math.absoluteValue
|
||||||
|
|
||||||
@@ -93,22 +90,17 @@ class NavigationScreen(
|
|||||||
var speedCameras = listOf<Elements>()
|
var speedCameras = listOf<Elements>()
|
||||||
val speedObserver = Observer<List<Elements>> { cameras ->
|
val speedObserver = Observer<List<Elements>> { cameras ->
|
||||||
speedCameras = cameras
|
speedCameras = cameras
|
||||||
|
|
||||||
val coordinates = mutableListOf<List<Double>>()
|
val coordinates = mutableListOf<List<Double>>()
|
||||||
val loc = location(0.0, 0.0)
|
|
||||||
cameras.forEach {
|
cameras.forEach {
|
||||||
val loc =
|
|
||||||
location(longitude = it.lon!!, latitude = it.lat!!)
|
|
||||||
coordinates.add(listOf(it.lon!!, it.lat!!))
|
coordinates.add(listOf(it.lon!!, it.lat!!))
|
||||||
}
|
}
|
||||||
val speedData = GeoUtils.createPointCollection(coordinates, "radar")
|
val speedData = GeoUtils.createPointCollection(coordinates, "radar")
|
||||||
surfaceRenderer.speedCamerasData.value =speedData
|
surfaceRenderer.speedCamerasData.value = speedData
|
||||||
}
|
}
|
||||||
|
|
||||||
init {
|
init {
|
||||||
viewModel.route.observe(this, observer)
|
viewModel.route.observe(this, observer)
|
||||||
viewModel.recentPlace.observe(this, recentObserver)
|
viewModel.recentPlace.observe(this, recentObserver)
|
||||||
viewModel.loadRecentPlace(location = surfaceRenderer.lastLocation)
|
|
||||||
viewModel.placeLocation.observe(this, placeObserver)
|
viewModel.placeLocation.observe(this, placeObserver)
|
||||||
viewModel.speedCameras.observe(this, speedObserver)
|
viewModel.speedCameras.observe(this, speedObserver)
|
||||||
}
|
}
|
||||||
@@ -240,7 +232,6 @@ class NavigationScreen(
|
|||||||
Distance.UNIT_METERS
|
Distance.UNIT_METERS
|
||||||
}
|
}
|
||||||
val nextStep = routeModel.nextStep(carContext = carContext)
|
val nextStep = routeModel.nextStep(carContext = carContext)
|
||||||
if (nextStep != null) {
|
|
||||||
return RoutingInfo.Builder()
|
return RoutingInfo.Builder()
|
||||||
.setCurrentStep(
|
.setCurrentStep(
|
||||||
routeModel.currentStep(carContext = carContext),
|
routeModel.currentStep(carContext = carContext),
|
||||||
@@ -248,14 +239,6 @@ class NavigationScreen(
|
|||||||
)
|
)
|
||||||
.setNextStep(nextStep)
|
.setNextStep(nextStep)
|
||||||
.build()
|
.build()
|
||||||
} else {
|
|
||||||
return RoutingInfo.Builder()
|
|
||||||
.setCurrentStep(
|
|
||||||
routeModel.currentStep(carContext = carContext),
|
|
||||||
Distance.create(currentDistance, displayUnit)
|
|
||||||
)
|
|
||||||
.build()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun createActionStripBuilder(): ActionStrip.Builder {
|
private fun createActionStripBuilder(): ActionStrip.Builder {
|
||||||
@@ -352,7 +335,7 @@ class NavigationScreen(
|
|||||||
return Action.Builder()
|
return Action.Builder()
|
||||||
.setIcon(routeModel.createCarIcon(carContext, R.drawable.settings_48px))
|
.setIcon(routeModel.createCarIcon(carContext, R.drawable.settings_48px))
|
||||||
.setOnClickListener {
|
.setOnClickListener {
|
||||||
screenManager.push(SettingsScreen(carContext))
|
screenManager.push(SettingsScreen(carContext, viewModel))
|
||||||
}
|
}
|
||||||
.build()
|
.build()
|
||||||
}
|
}
|
||||||
@@ -369,6 +352,7 @@ class NavigationScreen(
|
|||||||
.build()
|
.build()
|
||||||
).setOnClickListener {
|
).setOnClickListener {
|
||||||
surfaceRenderer.handleScale(1)
|
surfaceRenderer.handleScale(1)
|
||||||
|
invalidate()
|
||||||
}
|
}
|
||||||
.build()
|
.build()
|
||||||
}
|
}
|
||||||
@@ -385,6 +369,7 @@ class NavigationScreen(
|
|||||||
.build()
|
.build()
|
||||||
).setOnClickListener {
|
).setOnClickListener {
|
||||||
surfaceRenderer.handleScale(-1)
|
surfaceRenderer.handleScale(-1)
|
||||||
|
invalidate()
|
||||||
}
|
}
|
||||||
.build()
|
.build()
|
||||||
}
|
}
|
||||||
@@ -401,6 +386,7 @@ class NavigationScreen(
|
|||||||
.build()
|
.build()
|
||||||
).setOnClickListener {
|
).setOnClickListener {
|
||||||
surfaceRenderer.viewStyle = ViewStyle.VIEW
|
surfaceRenderer.viewStyle = ViewStyle.VIEW
|
||||||
|
invalidate()
|
||||||
}
|
}
|
||||||
.build()
|
.build()
|
||||||
}
|
}
|
||||||
@@ -465,7 +451,7 @@ class NavigationScreen(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun updateTrip(location: Location) {
|
fun updateTrip(location: Location) {
|
||||||
updateSpeedCamera(location)
|
updateSpeedCamera(surfaceRenderer.lastLocation)
|
||||||
with(routeModel) {
|
with(routeModel) {
|
||||||
updateLocation(location, viewModel)
|
updateLocation(location, viewModel)
|
||||||
if (routeState.maneuverType == Maneuver.TYPE_DESTINATION
|
if (routeState.maneuverType == Maneuver.TYPE_DESTINATION
|
||||||
|
|||||||
@@ -12,11 +12,12 @@ import androidx.car.app.model.Toggle
|
|||||||
import com.kouros.data.R
|
import com.kouros.data.R
|
||||||
import com.kouros.navigation.data.Constants.AVOID_MOTORWAY
|
import com.kouros.navigation.data.Constants.AVOID_MOTORWAY
|
||||||
import com.kouros.navigation.data.Constants.AVOID_TOLLWAY
|
import com.kouros.navigation.data.Constants.AVOID_TOLLWAY
|
||||||
|
import com.kouros.navigation.model.ViewModel
|
||||||
import com.kouros.navigation.utils.NavigationUtils.getBooleanKeyValue
|
import com.kouros.navigation.utils.NavigationUtils.getBooleanKeyValue
|
||||||
import com.kouros.navigation.utils.NavigationUtils.setBooleanKeyValue
|
import com.kouros.navigation.utils.NavigationUtils.setBooleanKeyValue
|
||||||
|
|
||||||
|
|
||||||
class NavigationSettings(private val carContext: CarContext) : Screen(carContext) {
|
class NavigationSettings(private val carContext: CarContext, private var viewModel: ViewModel) : Screen(carContext) {
|
||||||
|
|
||||||
private var motorWayToggleState = false
|
private var motorWayToggleState = false
|
||||||
|
|
||||||
@@ -52,7 +53,12 @@ class NavigationSettings(private val carContext: CarContext) : Screen(carContext
|
|||||||
tollWayToggleState = !tollWayToggleState
|
tollWayToggleState = !tollWayToggleState
|
||||||
}.setChecked(tollWayToggleState).build()
|
}.setChecked(tollWayToggleState).build()
|
||||||
listBuilder.addItem(buildRowForTemplate(R.string.avoid_tolls_row_title, tollwayToggle))
|
listBuilder.addItem(buildRowForTemplate(R.string.avoid_tolls_row_title, tollwayToggle))
|
||||||
|
listBuilder.addItem(
|
||||||
|
buildRowForScreenTemplate(
|
||||||
|
RoutingSettings(carContext, viewModel),
|
||||||
|
R.string.routing_engine
|
||||||
|
)
|
||||||
|
)
|
||||||
return ListTemplate.Builder()
|
return ListTemplate.Builder()
|
||||||
.setSingleList(listBuilder.build())
|
.setSingleList(listBuilder.build())
|
||||||
.setHeader(
|
.setHeader(
|
||||||
@@ -70,4 +76,12 @@ class NavigationSettings(private val carContext: CarContext) : Screen(carContext
|
|||||||
.setToggle(toggle)
|
.setToggle(toggle)
|
||||||
.build()
|
.build()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun buildRowForScreenTemplate(screen: Screen, title: Int): Row {
|
||||||
|
return Row.Builder()
|
||||||
|
.setTitle(carContext.getString(title))
|
||||||
|
.setOnClickListener { screenManager.push(screen) }
|
||||||
|
.setBrowsable(true)
|
||||||
|
.build()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -26,9 +26,10 @@ class RequestPermissionScreen(
|
|||||||
override fun onGetTemplate(): Template {
|
override fun onGetTemplate(): Template {
|
||||||
val permissions: MutableList<String?> = ArrayList()
|
val permissions: MutableList<String?> = ArrayList()
|
||||||
permissions.add(permission.ACCESS_FINE_LOCATION)
|
permissions.add(permission.ACCESS_FINE_LOCATION)
|
||||||
|
permissions.add("com.google.android.gms.permission.CAR_SPEED")
|
||||||
//permissions.add(permission.READ_CONTACTS)
|
//permissions.add(permission.READ_CONTACTS)
|
||||||
|
|
||||||
val message = "This app needs access to location in order to show the map around you"
|
val message = "This app needs access to location and to car speed"
|
||||||
|
|
||||||
val listener: OnClickListener = ParkedOnlyOnClickListener.create {
|
val listener: OnClickListener = ParkedOnlyOnClickListener.create {
|
||||||
carContext.requestPermissions(
|
carContext.requestPermissions(
|
||||||
|
|||||||
@@ -80,7 +80,6 @@ class RoutePreviewScreen(
|
|||||||
val header = Header.Builder()
|
val header = Header.Builder()
|
||||||
.setStartHeaderAction(Action.BACK)
|
.setStartHeaderAction(Action.BACK)
|
||||||
.setTitle(carContext.getString(R.string.route_preview))
|
.setTitle(carContext.getString(R.string.route_preview))
|
||||||
//.addEndHeaderAction(navigateAction)
|
|
||||||
.addEndHeaderAction(
|
.addEndHeaderAction(
|
||||||
favoriteAction()
|
favoriteAction()
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -0,0 +1,81 @@
|
|||||||
|
package com.kouros.navigation.car.screen
|
||||||
|
|
||||||
|
import androidx.car.app.CarContext
|
||||||
|
import androidx.car.app.CarToast
|
||||||
|
import androidx.car.app.Screen
|
||||||
|
import androidx.car.app.model.Action
|
||||||
|
import androidx.car.app.model.Header
|
||||||
|
import androidx.car.app.model.ItemList
|
||||||
|
import androidx.car.app.model.ListTemplate
|
||||||
|
import androidx.car.app.model.Row
|
||||||
|
import androidx.car.app.model.SectionedItemList
|
||||||
|
import androidx.car.app.model.Template
|
||||||
|
import com.kouros.data.R
|
||||||
|
import com.kouros.navigation.data.Constants.ROUTING_ENGINE
|
||||||
|
import com.kouros.navigation.data.RouteEngine
|
||||||
|
import com.kouros.navigation.model.ViewModel
|
||||||
|
import com.kouros.navigation.utils.NavigationUtils.getIntKeyValue
|
||||||
|
import com.kouros.navigation.utils.NavigationUtils.setIntKeyValue
|
||||||
|
|
||||||
|
class RoutingSettings(private val carContext: CarContext, private var viewModel: ViewModel) : Screen(carContext) {
|
||||||
|
private var routingEngine = RouteEngine.VALHALLA.ordinal
|
||||||
|
|
||||||
|
init {
|
||||||
|
routingEngine = getIntKeyValue(carContext, ROUTING_ENGINE)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onGetTemplate(): Template {
|
||||||
|
val templateBuilder = ListTemplate.Builder()
|
||||||
|
val radioList =
|
||||||
|
ItemList.Builder()
|
||||||
|
.addItem(
|
||||||
|
buildRowForTemplate(
|
||||||
|
R.string.valhalla,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.addItem(
|
||||||
|
buildRowForTemplate(
|
||||||
|
R.string.osrm,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.setOnSelectedListener { index: Int ->
|
||||||
|
this.onSelected(index)
|
||||||
|
}
|
||||||
|
.setSelectedIndex(routingEngine)
|
||||||
|
.build()
|
||||||
|
|
||||||
|
return templateBuilder
|
||||||
|
.addSectionedList(SectionedItemList.create(
|
||||||
|
radioList,
|
||||||
|
carContext.getString(R.string.routing_engine)
|
||||||
|
))
|
||||||
|
.setHeader(
|
||||||
|
Header.Builder()
|
||||||
|
.setTitle(carContext.getString(R.string.routing_engine))
|
||||||
|
.setStartHeaderAction(Action.BACK)
|
||||||
|
.build()
|
||||||
|
)
|
||||||
|
.build()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private fun onSelected(index: Int) {
|
||||||
|
setIntKeyValue(carContext, index, ROUTING_ENGINE)
|
||||||
|
viewModel.routingEngine.value = index
|
||||||
|
CarToast.makeText(
|
||||||
|
carContext,
|
||||||
|
(carContext
|
||||||
|
.getString(R.string.routing_engine)
|
||||||
|
+ ":"
|
||||||
|
+ " " + index), CarToast.LENGTH_LONG
|
||||||
|
)
|
||||||
|
.show()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun buildRowForTemplate(title: Int): Row {
|
||||||
|
return Row.Builder()
|
||||||
|
.setTitle(carContext.getString(title))
|
||||||
|
.build()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -24,10 +24,12 @@ import androidx.car.app.model.ListTemplate
|
|||||||
import androidx.car.app.model.Row
|
import androidx.car.app.model.Row
|
||||||
import androidx.car.app.model.Template
|
import androidx.car.app.model.Template
|
||||||
import com.kouros.data.R
|
import com.kouros.data.R
|
||||||
|
import com.kouros.navigation.model.ViewModel
|
||||||
|
|
||||||
/** A screen demonstrating selectable lists. */
|
/** A screen demonstrating selectable lists. */
|
||||||
class SettingsScreen(
|
class SettingsScreen(
|
||||||
carContext: CarContext,
|
carContext: CarContext,
|
||||||
|
private var viewModel: ViewModel,
|
||||||
) : Screen(carContext) {
|
) : Screen(carContext) {
|
||||||
|
|
||||||
override fun onGetTemplate(): Template {
|
override fun onGetTemplate(): Template {
|
||||||
@@ -40,7 +42,7 @@ class SettingsScreen(
|
|||||||
)
|
)
|
||||||
listBuilder.addItem(
|
listBuilder.addItem(
|
||||||
buildRowForTemplate(
|
buildRowForTemplate(
|
||||||
NavigationSettings(carContext),
|
NavigationSettings(carContext, viewModel),
|
||||||
R.string.navigation_settings
|
R.string.navigation_settings
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import com.kouros.navigation.data.Constants.home2Location
|
|||||||
import com.kouros.navigation.data.Constants.homeLocation
|
import com.kouros.navigation.data.Constants.homeLocation
|
||||||
import com.kouros.navigation.data.NavigationRepository
|
import com.kouros.navigation.data.NavigationRepository
|
||||||
import com.kouros.navigation.data.SearchFilter
|
import com.kouros.navigation.data.SearchFilter
|
||||||
|
import com.kouros.navigation.data.valhalla.ValhallaRepository
|
||||||
import com.kouros.navigation.model.RouteModel
|
import com.kouros.navigation.model.RouteModel
|
||||||
import com.kouros.navigation.model.ViewModel
|
import com.kouros.navigation.model.ViewModel
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
@@ -16,7 +17,7 @@ import org.junit.Test
|
|||||||
*/
|
*/
|
||||||
class ViewModelTest {
|
class ViewModelTest {
|
||||||
|
|
||||||
val repo = NavigationRepository()
|
val repo = ValhallaRepository()
|
||||||
val viewModel = ViewModel(repo)
|
val viewModel = ViewModel(repo)
|
||||||
val model = RouteModel()
|
val model = RouteModel()
|
||||||
|
|
||||||
@@ -33,6 +34,6 @@ class ViewModelTest {
|
|||||||
toLocation.longitude = home2Location.longitude
|
toLocation.longitude = home2Location.longitude
|
||||||
|
|
||||||
val route = repo.getRoute(fromLocation, toLocation, SearchFilter())
|
val route = repo.getRoute(fromLocation, toLocation, SearchFilter())
|
||||||
model.startNavigation(route)
|
//model.startNavigation(route)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ package com.kouros.navigation.data
|
|||||||
import android.location.Location
|
import android.location.Location
|
||||||
import android.location.LocationManager
|
import android.location.LocationManager
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
|
import com.kouros.navigation.data.route.Lane
|
||||||
import io.objectbox.annotation.Entity
|
import io.objectbox.annotation.Entity
|
||||||
import io.objectbox.annotation.Id
|
import io.objectbox.annotation.Id
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
@@ -63,7 +64,9 @@ data class StepData (
|
|||||||
|
|
||||||
var arrivalTime : Long,
|
var arrivalTime : Long,
|
||||||
|
|
||||||
var leftDistance: Double
|
var leftDistance: Double,
|
||||||
|
|
||||||
|
var lane: List<Lane> = listOf(Lane(valid = false, indications = emptyList())),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -171,6 +174,8 @@ object Constants {
|
|||||||
|
|
||||||
const val AVOID_TOLLWAY = "AvoidTollway"
|
const val AVOID_TOLLWAY = "AvoidTollway"
|
||||||
|
|
||||||
|
const val ROUTING_ENGINE = "RoutingEngine"
|
||||||
|
|
||||||
const val NEXT_STEP_THRESHOLD = 100.0
|
const val NEXT_STEP_THRESHOLD = 100.0
|
||||||
|
|
||||||
const val MAXIMAL_SNAP_CORRECTION = 50.0
|
const val MAXIMAL_SNAP_CORRECTION = 50.0
|
||||||
@@ -179,7 +184,6 @@ object Constants {
|
|||||||
|
|
||||||
const val DESTINATION_ARRIVAL_DISTANCE = 40.0
|
const val DESTINATION_ARRIVAL_DISTANCE = 40.0
|
||||||
|
|
||||||
val ROUTE_ENGINE = RouteEngine.VALHALLA.name
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -30,8 +30,6 @@ import kotlinx.serialization.json.Json
|
|||||||
|
|
||||||
abstract class NavigationRepository {
|
abstract class NavigationRepository {
|
||||||
|
|
||||||
private val placesUrl = "https://kouros-online.de/maps/placespwd";
|
|
||||||
|
|
||||||
private val nominatimUrl = "https://nominatim.openstreetmap.org/"
|
private val nominatimUrl = "https://nominatim.openstreetmap.org/"
|
||||||
|
|
||||||
|
|
||||||
@@ -58,27 +56,6 @@ abstract class NavigationRepository {
|
|||||||
return fetchUrl("${nominatimUrl}reverse?lat=${location.latitude}&lon=${location.longitude}&format=jsonv2&addressdetails=true&countrycodes=de", false)
|
return fetchUrl("${nominatimUrl}reverse?lat=${location.latitude}&lon=${location.longitude}&format=jsonv2&addressdetails=true&countrycodes=de", false)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getPlaces(): List<Place> {
|
|
||||||
val places: MutableList<Place> = ArrayList()
|
|
||||||
val placesStr = fetchUrl(placesUrl, true)
|
|
||||||
val jArray = JSONArray(placesStr)
|
|
||||||
for (i in 0..<jArray.length()) {
|
|
||||||
val json = jArray.getJSONObject(i)
|
|
||||||
val place = Place(
|
|
||||||
json.getString("id").toLong(),
|
|
||||||
json.getString("name"),
|
|
||||||
category = json.getString("category"),
|
|
||||||
latitude = json.getDouble("latitude"),
|
|
||||||
longitude = json.getDouble("longitude"),
|
|
||||||
postalCode = json.getString("postalCode"),
|
|
||||||
city = json.getString("city"),
|
|
||||||
street = json.getString("street"),
|
|
||||||
)
|
|
||||||
places.add(place)
|
|
||||||
}
|
|
||||||
return places
|
|
||||||
}
|
|
||||||
|
|
||||||
fun fetchUrl(url: String, authenticator : Boolean): String {
|
fun fetchUrl(url: String, authenticator : Boolean): String {
|
||||||
try {
|
try {
|
||||||
if (authenticator) {
|
if (authenticator) {
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ package com.kouros.navigation.data
|
|||||||
|
|
||||||
import android.location.Location
|
import android.location.Location
|
||||||
import com.google.gson.GsonBuilder
|
import com.google.gson.GsonBuilder
|
||||||
import com.kouros.navigation.data.Constants.ROUTE_ENGINE
|
|
||||||
import com.kouros.navigation.data.osrm.OsrmResponse
|
import com.kouros.navigation.data.osrm.OsrmResponse
|
||||||
import com.kouros.navigation.data.osrm.OsrmRoute
|
import com.kouros.navigation.data.osrm.OsrmRoute
|
||||||
import com.kouros.navigation.data.route.Leg
|
import com.kouros.navigation.data.route.Leg
|
||||||
@@ -11,8 +10,6 @@ import com.kouros.navigation.data.route.Summary
|
|||||||
import com.kouros.navigation.data.valhalla.ValhallaResponse
|
import com.kouros.navigation.data.valhalla.ValhallaResponse
|
||||||
import com.kouros.navigation.data.valhalla.ValhallaRoute
|
import com.kouros.navigation.data.valhalla.ValhallaRoute
|
||||||
import com.kouros.navigation.utils.GeoUtils.createCenterLocation
|
import com.kouros.navigation.utils.GeoUtils.createCenterLocation
|
||||||
import com.kouros.navigation.utils.NavigationUtils.getIntKeyValue
|
|
||||||
import com.kouros.navigation.utils.NavigationUtils.getRouteEngine
|
|
||||||
import com.kouros.navigation.utils.location
|
import com.kouros.navigation.utils.location
|
||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
import kotlinx.serialization.json.JsonElement
|
import kotlinx.serialization.json.JsonElement
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import com.google.gson.annotations.SerializedName
|
|||||||
|
|
||||||
data class Intersections(
|
data class Intersections(
|
||||||
|
|
||||||
|
@SerializedName("in") var inV: Int? = null,
|
||||||
@SerializedName("out") var out: Int? = null,
|
@SerializedName("out") var out: Int? = null,
|
||||||
@SerializedName("entry") var entry: ArrayList<Boolean> = arrayListOf(),
|
@SerializedName("entry") var entry: ArrayList<Boolean> = arrayListOf(),
|
||||||
@SerializedName("bearings") var bearings: ArrayList<Int> = arrayListOf(),
|
@SerializedName("bearings") var bearings: ArrayList<Int> = arrayListOf(),
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
package com.kouros.navigation.data.osrm
|
package com.kouros.navigation.data.osrm
|
||||||
|
|
||||||
import com.kouros.navigation.data.Route
|
import com.kouros.navigation.data.Route
|
||||||
|
import com.kouros.navigation.data.route.Intersection
|
||||||
|
import com.kouros.navigation.data.route.Lane
|
||||||
import com.kouros.navigation.data.route.Leg
|
import com.kouros.navigation.data.route.Leg
|
||||||
import com.kouros.navigation.data.route.Maneuver as RouteManeuver
|
import com.kouros.navigation.data.route.Maneuver as RouteManeuver
|
||||||
import com.kouros.navigation.data.route.Step
|
import com.kouros.navigation.data.route.Step
|
||||||
@@ -13,11 +15,12 @@ class OsrmRoute {
|
|||||||
fun mapToOsrm(routeJson: OsrmResponse, builder: Route.Builder) {
|
fun mapToOsrm(routeJson: OsrmResponse, builder: Route.Builder) {
|
||||||
val waypoints = mutableListOf<List<Double>>()
|
val waypoints = mutableListOf<List<Double>>()
|
||||||
val summary = Summary()
|
val summary = Summary()
|
||||||
summary.distance = routeJson.routes.first().distance!!
|
summary.distance = routeJson.routes.first().distance!! / 1000
|
||||||
summary.duration = routeJson.routes.first().duration!!
|
summary.duration = routeJson.routes.first().duration!! / 1000
|
||||||
val steps = mutableListOf<Step>()
|
val steps = mutableListOf<Step>()
|
||||||
var stepIndex = 0
|
var stepIndex = 0
|
||||||
routeJson.routes.first().legs.first().steps.forEach {
|
routeJson.routes.first().legs.first().steps.forEach {
|
||||||
|
val intersections = mutableListOf<Intersection>()
|
||||||
if (it.maneuver != null) {
|
if (it.maneuver != null) {
|
||||||
val points = decodePolyline(it.geometry!!, 5)
|
val points = decodePolyline(it.geometry!!, 5)
|
||||||
waypoints.addAll(points)
|
waypoints.addAll(points)
|
||||||
@@ -27,7 +30,17 @@ class OsrmRoute {
|
|||||||
type = convertType(it.maneuver!!),
|
type = convertType(it.maneuver!!),
|
||||||
waypoints = points
|
waypoints = points
|
||||||
)
|
)
|
||||||
val step = Step( index = stepIndex, name = it.name!!, distance = it.distance!!, duration = it.duration!!, maneuver = maneuver)
|
it.intersections.forEach { it2 ->
|
||||||
|
if (it2.location[0] != 0.0) {
|
||||||
|
val lanes = mutableListOf<Lane>()
|
||||||
|
it2.lanes.forEach { it3 ->
|
||||||
|
val lane = Lane(it3.valid, it3.indications)
|
||||||
|
lanes.add(lane)
|
||||||
|
}
|
||||||
|
intersections.add(Intersection(it2.location, lanes))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val step = Step( index = stepIndex, name = it.name!!, distance = it.distance!! / 1000, duration = it.duration!!, maneuver = maneuver, intersection = intersections)
|
||||||
steps.add(step)
|
steps.add(step)
|
||||||
stepIndex += 1
|
stepIndex += 1
|
||||||
}
|
}
|
||||||
@@ -53,12 +66,34 @@ class OsrmRoute {
|
|||||||
ManeuverType.continue_.value -> {
|
ManeuverType.continue_.value -> {
|
||||||
newType = androidx.car.app.navigation.model.Maneuver.TYPE_STRAIGHT
|
newType = androidx.car.app.navigation.model.Maneuver.TYPE_STRAIGHT
|
||||||
}
|
}
|
||||||
ManeuverType.turn.value -> {
|
ManeuverType.turn.value,
|
||||||
|
ManeuverType.endOfRoad.value -> {
|
||||||
if (maneuver.modifier == "right") {
|
if (maneuver.modifier == "right") {
|
||||||
newType = androidx.car.app.navigation.model.Maneuver.TYPE_TURN_NORMAL_RIGHT
|
newType = androidx.car.app.navigation.model.Maneuver.TYPE_TURN_NORMAL_RIGHT
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
ManeuverType.turn.value,
|
||||||
|
ManeuverType.endOfRoad.value,
|
||||||
|
ManeuverType.onRamp.value
|
||||||
|
-> {
|
||||||
|
if (maneuver.modifier == "left") {
|
||||||
|
newType = androidx.car.app.navigation.model.Maneuver.TYPE_TURN_NORMAL_LEFT
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
ManeuverType.fork.value
|
||||||
|
-> {
|
||||||
|
if (maneuver.modifier == "slight left") {
|
||||||
|
newType = androidx.car.app.navigation.model.Maneuver.TYPE_TURN_SLIGHT_LEFT
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ManeuverType.fork.value
|
||||||
|
-> {
|
||||||
|
if (maneuver.modifier == "slight right") {
|
||||||
|
newType = androidx.car.app.navigation.model.Maneuver.TYPE_TURN_SLIGHT_RIGHT
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return newType
|
return newType
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -64,6 +64,7 @@ class Overpass {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun overpassApi(httpURLConnection: HttpURLConnection, searchQuery: String) : List<Elements> {
|
fun overpassApi(httpURLConnection: HttpURLConnection, searchQuery: String) : List<Elements> {
|
||||||
|
try {
|
||||||
val outputStreamWriter = OutputStreamWriter(httpURLConnection.outputStream)
|
val outputStreamWriter = OutputStreamWriter(httpURLConnection.outputStream)
|
||||||
outputStreamWriter.write(searchQuery)
|
outputStreamWriter.write(searchQuery)
|
||||||
outputStreamWriter.flush()
|
outputStreamWriter.flush()
|
||||||
@@ -74,9 +75,10 @@ class Overpass {
|
|||||||
.use { it.readText() } // defaults to UTF-8
|
.use { it.readText() } // defaults to UTF-8
|
||||||
val gson = GsonBuilder().serializeNulls().create()
|
val gson = GsonBuilder().serializeNulls().create()
|
||||||
val overpass = gson.fromJson(response, Amenity::class.java)
|
val overpass = gson.fromJson(response, Amenity::class.java)
|
||||||
// println("Overpass: $response")
|
|
||||||
return overpass.elements
|
return overpass.elements
|
||||||
}
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
}
|
||||||
return emptyList()
|
return emptyList()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
package com.kouros.navigation.data.route
|
||||||
|
|
||||||
|
import java.util.Collections
|
||||||
|
|
||||||
|
data class Intersection(
|
||||||
|
val location: ArrayList<Double> = arrayListOf(0.0, 0.0),
|
||||||
|
val lane : List<Lane> = Collections.emptyList<Lane>(),
|
||||||
|
)
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
package com.kouros.navigation.data.route
|
||||||
|
|
||||||
|
data class Lane (
|
||||||
|
val valid: Boolean,
|
||||||
|
var indications: List<String>,
|
||||||
|
)
|
||||||
@@ -1,10 +1,15 @@
|
|||||||
package com.kouros.navigation.data.route
|
package com.kouros.navigation.data.route
|
||||||
|
|
||||||
class Step(
|
import android.location.Location
|
||||||
|
import com.kouros.navigation.utils.location
|
||||||
|
|
||||||
|
data class Step(
|
||||||
var index : Int = 0,
|
var index : Int = 0,
|
||||||
var waypointIndex : Int = 0,
|
var waypointIndex : Int = 0,
|
||||||
|
var wayPointLocation : Location = location(0.0,0.0),
|
||||||
val maneuver: Maneuver,
|
val maneuver: Maneuver,
|
||||||
val duration: Double = 0.0,
|
val duration: Double = 0.0,
|
||||||
val distance: Double = 0.0,
|
val distance: Double = 0.0,
|
||||||
val name : String = "",
|
val name : String = "",
|
||||||
|
val intersection: List<Intersection> = mutableListOf(),
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
package com.kouros.navigation.data.valhalla
|
package com.kouros.navigation.data.valhalla
|
||||||
|
|
||||||
|
import androidx.car.app.navigation.model.Maneuver
|
||||||
|
import com.kouros.data.R
|
||||||
import com.kouros.navigation.data.Route
|
import com.kouros.navigation.data.Route
|
||||||
import com.kouros.navigation.data.route.Leg
|
import com.kouros.navigation.data.route.Leg
|
||||||
import com.kouros.navigation.data.route.Maneuver
|
import com.kouros.navigation.data.route.Maneuver as RouteManeuver
|
||||||
import com.kouros.navigation.data.route.Step
|
import com.kouros.navigation.data.route.Step
|
||||||
import com.kouros.navigation.data.route.Summary
|
import com.kouros.navigation.data.route.Summary
|
||||||
import com.kouros.navigation.utils.GeoUtils.createLineStringCollection
|
import com.kouros.navigation.utils.GeoUtils.createLineStringCollection
|
||||||
@@ -18,10 +20,11 @@ class ValhallaRoute {
|
|||||||
val steps = mutableListOf<Step>()
|
val steps = mutableListOf<Step>()
|
||||||
var stepIndex = 0
|
var stepIndex = 0
|
||||||
routeJson.legs[0].maneuvers.forEach {
|
routeJson.legs[0].maneuvers.forEach {
|
||||||
val maneuver = Maneuver(
|
val maneuver = RouteManeuver(
|
||||||
bearingBefore = 0,
|
bearingBefore = 0,
|
||||||
bearingAfter = it.bearingAfter,
|
bearingAfter = it.bearingAfter,
|
||||||
type = it.type,
|
//type = it.type,
|
||||||
|
type = convertType(it),
|
||||||
waypoints =waypoints.subList(it.beginShapeIndex, it.endShapeIndex+1)
|
waypoints =waypoints.subList(it.beginShapeIndex, it.endShapeIndex+1)
|
||||||
)
|
)
|
||||||
var name = ""
|
var name = ""
|
||||||
@@ -40,4 +43,56 @@ class ValhallaRoute {
|
|||||||
.legs(listOf(leg))
|
.legs(listOf(leg))
|
||||||
.waypoints(waypoints)
|
.waypoints(waypoints)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun convertType(maneuver: Maneuvers): Int {
|
||||||
|
var newType = 0
|
||||||
|
when (maneuver.type) {
|
||||||
|
ManeuverType.None.value -> {
|
||||||
|
newType = Maneuver.TYPE_STRAIGHT
|
||||||
|
}
|
||||||
|
ManeuverType.Destination.value,
|
||||||
|
ManeuverType.DestinationRight.value,
|
||||||
|
ManeuverType.DestinationLeft.value,
|
||||||
|
-> {
|
||||||
|
newType = Maneuver.TYPE_DESTINATION
|
||||||
|
}
|
||||||
|
|
||||||
|
ManeuverType.Right.value -> {
|
||||||
|
newType = Maneuver.TYPE_TURN_NORMAL_RIGHT
|
||||||
|
}
|
||||||
|
|
||||||
|
ManeuverType.Left.value -> {
|
||||||
|
newType = Maneuver.TYPE_TURN_NORMAL_LEFT
|
||||||
|
}
|
||||||
|
|
||||||
|
ManeuverType.RampRight.value -> {
|
||||||
|
newType = Maneuver.TYPE_OFF_RAMP_SLIGHT_RIGHT
|
||||||
|
}
|
||||||
|
|
||||||
|
ManeuverType.RampLeft.value -> {
|
||||||
|
newType = Maneuver.TYPE_OFF_RAMP_SLIGHT_LEFT
|
||||||
|
}
|
||||||
|
|
||||||
|
ManeuverType.ExitRight.value -> {
|
||||||
|
newType = Maneuver.TYPE_TURN_SLIGHT_RIGHT
|
||||||
|
}
|
||||||
|
|
||||||
|
ManeuverType.StayRight.value -> {
|
||||||
|
newType = Maneuver.TYPE_KEEP_RIGHT
|
||||||
|
}
|
||||||
|
|
||||||
|
ManeuverType.StayLeft.value -> {
|
||||||
|
newType = Maneuver.TYPE_KEEP_LEFT
|
||||||
|
}
|
||||||
|
|
||||||
|
ManeuverType.RoundaboutEnter.value -> {
|
||||||
|
newType = Maneuver.TYPE_ROUNDABOUT_ENTER_CCW
|
||||||
|
}
|
||||||
|
|
||||||
|
ManeuverType.RoundaboutExit.value -> {
|
||||||
|
newType = Maneuver.TYPE_ROUNDABOUT_EXIT_CCW
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return newType
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
package com.kouros.navigation.model
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.res.Configuration
|
||||||
|
import androidx.compose.foundation.isSystemInDarkTheme
|
||||||
|
import com.kouros.data.R
|
||||||
|
import org.maplibre.compose.style.BaseStyle
|
||||||
|
|
||||||
|
class BaseStyleModel {
|
||||||
|
|
||||||
|
fun isDarkTheme(context: Context): Boolean {
|
||||||
|
return context.resources.configuration.uiMode and
|
||||||
|
Configuration.UI_MODE_NIGHT_MASK == Configuration.UI_MODE_NIGHT_YES
|
||||||
|
}
|
||||||
|
|
||||||
|
fun readStyle(context: Context, darkModeSettings: Int, isCarDarkMode: Boolean): BaseStyle.Json {
|
||||||
|
println("BaseStyle ${isDarkTheme(context)}")
|
||||||
|
val liberty = when(darkModeSettings) {
|
||||||
|
0 -> context.resources.openRawResource(R.raw.liberty)
|
||||||
|
1 -> context.resources.openRawResource(R.raw.liberty_night)
|
||||||
|
else -> {
|
||||||
|
if (isDarkTheme(context) || isCarDarkMode) {
|
||||||
|
context.resources.openRawResource(R.raw.liberty_night)
|
||||||
|
} else {
|
||||||
|
context.resources.openRawResource(R.raw.liberty)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val libertyString = liberty.bufferedReader().use { it.readText() }
|
||||||
|
val baseStyle = BaseStyle.Json(libertyString)
|
||||||
|
return baseStyle
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,26 +1,31 @@
|
|||||||
package com.kouros.navigation.model
|
package com.kouros.navigation.model
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
import android.graphics.Bitmap
|
||||||
|
import android.graphics.BitmapFactory
|
||||||
|
import android.graphics.Canvas
|
||||||
|
import android.graphics.Matrix
|
||||||
import android.location.Location
|
import android.location.Location
|
||||||
import androidx.car.app.navigation.model.Maneuver
|
import androidx.car.app.navigation.model.Maneuver
|
||||||
import androidx.car.app.navigation.model.Step
|
import androidx.car.app.navigation.model.Step
|
||||||
|
import androidx.core.graphics.createBitmap
|
||||||
|
import androidx.core.graphics.drawable.IconCompat
|
||||||
import com.kouros.data.R
|
import com.kouros.data.R
|
||||||
import com.kouros.navigation.data.Constants.NEXT_STEP_THRESHOLD
|
import com.kouros.navigation.data.Constants.NEXT_STEP_THRESHOLD
|
||||||
import com.kouros.navigation.data.Constants.ROUTE_ENGINE
|
import com.kouros.navigation.data.Constants.ROUTING_ENGINE
|
||||||
import com.kouros.navigation.data.valhalla.ManeuverType
|
|
||||||
import com.kouros.navigation.data.Place
|
import com.kouros.navigation.data.Place
|
||||||
import com.kouros.navigation.data.Route
|
import com.kouros.navigation.data.Route
|
||||||
import com.kouros.navigation.data.RouteEngine
|
|
||||||
import com.kouros.navigation.data.StepData
|
import com.kouros.navigation.data.StepData
|
||||||
|
import com.kouros.navigation.data.route.Intersection
|
||||||
|
import com.kouros.navigation.data.route.Lane
|
||||||
import com.kouros.navigation.data.route.Leg
|
import com.kouros.navigation.data.route.Leg
|
||||||
|
import com.kouros.navigation.data.valhalla.ManeuverType
|
||||||
|
import com.kouros.navigation.utils.GeoUtils.createCenterLocation
|
||||||
import com.kouros.navigation.utils.NavigationUtils.getIntKeyValue
|
import com.kouros.navigation.utils.NavigationUtils.getIntKeyValue
|
||||||
import com.kouros.navigation.utils.location
|
import com.kouros.navigation.utils.location
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.DelicateCoroutinesApi
|
import kotlinx.coroutines.DelicateCoroutinesApi
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.GlobalScope
|
|
||||||
import kotlinx.coroutines.coroutineScope
|
|
||||||
import kotlinx.coroutines.invoke
|
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
@@ -37,6 +42,9 @@ open class RouteModel() {
|
|||||||
val lastSpeedLocation: Location = location(0.0, 0.0),
|
val lastSpeedLocation: Location = location(0.0, 0.0),
|
||||||
val lastSpeedIndex: Int = 0,
|
val lastSpeedIndex: Int = 0,
|
||||||
val maxSpeed: Int = 0,
|
val maxSpeed: Int = 0,
|
||||||
|
val location: Location = location(0.0, 0.0),
|
||||||
|
val lastLocation: Location = location(0.0, 0.0),
|
||||||
|
val bearing : Float = 0F
|
||||||
)
|
)
|
||||||
|
|
||||||
var routeState = RouteState()
|
var routeState = RouteState()
|
||||||
@@ -52,11 +60,14 @@ open class RouteModel() {
|
|||||||
get() = routeState.route!!.legs!!.first()
|
get() = routeState.route!!.legs!!.first()
|
||||||
|
|
||||||
fun startNavigation(routeString: String, context: Context) {
|
fun startNavigation(routeString: String, context: Context) {
|
||||||
val routeEngine = getIntKeyValue(context = context, ROUTE_ENGINE)
|
val routeEngine = getIntKeyValue(context = context, ROUTING_ENGINE)
|
||||||
val newRoute = Route.Builder()
|
var newRoute = Route.Builder()
|
||||||
.routeEngine(routeEngine)
|
.routeEngine(routeEngine)
|
||||||
.route(routeString)
|
.route(routeString)
|
||||||
.build()
|
.build()
|
||||||
|
// TODO:
|
||||||
|
newRoute = newRoute.copy(centerLocation = createCenterLocation(newRoute.routeGeoJson))
|
||||||
|
println("Route ${newRoute.centerLocation}")
|
||||||
this.routeState = routeState.copy(
|
this.routeState = routeState.copy(
|
||||||
route = newRoute,
|
route = newRoute,
|
||||||
isNavigating = true
|
isNavigating = true
|
||||||
@@ -74,8 +85,10 @@ open class RouteModel() {
|
|||||||
|
|
||||||
@OptIn(DelicateCoroutinesApi::class)
|
@OptIn(DelicateCoroutinesApi::class)
|
||||||
fun updateLocation(location: Location, viewModel: ViewModel) {
|
fun updateLocation(location: Location, viewModel: ViewModel) {
|
||||||
|
routeState = routeState.copy(location = location)
|
||||||
findStep(location)
|
findStep(location)
|
||||||
updateSpeedLimit(location, viewModel)
|
updateSpeedLimit(location, viewModel)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun findStep(location: Location) {
|
private fun findStep(location: Location) {
|
||||||
@@ -89,6 +102,9 @@ open class RouteModel() {
|
|||||||
nearestDistance = distance
|
nearestDistance = distance
|
||||||
route.currentStep = step.index
|
route.currentStep = step.index
|
||||||
step.waypointIndex = wayIndex
|
step.waypointIndex = wayIndex
|
||||||
|
step.wayPointLocation = location(waypoint[0], waypoint[1])
|
||||||
|
val bearing = routeState.lastLocation.bearingTo(location)
|
||||||
|
this.routeState = routeState.copy(lastLocation = location, bearing = bearing)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (nearestDistance == 0F) {
|
if (nearestDistance == 0F) {
|
||||||
@@ -100,7 +116,19 @@ open class RouteModel() {
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
//println("Current Index ${route.currentStep} WayPoint: ${route.currentStep().waypointIndex}")
|
}
|
||||||
|
|
||||||
|
private fun currentIntersection(location: Location): Intersection {
|
||||||
|
var inter = Intersection()
|
||||||
|
var nearestDistance = 100000.0f
|
||||||
|
route.currentStep().intersection.forEach {
|
||||||
|
val distance = location.distanceTo(location(it.location[0], it.location[1]))
|
||||||
|
if (distance < nearestDistance) {
|
||||||
|
nearestDistance = distance
|
||||||
|
inter = it
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return inter
|
||||||
}
|
}
|
||||||
|
|
||||||
fun updateSpeedLimit(location: Location, viewModel: ViewModel) = runBlocking {
|
fun updateSpeedLimit(location: Location, viewModel: ViewModel) = runBlocking {
|
||||||
@@ -133,29 +161,31 @@ open class RouteModel() {
|
|||||||
var maneuverType = if (hasArrived(currentStep.maneuver.type)) {
|
var maneuverType = if (hasArrived(currentStep.maneuver.type)) {
|
||||||
currentStep.maneuver.type
|
currentStep.maneuver.type
|
||||||
} else {
|
} else {
|
||||||
ManeuverType.None.value
|
Maneuver.TYPE_STRAIGHT
|
||||||
}
|
}
|
||||||
// Get the single, correct maneuver for this state
|
// Get the single, correct maneuver for this state
|
||||||
val relevantManeuver = if (shouldAdvance) {
|
val relevantStep = if (shouldAdvance) {
|
||||||
route.nextStep() // This advances the route's state
|
route.nextStep() // This advances the route's state
|
||||||
} else {
|
} else {
|
||||||
route.currentStep()
|
route.currentStep()
|
||||||
}
|
}
|
||||||
// Safely get the street name from the maneuver
|
// Safely get the street name from the maneuver
|
||||||
val streetName = relevantManeuver.name
|
val streetName = relevantStep.name
|
||||||
if (shouldAdvance) {
|
if (shouldAdvance) {
|
||||||
maneuverType = relevantManeuver.maneuver.type
|
maneuverType = relevantStep.maneuver.type
|
||||||
}
|
}
|
||||||
val maneuverIconPair = maneuverIcon(maneuverType)
|
val maneuverIconPair = maneuverIcon(maneuverType)
|
||||||
routeState = routeState.copy(maneuverType = maneuverIconPair.first)
|
routeState = routeState.copy(maneuverType = maneuverIconPair.first)
|
||||||
// Construct and return the final StepData object
|
// Construct and return the final StepData object
|
||||||
|
val intersection = currentIntersection(routeState.location)
|
||||||
return StepData(
|
return StepData(
|
||||||
streetName,
|
streetName,
|
||||||
distanceToNextStep,
|
distanceToNextStep,
|
||||||
maneuverIconPair.first,
|
maneuverIconPair.first,
|
||||||
maneuverIconPair.second,
|
maneuverIconPair.second,
|
||||||
arrivalTime(),
|
arrivalTime(),
|
||||||
travelLeftDistance()
|
travelLeftDistance(),
|
||||||
|
intersection.lane
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -168,6 +198,7 @@ open class RouteModel() {
|
|||||||
when (distanceLeft) {
|
when (distanceLeft) {
|
||||||
in 0.0..NEXT_STEP_THRESHOLD -> {
|
in 0.0..NEXT_STEP_THRESHOLD -> {
|
||||||
}
|
}
|
||||||
|
|
||||||
else -> {
|
else -> {
|
||||||
if (step.name.isNotEmpty()) {
|
if (step.name.isNotEmpty()) {
|
||||||
text = step.name
|
text = step.name
|
||||||
@@ -243,68 +274,49 @@ open class RouteModel() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun maneuverIcon(routeManeuverType: Int): (Pair<Int, Int>) {
|
fun maneuverIcon(routeManeuverType: Int): (Pair<Int, Int>) {
|
||||||
var type = Maneuver.TYPE_DEPART
|
|
||||||
var currentTurnIcon = R.drawable.ic_turn_name_change
|
var currentTurnIcon = R.drawable.ic_turn_name_change
|
||||||
when (routeManeuverType) {
|
when (routeManeuverType) {
|
||||||
ManeuverType.None.value -> {
|
Maneuver.TYPE_STRAIGHT -> {
|
||||||
type = Maneuver.TYPE_STRAIGHT
|
|
||||||
currentTurnIcon = R.drawable.ic_turn_name_change
|
currentTurnIcon = R.drawable.ic_turn_name_change
|
||||||
}
|
}
|
||||||
|
|
||||||
ManeuverType.Destination.value,
|
Maneuver.TYPE_DESTINATION,
|
||||||
ManeuverType.DestinationRight.value,
|
|
||||||
ManeuverType.DestinationLeft.value,
|
|
||||||
-> {
|
-> {
|
||||||
type = Maneuver.TYPE_DESTINATION
|
|
||||||
currentTurnIcon = R.drawable.ic_turn_destination
|
currentTurnIcon = R.drawable.ic_turn_destination
|
||||||
}
|
}
|
||||||
|
|
||||||
ManeuverType.Right.value -> {
|
Maneuver.TYPE_TURN_NORMAL_RIGHT -> {
|
||||||
type = Maneuver.TYPE_TURN_NORMAL_RIGHT
|
|
||||||
currentTurnIcon = R.drawable.ic_turn_normal_right
|
currentTurnIcon = R.drawable.ic_turn_normal_right
|
||||||
}
|
}
|
||||||
|
|
||||||
ManeuverType.Left.value -> {
|
Maneuver.TYPE_TURN_NORMAL_LEFT -> {
|
||||||
type = Maneuver.TYPE_TURN_NORMAL_LEFT
|
|
||||||
currentTurnIcon = R.drawable.ic_turn_normal_left
|
currentTurnIcon = R.drawable.ic_turn_normal_left
|
||||||
}
|
}
|
||||||
|
|
||||||
ManeuverType.RampRight.value -> {
|
Maneuver.TYPE_OFF_RAMP_SLIGHT_RIGHT -> {
|
||||||
type = Maneuver.TYPE_OFF_RAMP_SLIGHT_RIGHT
|
|
||||||
currentTurnIcon = R.drawable.ic_turn_slight_right
|
currentTurnIcon = R.drawable.ic_turn_slight_right
|
||||||
}
|
}
|
||||||
|
|
||||||
ManeuverType.RampLeft.value -> {
|
Maneuver.TYPE_TURN_SLIGHT_RIGHT -> {
|
||||||
type = Maneuver.TYPE_TURN_NORMAL_LEFT
|
|
||||||
currentTurnIcon = R.drawable.ic_turn_normal_left
|
|
||||||
}
|
|
||||||
|
|
||||||
ManeuverType.ExitRight.value -> {
|
|
||||||
type = Maneuver.TYPE_TURN_SLIGHT_RIGHT
|
|
||||||
currentTurnIcon = R.drawable.ic_turn_slight_right
|
currentTurnIcon = R.drawable.ic_turn_slight_right
|
||||||
}
|
}
|
||||||
|
|
||||||
ManeuverType.StayRight.value -> {
|
Maneuver.TYPE_KEEP_RIGHT -> {
|
||||||
type = Maneuver.TYPE_KEEP_RIGHT
|
|
||||||
currentTurnIcon = R.drawable.ic_turn_name_change
|
currentTurnIcon = R.drawable.ic_turn_name_change
|
||||||
}
|
}
|
||||||
|
Maneuver.TYPE_KEEP_LEFT -> {
|
||||||
ManeuverType.StayLeft.value -> {
|
|
||||||
type = Maneuver.TYPE_KEEP_LEFT
|
|
||||||
currentTurnIcon = R.drawable.ic_turn_name_change
|
currentTurnIcon = R.drawable.ic_turn_name_change
|
||||||
}
|
}
|
||||||
|
Maneuver.TYPE_ROUNDABOUT_ENTER_CCW -> {
|
||||||
ManeuverType.RoundaboutEnter.value -> {
|
|
||||||
type = Maneuver.TYPE_ROUNDABOUT_ENTER_CCW
|
|
||||||
currentTurnIcon = R.drawable.ic_roundabout_ccw
|
currentTurnIcon = R.drawable.ic_roundabout_ccw
|
||||||
}
|
}
|
||||||
|
|
||||||
ManeuverType.RoundaboutExit.value -> {
|
Maneuver.TYPE_ROUNDABOUT_EXIT_CCW -> {
|
||||||
type = Maneuver.TYPE_ROUNDABOUT_EXIT_CCW
|
|
||||||
currentTurnIcon = R.drawable.ic_roundabout_ccw
|
currentTurnIcon = R.drawable.ic_roundabout_ccw
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return Pair(type, currentTurnIcon)
|
return Pair(routeManeuverType, currentTurnIcon)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun isNavigating(): Boolean {
|
fun isNavigating(): Boolean {
|
||||||
@@ -317,4 +329,79 @@ open class RouteModel() {
|
|||||||
|| type == ManeuverType.Destination.value
|
|| type == ManeuverType.Destination.value
|
||||||
|| type == ManeuverType.DestinationLeft.value
|
|| type == ManeuverType.DestinationLeft.value
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun createLaneIcon(context: Context, stepData: StepData): IconCompat {
|
||||||
|
val bitmaps = mutableListOf<Bitmap>()
|
||||||
|
stepData.lane.forEach {
|
||||||
|
if (it.indications.isNotEmpty()) {
|
||||||
|
it.indications.forEach { it2 ->
|
||||||
|
val resource = laneToResource(it2, it, stepData)
|
||||||
|
if (it2 != "none") {
|
||||||
|
println("Direction $resource")
|
||||||
|
if (resource.isNotEmpty()) {
|
||||||
|
val id = resourceId( resource);
|
||||||
|
val bitMap = BitmapFactory.decodeResource(context.resources, id)
|
||||||
|
bitmaps.add(bitMap)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return IconCompat.createWithBitmap(overlay(bitmaps = bitmaps))
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fun overlay(bitmaps: List<Bitmap>): Bitmap {
|
||||||
|
val matrix = Matrix()
|
||||||
|
if (bitmaps.size == 1) {
|
||||||
|
return bitmaps.first()
|
||||||
|
}
|
||||||
|
val bmOverlay = createBitmap(
|
||||||
|
bitmaps.first().getWidth() * bitmaps.size,
|
||||||
|
bitmaps.first().getHeight(),
|
||||||
|
bitmaps.first().getConfig()!!
|
||||||
|
)
|
||||||
|
val canvas = Canvas(bmOverlay)
|
||||||
|
canvas.drawBitmap(bitmaps.first(), matrix, null)
|
||||||
|
var i = 0
|
||||||
|
bitmaps.forEach {
|
||||||
|
if (i > 0) {
|
||||||
|
matrix.setTranslate(i * 40F, 0F)
|
||||||
|
canvas.drawBitmap(it, matrix, null)
|
||||||
|
}
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
return bmOverlay
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun laneToResource(direction: String, lane: Lane, stepData: StepData): String {
|
||||||
|
println("Maneuver ${stepData.maneuverType}")
|
||||||
|
return when (val direction = direction.replace(" ", "_")) {
|
||||||
|
"left" -> if (stepData.maneuverType == Maneuver.TYPE_TURN_NORMAL_LEFT) "${direction}_valid" else "${direction}_not_valid"
|
||||||
|
"right" -> if (stepData.maneuverType == Maneuver.TYPE_TURN_NORMAL_RIGHT) "${direction}_valid" else "${direction}_not_valid"
|
||||||
|
"straight" -> if (stepData.maneuverType == Maneuver.TYPE_STRAIGHT) "${direction}_valid" else "${direction}_not_valid"
|
||||||
|
"slight_right" -> if (stepData.maneuverType == Maneuver.TYPE_TURN_SLIGHT_RIGHT) "${direction}_valid" else "${direction}_not_valid"
|
||||||
|
"slight_left" -> if (stepData.maneuverType == Maneuver.TYPE_TURN_SLIGHT_LEFT) "${direction}_valid" else "${direction}_not_valid"
|
||||||
|
else -> {""}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun resourceId(
|
||||||
|
variableName: String,
|
||||||
|
): Int {
|
||||||
|
return when(variableName) {
|
||||||
|
"left_not_valid" -> R.drawable.left_not_valid
|
||||||
|
"left_valid" -> R.drawable.left_valid
|
||||||
|
"left_valid_right_not_valid" -> R.drawable.left_valid_right_not_valid
|
||||||
|
"right_not_valid" -> R.drawable.right_not_valid
|
||||||
|
"right_valid" -> R.drawable.right_valid
|
||||||
|
"slight_right_not_valid" -> R.drawable.slight_right_not_valid
|
||||||
|
"slight_right_valid" -> R.drawable.slight_right_valid
|
||||||
|
"straight_not_valid" -> R.drawable.straight_not_valid
|
||||||
|
"straight_not_valid_right_valid" -> R.drawable.straight_not_valid_right_valid
|
||||||
|
"straight_valid" -> R.drawable.straight_valid
|
||||||
|
else -> {R.drawable.ic_close_white_24dp}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -67,7 +67,12 @@ class ViewModel(private val repository: NavigationRepository) : ViewModel() {
|
|||||||
MutableLiveData()
|
MutableLiveData()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun loadRecentPlace(location: Location) {
|
val routingEngine: MutableLiveData<Int> by lazy {
|
||||||
|
MutableLiveData()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fun loadRecentPlace(location: Location, context: Context) {
|
||||||
viewModelScope.launch(Dispatchers.IO) {
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
try {
|
try {
|
||||||
val placeBox = boxStore.boxFor(Place::class)
|
val placeBox = boxStore.boxFor(Place::class)
|
||||||
@@ -79,12 +84,12 @@ class ViewModel(private val repository: NavigationRepository) : ViewModel() {
|
|||||||
query.close()
|
query.close()
|
||||||
for (place in results) {
|
for (place in results) {
|
||||||
val plLocation = location(place.longitude, place.latitude)
|
val plLocation = location(place.longitude, place.latitude)
|
||||||
//val distance = repository.getRouteDistance(location, plLocation, SearchFilter())
|
val distance = repository.getRouteDistance(location, plLocation, SearchFilter(), context)
|
||||||
//place.distance = distance.toFloat()
|
place.distance = distance.toFloat()
|
||||||
//if (place.distance == 0F) {
|
if (place.distance > 1F) {
|
||||||
recentPlace.postValue(place)
|
recentPlace.postValue(place)
|
||||||
return@launch
|
return@launch
|
||||||
//}
|
}
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
e.printStackTrace()
|
e.printStackTrace()
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import android.content.Context
|
|||||||
import android.location.Location
|
import android.location.Location
|
||||||
import android.location.LocationManager
|
import android.location.LocationManager
|
||||||
import androidx.core.content.edit
|
import androidx.core.content.edit
|
||||||
import com.kouros.navigation.data.Constants.ROUTE_ENGINE
|
import com.kouros.navigation.data.Constants.ROUTING_ENGINE
|
||||||
import com.kouros.navigation.data.Constants.SHARED_PREF_KEY
|
import com.kouros.navigation.data.Constants.SHARED_PREF_KEY
|
||||||
import com.kouros.navigation.data.RouteEngine
|
import com.kouros.navigation.data.RouteEngine
|
||||||
import com.kouros.navigation.data.osrm.OsrmRepository
|
import com.kouros.navigation.data.osrm.OsrmRepository
|
||||||
@@ -26,7 +26,7 @@ import kotlin.time.Duration.Companion.seconds
|
|||||||
object NavigationUtils {
|
object NavigationUtils {
|
||||||
|
|
||||||
fun getRouteEngine(context: Context): ViewModel {
|
fun getRouteEngine(context: Context): ViewModel {
|
||||||
val routeEngine = getIntKeyValue(context = context, ROUTE_ENGINE)
|
val routeEngine = getIntKeyValue(context = context, ROUTING_ENGINE)
|
||||||
return when (routeEngine) {
|
return when (routeEngine) {
|
||||||
RouteEngine.VALHALLA.ordinal -> ViewModel(ValhallaRepository())
|
RouteEngine.VALHALLA.ordinal -> ViewModel(ValhallaRepository())
|
||||||
else -> ViewModel(OsrmRepository())
|
else -> ViewModel(OsrmRepository())
|
||||||
|
|||||||
BIN
common/data/src/main/res/drawable/lanes.png
Normal file
|
After Width: | Height: | Size: 1.0 KiB |
BIN
common/data/src/main/res/drawable/lanes_leftleftssr.png
Normal file
|
After Width: | Height: | Size: 5.7 KiB |
BIN
common/data/src/main/res/drawable/lanes_ssr.png
Normal file
|
After Width: | Height: | Size: 1.2 KiB |
BIN
common/data/src/main/res/drawable/left_not_valid.png
Normal file
|
After Width: | Height: | Size: 4.8 KiB |
BIN
common/data/src/main/res/drawable/left_valid.png
Normal file
|
After Width: | Height: | Size: 914 B |
BIN
common/data/src/main/res/drawable/left_valid_right_not_valid.png
Normal file
|
After Width: | Height: | Size: 883 B |
BIN
common/data/src/main/res/drawable/right_not_valid.png
Normal file
|
After Width: | Height: | Size: 4.8 KiB |
BIN
common/data/src/main/res/drawable/right_valid.png
Normal file
|
After Width: | Height: | Size: 888 B |
BIN
common/data/src/main/res/drawable/slight_right_not_valid.png
Normal file
|
After Width: | Height: | Size: 4.8 KiB |
BIN
common/data/src/main/res/drawable/slight_right_valid.png
Normal file
|
After Width: | Height: | Size: 888 B |
10
common/data/src/main/res/drawable/speed_camera_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="M701,600L614,550L754,446L840,496L701,600ZM160,800L160,720L360,720Q360,720 360,720Q360,720 360,720L360,482L240,413Q211,396 202.5,364.5Q194,333 211,304L271,200Q288,171 319.5,162.5Q351,154 380,171L761,391L517,573L440,529L440,720Q440,753 416.5,776.5Q393,800 360,800L160,800Z"/>
|
||||||
|
</vector>
|
||||||
BIN
common/data/src/main/res/drawable/straight_not_valid.png
Normal file
|
After Width: | Height: | Size: 4.8 KiB |
|
After Width: | Height: | Size: 895 B |
BIN
common/data/src/main/res/drawable/straight_valid.png
Normal file
|
After Width: | Height: | Size: 723 B |
@@ -30,4 +30,7 @@
|
|||||||
<string name="reject_action_title">Reject</string>
|
<string name="reject_action_title">Reject</string>
|
||||||
<string name="ok_action_title">OK</string>
|
<string name="ok_action_title">OK</string>
|
||||||
<string name="search_action_title">Search</string>
|
<string name="search_action_title">Search</string>
|
||||||
|
<string name="valhalla">Valhalla</string>
|
||||||
|
<string name="osrm">Osrm</string>
|
||||||
|
<string name="routing_engine">Routing engine</string>
|
||||||
</resources>
|
</resources>
|
||||||
@@ -13,7 +13,7 @@ junitVersion = "1.3.0"
|
|||||||
espressoCore = "3.7.0"
|
espressoCore = "3.7.0"
|
||||||
kotlinxSerializationJson = "1.9.0"
|
kotlinxSerializationJson = "1.9.0"
|
||||||
lifecycleRuntimeKtx = "2.10.0"
|
lifecycleRuntimeKtx = "2.10.0"
|
||||||
composeBom = "2025.12.00"
|
composeBom = "2025.12.01"
|
||||||
appcompat = "1.7.1"
|
appcompat = "1.7.1"
|
||||||
material = "1.13.0"
|
material = "1.13.0"
|
||||||
carApp = "1.7.0"
|
carApp = "1.7.0"
|
||||||
@@ -42,6 +42,7 @@ foundationLayout = "1.10.0"
|
|||||||
|
|
||||||
[libraries]
|
[libraries]
|
||||||
android-sdk-turf = { module = "org.maplibre.gl:android-sdk-turf", version.ref = "androidSdkTurf" }
|
android-sdk-turf = { module = "org.maplibre.gl:android-sdk-turf", version.ref = "androidSdkTurf" }
|
||||||
|
androidx-app-projected = { module = "androidx.car.app:app-projected" }
|
||||||
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
|
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
|
||||||
gradle = { module = "com.android.tools.build:gradle", version.ref = "gradle" }
|
gradle = { module = "com.android.tools.build:gradle", version.ref = "gradle" }
|
||||||
junit = { group = "junit", name = "junit", version.ref = "junit" }
|
junit = { group = "junit", name = "junit", version.ref = "junit" }
|
||||||
|
|||||||