Launcher Icons, NotificationService
@@ -5,3 +5,6 @@
|
|||||||
## Simulation
|
## Simulation
|
||||||
|
|
||||||
adb shell dumpsys activity service com.kouros.navigation.car.NavigationCarAppService AUTO_DRIVE
|
adb shell dumpsys activity service com.kouros.navigation.car.NavigationCarAppService AUTO_DRIVE
|
||||||
|
|
||||||
|
## Signing
|
||||||
|
./gradlew bundleFull
|
||||||
|
|||||||
@@ -1,10 +1,14 @@
|
|||||||
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
|
import java.util.Properties
|
||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
alias(libs.plugins.android.application)
|
alias(libs.plugins.android.application)
|
||||||
alias(libs.plugins.kotlin.compose)
|
alias(libs.plugins.kotlin.compose)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val properties = Properties().apply {
|
||||||
|
load(File("signing.properties").reader())
|
||||||
|
}
|
||||||
|
|
||||||
android {
|
android {
|
||||||
namespace = "com.kouros.navigation"
|
namespace = "com.kouros.navigation"
|
||||||
compileSdk = 36
|
compileSdk = 36
|
||||||
@@ -13,8 +17,8 @@ android {
|
|||||||
applicationId = "com.kouros.navigation"
|
applicationId = "com.kouros.navigation"
|
||||||
minSdk = 33
|
minSdk = 33
|
||||||
targetSdk = 36
|
targetSdk = 36
|
||||||
versionCode = 76
|
versionCode = 82
|
||||||
versionName = "0.2.0.76"
|
versionName = "0.2.0.82"
|
||||||
base.archivesName = "navi-$versionName"
|
base.archivesName = "navi-$versionName"
|
||||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||||
}
|
}
|
||||||
@@ -22,23 +26,22 @@ android {
|
|||||||
signingConfigs {
|
signingConfigs {
|
||||||
getByName("debug") {
|
getByName("debug") {
|
||||||
keyAlias = "release"
|
keyAlias = "release"
|
||||||
keyPassword = "zeta67#gAe3aN3"
|
keyPassword = properties.getProperty("keyPassword")
|
||||||
storeFile = file("/home/kouros/work/keystore/keystoreRelease")
|
storeFile = file(properties.getProperty("storeFile"))
|
||||||
storePassword = "zeta67#gAe3aN3"
|
storePassword = properties.getProperty("storePassword")
|
||||||
}
|
}
|
||||||
create("release") {
|
create("release") {
|
||||||
keyAlias = "release"
|
keyAlias = "release"
|
||||||
keyPassword = "zeta67#gAe3aN3"
|
keyPassword = properties.getProperty("keyPassword")
|
||||||
storeFile = file("/home/kouros/work/keystore/keystoreRelease")
|
storeFile = file(properties.getProperty("storeFile"))
|
||||||
storePassword = "zeta67#gAe3aN3"
|
storePassword = properties.getProperty("storePassword")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
buildTypes {
|
buildTypes {
|
||||||
release {
|
release {
|
||||||
// Enables code-related app optimization.
|
signingConfig = signingConfigs.getByName("release")
|
||||||
isMinifyEnabled = false
|
isMinifyEnabled = false
|
||||||
// Enables resource shrinking.
|
|
||||||
isShrinkResources = false
|
isShrinkResources = false
|
||||||
|
|
||||||
proguardFiles(
|
proguardFiles(
|
||||||
@@ -48,15 +51,20 @@ android {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Specifies one flavor dimension.
|
// Specifies one flavor dimension.
|
||||||
flavorDimensions += "version"
|
flavorDimensions += "store"
|
||||||
productFlavors {
|
productFlavors {
|
||||||
|
create("play") {
|
||||||
|
dimension = "store"
|
||||||
|
applicationIdSuffix = ".play"
|
||||||
|
versionNameSuffix = "-play"
|
||||||
|
}
|
||||||
create("demo") {
|
create("demo") {
|
||||||
dimension = "version"
|
dimension = "store"
|
||||||
applicationIdSuffix = ".demo"
|
applicationIdSuffix = ".demo"
|
||||||
versionNameSuffix = "-demo"
|
versionNameSuffix = "-demo"
|
||||||
}
|
}
|
||||||
create("full") {
|
create("full") {
|
||||||
dimension = "version"
|
dimension = "store"
|
||||||
applicationIdSuffix = ".full"
|
applicationIdSuffix = ".full"
|
||||||
versionNameSuffix = "-full"
|
versionNameSuffix = "-full"
|
||||||
}
|
}
|
||||||
@@ -66,11 +74,20 @@ android {
|
|||||||
targetCompatibility = JavaVersion.VERSION_21
|
targetCompatibility = JavaVersion.VERSION_21
|
||||||
}
|
}
|
||||||
|
|
||||||
buildFeatures {
|
packaging {
|
||||||
compose = true
|
resources {
|
||||||
|
excludes +=
|
||||||
|
setOf(
|
||||||
|
"/META-INF/{AL2.0,LGPL2.1}",
|
||||||
|
"/META-INF/*.version",
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
buildFeatures {
|
||||||
|
compose = true
|
||||||
|
buildConfig = true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
@@ -95,7 +112,6 @@ dependencies {
|
|||||||
implementation(libs.androidx.compose.ui.graphics)
|
implementation(libs.androidx.compose.ui.graphics)
|
||||||
implementation(libs.androidx.window)
|
implementation(libs.androidx.window)
|
||||||
implementation(libs.androidx.compose.foundation.layout)
|
implementation(libs.androidx.compose.foundation.layout)
|
||||||
implementation(libs.android.gpx.parser)
|
|
||||||
implementation(libs.androidx.navigation.compose)
|
implementation(libs.androidx.navigation.compose)
|
||||||
implementation(libs.kotlinx.serialization.json)
|
implementation(libs.kotlinx.serialization.json)
|
||||||
implementation(libs.androidx.compose.foundation.layout)
|
implementation(libs.androidx.compose.foundation.layout)
|
||||||
@@ -107,4 +123,3 @@ dependencies {
|
|||||||
debugImplementation(libs.androidx.compose.ui.tooling)
|
debugImplementation(libs.androidx.compose.ui.tooling)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 37 KiB After Width: | Height: | Size: 28 KiB |
@@ -1,20 +1,12 @@
|
|||||||
package com.kouros.navigation.model
|
package com.kouros.navigation.model
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import androidx.lifecycle.lifecycleScope
|
|
||||||
import com.kouros.data.R
|
|
||||||
import com.kouros.navigation.MainApplication.Companion.navigationViewModel
|
import com.kouros.navigation.MainApplication.Companion.navigationViewModel
|
||||||
import com.kouros.navigation.utils.location
|
import com.kouros.navigation.utils.location
|
||||||
import io.ticofab.androidgpxparser.parser.GPXParser
|
|
||||||
import io.ticofab.androidgpxparser.parser.domain.Gpx
|
|
||||||
import io.ticofab.androidgpxparser.parser.domain.TrackSegment
|
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
import kotlinx.coroutines.delay
|
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import org.joda.time.DateTime
|
|
||||||
import kotlin.collections.forEach
|
|
||||||
|
|
||||||
var simulationJob: Job? = null
|
var simulationJob: Job? = null
|
||||||
fun simulate(routeModel: RouteModel, mock: MockLocation) {
|
fun simulate(routeModel: RouteModel, mock: MockLocation) {
|
||||||
@@ -91,43 +83,6 @@ fun testSingleUpdate(
|
|||||||
Thread.sleep(1_000)
|
Thread.sleep(1_000)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun gpx(context: Context, mock: MockLocation) {
|
|
||||||
CoroutineScope(Dispatchers.IO).launch {
|
|
||||||
var lastLocation = location(0.0, 0.0)
|
|
||||||
val parser = GPXParser()
|
|
||||||
val resourceId: Int = context.resources
|
|
||||||
.getIdentifier("vh", "raw", context.packageName)
|
|
||||||
val input = context.resources.openRawResource(resourceId)
|
|
||||||
val parsedGpx: Gpx? = parser.parse(input) // consider using a background thread
|
|
||||||
parsedGpx?.let {
|
|
||||||
val tracks = parsedGpx.tracks
|
|
||||||
tracks.forEach { tr ->
|
|
||||||
val segments: MutableList<TrackSegment?>? = tr.trackSegments
|
|
||||||
segments!!.forEach { seg ->
|
|
||||||
var lastTime = DateTime.now()
|
|
||||||
seg!!.trackPoints.forEach { p ->
|
|
||||||
val curLocation = location(p.longitude, p.latitude)
|
|
||||||
val ext = p.extensions
|
|
||||||
val speed: Double?
|
|
||||||
if (ext != null) {
|
|
||||||
speed = ext.speed
|
|
||||||
mock.curSpeed = speed.toFloat()
|
|
||||||
}
|
|
||||||
val duration = p.time.millis - lastTime.millis
|
|
||||||
val bearing = lastLocation.bearingTo(curLocation)
|
|
||||||
mock.setMockLocation(p.latitude, p.longitude, bearing)
|
|
||||||
if (duration > 0) {
|
|
||||||
delay(duration / 5)
|
|
||||||
}
|
|
||||||
lastTime = p.time
|
|
||||||
lastLocation = curLocation
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
enum class SimulationType {
|
enum class SimulationType {
|
||||||
SIMULATE, TEST, GPX, TEST_SINGLE
|
SIMULATE, TEST, GPX, TEST_SINGLE
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -50,7 +50,6 @@ import com.kouros.navigation.model.BaseStyleModel
|
|||||||
import com.kouros.navigation.model.MockLocation
|
import com.kouros.navigation.model.MockLocation
|
||||||
import com.kouros.navigation.model.RouteModel
|
import com.kouros.navigation.model.RouteModel
|
||||||
import com.kouros.navigation.model.SimulationType
|
import com.kouros.navigation.model.SimulationType
|
||||||
import com.kouros.navigation.model.gpx
|
|
||||||
import com.kouros.navigation.model.simulate
|
import com.kouros.navigation.model.simulate
|
||||||
import com.kouros.navigation.model.simulationJob
|
import com.kouros.navigation.model.simulationJob
|
||||||
import com.kouros.navigation.model.test
|
import com.kouros.navigation.model.test
|
||||||
@@ -112,11 +111,10 @@ class MainActivity : ComponentActivity() {
|
|||||||
when (type) {
|
when (type) {
|
||||||
SimulationType.SIMULATE -> simulate(routeModel, mock)
|
SimulationType.SIMULATE -> simulate(routeModel, mock)
|
||||||
SimulationType.TEST -> test(applicationContext, routeModel)
|
SimulationType.TEST -> test(applicationContext, routeModel)
|
||||||
SimulationType.GPX -> gpx(
|
|
||||||
context = applicationContext, mock
|
|
||||||
)
|
|
||||||
|
|
||||||
SimulationType.TEST_SINGLE -> testSingle(applicationContext, routeModel, mock)
|
SimulationType.TEST_SINGLE -> testSingle(applicationContext, routeModel, mock)
|
||||||
|
else -> {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,10 +13,10 @@ import androidx.compose.ui.Alignment
|
|||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.lifecycle.MutableLiveData
|
import androidx.lifecycle.MutableLiveData
|
||||||
import androidx.window.layout.WindowMetricsCalculator
|
import androidx.window.layout.WindowMetricsCalculator
|
||||||
import com.kouros.navigation.car.ViewStyle
|
|
||||||
import com.kouros.navigation.car.map.MapLibre
|
import com.kouros.navigation.car.map.MapLibre
|
||||||
import com.kouros.navigation.car.map.NavigationImage
|
import com.kouros.navigation.car.map.NavigationImage
|
||||||
import com.kouros.navigation.data.StepData
|
import com.kouros.navigation.data.StepData
|
||||||
|
import com.kouros.navigation.data.ViewStyle
|
||||||
import com.kouros.navigation.ui.app.AppViewModel
|
import com.kouros.navigation.ui.app.AppViewModel
|
||||||
import com.kouros.navigation.ui.app.appViewModel
|
import com.kouros.navigation.ui.app.appViewModel
|
||||||
import com.kouros.navigation.ui.navigation.NavigationInfo
|
import com.kouros.navigation.ui.navigation.NavigationInfo
|
||||||
|
|||||||
@@ -48,5 +48,11 @@ fun AppNavGraph(mainActivity: MainActivity) {
|
|||||||
navController
|
navController
|
||||||
) { navController.popBackStack() }
|
) { navController.popBackStack() }
|
||||||
}
|
}
|
||||||
|
composable("car_settings") {
|
||||||
|
SettingsRoute(
|
||||||
|
"car_settings",
|
||||||
|
navController
|
||||||
|
) { navController.popBackStack() }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,90 @@
|
|||||||
|
package com.kouros.navigation.ui.settings
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.Spacer
|
||||||
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.height
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.size
|
||||||
|
import androidx.compose.foundation.rememberScrollState
|
||||||
|
import androidx.compose.foundation.verticalScroll
|
||||||
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.IconButton
|
||||||
|
import androidx.compose.material3.OutlinedCard
|
||||||
|
import androidx.compose.material3.Scaffold
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.material3.TopAppBar
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.collectAsState
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.res.painterResource
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import com.kouros.data.R
|
||||||
|
import com.kouros.navigation.model.SettingsViewModel
|
||||||
|
import com.kouros.navigation.ui.components.RadioButtonSingleSelection
|
||||||
|
import com.kouros.navigation.ui.components.SectionTitle
|
||||||
|
|
||||||
|
|
||||||
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
|
@Composable
|
||||||
|
fun CarScreen(viewModel: SettingsViewModel, navigateBack: () -> Unit) {
|
||||||
|
|
||||||
|
val engineType by viewModel.engineType.collectAsState()
|
||||||
|
|
||||||
|
Scaffold(
|
||||||
|
topBar = {
|
||||||
|
TopAppBar(
|
||||||
|
title = {
|
||||||
|
Text(
|
||||||
|
stringResource(id = R.string.car_settings),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
navigationIcon = {
|
||||||
|
IconButton(onClick = navigateBack) {
|
||||||
|
Icon(
|
||||||
|
painter = painterResource(R.drawable.arrow_back_24px),
|
||||||
|
contentDescription = stringResource(id = R.string.accept_action_title),
|
||||||
|
modifier = Modifier.size(48.dp, 48.dp),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
{ paddingValues ->
|
||||||
|
val scrollState = rememberScrollState()
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(paddingValues)
|
||||||
|
.fillMaxSize()
|
||||||
|
.padding(top = 10.dp)
|
||||||
|
.verticalScroll(scrollState)
|
||||||
|
) {
|
||||||
|
|
||||||
|
|
||||||
|
OutlinedCard(modifier = Modifier.fillMaxWidth()) {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier.padding(10.dp),
|
||||||
|
) {
|
||||||
|
SectionTitle(stringResource(R.string.engine_type))
|
||||||
|
|
||||||
|
val radioOptions = listOf(
|
||||||
|
stringResource(R.string.combustion),
|
||||||
|
stringResource(R.string.electric),
|
||||||
|
)
|
||||||
|
RadioButtonSingleSelection(
|
||||||
|
modifier = Modifier.padding(),
|
||||||
|
selectedOption = engineType,
|
||||||
|
radioOptions = radioOptions,
|
||||||
|
onClick = viewModel::onEngineTypeChanged
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -39,4 +39,7 @@ fun SettingsRoute(route: String, navController: NavHostController, function: ()
|
|||||||
if (route == "settings_screen") {
|
if (route == "settings_screen") {
|
||||||
SettingsScreen(viewModel, navController, function)
|
SettingsScreen(viewModel, navController, function)
|
||||||
}
|
}
|
||||||
|
if (route == "car_settings") {
|
||||||
|
CarScreen (viewModel = viewModel, function)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -51,7 +51,14 @@ fun SettingsScreen(
|
|||||||
name = "Navigation Settings",
|
name = "Navigation Settings",
|
||||||
description = "",
|
description = "",
|
||||||
icon = R.drawable.navigation_24px
|
icon = R.drawable.navigation_24px
|
||||||
|
),
|
||||||
|
Item(
|
||||||
|
id = "car_settings",
|
||||||
|
name = "Car Settings",
|
||||||
|
description = "",
|
||||||
|
icon = R.drawable.electric_car_24px
|
||||||
)
|
)
|
||||||
|
|
||||||
)
|
)
|
||||||
|
|
||||||
Scaffold(
|
Scaffold(
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
android:height="108dp"
|
android:height="108dp"
|
||||||
android:viewportWidth="960"
|
android:viewportWidth="960"
|
||||||
android:viewportHeight="960"
|
android:viewportHeight="960"
|
||||||
android:tint="#000000">
|
android:tint="#1A7416">
|
||||||
<group android:scaleX="0.7888"
|
<group android:scaleX="0.7888"
|
||||||
android:scaleY="0.7888"
|
android:scaleY="0.7888"
|
||||||
android:translateX="101.376"
|
android:translateX="101.376"
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
<background android:drawable="@drawable/ic_launcher_background"/>
|
<background android:drawable="@color/ic_launcher_background"/>
|
||||||
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
|
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
|
||||||
|
<monochrome android:drawable="@drawable/ic_launcher_foreground"/>
|
||||||
</adaptive-icon>
|
</adaptive-icon>
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
<background android:drawable="@drawable/ic_launcher_background"/>
|
<background android:drawable="@color/ic_launcher_background"/>
|
||||||
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
|
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
|
||||||
|
<monochrome android:drawable="@drawable/ic_launcher_foreground"/>
|
||||||
</adaptive-icon>
|
</adaptive-icon>
|
||||||
|
Before Width: | Height: | Size: 2.9 KiB After Width: | Height: | Size: 2.1 KiB |
|
Before Width: | Height: | Size: 3.9 KiB After Width: | Height: | Size: 3.5 KiB |
|
Before Width: | Height: | Size: 2.3 KiB After Width: | Height: | Size: 1.6 KiB |
|
Before Width: | Height: | Size: 2.5 KiB After Width: | Height: | Size: 2.1 KiB |
|
Before Width: | Height: | Size: 3.7 KiB After Width: | Height: | Size: 2.8 KiB |
|
Before Width: | Height: | Size: 5.1 KiB After Width: | Height: | Size: 4.7 KiB |
|
Before Width: | Height: | Size: 5.6 KiB After Width: | Height: | Size: 4.2 KiB |
|
Before Width: | Height: | Size: 8.4 KiB After Width: | Height: | Size: 7.5 KiB |
|
Before Width: | Height: | Size: 7.3 KiB |
|
Before Width: | Height: | Size: 12 KiB |
4
app/src/main/res/values/ic_launcher_background.xml
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<color name="ic_launcher_background">#98DABB</color>
|
||||||
|
</resources>
|
||||||
@@ -1,170 +1,74 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
<vector
|
||||||
android:width="108dp"
|
|
||||||
android:height="108dp"
|
android:height="108dp"
|
||||||
|
android:width="108dp"
|
||||||
|
android:viewportHeight="108"
|
||||||
android:viewportWidth="108"
|
android:viewportWidth="108"
|
||||||
android:viewportHeight="108">
|
xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
<path
|
<path android:fillColor="#3DDC84"
|
||||||
android:fillColor="#3DDC84"
|
android:pathData="M0,0h108v108h-108z"/>
|
||||||
android:pathData="M0,0h108v108h-108z" />
|
<path android:fillColor="#00000000" android:pathData="M9,0L9,108"
|
||||||
<path
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||||
android:fillColor="#00000000"
|
<path android:fillColor="#00000000" android:pathData="M19,0L19,108"
|
||||||
android:pathData="M9,0L9,108"
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||||
android:strokeWidth="0.8"
|
<path android:fillColor="#00000000" android:pathData="M29,0L29,108"
|
||||||
android:strokeColor="#33FFFFFF" />
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||||
<path
|
<path android:fillColor="#00000000" android:pathData="M39,0L39,108"
|
||||||
android:fillColor="#00000000"
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||||
android:pathData="M19,0L19,108"
|
<path android:fillColor="#00000000" android:pathData="M49,0L49,108"
|
||||||
android:strokeWidth="0.8"
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||||
android:strokeColor="#33FFFFFF" />
|
<path android:fillColor="#00000000" android:pathData="M59,0L59,108"
|
||||||
<path
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||||
android:fillColor="#00000000"
|
<path android:fillColor="#00000000" android:pathData="M69,0L69,108"
|
||||||
android:pathData="M29,0L29,108"
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||||
android:strokeWidth="0.8"
|
<path android:fillColor="#00000000" android:pathData="M79,0L79,108"
|
||||||
android:strokeColor="#33FFFFFF" />
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||||
<path
|
<path android:fillColor="#00000000" android:pathData="M89,0L89,108"
|
||||||
android:fillColor="#00000000"
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||||
android:pathData="M39,0L39,108"
|
<path android:fillColor="#00000000" android:pathData="M99,0L99,108"
|
||||||
android:strokeWidth="0.8"
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||||
android:strokeColor="#33FFFFFF" />
|
<path android:fillColor="#00000000" android:pathData="M0,9L108,9"
|
||||||
<path
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||||
android:fillColor="#00000000"
|
<path android:fillColor="#00000000" android:pathData="M0,19L108,19"
|
||||||
android:pathData="M49,0L49,108"
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||||
android:strokeWidth="0.8"
|
<path android:fillColor="#00000000" android:pathData="M0,29L108,29"
|
||||||
android:strokeColor="#33FFFFFF" />
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||||
<path
|
<path android:fillColor="#00000000" android:pathData="M0,39L108,39"
|
||||||
android:fillColor="#00000000"
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||||
android:pathData="M59,0L59,108"
|
<path android:fillColor="#00000000" android:pathData="M0,49L108,49"
|
||||||
android:strokeWidth="0.8"
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||||
android:strokeColor="#33FFFFFF" />
|
<path android:fillColor="#00000000" android:pathData="M0,59L108,59"
|
||||||
<path
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||||
android:fillColor="#00000000"
|
<path android:fillColor="#00000000" android:pathData="M0,69L108,69"
|
||||||
android:pathData="M69,0L69,108"
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||||
android:strokeWidth="0.8"
|
<path android:fillColor="#00000000" android:pathData="M0,79L108,79"
|
||||||
android:strokeColor="#33FFFFFF" />
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||||
<path
|
<path android:fillColor="#00000000" android:pathData="M0,89L108,89"
|
||||||
android:fillColor="#00000000"
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||||
android:pathData="M79,0L79,108"
|
<path android:fillColor="#00000000" android:pathData="M0,99L108,99"
|
||||||
android:strokeWidth="0.8"
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||||
android:strokeColor="#33FFFFFF" />
|
<path android:fillColor="#00000000" android:pathData="M19,29L89,29"
|
||||||
<path
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||||
android:fillColor="#00000000"
|
<path android:fillColor="#00000000" android:pathData="M19,39L89,39"
|
||||||
android:pathData="M89,0L89,108"
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||||
android:strokeWidth="0.8"
|
<path android:fillColor="#00000000" android:pathData="M19,49L89,49"
|
||||||
android:strokeColor="#33FFFFFF" />
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||||
<path
|
<path android:fillColor="#00000000" android:pathData="M19,59L89,59"
|
||||||
android:fillColor="#00000000"
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||||
android:pathData="M99,0L99,108"
|
<path android:fillColor="#00000000" android:pathData="M19,69L89,69"
|
||||||
android:strokeWidth="0.8"
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||||
android:strokeColor="#33FFFFFF" />
|
<path android:fillColor="#00000000" android:pathData="M19,79L89,79"
|
||||||
<path
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||||
android:fillColor="#00000000"
|
<path android:fillColor="#00000000" android:pathData="M29,19L29,89"
|
||||||
android:pathData="M0,9L108,9"
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||||
android:strokeWidth="0.8"
|
<path android:fillColor="#00000000" android:pathData="M39,19L39,89"
|
||||||
android:strokeColor="#33FFFFFF" />
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||||
<path
|
<path android:fillColor="#00000000" android:pathData="M49,19L49,89"
|
||||||
android:fillColor="#00000000"
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||||
android:pathData="M0,19L108,19"
|
<path android:fillColor="#00000000" android:pathData="M59,19L59,89"
|
||||||
android:strokeWidth="0.8"
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||||
android:strokeColor="#33FFFFFF" />
|
<path android:fillColor="#00000000" android:pathData="M69,19L69,89"
|
||||||
<path
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||||
android:fillColor="#00000000"
|
<path android:fillColor="#00000000" android:pathData="M79,19L79,89"
|
||||||
android:pathData="M0,29L108,29"
|
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||||
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>
|
</vector>
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
android:height="108dp"
|
android:height="108dp"
|
||||||
android:viewportWidth="960"
|
android:viewportWidth="960"
|
||||||
android:viewportHeight="960"
|
android:viewportHeight="960"
|
||||||
android:tint="#000000">
|
android:tint="#1A7416">
|
||||||
<group android:scaleX="0.7888"
|
<group android:scaleX="0.7888"
|
||||||
android:scaleY="0.7888"
|
android:scaleY="0.7888"
|
||||||
android:translateX="101.376"
|
android:translateX="101.376"
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
<background android:drawable="@drawable/ic_launcher_background"/>
|
<background android:drawable="@color/ic_launcher_background"/>
|
||||||
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
|
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
|
||||||
|
<monochrome android:drawable="@drawable/ic_launcher_foreground"/>
|
||||||
</adaptive-icon>
|
</adaptive-icon>
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
<background android:drawable="@drawable/ic_launcher_background"/>
|
<background android:drawable="@color/ic_launcher_background"/>
|
||||||
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
|
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
|
||||||
|
<monochrome android:drawable="@drawable/ic_launcher_foreground"/>
|
||||||
</adaptive-icon>
|
</adaptive-icon>
|
||||||
|
Before Width: | Height: | Size: 2.9 KiB After Width: | Height: | Size: 2.1 KiB |
|
Before Width: | Height: | Size: 3.9 KiB After Width: | Height: | Size: 3.5 KiB |
|
Before Width: | Height: | Size: 2.3 KiB After Width: | Height: | Size: 1.6 KiB |
|
Before Width: | Height: | Size: 2.5 KiB After Width: | Height: | Size: 2.1 KiB |
|
Before Width: | Height: | Size: 3.7 KiB After Width: | Height: | Size: 2.8 KiB |
|
Before Width: | Height: | Size: 5.1 KiB After Width: | Height: | Size: 4.7 KiB |
|
Before Width: | Height: | Size: 5.6 KiB After Width: | Height: | Size: 4.2 KiB |
|
Before Width: | Height: | Size: 8.4 KiB After Width: | Height: | Size: 7.5 KiB |
|
Before Width: | Height: | Size: 7.3 KiB After Width: | Height: | Size: 5.3 KiB |
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 11 KiB |
@@ -29,6 +29,7 @@ android {
|
|||||||
}
|
}
|
||||||
buildFeatures {
|
buildFeatures {
|
||||||
compose = true
|
compose = true
|
||||||
|
buildConfig = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -52,6 +53,7 @@ dependencies {
|
|||||||
implementation(libs.play.services.location)
|
implementation(libs.play.services.location)
|
||||||
implementation(libs.androidx.datastore.core)
|
implementation(libs.androidx.datastore.core)
|
||||||
implementation(libs.androidx.monitor)
|
implementation(libs.androidx.monitor)
|
||||||
|
implementation(libs.android.gpx.parser)
|
||||||
|
|
||||||
androidTestImplementation(libs.androidx.junit)
|
androidTestImplementation(libs.androidx.junit)
|
||||||
androidTestImplementation(libs.androidx.runner)
|
androidTestImplementation(libs.androidx.runner)
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 22 KiB |
|
Before Width: | Height: | Size: 37 KiB |
@@ -137,7 +137,7 @@ class CarSensorManager(
|
|||||||
carCompassListener
|
carCompassListener
|
||||||
)
|
)
|
||||||
carSensors.addCarHardwareLocationListener(
|
carSensors.addCarHardwareLocationListener(
|
||||||
CarSensors.UPDATE_RATE_FASTEST,
|
CarSensors.UPDATE_RATE_NORMAL,
|
||||||
carContext.mainExecutor,
|
carContext.mainExecutor,
|
||||||
carLocationListener
|
carLocationListener
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
package com.kouros.navigation.car
|
package com.kouros.navigation.car
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
import android.location.Location
|
import android.net.Uri
|
||||||
import androidx.car.app.CarAppService
|
import androidx.car.app.CarAppService
|
||||||
import androidx.car.app.Session
|
import androidx.car.app.Session
|
||||||
import androidx.car.app.SessionInfo
|
import androidx.car.app.SessionInfo
|
||||||
@@ -10,6 +10,14 @@ import androidx.car.app.validation.HostValidator
|
|||||||
|
|
||||||
class NavigationCarAppService : CarAppService() {
|
class NavigationCarAppService : CarAppService() {
|
||||||
|
|
||||||
|
val INTENT_ACTION_NAV_NOTIFICATION_OPEN_APP =
|
||||||
|
"com.kouros.navigation.INTENT_ACTION_NAV_NOTIFICATION_OPEN_APP"
|
||||||
|
|
||||||
|
|
||||||
|
fun createDeepLinkUri(deepLinkAction: String): Uri {
|
||||||
|
return Uri.fromParts(NavigationSession.uriScheme, NavigationSession.uriHost, deepLinkAction)
|
||||||
|
}
|
||||||
|
|
||||||
@SuppressLint("PrivateResource")
|
@SuppressLint("PrivateResource")
|
||||||
override fun createHostValidator(): HostValidator {
|
override fun createHostValidator(): HostValidator {
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,232 @@
|
|||||||
|
package com.kouros.navigation.car
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
|
import android.app.Service
|
||||||
|
import android.content.ComponentName
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import android.os.Handler
|
||||||
|
import android.os.IBinder
|
||||||
|
import android.os.Looper
|
||||||
|
import android.os.Message
|
||||||
|
import androidx.car.app.notification.CarAppExtender
|
||||||
|
import androidx.car.app.notification.CarNotificationManager
|
||||||
|
import androidx.car.app.notification.CarPendingIntent
|
||||||
|
import androidx.core.app.NotificationChannelCompat
|
||||||
|
import androidx.core.app.NotificationCompat
|
||||||
|
import androidx.core.app.NotificationManagerCompat
|
||||||
|
import com.kouros.data.R
|
||||||
|
import java.math.RoundingMode
|
||||||
|
import java.text.DecimalFormat
|
||||||
|
import java.util.concurrent.TimeUnit
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A simple foreground service that imitates a client routing service posting navigation
|
||||||
|
* notifications.
|
||||||
|
*/
|
||||||
|
class NavigationNotificationService : Service() {
|
||||||
|
/**
|
||||||
|
* The number of notifications fired so far.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* We use this number to post notifications with a repeating list of directions. See [ ][.getDirectionInfo] for details.
|
||||||
|
*
|
||||||
|
* Note: Package private for inner class reference
|
||||||
|
*/
|
||||||
|
var mNotificationCount: Int = 0
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A handler that posts notifications when given the message request. See [ ] for details.
|
||||||
|
*
|
||||||
|
* Note: Package private for inner class reference
|
||||||
|
*/
|
||||||
|
val mHandler: Handler =
|
||||||
|
Handler(Looper.getMainLooper(), HandlerCallback())
|
||||||
|
|
||||||
|
override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
|
||||||
|
initNotifications(this)
|
||||||
|
startForeground(
|
||||||
|
NAV_NOTIFICATION_ID,
|
||||||
|
getNavigationNotification(this, mNotificationCount).build()
|
||||||
|
)
|
||||||
|
|
||||||
|
// Start updating the notification continuously.
|
||||||
|
mHandler.sendMessageDelayed(
|
||||||
|
mHandler.obtainMessage(MSG_SEND_NOTIFICATION), NAV_NOTIFICATION_DELAY_IN_MILLIS
|
||||||
|
)
|
||||||
|
|
||||||
|
return START_NOT_STICKY
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDestroy() {
|
||||||
|
mHandler.removeMessages(MSG_SEND_NOTIFICATION)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onBind(intent: Intent): IBinder? {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A [Handler.Callback] used to process the message queue for the notification service.
|
||||||
|
*/
|
||||||
|
internal inner class HandlerCallback : Handler.Callback {
|
||||||
|
override fun handleMessage(msg: Message): Boolean {
|
||||||
|
if (msg.what == MSG_SEND_NOTIFICATION) {
|
||||||
|
val context: Context = this@NavigationNotificationService
|
||||||
|
CarNotificationManager.from(context).notify(
|
||||||
|
NAV_NOTIFICATION_ID,
|
||||||
|
getNavigationNotification(context, mNotificationCount)
|
||||||
|
)
|
||||||
|
mNotificationCount++
|
||||||
|
mHandler.sendMessageDelayed(
|
||||||
|
mHandler.obtainMessage(MSG_SEND_NOTIFICATION),
|
||||||
|
NAV_NOTIFICATION_DELAY_IN_MILLIS
|
||||||
|
)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A container class that encapsulates the direction information to use in the notifications.
|
||||||
|
*/
|
||||||
|
internal class DirectionInfo(
|
||||||
|
val mTitle: String, val mDistance: String, val mIcon: Int,
|
||||||
|
val mOnlyAlertOnce: Boolean
|
||||||
|
)
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val MSG_SEND_NOTIFICATION = 1
|
||||||
|
private const val NAV_NOTIFICATION_CHANNEL_ID = "nav_channel_00"
|
||||||
|
private val NAV_NOTIFICATION_CHANNEL_NAME: CharSequence = "Navigation Channel"
|
||||||
|
private const val NAV_NOTIFICATION_ID = 10101
|
||||||
|
val NAV_NOTIFICATION_DELAY_IN_MILLIS: Long = TimeUnit.SECONDS.toMillis(1)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes the notifications, if needed.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* [NotificationManager.IMPORTANCE_HIGH] is needed to show the alerts on top of the car
|
||||||
|
* screen. However, the rail widget at the bottom of the screen will show regardless of the
|
||||||
|
* importance setting.
|
||||||
|
*/
|
||||||
|
// Suppressing 'ObsoleteSdkInt' as this code is shared between APKs with different min SDK
|
||||||
|
// levels
|
||||||
|
@SuppressLint("ObsoleteSdkInt")
|
||||||
|
private fun initNotifications(context: Context) {
|
||||||
|
val navChannel =
|
||||||
|
NotificationChannelCompat.Builder(
|
||||||
|
NAV_NOTIFICATION_CHANNEL_ID,
|
||||||
|
NotificationManagerCompat.IMPORTANCE_HIGH
|
||||||
|
)
|
||||||
|
.setName(NAV_NOTIFICATION_CHANNEL_NAME).build()
|
||||||
|
CarNotificationManager.from(context).createNotificationChannel(navChannel)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Returns the navigation notification that corresponds to the given notification count. */
|
||||||
|
fun getNavigationNotification(
|
||||||
|
context: Context, notificationCount: Int
|
||||||
|
): NotificationCompat.Builder {
|
||||||
|
val builder =
|
||||||
|
NotificationCompat.Builder(context, NAV_NOTIFICATION_CHANNEL_ID)
|
||||||
|
val directionInfo = getDirectionInfo(context, notificationCount)
|
||||||
|
|
||||||
|
// Set an intent to open the car app. The app receives this intent when the user taps the
|
||||||
|
// heads-up notification or the rail widget.
|
||||||
|
val pendingIntent = CarPendingIntent.getCarApp(
|
||||||
|
context,
|
||||||
|
NavigationCarAppService().INTENT_ACTION_NAV_NOTIFICATION_OPEN_APP.hashCode(),
|
||||||
|
Intent(
|
||||||
|
NavigationCarAppService().INTENT_ACTION_NAV_NOTIFICATION_OPEN_APP
|
||||||
|
).setComponent(
|
||||||
|
ComponentName(
|
||||||
|
context,
|
||||||
|
NavigationCarAppService()::class.java
|
||||||
|
)
|
||||||
|
).setData(
|
||||||
|
NavigationCarAppService().createDeepLinkUri(
|
||||||
|
NavigationCarAppService().INTENT_ACTION_NAV_NOTIFICATION_OPEN_APP
|
||||||
|
)
|
||||||
|
),
|
||||||
|
0
|
||||||
|
)
|
||||||
|
|
||||||
|
return builder // This title, text, and icon will be shown in both phone and car screen. These
|
||||||
|
// values can
|
||||||
|
// be overridden in the extender below, to customize notifications in the car
|
||||||
|
// screen.
|
||||||
|
.setContentTitle(directionInfo.mTitle)
|
||||||
|
.setContentText(directionInfo.mDistance)
|
||||||
|
.setSmallIcon(directionInfo.mIcon) // The notification must be set to 'ongoing' and its category must be set to
|
||||||
|
// CATEGORY_NAVIGATION in order to show it in the rail widget when the app is
|
||||||
|
// navigating on
|
||||||
|
// the background.
|
||||||
|
// These values cannot be overridden in the extender.
|
||||||
|
|
||||||
|
.setOngoing(true)
|
||||||
|
.setCategory(NotificationCompat.CATEGORY_NAVIGATION) // If set to true, the notification will only show the alert once in both phone and
|
||||||
|
// car screen. This value cannot be overridden in the extender.
|
||||||
|
|
||||||
|
.setOnlyAlertOnce(directionInfo.mOnlyAlertOnce) // This extender must be set in order to display the notification in the car screen.
|
||||||
|
// The extender also allows various customizations, such as showing different title
|
||||||
|
// or icon on the car screen.
|
||||||
|
|
||||||
|
.extend(
|
||||||
|
CarAppExtender.Builder()
|
||||||
|
.setContentIntent(pendingIntent)
|
||||||
|
.build()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a [DirectionInfo] that corresponds to the given notification count.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* There are 5 directions, repeating in order. For each direction, the alert will only show
|
||||||
|
* once, but the distance will update on every count on the rail widget.
|
||||||
|
*/
|
||||||
|
private fun getDirectionInfo(context: Context, notificationCount: Int): DirectionInfo {
|
||||||
|
val formatter = DecimalFormat("#.##")
|
||||||
|
formatter.setRoundingMode(RoundingMode.DOWN)
|
||||||
|
val repeatingCount = notificationCount % 35
|
||||||
|
if (repeatingCount in 0..<10) {
|
||||||
|
// Distance decreases from 1km to 0.1km
|
||||||
|
val distance = formatter.format((10 - repeatingCount) * 0.1) + "km"
|
||||||
|
return DirectionInfo(
|
||||||
|
context.getString(R.string.stop_action_title),
|
||||||
|
distance,
|
||||||
|
R.drawable.arrow_back_24px,
|
||||||
|
repeatingCount > 0
|
||||||
|
)
|
||||||
|
} else if (repeatingCount in 10..<20) {
|
||||||
|
// Distance decreases from 5km to 0.5km
|
||||||
|
val distance = formatter.format((20 - repeatingCount) * 0.5) + "km"
|
||||||
|
return DirectionInfo(
|
||||||
|
context.getString(R.string.route_preview),
|
||||||
|
distance,
|
||||||
|
R.drawable.ic_turn_normal_right, /* onlyAlertOnce= */
|
||||||
|
repeatingCount > 10
|
||||||
|
)
|
||||||
|
} else if (repeatingCount in 20..<25) {
|
||||||
|
// Distance decreases from 200m to 40m
|
||||||
|
val distance = formatter.format(((25 - repeatingCount) * 40).toLong()) + "m"
|
||||||
|
return DirectionInfo(
|
||||||
|
context.getString(R.string.route_preview),
|
||||||
|
distance,
|
||||||
|
R.drawable.navigation_48px, /* onlyAlertOnce= */
|
||||||
|
repeatingCount > 20
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
// Distance decreases from 1km to 0.1km
|
||||||
|
val distance = formatter.format((35 - repeatingCount) * 0.1) + "km"
|
||||||
|
return DirectionInfo(
|
||||||
|
context.getString(R.string.charging_station),
|
||||||
|
distance,
|
||||||
|
R.drawable.local_gas_station_24,
|
||||||
|
repeatingCount > 25
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -26,6 +26,7 @@ import com.kouros.navigation.car.navigation.RouteCarModel
|
|||||||
import com.kouros.navigation.car.navigation.Simulation
|
import com.kouros.navigation.car.navigation.Simulation
|
||||||
import com.kouros.navigation.car.screen.NavigationListener
|
import com.kouros.navigation.car.screen.NavigationListener
|
||||||
import com.kouros.navigation.car.screen.NavigationScreen
|
import com.kouros.navigation.car.screen.NavigationScreen
|
||||||
|
import com.kouros.navigation.car.screen.NavigationType
|
||||||
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.car.screen.checkPermission
|
import com.kouros.navigation.car.screen.checkPermission
|
||||||
@@ -37,6 +38,7 @@ import com.kouros.navigation.data.Constants.MAXIMAL_SNAP_CORRECTION
|
|||||||
import com.kouros.navigation.data.Constants.TAG
|
import com.kouros.navigation.data.Constants.TAG
|
||||||
import com.kouros.navigation.data.Place
|
import com.kouros.navigation.data.Place
|
||||||
import com.kouros.navigation.data.RouteEngine
|
import com.kouros.navigation.data.RouteEngine
|
||||||
|
import com.kouros.navigation.data.ViewStyle
|
||||||
import com.kouros.navigation.data.osrm.OsrmRepository
|
import com.kouros.navigation.data.osrm.OsrmRepository
|
||||||
import com.kouros.navigation.data.tomtom.TomTomRepository
|
import com.kouros.navigation.data.tomtom.TomTomRepository
|
||||||
import com.kouros.navigation.data.valhalla.ValhallaRepository
|
import com.kouros.navigation.data.valhalla.ValhallaRepository
|
||||||
@@ -229,9 +231,10 @@ class NavigationSession : Session(), NavigationListener {
|
|||||||
override fun onStopNavigation() {
|
override fun onStopNavigation() {
|
||||||
// Called when the user stops navigation in the car screen
|
// Called when the user stops navigation in the car screen
|
||||||
// Stop turn-by-turn logic and clean up
|
// Stop turn-by-turn logic and clean up
|
||||||
routeModel.stopNavigation()
|
stopNavigation()
|
||||||
autoDriveEnabled = false
|
if (autoDriveEnabled) {
|
||||||
deviceLocationManager.startLocationUpdates()
|
deviceLocationManager.startLocationUpdates()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
surfaceRenderer = SurfaceRenderer(carContext, lifecycle, routeModel, viewModelStoreOwner)
|
surfaceRenderer = SurfaceRenderer(carContext, lifecycle, routeModel, viewModelStoreOwner)
|
||||||
@@ -387,6 +390,7 @@ class NavigationSession : Session(), NavigationListener {
|
|||||||
* Snaps location to route and checks for deviation requiring reroute.
|
* Snaps location to route and checks for deviation requiring reroute.
|
||||||
*/
|
*/
|
||||||
private fun handleNavigationLocation(location: Location) {
|
private fun handleNavigationLocation(location: Location) {
|
||||||
|
|
||||||
if (guidanceAudio == 1) {
|
if (guidanceAudio == 1) {
|
||||||
handleGuidanceAudio()
|
handleGuidanceAudio()
|
||||||
}
|
}
|
||||||
@@ -420,6 +424,9 @@ class NavigationSession : Session(), NavigationListener {
|
|||||||
simulation.stopSimulation()
|
simulation.stopSimulation()
|
||||||
autoDriveEnabled = false
|
autoDriveEnabled = false
|
||||||
}
|
}
|
||||||
|
surfaceRenderer.routeData.value = ""
|
||||||
|
surfaceRenderer.viewStyle = ViewStyle.VIEW
|
||||||
|
navigationScreen.navigationType = NavigationType.VIEW
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -10,12 +10,17 @@ 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.animation.core.LinearOutSlowInEasing
|
||||||
|
import androidx.compose.animation.core.animateFloatAsState
|
||||||
|
import androidx.compose.animation.core.tween
|
||||||
import androidx.compose.foundation.layout.PaddingValues
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.collectAsState
|
import androidx.compose.runtime.collectAsState
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.livedata.observeAsState
|
import androidx.compose.runtime.livedata.observeAsState
|
||||||
|
import androidx.compose.runtime.mutableFloatStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.ui.platform.ComposeView
|
import androidx.compose.ui.platform.ComposeView
|
||||||
import androidx.lifecycle.DefaultLifecycleObserver
|
import androidx.lifecycle.DefaultLifecycleObserver
|
||||||
import androidx.lifecycle.Lifecycle
|
import androidx.lifecycle.Lifecycle
|
||||||
@@ -29,9 +34,11 @@ import com.kouros.navigation.car.map.MapLibre
|
|||||||
import com.kouros.navigation.car.map.cameraState
|
import com.kouros.navigation.car.map.cameraState
|
||||||
import com.kouros.navigation.car.map.getPaddingValues
|
import com.kouros.navigation.car.map.getPaddingValues
|
||||||
import com.kouros.navigation.car.navigation.RouteCarModel
|
import com.kouros.navigation.car.navigation.RouteCarModel
|
||||||
|
import com.kouros.navigation.data.Constants.TAG
|
||||||
import com.kouros.navigation.data.Constants.TILT
|
import com.kouros.navigation.data.Constants.TILT
|
||||||
import com.kouros.navigation.data.Constants.homeVogelhart
|
import com.kouros.navigation.data.Constants.homeVogelhart
|
||||||
import com.kouros.navigation.data.RouteEngine
|
import com.kouros.navigation.data.RouteEngine
|
||||||
|
import com.kouros.navigation.data.ViewStyle
|
||||||
import com.kouros.navigation.model.BaseStyleModel
|
import com.kouros.navigation.model.BaseStyleModel
|
||||||
import com.kouros.navigation.utils.bearing
|
import com.kouros.navigation.utils.bearing
|
||||||
import com.kouros.navigation.utils.calculateTilt
|
import com.kouros.navigation.utils.calculateTilt
|
||||||
@@ -45,9 +52,10 @@ import kotlinx.coroutines.flow.first
|
|||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
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.expressions.dsl.zoom
|
|
||||||
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 java.time.Duration
|
||||||
|
import java.time.LocalDateTime
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -123,6 +131,8 @@ class SurfaceRenderer(
|
|||||||
// Camera tilt angle (default 60 degrees for navigation)
|
// Camera tilt angle (default 60 degrees for navigation)
|
||||||
var tilt = TILT
|
var tilt = TILT
|
||||||
|
|
||||||
|
var lastLocationUpdate: LocalDateTime = LocalDateTime.now()
|
||||||
|
|
||||||
// Map base style (day/night)
|
// Map base style (day/night)
|
||||||
val style: MutableLiveData<BaseStyle> by lazy {
|
val style: MutableLiveData<BaseStyle> by lazy {
|
||||||
MutableLiveData()
|
MutableLiveData()
|
||||||
@@ -238,7 +248,6 @@ class SurfaceRenderer(
|
|||||||
init {
|
init {
|
||||||
lifecycle.addObserver(this)
|
lifecycle.addObserver(this)
|
||||||
speed.value = 0F
|
speed.value = 0F
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onBaseStyleStateUpdated(style: BaseStyle) {
|
fun onBaseStyleStateUpdated(style: BaseStyle) {
|
||||||
@@ -287,7 +296,7 @@ class SurfaceRenderer(
|
|||||||
darkMode: Boolean
|
darkMode: Boolean
|
||||||
) {
|
) {
|
||||||
val cameraDuration =
|
val cameraDuration =
|
||||||
duration(viewStyle == ViewStyle.PREVIEW, position!!.bearing, lastBearing)
|
duration(viewStyle == ViewStyle.PREVIEW, position!!.bearing, lastBearing, lastLocationUpdate)
|
||||||
val currentSpeed: Float? by speed.observeAsState()
|
val currentSpeed: Float? by speed.observeAsState()
|
||||||
val maximumSpeed: Int? by maxSpeed.observeAsState()
|
val maximumSpeed: Int? by maxSpeed.observeAsState()
|
||||||
val streetName: String? by street.observeAsState()
|
val streetName: String? by street.observeAsState()
|
||||||
@@ -311,9 +320,10 @@ class SurfaceRenderer(
|
|||||||
tilt = tilt,
|
tilt = tilt,
|
||||||
padding = paddingValues
|
padding = paddingValues
|
||||||
),
|
),
|
||||||
duration = cameraDuration
|
duration = cameraDuration,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
lastLocationUpdate = LocalDateTime.now()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCreate(owner: LifecycleOwner) {
|
override fun onCreate(owner: LifecycleOwner) {
|
||||||
@@ -396,6 +406,15 @@ class SurfaceRenderer(
|
|||||||
viewStyle = ViewStyle.VIEW
|
viewStyle = ViewStyle.VIEW
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Activates navigation View
|
||||||
|
*/
|
||||||
|
fun activateNavigationView() {
|
||||||
|
viewStyle = ViewStyle.VIEW
|
||||||
|
tilt = TILT
|
||||||
|
updateLocation(lastLocation)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Updates camera position with new bearing, zoom, and target.
|
* Updates camera position with new bearing, zoom, and target.
|
||||||
* Posts update to LiveData for UI observation.
|
* Posts update to LiveData for UI observation.
|
||||||
@@ -414,21 +433,6 @@ class SurfaceRenderer(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets route data for active navigation and switches to VIEW mode.
|
|
||||||
*/
|
|
||||||
fun clearRouteData() {
|
|
||||||
updateLocation(lastLocation)
|
|
||||||
routeData.value = ""
|
|
||||||
viewStyle = ViewStyle.VIEW
|
|
||||||
cameraPosition.postValue(
|
|
||||||
cameraPosition.value!!.copy(
|
|
||||||
zoom = 16.0
|
|
||||||
)
|
|
||||||
)
|
|
||||||
tilt = TILT
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Updates traffic incident data on the map.
|
* Updates traffic incident data on the map.
|
||||||
*/
|
*/
|
||||||
@@ -492,9 +496,9 @@ class SurfaceRenderer(
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Centers the map on a specific category/POI location.
|
* Centers the map on a specific POI location.
|
||||||
*/
|
*/
|
||||||
fun setCategoryLocation(location: Location, category: String) {
|
fun setCategoryLocation(location: Location) {
|
||||||
viewStyle = ViewStyle.AMENITY_VIEW
|
viewStyle = ViewStyle.AMENITY_VIEW
|
||||||
cameraPosition.postValue(
|
cameraPosition.postValue(
|
||||||
cameraPosition.value!!.copy(
|
cameraPosition.value!!.copy(
|
||||||
@@ -502,22 +506,4 @@ class SurfaceRenderer(
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
companion
|
|
||||||
object {
|
|
||||||
private const val TAG = "MapRenderer"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Enum representing different map view modes.
|
|
||||||
* - VIEW: Active navigation mode with follow-car camera
|
|
||||||
* - PREVIEW: Route overview before starting navigation
|
|
||||||
* - PAN_VIEW: User-controlled map panning
|
|
||||||
* - AMENITY_VIEW: Displaying POI/amenity locations
|
|
||||||
*/
|
|
||||||
enum class ViewStyle {
|
|
||||||
VIEW, PREVIEW, PAN_VIEW, AMENITY_VIEW
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,11 +26,12 @@ import androidx.compose.ui.text.style.TextOverflow
|
|||||||
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.data.R
|
import com.kouros.data.R
|
||||||
import com.kouros.navigation.car.ViewStyle
|
|
||||||
import com.kouros.navigation.data.Constants
|
import com.kouros.navigation.data.Constants
|
||||||
import com.kouros.navigation.data.NavigationColor
|
import com.kouros.navigation.data.NavigationColorDark
|
||||||
|
import com.kouros.navigation.data.NavigationColorLight
|
||||||
import com.kouros.navigation.data.RouteColor
|
import com.kouros.navigation.data.RouteColor
|
||||||
import com.kouros.navigation.data.SpeedColor
|
import com.kouros.navigation.data.SpeedColor
|
||||||
|
import com.kouros.navigation.data.ViewStyle
|
||||||
import com.kouros.navigation.utils.isMetricSystem
|
import com.kouros.navigation.utils.isMetricSystem
|
||||||
import org.maplibre.compose.camera.CameraPosition
|
import org.maplibre.compose.camera.CameraPosition
|
||||||
import org.maplibre.compose.camera.CameraState
|
import org.maplibre.compose.camera.CameraState
|
||||||
@@ -74,9 +75,9 @@ fun cameraState(
|
|||||||
latitude = position!!.target.latitude,
|
latitude = position!!.target.latitude,
|
||||||
longitude = position.target.longitude
|
longitude = position.target.longitude
|
||||||
),
|
),
|
||||||
zoom = 15.0,
|
zoom = position.zoom,
|
||||||
tilt = tilt,
|
tilt = tilt,
|
||||||
padding = padding
|
padding = padding,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -317,7 +318,10 @@ fun NavigationImage(
|
|||||||
) {
|
) {
|
||||||
|
|
||||||
val imageSize = (height / 8)
|
val imageSize = (height / 8)
|
||||||
val navigationColor = remember { NavigationColor }
|
val navigationColor = if (darkMode)
|
||||||
|
remember { NavigationColorDark }
|
||||||
|
else
|
||||||
|
remember { NavigationColorLight }
|
||||||
|
|
||||||
val textMeasurerStreet = rememberTextMeasurer()
|
val textMeasurerStreet = rememberTextMeasurer()
|
||||||
val street = streetName.toString()
|
val street = streetName.toString()
|
||||||
@@ -545,7 +549,7 @@ fun DebugInfo(
|
|||||||
fun getPaddingValues(height: Int, viewStyle: ViewStyle): PaddingValues {
|
fun getPaddingValues(height: Int, viewStyle: ViewStyle): PaddingValues {
|
||||||
return when (viewStyle) {
|
return when (viewStyle) {
|
||||||
ViewStyle.VIEW, ViewStyle.PAN_VIEW -> PaddingValues(
|
ViewStyle.VIEW, ViewStyle.PAN_VIEW -> PaddingValues(
|
||||||
start = 50.dp,
|
start = 100.dp,
|
||||||
top = distanceFromTop(height).dp
|
top = distanceFromTop(height).dp
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -1,80 +0,0 @@
|
|||||||
package com.kouros.navigation.car.navigation
|
|
||||||
|
|
||||||
import android.graphics.Canvas
|
|
||||||
import android.graphics.Color
|
|
||||||
import android.graphics.Paint
|
|
||||||
import android.media.AudioAttributes
|
|
||||||
import android.media.AudioFocusRequest
|
|
||||||
import android.media.AudioManager
|
|
||||||
import android.media.MediaPlayer
|
|
||||||
import android.media.MediaPlayer.OnCompletionListener
|
|
||||||
import android.speech.tts.TextToSpeech
|
|
||||||
import android.util.Log
|
|
||||||
import androidx.annotation.DrawableRes
|
|
||||||
import androidx.annotation.RawRes
|
|
||||||
import androidx.annotation.StringRes
|
|
||||||
import androidx.car.app.CarContext
|
|
||||||
import androidx.car.app.CarToast
|
|
||||||
import androidx.car.app.model.Action
|
|
||||||
import androidx.car.app.model.CarIcon
|
|
||||||
import androidx.car.app.model.CarText
|
|
||||||
import androidx.car.app.model.Row
|
|
||||||
import androidx.core.graphics.createBitmap
|
|
||||||
import androidx.core.graphics.drawable.IconCompat
|
|
||||||
import com.kouros.data.R
|
|
||||||
import com.kouros.navigation.data.Constants.CHARGING_STATION
|
|
||||||
import com.kouros.navigation.data.Constants.FUEL_STATION
|
|
||||||
import com.kouros.navigation.data.Constants.PHARMACY
|
|
||||||
import com.kouros.navigation.data.Constants.TAG
|
|
||||||
import java.io.IOException
|
|
||||||
import java.util.Locale
|
|
||||||
|
|
||||||
class NavigationUtils(private var carContext: CarContext) {
|
|
||||||
|
|
||||||
|
|
||||||
fun createCarIcon(@DrawableRes iconRes: Int): CarIcon {
|
|
||||||
return CarIcon.Builder(IconCompat.createWithResource(carContext, iconRes)).build()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun buildRowForTemplate(title: Int, resource: Int): Row {
|
|
||||||
return Row.Builder()
|
|
||||||
.setTitle(carContext.getString(title))
|
|
||||||
.setImage(
|
|
||||||
CarIcon.Builder(
|
|
||||||
IconCompat.createWithResource(
|
|
||||||
carContext,
|
|
||||||
resource
|
|
||||||
)
|
|
||||||
)
|
|
||||||
.build()
|
|
||||||
)
|
|
||||||
.build()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun createNumberIcon(category: String, number: String): IconCompat {
|
|
||||||
val size = 24
|
|
||||||
val bitmap = createBitmap(size, size)
|
|
||||||
val canvas = Canvas(bitmap)
|
|
||||||
val paint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
|
|
||||||
color = Color.WHITE
|
|
||||||
textSize = size * 0.7f
|
|
||||||
textAlign = Paint.Align.CENTER
|
|
||||||
isFakeBoldText = true
|
|
||||||
}
|
|
||||||
val xPos = size / 2f
|
|
||||||
val yPos = (size / 2f) - ((paint.descent() + paint.ascent()) / 2f)
|
|
||||||
|
|
||||||
val color = when (category) {
|
|
||||||
CHARGING_STATION -> Color.GREEN
|
|
||||||
FUEL_STATION -> Color.BLUE
|
|
||||||
PHARMACY -> Color.RED
|
|
||||||
else -> Color.WHITE
|
|
||||||
}
|
|
||||||
paint.color = color
|
|
||||||
canvas.drawCircle(size / 2f, size / 2f, size / 2f, paint)
|
|
||||||
|
|
||||||
paint.color = Color.WHITE
|
|
||||||
canvas.drawText(number, xPos, yPos, paint)
|
|
||||||
return IconCompat.createWithBitmap(bitmap)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -3,6 +3,7 @@ package com.kouros.navigation.car.navigation
|
|||||||
import android.text.SpannableString
|
import android.text.SpannableString
|
||||||
import android.text.SpannableStringBuilder
|
import android.text.SpannableStringBuilder
|
||||||
import android.text.Spanned
|
import android.text.Spanned
|
||||||
|
import android.util.Log
|
||||||
import androidx.annotation.StringRes
|
import androidx.annotation.StringRes
|
||||||
import androidx.car.app.AppManager
|
import androidx.car.app.AppManager
|
||||||
import androidx.car.app.CarContext
|
import androidx.car.app.CarContext
|
||||||
@@ -184,7 +185,6 @@ class RouteCarModel : RouteModel() {
|
|||||||
return CarText.create(carContext.getString(stringRes))
|
return CarText.create(carContext.getString(stringRes))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
fun createCarIcon(iconCompat: IconCompat): CarIcon {
|
fun createCarIcon(iconCompat: IconCompat): CarIcon {
|
||||||
return CarIcon.Builder(iconCompat).build()
|
return CarIcon.Builder(iconCompat).build()
|
||||||
}
|
}
|
||||||
@@ -235,4 +235,17 @@ class RouteCarModel : RouteModel() {
|
|||||||
.setFlags(flags)
|
.setFlags(flags)
|
||||||
.build()
|
.build()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun backGroundColor(): CarColor {
|
||||||
|
return if (isNavigating()) {
|
||||||
|
when (route.currentStep().countryCode) {
|
||||||
|
"DEU", "FRA", "AUT", "POL", "BEL", "NLD", "ESP", "PRT", "CZE", "SVK", "BGR", "HUN" -> CarColor.BLUE
|
||||||
|
else -> {
|
||||||
|
CarColor.GREEN
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
CarColor.GREEN
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,11 +4,17 @@ import android.location.Location
|
|||||||
import android.location.LocationManager
|
import android.location.LocationManager
|
||||||
import android.os.SystemClock
|
import android.os.SystemClock
|
||||||
import androidx.lifecycle.LifecycleCoroutineScope
|
import androidx.lifecycle.LifecycleCoroutineScope
|
||||||
import com.kouros.navigation.data.Constants.homeVogelhart
|
import com.kouros.android.cars.carappservice.BuildConfig
|
||||||
import com.kouros.navigation.utils.location
|
import com.kouros.navigation.data.tomtom.TomTomRepository
|
||||||
|
import io.ticofab.androidgpxparser.parser.GPXParser
|
||||||
|
import io.ticofab.androidgpxparser.parser.domain.Gpx
|
||||||
|
import io.ticofab.androidgpxparser.parser.domain.TrackSegment
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.runBlocking
|
||||||
|
import org.joda.time.DateTime
|
||||||
|
|
||||||
class Simulation {
|
class Simulation {
|
||||||
|
|
||||||
@@ -20,31 +26,104 @@ class Simulation {
|
|||||||
updateLocation: (Location) -> Unit
|
updateLocation: (Location) -> Unit
|
||||||
) {
|
) {
|
||||||
if (routeModel.navState.route.isRouteValid()) {
|
if (routeModel.navState.route.isRouteValid()) {
|
||||||
val points = routeModel.curRoute.waypoints
|
if (BuildConfig.DEBUG) {
|
||||||
if (points.isEmpty()) return
|
gpxSimulation(routeModel, lifecycleScope, updateLocation)
|
||||||
simulationJob?.cancel()
|
} else {
|
||||||
|
currentSimulation(routeModel, lifecycleScope, updateLocation)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun currentSimulation(
|
||||||
|
routeModel: RouteCarModel,
|
||||||
|
lifecycleScope: LifecycleCoroutineScope,
|
||||||
|
updateLocation: (Location) -> Unit
|
||||||
|
) {
|
||||||
|
val points = routeModel.curRoute.waypoints
|
||||||
|
if (points.isEmpty()) return
|
||||||
|
simulationJob?.cancel()
|
||||||
|
var lastLocation = Location(LocationManager.FUSED_PROVIDER)
|
||||||
|
var curBearing = 0f
|
||||||
|
simulationJob = lifecycleScope.launch {
|
||||||
|
for (point in points) {
|
||||||
|
val fakeLocation = Location(LocationManager.FUSED_PROVIDER).apply {
|
||||||
|
latitude = point[1]
|
||||||
|
longitude = point[0]
|
||||||
|
bearing = curBearing
|
||||||
|
speedAccuracyMetersPerSecond = 1.0f // ~1 m/s
|
||||||
|
speed = 13.0f // ~50 km/h
|
||||||
|
time = System.currentTimeMillis()
|
||||||
|
elapsedRealtimeNanos = SystemClock.elapsedRealtimeNanos()
|
||||||
|
}
|
||||||
|
curBearing = lastLocation.bearingTo(fakeLocation)
|
||||||
|
// Update your app's state as if a real GPS update occurred
|
||||||
|
updateLocation(fakeLocation)
|
||||||
|
// Wait before moving to the next point (e.g., every 1 second)
|
||||||
|
delay(1000)
|
||||||
|
lastLocation = fakeLocation
|
||||||
|
}
|
||||||
|
routeModel.stopNavigation()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun gpxSimulation(
|
||||||
|
routeModel: RouteCarModel,
|
||||||
|
lifecycleScope: LifecycleCoroutineScope,
|
||||||
|
updateLocation: (Location) -> Unit
|
||||||
|
) {
|
||||||
|
var route = ""
|
||||||
|
simulationJob?.cancel()
|
||||||
|
runBlocking {
|
||||||
|
simulationJob = launch(Dispatchers.IO) {
|
||||||
|
route = TomTomRepository().fetchUrl(
|
||||||
|
"https://kouros-online.de/vh.gpx",
|
||||||
|
false
|
||||||
|
)
|
||||||
|
}
|
||||||
|
simulationJob?.join()
|
||||||
|
}
|
||||||
|
simulationJob?.cancel()
|
||||||
|
simulationJob =lifecycleScope.launch() {
|
||||||
var lastLocation = Location(LocationManager.FUSED_PROVIDER)
|
var lastLocation = Location(LocationManager.FUSED_PROVIDER)
|
||||||
var curBearing = 0f
|
var curBearing = 0f
|
||||||
simulationJob = lifecycleScope.launch {
|
val parser = GPXParser()
|
||||||
for (point in points) {
|
val parsedGpx: Gpx? =
|
||||||
val fakeLocation = Location(LocationManager.FUSED_PROVIDER).apply {
|
parser.parse(route.byteInputStream())
|
||||||
latitude = point[1]
|
parsedGpx?.let {
|
||||||
longitude = point[0]
|
val tracks = parsedGpx.tracks
|
||||||
bearing = curBearing
|
tracks.forEach { tr ->
|
||||||
speedAccuracyMetersPerSecond = 1.0f // ~1 m/s
|
val segments: MutableList<TrackSegment?>? = tr.trackSegments
|
||||||
speed = 13.0f // ~50 km/h
|
segments!!.forEach { seg ->
|
||||||
time = System.currentTimeMillis()
|
var lastTime = DateTime.now()
|
||||||
elapsedRealtimeNanos = SystemClock.elapsedRealtimeNanos()
|
seg!!.trackPoints.forEach { p ->
|
||||||
|
val ext = p.extensions
|
||||||
|
var curSpeed = 0F
|
||||||
|
if (ext != null) {
|
||||||
|
curSpeed = ext.speed.toFloat()
|
||||||
|
}
|
||||||
|
val duration = p.time.millis - lastTime.millis
|
||||||
|
val fakeLocation = Location(LocationManager.FUSED_PROVIDER).apply {
|
||||||
|
latitude = p.latitude
|
||||||
|
longitude = p.longitude
|
||||||
|
speedAccuracyMetersPerSecond = 1.0f // ~1 m/s
|
||||||
|
speed = curSpeed
|
||||||
|
time = System.currentTimeMillis()
|
||||||
|
elapsedRealtimeNanos = SystemClock.elapsedRealtimeNanos()
|
||||||
|
}
|
||||||
|
// Update your app's state as if a real GPS update occurred
|
||||||
|
updateLocation(fakeLocation)
|
||||||
|
// Wait before moving to the next point (e.g., every 1 second)
|
||||||
|
if (duration > 100) {
|
||||||
|
delay(duration / 4)
|
||||||
|
}
|
||||||
|
lastTime = p.time
|
||||||
|
lastLocation = fakeLocation
|
||||||
|
}
|
||||||
}
|
}
|
||||||
curBearing = lastLocation.bearingTo(fakeLocation)
|
|
||||||
// Update your app's state as if a real GPS update occurred
|
|
||||||
updateLocation(fakeLocation)
|
|
||||||
// Wait before moving to the next point (e.g., every 1 second)
|
|
||||||
delay(500)
|
|
||||||
lastLocation = fakeLocation
|
|
||||||
}
|
}
|
||||||
routeModel.stopNavigation()
|
|
||||||
}
|
}
|
||||||
|
routeModel.stopNavigation()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
package com.kouros.navigation.car.screen
|
package com.kouros.navigation.car.screen
|
||||||
|
|
||||||
import androidx.activity.OnBackPressedCallback
|
|
||||||
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
|
||||||
@@ -13,18 +12,12 @@ import androidx.car.app.model.Template
|
|||||||
import androidx.core.graphics.drawable.IconCompat
|
import androidx.core.graphics.drawable.IconCompat
|
||||||
import com.kouros.data.R
|
import com.kouros.data.R
|
||||||
import com.kouros.navigation.car.SurfaceRenderer
|
import com.kouros.navigation.car.SurfaceRenderer
|
||||||
import com.kouros.navigation.car.ViewStyle
|
|
||||||
import com.kouros.navigation.data.Category
|
import com.kouros.navigation.data.Category
|
||||||
import com.kouros.navigation.data.Constants.CHARGING_STATION
|
import com.kouros.navigation.data.Constants.CHARGING_STATION
|
||||||
import com.kouros.navigation.data.Constants.FUEL_STATION
|
import com.kouros.navigation.data.Constants.FUEL_STATION
|
||||||
import com.kouros.navigation.data.Constants.PHARMACY
|
import com.kouros.navigation.data.Constants.PHARMACY
|
||||||
|
import com.kouros.navigation.data.ViewStyle
|
||||||
import com.kouros.navigation.model.NavigationViewModel
|
import com.kouros.navigation.model.NavigationViewModel
|
||||||
import com.kouros.navigation.car.navigation.NavigationUtils
|
|
||||||
import com.kouros.navigation.car.screen.observers.CategoryObserver
|
|
||||||
import com.kouros.navigation.car.screen.observers.CategoryObserverCallback
|
|
||||||
import com.kouros.navigation.data.overpass.Elements
|
|
||||||
import com.kouros.navigation.utils.GeoUtils.createPointCollection
|
|
||||||
import com.kouros.navigation.utils.location
|
|
||||||
|
|
||||||
class CategoriesScreen(
|
class CategoriesScreen(
|
||||||
private val carContext: CarContext,
|
private val carContext: CarContext,
|
||||||
@@ -100,7 +93,7 @@ fun carIcon(context: CarContext, category: String, index: Int): CarIcon {
|
|||||||
return CarIcon.Builder(IconCompat.createWithResource(context, resId)).build()
|
return CarIcon.Builder(IconCompat.createWithResource(context, resId)).build()
|
||||||
} else {
|
} else {
|
||||||
return CarIcon.Builder(
|
return CarIcon.Builder(
|
||||||
NavigationUtils(context).createNumberIcon(
|
createNumberIcon(
|
||||||
category,
|
category,
|
||||||
index.toString()
|
index.toString()
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -21,7 +21,6 @@ import androidx.lifecycle.DefaultLifecycleObserver
|
|||||||
import androidx.lifecycle.LifecycleOwner
|
import androidx.lifecycle.LifecycleOwner
|
||||||
import com.kouros.data.R
|
import com.kouros.data.R
|
||||||
import com.kouros.navigation.car.SurfaceRenderer
|
import com.kouros.navigation.car.SurfaceRenderer
|
||||||
import com.kouros.navigation.car.navigation.NavigationUtils
|
|
||||||
import com.kouros.navigation.car.screen.observers.CategoryObserver
|
import com.kouros.navigation.car.screen.observers.CategoryObserver
|
||||||
import com.kouros.navigation.car.screen.observers.CategoryObserverCallback
|
import com.kouros.navigation.car.screen.observers.CategoryObserverCallback
|
||||||
import com.kouros.navigation.data.Constants
|
import com.kouros.navigation.data.Constants
|
||||||
@@ -130,7 +129,7 @@ class CategoryScreen(
|
|||||||
val row = Row.Builder()
|
val row = Row.Builder()
|
||||||
.setOnClickListener {
|
.setOnClickListener {
|
||||||
val location = location(it.lon, it.lat)
|
val location = location(it.lon, it.lat)
|
||||||
surfaceRenderer.setCategoryLocation(location, category)
|
surfaceRenderer.setCategoryLocation(location)
|
||||||
}
|
}
|
||||||
.setTitle(name)
|
.setTitle(name)
|
||||||
.setImage(carIcon(carContext, category, index))
|
.setImage(carIcon(carContext, category, index))
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ import androidx.car.app.Screen
|
|||||||
import androidx.car.app.model.Action
|
import androidx.car.app.model.Action
|
||||||
import androidx.car.app.model.Action.FLAG_IS_PERSISTENT
|
import androidx.car.app.model.Action.FLAG_IS_PERSISTENT
|
||||||
import androidx.car.app.model.ActionStrip
|
import androidx.car.app.model.ActionStrip
|
||||||
import androidx.car.app.model.CarColor
|
|
||||||
import androidx.car.app.model.CarIcon
|
import androidx.car.app.model.CarIcon
|
||||||
import androidx.car.app.model.Distance
|
import androidx.car.app.model.Distance
|
||||||
import androidx.car.app.model.Header
|
import androidx.car.app.model.Header
|
||||||
@@ -31,28 +30,25 @@ import androidx.lifecycle.asLiveData
|
|||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
import com.kouros.data.R
|
import com.kouros.data.R
|
||||||
import com.kouros.navigation.car.SurfaceRenderer
|
import com.kouros.navigation.car.SurfaceRenderer
|
||||||
import com.kouros.navigation.car.ViewStyle
|
|
||||||
import com.kouros.navigation.car.navigation.RouteCarModel
|
import com.kouros.navigation.car.navigation.RouteCarModel
|
||||||
import com.kouros.navigation.car.screen.observers.NavigationObserverCallback
|
import com.kouros.navigation.car.screen.observers.NavigationObserverCallback
|
||||||
import com.kouros.navigation.car.screen.observers.NavigationObserverManager
|
import com.kouros.navigation.car.screen.observers.NavigationObserverManager
|
||||||
import com.kouros.navigation.car.screen.settings.SettingsScreen
|
import com.kouros.navigation.car.screen.settings.SettingsScreen
|
||||||
import com.kouros.navigation.data.Constants
|
import com.kouros.navigation.data.Constants
|
||||||
import com.kouros.navigation.data.Constants.DESTINATION_ARRIVAL_DISTANCE
|
import com.kouros.navigation.data.Constants.DESTINATION_ARRIVAL_DISTANCE
|
||||||
import com.kouros.navigation.data.Constants.TILT
|
|
||||||
import com.kouros.navigation.data.Constants.TRAFFIC_UPDATE
|
import com.kouros.navigation.data.Constants.TRAFFIC_UPDATE
|
||||||
import com.kouros.navigation.data.Place
|
import com.kouros.navigation.data.Place
|
||||||
|
import com.kouros.navigation.data.ViewStyle
|
||||||
import com.kouros.navigation.data.overpass.Elements
|
import com.kouros.navigation.data.overpass.Elements
|
||||||
import com.kouros.navigation.model.NavigationViewModel
|
import com.kouros.navigation.model.NavigationViewModel
|
||||||
import com.kouros.navigation.model.RouteModel
|
import com.kouros.navigation.model.RouteModel
|
||||||
import com.kouros.navigation.utils.GeoUtils
|
import com.kouros.navigation.utils.GeoUtils
|
||||||
import com.kouros.navigation.utils.calculateZoom
|
|
||||||
import com.kouros.navigation.utils.formattedDistance
|
import com.kouros.navigation.utils.formattedDistance
|
||||||
import com.kouros.navigation.utils.getSettingsRepository
|
import com.kouros.navigation.utils.getSettingsRepository
|
||||||
import com.kouros.navigation.utils.getSettingsViewModel
|
import com.kouros.navigation.utils.getSettingsViewModel
|
||||||
import com.kouros.navigation.utils.location
|
import com.kouros.navigation.utils.location
|
||||||
import kotlinx.coroutines.flow.first
|
import kotlinx.coroutines.flow.first
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import org.maplibre.spatialk.geojson.Position
|
|
||||||
import java.time.Duration
|
import java.time.Duration
|
||||||
import java.time.LocalDateTime
|
import java.time.LocalDateTime
|
||||||
import java.time.ZoneOffset
|
import java.time.ZoneOffset
|
||||||
@@ -70,8 +66,6 @@ open class NavigationScreen(
|
|||||||
private val navigationViewModel: NavigationViewModel
|
private val navigationViewModel: NavigationViewModel
|
||||||
) : Screen(carContext), NavigationObserverCallback {
|
) : Screen(carContext), NavigationObserverCallback {
|
||||||
|
|
||||||
val backGroundColor = CarColor.GREEN
|
|
||||||
|
|
||||||
var currentNavigationLocation = Location(LocationManager.GPS_PROVIDER)
|
var currentNavigationLocation = Location(LocationManager.GPS_PROVIDER)
|
||||||
|
|
||||||
var recentPlaces = mutableListOf<Place>()
|
var recentPlaces = mutableListOf<Place>()
|
||||||
@@ -191,7 +185,7 @@ open class NavigationScreen(
|
|||||||
)
|
)
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
.setBackgroundColor(backGroundColor)
|
.setBackgroundColor(routeModel.backGroundColor())
|
||||||
.build()
|
.build()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -200,7 +194,7 @@ open class NavigationScreen(
|
|||||||
*/
|
*/
|
||||||
private fun navigationViewTemplate(actionStripBuilder: ActionStrip.Builder): Template {
|
private fun navigationViewTemplate(actionStripBuilder: ActionStrip.Builder): Template {
|
||||||
return NavigationTemplate.Builder()
|
return NavigationTemplate.Builder()
|
||||||
.setBackgroundColor(backGroundColor)
|
.setBackgroundColor(routeModel.backGroundColor())
|
||||||
.setActionStrip(actionStripBuilder.build())
|
.setActionStrip(actionStripBuilder.build())
|
||||||
.setMapActionStrip(
|
.setMapActionStrip(
|
||||||
mapActionStrip(
|
mapActionStrip(
|
||||||
@@ -261,7 +255,7 @@ open class NavigationScreen(
|
|||||||
)
|
)
|
||||||
.build()
|
.build()
|
||||||
)
|
)
|
||||||
.setBackgroundColor(backGroundColor)
|
.setBackgroundColor(routeModel.backGroundColor())
|
||||||
.setActionStrip(actionStripBuilder.build())
|
.setActionStrip(actionStripBuilder.build())
|
||||||
.setMapActionStrip(
|
.setMapActionStrip(
|
||||||
mapActionStrip(
|
mapActionStrip(
|
||||||
@@ -298,7 +292,7 @@ open class NavigationScreen(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
val listBuilder = ItemList.Builder()
|
val listBuilder = ItemList.Builder()
|
||||||
recentPlaces.filter { it.category == Constants.RECENT }.forEach {
|
recentPlaces.filter { it.category == Constants.RECENT && it.distance > 300F }.forEach {
|
||||||
val row = Row.Builder()
|
val row = Row.Builder()
|
||||||
.setTitle(it.name!!)
|
.setTitle(it.name!!)
|
||||||
.addAction(
|
.addAction(
|
||||||
@@ -355,7 +349,7 @@ open class NavigationScreen(
|
|||||||
return NavigationTemplate.Builder()
|
return NavigationTemplate.Builder()
|
||||||
.setNavigationInfo(RoutingInfo.Builder().setLoading(true).build())
|
.setNavigationInfo(RoutingInfo.Builder().setLoading(true).build())
|
||||||
.setActionStrip(actionStripBuilder.build())
|
.setActionStrip(actionStripBuilder.build())
|
||||||
.setBackgroundColor(backGroundColor)
|
.setBackgroundColor(routeModel.backGroundColor())
|
||||||
.build()
|
.build()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -421,12 +415,6 @@ open class NavigationScreen(
|
|||||||
* Creates an action to start the settings screen.
|
* Creates an action to start the settings screen.
|
||||||
*/
|
*/
|
||||||
private fun settingsAction(): Action {
|
private fun settingsAction(): Action {
|
||||||
// return Action.Builder()
|
|
||||||
// .setIcon(createCarIcon(carContext, R.drawable.settings_48px))
|
|
||||||
// .setOnClickListener {
|
|
||||||
// screenManager.push(SettingsScreen(carContext, navigationViewModel))
|
|
||||||
// }
|
|
||||||
// .build()
|
|
||||||
return createAction(
|
return createAction(
|
||||||
carContext, R.drawable.settings_48px,
|
carContext, R.drawable.settings_48px,
|
||||||
0,
|
0,
|
||||||
@@ -514,13 +502,7 @@ open class NavigationScreen(
|
|||||||
navigationViewModel.route.value = preview
|
navigationViewModel.route.value = preview
|
||||||
}
|
}
|
||||||
routeModel.navState = routeModel.navState.copy(destination = place)
|
routeModel.navState = routeModel.navState.copy(destination = place)
|
||||||
surfaceRenderer.viewStyle = ViewStyle.VIEW
|
surfaceRenderer.activateNavigationView()
|
||||||
surfaceRenderer.updateCameraPosition(
|
|
||||||
0.0,
|
|
||||||
16.0,
|
|
||||||
Position(surfaceRenderer.lastLocation.longitude, surfaceRenderer.lastLocation.latitude),
|
|
||||||
TILT
|
|
||||||
)
|
|
||||||
invalidate()
|
invalidate()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -530,7 +512,6 @@ open class NavigationScreen(
|
|||||||
fun stopNavigation() {
|
fun stopNavigation() {
|
||||||
navigationType = NavigationType.VIEW
|
navigationType = NavigationType.VIEW
|
||||||
listener.stopNavigation()
|
listener.stopNavigation()
|
||||||
surfaceRenderer.routeData.value = ""
|
|
||||||
lastCameraSearch = 0
|
lastCameraSearch = 0
|
||||||
invalidate()
|
invalidate()
|
||||||
}
|
}
|
||||||
@@ -573,9 +554,9 @@ open class NavigationScreen(
|
|||||||
* Updates navigation state with the current location, checks for arrival, and traffic updates.
|
* Updates navigation state with the current location, checks for arrival, and traffic updates.
|
||||||
*/
|
*/
|
||||||
fun updateTrip(location: Location) {
|
fun updateTrip(location: Location) {
|
||||||
val current = LocalDateTime.now(ZoneOffset.UTC)
|
val currentDate = LocalDateTime.now(ZoneOffset.UTC)
|
||||||
checkRoute(current, location)
|
checkRoute(currentDate, location)
|
||||||
checkTraffic(current, location)
|
checkTraffic(currentDate, location)
|
||||||
|
|
||||||
updateSpeedCamera(location)
|
updateSpeedCamera(location)
|
||||||
|
|
||||||
@@ -588,11 +569,11 @@ open class NavigationScreen(
|
|||||||
/**
|
/**
|
||||||
* Checks if a new route is needed based on the time since the last update.
|
* Checks if a new route is needed based on the time since the last update.
|
||||||
*/
|
*/
|
||||||
private fun checkRoute(current: LocalDateTime, location: Location) {
|
private fun checkRoute(currentDate: LocalDateTime, location: Location) {
|
||||||
val duration = Duration.between(current, lastRouteDate)
|
val duration = Duration.between(currentDate, lastRouteDate)
|
||||||
val routeUpdate = routeModel.curRoute.summary.duration / 6
|
val routeUpdate = routeModel.curRoute.summary.duration / 4
|
||||||
if (duration.abs().seconds > routeUpdate) {
|
if (duration.abs().seconds > routeUpdate) {
|
||||||
lastRouteDate = current
|
lastRouteDate = currentDate
|
||||||
val destination = location(
|
val destination = location(
|
||||||
routeModel.navState.destination.longitude,
|
routeModel.navState.destination.longitude,
|
||||||
routeModel.navState.destination.latitude
|
routeModel.navState.destination.latitude
|
||||||
|
|||||||
@@ -36,10 +36,9 @@ import androidx.lifecycle.asLiveData
|
|||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
import com.kouros.data.R
|
import com.kouros.data.R
|
||||||
import com.kouros.navigation.car.SurfaceRenderer
|
import com.kouros.navigation.car.SurfaceRenderer
|
||||||
import com.kouros.navigation.car.ViewStyle
|
|
||||||
import com.kouros.navigation.car.navigation.NavigationUtils
|
|
||||||
import com.kouros.navigation.car.navigation.RouteCarModel
|
import com.kouros.navigation.car.navigation.RouteCarModel
|
||||||
import com.kouros.navigation.data.Place
|
import com.kouros.navigation.data.Place
|
||||||
|
import com.kouros.navigation.data.ViewStyle
|
||||||
import com.kouros.navigation.data.route.Routes
|
import com.kouros.navigation.data.route.Routes
|
||||||
import com.kouros.navigation.model.NavigationViewModel
|
import com.kouros.navigation.model.NavigationViewModel
|
||||||
import com.kouros.navigation.utils.getSettingsRepository
|
import com.kouros.navigation.utils.getSettingsRepository
|
||||||
@@ -324,7 +323,7 @@ class RoutePreviewScreen(
|
|||||||
.addAction(navigateAction)
|
.addAction(navigateAction)
|
||||||
if (route.summary.trafficDelay > 60) {
|
if (route.summary.trafficDelay > 60) {
|
||||||
row.addText(createDelay(route))
|
row.addText(createDelay(route))
|
||||||
row.setImage(NavigationUtils(carContext).createCarIcon(R.drawable.traffic_jam_48px))
|
row.setImage(createCarIcon(carContext = carContext, R.drawable.traffic_jam_48px))
|
||||||
}
|
}
|
||||||
return row.build()
|
return row.build()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +1,63 @@
|
|||||||
package com.kouros.navigation.car.screen
|
package com.kouros.navigation.car.screen
|
||||||
|
|
||||||
|
import android.graphics.Canvas
|
||||||
|
import android.graphics.Color
|
||||||
|
import android.graphics.Paint
|
||||||
import androidx.annotation.DrawableRes
|
import androidx.annotation.DrawableRes
|
||||||
import androidx.car.app.CarContext
|
import androidx.car.app.CarContext
|
||||||
import androidx.car.app.model.Action
|
import androidx.car.app.model.Action
|
||||||
import androidx.car.app.model.Action.FLAG_DEFAULT
|
import androidx.car.app.model.Action.FLAG_DEFAULT
|
||||||
import androidx.car.app.model.ActionStrip
|
import androidx.car.app.model.ActionStrip
|
||||||
import androidx.car.app.model.CarIcon
|
import androidx.car.app.model.CarIcon
|
||||||
|
import androidx.car.app.model.Row
|
||||||
|
import androidx.core.graphics.createBitmap
|
||||||
import androidx.core.graphics.drawable.IconCompat
|
import androidx.core.graphics.drawable.IconCompat
|
||||||
import com.kouros.navigation.car.ViewStyle
|
import com.kouros.navigation.data.Constants.CHARGING_STATION
|
||||||
|
import com.kouros.navigation.data.Constants.FUEL_STATION
|
||||||
|
import com.kouros.navigation.data.Constants.PHARMACY
|
||||||
|
import com.kouros.navigation.data.ViewStyle
|
||||||
|
|
||||||
|
fun buildRowForTemplate(carContext: CarContext, title: Int, resource: Int): Row {
|
||||||
|
return Row.Builder()
|
||||||
|
.setTitle(carContext.getString(title))
|
||||||
|
.setImage(
|
||||||
|
CarIcon.Builder(
|
||||||
|
IconCompat.createWithResource(
|
||||||
|
carContext,
|
||||||
|
resource
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.build()
|
||||||
|
)
|
||||||
|
.build()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun createNumberIcon(category: String, number: String): IconCompat {
|
||||||
|
val size = 24
|
||||||
|
val bitmap = createBitmap(size, size)
|
||||||
|
val canvas = Canvas(bitmap)
|
||||||
|
val paint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
|
||||||
|
color = Color.WHITE
|
||||||
|
textSize = size * 0.7f
|
||||||
|
textAlign = Paint.Align.CENTER
|
||||||
|
isFakeBoldText = true
|
||||||
|
}
|
||||||
|
val xPos = size / 2f
|
||||||
|
val yPos = (size / 2f) - ((paint.descent() + paint.ascent()) / 2f)
|
||||||
|
|
||||||
|
val color = when (category) {
|
||||||
|
CHARGING_STATION -> Color.GREEN
|
||||||
|
FUEL_STATION -> Color.BLUE
|
||||||
|
PHARMACY -> Color.RED
|
||||||
|
else -> Color.WHITE
|
||||||
|
}
|
||||||
|
paint.color = color
|
||||||
|
canvas.drawCircle(size / 2f, size / 2f, size / 2f, paint)
|
||||||
|
|
||||||
|
paint.color = Color.WHITE
|
||||||
|
canvas.drawText(number, xPos, yPos, paint)
|
||||||
|
return IconCompat.createWithBitmap(bitmap)
|
||||||
|
}
|
||||||
|
|
||||||
fun createActionStrip(executeAction: () -> Action): ActionStrip {
|
fun createActionStrip(executeAction: () -> Action): ActionStrip {
|
||||||
val actionStripBuilder: ActionStrip.Builder = ActionStrip.Builder()
|
val actionStripBuilder: ActionStrip.Builder = ActionStrip.Builder()
|
||||||
@@ -17,7 +67,7 @@ fun createActionStrip(executeAction: () -> Action): ActionStrip {
|
|||||||
return actionStripBuilder.build()
|
return actionStripBuilder.build()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun createActionStripBuilder(action1: () -> Action, action2: () -> Action): ActionStrip.Builder {
|
fun createActionStripBuilder(action1: () -> Action, action2: () -> Action): ActionStrip.Builder {
|
||||||
val actionStripBuilder: ActionStrip.Builder = ActionStrip.Builder()
|
val actionStripBuilder: ActionStrip.Builder = ActionStrip.Builder()
|
||||||
actionStripBuilder.addAction(
|
actionStripBuilder.addAction(
|
||||||
action1()
|
action1()
|
||||||
@@ -31,7 +81,12 @@ fun createActionStrip(executeAction: () -> Action): ActionStrip {
|
|||||||
/**
|
/**
|
||||||
* Creates an ActionStrip builder for map-related actions like zoom and pan.
|
* Creates an ActionStrip builder for map-related actions like zoom and pan.
|
||||||
*/
|
*/
|
||||||
fun mapActionStrip(viewStyle: ViewStyle, zoomPlus: () -> Action, zoomMinus: () -> Action , panAction: () -> Action): ActionStrip {
|
fun mapActionStrip(
|
||||||
|
viewStyle: ViewStyle,
|
||||||
|
zoomPlus: () -> Action,
|
||||||
|
zoomMinus: () -> Action,
|
||||||
|
panAction: () -> Action
|
||||||
|
): ActionStrip {
|
||||||
val actionStripBuilder = ActionStrip.Builder()
|
val actionStripBuilder = ActionStrip.Builder()
|
||||||
.addAction(zoomPlus())
|
.addAction(zoomPlus())
|
||||||
.addAction(zoomMinus())
|
.addAction(zoomMinus())
|
||||||
@@ -47,7 +102,12 @@ fun mapActionStrip(viewStyle: ViewStyle, zoomPlus: () -> Action, zoomMinus: () -
|
|||||||
/**
|
/**
|
||||||
* Creates an action to do something.
|
* Creates an action to do something.
|
||||||
*/
|
*/
|
||||||
fun createAction(carContext: CarContext, @DrawableRes iconRes: Int, flag: Int = FLAG_DEFAULT, onClickAction: () -> Unit): Action {
|
fun createAction(
|
||||||
|
carContext: CarContext,
|
||||||
|
@DrawableRes iconRes: Int,
|
||||||
|
flag: Int = FLAG_DEFAULT,
|
||||||
|
onClickAction: () -> Unit
|
||||||
|
): Action {
|
||||||
return Action.Builder()
|
return Action.Builder()
|
||||||
.setIcon(createCarIcon(carContext, iconRes))
|
.setIcon(createCarIcon(carContext, iconRes))
|
||||||
.setFlags(flag)
|
.setFlags(flag)
|
||||||
@@ -60,3 +120,6 @@ fun createAction(carContext: CarContext, @DrawableRes iconRes: Int, flag: Int =
|
|||||||
fun createCarIcon(carContext: CarContext, @DrawableRes iconRes: Int): CarIcon {
|
fun createCarIcon(carContext: CarContext, @DrawableRes iconRes: Int): CarIcon {
|
||||||
return CarIcon.Builder(IconCompat.createWithResource(carContext, iconRes)).build()
|
return CarIcon.Builder(IconCompat.createWithResource(carContext, iconRes)).build()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -14,12 +14,12 @@ import androidx.core.graphics.drawable.IconCompat
|
|||||||
import androidx.lifecycle.Observer
|
import androidx.lifecycle.Observer
|
||||||
import com.kouros.data.R
|
import com.kouros.data.R
|
||||||
import com.kouros.navigation.car.SurfaceRenderer
|
import com.kouros.navigation.car.SurfaceRenderer
|
||||||
import com.kouros.navigation.car.ViewStyle
|
|
||||||
import com.kouros.navigation.data.Category
|
import com.kouros.navigation.data.Category
|
||||||
import com.kouros.navigation.data.Constants.CATEGORIES
|
import com.kouros.navigation.data.Constants.CATEGORIES
|
||||||
import com.kouros.navigation.data.Constants.FAVORITES
|
import com.kouros.navigation.data.Constants.FAVORITES
|
||||||
import com.kouros.navigation.data.Constants.RECENT
|
import com.kouros.navigation.data.Constants.RECENT
|
||||||
import com.kouros.navigation.data.Place
|
import com.kouros.navigation.data.Place
|
||||||
|
import com.kouros.navigation.data.ViewStyle
|
||||||
import com.kouros.navigation.data.nominatim.SearchResult
|
import com.kouros.navigation.data.nominatim.SearchResult
|
||||||
import com.kouros.navigation.model.NavigationViewModel
|
import com.kouros.navigation.model.NavigationViewModel
|
||||||
|
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import androidx.car.app.model.Template
|
|||||||
import androidx.core.graphics.drawable.IconCompat
|
import androidx.core.graphics.drawable.IconCompat
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
import com.kouros.data.R
|
import com.kouros.data.R
|
||||||
import com.kouros.navigation.car.navigation.NavigationUtils
|
import com.kouros.navigation.car.screen.buildRowForTemplate
|
||||||
import com.kouros.navigation.utils.getSettingsViewModel
|
import com.kouros.navigation.utils.getSettingsViewModel
|
||||||
import kotlinx.coroutines.flow.first
|
import kotlinx.coroutines.flow.first
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
@@ -40,19 +40,22 @@ class AudioSettings(
|
|||||||
val radioList =
|
val radioList =
|
||||||
ItemList.Builder()
|
ItemList.Builder()
|
||||||
.addItem(
|
.addItem(
|
||||||
NavigationUtils(carContext).buildRowForTemplate(
|
buildRowForTemplate(
|
||||||
|
carContext,
|
||||||
R.string.muted,
|
R.string.muted,
|
||||||
R.drawable.volume_off_24px
|
R.drawable.volume_off_24px
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
.addItem(
|
.addItem(
|
||||||
NavigationUtils(carContext).buildRowForTemplate(
|
buildRowForTemplate(
|
||||||
|
carContext,
|
||||||
R.string.unmuted,
|
R.string.unmuted,
|
||||||
R.drawable.volume_up_24px,
|
R.drawable.volume_up_24px,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
.addItem(
|
.addItem(
|
||||||
NavigationUtils(carContext).buildRowForTemplate(
|
buildRowForTemplate(
|
||||||
|
carContext,
|
||||||
R.string.alerts_only,
|
R.string.alerts_only,
|
||||||
R.drawable.warning_24px,
|
R.drawable.warning_24px,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -0,0 +1,77 @@
|
|||||||
|
package com.kouros.navigation.car.screen.settings
|
||||||
|
|
||||||
|
import androidx.car.app.CarContext
|
||||||
|
import androidx.car.app.Screen
|
||||||
|
import androidx.car.app.model.Action
|
||||||
|
import androidx.car.app.model.Header
|
||||||
|
import androidx.car.app.model.ItemList
|
||||||
|
import androidx.car.app.model.ListTemplate
|
||||||
|
import androidx.car.app.model.SectionedItemList
|
||||||
|
import androidx.car.app.model.Template
|
||||||
|
import androidx.lifecycle.lifecycleScope
|
||||||
|
import com.kouros.data.R
|
||||||
|
import com.kouros.navigation.car.screen.buildRowForTemplate
|
||||||
|
import com.kouros.navigation.data.EngineType
|
||||||
|
import com.kouros.navigation.model.NavigationViewModel
|
||||||
|
import com.kouros.navigation.utils.getSettingsViewModel
|
||||||
|
import kotlinx.coroutines.flow.first
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
|
class CarSettings(
|
||||||
|
private val carContext: CarContext,
|
||||||
|
private var navigationViewModel: NavigationViewModel
|
||||||
|
) :
|
||||||
|
Screen(carContext) {
|
||||||
|
|
||||||
|
private var engineType = EngineType.COMBUSTION.ordinal
|
||||||
|
|
||||||
|
val settingsViewModel = getSettingsViewModel(carContext)
|
||||||
|
|
||||||
|
init {
|
||||||
|
lifecycleScope.launch {
|
||||||
|
settingsViewModel.engineType.first()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
override fun onGetTemplate(): Template {
|
||||||
|
engineType = settingsViewModel.engineType.value
|
||||||
|
val templateBuilder = ListTemplate.Builder()
|
||||||
|
val radioList =
|
||||||
|
ItemList.Builder()
|
||||||
|
.addItem(
|
||||||
|
buildRowForTemplate(carContext,
|
||||||
|
R.string.combustion,
|
||||||
|
R.drawable.ev_station_24px
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.addItem(
|
||||||
|
buildRowForTemplate(carContext,
|
||||||
|
R.string.electric,
|
||||||
|
R.drawable.electric_car_24px
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.setOnSelectedListener { index: Int ->
|
||||||
|
this.onSelected(index)
|
||||||
|
}
|
||||||
|
.setSelectedIndex(engineType)
|
||||||
|
.build()
|
||||||
|
|
||||||
|
return templateBuilder
|
||||||
|
.addSectionedList(
|
||||||
|
SectionedItemList.create(
|
||||||
|
radioList,
|
||||||
|
carContext.getString(R.string.engine_type)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.setHeader(
|
||||||
|
Header.Builder()
|
||||||
|
.setTitle(carContext.getString(R.string.car_settings))
|
||||||
|
.setStartHeaderAction(Action.BACK)
|
||||||
|
.build()
|
||||||
|
)
|
||||||
|
.build()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun onSelected(index: Int) {
|
||||||
|
settingsViewModel.onEngineTypeChanged(index)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -11,7 +11,7 @@ import androidx.car.app.model.SectionedItemList
|
|||||||
import androidx.car.app.model.Template
|
import androidx.car.app.model.Template
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
import com.kouros.data.R
|
import com.kouros.data.R
|
||||||
import com.kouros.navigation.car.navigation.NavigationUtils
|
import com.kouros.navigation.car.screen.buildRowForTemplate
|
||||||
import com.kouros.navigation.utils.getSettingsViewModel
|
import com.kouros.navigation.utils.getSettingsViewModel
|
||||||
import kotlinx.coroutines.flow.first
|
import kotlinx.coroutines.flow.first
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
@@ -34,19 +34,19 @@ class DarkModeSettings(private val carContext: CarContext) : Screen(carContext)
|
|||||||
val radioList =
|
val radioList =
|
||||||
ItemList.Builder()
|
ItemList.Builder()
|
||||||
.addItem(
|
.addItem(
|
||||||
NavigationUtils(carContext).buildRowForTemplate(
|
buildRowForTemplate(carContext,
|
||||||
R.string.off_action_title,
|
R.string.off_action_title,
|
||||||
R.drawable.light_mode_24px
|
R.drawable.light_mode_24px
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
.addItem(
|
.addItem(
|
||||||
NavigationUtils(carContext).buildRowForTemplate(
|
buildRowForTemplate(carContext,
|
||||||
R.string.on_action_title,
|
R.string.on_action_title,
|
||||||
R.drawable.dark_mode_24px
|
R.drawable.dark_mode_24px
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
.addItem(
|
.addItem(
|
||||||
NavigationUtils(carContext).buildRowForTemplate(
|
buildRowForTemplate(carContext,
|
||||||
R.string.use_car_settings,
|
R.string.use_car_settings,
|
||||||
R.drawable.directions_car_24px
|
R.drawable.directions_car_24px
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import androidx.car.app.model.Toggle
|
|||||||
import androidx.core.graphics.drawable.IconCompat
|
import androidx.core.graphics.drawable.IconCompat
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
import com.kouros.data.R
|
import com.kouros.data.R
|
||||||
import com.kouros.navigation.car.navigation.NavigationUtils
|
import com.kouros.navigation.car.screen.createCarIcon
|
||||||
import com.kouros.navigation.model.NavigationViewModel
|
import com.kouros.navigation.model.NavigationViewModel
|
||||||
import com.kouros.navigation.utils.getSettingsViewModel
|
import com.kouros.navigation.utils.getSettingsViewModel
|
||||||
import kotlinx.coroutines.flow.first
|
import kotlinx.coroutines.flow.first
|
||||||
@@ -62,7 +62,7 @@ class NavigationSettings(
|
|||||||
buildRowForTemplate(
|
buildRowForTemplate(
|
||||||
R.string.avoid_highways_row_title,
|
R.string.avoid_highways_row_title,
|
||||||
highwayToggle,
|
highwayToggle,
|
||||||
NavigationUtils(carContext).createCarIcon(R.drawable.baseline_add_road_24)
|
createCarIcon(carContext, R.drawable.baseline_add_road_24)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -72,7 +72,7 @@ class NavigationSettings(
|
|||||||
settingsViewModel.onAvoidTollway(checked)
|
settingsViewModel.onAvoidTollway(checked)
|
||||||
tollWayToggleState = !tollWayToggleState
|
tollWayToggleState = !tollWayToggleState
|
||||||
}.setChecked(tollWayToggleState).build()
|
}.setChecked(tollWayToggleState).build()
|
||||||
listBuilder.addItem(buildRowForTemplate(R.string.avoid_tolls_row_title, tollwayToggle, NavigationUtils(carContext).createCarIcon(R.drawable.baseline_toll_24)))
|
listBuilder.addItem(buildRowForTemplate(R.string.avoid_tolls_row_title, tollwayToggle, createCarIcon(carContext,R.drawable.baseline_toll_24)))
|
||||||
|
|
||||||
// Ferry
|
// Ferry
|
||||||
val ferryToggle: Toggle =
|
val ferryToggle: Toggle =
|
||||||
@@ -80,7 +80,7 @@ class NavigationSettings(
|
|||||||
settingsViewModel.onAvoidFerry(checked)
|
settingsViewModel.onAvoidFerry(checked)
|
||||||
ferryToggleState = !ferryToggleState
|
ferryToggleState = !ferryToggleState
|
||||||
}.setChecked(ferryToggleState).build()
|
}.setChecked(ferryToggleState).build()
|
||||||
listBuilder.addItem(buildRowForTemplate(R.string.avoid_ferries, ferryToggle, NavigationUtils(carContext).createCarIcon(R.drawable.baseline_directions_boat_filled_24)))
|
listBuilder.addItem(buildRowForTemplate(R.string.avoid_ferries, ferryToggle, createCarIcon(carContext, R.drawable.baseline_directions_boat_filled_24)))
|
||||||
|
|
||||||
// CarLocation
|
// CarLocation
|
||||||
val carLocationToggle: Toggle =
|
val carLocationToggle: Toggle =
|
||||||
@@ -93,7 +93,7 @@ class NavigationSettings(
|
|||||||
buildRowForTemplate(
|
buildRowForTemplate(
|
||||||
R.string.use_car_location,
|
R.string.use_car_location,
|
||||||
carLocationToggle,
|
carLocationToggle,
|
||||||
NavigationUtils(carContext).createCarIcon(R.drawable.ic_place_white_24dp)
|
createCarIcon(carContext,R.drawable.ic_place_white_24dp)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -85,7 +85,7 @@ class SettingsScreen(
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
// Navigation --------------
|
// Drive settings --------------
|
||||||
listBuilder = ItemList.Builder()
|
listBuilder = ItemList.Builder()
|
||||||
listBuilder.addItem(
|
listBuilder.addItem(
|
||||||
buildRowForTemplate(
|
buildRowForTemplate(
|
||||||
@@ -94,10 +94,17 @@ class SettingsScreen(
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
listBuilder.addItem(
|
||||||
|
buildRowForTemplate(
|
||||||
|
CarSettings(carContext, navigationViewModel),
|
||||||
|
R.string.car_settings
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
templateBuilder.addSectionedList(
|
templateBuilder.addSectionedList(
|
||||||
SectionedItemList.create(
|
SectionedItemList.create(
|
||||||
listBuilder.build(),
|
listBuilder.build(),
|
||||||
carContext.getString(R.string.navigation_settings)
|
carContext.getString(R.string.drive_settings)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -109,6 +116,8 @@ class SettingsScreen(
|
|||||||
.setStartHeaderAction(Action.BACK)
|
.setStartHeaderAction(Action.BACK)
|
||||||
.build())
|
.build())
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getTitle(): String {
|
private fun getTitle(): String {
|
||||||
|
|||||||
@@ -2,7 +2,9 @@ package com.kouros.navigation.data
|
|||||||
|
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
|
|
||||||
val NavigationColor = Color(0xFF16BBB6)
|
val NavigationColorLight = Color(0xFF066462)
|
||||||
|
|
||||||
|
val NavigationColorDark = Color(0xFF10DED9)
|
||||||
|
|
||||||
val RouteColor = Color(0xFF7B06E1)
|
val RouteColor = Color(0xFF7B06E1)
|
||||||
|
|
||||||
|
|||||||
@@ -77,9 +77,10 @@ data class Locations (
|
|||||||
)
|
)
|
||||||
|
|
||||||
data class SearchFilter(
|
data class SearchFilter(
|
||||||
var avoidMotorway: Boolean = false,
|
val avoidMotorway: Boolean = false,
|
||||||
var avoidTollway : Boolean = false,
|
val avoidTollway : Boolean = false,
|
||||||
var avoidFerry : Boolean = false,
|
val avoidFerry : Boolean = false,
|
||||||
|
val engineType : Int = 0,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -139,11 +140,27 @@ object Constants {
|
|||||||
const val TILT = 60.0
|
const val TILT = 60.0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enum representing different map view modes.
|
||||||
|
* - VIEW: Active navigation mode with follow-car camera
|
||||||
|
* - PREVIEW: Route overview before starting navigation
|
||||||
|
* - PAN_VIEW: User-controlled map panning
|
||||||
|
* - AMENITY_VIEW: Displaying POI/amenity locations
|
||||||
|
*/
|
||||||
|
enum class ViewStyle {
|
||||||
|
VIEW, PREVIEW, PAN_VIEW, AMENITY_VIEW
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
enum class RouteEngine {
|
enum class RouteEngine {
|
||||||
VALHALLA, OSRM, TOMTOM
|
VALHALLA, OSRM, TOMTOM
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum class EngineType {
|
||||||
|
COMBUSTION, ELECTRIC
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
enum class NavigationThemeColor(val color: Long) {
|
enum class NavigationThemeColor(val color: Long) {
|
||||||
RED(0xFFD32F2F),
|
RED(0xFFD32F2F),
|
||||||
ORANGE(0xFFF57C00),
|
ORANGE(0xFFF57C00),
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package com.kouros.navigation.data
|
|||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.location.Location
|
import android.location.Location
|
||||||
|
import android.util.Log
|
||||||
import com.kouros.navigation.utils.GeoUtils.calculateSquareRadius
|
import com.kouros.navigation.utils.GeoUtils.calculateSquareRadius
|
||||||
import java.net.Authenticator
|
import java.net.Authenticator
|
||||||
import java.net.HttpURLConnection
|
import java.net.HttpURLConnection
|
||||||
@@ -66,7 +67,7 @@ abstract class NavigationRepository {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
println(url)
|
Log.d("fetchUrl", url)
|
||||||
val httpURLConnection = URL(url).openConnection() as HttpURLConnection
|
val httpURLConnection = URL(url).openConnection() as HttpURLConnection
|
||||||
httpURLConnection.setRequestProperty(
|
httpURLConnection.setRequestProperty(
|
||||||
"Accept",
|
"Accept",
|
||||||
@@ -81,7 +82,7 @@ abstract class NavigationRepository {
|
|||||||
return response
|
return response
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
println("Exception ${e.message}")
|
Log.e("fetchUrl", e.toString())
|
||||||
}
|
}
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package com.kouros.navigation.data
|
package com.kouros.navigation.data
|
||||||
|
|
||||||
|
import android.util.Log
|
||||||
import com.google.gson.GsonBuilder
|
import com.google.gson.GsonBuilder
|
||||||
import com.kouros.navigation.data.osrm.OsrmResponse
|
import com.kouros.navigation.data.osrm.OsrmResponse
|
||||||
import com.kouros.navigation.data.osrm.OsrmRoute
|
import com.kouros.navigation.data.osrm.OsrmRoute
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import androidx.datastore.preferences.core.edit
|
|||||||
import androidx.datastore.preferences.core.intPreferencesKey
|
import androidx.datastore.preferences.core.intPreferencesKey
|
||||||
import androidx.datastore.preferences.core.stringPreferencesKey
|
import androidx.datastore.preferences.core.stringPreferencesKey
|
||||||
import androidx.datastore.preferences.preferencesDataStore
|
import androidx.datastore.preferences.preferencesDataStore
|
||||||
|
import com.kouros.navigation.data.EngineType
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.first
|
import kotlinx.coroutines.flow.first
|
||||||
import kotlinx.coroutines.flow.map
|
import kotlinx.coroutines.flow.map
|
||||||
@@ -55,6 +56,8 @@ class DataStoreManager(private val context: Context) {
|
|||||||
|
|
||||||
val TRIP_SUGGESTION = booleanPreferencesKey("TripSuggestion")
|
val TRIP_SUGGESTION = booleanPreferencesKey("TripSuggestion")
|
||||||
|
|
||||||
|
val ENGINE_TYPE = intPreferencesKey("EngineType")
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Read values
|
// Read values
|
||||||
@@ -136,6 +139,12 @@ class DataStoreManager(private val context: Context) {
|
|||||||
preferences[PreferencesKeys.TRIP_SUGGESTION] == true
|
preferences[PreferencesKeys.TRIP_SUGGESTION] == true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val engineTypeFlow: Flow<Int> =
|
||||||
|
context.dataStore.data.map { preferences ->
|
||||||
|
preferences[PreferencesKeys.ENGINE_TYPE]
|
||||||
|
?: EngineType.COMBUSTION.ordinal
|
||||||
|
}
|
||||||
|
|
||||||
// Save values
|
// Save values
|
||||||
suspend fun setShow3D(enabled: Boolean) {
|
suspend fun setShow3D(enabled: Boolean) {
|
||||||
context.dataStore.edit { preferences ->
|
context.dataStore.edit { preferences ->
|
||||||
@@ -220,4 +229,10 @@ class DataStoreManager(private val context: Context) {
|
|||||||
preferences[PreferencesKeys.TRIP_SUGGESTION] = enabled
|
preferences[PreferencesKeys.TRIP_SUGGESTION] = enabled
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
suspend fun setEngineType(mode: Int) {
|
||||||
|
context.dataStore.edit { prefs ->
|
||||||
|
prefs[PreferencesKeys.ENGINE_TYPE] = mode
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package com.kouros.navigation.data.tomtom
|
|||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.location.Location
|
import android.location.Location
|
||||||
import com.kouros.data.R
|
import com.kouros.data.R
|
||||||
|
import com.kouros.navigation.data.EngineType
|
||||||
import com.kouros.navigation.data.NavigationRepository
|
import com.kouros.navigation.data.NavigationRepository
|
||||||
import com.kouros.navigation.data.SearchFilter
|
import com.kouros.navigation.data.SearchFilter
|
||||||
import com.kouros.navigation.utils.GeoUtils.calculateSquareRadius
|
import com.kouros.navigation.utils.GeoUtils.calculateSquareRadius
|
||||||
@@ -19,9 +20,9 @@ const val tomtomTrafficUrl = "https://api.tomtom.com/traffic/services/5/incident
|
|||||||
private const val tomtomFields =
|
private const val tomtomFields =
|
||||||
"{incidents{type,geometry{type,coordinates},properties{iconCategory,events{description}}}}"
|
"{incidents{type,geometry{type,coordinates},properties{iconCategory,events{description}}}}"
|
||||||
|
|
||||||
const val useLocal = true
|
const val useLocal = false
|
||||||
|
|
||||||
const val useLocalTraffic = true
|
const val useLocalTraffic = false
|
||||||
|
|
||||||
|
|
||||||
class TomTomRepository : NavigationRepository() {
|
class TomTomRepository : NavigationRepository() {
|
||||||
@@ -38,6 +39,7 @@ class TomTomRepository : NavigationRepository() {
|
|||||||
false
|
false
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
var engineType = "combustion"
|
||||||
var filter = ""
|
var filter = ""
|
||||||
if (searchFilter.avoidMotorway) {
|
if (searchFilter.avoidMotorway) {
|
||||||
filter = "$filter&avoid=motorways"
|
filter = "$filter&avoid=motorways"
|
||||||
@@ -48,6 +50,9 @@ class TomTomRepository : NavigationRepository() {
|
|||||||
if (searchFilter.avoidFerry) {
|
if (searchFilter.avoidFerry) {
|
||||||
filter = "$filter&avoid=ferries"
|
filter = "$filter&avoid=ferries"
|
||||||
}
|
}
|
||||||
|
if (searchFilter.engineType == EngineType.ELECTRIC.ordinal) {
|
||||||
|
engineType = "electric"
|
||||||
|
}
|
||||||
val repository = getSettingsRepository(context)
|
val repository = getSettingsRepository(context)
|
||||||
val tomtomApiKey = runBlocking { repository.tomTomApiKeyFlow.first() }
|
val tomtomApiKey = runBlocking { repository.tomTomApiKeyFlow.first() }
|
||||||
val currentLocale = Locale.getDefault()
|
val currentLocale = Locale.getDefault()
|
||||||
@@ -60,7 +65,7 @@ class TomTomRepository : NavigationRepository() {
|
|||||||
"&instructionsType=text&language=$language§ionType=lanes" +
|
"&instructionsType=text&language=$language§ionType=lanes" +
|
||||||
"&routeRepresentation=encodedPolyline" +
|
"&routeRepresentation=encodedPolyline" +
|
||||||
"&maxAlternatives=2" +
|
"&maxAlternatives=2" +
|
||||||
"&vehicleEngineType=combustion$filter&key=$tomtomApiKey"
|
"&vehicleEngineType=$engineType$filter&key=$tomtomApiKey"
|
||||||
return fetchUrl(
|
return fetchUrl(
|
||||||
url,
|
url,
|
||||||
false
|
false
|
||||||
|
|||||||
@@ -163,7 +163,7 @@ class NavigationViewModel(private val repository: NavigationRepository) : ViewMo
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
recentPlaces.postValue(pl)
|
recentPlaces.postValue(pl.sortedBy { it.distance })
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
e.printStackTrace()
|
e.printStackTrace()
|
||||||
}
|
}
|
||||||
@@ -538,7 +538,8 @@ class NavigationViewModel(private val repository: NavigationRepository) : ViewMo
|
|||||||
val avoidMotorway = runBlocking { repository.avoidMotorwayFlow.first() }
|
val avoidMotorway = runBlocking { repository.avoidMotorwayFlow.first() }
|
||||||
val avoidTollway = runBlocking { repository.avoidTollwayFlow.first() }
|
val avoidTollway = runBlocking { repository.avoidTollwayFlow.first() }
|
||||||
val avoidFerry = runBlocking { repository.avoidFerryFlow.first() }
|
val avoidFerry = runBlocking { repository.avoidFerryFlow.first() }
|
||||||
return SearchFilter(avoidMotorway, avoidTollway, avoidFerry)
|
val engineType = runBlocking { repository.engineTypeFlow.first() }
|
||||||
|
return SearchFilter(avoidMotorway, avoidTollway, avoidFerry, engineType)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package com.kouros.navigation.model
|
package com.kouros.navigation.model
|
||||||
|
|
||||||
import android.location.Location
|
import android.location.Location
|
||||||
|
import android.util.Log
|
||||||
import androidx.car.app.navigation.model.Step
|
import androidx.car.app.navigation.model.Step
|
||||||
import com.kouros.navigation.data.Constants.MAXIMUM_LOCATION_DISTANCE
|
import com.kouros.navigation.data.Constants.MAXIMUM_LOCATION_DISTANCE
|
||||||
import com.kouros.navigation.data.Constants.NEAREST_LOCATION_DISTANCE
|
import com.kouros.navigation.data.Constants.NEAREST_LOCATION_DISTANCE
|
||||||
|
|||||||
@@ -96,6 +96,12 @@ class SettingsViewModel(private val repository: SettingsRepository) : ViewModel(
|
|||||||
false
|
false
|
||||||
)
|
)
|
||||||
|
|
||||||
|
val engineType = repository.engineTypeFlow.stateIn(
|
||||||
|
viewModelScope,
|
||||||
|
SharingStarted.WhileSubscribed(5_000),
|
||||||
|
0
|
||||||
|
)
|
||||||
|
|
||||||
fun onShow3DChanged(enabled: Boolean) {
|
fun onShow3DChanged(enabled: Boolean) {
|
||||||
viewModelScope.launch { repository.setShow3D(enabled) }
|
viewModelScope.launch { repository.setShow3D(enabled) }
|
||||||
}
|
}
|
||||||
@@ -148,4 +154,9 @@ class SettingsViewModel(private val repository: SettingsRepository) : ViewModel(
|
|||||||
fun onTripSuggestion(enabled: Boolean) {
|
fun onTripSuggestion(enabled: Boolean) {
|
||||||
viewModelScope.launch { repository.setTripSuggestion(enabled) }
|
viewModelScope.launch { repository.setTripSuggestion(enabled) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun onEngineTypeChanged(mode: Int) {
|
||||||
|
viewModelScope.launch { repository.setEngineType(mode) }
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -47,6 +47,9 @@ class SettingsRepository(
|
|||||||
val tripSuggestionFlow: Flow<Boolean> =
|
val tripSuggestionFlow: Flow<Boolean> =
|
||||||
dataStoreManager.tripSuggestionFlow
|
dataStoreManager.tripSuggestionFlow
|
||||||
|
|
||||||
|
val engineTypeFlow: Flow<Int> =
|
||||||
|
dataStoreManager.engineTypeFlow
|
||||||
|
|
||||||
suspend fun setShow3D(enabled: Boolean) {
|
suspend fun setShow3D(enabled: Boolean) {
|
||||||
dataStoreManager.setShow3D(enabled)
|
dataStoreManager.setShow3D(enabled)
|
||||||
}
|
}
|
||||||
@@ -102,4 +105,8 @@ class SettingsRepository(
|
|||||||
suspend fun setTripSuggestion(enabled: Boolean) {
|
suspend fun setTripSuggestion(enabled: Boolean) {
|
||||||
dataStoreManager.setTripSuggestion(enabled)
|
dataStoreManager.setTripSuggestion(enabled)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
suspend fun setEngineType(mode: Int) {
|
||||||
|
dataStoreManager.setEngineType(mode)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ import com.kouros.navigation.data.osrm.OsrmRepository
|
|||||||
import com.kouros.navigation.data.tomtom.TomTomRepository
|
import com.kouros.navigation.data.tomtom.TomTomRepository
|
||||||
import com.kouros.navigation.data.valhalla.ValhallaRepository
|
import com.kouros.navigation.data.valhalla.ValhallaRepository
|
||||||
import com.kouros.navigation.model.NavigationViewModel
|
import com.kouros.navigation.model.NavigationViewModel
|
||||||
import com.kouros.navigation.utils.GeoUtils.calculateSquareRadius
|
|
||||||
import kotlinx.coroutines.flow.first
|
import kotlinx.coroutines.flow.first
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
import java.lang.Math.toDegrees
|
import java.lang.Math.toDegrees
|
||||||
@@ -29,6 +28,8 @@ import kotlin.math.pow
|
|||||||
import kotlin.math.roundToInt
|
import kotlin.math.roundToInt
|
||||||
import kotlin.time.Duration
|
import kotlin.time.Duration
|
||||||
import kotlin.time.Duration.Companion.seconds
|
import kotlin.time.Duration.Companion.seconds
|
||||||
|
import kotlin.time.DurationUnit
|
||||||
|
import kotlin.time.toDuration
|
||||||
|
|
||||||
|
|
||||||
object NavigationUtils {
|
object NavigationUtils {
|
||||||
@@ -50,7 +51,7 @@ fun calculateZoom(speed: Double?): Double {
|
|||||||
}
|
}
|
||||||
val speedKmh = (speed * 3.6).toInt()
|
val speedKmh = (speed * 3.6).toInt()
|
||||||
val zoom = when (speedKmh) {
|
val zoom = when (speedKmh) {
|
||||||
in 0..10 -> 18.0
|
in 0..10 -> 17.0
|
||||||
in 11..30 -> 17.5
|
in 11..30 -> 17.5
|
||||||
in 31..65 -> 17.0
|
in 31..65 -> 17.0
|
||||||
in 66..70 -> 16.5
|
in 66..70 -> 16.5
|
||||||
@@ -128,14 +129,20 @@ fun Double.round(numFractionDigits: Int): Double {
|
|||||||
return (this * factor).roundToInt() / factor
|
return (this * factor).roundToInt() / factor
|
||||||
}
|
}
|
||||||
|
|
||||||
fun duration(preview: Boolean, bearing: Double, lastBearing: Double): Duration {
|
fun duration(
|
||||||
|
preview: Boolean,
|
||||||
|
bearing: Double,
|
||||||
|
lastBearing: Double,
|
||||||
|
lastLocationUpdate: LocalDateTime
|
||||||
|
): Duration {
|
||||||
if (preview) {
|
if (preview) {
|
||||||
return 3.seconds
|
return 3.seconds
|
||||||
}
|
}
|
||||||
val cameraDuration = if ((lastBearing - bearing).absoluteValue > 20.0) {
|
val cameraDuration = if ((lastBearing - bearing).absoluteValue > 20.0) {
|
||||||
2.seconds
|
2.seconds
|
||||||
} else {
|
} else {
|
||||||
1.seconds
|
val updateDuration = java.time.Duration.between(LocalDateTime.now(), lastLocationUpdate)
|
||||||
|
((updateDuration!!.toMillis().absoluteValue * 1.2).toDuration(DurationUnit.MILLISECONDS))
|
||||||
}
|
}
|
||||||
return cameraDuration
|
return cameraDuration
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -66,4 +66,9 @@
|
|||||||
<string name="general">Allgemein</string>
|
<string name="general">Allgemein</string>
|
||||||
<string name="traffic">Verkehr anzeigen</string>
|
<string name="traffic">Verkehr anzeigen</string>
|
||||||
<string name="trip_suggestion">Fahrten-Vorschläge</string>
|
<string name="trip_suggestion">Fahrten-Vorschläge</string>
|
||||||
|
<string name="drive_settings">Drive settings</string>
|
||||||
|
<string name="car_settings">Car settings</string>
|
||||||
|
<string name="combustion">Combustion</string>
|
||||||
|
<string name="electric">Electric</string>
|
||||||
|
<string name="engine_type">Engine type</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -50,4 +50,9 @@
|
|||||||
<string name="general">Γενικά</string>
|
<string name="general">Γενικά</string>
|
||||||
<string name="traffic">Εμφάνιση κίνησης</string>
|
<string name="traffic">Εμφάνιση κίνησης</string>
|
||||||
<string name="trip_suggestion">Προτάσεις διαδρομής</string>
|
<string name="trip_suggestion">Προτάσεις διαδρομής</string>
|
||||||
|
<string name="drive_settings">Drive settings</string>
|
||||||
|
<string name="car_settings">Car settings</string>
|
||||||
|
<string name="combustion">Combustion</string>
|
||||||
|
<string name="electric">Electric</string>
|
||||||
|
<string name="engine_type">Engine type</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -50,4 +50,9 @@
|
|||||||
<string name="general">Ogólne</string>
|
<string name="general">Ogólne</string>
|
||||||
<string name="traffic">Pokaż natężenie ruchu</string>
|
<string name="traffic">Pokaż natężenie ruchu</string>
|
||||||
<string name="trip_suggestion">Sugestie dotyczące podróży</string>
|
<string name="trip_suggestion">Sugestie dotyczące podróży</string>
|
||||||
|
<string name="drive_settings">Drive settings</string>
|
||||||
|
<string name="car_settings">Car settings</string>
|
||||||
|
<string name="combustion">Combustion</string>
|
||||||
|
<string name="electric">Electric</string>
|
||||||
|
<string name="engine_type">Engine type</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -53,4 +53,9 @@
|
|||||||
<string name="general">General</string>
|
<string name="general">General</string>
|
||||||
<string name="traffic">Show traffic</string>
|
<string name="traffic">Show traffic</string>
|
||||||
<string name="trip_suggestion">Trip suggestions</string>
|
<string name="trip_suggestion">Trip suggestions</string>
|
||||||
|
<string name="drive_settings">Drive settings</string>
|
||||||
|
<string name="car_settings">Car settings</string>
|
||||||
|
<string name="combustion">Combustion</string>
|
||||||
|
<string name="electric">Electric</string>
|
||||||
|
<string name="engine_type">Engine type</string>
|
||||||
</resources>
|
</resources>
|
||||||