Nominatim
@@ -16,7 +16,7 @@
|
|||||||
android:label="@string/app_name"
|
android:label="@string/app_name"
|
||||||
android:roundIcon="@mipmap/ic_launcher_round"
|
android:roundIcon="@mipmap/ic_launcher_round"
|
||||||
android:supportsRtl="true"
|
android:supportsRtl="true"
|
||||||
android:theme="@style/Theme.Places">
|
android:theme="@style/Theme.Navigation">
|
||||||
|
|
||||||
<meta-data
|
<meta-data
|
||||||
android:name="com.google.android.gms.car.application"
|
android:name="com.google.android.gms.car.application"
|
||||||
@@ -25,7 +25,7 @@
|
|||||||
<activity
|
<activity
|
||||||
android:name="com.kouros.navigation.MainActivity"
|
android:name="com.kouros.navigation.MainActivity"
|
||||||
android:exported="true"
|
android:exported="true"
|
||||||
android:theme="@style/Theme.Places">
|
android:theme="@style/Theme.Navigation">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.MAIN" />
|
<action android:name="android.intent.action.MAIN" />
|
||||||
<category android:name="android.intent.category.LAUNCHER" />
|
<category android:name="android.intent.category.LAUNCHER" />
|
||||||
|
|||||||
@@ -48,12 +48,13 @@ import androidx.compose.ui.unit.dp
|
|||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
import androidx.lifecycle.MutableLiveData
|
import androidx.lifecycle.MutableLiveData
|
||||||
import androidx.lifecycle.Observer
|
import androidx.lifecycle.Observer
|
||||||
import com.example.places.ui.theme.PlacesTheme
|
import com.kouros.navigation.ui.theme.NavigationTheme
|
||||||
import com.google.accompanist.permissions.ExperimentalPermissionsApi
|
import com.google.accompanist.permissions.ExperimentalPermissionsApi
|
||||||
import com.google.accompanist.permissions.rememberMultiplePermissionsState
|
import com.google.accompanist.permissions.rememberMultiplePermissionsState
|
||||||
import com.kouros.android.cars.carappservice.R
|
import com.kouros.android.cars.carappservice.R
|
||||||
import com.kouros.navigation.car.BuildingLayer
|
import com.kouros.navigation.car.BuildingLayer
|
||||||
import com.kouros.navigation.car.Puck
|
import com.kouros.navigation.car.Puck
|
||||||
|
import com.kouros.navigation.car.PuckState
|
||||||
import com.kouros.navigation.car.RouteLayer
|
import com.kouros.navigation.car.RouteLayer
|
||||||
|
|
||||||
import com.kouros.navigation.data.Category
|
import com.kouros.navigation.data.Category
|
||||||
@@ -68,15 +69,11 @@ import com.kouros.navigation.utils.NavigationUtils.snapLocation
|
|||||||
import com.kouros.navigation.utils.calculateZoom
|
import com.kouros.navigation.utils.calculateZoom
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.coroutineScope
|
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import org.koin.androidx.compose.koinViewModel
|
import org.koin.androidx.compose.koinViewModel
|
||||||
import org.maplibre.compose.camera.CameraPosition
|
import org.maplibre.compose.camera.CameraPosition
|
||||||
import org.maplibre.compose.camera.rememberCameraState
|
import org.maplibre.compose.camera.rememberCameraState
|
||||||
import org.maplibre.compose.expressions.dsl.const
|
|
||||||
import org.maplibre.compose.layers.FillLayer
|
|
||||||
import org.maplibre.compose.layers.LineLayer
|
|
||||||
import org.maplibre.compose.location.DesiredAccuracy
|
import org.maplibre.compose.location.DesiredAccuracy
|
||||||
import org.maplibre.compose.location.LocationPuck
|
import org.maplibre.compose.location.LocationPuck
|
||||||
import org.maplibre.compose.location.LocationPuckColors
|
import org.maplibre.compose.location.LocationPuckColors
|
||||||
@@ -85,9 +82,7 @@ import org.maplibre.compose.location.LocationTrackingEffect
|
|||||||
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.map.MaplibreMap
|
import org.maplibre.compose.map.MaplibreMap
|
||||||
import org.maplibre.compose.sources.GeoJsonData
|
|
||||||
import org.maplibre.compose.sources.getBaseSource
|
import org.maplibre.compose.sources.getBaseSource
|
||||||
import org.maplibre.compose.sources.rememberGeoJsonSource
|
|
||||||
import org.maplibre.compose.style.BaseStyle
|
import org.maplibre.compose.style.BaseStyle
|
||||||
import org.maplibre.spatialk.geojson.Position
|
import org.maplibre.spatialk.geojson.Position
|
||||||
import kotlin.time.Duration.Companion.seconds
|
import kotlin.time.Duration.Companion.seconds
|
||||||
@@ -143,7 +138,7 @@ class MainActivity : ComponentActivity() {
|
|||||||
val scope = rememberCoroutineScope()
|
val scope = rememberCoroutineScope()
|
||||||
val snackbarHostState = remember { SnackbarHostState() }
|
val snackbarHostState = remember { SnackbarHostState() }
|
||||||
|
|
||||||
PlacesTheme {
|
NavigationTheme {
|
||||||
ModalNavigationDrawer(
|
ModalNavigationDrawer(
|
||||||
drawerContent = {
|
drawerContent = {
|
||||||
ModalDrawerSheet {
|
ModalDrawerSheet {
|
||||||
@@ -271,7 +266,7 @@ class MainActivity : ComponentActivity() {
|
|||||||
@Composable
|
@Composable
|
||||||
fun MapView() {
|
fun MapView() {
|
||||||
val locationProvider = rememberDefaultLocationProvider(
|
val locationProvider = rememberDefaultLocationProvider(
|
||||||
updateInterval = 0.1.seconds,
|
updateInterval = 0.5.seconds,
|
||||||
desiredAccuracy = DesiredAccuracy.Highest
|
desiredAccuracy = DesiredAccuracy.Highest
|
||||||
)
|
)
|
||||||
val userLocationState = rememberUserLocationState(locationProvider)
|
val userLocationState = rememberUserLocationState(locationProvider)
|
||||||
@@ -304,7 +299,7 @@ class MainActivity : ComponentActivity() {
|
|||||||
baseStyle = BaseStyle.Uri(Constants.STYLE),
|
baseStyle = BaseStyle.Uri(Constants.STYLE),
|
||||||
) {
|
) {
|
||||||
getBaseSource(id = "openmaptiles")?.let { tiles ->
|
getBaseSource(id = "openmaptiles")?.let { tiles ->
|
||||||
if (!getBooleanKeyValue(context = applicationContext, SHOW_THREED_BUILDING) && Constants.STYLE.contains("liberty")) {
|
if (!getBooleanKeyValue(context = applicationContext, SHOW_THREED_BUILDING)) {
|
||||||
BuildingLayer(tiles)
|
BuildingLayer(tiles)
|
||||||
}
|
}
|
||||||
RouteLayer(route, "")
|
RouteLayer(route, "")
|
||||||
@@ -313,20 +308,8 @@ class MainActivity : ComponentActivity() {
|
|||||||
val location = Location(LocationManager.GPS_PROVIDER)
|
val location = Location(LocationManager.GPS_PROVIDER)
|
||||||
location.longitude = userLocationState.location!!.position.longitude
|
location.longitude = userLocationState.location!!.position.longitude
|
||||||
location.latitude = userLocationState.location!!.position.latitude
|
location.latitude = userLocationState.location!!.position.latitude
|
||||||
Puck(cameraState, location,)
|
PuckState(cameraState, userLocationState,)
|
||||||
}
|
}
|
||||||
LocationPuck(
|
|
||||||
idPrefix = "user-location1",
|
|
||||||
locationState = userLocationState,
|
|
||||||
cameraState = cameraState,
|
|
||||||
accuracyThreshold = 10f,
|
|
||||||
showBearing = false,
|
|
||||||
sizes = LocationPuckSizes(dotRadius = 10.dp),
|
|
||||||
colors = LocationPuckColors(
|
|
||||||
dotFillColorCurrentLocation = Color.Cyan,
|
|
||||||
accuracyStrokeColor = Color.Green
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
LocationTrackingEffect(
|
LocationTrackingEffect(
|
||||||
|
|||||||
@@ -2,7 +2,8 @@ package com.kouros.navigation
|
|||||||
|
|
||||||
import android.app.Application
|
import android.app.Application
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import com.example.places.di.appModule
|
import com.kouros.navigation.data.ObjectBox
|
||||||
|
import com.kouros.navigation.di.appModule
|
||||||
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
|
||||||
@@ -11,6 +12,7 @@ class MainApplication : Application() {
|
|||||||
|
|
||||||
override fun onCreate() {
|
override fun onCreate() {
|
||||||
super.onCreate()
|
super.onCreate()
|
||||||
|
ObjectBox.init(applicationContext);
|
||||||
appContext = applicationContext
|
appContext = applicationContext
|
||||||
startKoin {
|
startKoin {
|
||||||
androidLogger(Level.DEBUG)
|
androidLogger(Level.DEBUG)
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
package com.example.places.di
|
package com.kouros.navigation.di
|
||||||
|
|
||||||
import com.kouros.navigation.data.NavigationRepository
|
import com.kouros.navigation.data.NavigationRepository
|
||||||
import com.kouros.navigation.model.ViewModel
|
import com.kouros.navigation.model.ViewModel
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
package com.example.places.ui.theme
|
package com.kouros.navigation.ui.theme
|
||||||
|
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
package com.example.places.ui.theme
|
package com.kouros.navigation.ui.theme
|
||||||
|
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import androidx.compose.foundation.isSystemInDarkTheme
|
import androidx.compose.foundation.isSystemInDarkTheme
|
||||||
@@ -33,7 +33,7 @@ private val LightColorScheme = lightColorScheme(
|
|||||||
)
|
)
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun PlacesTheme(
|
fun NavigationTheme(
|
||||||
darkTheme: Boolean = isSystemInDarkTheme(),
|
darkTheme: Boolean = isSystemInDarkTheme(),
|
||||||
// Dynamic color is available on Android 12+
|
// Dynamic color is available on Android 12+
|
||||||
dynamicColor: Boolean = true,
|
dynamicColor: Boolean = true,
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
package com.example.places.ui.theme
|
package com.kouros.navigation.ui.theme
|
||||||
|
|
||||||
import androidx.compose.material3.Typography
|
import androidx.compose.material3.Typography
|
||||||
import androidx.compose.ui.text.TextStyle
|
import androidx.compose.ui.text.TextStyle
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources>
|
<resources>
|
||||||
|
|
||||||
<style name="Theme.Places" parent="android:Theme.Material.Light.NoActionBar" />
|
<style name="Theme.Navigation" parent="android:Theme.Material.Light.NoActionBar" />
|
||||||
</resources>
|
</resources>
|
||||||
1
automotive/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
/build
|
||||||
47
automotive/build.gradle.kts
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
plugins {
|
||||||
|
alias(libs.plugins.android.application)
|
||||||
|
alias(libs.plugins.kotlin.android)
|
||||||
|
}
|
||||||
|
|
||||||
|
android {
|
||||||
|
namespace = "com.kouros.navigation.automotive"
|
||||||
|
compileSdk {
|
||||||
|
version = release(36)
|
||||||
|
}
|
||||||
|
|
||||||
|
defaultConfig {
|
||||||
|
applicationId = "com.kouros.navigation.automotive"
|
||||||
|
minSdk = 35
|
||||||
|
targetSdk = 36
|
||||||
|
versionCode = 1
|
||||||
|
versionName = "1.0"
|
||||||
|
|
||||||
|
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||||
|
}
|
||||||
|
|
||||||
|
buildTypes {
|
||||||
|
release {
|
||||||
|
isMinifyEnabled = false
|
||||||
|
proguardFiles(
|
||||||
|
getDefaultProguardFile("proguard-android-optimize.txt"),
|
||||||
|
"proguard-rules.pro"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
compileOptions {
|
||||||
|
sourceCompatibility = JavaVersion.VERSION_11
|
||||||
|
targetCompatibility = JavaVersion.VERSION_11
|
||||||
|
}
|
||||||
|
kotlinOptions {
|
||||||
|
jvmTarget = "11"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
implementation(libs.androidx.core.ktx)
|
||||||
|
implementation(libs.androidx.appcompat)
|
||||||
|
implementation(libs.material)
|
||||||
|
testImplementation(libs.junit)
|
||||||
|
androidTestImplementation(libs.androidx.junit)
|
||||||
|
androidTestImplementation(libs.androidx.espresso.core)
|
||||||
|
}
|
||||||
21
automotive/proguard-rules.pro
vendored
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
# Add project specific ProGuard rules here.
|
||||||
|
# You can control the set of applied configuration files using the
|
||||||
|
# proguardFiles setting in build.gradle.
|
||||||
|
#
|
||||||
|
# For more details, see
|
||||||
|
# http://developer.android.com/guide/developing/tools/proguard.html
|
||||||
|
|
||||||
|
# If your project uses WebView with JS, uncomment the following
|
||||||
|
# and specify the fully qualified class name to the JavaScript interface
|
||||||
|
# class:
|
||||||
|
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
|
||||||
|
# public *;
|
||||||
|
#}
|
||||||
|
|
||||||
|
# Uncomment this to preserve the line number information for
|
||||||
|
# debugging stack traces.
|
||||||
|
#-keepattributes SourceFile,LineNumberTable
|
||||||
|
|
||||||
|
# If you keep the line number information, uncomment this to
|
||||||
|
# hide the original source file name.
|
||||||
|
#-renamesourcefileattribute SourceFile
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
package com.kouros.navigation.automotive
|
||||||
|
|
||||||
|
import androidx.test.platform.app.InstrumentationRegistry
|
||||||
|
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||||
|
|
||||||
|
import org.junit.Test
|
||||||
|
import org.junit.runner.RunWith
|
||||||
|
|
||||||
|
import org.junit.Assert.*
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Instrumented test, which will execute on an Android device.
|
||||||
|
*
|
||||||
|
* See [testing documentation](http://d.android.com/tools/testing).
|
||||||
|
*/
|
||||||
|
@RunWith(AndroidJUnit4::class)
|
||||||
|
class ExampleInstrumentedTest {
|
||||||
|
@Test
|
||||||
|
fun useAppContext() {
|
||||||
|
// Context of the app under test.
|
||||||
|
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
|
||||||
|
assertEquals("com.kouros.navigation.automotive", appContext.packageName)
|
||||||
|
}
|
||||||
|
}
|
||||||
66
automotive/src/main/AndroidManifest.xml
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools">
|
||||||
|
|
||||||
|
<uses-permission android:name="android.permission.INTERNET" />
|
||||||
|
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||||
|
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
|
||||||
|
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
|
||||||
|
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_LOCATION" />
|
||||||
|
<uses-permission android:name="androidx.car.app.NAVIGATION_TEMPLATES" />
|
||||||
|
<uses-permission android:name="androidx.car.app.ACCESS_SURFACE" />
|
||||||
|
|
||||||
|
<uses-feature
|
||||||
|
android:name="android.software.car.templates_host"
|
||||||
|
android:required="true" />
|
||||||
|
<uses-feature
|
||||||
|
android:name="android.hardware.wifi"
|
||||||
|
android:required="false" />
|
||||||
|
<uses-feature
|
||||||
|
android:name="android.hardware.screen.portrait"
|
||||||
|
android:required="false" />
|
||||||
|
<uses-feature
|
||||||
|
android:name="android.hardware.screen.landscape"
|
||||||
|
android:required="false" />
|
||||||
|
|
||||||
|
<application
|
||||||
|
android:allowBackup="true"
|
||||||
|
android:icon="@mipmap/ic_launcher"
|
||||||
|
android:label="@string/app_name"
|
||||||
|
android:roundIcon="@mipmap/ic_launcher_round"
|
||||||
|
android:supportsRtl="true"
|
||||||
|
android:theme="@style/Theme.Navigation">
|
||||||
|
|
||||||
|
<meta-data
|
||||||
|
android:name="com.android.automotive"
|
||||||
|
android:resource="@xml/automotive_app_desc"
|
||||||
|
tools:ignore="MetadataTagInsideApplicationTag" />
|
||||||
|
|
||||||
|
<meta-data
|
||||||
|
android:name="androidx.car.app.minCarApiLevel"
|
||||||
|
android:value="1"
|
||||||
|
tools:ignore="MetadataTagInsideApplicationTag" />
|
||||||
|
|
||||||
|
<activity
|
||||||
|
android:name="androidx.car.app.activity.CarAppActivity"
|
||||||
|
android:configChanges="uiMode"
|
||||||
|
android:theme="@android:style/Theme.DeviceDefault.NoActionBar"
|
||||||
|
android:exported="true"
|
||||||
|
android:launchMode="singleTask"
|
||||||
|
android:label="Navigation">
|
||||||
|
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.MAIN" />
|
||||||
|
<category android:name="android.intent.category.LAUNCHER" />
|
||||||
|
</intent-filter>
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="androidx.car.app.action.NAVIGATE" />
|
||||||
|
<category android:name="android.intent.category.DEFAULT"/>
|
||||||
|
<data android:scheme="geo" />
|
||||||
|
</intent-filter>
|
||||||
|
<meta-data android:name="distractionOptimized" android:value="true"/>
|
||||||
|
</activity>
|
||||||
|
|
||||||
|
<application />
|
||||||
|
|
||||||
|
</manifest>
|
||||||
170
automotive/src/main/res/drawable/ic_launcher_background.xml
Normal file
@@ -0,0 +1,170 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="108dp"
|
||||||
|
android:height="108dp"
|
||||||
|
android:viewportWidth="108"
|
||||||
|
android:viewportHeight="108">
|
||||||
|
<path
|
||||||
|
android:fillColor="#3DDC84"
|
||||||
|
android:pathData="M0,0h108v108h-108z" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M9,0L9,108"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M19,0L19,108"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M29,0L29,108"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M39,0L39,108"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M49,0L49,108"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M59,0L59,108"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M69,0L69,108"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M79,0L79,108"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M89,0L89,108"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M99,0L99,108"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M0,9L108,9"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M0,19L108,19"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M0,29L108,29"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M0,39L108,39"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M0,49L108,49"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M0,59L108,59"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M0,69L108,69"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M0,79L108,79"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M0,89L108,89"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M0,99L108,99"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M19,29L89,29"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M19,39L89,39"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M19,49L89,49"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M19,59L89,59"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M19,69L89,69"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M19,79L89,79"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M29,19L29,89"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M39,19L39,89"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M49,19L49,89"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M59,19L59,89"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M69,19L69,89"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M79,19L79,89"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
</vector>
|
||||||
30
automotive/src/main/res/drawable/ic_launcher_foreground.xml
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:aapt="http://schemas.android.com/aapt"
|
||||||
|
android:width="108dp"
|
||||||
|
android:height="108dp"
|
||||||
|
android:viewportWidth="108"
|
||||||
|
android:viewportHeight="108">
|
||||||
|
<path android:pathData="M31,63.928c0,0 6.4,-11 12.1,-13.1c7.2,-2.6 26,-1.4 26,-1.4l38.1,38.1L107,108.928l-32,-1L31,63.928z">
|
||||||
|
<aapt:attr name="android:fillColor">
|
||||||
|
<gradient
|
||||||
|
android:endX="85.84757"
|
||||||
|
android:endY="92.4963"
|
||||||
|
android:startX="42.9492"
|
||||||
|
android:startY="49.59793"
|
||||||
|
android:type="linear">
|
||||||
|
<item
|
||||||
|
android:color="#44000000"
|
||||||
|
android:offset="0.0" />
|
||||||
|
<item
|
||||||
|
android:color="#00000000"
|
||||||
|
android:offset="1.0" />
|
||||||
|
</gradient>
|
||||||
|
</aapt:attr>
|
||||||
|
</path>
|
||||||
|
<path
|
||||||
|
android:fillColor="#FFFFFF"
|
||||||
|
android:fillType="nonZero"
|
||||||
|
android:pathData="M65.3,45.828l3.8,-6.6c0.2,-0.4 0.1,-0.9 -0.3,-1.1c-0.4,-0.2 -0.9,-0.1 -1.1,0.3l-3.9,6.7c-6.3,-2.8 -13.4,-2.8 -19.7,0l-3.9,-6.7c-0.2,-0.4 -0.7,-0.5 -1.1,-0.3C38.8,38.328 38.7,38.828 38.9,39.228l3.8,6.6C36.2,49.428 31.7,56.028 31,63.928h46C76.3,56.028 71.8,49.428 65.3,45.828zM43.4,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2c-0.3,-0.7 -0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C45.3,56.528 44.5,57.328 43.4,57.328L43.4,57.328zM64.6,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2s-0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C66.5,56.528 65.6,57.328 64.6,57.328L64.6,57.328z"
|
||||||
|
android:strokeWidth="1"
|
||||||
|
android:strokeColor="#00000000" />
|
||||||
|
</vector>
|
||||||
6
automotive/src/main/res/mipmap-anydpi/ic_launcher.xml
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<background android:drawable="@drawable/ic_launcher_background" />
|
||||||
|
<foreground android:drawable="@drawable/ic_launcher_foreground" />
|
||||||
|
<monochrome android:drawable="@drawable/ic_launcher_foreground" />
|
||||||
|
</adaptive-icon>
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<background android:drawable="@drawable/ic_launcher_background" />
|
||||||
|
<foreground android:drawable="@drawable/ic_launcher_foreground" />
|
||||||
|
<monochrome android:drawable="@drawable/ic_launcher_foreground" />
|
||||||
|
</adaptive-icon>
|
||||||
BIN
automotive/src/main/res/mipmap-hdpi/ic_launcher.webp
Normal file
|
After Width: | Height: | Size: 1.4 KiB |
BIN
automotive/src/main/res/mipmap-hdpi/ic_launcher_round.webp
Normal file
|
After Width: | Height: | Size: 2.8 KiB |
BIN
automotive/src/main/res/mipmap-mdpi/ic_launcher.webp
Normal file
|
After Width: | Height: | Size: 982 B |
BIN
automotive/src/main/res/mipmap-mdpi/ic_launcher_round.webp
Normal file
|
After Width: | Height: | Size: 1.7 KiB |
BIN
automotive/src/main/res/mipmap-xhdpi/ic_launcher.webp
Normal file
|
After Width: | Height: | Size: 1.9 KiB |
BIN
automotive/src/main/res/mipmap-xhdpi/ic_launcher_round.webp
Normal file
|
After Width: | Height: | Size: 3.8 KiB |
BIN
automotive/src/main/res/mipmap-xxhdpi/ic_launcher.webp
Normal file
|
After Width: | Height: | Size: 2.8 KiB |
BIN
automotive/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp
Normal file
|
After Width: | Height: | Size: 5.8 KiB |
BIN
automotive/src/main/res/mipmap-xxxhdpi/ic_launcher.webp
Normal file
|
After Width: | Height: | Size: 3.8 KiB |
BIN
automotive/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp
Normal file
|
After Width: | Height: | Size: 7.6 KiB |
16
automotive/src/main/res/values-night/themes.xml
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
<resources xmlns:tools="http://schemas.android.com/tools">
|
||||||
|
<!-- Base application theme. -->
|
||||||
|
<style name="Theme.Navigation" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
|
||||||
|
<!-- Primary brand color. -->
|
||||||
|
<item name="colorPrimary">@color/purple_200</item>
|
||||||
|
<item name="colorPrimaryVariant">@color/purple_700</item>
|
||||||
|
<item name="colorOnPrimary">@color/black</item>
|
||||||
|
<!-- Secondary brand color. -->
|
||||||
|
<item name="colorSecondary">@color/teal_200</item>
|
||||||
|
<item name="colorSecondaryVariant">@color/teal_200</item>
|
||||||
|
<item name="colorOnSecondary">@color/black</item>
|
||||||
|
<!-- Status bar color. -->
|
||||||
|
<item name="android:statusBarColor">?attr/colorPrimaryVariant</item>
|
||||||
|
<!-- Customize your theme here. -->
|
||||||
|
</style>
|
||||||
|
</resources>
|
||||||
10
automotive/src/main/res/values/colors.xml
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<color name="purple_200">#FFBB86FC</color>
|
||||||
|
<color name="purple_500">#FF6200EE</color>
|
||||||
|
<color name="purple_700">#FF3700B3</color>
|
||||||
|
<color name="teal_200">#FF03DAC5</color>
|
||||||
|
<color name="teal_700">#FF018786</color>
|
||||||
|
<color name="black">#FF000000</color>
|
||||||
|
<color name="white">#FFFFFFFF</color>
|
||||||
|
</resources>
|
||||||
3
automotive/src/main/res/values/strings.xml
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
<resources>
|
||||||
|
<string name="app_name">automotive</string>
|
||||||
|
</resources>
|
||||||
16
automotive/src/main/res/values/themes.xml
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
<resources xmlns:tools="http://schemas.android.com/tools">
|
||||||
|
<!-- Base application theme. -->
|
||||||
|
<style name="Theme.Navigation" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
|
||||||
|
<!-- Primary brand color. -->
|
||||||
|
<item name="colorPrimary">@color/purple_500</item>
|
||||||
|
<item name="colorPrimaryVariant">@color/purple_700</item>
|
||||||
|
<item name="colorOnPrimary">@color/white</item>
|
||||||
|
<!-- Secondary brand color. -->
|
||||||
|
<item name="colorSecondary">@color/teal_200</item>
|
||||||
|
<item name="colorSecondaryVariant">@color/teal_700</item>
|
||||||
|
<item name="colorOnSecondary">@color/black</item>
|
||||||
|
<!-- Status bar color. -->
|
||||||
|
<item name="android:statusBarColor">?attr/colorPrimaryVariant</item>
|
||||||
|
<!-- Customize your theme here. -->
|
||||||
|
</style>
|
||||||
|
</resources>
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
package com.kouros.navigation.automotive
|
||||||
|
|
||||||
|
import org.junit.Test
|
||||||
|
|
||||||
|
import org.junit.Assert.*
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Example local unit test, which will execute on the development machine (host).
|
||||||
|
*
|
||||||
|
* See [testing documentation](http://d.android.com/tools/testing).
|
||||||
|
*/
|
||||||
|
class ExampleUnitTest {
|
||||||
|
@Test
|
||||||
|
fun addition_isCorrect() {
|
||||||
|
assertEquals(4, 2 + 2)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,9 +1,6 @@
|
|||||||
package com.kouros.navigation.car
|
package com.kouros.navigation.car
|
||||||
|
|
||||||
import android.R.attr.strokeWidth
|
|
||||||
import android.graphics.Rect
|
|
||||||
import android.location.Location
|
import android.location.Location
|
||||||
import androidx.car.app.CarContext
|
|
||||||
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
|
||||||
@@ -18,7 +15,6 @@ import androidx.compose.ui.geometry.Offset
|
|||||||
import androidx.compose.ui.geometry.Size
|
import androidx.compose.ui.geometry.Size
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.graphics.ColorFilter
|
import androidx.compose.ui.graphics.ColorFilter
|
||||||
import androidx.compose.ui.graphics.drawscope.Stroke
|
|
||||||
import androidx.compose.ui.graphics.vector.ImageVector
|
import androidx.compose.ui.graphics.vector.ImageVector
|
||||||
import androidx.compose.ui.graphics.vector.rememberVectorPainter
|
import androidx.compose.ui.graphics.vector.rememberVectorPainter
|
||||||
import androidx.compose.ui.res.vectorResource
|
import androidx.compose.ui.res.vectorResource
|
||||||
@@ -29,8 +25,8 @@ import androidx.compose.ui.text.rememberTextMeasurer
|
|||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
import com.kouros.android.cars.carappservice.R
|
import com.kouros.android.cars.carappservice.R
|
||||||
import com.kouros.navigation.data.Constants.SHOW_THREED_BUILDING
|
import com.kouros.navigation.data.NavigationColor
|
||||||
import com.kouros.navigation.utils.NavigationUtils.getBooleanKeyValue
|
import com.kouros.navigation.data.RouteColor
|
||||||
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
|
||||||
@@ -38,8 +34,10 @@ import org.maplibre.compose.expressions.dsl.const
|
|||||||
import org.maplibre.compose.layers.Anchor
|
import org.maplibre.compose.layers.Anchor
|
||||||
import org.maplibre.compose.layers.FillLayer
|
import org.maplibre.compose.layers.FillLayer
|
||||||
import org.maplibre.compose.layers.LineLayer
|
import org.maplibre.compose.layers.LineLayer
|
||||||
|
import org.maplibre.compose.location.LocationPuck
|
||||||
import org.maplibre.compose.location.LocationPuckColors
|
import org.maplibre.compose.location.LocationPuckColors
|
||||||
import org.maplibre.compose.location.LocationPuckSizes
|
import org.maplibre.compose.location.LocationPuckSizes
|
||||||
|
import org.maplibre.compose.location.UserLocationState
|
||||||
import org.maplibre.compose.sources.GeoJsonData
|
import org.maplibre.compose.sources.GeoJsonData
|
||||||
import org.maplibre.compose.sources.Source
|
import org.maplibre.compose.sources.Source
|
||||||
import org.maplibre.compose.sources.rememberGeoJsonSource
|
import org.maplibre.compose.sources.rememberGeoJsonSource
|
||||||
@@ -47,8 +45,8 @@ import org.maplibre.spatialk.geojson.Position
|
|||||||
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun cameraState(position: CameraPosition?, tilt: Double, preview: Boolean): CameraState {
|
fun cameraState(width: Int, height: Int, position: CameraPosition?, tilt: Double, preview: Boolean): CameraState {
|
||||||
val padding = getPaddingValues(preview)
|
val padding = getPaddingValues(width, height, preview)
|
||||||
return rememberCameraState(
|
return rememberCameraState(
|
||||||
firstPosition =
|
firstPosition =
|
||||||
CameraPosition(
|
CameraPosition(
|
||||||
@@ -77,7 +75,7 @@ fun RouteLayer(routeData: String?, previewRoute: String?) {
|
|||||||
LineLayer(
|
LineLayer(
|
||||||
id = "routes",
|
id = "routes",
|
||||||
source = routes,
|
source = routes,
|
||||||
color = const(Color.Blue),
|
color = const(RouteColor),
|
||||||
width = const(14.dp),
|
width = const(14.dp),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -93,7 +91,7 @@ fun RouteLayer(routeData: String?, previewRoute: String?) {
|
|||||||
LineLayer(
|
LineLayer(
|
||||||
id = "routes-pre",
|
id = "routes-pre",
|
||||||
source = routes,
|
source = routes,
|
||||||
color = const(Color.Cyan),
|
color = const(RouteColor),
|
||||||
width = const(4.dp),
|
width = const(4.dp),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -116,7 +114,7 @@ fun DrawImage(location: Location) {
|
|||||||
val textMeasurer = rememberTextMeasurer()
|
val textMeasurer = rememberTextMeasurer()
|
||||||
val vector = ImageVector.vectorResource(id = R.drawable.assistant_navigation_48px)
|
val vector = ImageVector.vectorResource(id = R.drawable.assistant_navigation_48px)
|
||||||
val painter = rememberVectorPainter(image = vector)
|
val painter = rememberVectorPainter(image = vector)
|
||||||
val tint = remember { ColorFilter.tint(Color.Blue) }
|
val tint = remember { ColorFilter.tint(NavigationColor) }
|
||||||
|
|
||||||
Box(
|
Box(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
@@ -138,11 +136,11 @@ fun DrawImage(location: Location) {
|
|||||||
val measuredText =
|
val measuredText =
|
||||||
textMeasurer.measure(
|
textMeasurer.measure(
|
||||||
AnnotatedString("${(location.speed * 3.6).toInt()}"),
|
AnnotatedString("${(location.speed * 3.6).toInt()}"),
|
||||||
style = TextStyle(fontSize = 22.sp)
|
style = TextStyle(color = Color.White, fontSize = 22.sp)
|
||||||
)
|
)
|
||||||
onDrawBehind {
|
onDrawBehind {
|
||||||
drawCircle(
|
drawCircle(
|
||||||
Color.LightGray, radius = 30.dp.toPx(), center = Offset(5f, 10f)
|
Color.Black, radius = 30.dp.toPx(), center = Offset(15f, 12f)
|
||||||
)
|
)
|
||||||
drawText(measuredText)
|
drawText(measuredText)
|
||||||
}
|
}
|
||||||
@@ -167,8 +165,23 @@ fun Puck(cameraState: CameraState, location: Location) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun PuckState(cameraState: CameraState, userLocationState: UserLocationState) {
|
||||||
|
LocationPuck(
|
||||||
|
idPrefix = "user-location1",
|
||||||
|
locationState = userLocationState,
|
||||||
|
cameraState = cameraState,
|
||||||
|
accuracyThreshold = 10f,
|
||||||
|
showBearing = false,
|
||||||
|
sizes = LocationPuckSizes(dotRadius = 10.dp),
|
||||||
|
colors = LocationPuckColors(
|
||||||
|
dotFillColorCurrentLocation = Color.Cyan,
|
||||||
|
accuracyStrokeColor = Color.Green
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
fun getPaddingValues(preView: Boolean): PaddingValues {
|
fun getPaddingValues(width: Int, height: Int, preView: Boolean): PaddingValues {
|
||||||
val padding = PaddingValues(start = 100.dp, top = 300.dp)
|
val padding = PaddingValues(start = 100.dp, top = 300.dp)
|
||||||
val prePadding = PaddingValues(start = 150.dp, bottom = 0.dp)
|
val prePadding = PaddingValues(start = 150.dp, bottom = 0.dp)
|
||||||
return if (preView) {
|
return if (preView) {
|
||||||
|
|||||||
@@ -22,14 +22,16 @@ 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.MAXIMAL_ROUTE_DEVIATION
|
||||||
import com.kouros.navigation.data.Constants.TAG
|
import com.kouros.navigation.data.Constants.TAG
|
||||||
import com.kouros.navigation.data.ObjectBox
|
import com.kouros.navigation.data.ObjectBox
|
||||||
|
import com.kouros.navigation.utils.NavigationUtils.snapLocation
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
class NavigationSession : Session() {
|
class NavigationSession : Session(), NavigationScreen.Listener {
|
||||||
val uriScheme = "samples";
|
val uriScheme = "samples";
|
||||||
|
|
||||||
val uriHost = "navigation";
|
val uriHost = "navigation";
|
||||||
@@ -40,7 +42,7 @@ class NavigationSession : Session() {
|
|||||||
|
|
||||||
lateinit var surfaceRenderer: SurfaceRenderer
|
lateinit var surfaceRenderer: SurfaceRenderer
|
||||||
|
|
||||||
var locationIndex = 100
|
var locationIndex = 0
|
||||||
|
|
||||||
val simulate = true
|
val simulate = true
|
||||||
|
|
||||||
@@ -83,11 +85,10 @@ class NavigationSession : Session() {
|
|||||||
|
|
||||||
override fun onCreateScreen(intent: Intent): Screen {
|
override fun onCreateScreen(intent: Intent): Screen {
|
||||||
routeModel = RouteCarModel()
|
routeModel = RouteCarModel()
|
||||||
ObjectBox.init(carContext);
|
|
||||||
|
|
||||||
surfaceRenderer = SurfaceRenderer(carContext, lifecycle, routeModel)
|
surfaceRenderer = SurfaceRenderer(carContext, lifecycle, routeModel)
|
||||||
|
|
||||||
navigationScreen = NavigationScreen(carContext, surfaceRenderer, routeModel)
|
navigationScreen = NavigationScreen(carContext, surfaceRenderer, routeModel, this)
|
||||||
|
|
||||||
if (carContext.checkSelfPermission(Manifest.permission.ACCESS_FINE_LOCATION)
|
if (carContext.checkSelfPermission(Manifest.permission.ACCESS_FINE_LOCATION)
|
||||||
== PackageManager.PERMISSION_GRANTED
|
== PackageManager.PERMISSION_GRANTED
|
||||||
@@ -159,7 +160,7 @@ class NavigationSession : Session() {
|
|||||||
updateLocation(location)
|
updateLocation(location)
|
||||||
locationManager.requestLocationUpdates(
|
locationManager.requestLocationUpdates(
|
||||||
LocationManager.GPS_PROVIDER,
|
LocationManager.GPS_PROVIDER,
|
||||||
/* minTimeMs= */ 10,
|
/* minTimeMs= */ 100,
|
||||||
/* minDistanceM= */ 0f,
|
/* minDistanceM= */ 0f,
|
||||||
mLocationListener
|
mLocationListener
|
||||||
)
|
)
|
||||||
@@ -183,8 +184,14 @@ class NavigationSession : Session() {
|
|||||||
)
|
)
|
||||||
val loc = routeModel.route.waypoints[locationIndex]
|
val loc = routeModel.route.waypoints[locationIndex]
|
||||||
val curLocation = Location(LocationManager.GPS_PROVIDER)
|
val curLocation = Location(LocationManager.GPS_PROVIDER)
|
||||||
curLocation.longitude = loc[0] + 0.0003
|
if ( locationIndex == 1500) {
|
||||||
curLocation.latitude = loc[1] + 0.0002
|
curLocation.longitude = loc[0] + 0.003
|
||||||
|
curLocation.latitude = loc[1] + 0.003
|
||||||
|
} else {
|
||||||
|
curLocation.longitude = loc[0]
|
||||||
|
curLocation.latitude = loc[1]
|
||||||
|
}
|
||||||
|
curLocation.speed = 15F
|
||||||
update(curLocation)
|
update(curLocation)
|
||||||
locationIndex += 1
|
locationIndex += 1
|
||||||
if (locationIndex > routeModel.route.waypoints.size) {
|
if (locationIndex > routeModel.route.waypoints.size) {
|
||||||
@@ -203,6 +210,13 @@ class NavigationSession : Session() {
|
|||||||
routeModel.updateLocation(location)
|
routeModel.updateLocation(location)
|
||||||
navigationScreen.updateTrip()
|
navigationScreen.updateTrip()
|
||||||
}
|
}
|
||||||
surfaceRenderer.updateLocation(location)
|
val result = surfaceRenderer.updateLocation(location)
|
||||||
|
if (!result) {
|
||||||
|
navigationScreen.stopNavigation()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun stopNavigation() {
|
||||||
|
routeModel.stopNavigation()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,7 +1,6 @@
|
|||||||
package com.kouros.navigation.car
|
package com.kouros.navigation.car
|
||||||
|
|
||||||
import android.app.Presentation
|
import android.app.Presentation
|
||||||
import android.content.res.Resources.getSystem
|
|
||||||
import android.graphics.Rect
|
import android.graphics.Rect
|
||||||
import android.hardware.display.DisplayManager
|
import android.hardware.display.DisplayManager
|
||||||
import android.hardware.display.VirtualDisplay
|
import android.hardware.display.VirtualDisplay
|
||||||
@@ -12,6 +11,7 @@ import androidx.car.app.AppManager
|
|||||||
import androidx.car.app.CarContext
|
import androidx.car.app.CarContext
|
||||||
import androidx.car.app.SurfaceCallback
|
import androidx.car.app.SurfaceCallback
|
||||||
import androidx.car.app.SurfaceContainer
|
import androidx.car.app.SurfaceContainer
|
||||||
|
import androidx.compose.foundation.isSystemInDarkTheme
|
||||||
import androidx.compose.runtime.Composable
|
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
|
||||||
@@ -25,11 +25,13 @@ import androidx.lifecycle.setViewTreeLifecycleOwner
|
|||||||
import androidx.savedstate.setViewTreeSavedStateRegistryOwner
|
import androidx.savedstate.setViewTreeSavedStateRegistryOwner
|
||||||
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.MAXIMAL_ROUTE_DEVIATION
|
||||||
import com.kouros.navigation.data.Constants.SHOW_THREED_BUILDING
|
import com.kouros.navigation.data.Constants.SHOW_THREED_BUILDING
|
||||||
import com.kouros.navigation.model.RouteModel
|
import com.kouros.navigation.model.RouteModel
|
||||||
import com.kouros.navigation.utils.NavigationUtils.getBooleanKeyValue
|
import com.kouros.navigation.utils.NavigationUtils.getBooleanKeyValue
|
||||||
import com.kouros.navigation.utils.NavigationUtils.snapLocation
|
import com.kouros.navigation.utils.NavigationUtils.snapLocation
|
||||||
import com.kouros.navigation.utils.calculateZoom
|
import com.kouros.navigation.utils.calculateZoom
|
||||||
|
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.map.MaplibreMap
|
import org.maplibre.compose.map.MaplibreMap
|
||||||
@@ -59,11 +61,9 @@ class SurfaceRenderer(
|
|||||||
|
|
||||||
var stableArea = Rect()
|
var stableArea = Rect()
|
||||||
|
|
||||||
var containerWidth = 0
|
var width = 0
|
||||||
|
|
||||||
var containerHeight = 0
|
var height = 0
|
||||||
|
|
||||||
var containerDpi = 1
|
|
||||||
|
|
||||||
var lastBearing = 0.0
|
var lastBearing = 0.0
|
||||||
val routeData = MutableLiveData("")
|
val routeData = MutableLiveData("")
|
||||||
@@ -107,17 +107,8 @@ class SurfaceRenderer(
|
|||||||
0
|
0
|
||||||
)
|
)
|
||||||
|
|
||||||
containerWidth = surfaceContainer.width
|
width = surfaceContainer.width
|
||||||
containerHeight = surfaceContainer.height
|
height = surfaceContainer.height
|
||||||
if (surfaceContainer.dpi != 0) {
|
|
||||||
containerDpi = surfaceContainer.dpi
|
|
||||||
}
|
|
||||||
println(surfaceContainer.toString())
|
|
||||||
println(containerDpi)
|
|
||||||
println(carContext.resources.displayMetrics.density)
|
|
||||||
println(carContext.resources.displayMetrics.densityDpi)
|
|
||||||
println(getSystem().displayMetrics.density)
|
|
||||||
println(getSystem().displayMetrics.densityDpi)
|
|
||||||
|
|
||||||
mapView = ComposeView(carContext).apply {
|
mapView = ComposeView(carContext).apply {
|
||||||
this.setViewTreeLifecycleOwner(lifecycleOwner)
|
this.setViewTreeLifecycleOwner(lifecycleOwner)
|
||||||
@@ -175,15 +166,18 @@ class SurfaceRenderer(
|
|||||||
val position: CameraPosition? by cameraPosition.observeAsState()
|
val position: CameraPosition? by cameraPosition.observeAsState()
|
||||||
val route: String? by routeData.observeAsState()
|
val route: String? by routeData.observeAsState()
|
||||||
val previewRoute: String? by previewRouteData.observeAsState()
|
val previewRoute: String? by previewRouteData.observeAsState()
|
||||||
val cameraState = cameraState(position, tilt, preview)
|
val cameraState = cameraState(width, height, position, tilt, preview)
|
||||||
|
val baseStyle =
|
||||||
|
if (isSystemInDarkTheme()) BaseStyle.Uri(Constants.STYLE_DARK) else BaseStyle.Uri(
|
||||||
|
Constants.STYLE
|
||||||
|
)
|
||||||
|
|
||||||
MaplibreMap(
|
MaplibreMap(
|
||||||
cameraState = cameraState,
|
cameraState = cameraState,
|
||||||
baseStyle = BaseStyle.Uri(Constants.STYLE),
|
baseStyle = baseStyle,
|
||||||
) {
|
) {
|
||||||
getBaseSource(id = "openmaptiles")?.let { tiles ->
|
getBaseSource(id = "openmaptiles")?.let { tiles ->
|
||||||
if (!getBooleanKeyValue(context = carContext, SHOW_THREED_BUILDING)
|
if (!getBooleanKeyValue(context = carContext, SHOW_THREED_BUILDING)) {
|
||||||
&& Constants.STYLE.contains("liberty")) {
|
|
||||||
BuildingLayer(tiles)
|
BuildingLayer(tiles)
|
||||||
}
|
}
|
||||||
RouteLayer(route, previewRoute)
|
RouteLayer(route, previewRoute)
|
||||||
@@ -193,42 +187,35 @@ class SurfaceRenderer(
|
|||||||
ShowPosition(cameraState, position)
|
ShowPosition(cameraState, position)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun ShowPosition(cameraState: CameraState, position: CameraPosition?) {
|
fun ShowPosition(cameraState: CameraState, position: CameraPosition?) {
|
||||||
|
var cameraDuration = duration(position)
|
||||||
|
var bearing = position!!.bearing
|
||||||
|
var zoom = position.zoom
|
||||||
|
var target = position.target
|
||||||
|
var localTilt = tilt
|
||||||
if (!preview) {
|
if (!preview) {
|
||||||
DrawImage(lastLocation)
|
DrawImage(lastLocation)
|
||||||
val cameraDuration = duration(position)
|
} else {
|
||||||
|
cameraDuration = 3.seconds
|
||||||
|
bearing = 0.0
|
||||||
|
zoom = 11.0
|
||||||
|
target = Position(centerLocation.longitude, centerLocation.latitude)
|
||||||
|
localTilt = 0.0
|
||||||
|
}
|
||||||
LaunchedEffect(position) {
|
LaunchedEffect(position) {
|
||||||
cameraState.animateTo(
|
cameraState.animateTo(
|
||||||
finalPosition = CameraPosition(
|
finalPosition = CameraPosition(
|
||||||
bearing = position!!.bearing,
|
bearing = bearing,
|
||||||
zoom = position.zoom,
|
zoom = zoom,
|
||||||
target = position.target,
|
target = target,
|
||||||
tilt = tilt,
|
tilt = localTilt,
|
||||||
padding = getPaddingValues(preview)
|
padding = getPaddingValues(width, height, preview)
|
||||||
),
|
),
|
||||||
duration = cameraDuration
|
duration = cameraDuration
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
LaunchedEffect(position) {
|
|
||||||
cameraState.animateTo(
|
|
||||||
finalPosition = CameraPosition(
|
|
||||||
bearing = 0.0,
|
|
||||||
zoom = 11.0,
|
|
||||||
target = Position(centerLocation.longitude, centerLocation.latitude),
|
|
||||||
tilt = 0.0,
|
|
||||||
padding = getPaddingValues(preview)
|
|
||||||
),
|
|
||||||
duration = 3.seconds
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
override fun onCreate(owner: LifecycleOwner) {
|
override fun onCreate(owner: LifecycleOwner) {
|
||||||
Log.i(TAG, "SurfaceRenderer created")
|
Log.i(TAG, "SurfaceRenderer created")
|
||||||
@@ -263,55 +250,56 @@ class SurfaceRenderer(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun updateLocation(location: Location) {
|
fun updateLocation(location: Location) : Boolean {
|
||||||
synchronized(this) {
|
synchronized(this) {
|
||||||
if (!preview) {
|
if (!preview) {
|
||||||
var snapedLocation = location
|
var snapedLocation = location
|
||||||
var bearing: Double
|
var bearing: Double
|
||||||
|
bearing = cameraPosition.value!!.bearing
|
||||||
if (routeModel.isNavigating()) {
|
if (routeModel.isNavigating()) {
|
||||||
snapedLocation = snapLocation(location, routeModel.route.maneuverLocations())
|
snapedLocation = snapLocation(location, routeModel.route.maneuverLocations())
|
||||||
// stimmt nicht
|
bearing = routeModel.currentStep().bearing
|
||||||
//bearing = routeModel.currentStep().bearing
|
if (snapedLocation.longitude == 0.0) {
|
||||||
}
|
//reRoute()
|
||||||
bearing = cameraPosition.value!!.bearing
|
return false
|
||||||
if (lastLocation.latitude != snapedLocation.latitude) {
|
|
||||||
if (lastLocation.distanceTo(snapedLocation) > 5) {
|
|
||||||
bearing = lastLocation.bearingTo(snapedLocation).toDouble()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val zoom = if (!panView) {
|
val zoom = if (!panView) {
|
||||||
calculateZoom(snapedLocation.speed.toDouble())
|
calculateZoom(snapedLocation.speed.toDouble())
|
||||||
} else {
|
} else {
|
||||||
cameraPosition.value!!.zoom
|
cameraPosition.value!!.zoom
|
||||||
}
|
}
|
||||||
|
updateCameraPosition(bearing, zoom, Position(snapedLocation.longitude, snapedLocation.latitude))
|
||||||
lastBearing = cameraPosition.value!!.bearing
|
lastBearing = cameraPosition.value!!.bearing
|
||||||
cameraPosition.postValue(
|
|
||||||
cameraPosition.value!!.copy(
|
|
||||||
bearing = bearing,
|
|
||||||
zoom = zoom,
|
|
||||||
padding = getPaddingValues(preview),
|
|
||||||
target = Position(snapedLocation.longitude, snapedLocation.latitude),
|
|
||||||
)
|
|
||||||
)
|
|
||||||
lastLocation = snapedLocation
|
lastLocation = snapedLocation
|
||||||
} else {
|
} else {
|
||||||
val bearing = 0.0
|
val bearing = 0.0
|
||||||
val zoom = 14.0
|
val zoom = 14.0
|
||||||
|
updateCameraPosition(
|
||||||
|
bearing,
|
||||||
|
zoom,
|
||||||
|
Position(centerLocation.longitude, centerLocation.latitude)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun updateCameraPosition(bearing: Double, zoom: Double, target: Position) {
|
||||||
cameraPosition.postValue(
|
cameraPosition.postValue(
|
||||||
cameraPosition.value!!.copy(
|
cameraPosition.value!!.copy(
|
||||||
bearing = bearing,
|
bearing = bearing,
|
||||||
zoom = zoom,
|
zoom = zoom,
|
||||||
tilt = 0.0,
|
tilt = 0.0,
|
||||||
target = Position(centerLocation.longitude, centerLocation.latitude)
|
padding = getPaddingValues(width, height, preview),
|
||||||
|
target = target
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun setRouteData() {
|
fun setRouteData() {
|
||||||
routeData.value = routeModel.route.routeGeoJson
|
routeData.value = routeModel.route.routeGeoJson
|
||||||
|
previewRouteData.value = ""
|
||||||
preview = false
|
preview = false
|
||||||
panView = false
|
panView = false
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,9 +29,9 @@ import androidx.car.app.navigation.model.Step
|
|||||||
import androidx.car.app.navigation.model.TravelEstimate
|
import androidx.car.app.navigation.model.TravelEstimate
|
||||||
import androidx.core.graphics.drawable.IconCompat
|
import androidx.core.graphics.drawable.IconCompat
|
||||||
import com.kouros.android.cars.carappservice.R
|
import com.kouros.android.cars.carappservice.R
|
||||||
|
import com.kouros.navigation.data.Constants.NEXT_STEP_THRESHOLD
|
||||||
import com.kouros.navigation.data.ManeuverType
|
import com.kouros.navigation.data.ManeuverType
|
||||||
import com.kouros.navigation.model.RouteModel
|
import com.kouros.navigation.model.RouteModel
|
||||||
import org.json.JSONObject
|
|
||||||
import java.util.TimeZone
|
import java.util.TimeZone
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
|
|
||||||
@@ -52,8 +52,8 @@ class RouteCarModel() : RouteModel() {
|
|||||||
routingData(ManeuverType.None.value, carContext)
|
routingData(ManeuverType.None.value, carContext)
|
||||||
}
|
}
|
||||||
when (stepData.leftDistance) {
|
when (stepData.leftDistance) {
|
||||||
in 0.0..100.0 -> {
|
in 0.0..NEXT_STEP_THRESHOLD -> {
|
||||||
if (route.currentIndex < route.maneuvers.size) {
|
if (route.currentManeuverIndex < route.maneuvers.size) {
|
||||||
val maneuver = route.nextManeuver()
|
val maneuver = route.nextManeuver()
|
||||||
val maneuverType = maneuver.type
|
val maneuverType = maneuver.type
|
||||||
routing = routingData(maneuverType, carContext)
|
routing = routingData(maneuverType, carContext)
|
||||||
@@ -76,7 +76,7 @@ class RouteCarModel() : RouteModel() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** 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 maneuver = route.nextManeuver()
|
val maneuver = route.nextManeuver()
|
||||||
val maneuverType = maneuver.type
|
val maneuverType = maneuver.type
|
||||||
val routing = routingData(maneuverType, carContext)
|
val routing = routingData(maneuverType, carContext)
|
||||||
@@ -84,10 +84,11 @@ class RouteCarModel() : RouteModel() {
|
|||||||
val distanceLeft = leftStepDistance() * 1000
|
val distanceLeft = leftStepDistance() * 1000
|
||||||
|
|
||||||
when (distanceLeft) {
|
when (distanceLeft) {
|
||||||
in 0.0..100.0 -> {
|
in 0.0..NEXT_STEP_THRESHOLD -> {
|
||||||
if (maneuver.streetNames != null && maneuver.streetNames!!.isNotEmpty()) {
|
return null
|
||||||
text = maneuver.streetNames!![0]
|
// if (maneuver.streetNames != null && maneuver.streetNames!!.isNotEmpty()) {
|
||||||
}
|
// text = maneuver.streetNames!![0]
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
else -> {
|
else -> {
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ import androidx.lifecycle.Observer
|
|||||||
import com.kouros.android.cars.carappservice.R
|
import com.kouros.android.cars.carappservice.R
|
||||||
import com.kouros.navigation.car.NavigationCarAppService
|
import com.kouros.navigation.car.NavigationCarAppService
|
||||||
import com.kouros.navigation.car.SurfaceRenderer
|
import com.kouros.navigation.car.SurfaceRenderer
|
||||||
|
import com.kouros.navigation.car.navigation.NavigationMessage
|
||||||
import com.kouros.navigation.car.navigation.RouteCarModel
|
import com.kouros.navigation.car.navigation.RouteCarModel
|
||||||
import com.kouros.navigation.data.NavigationRepository
|
import com.kouros.navigation.data.NavigationRepository
|
||||||
import com.kouros.navigation.data.Place
|
import com.kouros.navigation.data.Place
|
||||||
@@ -32,10 +33,18 @@ import com.kouros.navigation.model.ViewModel
|
|||||||
class NavigationScreen(
|
class NavigationScreen(
|
||||||
carContext: CarContext,
|
carContext: CarContext,
|
||||||
private var surfaceRenderer: SurfaceRenderer,
|
private var surfaceRenderer: SurfaceRenderer,
|
||||||
private var routeModel: RouteCarModel
|
private var routeModel: RouteCarModel,
|
||||||
|
private var listener: Listener
|
||||||
|
|
||||||
) :
|
) :
|
||||||
Screen(carContext) {
|
Screen(carContext) {
|
||||||
|
|
||||||
|
/** A listener for navigation start and stop signals. */
|
||||||
|
interface Listener {
|
||||||
|
/** Stops navigation. */
|
||||||
|
fun stopNavigation()
|
||||||
|
}
|
||||||
|
|
||||||
var currentNavigationLocation = Location(LocationManager.GPS_PROVIDER)
|
var currentNavigationLocation = Location(LocationManager.GPS_PROVIDER)
|
||||||
val vieModel = ViewModel(NavigationRepository())
|
val vieModel = ViewModel(NavigationRepository())
|
||||||
val observer = Observer<String> { route ->
|
val observer = Observer<String> { route ->
|
||||||
@@ -77,7 +86,7 @@ class NavigationScreen(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getNavigationTemplate(actionStripBuilder: ActionStrip.Builder): NavigationTemplate{
|
private fun getNavigationTemplate(actionStripBuilder: ActionStrip.Builder): NavigationTemplate {
|
||||||
actionStripBuilder.addAction(
|
actionStripBuilder.addAction(
|
||||||
Action.Builder()
|
Action.Builder()
|
||||||
.setTitle(carContext.getString(R.string.stop_action_title))
|
.setTitle(carContext.getString(R.string.stop_action_title))
|
||||||
@@ -91,9 +100,7 @@ class NavigationScreen(
|
|||||||
.build()
|
.build()
|
||||||
)
|
)
|
||||||
.setOnClickListener {
|
.setOnClickListener {
|
||||||
routeModel.stopNavigation()
|
stopNavigation()
|
||||||
surfaceRenderer.routeData.postValue("")
|
|
||||||
invalidate()
|
|
||||||
}
|
}
|
||||||
.build()
|
.build()
|
||||||
)
|
)
|
||||||
@@ -107,9 +114,10 @@ class NavigationScreen(
|
|||||||
.setBackgroundColor(CarColor.GREEN)
|
.setBackgroundColor(CarColor.GREEN)
|
||||||
.build()
|
.build()
|
||||||
}
|
}
|
||||||
private fun getNavigationEndTemplate(actionStripBuilder: ActionStrip.Builder): NavigationTemplate{
|
|
||||||
|
private fun getNavigationEndTemplate(actionStripBuilder: ActionStrip.Builder): NavigationTemplate {
|
||||||
if (routeModel.isArrived()) {
|
if (routeModel.isArrived()) {
|
||||||
val timer = object: CountDownTimer(10000, 10000) {
|
val timer = object : CountDownTimer(10000, 10000) {
|
||||||
override fun onTick(millisUntilFinished: Long) {}
|
override fun onTick(millisUntilFinished: Long) {}
|
||||||
override fun onFinish() {
|
override fun onFinish() {
|
||||||
routeModel.arrived = false
|
routeModel.arrived = false
|
||||||
@@ -155,13 +163,23 @@ class NavigationScreen(
|
|||||||
} else {
|
} else {
|
||||||
Distance.UNIT_METERS
|
Distance.UNIT_METERS
|
||||||
}
|
}
|
||||||
|
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),
|
||||||
Distance.create(currentDistance, displayUnit)
|
Distance.create(currentDistance, displayUnit)
|
||||||
)
|
)
|
||||||
.setNextStep(routeModel.nextStep(carContext = carContext))
|
.setNextStep(nextStep)
|
||||||
.build()
|
.build()
|
||||||
|
} else {
|
||||||
|
return RoutingInfo.Builder()
|
||||||
|
.setCurrentStep(
|
||||||
|
routeModel.currentStep(carContext = carContext),
|
||||||
|
Distance.create(currentDistance, displayUnit)
|
||||||
|
)
|
||||||
|
.build()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -196,8 +214,7 @@ class NavigationScreen(
|
|||||||
surfaceRenderer.handleScale(-1)
|
surfaceRenderer.handleScale(-1)
|
||||||
}
|
}
|
||||||
.build())
|
.build())
|
||||||
if (surfaceRenderer.panView)
|
if (surfaceRenderer.panView) {
|
||||||
{
|
|
||||||
actionStripBuilder.addAction(
|
actionStripBuilder.addAction(
|
||||||
Action.Builder()
|
Action.Builder()
|
||||||
.setIcon(
|
.setIcon(
|
||||||
@@ -258,9 +275,15 @@ class NavigationScreen(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun stopNavigation() {
|
||||||
|
listener.stopNavigation()
|
||||||
|
surfaceRenderer.routeData.postValue("")
|
||||||
|
invalidate()
|
||||||
|
}
|
||||||
|
|
||||||
fun reRoute() {
|
fun reRoute() {
|
||||||
//NavigationMessage(carContext).createAlert()
|
NavigationMessage(carContext).createAlert()
|
||||||
//vieModel.loadRoute(surfaceRenderer.lastLocation, currentNavigationLocation)
|
vieModel.loadRoute(surfaceRenderer.lastLocation, currentNavigationLocation)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun updateTrip() {
|
fun updateTrip() {
|
||||||
|
|||||||
@@ -71,6 +71,7 @@ class RoutePreviewScreen(
|
|||||||
routeModel.startNavigation(route)
|
routeModel.startNavigation(route)
|
||||||
surfaceRenderer.setPreviewRouteData(routeModel)
|
surfaceRenderer.setPreviewRouteData(routeModel)
|
||||||
val geocoder = Geocoder(carContext)
|
val geocoder = Geocoder(carContext)
|
||||||
|
// nominatim ->
|
||||||
geocoder.getFromLocation(destination.latitude, destination.longitude, 1) {
|
geocoder.getFromLocation(destination.latitude, destination.longitude, 1) {
|
||||||
for (address in it) {
|
for (address in it) {
|
||||||
street = address.getAddressLine(0)
|
street = address.getAddressLine(0)
|
||||||
|
|||||||
@@ -1,9 +1,7 @@
|
|||||||
package com.kouros.navigation.car.screen
|
package com.kouros.navigation.car.screen
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
import android.location.Geocoder
|
|
||||||
import android.location.Location
|
import android.location.Location
|
||||||
import android.location.LocationManager
|
|
||||||
import androidx.car.app.CarContext
|
import androidx.car.app.CarContext
|
||||||
import androidx.car.app.Screen
|
import androidx.car.app.Screen
|
||||||
import androidx.car.app.model.Action
|
import androidx.car.app.model.Action
|
||||||
@@ -12,12 +10,15 @@ import androidx.car.app.model.Row
|
|||||||
import androidx.car.app.model.SearchTemplate
|
import androidx.car.app.model.SearchTemplate
|
||||||
import androidx.car.app.model.SearchTemplate.SearchCallback
|
import androidx.car.app.model.SearchTemplate.SearchCallback
|
||||||
import androidx.car.app.model.Template
|
import androidx.car.app.model.Template
|
||||||
|
import androidx.lifecycle.Observer
|
||||||
import com.kouros.android.cars.carappservice.R
|
import com.kouros.android.cars.carappservice.R
|
||||||
import com.kouros.navigation.car.SurfaceRenderer
|
import com.kouros.navigation.car.SurfaceRenderer
|
||||||
import com.kouros.navigation.data.Category
|
import com.kouros.navigation.data.Category
|
||||||
import com.kouros.navigation.data.Constants
|
import com.kouros.navigation.data.Constants
|
||||||
|
import com.kouros.navigation.data.NavigationRepository
|
||||||
import com.kouros.navigation.data.Place
|
import com.kouros.navigation.data.Place
|
||||||
import com.kouros.navigation.utils.NavigationUtils.getBoundingBox
|
import com.kouros.navigation.data.nominatim.Search
|
||||||
|
import com.kouros.navigation.model.ViewModel
|
||||||
|
|
||||||
|
|
||||||
class SearchScreen(
|
class SearchScreen(
|
||||||
@@ -28,13 +29,25 @@ class SearchScreen(
|
|||||||
|
|
||||||
var isSearchComplete: Boolean = false
|
var isSearchComplete: Boolean = false
|
||||||
|
|
||||||
var isSearching: Boolean = false
|
|
||||||
|
|
||||||
var categories: List<Category> = listOf(
|
var categories: List<Category> = listOf(
|
||||||
Category(id = Constants.RECENT, name = carContext.getString(R.string.recent_destinations)),
|
Category(id = Constants.RECENT, name = carContext.getString(R.string.recent_destinations)),
|
||||||
Category(id = Constants.CONTACTS, name = carContext.getString(R.string.contacts))
|
Category(id = Constants.CONTACTS, name = carContext.getString(R.string.contacts))
|
||||||
)
|
)
|
||||||
|
|
||||||
|
lateinit var searchResult: Search
|
||||||
|
|
||||||
|
val observer = Observer<Search> { newSearch ->
|
||||||
|
println(newSearch)
|
||||||
|
searchResult = newSearch
|
||||||
|
invalidate()
|
||||||
|
}
|
||||||
|
|
||||||
|
val viewModel = ViewModel(NavigationRepository())
|
||||||
|
|
||||||
|
init {
|
||||||
|
viewModel.searchPlaces.observe(this, observer)
|
||||||
|
}
|
||||||
|
|
||||||
override fun onGetTemplate(): Template {
|
override fun onGetTemplate(): Template {
|
||||||
|
|
||||||
val itemListBuilder = ItemList.Builder()
|
val itemListBuilder = ItemList.Builder()
|
||||||
@@ -42,7 +55,7 @@ class SearchScreen(
|
|||||||
|
|
||||||
val searchItemListBuilder = ItemList.Builder()
|
val searchItemListBuilder = ItemList.Builder()
|
||||||
.setNoItemsMessage("No search results to show")
|
.setNoItemsMessage("No search results to show")
|
||||||
if (!isSearching) {
|
if (!isSearchComplete) {
|
||||||
categories.forEach {
|
categories.forEach {
|
||||||
it.name
|
it.name
|
||||||
itemListBuilder.addItem(
|
itemListBuilder.addItem(
|
||||||
@@ -68,9 +81,11 @@ class SearchScreen(
|
|||||||
.build()
|
.build()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
doSearch(searchItemListBuilder)
|
||||||
}
|
}
|
||||||
|
|
||||||
val itemList = if (!isSearching) {
|
val itemList = if (!isSearchComplete) {
|
||||||
itemListBuilder.build()
|
itemListBuilder.build()
|
||||||
} else {
|
} else {
|
||||||
searchItemListBuilder.build()
|
searchItemListBuilder.build()
|
||||||
@@ -79,12 +94,12 @@ class SearchScreen(
|
|||||||
return SearchTemplate.Builder(
|
return SearchTemplate.Builder(
|
||||||
object : SearchCallback {
|
object : SearchCallback {
|
||||||
override fun onSearchTextChanged(searchText: String) {
|
override fun onSearchTextChanged(searchText: String) {
|
||||||
doSearch(searchText, searchItemListBuilder)
|
//doSearch(searchText, searchItemListBuilder)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onSearchSubmitted(searchTerm: String) {
|
override fun onSearchSubmitted(searchTerm: String) {
|
||||||
isSearchComplete = true
|
isSearchComplete = true
|
||||||
doSearch(searchTerm, searchItemListBuilder)
|
viewModel.searchPlaces(searchTerm)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.setHeaderAction(Action.BACK)
|
.setHeaderAction(Action.BACK)
|
||||||
@@ -94,42 +109,25 @@ class SearchScreen(
|
|||||||
}
|
}
|
||||||
|
|
||||||
@SuppressLint("DefaultLocale")
|
@SuppressLint("DefaultLocale")
|
||||||
fun doSearch(searchText: String, searchItemListBuilder: ItemList.Builder) {
|
fun doSearch(searchItemListBuilder: ItemList.Builder) {
|
||||||
isSearching = true
|
searchResult.forEach {
|
||||||
val geocoder = Geocoder(carContext)
|
println(it.displayName)
|
||||||
val box = getBoundingBox(location.latitude, location.longitude, 100.0)
|
//val name: String = address.getAddressLine(0)
|
||||||
|
//addressLocation.latitude = address.latitude
|
||||||
val lowerLeft = box["sw"]
|
//adressLocation.longitude = address.longitude
|
||||||
val lowerLeftLat = lowerLeft!!["lat"]!!
|
//val distance = location.distanceTo(addressLocation)
|
||||||
val lowerLeftLon = lowerLeft["lon"]!!
|
//val dist = String.format("%.1f", (distance / 1000))
|
||||||
val upperRight = box["ne"]
|
|
||||||
val upperRightLat = upperRight!!["lat"]!!
|
|
||||||
val upperRightLon = upperRight["lon"]!!
|
|
||||||
val addressLocation = Location(LocationManager.GPS_PROVIDER)
|
|
||||||
|
|
||||||
geocoder.getFromLocationName(
|
|
||||||
searchText, 5,
|
|
||||||
//lowerLeftLat, lowerLeftLon, upperRightLat, upperRightLon
|
|
||||||
) {
|
|
||||||
for (address in it) {
|
|
||||||
val name: String = address.getAddressLine(0)
|
|
||||||
addressLocation.latitude = address.latitude
|
|
||||||
addressLocation.longitude = address.longitude
|
|
||||||
val distance = location.distanceTo(addressLocation)
|
|
||||||
val dist = String.format("%.1f", (distance / 1000))
|
|
||||||
searchItemListBuilder.addItem(
|
searchItemListBuilder.addItem(
|
||||||
Row.Builder()
|
Row.Builder()
|
||||||
.setTitle("$name $dist km")
|
.setTitle(it.displayName)
|
||||||
.setOnClickListener {
|
.setOnClickListener {
|
||||||
val place = Place(
|
val place = Place(
|
||||||
0,
|
name = it.displayName,
|
||||||
name,
|
latitude = it.lat.toDouble(),
|
||||||
name,
|
longitude = it.lon.toDouble(),
|
||||||
address.latitude,
|
street = it.address.road,
|
||||||
address.longitude,
|
city = it.address.city,
|
||||||
"0",
|
postalCode = it.address.postcode
|
||||||
"city",
|
|
||||||
name
|
|
||||||
)
|
)
|
||||||
setResult(place)
|
setResult(place)
|
||||||
finish()
|
finish()
|
||||||
@@ -138,11 +136,8 @@ class SearchScreen(
|
|||||||
.build()
|
.build()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
if (searchText.isEmpty()) {
|
// val itemList = searchItemListBuilder.build()
|
||||||
isSearching = false
|
|
||||||
}
|
|
||||||
val itemList = searchItemListBuilder.build()
|
|
||||||
invalidate()
|
invalidate()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -21,7 +21,6 @@
|
|||||||
<item name="carColorPrimaryDark">#5904DF</item>
|
<item name="carColorPrimaryDark">#5904DF</item>
|
||||||
<item name="carColorSecondary">#328E10</item>
|
<item name="carColorSecondary">#328E10</item>
|
||||||
<item name="carColorSecondaryDark">#1A6004</item>
|
<item name="carColorSecondaryDark">#1A6004</item>
|
||||||
<item name="carPermissionActivityLayout">@layout/permission_request</item>
|
|
||||||
<item name="markerIconTintColor">#FF7F39FB</item>
|
<item name="markerIconTintColor">#FF7F39FB</item>
|
||||||
<item name="markerIconTintColorDark">#FF5904DF</item>
|
<item name="markerIconTintColorDark">#FF5904DF</item>
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -9,21 +9,20 @@ import com.kouros.navigation.model.RouteModel
|
|||||||
import com.kouros.navigation.model.ViewModel
|
import com.kouros.navigation.model.ViewModel
|
||||||
import org.junit.Assert.assertEquals
|
import org.junit.Assert.assertEquals
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import org.junit.runner.RunWith
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Example local unit test, which will execute on the development machine (host).
|
|
||||||
*
|
*
|
||||||
* See [testing documentation](http://d.android.com/tools/testing).
|
* See [testing documentation](http://d.android.com/tools/testing).
|
||||||
*/
|
*/
|
||||||
class ExampleUnitTest {
|
class ViewModelTest {
|
||||||
|
|
||||||
@Test
|
|
||||||
fun addition_isCorrect() {
|
|
||||||
assertEquals(4, 2 + 2)
|
|
||||||
val model = RouteModel()
|
|
||||||
val repo = NavigationRepository()
|
val repo = NavigationRepository()
|
||||||
val viewModel = ViewModel(repo)
|
val viewModel = ViewModel(repo)
|
||||||
|
val model = RouteModel()
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun routeViewModelTest() {
|
||||||
|
|
||||||
val fromLocation = Location(LocationManager.GPS_PROVIDER)
|
val fromLocation = Location(LocationManager.GPS_PROVIDER)
|
||||||
fromLocation.latitude = homeLocation.latitude
|
fromLocation.latitude = homeLocation.latitude
|
||||||
fromLocation.longitude = homeLocation.longitude
|
fromLocation.longitude = homeLocation.longitude
|
||||||
|
|||||||
@@ -0,0 +1,7 @@
|
|||||||
|
package com.kouros.navigation.data
|
||||||
|
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
|
||||||
|
val NavigationColor = Color(0xFF1965D9)
|
||||||
|
|
||||||
|
val RouteColor = Color(0xFF2E75E1)
|
||||||
@@ -119,9 +119,10 @@ data class ValhallaLocation (
|
|||||||
object Constants {
|
object Constants {
|
||||||
|
|
||||||
const val STYLE: String = "https://kouros-online.de/liberty.json"
|
const val STYLE: String = "https://kouros-online.de/liberty.json"
|
||||||
|
const val STYLE_DARK: String = "https://kouros-online.de/liberty_night.json"
|
||||||
//const val STYLE: String = "https://tiles.openfreemap.org/styles/liberty"
|
//const val STYLE: String = "https://tiles.openfreemap.org/styles/liberty"
|
||||||
|
|
||||||
//const val STYLE: String = "https://tiles.openfreemap.org/styles/dark"
|
|
||||||
const val TAG: String = "Navigation"
|
const val TAG: String = "Navigation"
|
||||||
|
|
||||||
const val CONTACTS: String = "Contacts"
|
const val CONTACTS: String = "Contacts"
|
||||||
@@ -145,6 +146,10 @@ object Constants {
|
|||||||
|
|
||||||
const val SHOW_THREED_BUILDING = "Show3D"
|
const val SHOW_THREED_BUILDING = "Show3D"
|
||||||
|
|
||||||
|
const val NEXT_STEP_THRESHOLD = 100.0
|
||||||
|
|
||||||
|
const val MAXIMAL_ROUTE_DEVIATION = 70.0
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ class NavigationRepository {
|
|||||||
|
|
||||||
private val routeUrl = "https://kouros-online.de/valhalla/route?json="
|
private val routeUrl = "https://kouros-online.de/valhalla/route?json="
|
||||||
|
|
||||||
|
private val nominatimUrl = "https://nominatim.openstreetmap.org/search?q="
|
||||||
fun getRoute(currentLocation : Location, location: Location): String {
|
fun getRoute(currentLocation : Location, location: Location): String {
|
||||||
val vLocation = listOf(
|
val vLocation = listOf(
|
||||||
Locations(lat = currentLocation.latitude, lon = currentLocation.longitude),
|
Locations(lat = currentLocation.latitude, lon = currentLocation.longitude),
|
||||||
@@ -45,7 +46,7 @@ class NavigationRepository {
|
|||||||
language = "de-DE"
|
language = "de-DE"
|
||||||
)
|
)
|
||||||
val routeLocation = Json.encodeToString(valhallaLocation)
|
val routeLocation = Json.encodeToString(valhallaLocation)
|
||||||
return fetchUrl(routeUrl + routeLocation)
|
return fetchUrl(routeUrl + routeLocation, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getRouteDistance(currentLocation : Location, location: Location): Double {
|
fun getRouteDistance(currentLocation : Location, location: Location): Double {
|
||||||
@@ -55,9 +56,13 @@ class NavigationRepository {
|
|||||||
return routeModel.route.distance
|
return routeModel.route.distance
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun searchPlaces(search : String) : String {
|
||||||
|
return fetchUrl("$nominatimUrl$search&format=jsonv2&addressdetails=true&countrycodes=de", false)
|
||||||
|
}
|
||||||
|
|
||||||
fun getPlaces(): List<Place> {
|
fun getPlaces(): List<Place> {
|
||||||
val places: MutableList<Place> = ArrayList()
|
val places: MutableList<Place> = ArrayList()
|
||||||
val placesStr = fetchUrl(placesUrl)
|
val placesStr = fetchUrl(placesUrl, true)
|
||||||
val jArray = JSONArray(placesStr)
|
val jArray = JSONArray(placesStr)
|
||||||
for (i in 0..<jArray.length()) {
|
for (i in 0..<jArray.length()) {
|
||||||
val json = jArray.getJSONObject(i)
|
val json = jArray.getJSONObject(i)
|
||||||
@@ -77,8 +82,9 @@ class NavigationRepository {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private fun fetchUrl(url: String): String {
|
private fun fetchUrl(url: String, authenticator : Boolean): String {
|
||||||
try {
|
try {
|
||||||
|
if (authenticator) {
|
||||||
Authenticator.setDefault(object : Authenticator() {
|
Authenticator.setDefault(object : Authenticator() {
|
||||||
override fun getPasswordAuthentication(): PasswordAuthentication {
|
override fun getPasswordAuthentication(): PasswordAuthentication {
|
||||||
return PasswordAuthentication(
|
return PasswordAuthentication(
|
||||||
@@ -87,12 +93,14 @@ class NavigationRepository {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
}
|
||||||
println(url)
|
println(url)
|
||||||
val httpURLConnection = URL(url).openConnection() as HttpURLConnection
|
val httpURLConnection = URL(url).openConnection() as HttpURLConnection
|
||||||
httpURLConnection.setRequestProperty(
|
httpURLConnection.setRequestProperty(
|
||||||
"Accept",
|
"Accept",
|
||||||
"application/json"
|
"application/json"
|
||||||
) // The format of response we want to get from the server
|
) // The format of response we want to get from the server
|
||||||
|
httpURLConnection.setRequestProperty("User-Agent", "email=nominatim@kouros-online.de");
|
||||||
httpURLConnection.requestMethod = "GET"
|
httpURLConnection.requestMethod = "GET"
|
||||||
val responseCode = httpURLConnection.responseCode
|
val responseCode = httpURLConnection.responseCode
|
||||||
if (responseCode == HttpURLConnection.HTTP_OK) {
|
if (responseCode == HttpURLConnection.HTTP_OK) {
|
||||||
@@ -101,7 +109,7 @@ class NavigationRepository {
|
|||||||
return response
|
return response
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
println(e.message)
|
println("Exception ${e.message}")
|
||||||
}
|
}
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ data class Route (
|
|||||||
|
|
||||||
var routeGeoJson : String,
|
var routeGeoJson : String,
|
||||||
|
|
||||||
var currentIndex: Int
|
var currentManeuverIndex: Int
|
||||||
|
|
||||||
) {
|
) {
|
||||||
|
|
||||||
@@ -103,10 +103,10 @@ data class Route (
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun currentManeuver() : Maneuvers {
|
fun currentManeuver() : Maneuvers {
|
||||||
return maneuvers[currentIndex]
|
return maneuvers[currentManeuverIndex]
|
||||||
}
|
}
|
||||||
|
|
||||||
fun nextManeuver() : Maneuvers {
|
fun nextManeuver() : Maneuvers {
|
||||||
return maneuvers[currentIndex+1]
|
return maneuvers[currentManeuverIndex+1]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
package com.kouros.navigation.data.nominatim
|
||||||
|
|
||||||
|
import com.google.gson.annotations.SerializedName
|
||||||
|
import kotlinx.serialization.ExperimentalSerializationApi
|
||||||
|
import kotlinx.serialization.json.JsonIgnoreUnknownKeys
|
||||||
|
|
||||||
|
@OptIn(ExperimentalSerializationApi::class)
|
||||||
|
@JsonIgnoreUnknownKeys
|
||||||
|
data class Address(
|
||||||
|
@SerializedName("city") var city: String = "",
|
||||||
|
@SerializedName("country") var country: String = "",
|
||||||
|
@SerializedName("country_code") var countryCode: String = "",
|
||||||
|
@SerializedName("neighbourhood") var neighbourhood: String = "",
|
||||||
|
@SerializedName("postcode") var postcode: String = "",
|
||||||
|
@SerializedName("road") var road: String = "",
|
||||||
|
@SerializedName("state") var state: String = "",
|
||||||
|
@SerializedName("suburb") var suburb: String = "",
|
||||||
|
)
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
package com.kouros.navigation.data.nominatim
|
||||||
|
|
||||||
|
import com.google.gson.annotations.SerializedName
|
||||||
|
import kotlinx.serialization.ExperimentalSerializationApi
|
||||||
|
import kotlinx.serialization.json.JsonIgnoreUnknownKeys
|
||||||
|
|
||||||
|
|
||||||
|
class Search : ArrayList<SearchResult>()
|
||||||
|
|
||||||
|
@OptIn(ExperimentalSerializationApi::class)
|
||||||
|
@JsonIgnoreUnknownKeys
|
||||||
|
data class SearchResult(
|
||||||
|
|
||||||
|
@SerializedName("place_id") var placeId: Int = 0,
|
||||||
|
@SerializedName("licence") var licence: String = "",
|
||||||
|
@SerializedName("osm_type") var osmType: String = "",
|
||||||
|
@SerializedName("osm_id") var osmId: Long = 0,
|
||||||
|
@SerializedName("lat") var lat: String = "",
|
||||||
|
@SerializedName("lon") var lon: String = "",
|
||||||
|
@SerializedName("category") var category: String = "",
|
||||||
|
@SerializedName("type") var type: String = "",
|
||||||
|
@SerializedName("place_rank") var placeRank: Int = 0,
|
||||||
|
@SerializedName("importance") var importance: Double = 0.0,
|
||||||
|
@SerializedName("addresstype") var addresstype: String = "",
|
||||||
|
@SerializedName("address") var address: Address,
|
||||||
|
@SerializedName("name") var name: String = "",
|
||||||
|
@SerializedName("display_name") var displayName: String = "",
|
||||||
|
@SerializedName("boundingbox") var boundingbox: ArrayList<String> = arrayListOf()
|
||||||
|
|
||||||
|
)
|
||||||
@@ -2,11 +2,10 @@ package com.kouros.navigation.model
|
|||||||
|
|
||||||
import android.location.Location
|
import android.location.Location
|
||||||
import android.location.LocationManager
|
import android.location.LocationManager
|
||||||
import com.kouros.navigation.data.Constants.homeLocation
|
import com.kouros.navigation.data.Constants.NEXT_STEP_THRESHOLD
|
||||||
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.StepData
|
import com.kouros.navigation.data.StepData
|
||||||
import com.kouros.navigation.utils.NavigationUtils
|
|
||||||
import com.kouros.navigation.utils.location
|
import com.kouros.navigation.utils.location
|
||||||
import org.maplibre.geojson.FeatureCollection
|
import org.maplibre.geojson.FeatureCollection
|
||||||
import org.maplibre.geojson.Point
|
import org.maplibre.geojson.Point
|
||||||
@@ -26,9 +25,9 @@ open class RouteModel() {
|
|||||||
var maneuverType = 0
|
var maneuverType = 0
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Index in a maneuver
|
current shapeIndex
|
||||||
*/
|
*/
|
||||||
var currentIndex = 0
|
private var currentShapeIndex = 0
|
||||||
|
|
||||||
var distanceToStepEnd = 0F
|
var distanceToStepEnd = 0F
|
||||||
|
|
||||||
@@ -64,7 +63,7 @@ open class RouteModel() {
|
|||||||
|
|
||||||
fun updateLocation(location: Location) {
|
fun updateLocation(location: Location) {
|
||||||
var nearestDistance = 100000.0f
|
var nearestDistance = 100000.0f
|
||||||
route.currentIndex = -1
|
route.currentManeuverIndex = -1
|
||||||
// find maneuver
|
// find maneuver
|
||||||
for ((i, maneuver) in route.maneuvers.withIndex()) {
|
for ((i, maneuver) in route.maneuvers.withIndex()) {
|
||||||
val beginShapeIndex = maneuver.beginShapeIndex
|
val beginShapeIndex = maneuver.beginShapeIndex
|
||||||
@@ -72,7 +71,7 @@ open class RouteModel() {
|
|||||||
val distance = calculateDistance(beginShapeIndex, endShapeIndex, location)
|
val distance = calculateDistance(beginShapeIndex, endShapeIndex, location)
|
||||||
if (distance < nearestDistance) {
|
if (distance < nearestDistance) {
|
||||||
nearestDistance = distance
|
nearestDistance = distance
|
||||||
route.currentIndex = i
|
route.currentManeuverIndex = i
|
||||||
calculateCurrentIndex(beginShapeIndex, endShapeIndex, location)
|
calculateCurrentIndex(beginShapeIndex, endShapeIndex, location)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -84,13 +83,13 @@ open class RouteModel() {
|
|||||||
if (maneuver.streetNames != null && maneuver.streetNames.isNotEmpty()) {
|
if (maneuver.streetNames != null && maneuver.streetNames.isNotEmpty()) {
|
||||||
text = maneuver.streetNames[0]
|
text = maneuver.streetNames[0]
|
||||||
}
|
}
|
||||||
if (bearing == 0F) {
|
val curLocation = location(route.pointLocations[currentShapeIndex].latitude(), route.pointLocations[currentShapeIndex].longitude())
|
||||||
bearing = maneuver.bearingAfter.toFloat()
|
val nextLocation = location(route.pointLocations[currentShapeIndex+1].latitude(), route.pointLocations[currentShapeIndex+1].longitude())
|
||||||
}
|
bearing = curLocation.bearingTo(nextLocation)
|
||||||
val distanceStepLeft = leftStepDistance() * 1000
|
val distanceStepLeft = leftStepDistance() * 1000
|
||||||
when (distanceStepLeft) {
|
when (distanceStepLeft) {
|
||||||
in 0.0..100.0 -> {
|
in 0.0..NEXT_STEP_THRESHOLD -> {
|
||||||
if (route.currentIndex < route.maneuvers.size) {
|
if (route.currentManeuverIndex < route.maneuvers.size) {
|
||||||
val maneuver = route.nextManeuver()
|
val maneuver = route.nextManeuver()
|
||||||
if (maneuver.streetNames != null && maneuver.streetNames.isNotEmpty()) {
|
if (maneuver.streetNames != null && maneuver.streetNames.isNotEmpty()) {
|
||||||
text = maneuver.streetNames[0]
|
text = maneuver.streetNames[0]
|
||||||
@@ -109,13 +108,13 @@ open class RouteModel() {
|
|||||||
) {
|
) {
|
||||||
var nearestLocation = 100000.0f
|
var nearestLocation = 100000.0f
|
||||||
for (i in beginShapeIndex..endShapeIndex) {
|
for (i in beginShapeIndex..endShapeIndex) {
|
||||||
val polylineLocation = Location(LocationManager.GPS_PROVIDER)
|
val waypoint = Location(LocationManager.GPS_PROVIDER)
|
||||||
polylineLocation.longitude = route.waypoints[i][0]
|
waypoint.longitude = route.waypoints[i][0]
|
||||||
polylineLocation.latitude = route.waypoints[i][1]
|
waypoint.latitude = route.waypoints[i][1]
|
||||||
val distance: Float = location.distanceTo(polylineLocation)
|
val distance: Float = location.distanceTo(waypoint)
|
||||||
if (distance < nearestLocation) {
|
if (distance < nearestLocation) {
|
||||||
nearestLocation = distance
|
nearestLocation = distance
|
||||||
currentIndex = i
|
currentShapeIndex = i
|
||||||
beginIndex = beginShapeIndex
|
beginIndex = beginShapeIndex
|
||||||
endIndex = endShapeIndex
|
endIndex = endShapeIndex
|
||||||
distanceToStepEnd = 0F
|
distanceToStepEnd = 0F
|
||||||
@@ -157,14 +156,14 @@ open class RouteModel() {
|
|||||||
|
|
||||||
fun travelLeftTime(): Double {
|
fun travelLeftTime(): Double {
|
||||||
var timeLeft = 0.0
|
var timeLeft = 0.0
|
||||||
for (i in route.currentIndex + 1..<route.maneuvers.size) {
|
for (i in route.currentManeuverIndex + 1..<route.maneuvers.size) {
|
||||||
val maneuver = route.maneuvers[i]
|
val maneuver = route.maneuvers[i]
|
||||||
timeLeft += maneuver.time
|
timeLeft += maneuver.time
|
||||||
}
|
}
|
||||||
if (endIndex > 0) {
|
if (endIndex > 0) {
|
||||||
val maneuver = route.currentManeuver()
|
val maneuver = route.currentManeuver()
|
||||||
val curTime = maneuver.time
|
val curTime = maneuver.time
|
||||||
val percent = 100 * (endIndex - currentIndex) / (endIndex - beginIndex)
|
val percent = 100 * (endIndex - currentShapeIndex) / (endIndex - beginIndex)
|
||||||
val time = curTime * percent / 100
|
val time = curTime * percent / 100
|
||||||
timeLeft += time
|
timeLeft += time
|
||||||
}
|
}
|
||||||
@@ -183,14 +182,14 @@ open class RouteModel() {
|
|||||||
|
|
||||||
fun travelLeftDistance(): Double {
|
fun travelLeftDistance(): Double {
|
||||||
var leftDistance = 0.0
|
var leftDistance = 0.0
|
||||||
for (i in route.currentIndex + 1..<route.maneuvers.size) {
|
for (i in route.currentManeuverIndex + 1..<route.maneuvers.size) {
|
||||||
val maneuver = route.maneuvers[i]
|
val maneuver = route.maneuvers[i]
|
||||||
leftDistance += maneuver.length
|
leftDistance += maneuver.length
|
||||||
}
|
}
|
||||||
if (endIndex > 0) {
|
if (endIndex > 0) {
|
||||||
val maneuver = route.currentManeuver()
|
val maneuver = route.currentManeuver()
|
||||||
val curDistance = maneuver.length
|
val curDistance = maneuver.length
|
||||||
val percent = 100 * (endIndex - currentIndex) / (endIndex - beginIndex)
|
val percent = 100 * (endIndex - currentShapeIndex) / (endIndex - beginIndex)
|
||||||
val time = curDistance * percent / 100
|
val time = curDistance * percent / 100
|
||||||
leftDistance += time
|
leftDistance += time
|
||||||
}
|
}
|
||||||
@@ -208,10 +207,9 @@ open class RouteModel() {
|
|||||||
fun stopNavigation() {
|
fun stopNavigation() {
|
||||||
route.clear()
|
route.clear()
|
||||||
navigating = false
|
navigating = false
|
||||||
currentIndex = 0
|
currentShapeIndex = 0
|
||||||
distanceToStepEnd = 0F
|
distanceToStepEnd = 0F
|
||||||
beginIndex = 0
|
beginIndex = 0
|
||||||
endIndex = 0
|
endIndex = 0
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -3,16 +3,16 @@ package com.kouros.navigation.model
|
|||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.location.Geocoder
|
import android.location.Geocoder
|
||||||
import android.location.Location
|
import android.location.Location
|
||||||
import android.location.LocationManager
|
|
||||||
import androidx.lifecycle.MutableLiveData
|
import androidx.lifecycle.MutableLiveData
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
|
import com.google.gson.GsonBuilder
|
||||||
import com.kouros.navigation.data.Constants
|
import com.kouros.navigation.data.Constants
|
||||||
import com.kouros.navigation.data.NavigationRepository
|
import com.kouros.navigation.data.NavigationRepository
|
||||||
import com.kouros.navigation.data.ObjectBox.boxStore
|
import com.kouros.navigation.data.ObjectBox.boxStore
|
||||||
import com.kouros.navigation.data.Place
|
import com.kouros.navigation.data.Place
|
||||||
import com.kouros.navigation.data.Place_
|
import com.kouros.navigation.data.Place_
|
||||||
import com.kouros.navigation.utils.NavigationUtils
|
import com.kouros.navigation.data.nominatim.Search
|
||||||
import com.kouros.navigation.utils.location
|
import com.kouros.navigation.utils.location
|
||||||
import io.objectbox.kotlin.boxFor
|
import io.objectbox.kotlin.boxFor
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
@@ -32,6 +32,10 @@ class ViewModel(private val repository: NavigationRepository) : ViewModel() {
|
|||||||
MutableLiveData<List<Place>>()
|
MutableLiveData<List<Place>>()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val searchPlaces: MutableLiveData<Search> by lazy {
|
||||||
|
MutableLiveData<Search>()
|
||||||
|
}
|
||||||
|
|
||||||
val contactAddress: MutableLiveData<List<Place>> by lazy {
|
val contactAddress: MutableLiveData<List<Place>> by lazy {
|
||||||
MutableLiveData<List<Place>>()
|
MutableLiveData<List<Place>>()
|
||||||
}
|
}
|
||||||
@@ -85,11 +89,13 @@ class ViewModel(private val repository: NavigationRepository) : ViewModel() {
|
|||||||
for (address in addresses) {
|
for (address in addresses) {
|
||||||
val addressLines = address.address.split("\n")
|
val addressLines = address.address.split("\n")
|
||||||
geocoder.getFromLocationName(
|
geocoder.getFromLocationName(
|
||||||
address.address, 5) {
|
address.address, 5
|
||||||
|
) {
|
||||||
for (adr in it) {
|
for (adr in it) {
|
||||||
if (addressLines.size > 1) {
|
if (addressLines.size > 1) {
|
||||||
val plLocation = location(adr.latitude, adr.longitude)
|
val plLocation = location(adr.latitude, adr.longitude)
|
||||||
val distance = repository.getRouteDistance(currentLocation, plLocation)
|
val distance =
|
||||||
|
repository.getRouteDistance(currentLocation, plLocation)
|
||||||
contactList.add(
|
contactList.add(
|
||||||
Place(
|
Place(
|
||||||
id = address.contactId,
|
id = address.contactId,
|
||||||
@@ -115,6 +121,15 @@ class ViewModel(private val repository: NavigationRepository) : ViewModel() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun searchPlaces(search: String) {
|
||||||
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
|
val placesJson = repository.searchPlaces(search)
|
||||||
|
val gson = GsonBuilder().serializeNulls().create()
|
||||||
|
val places = gson.fromJson(placesJson, Search::class.java)
|
||||||
|
searchPlaces.postValue(places)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun saveRecent(place: Place) {
|
fun saveRecent(place: Place) {
|
||||||
viewModelScope.launch(Dispatchers.IO) {
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
place.category = Constants.RECENT
|
place.category = Constants.RECENT
|
||||||
|
|||||||
@@ -4,6 +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.MAXIMAL_ROUTE_DEVIATION
|
||||||
import com.kouros.navigation.data.Constants.SHARED_PREF_KEY
|
import com.kouros.navigation.data.Constants.SHARED_PREF_KEY
|
||||||
import com.kouros.navigation.data.Constants.SHOW_THREED_BUILDING
|
import com.kouros.navigation.data.Constants.SHOW_THREED_BUILDING
|
||||||
import com.kouros.navigation.data.GeoJsonFeature
|
import com.kouros.navigation.data.GeoJsonFeature
|
||||||
@@ -57,11 +58,17 @@ object NavigationUtils {
|
|||||||
}
|
}
|
||||||
fun snapLocation(location: Location, stepCoordinates: List<Point>): Location {
|
fun snapLocation(location: Location, stepCoordinates: List<Point>): Location {
|
||||||
val oldPoint = Point.fromLngLat(location.longitude, location.latitude)
|
val oldPoint = Point.fromLngLat(location.longitude, location.latitude)
|
||||||
|
val oldLocation = location(location.latitude, location.longitude)
|
||||||
if (stepCoordinates.size > 1) {
|
if (stepCoordinates.size > 1) {
|
||||||
val pointFeature = TurfMisc.nearestPointOnLine(oldPoint, stepCoordinates)
|
val pointFeature = TurfMisc.nearestPointOnLine(oldPoint, stepCoordinates)
|
||||||
val point = pointFeature.geometry() as Point
|
val point = pointFeature.geometry() as Point
|
||||||
location.latitude = point.latitude()
|
location.latitude = point.latitude()
|
||||||
location.longitude = point.longitude()
|
location.longitude = point.longitude()
|
||||||
|
val distance = oldLocation.distanceTo(location)
|
||||||
|
if (distance > MAXIMAL_ROUTE_DEVIATION) {
|
||||||
|
println("Distance to big")
|
||||||
|
return location(0.0, 0.0)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return location
|
return location
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,3 +31,6 @@ include(
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
include(":common:automotive")
|
||||||
|
include(":app:automotive")
|
||||||
|
include(":automotive")
|
||||||
|
|||||||