diff --git a/README.md b/README.md index 02cace7..a922cd5 100644 --- a/README.md +++ b/README.md @@ -5,3 +5,6 @@ ## Simulation adb shell dumpsys activity service com.kouros.navigation.car.NavigationCarAppService AUTO_DRIVE + +## Signing +./gradlew bundleFull diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 09850d8..7bfce88 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -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) } - diff --git a/app/src/main/ic_launcher-playstore.png b/app/src/main/ic_launcher-playstore.png index 55787b0..c40baa1 100644 Binary files a/app/src/main/ic_launcher-playstore.png and b/app/src/main/ic_launcher-playstore.png differ diff --git a/app/src/main/java/com/kouros/navigation/model/Simulation.kt b/app/src/main/java/com/kouros/navigation/model/Simulation.kt index f4a6b67..4d2e948 100644 --- a/app/src/main/java/com/kouros/navigation/model/Simulation.kt +++ b/app/src/main/java/com/kouros/navigation/model/Simulation.kt @@ -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? = 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 } diff --git a/app/src/main/java/com/kouros/navigation/ui/MainActivity.kt b/app/src/main/java/com/kouros/navigation/ui/MainActivity.kt index 77bf96a..8e91eae 100644 --- a/app/src/main/java/com/kouros/navigation/ui/MainActivity.kt +++ b/app/src/main/java/com/kouros/navigation/ui/MainActivity.kt @@ -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 -> {} } } } diff --git a/app/src/main/java/com/kouros/navigation/ui/MapView.kt b/app/src/main/java/com/kouros/navigation/ui/MapView.kt index 206960f..2f08337 100644 --- a/app/src/main/java/com/kouros/navigation/ui/MapView.kt +++ b/app/src/main/java/com/kouros/navigation/ui/MapView.kt @@ -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 diff --git a/app/src/main/java/com/kouros/navigation/ui/navigation/AppNavGraph.kt b/app/src/main/java/com/kouros/navigation/ui/navigation/AppNavGraph.kt index 1b137c4..fe5c397 100644 --- a/app/src/main/java/com/kouros/navigation/ui/navigation/AppNavGraph.kt +++ b/app/src/main/java/com/kouros/navigation/ui/navigation/AppNavGraph.kt @@ -48,5 +48,11 @@ fun AppNavGraph(mainActivity: MainActivity) { navController ) { navController.popBackStack() } } + composable("car_settings") { + SettingsRoute( + "car_settings", + navController + ) { navController.popBackStack() } + } } } diff --git a/app/src/main/java/com/kouros/navigation/ui/settings/CarScreen.kt b/app/src/main/java/com/kouros/navigation/ui/settings/CarScreen.kt new file mode 100644 index 0000000..cc9fd16 --- /dev/null +++ b/app/src/main/java/com/kouros/navigation/ui/settings/CarScreen.kt @@ -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 + ) + } + } + } + } +} + diff --git a/app/src/main/java/com/kouros/navigation/ui/settings/SettingsRoute.kt b/app/src/main/java/com/kouros/navigation/ui/settings/SettingsRoute.kt index dce7e9d..d88b22e 100644 --- a/app/src/main/java/com/kouros/navigation/ui/settings/SettingsRoute.kt +++ b/app/src/main/java/com/kouros/navigation/ui/settings/SettingsRoute.kt @@ -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) + } } diff --git a/app/src/main/java/com/kouros/navigation/ui/settings/SettingsScreen.kt b/app/src/main/java/com/kouros/navigation/ui/settings/SettingsScreen.kt index 2285bb7..db9d386 100644 --- a/app/src/main/java/com/kouros/navigation/ui/settings/SettingsScreen.kt +++ b/app/src/main/java/com/kouros/navigation/ui/settings/SettingsScreen.kt @@ -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( diff --git a/app/src/main/res/drawable/ic_launcher_foreground.xml b/app/src/main/res/drawable/ic_launcher_foreground.xml index 7010fed..1b2a683 100644 --- a/app/src/main/res/drawable/ic_launcher_foreground.xml +++ b/app/src/main/res/drawable/ic_launcher_foreground.xml @@ -3,7 +3,7 @@ android:height="108dp" android:viewportWidth="960" android:viewportHeight="960" - android:tint="#000000"> + android:tint="#1A7416"> - + + \ No newline at end of file diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml index bbd3e02..ef49c99 100644 --- a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml +++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml @@ -1,5 +1,6 @@ - + + \ No newline at end of file diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher.webp b/app/src/main/res/mipmap-hdpi/ic_launcher.webp index da24c35..e0d523c 100644 Binary files a/app/src/main/res/mipmap-hdpi/ic_launcher.webp and b/app/src/main/res/mipmap-hdpi/ic_launcher.webp differ diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp index 2850a90..b0800bc 100644 Binary files a/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp and b/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp differ diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher.webp b/app/src/main/res/mipmap-mdpi/ic_launcher.webp index 8042972..892b0b7 100644 Binary files a/app/src/main/res/mipmap-mdpi/ic_launcher.webp and b/app/src/main/res/mipmap-mdpi/ic_launcher.webp differ diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp index 940caf5..6662a13 100644 Binary files a/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp and b/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp differ diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher.webp b/app/src/main/res/mipmap-xhdpi/ic_launcher.webp index c52ae69..5b55a3a 100644 Binary files a/app/src/main/res/mipmap-xhdpi/ic_launcher.webp and b/app/src/main/res/mipmap-xhdpi/ic_launcher.webp differ diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp index 56b8e2b..c918987 100644 Binary files a/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp and b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp differ diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp b/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp index 284db5a..97c5070 100644 Binary files a/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp and b/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp differ diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp index 2f729db..60383ba 100644 Binary files a/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp and b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp differ diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp deleted file mode 100644 index 9b5e24c..0000000 Binary files a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp and /dev/null differ diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp deleted file mode 100644 index 56a171b..0000000 Binary files a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp and /dev/null differ diff --git a/app/src/main/res/values/ic_launcher_background.xml b/app/src/main/res/values/ic_launcher_background.xml new file mode 100644 index 0000000..1edb315 --- /dev/null +++ b/app/src/main/res/values/ic_launcher_background.xml @@ -0,0 +1,4 @@ + + + #98DABB + \ No newline at end of file diff --git a/automotive/src/main/res/drawable/ic_launcher_background.xml b/automotive/src/main/res/drawable/ic_launcher_background.xml index 07d5da9..ca3826a 100644 --- a/automotive/src/main/res/drawable/ic_launcher_background.xml +++ b/automotive/src/main/res/drawable/ic_launcher_background.xml @@ -1,170 +1,74 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + xmlns:android="http://schemas.android.com/apk/res/android"> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/automotive/src/main/res/drawable/ic_launcher_foreground.xml b/automotive/src/main/res/drawable/ic_launcher_foreground.xml index 7010fed..1b2a683 100644 --- a/automotive/src/main/res/drawable/ic_launcher_foreground.xml +++ b/automotive/src/main/res/drawable/ic_launcher_foreground.xml @@ -3,7 +3,7 @@ android:height="108dp" android:viewportWidth="960" android:viewportHeight="960" - android:tint="#000000"> + android:tint="#1A7416"> - + + \ No newline at end of file diff --git a/automotive/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/automotive/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml index bbd3e02..ef49c99 100644 --- a/automotive/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml +++ b/automotive/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml @@ -1,5 +1,6 @@ - + + \ No newline at end of file diff --git a/automotive/src/main/res/mipmap-hdpi/ic_launcher.webp b/automotive/src/main/res/mipmap-hdpi/ic_launcher.webp index da24c35..e0d523c 100644 Binary files a/automotive/src/main/res/mipmap-hdpi/ic_launcher.webp and b/automotive/src/main/res/mipmap-hdpi/ic_launcher.webp differ diff --git a/automotive/src/main/res/mipmap-hdpi/ic_launcher_round.webp b/automotive/src/main/res/mipmap-hdpi/ic_launcher_round.webp index 2850a90..b0800bc 100644 Binary files a/automotive/src/main/res/mipmap-hdpi/ic_launcher_round.webp and b/automotive/src/main/res/mipmap-hdpi/ic_launcher_round.webp differ diff --git a/automotive/src/main/res/mipmap-mdpi/ic_launcher.webp b/automotive/src/main/res/mipmap-mdpi/ic_launcher.webp index 8042972..892b0b7 100644 Binary files a/automotive/src/main/res/mipmap-mdpi/ic_launcher.webp and b/automotive/src/main/res/mipmap-mdpi/ic_launcher.webp differ diff --git a/automotive/src/main/res/mipmap-mdpi/ic_launcher_round.webp b/automotive/src/main/res/mipmap-mdpi/ic_launcher_round.webp index 940caf5..6662a13 100644 Binary files a/automotive/src/main/res/mipmap-mdpi/ic_launcher_round.webp and b/automotive/src/main/res/mipmap-mdpi/ic_launcher_round.webp differ diff --git a/automotive/src/main/res/mipmap-xhdpi/ic_launcher.webp b/automotive/src/main/res/mipmap-xhdpi/ic_launcher.webp index c52ae69..5b55a3a 100644 Binary files a/automotive/src/main/res/mipmap-xhdpi/ic_launcher.webp and b/automotive/src/main/res/mipmap-xhdpi/ic_launcher.webp differ diff --git a/automotive/src/main/res/mipmap-xhdpi/ic_launcher_round.webp b/automotive/src/main/res/mipmap-xhdpi/ic_launcher_round.webp index 56b8e2b..c918987 100644 Binary files a/automotive/src/main/res/mipmap-xhdpi/ic_launcher_round.webp and b/automotive/src/main/res/mipmap-xhdpi/ic_launcher_round.webp differ diff --git a/automotive/src/main/res/mipmap-xxhdpi/ic_launcher.webp b/automotive/src/main/res/mipmap-xxhdpi/ic_launcher.webp index 284db5a..97c5070 100644 Binary files a/automotive/src/main/res/mipmap-xxhdpi/ic_launcher.webp and b/automotive/src/main/res/mipmap-xxhdpi/ic_launcher.webp differ diff --git a/automotive/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp b/automotive/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp index 2f729db..60383ba 100644 Binary files a/automotive/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp and b/automotive/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp differ diff --git a/automotive/src/main/res/mipmap-xxxhdpi/ic_launcher.webp b/automotive/src/main/res/mipmap-xxxhdpi/ic_launcher.webp index 9b5e24c..3464553 100644 Binary files a/automotive/src/main/res/mipmap-xxxhdpi/ic_launcher.webp and b/automotive/src/main/res/mipmap-xxxhdpi/ic_launcher.webp differ diff --git a/automotive/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp b/automotive/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp index 56a171b..dafc417 100644 Binary files a/automotive/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp and b/automotive/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp differ diff --git a/common/car/build.gradle.kts b/common/car/build.gradle.kts index 9c3dc48..b064eb1 100644 --- a/common/car/build.gradle.kts +++ b/common/car/build.gradle.kts @@ -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) diff --git a/common/car/src/main/ic_delete-playstore.png b/common/car/src/main/ic_delete-playstore.png deleted file mode 100644 index 60f9cbd..0000000 Binary files a/common/car/src/main/ic_delete-playstore.png and /dev/null differ diff --git a/common/car/src/main/ic_launcher-playstore.png b/common/car/src/main/ic_launcher-playstore.png deleted file mode 100644 index 55787b0..0000000 Binary files a/common/car/src/main/ic_launcher-playstore.png and /dev/null differ diff --git a/common/car/src/main/java/com/kouros/navigation/car/CarSensorManager.kt b/common/car/src/main/java/com/kouros/navigation/car/CarSensorManager.kt index 3b56910..f7a6adf 100644 --- a/common/car/src/main/java/com/kouros/navigation/car/CarSensorManager.kt +++ b/common/car/src/main/java/com/kouros/navigation/car/CarSensorManager.kt @@ -137,7 +137,7 @@ class CarSensorManager( carCompassListener ) carSensors.addCarHardwareLocationListener( - CarSensors.UPDATE_RATE_FASTEST, + CarSensors.UPDATE_RATE_NORMAL, carContext.mainExecutor, carLocationListener ) diff --git a/common/car/src/main/java/com/kouros/navigation/car/NavigationCarAppService.kt b/common/car/src/main/java/com/kouros/navigation/car/NavigationCarAppService.kt index e66897d..c103e48 100644 --- a/common/car/src/main/java/com/kouros/navigation/car/NavigationCarAppService.kt +++ b/common/car/src/main/java/com/kouros/navigation/car/NavigationCarAppService.kt @@ -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 { diff --git a/common/car/src/main/java/com/kouros/navigation/car/NavigationNotificationService.kt b/common/car/src/main/java/com/kouros/navigation/car/NavigationNotificationService.kt new file mode 100644 index 0000000..7a15cde --- /dev/null +++ b/common/car/src/main/java/com/kouros/navigation/car/NavigationNotificationService.kt @@ -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 + ) + } + } + } +} diff --git a/common/car/src/main/java/com/kouros/navigation/car/NavigationSession.kt b/common/car/src/main/java/com/kouros/navigation/car/NavigationSession.kt index 14057ca..98950f4 100644 --- a/common/car/src/main/java/com/kouros/navigation/car/NavigationSession.kt +++ b/common/car/src/main/java/com/kouros/navigation/car/NavigationSession.kt @@ -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 } /** diff --git a/common/car/src/main/java/com/kouros/navigation/car/SurfaceRenderer.kt b/common/car/src/main/java/com/kouros/navigation/car/SurfaceRenderer.kt index fb7f6ce..8f1ecf7 100644 --- a/common/car/src/main/java/com/kouros/navigation/car/SurfaceRenderer.kt +++ b/common/car/src/main/java/com/kouros/navigation/car/SurfaceRenderer.kt @@ -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 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 - } diff --git a/common/car/src/main/java/com/kouros/navigation/car/map/MapView.kt b/common/car/src/main/java/com/kouros/navigation/car/map/MapView.kt index 9410eda..7dd7c67 100644 --- a/common/car/src/main/java/com/kouros/navigation/car/map/MapView.kt +++ b/common/car/src/main/java/com/kouros/navigation/car/map/MapView.kt @@ -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 ) diff --git a/common/car/src/main/java/com/kouros/navigation/car/navigation/NavigationUtils.kt b/common/car/src/main/java/com/kouros/navigation/car/navigation/NavigationUtils.kt deleted file mode 100644 index 370f3a8..0000000 --- a/common/car/src/main/java/com/kouros/navigation/car/navigation/NavigationUtils.kt +++ /dev/null @@ -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) - } -} \ No newline at end of file diff --git a/common/car/src/main/java/com/kouros/navigation/car/navigation/RouteCarModel.kt b/common/car/src/main/java/com/kouros/navigation/car/navigation/RouteCarModel.kt index f3ddb0b..9e8cd12 100644 --- a/common/car/src/main/java/com/kouros/navigation/car/navigation/RouteCarModel.kt +++ b/common/car/src/main/java/com/kouros/navigation/car/navigation/RouteCarModel.kt @@ -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 + } + } } diff --git a/common/car/src/main/java/com/kouros/navigation/car/navigation/Simulation.kt b/common/car/src/main/java/com/kouros/navigation/car/navigation/Simulation.kt index 86d260e..c4e18b1 100644 --- a/common/car/src/main/java/com/kouros/navigation/car/navigation/Simulation.kt +++ b/common/car/src/main/java/com/kouros/navigation/car/navigation/Simulation.kt @@ -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? = 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() } } diff --git a/common/car/src/main/java/com/kouros/navigation/car/screen/CategoriesScreen.kt b/common/car/src/main/java/com/kouros/navigation/car/screen/CategoriesScreen.kt index ef87d91..a0f1d6b 100644 --- a/common/car/src/main/java/com/kouros/navigation/car/screen/CategoriesScreen.kt +++ b/common/car/src/main/java/com/kouros/navigation/car/screen/CategoriesScreen.kt @@ -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() ) diff --git a/common/car/src/main/java/com/kouros/navigation/car/screen/CategoryScreen.kt b/common/car/src/main/java/com/kouros/navigation/car/screen/CategoryScreen.kt index 8276d28..fb3ff9f 100644 --- a/common/car/src/main/java/com/kouros/navigation/car/screen/CategoryScreen.kt +++ b/common/car/src/main/java/com/kouros/navigation/car/screen/CategoryScreen.kt @@ -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)) diff --git a/common/car/src/main/java/com/kouros/navigation/car/screen/NavigationScreen.kt b/common/car/src/main/java/com/kouros/navigation/car/screen/NavigationScreen.kt index bcd6e06..9372964 100644 --- a/common/car/src/main/java/com/kouros/navigation/car/screen/NavigationScreen.kt +++ b/common/car/src/main/java/com/kouros/navigation/car/screen/NavigationScreen.kt @@ -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() @@ -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 diff --git a/common/car/src/main/java/com/kouros/navigation/car/screen/RoutePreviewScreen.kt b/common/car/src/main/java/com/kouros/navigation/car/screen/RoutePreviewScreen.kt index 6d87a1f..25764ab 100644 --- a/common/car/src/main/java/com/kouros/navigation/car/screen/RoutePreviewScreen.kt +++ b/common/car/src/main/java/com/kouros/navigation/car/screen/RoutePreviewScreen.kt @@ -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() } diff --git a/common/car/src/main/java/com/kouros/navigation/car/screen/ScreenUtils.kt b/common/car/src/main/java/com/kouros/navigation/car/screen/ScreenUtils.kt index 96a2a8c..137c762 100644 --- a/common/car/src/main/java/com/kouros/navigation/car/screen/ScreenUtils.kt +++ b/common/car/src/main/java/com/kouros/navigation/car/screen/ScreenUtils.kt @@ -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() } + + + diff --git a/common/car/src/main/java/com/kouros/navigation/car/screen/SearchScreen.kt b/common/car/src/main/java/com/kouros/navigation/car/screen/SearchScreen.kt index 029518d..2136246 100644 --- a/common/car/src/main/java/com/kouros/navigation/car/screen/SearchScreen.kt +++ b/common/car/src/main/java/com/kouros/navigation/car/screen/SearchScreen.kt @@ -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 diff --git a/common/car/src/main/java/com/kouros/navigation/car/screen/settings/AudioSettings.kt b/common/car/src/main/java/com/kouros/navigation/car/screen/settings/AudioSettings.kt index 1d6cc45..a5b99d6 100644 --- a/common/car/src/main/java/com/kouros/navigation/car/screen/settings/AudioSettings.kt +++ b/common/car/src/main/java/com/kouros/navigation/car/screen/settings/AudioSettings.kt @@ -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, ) diff --git a/common/car/src/main/java/com/kouros/navigation/car/screen/settings/CarSettings.kt b/common/car/src/main/java/com/kouros/navigation/car/screen/settings/CarSettings.kt new file mode 100644 index 0000000..6933485 --- /dev/null +++ b/common/car/src/main/java/com/kouros/navigation/car/screen/settings/CarSettings.kt @@ -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) + } +} diff --git a/common/car/src/main/java/com/kouros/navigation/car/screen/settings/DarkModeSettings.kt b/common/car/src/main/java/com/kouros/navigation/car/screen/settings/DarkModeSettings.kt index 9d1bf90..6e72269 100644 --- a/common/car/src/main/java/com/kouros/navigation/car/screen/settings/DarkModeSettings.kt +++ b/common/car/src/main/java/com/kouros/navigation/car/screen/settings/DarkModeSettings.kt @@ -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 ) diff --git a/common/car/src/main/java/com/kouros/navigation/car/screen/settings/NavigationSettings.kt b/common/car/src/main/java/com/kouros/navigation/car/screen/settings/NavigationSettings.kt index 5432c99..de4fa1c 100644 --- a/common/car/src/main/java/com/kouros/navigation/car/screen/settings/NavigationSettings.kt +++ b/common/car/src/main/java/com/kouros/navigation/car/screen/settings/NavigationSettings.kt @@ -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) ) ) diff --git a/common/car/src/main/java/com/kouros/navigation/car/screen/settings/SettingsScreen.kt b/common/car/src/main/java/com/kouros/navigation/car/screen/settings/SettingsScreen.kt index df9efa3..0ff6900 100644 --- a/common/car/src/main/java/com/kouros/navigation/car/screen/settings/SettingsScreen.kt +++ b/common/car/src/main/java/com/kouros/navigation/car/screen/settings/SettingsScreen.kt @@ -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 { diff --git a/common/data/src/main/java/com/kouros/navigation/data/Color.kt b/common/data/src/main/java/com/kouros/navigation/data/Color.kt index 7c27b77..7b79ca6 100644 --- a/common/data/src/main/java/com/kouros/navigation/data/Color.kt +++ b/common/data/src/main/java/com/kouros/navigation/data/Color.kt @@ -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) diff --git a/common/data/src/main/java/com/kouros/navigation/data/Data.kt b/common/data/src/main/java/com/kouros/navigation/data/Data.kt index 9f21254..49fdd30 100644 --- a/common/data/src/main/java/com/kouros/navigation/data/Data.kt +++ b/common/data/src/main/java/com/kouros/navigation/data/Data.kt @@ -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), diff --git a/common/data/src/main/java/com/kouros/navigation/data/NavigationRepository.kt b/common/data/src/main/java/com/kouros/navigation/data/NavigationRepository.kt index 56a8a76..2a157c4 100644 --- a/common/data/src/main/java/com/kouros/navigation/data/NavigationRepository.kt +++ b/common/data/src/main/java/com/kouros/navigation/data/NavigationRepository.kt @@ -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 "" } diff --git a/common/data/src/main/java/com/kouros/navigation/data/Route.kt b/common/data/src/main/java/com/kouros/navigation/data/Route.kt index b0ed13d..895201f 100644 --- a/common/data/src/main/java/com/kouros/navigation/data/Route.kt +++ b/common/data/src/main/java/com/kouros/navigation/data/Route.kt @@ -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 diff --git a/common/data/src/main/java/com/kouros/navigation/data/datastore/DataStoreManager.kt b/common/data/src/main/java/com/kouros/navigation/data/datastore/DataStoreManager.kt index 99840a4..10efc41 100644 --- a/common/data/src/main/java/com/kouros/navigation/data/datastore/DataStoreManager.kt +++ b/common/data/src/main/java/com/kouros/navigation/data/datastore/DataStoreManager.kt @@ -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 = + 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 + } + } } diff --git a/common/data/src/main/java/com/kouros/navigation/data/tomtom/TomTomRepository.kt b/common/data/src/main/java/com/kouros/navigation/data/tomtom/TomTomRepository.kt index 5451efa..7c5f5e8 100644 --- a/common/data/src/main/java/com/kouros/navigation/data/tomtom/TomTomRepository.kt +++ b/common/data/src/main/java/com/kouros/navigation/data/tomtom/TomTomRepository.kt @@ -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 diff --git a/common/data/src/main/java/com/kouros/navigation/model/NavigationViewModel.kt b/common/data/src/main/java/com/kouros/navigation/model/NavigationViewModel.kt index f3afdf5..c200c96 100644 --- a/common/data/src/main/java/com/kouros/navigation/model/NavigationViewModel.kt +++ b/common/data/src/main/java/com/kouros/navigation/model/NavigationViewModel.kt @@ -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) } /** diff --git a/common/data/src/main/java/com/kouros/navigation/model/RouteCalculator.kt b/common/data/src/main/java/com/kouros/navigation/model/RouteCalculator.kt index cbddb81..c5eb723 100644 --- a/common/data/src/main/java/com/kouros/navigation/model/RouteCalculator.kt +++ b/common/data/src/main/java/com/kouros/navigation/model/RouteCalculator.kt @@ -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 diff --git a/common/data/src/main/java/com/kouros/navigation/model/SettingsViewModel.kt b/common/data/src/main/java/com/kouros/navigation/model/SettingsViewModel.kt index 0828beb..20a78cd 100644 --- a/common/data/src/main/java/com/kouros/navigation/model/SettingsViewModel.kt +++ b/common/data/src/main/java/com/kouros/navigation/model/SettingsViewModel.kt @@ -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) } + } + } diff --git a/common/data/src/main/java/com/kouros/navigation/repository/SettingsRepository.kt b/common/data/src/main/java/com/kouros/navigation/repository/SettingsRepository.kt index 2a8efa5..7469645 100644 --- a/common/data/src/main/java/com/kouros/navigation/repository/SettingsRepository.kt +++ b/common/data/src/main/java/com/kouros/navigation/repository/SettingsRepository.kt @@ -47,6 +47,9 @@ class SettingsRepository( val tripSuggestionFlow: Flow = dataStoreManager.tripSuggestionFlow + val engineTypeFlow: Flow = + 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) + } } diff --git a/common/data/src/main/java/com/kouros/navigation/utils/NavigationUtils.kt b/common/data/src/main/java/com/kouros/navigation/utils/NavigationUtils.kt index 17f85ea..fa14350 100644 --- a/common/data/src/main/java/com/kouros/navigation/utils/NavigationUtils.kt +++ b/common/data/src/main/java/com/kouros/navigation/utils/NavigationUtils.kt @@ -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 } diff --git a/common/data/src/main/res/values-de/strings.xml b/common/data/src/main/res/values-de/strings.xml index d786a06..7673c57 100644 --- a/common/data/src/main/res/values-de/strings.xml +++ b/common/data/src/main/res/values-de/strings.xml @@ -66,4 +66,9 @@ Allgemein Verkehr anzeigen Fahrten-Vorschläge + Drive settings + Car settings + Combustion + Electric + Engine type diff --git a/common/data/src/main/res/values-el/strings.xml b/common/data/src/main/res/values-el/strings.xml index 33fc688..9cb7f14 100644 --- a/common/data/src/main/res/values-el/strings.xml +++ b/common/data/src/main/res/values-el/strings.xml @@ -50,4 +50,9 @@ Γενικά Εμφάνιση κίνησης Προτάσεις διαδρομής + Drive settings + Car settings + Combustion + Electric + Engine type diff --git a/common/data/src/main/res/values-pl/strings.xml b/common/data/src/main/res/values-pl/strings.xml index 8a45bc3..9338f09 100644 --- a/common/data/src/main/res/values-pl/strings.xml +++ b/common/data/src/main/res/values-pl/strings.xml @@ -50,4 +50,9 @@ Ogólne Pokaż natężenie ruchu Sugestie dotyczące podróży + Drive settings + Car settings + Combustion + Electric + Engine type diff --git a/common/data/src/main/res/values/strings.xml b/common/data/src/main/res/values/strings.xml index f51af49..17e79fb 100644 --- a/common/data/src/main/res/values/strings.xml +++ b/common/data/src/main/res/values/strings.xml @@ -53,4 +53,9 @@ General Show traffic Trip suggestions + Drive settings + Car settings + Combustion + Electric + Engine type \ No newline at end of file