Launcher Icons, NotificationService
@@ -5,3 +5,6 @@
|
||||
## Simulation
|
||||
|
||||
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 {
|
||||
alias(libs.plugins.android.application)
|
||||
alias(libs.plugins.kotlin.compose)
|
||||
}
|
||||
|
||||
val properties = Properties().apply {
|
||||
load(File("signing.properties").reader())
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "com.kouros.navigation"
|
||||
compileSdk = 36
|
||||
@@ -13,8 +17,8 @@ android {
|
||||
applicationId = "com.kouros.navigation"
|
||||
minSdk = 33
|
||||
targetSdk = 36
|
||||
versionCode = 76
|
||||
versionName = "0.2.0.76"
|
||||
versionCode = 82
|
||||
versionName = "0.2.0.82"
|
||||
base.archivesName = "navi-$versionName"
|
||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||
}
|
||||
@@ -22,23 +26,22 @@ android {
|
||||
signingConfigs {
|
||||
getByName("debug") {
|
||||
keyAlias = "release"
|
||||
keyPassword = "zeta67#gAe3aN3"
|
||||
storeFile = file("/home/kouros/work/keystore/keystoreRelease")
|
||||
storePassword = "zeta67#gAe3aN3"
|
||||
keyPassword = properties.getProperty("keyPassword")
|
||||
storeFile = file(properties.getProperty("storeFile"))
|
||||
storePassword = properties.getProperty("storePassword")
|
||||
}
|
||||
create("release") {
|
||||
keyAlias = "release"
|
||||
keyPassword = "zeta67#gAe3aN3"
|
||||
storeFile = file("/home/kouros/work/keystore/keystoreRelease")
|
||||
storePassword = "zeta67#gAe3aN3"
|
||||
keyPassword = properties.getProperty("keyPassword")
|
||||
storeFile = file(properties.getProperty("storeFile"))
|
||||
storePassword = properties.getProperty("storePassword")
|
||||
}
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
// Enables code-related app optimization.
|
||||
signingConfig = signingConfigs.getByName("release")
|
||||
isMinifyEnabled = false
|
||||
// Enables resource shrinking.
|
||||
isShrinkResources = false
|
||||
|
||||
proguardFiles(
|
||||
@@ -48,15 +51,20 @@ android {
|
||||
}
|
||||
}
|
||||
// Specifies one flavor dimension.
|
||||
flavorDimensions += "version"
|
||||
flavorDimensions += "store"
|
||||
productFlavors {
|
||||
create("play") {
|
||||
dimension = "store"
|
||||
applicationIdSuffix = ".play"
|
||||
versionNameSuffix = "-play"
|
||||
}
|
||||
create("demo") {
|
||||
dimension = "version"
|
||||
dimension = "store"
|
||||
applicationIdSuffix = ".demo"
|
||||
versionNameSuffix = "-demo"
|
||||
}
|
||||
create("full") {
|
||||
dimension = "version"
|
||||
dimension = "store"
|
||||
applicationIdSuffix = ".full"
|
||||
versionNameSuffix = "-full"
|
||||
}
|
||||
@@ -66,11 +74,20 @@ android {
|
||||
targetCompatibility = JavaVersion.VERSION_21
|
||||
}
|
||||
|
||||
buildFeatures {
|
||||
compose = true
|
||||
packaging {
|
||||
resources {
|
||||
excludes +=
|
||||
setOf(
|
||||
"/META-INF/{AL2.0,LGPL2.1}",
|
||||
"/META-INF/*.version",
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
buildFeatures {
|
||||
compose = true
|
||||
buildConfig = true
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
@@ -95,7 +112,6 @@ dependencies {
|
||||
implementation(libs.androidx.compose.ui.graphics)
|
||||
implementation(libs.androidx.window)
|
||||
implementation(libs.androidx.compose.foundation.layout)
|
||||
implementation(libs.android.gpx.parser)
|
||||
implementation(libs.androidx.navigation.compose)
|
||||
implementation(libs.kotlinx.serialization.json)
|
||||
implementation(libs.androidx.compose.foundation.layout)
|
||||
@@ -107,4 +123,3 @@ dependencies {
|
||||
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
|
||||
|
||||
import android.content.Context
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import com.kouros.data.R
|
||||
import com.kouros.navigation.MainApplication.Companion.navigationViewModel
|
||||
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.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
import org.joda.time.DateTime
|
||||
import kotlin.collections.forEach
|
||||
|
||||
var simulationJob: Job? = null
|
||||
fun simulate(routeModel: RouteModel, mock: MockLocation) {
|
||||
@@ -91,43 +83,6 @@ fun testSingleUpdate(
|
||||
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 {
|
||||
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.RouteModel
|
||||
import com.kouros.navigation.model.SimulationType
|
||||
import com.kouros.navigation.model.gpx
|
||||
import com.kouros.navigation.model.simulate
|
||||
import com.kouros.navigation.model.simulationJob
|
||||
import com.kouros.navigation.model.test
|
||||
@@ -112,11 +111,10 @@ class MainActivity : ComponentActivity() {
|
||||
when (type) {
|
||||
SimulationType.SIMULATE -> simulate(routeModel, mock)
|
||||
SimulationType.TEST -> test(applicationContext, routeModel)
|
||||
SimulationType.GPX -> gpx(
|
||||
context = applicationContext, 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.lifecycle.MutableLiveData
|
||||
import androidx.window.layout.WindowMetricsCalculator
|
||||
import com.kouros.navigation.car.ViewStyle
|
||||
import com.kouros.navigation.car.map.MapLibre
|
||||
import com.kouros.navigation.car.map.NavigationImage
|
||||
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.navigation.NavigationInfo
|
||||
|
||||
@@ -48,5 +48,11 @@ fun AppNavGraph(mainActivity: MainActivity) {
|
||||
navController
|
||||
) { 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") {
|
||||
SettingsScreen(viewModel, navController, function)
|
||||
}
|
||||
if (route == "car_settings") {
|
||||
CarScreen (viewModel = viewModel, function)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,7 +51,14 @@ fun SettingsScreen(
|
||||
name = "Navigation Settings",
|
||||
description = "",
|
||||
icon = R.drawable.navigation_24px
|
||||
),
|
||||
Item(
|
||||
id = "car_settings",
|
||||
name = "Car Settings",
|
||||
description = "",
|
||||
icon = R.drawable.electric_car_24px
|
||||
)
|
||||
|
||||
)
|
||||
|
||||
Scaffold(
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
android:height="108dp"
|
||||
android:viewportWidth="960"
|
||||
android:viewportHeight="960"
|
||||
android:tint="#000000">
|
||||
android:tint="#1A7416">
|
||||
<group android:scaleX="0.7888"
|
||||
android:scaleY="0.7888"
|
||||
android:translateX="101.376"
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@drawable/ic_launcher_background"/>
|
||||
<background android:drawable="@color/ic_launcher_background"/>
|
||||
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
|
||||
<monochrome android:drawable="@drawable/ic_launcher_foreground"/>
|
||||
</adaptive-icon>
|
||||
@@ -1,5 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@drawable/ic_launcher_background"/>
|
||||
<background android:drawable="@color/ic_launcher_background"/>
|
||||
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
|
||||
<monochrome android:drawable="@drawable/ic_launcher_foreground"/>
|
||||
</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"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="108dp"
|
||||
<vector
|
||||
android:height="108dp"
|
||||
android:width="108dp"
|
||||
android:viewportHeight="108"
|
||||
android:viewportWidth="108"
|
||||
android:viewportHeight="108">
|
||||
<path
|
||||
android:fillColor="#3DDC84"
|
||||
android:pathData="M0,0h108v108h-108z" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M9,0L9,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,0L19,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M29,0L29,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M39,0L39,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M49,0L49,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M59,0L59,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M69,0L69,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M79,0L79,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M89,0L89,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M99,0L99,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,9L108,9"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,19L108,19"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,29L108,29"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,39L108,39"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,49L108,49"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,59L108,59"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,69L108,69"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,79L108,79"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,89L108,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,99L108,99"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,29L89,29"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,39L89,39"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,49L89,49"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,59L89,59"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,69L89,69"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,79L89,79"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M29,19L29,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M39,19L39,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M49,19L49,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M59,19L59,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M69,19L69,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M79,19L79,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path android:fillColor="#3DDC84"
|
||||
android:pathData="M0,0h108v108h-108z"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M9,0L9,108"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M19,0L19,108"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M29,0L29,108"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M39,0L39,108"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M49,0L49,108"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M59,0L59,108"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M69,0L69,108"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M79,0L79,108"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M89,0L89,108"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M99,0L99,108"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M0,9L108,9"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M0,19L108,19"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M0,29L108,29"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M0,39L108,39"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M0,49L108,49"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M0,59L108,59"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M0,69L108,69"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M0,79L108,79"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M0,89L108,89"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M0,99L108,99"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M19,29L89,29"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M19,39L89,39"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M19,49L89,49"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M19,59L89,59"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M19,69L89,69"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M19,79L89,79"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M29,19L29,89"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M39,19L39,89"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M49,19L49,89"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M59,19L59,89"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M69,19L69,89"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M79,19L79,89"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
</vector>
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
android:height="108dp"
|
||||
android:viewportWidth="960"
|
||||
android:viewportHeight="960"
|
||||
android:tint="#000000">
|
||||
android:tint="#1A7416">
|
||||
<group android:scaleX="0.7888"
|
||||
android:scaleY="0.7888"
|
||||
android:translateX="101.376"
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@drawable/ic_launcher_background"/>
|
||||
<background android:drawable="@color/ic_launcher_background"/>
|
||||
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
|
||||
<monochrome android:drawable="@drawable/ic_launcher_foreground"/>
|
||||
</adaptive-icon>
|
||||
@@ -1,5 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@drawable/ic_launcher_background"/>
|
||||
<background android:drawable="@color/ic_launcher_background"/>
|
||||
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
|
||||
<monochrome android:drawable="@drawable/ic_launcher_foreground"/>
|
||||
</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 {
|
||||
compose = true
|
||||
buildConfig = true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -52,7 +53,8 @@ dependencies {
|
||||
implementation(libs.play.services.location)
|
||||
implementation(libs.androidx.datastore.core)
|
||||
implementation(libs.androidx.monitor)
|
||||
|
||||
implementation(libs.android.gpx.parser)
|
||||
|
||||
androidTestImplementation(libs.androidx.junit)
|
||||
androidTestImplementation(libs.androidx.runner)
|
||||
androidTestImplementation(libs.androidx.rules)
|
||||
|
||||
|
Before Width: | Height: | Size: 22 KiB |
|
Before Width: | Height: | Size: 37 KiB |
@@ -137,7 +137,7 @@ class CarSensorManager(
|
||||
carCompassListener
|
||||
)
|
||||
carSensors.addCarHardwareLocationListener(
|
||||
CarSensors.UPDATE_RATE_FASTEST,
|
||||
CarSensors.UPDATE_RATE_NORMAL,
|
||||
carContext.mainExecutor,
|
||||
carLocationListener
|
||||
)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package com.kouros.navigation.car
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.location.Location
|
||||
import android.net.Uri
|
||||
import androidx.car.app.CarAppService
|
||||
import androidx.car.app.Session
|
||||
import androidx.car.app.SessionInfo
|
||||
@@ -10,6 +10,14 @@ import androidx.car.app.validation.HostValidator
|
||||
|
||||
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")
|
||||
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.screen.NavigationListener
|
||||
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.SearchScreen
|
||||
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.Place
|
||||
import com.kouros.navigation.data.RouteEngine
|
||||
import com.kouros.navigation.data.ViewStyle
|
||||
import com.kouros.navigation.data.osrm.OsrmRepository
|
||||
import com.kouros.navigation.data.tomtom.TomTomRepository
|
||||
import com.kouros.navigation.data.valhalla.ValhallaRepository
|
||||
@@ -229,9 +231,10 @@ class NavigationSession : Session(), NavigationListener {
|
||||
override fun onStopNavigation() {
|
||||
// Called when the user stops navigation in the car screen
|
||||
// Stop turn-by-turn logic and clean up
|
||||
routeModel.stopNavigation()
|
||||
autoDriveEnabled = false
|
||||
deviceLocationManager.startLocationUpdates()
|
||||
stopNavigation()
|
||||
if (autoDriveEnabled) {
|
||||
deviceLocationManager.startLocationUpdates()
|
||||
}
|
||||
}
|
||||
})
|
||||
surfaceRenderer = SurfaceRenderer(carContext, lifecycle, routeModel, viewModelStoreOwner)
|
||||
@@ -387,6 +390,7 @@ class NavigationSession : Session(), NavigationListener {
|
||||
* Snaps location to route and checks for deviation requiring reroute.
|
||||
*/
|
||||
private fun handleNavigationLocation(location: Location) {
|
||||
|
||||
if (guidanceAudio == 1) {
|
||||
handleGuidanceAudio()
|
||||
}
|
||||
@@ -420,6 +424,9 @@ class NavigationSession : Session(), NavigationListener {
|
||||
simulation.stopSimulation()
|
||||
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.SurfaceCallback
|
||||
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.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.livedata.observeAsState
|
||||
import androidx.compose.runtime.mutableFloatStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.platform.ComposeView
|
||||
import androidx.lifecycle.DefaultLifecycleObserver
|
||||
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.getPaddingValues
|
||||
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.homeVogelhart
|
||||
import com.kouros.navigation.data.RouteEngine
|
||||
import com.kouros.navigation.data.ViewStyle
|
||||
import com.kouros.navigation.model.BaseStyleModel
|
||||
import com.kouros.navigation.utils.bearing
|
||||
import com.kouros.navigation.utils.calculateTilt
|
||||
@@ -45,9 +52,10 @@ import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.maplibre.compose.camera.CameraPosition
|
||||
import org.maplibre.compose.camera.CameraState
|
||||
import org.maplibre.compose.expressions.dsl.zoom
|
||||
import org.maplibre.compose.style.BaseStyle
|
||||
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)
|
||||
var tilt = TILT
|
||||
|
||||
var lastLocationUpdate: LocalDateTime = LocalDateTime.now()
|
||||
|
||||
// Map base style (day/night)
|
||||
val style: MutableLiveData<BaseStyle> by lazy {
|
||||
MutableLiveData()
|
||||
@@ -238,7 +248,6 @@ class SurfaceRenderer(
|
||||
init {
|
||||
lifecycle.addObserver(this)
|
||||
speed.value = 0F
|
||||
|
||||
}
|
||||
|
||||
fun onBaseStyleStateUpdated(style: BaseStyle) {
|
||||
@@ -287,7 +296,7 @@ class SurfaceRenderer(
|
||||
darkMode: Boolean
|
||||
) {
|
||||
val cameraDuration =
|
||||
duration(viewStyle == ViewStyle.PREVIEW, position!!.bearing, lastBearing)
|
||||
duration(viewStyle == ViewStyle.PREVIEW, position!!.bearing, lastBearing, lastLocationUpdate)
|
||||
val currentSpeed: Float? by speed.observeAsState()
|
||||
val maximumSpeed: Int? by maxSpeed.observeAsState()
|
||||
val streetName: String? by street.observeAsState()
|
||||
@@ -311,9 +320,10 @@ class SurfaceRenderer(
|
||||
tilt = tilt,
|
||||
padding = paddingValues
|
||||
),
|
||||
duration = cameraDuration
|
||||
duration = cameraDuration,
|
||||
)
|
||||
}
|
||||
lastLocationUpdate = LocalDateTime.now()
|
||||
}
|
||||
|
||||
override fun onCreate(owner: LifecycleOwner) {
|
||||
@@ -396,6 +406,15 @@ class SurfaceRenderer(
|
||||
viewStyle = ViewStyle.VIEW
|
||||
}
|
||||
|
||||
/**
|
||||
* Activates navigation View
|
||||
*/
|
||||
fun activateNavigationView() {
|
||||
viewStyle = ViewStyle.VIEW
|
||||
tilt = TILT
|
||||
updateLocation(lastLocation)
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates camera position with new bearing, zoom, and target.
|
||||
* 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.
|
||||
*/
|
||||
@@ -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
|
||||
cameraPosition.postValue(
|
||||
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.sp
|
||||
import com.kouros.data.R
|
||||
import com.kouros.navigation.car.ViewStyle
|
||||
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.SpeedColor
|
||||
import com.kouros.navigation.data.ViewStyle
|
||||
import com.kouros.navigation.utils.isMetricSystem
|
||||
import org.maplibre.compose.camera.CameraPosition
|
||||
import org.maplibre.compose.camera.CameraState
|
||||
@@ -74,9 +75,9 @@ fun cameraState(
|
||||
latitude = position!!.target.latitude,
|
||||
longitude = position.target.longitude
|
||||
),
|
||||
zoom = 15.0,
|
||||
zoom = position.zoom,
|
||||
tilt = tilt,
|
||||
padding = padding
|
||||
padding = padding,
|
||||
)
|
||||
)
|
||||
}
|
||||
@@ -317,7 +318,10 @@ fun NavigationImage(
|
||||
) {
|
||||
|
||||
val imageSize = (height / 8)
|
||||
val navigationColor = remember { NavigationColor }
|
||||
val navigationColor = if (darkMode)
|
||||
remember { NavigationColorDark }
|
||||
else
|
||||
remember { NavigationColorLight }
|
||||
|
||||
val textMeasurerStreet = rememberTextMeasurer()
|
||||
val street = streetName.toString()
|
||||
@@ -545,7 +549,7 @@ fun DebugInfo(
|
||||
fun getPaddingValues(height: Int, viewStyle: ViewStyle): PaddingValues {
|
||||
return when (viewStyle) {
|
||||
ViewStyle.VIEW, ViewStyle.PAN_VIEW -> PaddingValues(
|
||||
start = 50.dp,
|
||||
start = 100.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.SpannableStringBuilder
|
||||
import android.text.Spanned
|
||||
import android.util.Log
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.car.app.AppManager
|
||||
import androidx.car.app.CarContext
|
||||
@@ -184,7 +185,6 @@ class RouteCarModel : RouteModel() {
|
||||
return CarText.create(carContext.getString(stringRes))
|
||||
}
|
||||
|
||||
|
||||
fun createCarIcon(iconCompat: IconCompat): CarIcon {
|
||||
return CarIcon.Builder(iconCompat).build()
|
||||
}
|
||||
@@ -235,4 +235,17 @@ class RouteCarModel : RouteModel() {
|
||||
.setFlags(flags)
|
||||
.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,47 +4,126 @@ import android.location.Location
|
||||
import android.location.LocationManager
|
||||
import android.os.SystemClock
|
||||
import androidx.lifecycle.LifecycleCoroutineScope
|
||||
import com.kouros.navigation.data.Constants.homeVogelhart
|
||||
import com.kouros.navigation.utils.location
|
||||
import com.kouros.android.cars.carappservice.BuildConfig
|
||||
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.delay
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.joda.time.DateTime
|
||||
|
||||
class Simulation {
|
||||
|
||||
private var simulationJob: Job? = null
|
||||
|
||||
|
||||
fun startSimulation(
|
||||
routeModel: RouteCarModel,
|
||||
lifecycleScope: LifecycleCoroutineScope,
|
||||
updateLocation: (Location) -> Unit
|
||||
) {
|
||||
if (routeModel.navState.route.isRouteValid()) {
|
||||
val points = routeModel.curRoute.waypoints
|
||||
if (points.isEmpty()) return
|
||||
simulationJob?.cancel()
|
||||
if (BuildConfig.DEBUG) {
|
||||
gpxSimulation(routeModel, lifecycleScope, updateLocation)
|
||||
} 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 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()
|
||||
val parser = GPXParser()
|
||||
val parsedGpx: Gpx? =
|
||||
parser.parse(route.byteInputStream())
|
||||
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 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
|
||||
|
||||
import androidx.activity.OnBackPressedCallback
|
||||
import androidx.car.app.CarContext
|
||||
import androidx.car.app.Screen
|
||||
import androidx.car.app.model.Action
|
||||
@@ -13,18 +12,12 @@ import androidx.car.app.model.Template
|
||||
import androidx.core.graphics.drawable.IconCompat
|
||||
import com.kouros.data.R
|
||||
import com.kouros.navigation.car.SurfaceRenderer
|
||||
import com.kouros.navigation.car.ViewStyle
|
||||
import com.kouros.navigation.data.Category
|
||||
import com.kouros.navigation.data.Constants.CHARGING_STATION
|
||||
import com.kouros.navigation.data.Constants.FUEL_STATION
|
||||
import com.kouros.navigation.data.Constants.PHARMACY
|
||||
import com.kouros.navigation.data.ViewStyle
|
||||
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(
|
||||
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()
|
||||
} else {
|
||||
return CarIcon.Builder(
|
||||
NavigationUtils(context).createNumberIcon(
|
||||
createNumberIcon(
|
||||
category,
|
||||
index.toString()
|
||||
)
|
||||
|
||||
@@ -21,7 +21,6 @@ import androidx.lifecycle.DefaultLifecycleObserver
|
||||
import androidx.lifecycle.LifecycleOwner
|
||||
import com.kouros.data.R
|
||||
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.CategoryObserverCallback
|
||||
import com.kouros.navigation.data.Constants
|
||||
@@ -130,7 +129,7 @@ class CategoryScreen(
|
||||
val row = Row.Builder()
|
||||
.setOnClickListener {
|
||||
val location = location(it.lon, it.lat)
|
||||
surfaceRenderer.setCategoryLocation(location, category)
|
||||
surfaceRenderer.setCategoryLocation(location)
|
||||
}
|
||||
.setTitle(name)
|
||||
.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.FLAG_IS_PERSISTENT
|
||||
import androidx.car.app.model.ActionStrip
|
||||
import androidx.car.app.model.CarColor
|
||||
import androidx.car.app.model.CarIcon
|
||||
import androidx.car.app.model.Distance
|
||||
import androidx.car.app.model.Header
|
||||
@@ -31,28 +30,25 @@ import androidx.lifecycle.asLiveData
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import com.kouros.data.R
|
||||
import com.kouros.navigation.car.SurfaceRenderer
|
||||
import com.kouros.navigation.car.ViewStyle
|
||||
import com.kouros.navigation.car.navigation.RouteCarModel
|
||||
import com.kouros.navigation.car.screen.observers.NavigationObserverCallback
|
||||
import com.kouros.navigation.car.screen.observers.NavigationObserverManager
|
||||
import com.kouros.navigation.car.screen.settings.SettingsScreen
|
||||
import com.kouros.navigation.data.Constants
|
||||
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.Place
|
||||
import com.kouros.navigation.data.ViewStyle
|
||||
import com.kouros.navigation.data.overpass.Elements
|
||||
import com.kouros.navigation.model.NavigationViewModel
|
||||
import com.kouros.navigation.model.RouteModel
|
||||
import com.kouros.navigation.utils.GeoUtils
|
||||
import com.kouros.navigation.utils.calculateZoom
|
||||
import com.kouros.navigation.utils.formattedDistance
|
||||
import com.kouros.navigation.utils.getSettingsRepository
|
||||
import com.kouros.navigation.utils.getSettingsViewModel
|
||||
import com.kouros.navigation.utils.location
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.launch
|
||||
import org.maplibre.spatialk.geojson.Position
|
||||
import java.time.Duration
|
||||
import java.time.LocalDateTime
|
||||
import java.time.ZoneOffset
|
||||
@@ -70,8 +66,6 @@ open class NavigationScreen(
|
||||
private val navigationViewModel: NavigationViewModel
|
||||
) : Screen(carContext), NavigationObserverCallback {
|
||||
|
||||
val backGroundColor = CarColor.GREEN
|
||||
|
||||
var currentNavigationLocation = Location(LocationManager.GPS_PROVIDER)
|
||||
|
||||
var recentPlaces = mutableListOf<Place>()
|
||||
@@ -191,7 +185,7 @@ open class NavigationScreen(
|
||||
)
|
||||
})
|
||||
)
|
||||
.setBackgroundColor(backGroundColor)
|
||||
.setBackgroundColor(routeModel.backGroundColor())
|
||||
.build()
|
||||
}
|
||||
|
||||
@@ -200,7 +194,7 @@ open class NavigationScreen(
|
||||
*/
|
||||
private fun navigationViewTemplate(actionStripBuilder: ActionStrip.Builder): Template {
|
||||
return NavigationTemplate.Builder()
|
||||
.setBackgroundColor(backGroundColor)
|
||||
.setBackgroundColor(routeModel.backGroundColor())
|
||||
.setActionStrip(actionStripBuilder.build())
|
||||
.setMapActionStrip(
|
||||
mapActionStrip(
|
||||
@@ -261,7 +255,7 @@ open class NavigationScreen(
|
||||
)
|
||||
.build()
|
||||
)
|
||||
.setBackgroundColor(backGroundColor)
|
||||
.setBackgroundColor(routeModel.backGroundColor())
|
||||
.setActionStrip(actionStripBuilder.build())
|
||||
.setMapActionStrip(
|
||||
mapActionStrip(
|
||||
@@ -298,7 +292,7 @@ open class NavigationScreen(
|
||||
)
|
||||
}
|
||||
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()
|
||||
.setTitle(it.name!!)
|
||||
.addAction(
|
||||
@@ -355,7 +349,7 @@ open class NavigationScreen(
|
||||
return NavigationTemplate.Builder()
|
||||
.setNavigationInfo(RoutingInfo.Builder().setLoading(true).build())
|
||||
.setActionStrip(actionStripBuilder.build())
|
||||
.setBackgroundColor(backGroundColor)
|
||||
.setBackgroundColor(routeModel.backGroundColor())
|
||||
.build()
|
||||
}
|
||||
|
||||
@@ -421,12 +415,6 @@ open class NavigationScreen(
|
||||
* Creates an action to start the settings screen.
|
||||
*/
|
||||
private fun settingsAction(): Action {
|
||||
// return Action.Builder()
|
||||
// .setIcon(createCarIcon(carContext, R.drawable.settings_48px))
|
||||
// .setOnClickListener {
|
||||
// screenManager.push(SettingsScreen(carContext, navigationViewModel))
|
||||
// }
|
||||
// .build()
|
||||
return createAction(
|
||||
carContext, R.drawable.settings_48px,
|
||||
0,
|
||||
@@ -514,13 +502,7 @@ open class NavigationScreen(
|
||||
navigationViewModel.route.value = preview
|
||||
}
|
||||
routeModel.navState = routeModel.navState.copy(destination = place)
|
||||
surfaceRenderer.viewStyle = ViewStyle.VIEW
|
||||
surfaceRenderer.updateCameraPosition(
|
||||
0.0,
|
||||
16.0,
|
||||
Position(surfaceRenderer.lastLocation.longitude, surfaceRenderer.lastLocation.latitude),
|
||||
TILT
|
||||
)
|
||||
surfaceRenderer.activateNavigationView()
|
||||
invalidate()
|
||||
}
|
||||
|
||||
@@ -530,7 +512,6 @@ open class NavigationScreen(
|
||||
fun stopNavigation() {
|
||||
navigationType = NavigationType.VIEW
|
||||
listener.stopNavigation()
|
||||
surfaceRenderer.routeData.value = ""
|
||||
lastCameraSearch = 0
|
||||
invalidate()
|
||||
}
|
||||
@@ -573,9 +554,9 @@ open class NavigationScreen(
|
||||
* Updates navigation state with the current location, checks for arrival, and traffic updates.
|
||||
*/
|
||||
fun updateTrip(location: Location) {
|
||||
val current = LocalDateTime.now(ZoneOffset.UTC)
|
||||
checkRoute(current, location)
|
||||
checkTraffic(current, location)
|
||||
val currentDate = LocalDateTime.now(ZoneOffset.UTC)
|
||||
checkRoute(currentDate, location)
|
||||
checkTraffic(currentDate, 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.
|
||||
*/
|
||||
private fun checkRoute(current: LocalDateTime, location: Location) {
|
||||
val duration = Duration.between(current, lastRouteDate)
|
||||
val routeUpdate = routeModel.curRoute.summary.duration / 6
|
||||
private fun checkRoute(currentDate: LocalDateTime, location: Location) {
|
||||
val duration = Duration.between(currentDate, lastRouteDate)
|
||||
val routeUpdate = routeModel.curRoute.summary.duration / 4
|
||||
if (duration.abs().seconds > routeUpdate) {
|
||||
lastRouteDate = current
|
||||
lastRouteDate = currentDate
|
||||
val destination = location(
|
||||
routeModel.navState.destination.longitude,
|
||||
routeModel.navState.destination.latitude
|
||||
|
||||
@@ -36,10 +36,9 @@ import androidx.lifecycle.asLiveData
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import com.kouros.data.R
|
||||
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.data.Place
|
||||
import com.kouros.navigation.data.ViewStyle
|
||||
import com.kouros.navigation.data.route.Routes
|
||||
import com.kouros.navigation.model.NavigationViewModel
|
||||
import com.kouros.navigation.utils.getSettingsRepository
|
||||
@@ -324,7 +323,7 @@ class RoutePreviewScreen(
|
||||
.addAction(navigateAction)
|
||||
if (route.summary.trafficDelay > 60) {
|
||||
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()
|
||||
}
|
||||
|
||||
@@ -1,13 +1,63 @@
|
||||
package com.kouros.navigation.car.screen
|
||||
|
||||
import android.graphics.Canvas
|
||||
import android.graphics.Color
|
||||
import android.graphics.Paint
|
||||
import androidx.annotation.DrawableRes
|
||||
import androidx.car.app.CarContext
|
||||
import androidx.car.app.model.Action
|
||||
import androidx.car.app.model.Action.FLAG_DEFAULT
|
||||
import androidx.car.app.model.ActionStrip
|
||||
import androidx.car.app.model.CarIcon
|
||||
import androidx.car.app.model.Row
|
||||
import androidx.core.graphics.createBitmap
|
||||
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 {
|
||||
val actionStripBuilder: ActionStrip.Builder = ActionStrip.Builder()
|
||||
@@ -17,7 +67,7 @@ fun createActionStrip(executeAction: () -> Action): ActionStrip {
|
||||
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()
|
||||
actionStripBuilder.addAction(
|
||||
action1()
|
||||
@@ -31,7 +81,12 @@ fun createActionStrip(executeAction: () -> Action): ActionStrip {
|
||||
/**
|
||||
* 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()
|
||||
.addAction(zoomPlus())
|
||||
.addAction(zoomMinus())
|
||||
@@ -47,7 +102,12 @@ fun mapActionStrip(viewStyle: ViewStyle, zoomPlus: () -> Action, zoomMinus: () -
|
||||
/**
|
||||
* 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()
|
||||
.setIcon(createCarIcon(carContext, iconRes))
|
||||
.setFlags(flag)
|
||||
@@ -60,3 +120,6 @@ fun createAction(carContext: CarContext, @DrawableRes iconRes: Int, flag: Int =
|
||||
fun createCarIcon(carContext: CarContext, @DrawableRes iconRes: Int): CarIcon {
|
||||
return CarIcon.Builder(IconCompat.createWithResource(carContext, iconRes)).build()
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -14,12 +14,12 @@ import androidx.core.graphics.drawable.IconCompat
|
||||
import androidx.lifecycle.Observer
|
||||
import com.kouros.data.R
|
||||
import com.kouros.navigation.car.SurfaceRenderer
|
||||
import com.kouros.navigation.car.ViewStyle
|
||||
import com.kouros.navigation.data.Category
|
||||
import com.kouros.navigation.data.Constants.CATEGORIES
|
||||
import com.kouros.navigation.data.Constants.FAVORITES
|
||||
import com.kouros.navigation.data.Constants.RECENT
|
||||
import com.kouros.navigation.data.Place
|
||||
import com.kouros.navigation.data.ViewStyle
|
||||
import com.kouros.navigation.data.nominatim.SearchResult
|
||||
import com.kouros.navigation.model.NavigationViewModel
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ import androidx.car.app.model.Template
|
||||
import androidx.core.graphics.drawable.IconCompat
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
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 kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.launch
|
||||
@@ -40,19 +40,22 @@ class AudioSettings(
|
||||
val radioList =
|
||||
ItemList.Builder()
|
||||
.addItem(
|
||||
NavigationUtils(carContext).buildRowForTemplate(
|
||||
buildRowForTemplate(
|
||||
carContext,
|
||||
R.string.muted,
|
||||
R.drawable.volume_off_24px
|
||||
)
|
||||
)
|
||||
.addItem(
|
||||
NavigationUtils(carContext).buildRowForTemplate(
|
||||
buildRowForTemplate(
|
||||
carContext,
|
||||
R.string.unmuted,
|
||||
R.drawable.volume_up_24px,
|
||||
)
|
||||
)
|
||||
.addItem(
|
||||
NavigationUtils(carContext).buildRowForTemplate(
|
||||
buildRowForTemplate(
|
||||
carContext,
|
||||
R.string.alerts_only,
|
||||
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.lifecycle.lifecycleScope
|
||||
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 kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.launch
|
||||
@@ -34,19 +34,19 @@ class DarkModeSettings(private val carContext: CarContext) : Screen(carContext)
|
||||
val radioList =
|
||||
ItemList.Builder()
|
||||
.addItem(
|
||||
NavigationUtils(carContext).buildRowForTemplate(
|
||||
buildRowForTemplate(carContext,
|
||||
R.string.off_action_title,
|
||||
R.drawable.light_mode_24px
|
||||
)
|
||||
)
|
||||
.addItem(
|
||||
NavigationUtils(carContext).buildRowForTemplate(
|
||||
buildRowForTemplate(carContext,
|
||||
R.string.on_action_title,
|
||||
R.drawable.dark_mode_24px
|
||||
)
|
||||
)
|
||||
.addItem(
|
||||
NavigationUtils(carContext).buildRowForTemplate(
|
||||
buildRowForTemplate(carContext,
|
||||
R.string.use_car_settings,
|
||||
R.drawable.directions_car_24px
|
||||
)
|
||||
|
||||
@@ -13,7 +13,7 @@ import androidx.car.app.model.Toggle
|
||||
import androidx.core.graphics.drawable.IconCompat
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
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.utils.getSettingsViewModel
|
||||
import kotlinx.coroutines.flow.first
|
||||
@@ -62,7 +62,7 @@ class NavigationSettings(
|
||||
buildRowForTemplate(
|
||||
R.string.avoid_highways_row_title,
|
||||
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)
|
||||
tollWayToggleState = !tollWayToggleState
|
||||
}.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
|
||||
val ferryToggle: Toggle =
|
||||
@@ -80,7 +80,7 @@ class NavigationSettings(
|
||||
settingsViewModel.onAvoidFerry(checked)
|
||||
ferryToggleState = !ferryToggleState
|
||||
}.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
|
||||
val carLocationToggle: Toggle =
|
||||
@@ -93,7 +93,7 @@ class NavigationSettings(
|
||||
buildRowForTemplate(
|
||||
R.string.use_car_location,
|
||||
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.addItem(
|
||||
buildRowForTemplate(
|
||||
@@ -94,10 +94,17 @@ class SettingsScreen(
|
||||
)
|
||||
)
|
||||
|
||||
listBuilder.addItem(
|
||||
buildRowForTemplate(
|
||||
CarSettings(carContext, navigationViewModel),
|
||||
R.string.car_settings
|
||||
)
|
||||
)
|
||||
|
||||
templateBuilder.addSectionedList(
|
||||
SectionedItemList.create(
|
||||
listBuilder.build(),
|
||||
carContext.getString(R.string.navigation_settings)
|
||||
carContext.getString(R.string.drive_settings)
|
||||
)
|
||||
)
|
||||
|
||||
@@ -109,6 +116,8 @@ class SettingsScreen(
|
||||
.setStartHeaderAction(Action.BACK)
|
||||
.build())
|
||||
.build()
|
||||
|
||||
|
||||
}
|
||||
|
||||
private fun getTitle(): String {
|
||||
|
||||
@@ -2,7 +2,9 @@ package com.kouros.navigation.data
|
||||
|
||||
import androidx.compose.ui.graphics.Color
|
||||
|
||||
val NavigationColor = Color(0xFF16BBB6)
|
||||
val NavigationColorLight = Color(0xFF066462)
|
||||
|
||||
val NavigationColorDark = Color(0xFF10DED9)
|
||||
|
||||
val RouteColor = Color(0xFF7B06E1)
|
||||
|
||||
|
||||
@@ -77,9 +77,10 @@ data class Locations (
|
||||
)
|
||||
|
||||
data class SearchFilter(
|
||||
var avoidMotorway: Boolean = false,
|
||||
var avoidTollway : Boolean = false,
|
||||
var avoidFerry : Boolean = false,
|
||||
val avoidMotorway: Boolean = false,
|
||||
val avoidTollway : Boolean = false,
|
||||
val avoidFerry : Boolean = false,
|
||||
val engineType : Int = 0,
|
||||
)
|
||||
|
||||
|
||||
@@ -139,11 +140,27 @@ object Constants {
|
||||
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 {
|
||||
VALHALLA, OSRM, TOMTOM
|
||||
}
|
||||
|
||||
enum class EngineType {
|
||||
COMBUSTION, ELECTRIC
|
||||
}
|
||||
|
||||
|
||||
enum class NavigationThemeColor(val color: Long) {
|
||||
RED(0xFFD32F2F),
|
||||
ORANGE(0xFFF57C00),
|
||||
|
||||
@@ -2,6 +2,7 @@ package com.kouros.navigation.data
|
||||
|
||||
import android.content.Context
|
||||
import android.location.Location
|
||||
import android.util.Log
|
||||
import com.kouros.navigation.utils.GeoUtils.calculateSquareRadius
|
||||
import java.net.Authenticator
|
||||
import java.net.HttpURLConnection
|
||||
@@ -66,7 +67,7 @@ abstract class NavigationRepository {
|
||||
}
|
||||
})
|
||||
}
|
||||
println(url)
|
||||
Log.d("fetchUrl", url)
|
||||
val httpURLConnection = URL(url).openConnection() as HttpURLConnection
|
||||
httpURLConnection.setRequestProperty(
|
||||
"Accept",
|
||||
@@ -81,7 +82,7 @@ abstract class NavigationRepository {
|
||||
return response
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
println("Exception ${e.message}")
|
||||
Log.e("fetchUrl", e.toString())
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.kouros.navigation.data
|
||||
|
||||
import android.util.Log
|
||||
import com.google.gson.GsonBuilder
|
||||
import com.kouros.navigation.data.osrm.OsrmResponse
|
||||
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.stringPreferencesKey
|
||||
import androidx.datastore.preferences.preferencesDataStore
|
||||
import com.kouros.navigation.data.EngineType
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.flow.map
|
||||
@@ -55,6 +56,8 @@ class DataStoreManager(private val context: Context) {
|
||||
|
||||
val TRIP_SUGGESTION = booleanPreferencesKey("TripSuggestion")
|
||||
|
||||
val ENGINE_TYPE = intPreferencesKey("EngineType")
|
||||
|
||||
}
|
||||
|
||||
// Read values
|
||||
@@ -136,6 +139,12 @@ class DataStoreManager(private val context: Context) {
|
||||
preferences[PreferencesKeys.TRIP_SUGGESTION] == true
|
||||
}
|
||||
|
||||
val engineTypeFlow: Flow<Int> =
|
||||
context.dataStore.data.map { preferences ->
|
||||
preferences[PreferencesKeys.ENGINE_TYPE]
|
||||
?: EngineType.COMBUSTION.ordinal
|
||||
}
|
||||
|
||||
// Save values
|
||||
suspend fun setShow3D(enabled: Boolean) {
|
||||
context.dataStore.edit { preferences ->
|
||||
@@ -220,4 +229,10 @@ class DataStoreManager(private val context: Context) {
|
||||
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.location.Location
|
||||
import com.kouros.data.R
|
||||
import com.kouros.navigation.data.EngineType
|
||||
import com.kouros.navigation.data.NavigationRepository
|
||||
import com.kouros.navigation.data.SearchFilter
|
||||
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 =
|
||||
"{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() {
|
||||
@@ -38,6 +39,7 @@ class TomTomRepository : NavigationRepository() {
|
||||
false
|
||||
)
|
||||
}
|
||||
var engineType = "combustion"
|
||||
var filter = ""
|
||||
if (searchFilter.avoidMotorway) {
|
||||
filter = "$filter&avoid=motorways"
|
||||
@@ -48,6 +50,9 @@ class TomTomRepository : NavigationRepository() {
|
||||
if (searchFilter.avoidFerry) {
|
||||
filter = "$filter&avoid=ferries"
|
||||
}
|
||||
if (searchFilter.engineType == EngineType.ELECTRIC.ordinal) {
|
||||
engineType = "electric"
|
||||
}
|
||||
val repository = getSettingsRepository(context)
|
||||
val tomtomApiKey = runBlocking { repository.tomTomApiKeyFlow.first() }
|
||||
val currentLocale = Locale.getDefault()
|
||||
@@ -60,7 +65,7 @@ class TomTomRepository : NavigationRepository() {
|
||||
"&instructionsType=text&language=$language§ionType=lanes" +
|
||||
"&routeRepresentation=encodedPolyline" +
|
||||
"&maxAlternatives=2" +
|
||||
"&vehicleEngineType=combustion$filter&key=$tomtomApiKey"
|
||||
"&vehicleEngineType=$engineType$filter&key=$tomtomApiKey"
|
||||
return fetchUrl(
|
||||
url,
|
||||
false
|
||||
|
||||
@@ -163,7 +163,7 @@ class NavigationViewModel(private val repository: NavigationRepository) : ViewMo
|
||||
}
|
||||
}
|
||||
}
|
||||
recentPlaces.postValue(pl)
|
||||
recentPlaces.postValue(pl.sortedBy { it.distance })
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
@@ -538,7 +538,8 @@ class NavigationViewModel(private val repository: NavigationRepository) : ViewMo
|
||||
val avoidMotorway = runBlocking { repository.avoidMotorwayFlow.first() }
|
||||
val avoidTollway = runBlocking { repository.avoidTollwayFlow.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
|
||||
|
||||
import android.location.Location
|
||||
import android.util.Log
|
||||
import androidx.car.app.navigation.model.Step
|
||||
import com.kouros.navigation.data.Constants.MAXIMUM_LOCATION_DISTANCE
|
||||
import com.kouros.navigation.data.Constants.NEAREST_LOCATION_DISTANCE
|
||||
|
||||
@@ -96,6 +96,12 @@ class SettingsViewModel(private val repository: SettingsRepository) : ViewModel(
|
||||
false
|
||||
)
|
||||
|
||||
val engineType = repository.engineTypeFlow.stateIn(
|
||||
viewModelScope,
|
||||
SharingStarted.WhileSubscribed(5_000),
|
||||
0
|
||||
)
|
||||
|
||||
fun onShow3DChanged(enabled: Boolean) {
|
||||
viewModelScope.launch { repository.setShow3D(enabled) }
|
||||
}
|
||||
@@ -148,4 +154,9 @@ class SettingsViewModel(private val repository: SettingsRepository) : ViewModel(
|
||||
fun onTripSuggestion(enabled: Boolean) {
|
||||
viewModelScope.launch { repository.setTripSuggestion(enabled) }
|
||||
}
|
||||
|
||||
fun onEngineTypeChanged(mode: Int) {
|
||||
viewModelScope.launch { repository.setEngineType(mode) }
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -47,6 +47,9 @@ class SettingsRepository(
|
||||
val tripSuggestionFlow: Flow<Boolean> =
|
||||
dataStoreManager.tripSuggestionFlow
|
||||
|
||||
val engineTypeFlow: Flow<Int> =
|
||||
dataStoreManager.engineTypeFlow
|
||||
|
||||
suspend fun setShow3D(enabled: Boolean) {
|
||||
dataStoreManager.setShow3D(enabled)
|
||||
}
|
||||
@@ -102,4 +105,8 @@ class SettingsRepository(
|
||||
suspend fun setTripSuggestion(enabled: Boolean) {
|
||||
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.valhalla.ValhallaRepository
|
||||
import com.kouros.navigation.model.NavigationViewModel
|
||||
import com.kouros.navigation.utils.GeoUtils.calculateSquareRadius
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import java.lang.Math.toDegrees
|
||||
@@ -29,6 +28,8 @@ import kotlin.math.pow
|
||||
import kotlin.math.roundToInt
|
||||
import kotlin.time.Duration
|
||||
import kotlin.time.Duration.Companion.seconds
|
||||
import kotlin.time.DurationUnit
|
||||
import kotlin.time.toDuration
|
||||
|
||||
|
||||
object NavigationUtils {
|
||||
@@ -50,7 +51,7 @@ fun calculateZoom(speed: Double?): Double {
|
||||
}
|
||||
val speedKmh = (speed * 3.6).toInt()
|
||||
val zoom = when (speedKmh) {
|
||||
in 0..10 -> 18.0
|
||||
in 0..10 -> 17.0
|
||||
in 11..30 -> 17.5
|
||||
in 31..65 -> 17.0
|
||||
in 66..70 -> 16.5
|
||||
@@ -128,14 +129,20 @@ fun Double.round(numFractionDigits: Int): Double {
|
||||
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) {
|
||||
return 3.seconds
|
||||
}
|
||||
val cameraDuration = if ((lastBearing - bearing).absoluteValue > 20.0) {
|
||||
2.seconds
|
||||
} else {
|
||||
1.seconds
|
||||
val updateDuration = java.time.Duration.between(LocalDateTime.now(), lastLocationUpdate)
|
||||
((updateDuration!!.toMillis().absoluteValue * 1.2).toDuration(DurationUnit.MILLISECONDS))
|
||||
}
|
||||
return cameraDuration
|
||||
}
|
||||
|
||||
@@ -66,4 +66,9 @@
|
||||
<string name="general">Allgemein</string>
|
||||
<string name="traffic">Verkehr anzeigen</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>
|
||||
|
||||
@@ -50,4 +50,9 @@
|
||||
<string name="general">Γενικά</string>
|
||||
<string name="traffic">Εμφάνιση κίνησης</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>
|
||||
|
||||
@@ -50,4 +50,9 @@
|
||||
<string name="general">Ogólne</string>
|
||||
<string name="traffic">Pokaż natężenie ruchu</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>
|
||||
|
||||
@@ -53,4 +53,9 @@
|
||||
<string name="general">General</string>
|
||||
<string name="traffic">Show traffic</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>
|
||||