TomTom Routing
This commit is contained in:
200
CLAUDE.md
Normal file
200
CLAUDE.md
Normal file
@@ -0,0 +1,200 @@
|
||||
# CLAUDE.md
|
||||
|
||||
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
||||
|
||||
## Project Overview
|
||||
|
||||
This is an Android navigation app built with Jetpack Compose that supports multiple routing providers (OSRM, Valhalla, TomTom) and includes Android Auto/Automotive OS integration. The app uses MapLibre for rendering, ObjectBox for local persistence, and Koin for dependency injection.
|
||||
|
||||
## Build Commands
|
||||
|
||||
```bash
|
||||
# Build the app (from repository root)
|
||||
./gradlew :app:assembleDebug
|
||||
|
||||
# Build specific flavor
|
||||
./gradlew :app:assembleDemoDebug
|
||||
./gradlew :app:assembleFullDebug
|
||||
|
||||
# Run tests
|
||||
./gradlew test
|
||||
|
||||
# Run tests for specific module
|
||||
./gradlew :common:data:test
|
||||
./gradlew :common:car:test
|
||||
|
||||
# Install on device
|
||||
./gradlew :app:installDebug
|
||||
|
||||
# Clean build
|
||||
./gradlew clean
|
||||
```
|
||||
|
||||
## Module Structure
|
||||
|
||||
The project uses a multi-module architecture:
|
||||
|
||||
- **app/** - Main Android app with Jetpack Compose UI for phone
|
||||
- **common/data/** - Core data layer with routing logic, repositories, and data models (shared by all modules)
|
||||
- **common/car/** - Android Auto/Automotive OS UI implementation
|
||||
- **automotive/** - Placeholder for future native Automotive OS app
|
||||
|
||||
Dependencies flow: `app` → `common:car` → `common:data`
|
||||
|
||||
## Architecture
|
||||
|
||||
### Routing Providers (Pluggable System)
|
||||
|
||||
The app supports three routing engines that implement the `NavigationRepository` abstract class:
|
||||
|
||||
1. **OsrmRepository** - OSRM routing engine
|
||||
2. **ValhallaRepository** - Valhalla routing engine
|
||||
3. **TomTomRepository** - TomTom routing engine
|
||||
|
||||
Each provider has a corresponding mapper class (`OsrmRoute`, `ValhallaRoute`, `TomTomRoute`) that converts provider-specific JSON responses to the universal `Route` data model.
|
||||
|
||||
**Adding a new routing provider:**
|
||||
1. Create `NewProviderRepository` extending `NavigationRepository` in `common/data/src/main/java/com/kouros/navigation/data/`
|
||||
2. Implement `getRoute()` method
|
||||
3. Create `NewProviderRoute.kt` with `mapToRoute()` function
|
||||
4. Add provider detection logic in `Route.Builder.route()`
|
||||
5. Update `NavigationUtils.getViewModel()` to return appropriate ViewModel
|
||||
|
||||
### Data Flow
|
||||
|
||||
```
|
||||
User Action (search/select destination)
|
||||
↓
|
||||
ViewModel.loadRoute() [LiveData]
|
||||
↓
|
||||
NavigationRepository.getRoute() [Selected provider]
|
||||
↓
|
||||
*Route.mapToRoute() [Convert to universal Route model]
|
||||
↓
|
||||
RouteModel.startNavigation()
|
||||
↓
|
||||
RouteModel.updateLocation() [On each location update]
|
||||
↓
|
||||
UI observes LiveData and displays current step
|
||||
```
|
||||
|
||||
### Key Classes
|
||||
|
||||
**Navigation Logic:**
|
||||
- `RouteModel.kt` - Core navigation engine (tracks position, calculates distances, manages steps)
|
||||
- `RouteCarModel.kt` - Extends RouteModel with Android Auto-specific formatting
|
||||
- `ViewModel.kt` - androidx.ViewModel with LiveData for route, traffic, places, etc.
|
||||
|
||||
**Data Models:**
|
||||
- `Route.kt` - Universal route structure used by all providers
|
||||
- `Place.kt` - ObjectBox entity for favorites/recent locations
|
||||
- `StepData.kt` - Display data for current navigation instruction
|
||||
|
||||
**Repositories:**
|
||||
- `NavigationRepository.kt` - Abstract base class for all routing providers
|
||||
- Also handles Nominatim geocoding search and TomTom traffic incidents
|
||||
|
||||
**Android Auto:**
|
||||
- `NavigationCarAppService.kt` - Entry point for Android Auto/Automotive OS
|
||||
- `NavigationSession.kt` - Session management
|
||||
- `NavigationScreen.kt` - Car screen templates with NavigationType state machine
|
||||
- `SurfaceRenderer.kt` - Handles virtual display and map rendering
|
||||
|
||||
### External APIs
|
||||
|
||||
| Service | Purpose | Base URL |
|
||||
|---------|---------|----------|
|
||||
| OSRM | Routing | `https://kouros-online.de/osrm/route/v1/driving/` |
|
||||
| Valhalla | Routing | `https://kouros-online.de/valhalla/route` |
|
||||
| TomTom | Traffic incidents | `https://api.tomtom.com/traffic/services/5/incidentDetails` |
|
||||
| Nominatim | Geocoding search | `https://kouros-online.de/nominatim/` |
|
||||
| Overpass | POI & speed limits | OpenStreetMap Overpass API |
|
||||
|
||||
## Important Constants
|
||||
|
||||
Located in `Constants.kt` (`common/data`):
|
||||
|
||||
```kotlin
|
||||
NEXT_STEP_THRESHOLD = 120.0 m // Distance to show next maneuver
|
||||
DESTINATION_ARRIVAL_DISTANCE = 40.0 m // Distance to trigger arrival
|
||||
MAXIMAL_SNAP_CORRECTION = 50.0 m // Max distance to snap to route
|
||||
MAXIMAL_ROUTE_DEVIATION = 80.0 m // Max deviation before reroute
|
||||
```
|
||||
|
||||
SharedPreferences keys:
|
||||
- `ROUTING_ENGINE` - Selected provider (0=Valhalla, 1=OSRM, 2=TomTom)
|
||||
- `DARK_MODE_SETTINGS` - Theme preference
|
||||
- `AVOID_MOTORWAY`, `AVOID_TOLLWAY` - Route preferences
|
||||
|
||||
## Navigation Flow
|
||||
|
||||
1. **Route Loading**: User searches via Nominatim → selects place → ViewModel.loadRoute() calls selected repository
|
||||
2. **Route Parsing**: Provider JSON → mapper converts to universal Route → RouteModel.startNavigation()
|
||||
3. **Location Tracking**: FusedLocationProviderClient provides updates → RouteModel.updateLocation()
|
||||
4. **Step Calculation**: findStep() snaps location to nearest waypoint → updates current step
|
||||
5. **UI Updates**: currentStep() and nextStep() provide display data (instruction, distance, icon, lanes)
|
||||
6. **Arrival**: When distance < DESTINATION_ARRIVAL_DISTANCE, navigation ends
|
||||
|
||||
## Testing Navigation
|
||||
|
||||
The app includes mock location support for testing:
|
||||
|
||||
- Set `useMock = true` in MainActivity
|
||||
- Enable "Mock location app" in Android Developer Options
|
||||
- Choose test mode:
|
||||
- `type = 1` - Simulate movement along entire route
|
||||
- `type = 2` - Test specific step range
|
||||
- `type = 3` - Replay GPX track file
|
||||
|
||||
## ObjectBox Database
|
||||
|
||||
ObjectBox is configured in `common/data/build.gradle.kts` with the kapt plugin. The database stores:
|
||||
|
||||
- Recent destinations (category: "Recent")
|
||||
- Favorite places (category: "Favorites")
|
||||
- Imported contacts (category: "Contacts")
|
||||
|
||||
Queries use ObjectBox query builder pattern with generated `Place_` property accessors.
|
||||
|
||||
## Compose UI Structure
|
||||
|
||||
**Phone App:**
|
||||
- `MainActivity.kt` - Main entry with permission handling and Navigation Compose
|
||||
- `NavigationScreen.kt` - Turn-by-turn navigation display
|
||||
- `SearchSheet.kt` / `NavigationSheet.kt` - Bottom sheet content
|
||||
- `MapView.kt` - MapLibre rendering with camera state management
|
||||
|
||||
**Android Auto:**
|
||||
- Uses CarAppService Screen templates (NavigationTemplate, MessageTemplate, MapWithContentTemplate)
|
||||
- NavigationType enum controls which template to display (VIEW, NAVIGATION, REROUTE, RECENT, ARRIVAL)
|
||||
|
||||
## Build Flavors
|
||||
|
||||
Two product flavors with dimension "version":
|
||||
- **demo** - applicationId: `com.kouros.navigation.demo`
|
||||
- **full** - applicationId: `com.kouros.navigation.full`
|
||||
|
||||
## Common Patterns
|
||||
|
||||
**Dependency Injection (Koin):**
|
||||
```kotlin
|
||||
single { OsrmRepository() }
|
||||
viewModel { ViewModel(get()) }
|
||||
```
|
||||
|
||||
**LiveData Observation:**
|
||||
```kotlin
|
||||
viewModel.route.observe(this) { routeJson ->
|
||||
routeModel.startNavigation(routeJson, context)
|
||||
}
|
||||
```
|
||||
|
||||
**Step Finding Algorithm:**
|
||||
RouteModel iterates through all step waypoints, calculates distance to current location, and snaps to the nearest waypoint to determine current step index.
|
||||
|
||||
## Known Limitations
|
||||
|
||||
- Valhalla route mapping is incomplete (search for TODO comments in ValhallaRoute.kt)
|
||||
- Rerouting logic exists but needs more testing
|
||||
- Speed limit queries via Overpass API could be optimized for performance
|
||||
- TomTom implementation uses local JSON file (R.raw.tomom_routing) instead of live API
|
||||
@@ -14,8 +14,8 @@ android {
|
||||
applicationId = "com.kouros.navigation"
|
||||
minSdk = 33
|
||||
targetSdk = 36
|
||||
versionCode = 32
|
||||
versionName = "0.1.3.32"
|
||||
versionCode = 36
|
||||
versionName = "0.2.0.36"
|
||||
base.archivesName = "navi-$versionName"
|
||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||
}
|
||||
@@ -95,6 +95,12 @@ dependencies {
|
||||
implementation(libs.androidx.window)
|
||||
implementation(libs.androidx.compose.foundation.layout)
|
||||
implementation("com.github.ticofab:android-gpx-parser:2.3.1")
|
||||
implementation(libs.androidx.navigation.compose)
|
||||
implementation(libs.kotlinx.serialization.json)
|
||||
implementation("com.github.alorma.compose-settings:ui-tiles:2.25.0")
|
||||
implementation("com.github.alorma.compose-settings:ui-tiles-extended:2.25.0")
|
||||
implementation("com.github.alorma.compose-settings:ui-tiles-expressive:2.25.0")
|
||||
implementation(libs.androidx.foundation.layout)
|
||||
testImplementation(libs.junit)
|
||||
androidTestImplementation(libs.androidx.junit)
|
||||
androidTestImplementation(libs.androidx.espresso.core)
|
||||
|
||||
@@ -48,8 +48,8 @@ class MockLocation (private var locationManager: LocationManager) {
|
||||
this.latitude = latitude
|
||||
this.longitude = longitude
|
||||
this.altitude = 0.0
|
||||
this.accuracy = 1.0f
|
||||
this.speed = 10f
|
||||
this.accuracy = 0f
|
||||
this.speed = 0f
|
||||
this.time = System.currentTimeMillis()
|
||||
this.elapsedRealtimeNanos = SystemClock.elapsedRealtimeNanos()
|
||||
|
||||
@@ -71,8 +71,8 @@ class MockLocation (private var locationManager: LocationManager) {
|
||||
this.latitude = latitude
|
||||
this.longitude = longitude
|
||||
this.altitude = 0.0
|
||||
this.accuracy = 1.0f
|
||||
this.speed = 10f
|
||||
this.accuracy = 0f
|
||||
this.speed = 0f
|
||||
this.time = System.currentTimeMillis()
|
||||
this.elapsedRealtimeNanos = SystemClock.elapsedRealtimeNanos()
|
||||
|
||||
|
||||
@@ -0,0 +1,162 @@
|
||||
package com.kouros.navigation.ui
|
||||
|
||||
import android.content.Context
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.ColumnScope
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.consumeWindowInsets
|
||||
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.Card
|
||||
import androidx.compose.material3.CardDefaults
|
||||
import androidx.compose.material3.CenterAlignedTopAppBar
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.mutableIntStateOf
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
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.alorma.compose.settings.ui.SettingsCheckbox
|
||||
import com.alorma.compose.settings.ui.SettingsGroup
|
||||
import com.alorma.compose.settings.ui.SettingsRadioButton
|
||||
import com.alorma.compose.settings.ui.base.internal.LocalSettingsTileColors
|
||||
import com.alorma.compose.settings.ui.base.internal.SettingsTileDefaults
|
||||
import com.kouros.data.R
|
||||
import com.kouros.navigation.data.Constants.DARK_MODE_SETTINGS
|
||||
import com.kouros.navigation.data.Constants.SHOW_THREED_BUILDING
|
||||
import com.kouros.navigation.utils.NavigationUtils
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun DisplayScreenSettings(context: Context, navigateBack: () -> Unit) {
|
||||
Scaffold(
|
||||
topBar = {
|
||||
CenterAlignedTopAppBar(
|
||||
title = {
|
||||
Text(
|
||||
stringResource(id = R.string.display_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),
|
||||
)
|
||||
}
|
||||
},
|
||||
)
|
||||
},
|
||||
) { padding ->
|
||||
val scrollState = rememberScrollState()
|
||||
Column(
|
||||
modifier =
|
||||
Modifier
|
||||
.consumeWindowInsets(padding)
|
||||
.verticalScroll(scrollState)
|
||||
.padding(top = padding.calculateTopPadding()),
|
||||
) {
|
||||
DisplaySettings(context)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Composable
|
||||
private fun DisplaySettings(context: Context) {
|
||||
Section(title = "Anzeige") {
|
||||
val state = remember {
|
||||
mutableStateOf(
|
||||
NavigationUtils.getBooleanKeyValue(
|
||||
context,
|
||||
SHOW_THREED_BUILDING
|
||||
)
|
||||
)
|
||||
}
|
||||
SettingsCheckbox(
|
||||
state = state.value,
|
||||
title = { Text(text = stringResource(R.string.threed_building)) },
|
||||
onCheckedChange = {
|
||||
state.value = it
|
||||
NavigationUtils.setBooleanKeyValue(context, it, SHOW_THREED_BUILDING)
|
||||
},
|
||||
)
|
||||
}
|
||||
Section(title = "Dunkles Design") {
|
||||
val state = remember {
|
||||
mutableIntStateOf(
|
||||
NavigationUtils.getIntKeyValue(
|
||||
context,
|
||||
DARK_MODE_SETTINGS
|
||||
)
|
||||
)
|
||||
}
|
||||
DarkModeData(context).darkDesign.forEach { sampleItem ->
|
||||
SettingsRadioButton(
|
||||
state = state.intValue == sampleItem.key,
|
||||
title = { Text(text = sampleItem.title) },
|
||||
onClick = {
|
||||
state.intValue = sampleItem.key
|
||||
NavigationUtils.setIntKeyValue(context, state.intValue, DARK_MODE_SETTINGS)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
internal fun Section(
|
||||
title: String,
|
||||
enabled: Boolean = true,
|
||||
verticalArrangement: Arrangement.Vertical = Arrangement.spacedBy(4.dp),
|
||||
content: @Composable ColumnScope.() -> Unit,
|
||||
) {
|
||||
SettingsGroup(
|
||||
contentPadding = PaddingValues(0.dp),
|
||||
verticalArrangement = verticalArrangement,
|
||||
enabled = enabled,
|
||||
title = { Text(text = title) },
|
||||
) {
|
||||
Card(
|
||||
colors = CardDefaults.cardColors(
|
||||
containerColor = (LocalSettingsTileColors.current
|
||||
?: SettingsTileDefaults.colors()).containerColor
|
||||
),
|
||||
) {
|
||||
content()
|
||||
}
|
||||
}
|
||||
}
|
||||
internal class DarkModeData(context: Context) {
|
||||
val darkDesign =
|
||||
listOf(
|
||||
Item(
|
||||
key = 0,
|
||||
title = context.getString(R.string.off_action_title),
|
||||
),
|
||||
Item(
|
||||
key = 1,
|
||||
title = context.getString(R.string.on_action_title),
|
||||
),
|
||||
Item(
|
||||
key = 2,
|
||||
title = context.getString(R.string.use_telephon_settings),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
internal data class Item(
|
||||
val key: Int,
|
||||
val title: String,
|
||||
)
|
||||
@@ -15,9 +15,13 @@ import androidx.activity.enableEdgeToEdge
|
||||
import androidx.annotation.RequiresPermission
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.material3.BottomSheetScaffold
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.FloatingActionButton
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.SnackbarHost
|
||||
import androidx.compose.material3.SnackbarHostState
|
||||
import androidx.compose.material3.Text
|
||||
@@ -33,17 +37,23 @@ import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.Observer
|
||||
import androidx.navigation.NavController
|
||||
import androidx.navigation.NavHostController
|
||||
import androidx.navigation.compose.NavHost
|
||||
import androidx.navigation.compose.composable
|
||||
import androidx.navigation.compose.rememberNavController
|
||||
import com.google.android.gms.location.FusedLocationProviderClient
|
||||
import com.google.android.gms.location.LocationServices
|
||||
import com.kouros.data.R
|
||||
import com.kouros.navigation.MainApplication.Companion.navigationViewModel
|
||||
import com.kouros.navigation.data.Constants
|
||||
import com.kouros.navigation.data.Constants.DESTINATION_ARRIVAL_DISTANCE
|
||||
import com.kouros.navigation.data.Constants.homeHohenwaldeck
|
||||
import com.kouros.navigation.data.Constants.homeVogelhart
|
||||
import com.kouros.navigation.data.StepData
|
||||
import com.kouros.navigation.model.BaseStyleModel
|
||||
@@ -78,6 +88,7 @@ class MainActivity : ComponentActivity() {
|
||||
val routeModel = RouteModel()
|
||||
var tilt = 50.0
|
||||
val useMock = false
|
||||
val type = 1 // simulate 2 test 3 gpx
|
||||
|
||||
var currentIndex = 0
|
||||
val stepData: MutableLiveData<StepData> by lazy {
|
||||
@@ -92,9 +103,13 @@ class MainActivity : ComponentActivity() {
|
||||
routeModel.startNavigation(newRoute, applicationContext)
|
||||
routeData.value = routeModel.curRoute.routeGeoJson
|
||||
if (useMock) {
|
||||
simulate()
|
||||
//test()
|
||||
///gpx(applicationContext)
|
||||
when (type) {
|
||||
1 -> simulate()
|
||||
2 -> test()
|
||||
3 -> gpx(
|
||||
context = applicationContext
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -114,9 +129,6 @@ class MainActivity : ComponentActivity() {
|
||||
|
||||
lateinit var baseStyle: BaseStyle.Json
|
||||
|
||||
init {
|
||||
|
||||
}
|
||||
|
||||
@RequiresPermission(allOf = [Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.ACCESS_COARSE_LOCATION])
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
@@ -156,24 +168,28 @@ class MainActivity : ComponentActivity() {
|
||||
permissions = permissions,
|
||||
requiredPermissions = listOf(permissions.first()),
|
||||
onGranted = {
|
||||
Content()
|
||||
App()
|
||||
// auto navigate
|
||||
if (useMock) {
|
||||
navigationViewModel.loadRoute(
|
||||
applicationContext,
|
||||
homeVogelhart,
|
||||
homeHohenwaldeck,
|
||||
0F
|
||||
)
|
||||
// navigationViewModel.loadRoute(
|
||||
// applicationContext,
|
||||
// homeVogelhart,
|
||||
// homeHohenwaldeck,
|
||||
// 0F
|
||||
// )
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@SuppressLint("AutoboxingStateCreation")
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun Content() {
|
||||
fun StartScreen(
|
||||
navController: NavHostController
|
||||
|
||||
) {
|
||||
|
||||
val scaffoldState = rememberBottomSheetScaffoldState()
|
||||
val snackbarHostState = remember { SnackbarHostState() }
|
||||
val scope = rememberCoroutineScope()
|
||||
@@ -226,6 +242,42 @@ class MainActivity : ComponentActivity() {
|
||||
baseStyle
|
||||
)
|
||||
}
|
||||
if (!routeModel.isNavigating()) {
|
||||
Settings(navController, modifier = Modifier.fillMaxWidth())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun App() {
|
||||
val navController = rememberNavController()
|
||||
NavHost(navController = navController, startDestination = "startScreen") {
|
||||
composable("startScreen") { StartScreen(navController) }
|
||||
composable("settings") { SettingsScreen(navController) { navController.popBackStack() } }
|
||||
composable("display_settings") { DisplayScreenSettings(applicationContext) { navController.popBackStack() } }
|
||||
composable("nav_settings") { NavigationScreenSettings(applicationContext) { navController.popBackStack() } }
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun Settings(navController: NavController, modifier: Modifier = Modifier) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
) {
|
||||
FloatingActionButton(
|
||||
modifier = Modifier
|
||||
.padding(start = 10.dp, top = 40.dp),
|
||||
onClick = {
|
||||
navController.navigate("settings")
|
||||
},
|
||||
) {
|
||||
Icon(
|
||||
painter = painterResource(R.drawable.menu_24px),
|
||||
contentDescription = stringResource(id = R.string.accept_action_title),
|
||||
modifier = Modifier.size(24.dp, 24.dp),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -265,7 +317,7 @@ class MainActivity : ComponentActivity() {
|
||||
if (isNavigating()) {
|
||||
updateLocation(currentLocation, navigationViewModel)
|
||||
stepData.value = currentStep()
|
||||
if (route.currentStep + 1 <= curLeg.steps.size) {
|
||||
if (route.currentStepIndex + 1 <= curLeg.steps.size) {
|
||||
nextStepData.value = nextStep()
|
||||
}
|
||||
if (maneuverType in 39..42
|
||||
@@ -338,8 +390,8 @@ class MainActivity : ComponentActivity() {
|
||||
for ((index, waypoint) in routeModel.curRoute.waypoints.withIndex()) {
|
||||
if (routeModel.isNavigating()) {
|
||||
val deviation = 0.0
|
||||
if (index in 0..routeModel.curRoute.waypoints.size) {
|
||||
mock.setMockLocation(waypoint[1] + deviation, waypoint[0])
|
||||
if (index in 300..routeModel.curRoute.waypoints.size) {
|
||||
mock.setMockLocation(waypoint[1], waypoint[0])
|
||||
delay(500L) //
|
||||
}
|
||||
}
|
||||
@@ -349,13 +401,14 @@ class MainActivity : ComponentActivity() {
|
||||
|
||||
fun test() {
|
||||
for ((index, step) in routeModel.curLeg.steps.withIndex()) {
|
||||
if (index in 3..3) {
|
||||
//if (index in 3..3) {
|
||||
for ((windex, waypoint) in step.maneuver.waypoints.withIndex()) {
|
||||
routeModel.updateLocation(
|
||||
location(waypoint[0], waypoint[1]),
|
||||
navigationViewModel
|
||||
)
|
||||
val step = routeModel.currentStep()
|
||||
println("Step: ${step}")
|
||||
if (step.leftStepDistance == 70.0) {
|
||||
println("")
|
||||
}
|
||||
@@ -363,7 +416,7 @@ class MainActivity : ComponentActivity() {
|
||||
//nextStepData.value = routeModel.nextStep()
|
||||
}
|
||||
}
|
||||
}
|
||||
//}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -377,7 +430,7 @@ class MainActivity : ComponentActivity() {
|
||||
fun gpx(context: Context) {
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
val parser = GPXParser()
|
||||
val input = context.resources.openRawResource(R.raw.hv)
|
||||
val input = context.resources.openRawResource(R.raw.vh)
|
||||
val parsedGpx: Gpx? = parser.parse(input) // consider using a background thread
|
||||
parsedGpx?.let {
|
||||
val tracks = parsedGpx.tracks
|
||||
|
||||
@@ -0,0 +1,162 @@
|
||||
package com.kouros.navigation.ui
|
||||
|
||||
import android.content.Context
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.consumeWindowInsets
|
||||
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.CenterAlignedTopAppBar
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.mutableIntStateOf
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
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.alorma.compose.settings.ui.SettingsCheckbox
|
||||
import com.alorma.compose.settings.ui.SettingsRadioButton
|
||||
import com.kouros.data.R
|
||||
import com.kouros.navigation.data.Constants
|
||||
import com.kouros.navigation.data.Constants.DARK_MODE_SETTINGS
|
||||
import com.kouros.navigation.data.Constants.ROUTING_ENGINE
|
||||
import com.kouros.navigation.data.RouteEngine
|
||||
import com.kouros.navigation.utils.NavigationUtils
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun NavigationScreenSettings(context: Context, navigateBack: () -> Unit) {
|
||||
Scaffold(
|
||||
topBar = {
|
||||
CenterAlignedTopAppBar(
|
||||
title = {
|
||||
Text(
|
||||
stringResource(id = R.string.navigation_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),
|
||||
)
|
||||
}
|
||||
},
|
||||
)
|
||||
},
|
||||
) { padding ->
|
||||
val scrollState = rememberScrollState()
|
||||
Column(
|
||||
modifier =
|
||||
Modifier
|
||||
.consumeWindowInsets(padding)
|
||||
.verticalScroll(scrollState)
|
||||
.padding(top = padding.calculateTopPadding()),
|
||||
) {
|
||||
NavigationSettings(context)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun NavigationSettings(context: Context) {
|
||||
Section(title = stringResource(id = R.string.options)) {
|
||||
val avoidMotorwayState = remember {
|
||||
mutableStateOf(
|
||||
NavigationUtils.getBooleanKeyValue(
|
||||
context,
|
||||
Constants.AVOID_MOTORWAY
|
||||
)
|
||||
)
|
||||
}
|
||||
SettingsCheckbox(
|
||||
state = avoidMotorwayState.value,
|
||||
title = { Text(text = stringResource(id = R.string.avoid_highways_row_title)) },
|
||||
onCheckedChange = {
|
||||
avoidMotorwayState.value = it
|
||||
NavigationUtils.setBooleanKeyValue(context, it, Constants.AVOID_MOTORWAY)
|
||||
},
|
||||
)
|
||||
|
||||
val avoidTollwayState = remember {
|
||||
mutableStateOf(
|
||||
NavigationUtils.getBooleanKeyValue(
|
||||
context,
|
||||
Constants.AVOID_TOLLWAY
|
||||
)
|
||||
)
|
||||
}
|
||||
SettingsCheckbox(
|
||||
state = avoidTollwayState.value,
|
||||
title = { Text(text = stringResource(id = R.string.avoid_tolls_row_title)) },
|
||||
onCheckedChange = {
|
||||
avoidTollwayState.value = it
|
||||
NavigationUtils.setBooleanKeyValue(context, it, Constants.AVOID_TOLLWAY)
|
||||
},
|
||||
)
|
||||
|
||||
val carLocationState = remember {
|
||||
mutableStateOf(
|
||||
NavigationUtils.getBooleanKeyValue(
|
||||
context,
|
||||
Constants.CAR_LOCATION
|
||||
)
|
||||
)
|
||||
}
|
||||
SettingsCheckbox(
|
||||
state = carLocationState.value,
|
||||
title = { Text(text = stringResource(id = R.string.use_car_location)) },
|
||||
onCheckedChange = {
|
||||
carLocationState.value = it
|
||||
NavigationUtils.setBooleanKeyValue(context, it, Constants.CAR_LOCATION)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
Section(title = stringResource(id = R.string.routing_engine)) {
|
||||
val state = remember {
|
||||
mutableIntStateOf(
|
||||
NavigationUtils.getIntKeyValue(
|
||||
context,
|
||||
ROUTING_ENGINE
|
||||
)
|
||||
)
|
||||
}
|
||||
RoutingEngineData.engines.forEach { sampleItem ->
|
||||
SettingsRadioButton(
|
||||
state = state.intValue == sampleItem.key,
|
||||
title = { Text(text = sampleItem.title) },
|
||||
onClick = {
|
||||
state.intValue = sampleItem.key
|
||||
NavigationUtils.setIntKeyValue(context, state.intValue, ROUTING_ENGINE)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal object RoutingEngineData {
|
||||
val engines =
|
||||
listOf(
|
||||
Item(
|
||||
key = 0,
|
||||
title = RouteEngine.VALHALLA.toString(),
|
||||
),
|
||||
Item(
|
||||
key = 1,
|
||||
title = RouteEngine.OSRM.toString(),
|
||||
),
|
||||
Item(
|
||||
key = 2,
|
||||
title = RouteEngine.TOMTOM.toString(),
|
||||
),
|
||||
)
|
||||
}
|
||||
@@ -13,6 +13,7 @@ import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.core.graphics.drawable.IconCompat
|
||||
import com.kouros.data.R
|
||||
import com.kouros.navigation.data.Constants.NEXT_STEP_THRESHOLD
|
||||
import com.kouros.navigation.data.StepData
|
||||
@@ -30,14 +31,11 @@ fun NavigationSheet(
|
||||
stopNavigation: () -> Unit,
|
||||
simulateNavigation: () -> Unit,
|
||||
) {
|
||||
val distance = step.leftDistance.round(1)
|
||||
val distance = (step.leftDistance / 1000).round(1)
|
||||
|
||||
step.lane.forEach {
|
||||
if (it.indications.isNotEmpty()) {
|
||||
routeModel.createLaneIcon(applicationContext, step)
|
||||
if (step.lane.isNotEmpty()) {
|
||||
routeModel.addLanes( step)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Column {
|
||||
FlowRow(horizontalArrangement = Arrangement.SpaceEvenly) {
|
||||
@@ -45,6 +43,7 @@ fun NavigationSheet(
|
||||
Spacer(Modifier.size(30.dp))
|
||||
Text("$distance km", fontSize = 22.sp)
|
||||
}
|
||||
|
||||
HorizontalDivider()
|
||||
FlowRow(horizontalArrangement = Arrangement.SpaceEvenly) {
|
||||
if (routeModel.isNavigating()) {
|
||||
|
||||
86
app/src/main/java/com/kouros/navigation/ui/SettingsScreen.kt
Normal file
86
app/src/main/java/com/kouros/navigation/ui/SettingsScreen.kt
Normal file
@@ -0,0 +1,86 @@
|
||||
package com.kouros.navigation.ui
|
||||
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.consumeWindowInsets
|
||||
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.CenterAlignedTopAppBar
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.navigation.NavHostController
|
||||
import com.alorma.compose.settings.ui.SettingsMenuLink
|
||||
import com.kouros.data.R
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterial3ExpressiveApi::class)
|
||||
@Composable
|
||||
fun SettingsScreen(navController: NavHostController, navigateBack: () -> Unit) {
|
||||
Scaffold(
|
||||
topBar = {
|
||||
CenterAlignedTopAppBar(
|
||||
title = {
|
||||
Text(
|
||||
stringResource(id = R.string.settings_action_title),
|
||||
)
|
||||
},
|
||||
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),
|
||||
)
|
||||
}
|
||||
},
|
||||
)
|
||||
},
|
||||
) { padding ->
|
||||
val scrollState = rememberScrollState()
|
||||
Column(
|
||||
modifier =
|
||||
Modifier
|
||||
.consumeWindowInsets(padding)
|
||||
.verticalScroll(scrollState)
|
||||
.padding(top = padding.calculateTopPadding()),
|
||||
) {
|
||||
SettingsMenuLink(
|
||||
title = { Text(text = stringResource(R.string.display_settings)) },
|
||||
modifier = Modifier,
|
||||
enabled = true,
|
||||
onClick = { navController.navigate("display_settings")},
|
||||
icon = {
|
||||
Icon(
|
||||
painter = painterResource(R.drawable.ic_place_white_24dp),
|
||||
contentDescription = stringResource(id = R.string.display_settings),
|
||||
modifier = Modifier.size(48.dp, 48.dp),
|
||||
)
|
||||
}
|
||||
)
|
||||
SettingsMenuLink(
|
||||
title = { Text(text = stringResource(R.string.navigation_settings)) },
|
||||
modifier = Modifier,
|
||||
enabled = true,
|
||||
onClick = { navController.navigate("nav_settings")},
|
||||
icon = {
|
||||
Icon(
|
||||
painter = painterResource(R.drawable.navigation_24px),
|
||||
contentDescription = stringResource(id = R.string.navigation_settings),
|
||||
modifier = Modifier.size(48.dp, 48.dp),
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,10 +2,75 @@ package com.kouros.navigation.ui.theme
|
||||
|
||||
import androidx.compose.ui.graphics.Color
|
||||
|
||||
val Purple80 = Color(0xFFD0BCFF)
|
||||
val PurpleGrey80 = Color(0xFFCCC2DC)
|
||||
val Pink80 = Color(0xFFEFB8C8)
|
||||
//val Purple80 = Color(0xFFD0BCFF)
|
||||
//val PurpleGrey80 = Color(0xFFCCC2DC)
|
||||
//val Pink80 = Color(0xFFEFB8C8)
|
||||
//
|
||||
//val Purple40 = Color(0xFF6650a4)
|
||||
//val PurpleGrey40 = Color(0xFF625b71)
|
||||
//val Pink40 = Color(0xFF7D5260)
|
||||
|
||||
val Purple40 = Color(0xFF6650a4)
|
||||
val PurpleGrey40 = Color(0xFF625b71)
|
||||
val Pink40 = Color(0xFF7D5260)
|
||||
val md_theme_light_primary = Color(0xFF825500)
|
||||
val md_theme_light_onPrimary = Color(0xFFFFFFFF)
|
||||
val md_theme_light_primaryContainer = Color(0xFFFFDDB3)
|
||||
val md_theme_light_onPrimaryContainer = Color(0xFF291800)
|
||||
val md_theme_light_secondary = Color(0xFF6F5B40)
|
||||
val md_theme_light_onSecondary = Color(0xFFFFFFFF)
|
||||
val md_theme_light_secondaryContainer = Color(0xFFFBDEBC)
|
||||
val md_theme_light_onSecondaryContainer = Color(0xFF271904)
|
||||
val md_theme_light_tertiary = Color(0xFF51643F)
|
||||
val md_theme_light_onTertiary = Color(0xFFFFFFFF)
|
||||
val md_theme_light_tertiaryContainer = Color(0xFFD4EABB)
|
||||
val md_theme_light_onTertiaryContainer = Color(0xFF102004)
|
||||
val md_theme_light_error = Color(0xFFBA1A1A)
|
||||
val md_theme_light_errorContainer = Color(0xFFFFDAD6)
|
||||
val md_theme_light_onError = Color(0xFFFFFFFF)
|
||||
val md_theme_light_onErrorContainer = Color(0xFF410002)
|
||||
val md_theme_light_background = Color(0xFFFFFBFF)
|
||||
val md_theme_light_onBackground = Color(0xFF1F1B16)
|
||||
val md_theme_light_surface = Color(0xFFFFFBFF)
|
||||
val md_theme_light_onSurface = Color(0xFF1F1B16)
|
||||
val md_theme_light_surfaceVariant = Color(0xFFF0E0CF)
|
||||
val md_theme_light_onSurfaceVariant = Color(0xFF4F4539)
|
||||
val md_theme_light_outline = Color(0xFF817567)
|
||||
val md_theme_light_inverseOnSurface = Color(0xFFF9EFE7)
|
||||
val md_theme_light_inverseSurface = Color(0xFF34302A)
|
||||
val md_theme_light_inversePrimary = Color(0xFFFFB951)
|
||||
val md_theme_light_shadow = Color(0xFF000000)
|
||||
val md_theme_light_surfaceTint = Color(0xFF825500)
|
||||
val md_theme_light_outlineVariant = Color(0xFFD3C4B4)
|
||||
val md_theme_light_scrim = Color(0xFF000000)
|
||||
|
||||
val md_theme_dark_primary = Color(0xFFFFB951)
|
||||
val md_theme_dark_onPrimary = Color(0xFF452B00)
|
||||
val md_theme_dark_primaryContainer = Color(0xFF633F00)
|
||||
val md_theme_dark_onPrimaryContainer = Color(0xFFFFDDB3)
|
||||
val md_theme_dark_secondary = Color(0xFFDDC2A1)
|
||||
val md_theme_dark_onSecondary = Color(0xFF3E2D16)
|
||||
val md_theme_dark_secondaryContainer = Color(0xFF56442A)
|
||||
val md_theme_dark_onSecondaryContainer = Color(0xFFFBDEBC)
|
||||
val md_theme_dark_tertiary = Color(0xFFB8CEA1)
|
||||
val md_theme_dark_onTertiary = Color(0xFF243515)
|
||||
val md_theme_dark_tertiaryContainer = Color(0xFF3A4C2A)
|
||||
val md_theme_dark_onTertiaryContainer = Color(0xFFD4EABB)
|
||||
val md_theme_dark_error = Color(0xFFFFB4AB)
|
||||
val md_theme_dark_errorContainer = Color(0xFF93000A)
|
||||
val md_theme_dark_onError = Color(0xFF690005)
|
||||
val md_theme_dark_onErrorContainer = Color(0xFFFFDAD6)
|
||||
val md_theme_dark_background = Color(0xFF1F1B16)
|
||||
val md_theme_dark_onBackground = Color(0xFFEAE1D9)
|
||||
val md_theme_dark_surface = Color(0xFF1F1B16)
|
||||
val md_theme_dark_onSurface = Color(0xFFEAE1D9)
|
||||
val md_theme_dark_surfaceVariant = Color(0xFF4F4539)
|
||||
val md_theme_dark_onSurfaceVariant = Color(0xFFD3C4B4)
|
||||
val md_theme_dark_outline = Color(0xFF9C8F80)
|
||||
val md_theme_dark_inverseOnSurface = Color(0xFF1F1B16)
|
||||
val md_theme_dark_inverseSurface = Color(0xFFEAE1D9)
|
||||
val md_theme_dark_inversePrimary = Color(0xFF825500)
|
||||
val md_theme_dark_shadow = Color(0xFF000000)
|
||||
val md_theme_dark_surfaceTint = Color(0xFFFFB951)
|
||||
val md_theme_dark_outlineVariant = Color(0xFF4F4539)
|
||||
val md_theme_dark_scrim = Color(0xFF000000)
|
||||
|
||||
|
||||
val seed = Color(0xFF825500)
|
||||
|
||||
29
app/src/main/java/com/kouros/navigation/ui/theme/Shapes.kt
Normal file
29
app/src/main/java/com/kouros/navigation/ui/theme/Shapes.kt
Normal file
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* Copyright 2022 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.kouros.navigation.ui.theme
|
||||
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material3.Shapes
|
||||
import androidx.compose.ui.unit.dp
|
||||
|
||||
val shapes = Shapes(
|
||||
extraSmall = RoundedCornerShape(4.dp),
|
||||
small = RoundedCornerShape(8.dp),
|
||||
medium = RoundedCornerShape(16.dp),
|
||||
large = RoundedCornerShape(24.dp),
|
||||
extraLarge = RoundedCornerShape(32.dp)
|
||||
)
|
||||
@@ -1,57 +1,111 @@
|
||||
package com.kouros.navigation.ui.theme
|
||||
|
||||
import android.app.Activity
|
||||
import android.os.Build
|
||||
import androidx.compose.foundation.isSystemInDarkTheme
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.MaterialTheme.colorScheme
|
||||
import androidx.compose.material3.darkColorScheme
|
||||
import androidx.compose.material3.dynamicDarkColorScheme
|
||||
import androidx.compose.material3.dynamicLightColorScheme
|
||||
import androidx.compose.material3.lightColorScheme
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.SideEffect
|
||||
import androidx.compose.ui.graphics.toArgb
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.platform.LocalView
|
||||
import androidx.core.view.WindowCompat
|
||||
|
||||
private val DarkColorScheme = darkColorScheme(
|
||||
primary = Purple80,
|
||||
secondary = PurpleGrey80,
|
||||
tertiary = Pink80
|
||||
private val LightColors = lightColorScheme(
|
||||
primary = md_theme_light_primary,
|
||||
onPrimary = md_theme_light_onPrimary,
|
||||
primaryContainer = md_theme_light_primaryContainer,
|
||||
onPrimaryContainer = md_theme_light_onPrimaryContainer,
|
||||
secondary = md_theme_light_secondary,
|
||||
onSecondary = md_theme_light_onSecondary,
|
||||
secondaryContainer = md_theme_light_secondaryContainer,
|
||||
onSecondaryContainer = md_theme_light_onSecondaryContainer,
|
||||
tertiary = md_theme_light_tertiary,
|
||||
onTertiary = md_theme_light_onTertiary,
|
||||
tertiaryContainer = md_theme_light_tertiaryContainer,
|
||||
onTertiaryContainer = md_theme_light_onTertiaryContainer,
|
||||
error = md_theme_light_error,
|
||||
errorContainer = md_theme_light_errorContainer,
|
||||
onError = md_theme_light_onError,
|
||||
onErrorContainer = md_theme_light_onErrorContainer,
|
||||
background = md_theme_light_background,
|
||||
onBackground = md_theme_light_onBackground,
|
||||
surface = md_theme_light_surface,
|
||||
onSurface = md_theme_light_onSurface,
|
||||
surfaceVariant = md_theme_light_surfaceVariant,
|
||||
onSurfaceVariant = md_theme_light_onSurfaceVariant,
|
||||
outline = md_theme_light_outline,
|
||||
inverseOnSurface = md_theme_light_inverseOnSurface,
|
||||
inverseSurface = md_theme_light_inverseSurface,
|
||||
inversePrimary = md_theme_light_inversePrimary,
|
||||
surfaceTint = md_theme_light_surfaceTint,
|
||||
outlineVariant = md_theme_light_outlineVariant,
|
||||
scrim = md_theme_light_scrim,
|
||||
)
|
||||
|
||||
private val LightColorScheme = lightColorScheme(
|
||||
primary = Purple40,
|
||||
secondary = PurpleGrey40,
|
||||
tertiary = Pink40
|
||||
|
||||
/* Other default colors to override
|
||||
background = Color(0xFFFFFBFE),
|
||||
surface = Color(0xFFFFFBFE),
|
||||
onPrimary = Color.White,
|
||||
onSecondary = Color.White,
|
||||
onTertiary = Color.White,
|
||||
onBackground = Color(0xFF1C1B1F),
|
||||
onSurface = Color(0xFF1C1B1F),
|
||||
*/
|
||||
private val DarkColors = darkColorScheme(
|
||||
primary = md_theme_dark_primary,
|
||||
onPrimary = md_theme_dark_onPrimary,
|
||||
primaryContainer = md_theme_dark_primaryContainer,
|
||||
onPrimaryContainer = md_theme_dark_onPrimaryContainer,
|
||||
secondary = md_theme_dark_secondary,
|
||||
onSecondary = md_theme_dark_onSecondary,
|
||||
secondaryContainer = md_theme_dark_secondaryContainer,
|
||||
onSecondaryContainer = md_theme_dark_onSecondaryContainer,
|
||||
tertiary = md_theme_dark_tertiary,
|
||||
onTertiary = md_theme_dark_onTertiary,
|
||||
tertiaryContainer = md_theme_dark_tertiaryContainer,
|
||||
onTertiaryContainer = md_theme_dark_onTertiaryContainer,
|
||||
error = md_theme_dark_error,
|
||||
errorContainer = md_theme_dark_errorContainer,
|
||||
onError = md_theme_dark_onError,
|
||||
onErrorContainer = md_theme_dark_onErrorContainer,
|
||||
background = md_theme_dark_background,
|
||||
onBackground = md_theme_dark_onBackground,
|
||||
surface = md_theme_dark_surface,
|
||||
onSurface = md_theme_dark_onSurface,
|
||||
surfaceVariant = md_theme_dark_surfaceVariant,
|
||||
onSurfaceVariant = md_theme_dark_onSurfaceVariant,
|
||||
outline = md_theme_dark_outline,
|
||||
inverseOnSurface = md_theme_dark_inverseOnSurface,
|
||||
inverseSurface = md_theme_dark_inverseSurface,
|
||||
inversePrimary = md_theme_dark_inversePrimary,
|
||||
surfaceTint = md_theme_dark_surfaceTint,
|
||||
outlineVariant = md_theme_dark_outlineVariant,
|
||||
scrim = md_theme_dark_scrim,
|
||||
)
|
||||
|
||||
@Composable
|
||||
fun NavigationTheme(
|
||||
darkTheme: Boolean = isSystemInDarkTheme(),
|
||||
// Dynamic color is available on Android 12+
|
||||
dynamicColor: Boolean = true,
|
||||
useDarkTheme: Boolean = isSystemInDarkTheme(),
|
||||
content: @Composable () -> Unit
|
||||
) {
|
||||
val colorScheme = when {
|
||||
dynamicColor -> {
|
||||
val context = LocalContext.current
|
||||
if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context)
|
||||
val colors = run {
|
||||
if (useDarkTheme) dynamicDarkColorScheme(context)
|
||||
else dynamicLightColorScheme(context)
|
||||
}
|
||||
|
||||
darkTheme -> DarkColorScheme
|
||||
else -> LightColorScheme
|
||||
val view = LocalView.current
|
||||
if (!view.isInEditMode) {
|
||||
SideEffect {
|
||||
val window = (view.context as Activity).window
|
||||
window.statusBarColor = colors.primary.toArgb()
|
||||
WindowCompat.getInsetsController(window, view).isAppearanceLightStatusBars =
|
||||
useDarkTheme
|
||||
}
|
||||
|
||||
MaterialTheme(
|
||||
colorScheme = colorScheme,
|
||||
typography = Typography,
|
||||
content = content
|
||||
typography = typography,
|
||||
content = content,
|
||||
shapes = shapes,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -7,28 +7,35 @@ import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.sp
|
||||
|
||||
// Set of Material typography styles to start with
|
||||
val Typography = Typography(
|
||||
val typography = Typography(
|
||||
headlineSmall = TextStyle(
|
||||
fontWeight = FontWeight.SemiBold,
|
||||
fontSize = 24.sp,
|
||||
lineHeight = 32.sp,
|
||||
letterSpacing = 0.sp
|
||||
),
|
||||
titleLarge = TextStyle(
|
||||
fontWeight = FontWeight.SemiBold,
|
||||
fontSize = 18.sp,
|
||||
lineHeight = 32.sp,
|
||||
letterSpacing = 0.sp
|
||||
),
|
||||
bodyLarge = TextStyle(
|
||||
fontFamily = FontFamily.Default,
|
||||
fontWeight = FontWeight.Normal,
|
||||
fontSize = 16.sp,
|
||||
lineHeight = 24.sp,
|
||||
letterSpacing = 0.5.sp
|
||||
)
|
||||
/* Other default text styles to override
|
||||
titleLarge = TextStyle(
|
||||
fontFamily = FontFamily.Default,
|
||||
fontWeight = FontWeight.Normal,
|
||||
fontSize = 22.sp,
|
||||
lineHeight = 28.sp,
|
||||
letterSpacing = 0.sp
|
||||
letterSpacing = 0.15.sp
|
||||
),
|
||||
labelSmall = TextStyle(
|
||||
fontFamily = FontFamily.Default,
|
||||
bodyMedium = TextStyle(
|
||||
fontWeight = FontWeight.Medium,
|
||||
fontSize = 11.sp,
|
||||
fontSize = 14.sp,
|
||||
lineHeight = 20.sp,
|
||||
letterSpacing = 0.25.sp
|
||||
),
|
||||
labelMedium = TextStyle(
|
||||
fontWeight = FontWeight.SemiBold,
|
||||
fontSize = 12.sp,
|
||||
lineHeight = 16.sp,
|
||||
letterSpacing = 0.5.sp
|
||||
)
|
||||
*/
|
||||
)
|
||||
@@ -213,7 +213,7 @@ class SurfaceRenderer(
|
||||
DrawNavigationImages(
|
||||
paddingValues,
|
||||
currentSpeed,
|
||||
routeModel.maxSpeed,
|
||||
routeModel,
|
||||
width,
|
||||
height
|
||||
)
|
||||
|
||||
@@ -2,7 +2,6 @@ package com.kouros.navigation.car.map
|
||||
|
||||
import android.content.Context
|
||||
import android.location.Location
|
||||
import androidx.car.app.connection.CarConnection
|
||||
import androidx.compose.foundation.Canvas
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
@@ -32,10 +31,9 @@ import com.kouros.navigation.car.ViewStyle
|
||||
import com.kouros.navigation.data.Constants
|
||||
import com.kouros.navigation.data.Constants.SHOW_THREED_BUILDING
|
||||
import com.kouros.navigation.data.NavigationColor
|
||||
import com.kouros.navigation.data.ObjectBox
|
||||
import com.kouros.navigation.data.RouteColor
|
||||
import com.kouros.navigation.data.SpeedColor
|
||||
import com.kouros.navigation.data.tomtom.TrafficData
|
||||
import com.kouros.navigation.model.RouteModel
|
||||
import com.kouros.navigation.utils.NavigationUtils.getBooleanKeyValue
|
||||
import org.maplibre.compose.camera.CameraPosition
|
||||
import org.maplibre.compose.camera.CameraState
|
||||
@@ -112,6 +110,7 @@ fun MapLibre(
|
||||
AmenityLayer(route)
|
||||
} else {
|
||||
RouteLayer(route, traffic!!)
|
||||
//RouteLayerPoint(route )
|
||||
}
|
||||
SpeedCameraLayer(speedCameras)
|
||||
}
|
||||
@@ -164,9 +163,9 @@ fun RouteLayer(routeData: String?, trafficData: Map<String, String>) {
|
||||
type = exponential(1.2f),
|
||||
input = zoom(),
|
||||
5 to const(0.4.dp),
|
||||
6 to const(0.8.dp),
|
||||
7 to const(2.0.dp),
|
||||
20 to const(24.dp),
|
||||
6 to const(0.6.dp),
|
||||
7 to const(1.8.dp),
|
||||
20 to const(20.dp),
|
||||
),
|
||||
)
|
||||
LineLayer(
|
||||
@@ -178,15 +177,41 @@ fun RouteLayer(routeData: String?, trafficData: Map<String, String>) {
|
||||
type = exponential(1.2f),
|
||||
input = zoom(),
|
||||
5 to const(0.4.dp),
|
||||
6 to const(0.7.dp),
|
||||
7 to const(1.75.dp),
|
||||
20 to const(22.dp),
|
||||
6 to const(0.5.dp),
|
||||
7 to const(1.6.dp),
|
||||
20 to const(18.dp),
|
||||
),
|
||||
)
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Composable
|
||||
fun RouteLayerPoint(routeData: String?) {
|
||||
if (routeData != null && routeData.isNotEmpty()) {
|
||||
val routes = rememberGeoJsonSource(GeoJsonData.JsonString(routeData))
|
||||
val img = image(painterResource(R.drawable.ic_favorite_filled_white_24dp), drawAsSdf = true)
|
||||
SymbolLayer(
|
||||
id = "point-layer",
|
||||
source = routes,
|
||||
iconOpacity = const(2.0f),
|
||||
iconColor = const(Color.Red),
|
||||
iconImage = img,
|
||||
iconSize =
|
||||
interpolate(
|
||||
type = exponential(1.2f),
|
||||
input = zoom(),
|
||||
5 to const(0.4f),
|
||||
6 to const(0.6f),
|
||||
7 to const(0.8f),
|
||||
20 to const(1.0f),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
fun trafficColor(key: String): Expression<ColorValue> {
|
||||
when (key) {
|
||||
"queuing" -> return const(Color(0xFFD24417))
|
||||
@@ -269,17 +294,18 @@ fun BuildingLayer(tiles: Source) {
|
||||
fun DrawNavigationImages(
|
||||
padding: PaddingValues,
|
||||
speed: Float?,
|
||||
maxSpeed: Int,
|
||||
routeModel: RouteModel,
|
||||
width: Int,
|
||||
height: Int
|
||||
) {
|
||||
NavigationImage(padding, width, height)
|
||||
if (speed != null) {
|
||||
CurrentSpeed(width, height, speed, maxSpeed)
|
||||
CurrentSpeed(width, height, speed, routeModel.maxSpeed)
|
||||
}
|
||||
if (speed != null && maxSpeed > 0 && (speed * 3.6) > maxSpeed) {
|
||||
MaxSpeed(width, height, maxSpeed)
|
||||
if (speed != null && routeModel.maxSpeed > 0 && (speed * 3.6) > routeModel.maxSpeed) {
|
||||
MaxSpeed(width, height, routeModel.maxSpeed)
|
||||
}
|
||||
//DebugInfo(width, height, routeModel)
|
||||
}
|
||||
|
||||
@Composable
|
||||
@@ -428,6 +454,45 @@ private fun MaxSpeed(
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun DebugInfo(
|
||||
width: Int,
|
||||
height: Int,
|
||||
routeModel: RouteModel,
|
||||
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.padding(
|
||||
start = 20.dp,
|
||||
top = 0.dp
|
||||
),
|
||||
contentAlignment = Alignment.CenterStart
|
||||
) {
|
||||
val textMeasurerLocation = rememberTextMeasurer()
|
||||
val location = routeModel.location.latitude.toString()
|
||||
val styleSpeed = TextStyle(
|
||||
fontSize = 26.sp,
|
||||
fontWeight = FontWeight.Bold,
|
||||
color = Color.Black,
|
||||
)
|
||||
val textLayoutLocation = remember(location) {
|
||||
textMeasurerLocation.measure(location, styleSpeed)
|
||||
}
|
||||
Canvas(modifier = Modifier.fillMaxSize()) {
|
||||
drawText(
|
||||
textMeasurer = textMeasurerLocation,
|
||||
text = location,
|
||||
style = styleSpeed,
|
||||
topLeft = Offset(
|
||||
x = center.x - textLayoutLocation.size.width / 2,
|
||||
y = center.y - textLayoutLocation.size.height / 2,
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun rememberBaseStyle(baseStyle: BaseStyle.Json): BaseStyle.Json {
|
||||
val rememberBaseStyle by remember() {
|
||||
@@ -436,7 +501,6 @@ fun rememberBaseStyle( baseStyle : BaseStyle.Json): BaseStyle.Json {
|
||||
return rememberBaseStyle
|
||||
}
|
||||
|
||||
|
||||
fun getPaddingValues(height: Int, viewStyle: ViewStyle): PaddingValues {
|
||||
return when (viewStyle) {
|
||||
ViewStyle.VIEW, ViewStyle.PAN_VIEW -> PaddingValues(
|
||||
|
||||
@@ -16,7 +16,6 @@
|
||||
package com.kouros.navigation.car.navigation
|
||||
|
||||
import android.text.SpannableString
|
||||
import android.text.Spanned
|
||||
import androidx.annotation.DrawableRes
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.car.app.AppManager
|
||||
@@ -27,7 +26,6 @@ import androidx.car.app.model.Alert
|
||||
import androidx.car.app.model.AlertCallback
|
||||
import androidx.car.app.model.CarColor
|
||||
import androidx.car.app.model.CarIcon
|
||||
import androidx.car.app.model.CarIconSpan
|
||||
import androidx.car.app.model.CarText
|
||||
import androidx.car.app.model.DateTimeWithZone
|
||||
import androidx.car.app.model.Distance
|
||||
@@ -42,8 +40,9 @@ import androidx.core.graphics.drawable.IconCompat
|
||||
import com.kouros.data.R
|
||||
import com.kouros.navigation.data.StepData
|
||||
import com.kouros.navigation.model.RouteModel
|
||||
import org.maplibre.compose.expressions.dsl.step
|
||||
import com.kouros.navigation.utils.location
|
||||
import java.util.Collections
|
||||
import java.util.Locale
|
||||
import java.util.TimeZone
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
@@ -59,7 +58,8 @@ class RouteCarModel() : RouteModel() {
|
||||
val maneuver = Maneuver.Builder(stepData.currentManeuverType)
|
||||
.setIcon(createCarIcon(carContext, stepData.icon))
|
||||
if (stepData.currentManeuverType == TYPE_ROUNDABOUT_ENTER_AND_EXIT_CW
|
||||
|| stepData.currentManeuverType == TYPE_ROUNDABOUT_ENTER_AND_EXIT_CCW) {
|
||||
|| stepData.currentManeuverType == TYPE_ROUNDABOUT_ENTER_AND_EXIT_CCW
|
||||
) {
|
||||
maneuver.setRoundaboutExitNumber(stepData.exitNumber)
|
||||
}
|
||||
val step =
|
||||
@@ -84,7 +84,8 @@ class RouteCarModel() : RouteModel() {
|
||||
val maneuver = Maneuver.Builder(stepData.currentManeuverType)
|
||||
.setIcon(createCarIcon(carContext, stepData.icon))
|
||||
if (stepData.currentManeuverType == TYPE_ROUNDABOUT_ENTER_AND_EXIT_CW
|
||||
|| stepData.currentManeuverType == TYPE_ROUNDABOUT_ENTER_AND_EXIT_CCW) {
|
||||
|| stepData.currentManeuverType == TYPE_ROUNDABOUT_ENTER_AND_EXIT_CCW
|
||||
) {
|
||||
maneuver.setRoundaboutExitNumber(stepData.exitNumber)
|
||||
}
|
||||
val step =
|
||||
@@ -100,13 +101,13 @@ class RouteCarModel() : RouteModel() {
|
||||
val timeLeft = travelLeftTime()
|
||||
val timeToDestinationMillis =
|
||||
TimeUnit.SECONDS.toMillis(timeLeft.toLong())
|
||||
val leftDistance = travelLeftDistance()
|
||||
val leftDistance = travelLeftDistance() / 1000
|
||||
val displayUnit = if (leftDistance > 1.0) {
|
||||
Distance.UNIT_KILOMETERS
|
||||
} else {
|
||||
Distance.UNIT_METERS
|
||||
}
|
||||
val arivalTime = DateTimeWithZone.create(
|
||||
val arrivalTime = DateTimeWithZone.create(
|
||||
arrivalTime(),
|
||||
TimeZone.getTimeZone("Europe/Berlin")
|
||||
)
|
||||
@@ -115,7 +116,7 @@ class RouteCarModel() : RouteModel() {
|
||||
leftDistance,
|
||||
displayUnit
|
||||
), // Arrival time at the destination with the destination time zone.
|
||||
arivalTime
|
||||
arrivalTime
|
||||
)
|
||||
.setRemainingTimeSeconds(
|
||||
TimeUnit.MILLISECONDS.toSeconds(
|
||||
@@ -145,62 +146,7 @@ class RouteCarModel() : RouteModel() {
|
||||
"${direction}_${it2.trim()}"
|
||||
}
|
||||
}
|
||||
val laneDirection = when (direction) {
|
||||
"left_straight" -> {
|
||||
when (stepData.currentManeuverType) {
|
||||
Maneuver.TYPE_TURN_NORMAL_LEFT -> LaneDirection.SHAPE_NORMAL_LEFT
|
||||
Maneuver.TYPE_STRAIGHT -> LaneDirection.SHAPE_STRAIGHT
|
||||
else
|
||||
-> LaneDirection.SHAPE_UNKNOWN
|
||||
}
|
||||
}
|
||||
"left" -> {
|
||||
when (stepData.currentManeuverType) {
|
||||
Maneuver.TYPE_TURN_NORMAL_LEFT -> LaneDirection.SHAPE_NORMAL_LEFT
|
||||
else
|
||||
-> LaneDirection.SHAPE_UNKNOWN
|
||||
}
|
||||
}
|
||||
"straight" -> {
|
||||
when (stepData.currentManeuverType) {
|
||||
Maneuver.TYPE_STRAIGHT -> LaneDirection.SHAPE_STRAIGHT
|
||||
else
|
||||
-> LaneDirection.SHAPE_UNKNOWN
|
||||
}
|
||||
}
|
||||
"right" -> {
|
||||
when (stepData.currentManeuverType) {
|
||||
Maneuver.TYPE_TURN_NORMAL_RIGHT -> LaneDirection.SHAPE_NORMAL_RIGHT
|
||||
else
|
||||
-> LaneDirection.SHAPE_UNKNOWN
|
||||
}
|
||||
}
|
||||
"right_straight" -> {
|
||||
when (stepData.currentManeuverType) {
|
||||
Maneuver.TYPE_TURN_NORMAL_RIGHT -> LaneDirection.SHAPE_NORMAL_RIGHT
|
||||
Maneuver.TYPE_STRAIGHT -> LaneDirection.SHAPE_STRAIGHT
|
||||
else
|
||||
-> LaneDirection.SHAPE_UNKNOWN
|
||||
}
|
||||
}
|
||||
"left_slight" -> {
|
||||
when (stepData.currentManeuverType) {
|
||||
Maneuver.TYPE_TURN_SLIGHT_LEFT -> LaneDirection.SHAPE_SLIGHT_LEFT
|
||||
else
|
||||
-> LaneDirection.SHAPE_UNKNOWN
|
||||
}
|
||||
}
|
||||
"right_slight" -> {
|
||||
when (stepData.currentManeuverType) {
|
||||
Maneuver.TYPE_TURN_SLIGHT_RIGHT-> LaneDirection.SHAPE_NORMAL_RIGHT
|
||||
else
|
||||
-> LaneDirection.SHAPE_UNKNOWN
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
LaneDirection.SHAPE_UNKNOWN
|
||||
}
|
||||
}
|
||||
val laneDirection = addLanes(direction, stepData)
|
||||
if (laneDirection != LaneDirection.SHAPE_UNKNOWN) {
|
||||
if (!laneImageAdded) {
|
||||
step.setLanesImage(createCarIcon(createLaneIcon(carContext, stepData)))
|
||||
@@ -216,18 +162,6 @@ class RouteCarModel() : RouteModel() {
|
||||
}
|
||||
}
|
||||
|
||||
private fun createStringWithIcon(
|
||||
carContext: CarContext,
|
||||
text: String,
|
||||
@DrawableRes iconRes: Int
|
||||
): SpannableString {
|
||||
val start = 0
|
||||
val end = text.length
|
||||
val span = CarIconSpan.create(createCarIcon(carContext, iconRes), CarIconSpan.ALIGN_CENTER)
|
||||
val spannableString = SpannableString(text)
|
||||
spannableString.setSpan(span, start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
|
||||
return spannableString
|
||||
}
|
||||
fun createString(
|
||||
text: String
|
||||
): SpannableString {
|
||||
@@ -243,18 +177,23 @@ class RouteCarModel() : RouteModel() {
|
||||
return CarIcon.Builder(IconCompat.createWithResource(carContext, iconRes)).build()
|
||||
}
|
||||
|
||||
fun createCarIcon(iconCompat: IconCompat): CarIcon {
|
||||
return CarIcon.Builder(iconCompat).build()
|
||||
}
|
||||
// fun createCarIcon(iconCompat: IconCompat): CarIcon {
|
||||
// return CarIcon.Builder(iconCompat).build()
|
||||
// }
|
||||
|
||||
fun showSpeedCamera(carContext: CarContext, distance: Double, maxSpeed: String) {
|
||||
carContext.getCarService<AppManager?>(AppManager::class.java)
|
||||
.showAlert(createAlert(carContext, distance, maxSpeed, createCarIcon(carContext, R.drawable.speed_camera_24px)))
|
||||
.showAlert(
|
||||
createAlert(
|
||||
carContext,
|
||||
maxSpeed,
|
||||
createCarIcon(carContext, R.drawable.speed_camera_24px)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
fun createAlert(
|
||||
carContext: CarContext,
|
||||
distance: Double,
|
||||
maxSpeed: String?,
|
||||
icon: CarIcon
|
||||
): Alert {
|
||||
|
||||
@@ -489,7 +489,6 @@ class NavigationScreen(
|
||||
lastTrafficDate = current
|
||||
viewModel.loadTraffic(carContext, location, surfaceRenderer.carOrientation)
|
||||
}
|
||||
//updateTraffic(location)
|
||||
updateSpeedCamera(location)
|
||||
with(routeModel) {
|
||||
updateLocation(location, viewModel)
|
||||
@@ -524,7 +523,7 @@ class NavigationScreen(
|
||||
val updatedCameras = mutableListOf<Elements>()
|
||||
speedCameras.forEach {
|
||||
val plLocation =
|
||||
location(longitude = it.lon!!, latitude = it.lat!!)
|
||||
location(longitude = it.lon, latitude = it.lat)
|
||||
val distance = plLocation.distanceTo(location)
|
||||
it.distance = distance.toDouble()
|
||||
updatedCameras.add(it)
|
||||
@@ -533,7 +532,11 @@ class NavigationScreen(
|
||||
val camera = sortedList.first()
|
||||
val bearingRoute = surfaceRenderer.lastLocation.bearingTo(location)
|
||||
val bearingSpeedCamera = if (camera.tags.direction != null) {
|
||||
try {
|
||||
camera.tags.direction!!.toFloat()
|
||||
} catch ( e: Exception) {
|
||||
0F
|
||||
}
|
||||
} else {
|
||||
location.bearingTo(location(camera.lon, camera.lat)).absoluteValue
|
||||
}
|
||||
|
||||
@@ -34,7 +34,7 @@ class SearchScreen(
|
||||
|
||||
var categories: List<Category> = listOf(
|
||||
Category(id = Constants.RECENT, name = carContext.getString(R.string.recent_destinations)),
|
||||
Category(id = Constants.CONTACTS, name = carContext.getString(R.string.contacts)),
|
||||
// Category(id = Constants.CONTACTS, name = carContext.getString(R.string.contacts)),
|
||||
Category(id = Constants.CATEGORIES, name = carContext.getString(R.string.category_title)),
|
||||
Category(id = Constants.FAVORITES, name = carContext.getString(R.string.favorites))
|
||||
)
|
||||
|
||||
@@ -1,11 +1,5 @@
|
||||
package com.kouros.navigation.car
|
||||
|
||||
import android.location.Location
|
||||
import android.location.LocationManager
|
||||
import com.kouros.navigation.data.Constants.home2Location
|
||||
import com.kouros.navigation.data.Constants.homeLocation
|
||||
import com.kouros.navigation.data.NavigationRepository
|
||||
import com.kouros.navigation.data.SearchFilter
|
||||
import com.kouros.navigation.data.valhalla.ValhallaRepository
|
||||
import com.kouros.navigation.model.RouteModel
|
||||
import com.kouros.navigation.model.ViewModel
|
||||
@@ -24,16 +18,16 @@ class ViewModelTest {
|
||||
@Test
|
||||
fun routeViewModelTest() {
|
||||
|
||||
val fromLocation = Location(LocationManager.GPS_PROVIDER)
|
||||
fromLocation.isMock = true
|
||||
fromLocation.latitude = homeLocation.latitude
|
||||
fromLocation.longitude = homeLocation.longitude
|
||||
val toLocation = Location(LocationManager.GPS_PROVIDER)
|
||||
toLocation.isMock = true
|
||||
toLocation.latitude = home2Location.latitude
|
||||
toLocation.longitude = home2Location.longitude
|
||||
|
||||
val route = repo.getRoute(fromLocation, toLocation, SearchFilter())
|
||||
// val fromLocation = Location(LocationManager.GPS_PROVIDER)
|
||||
// fromLocation.isMock = true
|
||||
// fromLocation.latitude = homeLocation.latitude
|
||||
// fromLocation.longitude = homeLocation.longitude
|
||||
// val toLocation = Location(LocationManager.GPS_PROVIDER)
|
||||
// toLocation.isMock = true
|
||||
// toLocation.latitude = home2Location.latitude
|
||||
// toLocation.longitude = home2Location.longitude
|
||||
//
|
||||
// val route = repo.getRoute(fromLocation, toLocation, SearchFilter())
|
||||
//model.startNavigation(route)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -58,7 +58,7 @@ dependencies {
|
||||
|
||||
implementation(libs.kotlinx.serialization.json)
|
||||
implementation(libs.maplibre.compose)
|
||||
|
||||
implementation("androidx.compose.material:material-icons-extended:1.7.8")
|
||||
testImplementation(libs.junit)
|
||||
androidTestImplementation(libs.androidx.junit)
|
||||
androidTestImplementation(libs.androidx.espresso.core)
|
||||
|
||||
@@ -29,11 +29,11 @@ import java.net.URL
|
||||
|
||||
abstract class NavigationRepository {
|
||||
|
||||
//private val nominatimUrl = "https://nominatim.openstreetmap.org/"
|
||||
private val nominatimUrl = "https://nominatim.openstreetmap.org/"
|
||||
|
||||
private val nominatimUrl = "https://kouros-online.de/nominatim/"
|
||||
//private val nominatimUrl = "https://kouros-online.de/nominatim/"
|
||||
|
||||
private val tomtomApiKey = "678k5v6940cSXXIS5oD92qIrDgW3RBZ3"
|
||||
val tomtomApiKey = "678k5v6940cSXXIS5oD92qIrDgW3RBZ3"
|
||||
|
||||
private val tomtomUrl = "https://api.tomtom.com/traffic/services/5/incidentDetails"
|
||||
|
||||
@@ -55,18 +55,19 @@ abstract class NavigationRepository {
|
||||
searchFilter: SearchFilter,
|
||||
context: Context
|
||||
): Double {
|
||||
val route = getRoute(context, currentLocation, location, carOrientation, searchFilter)
|
||||
val routeModel = RouteModel()
|
||||
routeModel.startNavigation(route, context)
|
||||
return routeModel.curRoute.summary.distance
|
||||
//val route = getRoute(context, currentLocation, location, carOrientation, searchFilter)
|
||||
//val routeModel = RouteModel()
|
||||
//routeModel.startNavigation(route, context)
|
||||
// return routeModel.curRoute.summary.distance
|
||||
return 0.0
|
||||
}
|
||||
|
||||
fun searchPlaces(search: String, location: Location): String {
|
||||
val box = calculateSquareRadius(location.latitude, location.longitude, 20.0)
|
||||
val box = calculateSquareRadius(location.latitude, location.longitude, 100.0)
|
||||
val viewbox = "&bounded=1&viewbox=${box}"
|
||||
return fetchUrl(
|
||||
"${nominatimUrl}search?q=$search&format=jsonv2&addressdetails=true$viewbox",
|
||||
false
|
||||
true
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@ data class Route(
|
||||
|
||||
val routeEngine: Int,
|
||||
val routes: List<com.kouros.navigation.data.route.Routes>,
|
||||
var currentStep: Int = 0,
|
||||
var currentStepIndex: Int = 0,
|
||||
) {
|
||||
|
||||
data class Builder(
|
||||
@@ -51,15 +51,15 @@ data class Route(
|
||||
jsonObject["trip"].toString(),
|
||||
ValhallaResponse::class.java
|
||||
)
|
||||
ValhallaRoute().mapJsonToValhalla(routeJson, this)
|
||||
ValhallaRoute().mapToRoute(routeJson, this)
|
||||
}
|
||||
RouteEngine.OSRM.ordinal -> {
|
||||
val osrmJson = gson.fromJson(route, OsrmResponse::class.java)
|
||||
OsrmRoute().mapToOsrm(osrmJson, this)
|
||||
OsrmRoute().mapToRoute(osrmJson, this)
|
||||
}
|
||||
else -> {
|
||||
val tomtomJson = gson.fromJson(route, TomTomResponse::class.java)
|
||||
TomTomRoute().mapToOsrm(tomtomJson, this)
|
||||
TomTomRoute().mapToRoute(tomtomJson, this)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -91,21 +91,23 @@ data class Route(
|
||||
}
|
||||
}
|
||||
|
||||
fun isRouteValid(): Boolean {
|
||||
return routes.isNotEmpty() && legs().isNotEmpty()
|
||||
}
|
||||
fun currentStep(): Step {
|
||||
|
||||
return if (routes.isNotEmpty() && legs().isNotEmpty()) {
|
||||
legs().first().steps[currentStep]
|
||||
return if (isRouteValid()) {
|
||||
legs().first().steps[currentStepIndex]
|
||||
} else {
|
||||
Step(maneuver = Maneuver(waypoints = emptyList(), location = location(0.0, 0.0)))
|
||||
}
|
||||
}
|
||||
|
||||
fun nextStep(): Step {
|
||||
val nextIndex = currentStep + 1
|
||||
return if (nextIndex < legs().first().steps.size) {
|
||||
fun nextStep(steps : Int): Step {
|
||||
val nextIndex = currentStepIndex + steps
|
||||
return if (isRouteValid() && nextIndex < legs().first().steps.size) {
|
||||
legs().first().steps[nextIndex]
|
||||
} else {
|
||||
throw IndexOutOfBoundsException("No next maneuver available.")
|
||||
currentStep()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ import com.google.gson.annotations.SerializedName
|
||||
|
||||
data class Legs (
|
||||
|
||||
@SerializedName("steps" ) var steps : ArrayList<Steps> = arrayListOf(),
|
||||
@SerializedName("steps" ) var steps : List<Steps> = listOf(),
|
||||
@SerializedName("weight" ) var weight : Double = 0.0,
|
||||
@SerializedName("summary" ) var summary : String = "",
|
||||
@SerializedName("duration" ) var duration : Double = 0.0,
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package com.kouros.navigation.data.osrm
|
||||
|
||||
import com.kouros.navigation.data.Route
|
||||
import com.kouros.navigation.data.RouteEngine
|
||||
import com.kouros.navigation.data.route.Intersection
|
||||
import com.kouros.navigation.data.route.Lane
|
||||
import com.kouros.navigation.data.route.Leg
|
||||
@@ -14,7 +15,7 @@ import com.kouros.navigation.utils.location
|
||||
|
||||
class OsrmRoute {
|
||||
|
||||
fun mapToOsrm(routeJson: OsrmResponse, builder: Route.Builder) {
|
||||
fun mapToRoute(routeJson: OsrmResponse, builder: Route.Builder) {
|
||||
|
||||
val routes = mutableListOf<com.kouros.navigation.data.route.Routes>()
|
||||
var stepIndex = 0
|
||||
@@ -61,7 +62,7 @@ class OsrmRoute {
|
||||
distance = step.distance / 1000,
|
||||
duration = step.duration,
|
||||
maneuver = maneuver,
|
||||
intersection = intersections
|
||||
//intersection = intersections
|
||||
)
|
||||
steps.add(step)
|
||||
stepIndex += 1
|
||||
@@ -80,7 +81,7 @@ class OsrmRoute {
|
||||
routes.add(newRoute)
|
||||
}
|
||||
builder
|
||||
.routeType(1)
|
||||
.routeType(RouteEngine.OSRM.ordinal)
|
||||
.routes(routes)
|
||||
}
|
||||
|
||||
|
||||
@@ -3,6 +3,6 @@ package com.kouros.navigation.data.route
|
||||
import java.util.Collections
|
||||
|
||||
data class Intersection(
|
||||
val location: ArrayList<Double> = arrayListOf(0.0, 0.0),
|
||||
val location: List<Double> = listOf(0.0, 0.0),
|
||||
val lane : List<Lane> = Collections.emptyList<Lane>(),
|
||||
)
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.kouros.navigation.data.route
|
||||
|
||||
data class Leg(
|
||||
var steps : List<Step> = arrayListOf()
|
||||
var steps : List<Step> = arrayListOf(),
|
||||
var intersection: List<Intersection> = arrayListOf()
|
||||
)
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
package com.kouros.navigation.data.tomtom
|
||||
|
||||
data class Cause(
|
||||
val mainCauseCode: Int
|
||||
)
|
||||
@@ -0,0 +1,6 @@
|
||||
package com.kouros.navigation.data.tomtom
|
||||
|
||||
data class Guidance(
|
||||
val instructionGroups: List<InstructionGroup>,
|
||||
val instructions: List<Instruction>
|
||||
)
|
||||
@@ -0,0 +1,21 @@
|
||||
package com.kouros.navigation.data.tomtom
|
||||
|
||||
data class Instruction(
|
||||
val combinedMessage: String,
|
||||
val countryCode: String,
|
||||
val drivingSide: String,
|
||||
val instructionType: String,
|
||||
val junctionType: String,
|
||||
val maneuver: String,
|
||||
val message: String,
|
||||
val point: Point,
|
||||
val pointIndex: Int,
|
||||
val possibleCombineWithNext: Boolean,
|
||||
val roadNumbers: List<String>,
|
||||
val routeOffsetInMeters: Int,
|
||||
val signpostText: String,
|
||||
val street: String = "",
|
||||
val travelTimeInSeconds: Int,
|
||||
val turnAngleInDecimalDegrees: Int,
|
||||
val exitNumber: String? = "0",
|
||||
)
|
||||
@@ -0,0 +1,8 @@
|
||||
package com.kouros.navigation.data.tomtom
|
||||
|
||||
data class InstructionGroup(
|
||||
val firstInstructionIndex: Int,
|
||||
val groupLengthInMeters: Int,
|
||||
val groupMessage: String,
|
||||
val lastInstructionIndex: Int
|
||||
)
|
||||
@@ -0,0 +1,6 @@
|
||||
package com.kouros.navigation.data.tomtom
|
||||
|
||||
data class Lane(
|
||||
val directions: List<String>,
|
||||
val follow: String
|
||||
)
|
||||
@@ -0,0 +1,7 @@
|
||||
package com.kouros.navigation.data.tomtom
|
||||
|
||||
data class Leg(
|
||||
val encodedPolyline: String,
|
||||
val encodedPolylinePrecision: Int,
|
||||
val summary: SummaryX
|
||||
)
|
||||
@@ -0,0 +1,6 @@
|
||||
package com.kouros.navigation.data.tomtom
|
||||
|
||||
data class Point(
|
||||
val latitude: Double,
|
||||
val longitude: Double
|
||||
)
|
||||
@@ -1,5 +0,0 @@
|
||||
package com.kouros.navigation.data.tomtom
|
||||
|
||||
data class Report(
|
||||
val effectiveSettings: List<EffectiveSetting>
|
||||
)
|
||||
@@ -0,0 +1,8 @@
|
||||
package com.kouros.navigation.data.tomtom
|
||||
|
||||
data class Route(
|
||||
val guidance: Guidance,
|
||||
val legs: List<Leg>,
|
||||
val sections: List<Section>,
|
||||
val summary: SummaryX
|
||||
)
|
||||
@@ -0,0 +1,16 @@
|
||||
package com.kouros.navigation.data.tomtom
|
||||
|
||||
import java.util.Collections
|
||||
|
||||
data class Section(
|
||||
val delayInSeconds: Int,
|
||||
val effectiveSpeedInKmh: Int,
|
||||
val endPointIndex: Int,
|
||||
val eventId: String,
|
||||
val laneSeparators: List<String>,
|
||||
val lanes: List<Lane>? = Collections.emptyList<Lane>(),
|
||||
val magnitudeOfDelay: Int,
|
||||
val sectionType: String,
|
||||
val simpleCategory: String,
|
||||
val startPointIndex: Int,
|
||||
)
|
||||
@@ -0,0 +1,10 @@
|
||||
package com.kouros.navigation.data.tomtom
|
||||
|
||||
data class SummaryX(
|
||||
val arrivalTime: String,
|
||||
val departureTime: String,
|
||||
val lengthInMeters: Int,
|
||||
val trafficDelayInSeconds: Int,
|
||||
val trafficLengthInMeters: Int,
|
||||
val travelTimeInSeconds: Int
|
||||
)
|
||||
@@ -0,0 +1,36 @@
|
||||
package com.kouros.navigation.data.tomtom
|
||||
|
||||
import android.content.Context
|
||||
import android.location.Location
|
||||
import com.kouros.data.R
|
||||
import com.kouros.navigation.data.NavigationRepository
|
||||
import com.kouros.navigation.data.SearchFilter
|
||||
|
||||
|
||||
private const val routeUrl = "https://api.tomtom.com/routing/1/calculateRoute/"
|
||||
|
||||
class TomTomRepository : NavigationRepository() {
|
||||
override fun getRoute(
|
||||
context: Context,
|
||||
currentLocation: Location,
|
||||
location: Location,
|
||||
carOrientation: Float,
|
||||
searchFilter: SearchFilter
|
||||
): String {
|
||||
//val routeJson = context.resources.openRawResource(R.raw.tomom_routing)
|
||||
//val routeJsonString = routeJson.bufferedReader().use { it.readText() }
|
||||
//return routeJsonString
|
||||
val url =
|
||||
routeUrl + "${currentLocation.latitude},${currentLocation.longitude}:${location.latitude},${location.longitude}" +
|
||||
"/json?vehicleHeading=90§ionType=traffic&report=effectiveSettings&routeType=eco" +
|
||||
"&traffic=true&avoid=unpavedRoads&travelMode=car" +
|
||||
"&vehicleMaxSpeed=120&vehicleCommercial=false" +
|
||||
"&instructionsType=text&language=en-GB§ionType=lanes" +
|
||||
"&routeRepresentation=encodedPolyline" +
|
||||
"&vehicleEngineType=combustion&key=$tomtomApiKey"
|
||||
return fetchUrl(
|
||||
url,
|
||||
false
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
package com.kouros.navigation.data.tomtom
|
||||
|
||||
data class TomTomResponse(
|
||||
val formatVersion: String,
|
||||
val routes: List<Route>
|
||||
)
|
||||
@@ -1,63 +1,120 @@
|
||||
package com.kouros.navigation.data.tomtom
|
||||
|
||||
import com.kouros.navigation.data.Route
|
||||
import com.kouros.navigation.data.osrm.OsrmResponse
|
||||
import com.kouros.navigation.data.osrm.OsrmRoute.ManeuverType
|
||||
import com.kouros.navigation.data.RouteEngine
|
||||
import com.kouros.navigation.data.route.Intersection
|
||||
import com.kouros.navigation.data.route.Lane
|
||||
import com.kouros.navigation.data.route.Leg
|
||||
import com.kouros.navigation.data.route.Maneuver
|
||||
import com.kouros.navigation.data.route.Step
|
||||
import com.kouros.navigation.data.route.Summary
|
||||
import com.kouros.navigation.utils.GeoUtils.createCenterLocation
|
||||
import com.kouros.navigation.utils.GeoUtils.createLineStringCollection
|
||||
import com.kouros.navigation.utils.GeoUtils.decodePolyline
|
||||
import com.kouros.navigation.utils.location
|
||||
import com.kouros.navigation.data.route.Maneuver as RouteManeuver
|
||||
|
||||
/**
|
||||
curl -X GET "https://api.tomtom.com/routing/1/calculateRoute/\
|
||||
48.1856548,11.57928:48.1183,11.59485/json?\
|
||||
vehicleHeading=90§ionType=traffic\
|
||||
&report=effectiveSettings&routeType=eco\
|
||||
&traffic=true&avoid=unpavedRoadimport com.kouros.navigation.data.route.Maneuver as RouteManeuvers&travelMode=car\
|
||||
&vehicleMaxSpeed=120&vehicleCommercial=false\
|
||||
&instructionsType=text&language=en-GB§ionType=lanes\
|
||||
&routeRepresentation=encodedPolyline\
|
||||
&vehicleEngineType=combustion&key=678k5v6940cSXXIS5oD92qIrDgW3RBZ3"
|
||||
*/
|
||||
|
||||
class TomTomRoute {
|
||||
|
||||
fun mapToOsrm(routeJson: TomTomResponse, builder: Route.Builder) {
|
||||
fun mapToRoute(routeJson: TomTomResponse, builder: Route.Builder) {
|
||||
val routes = mutableListOf<com.kouros.navigation.data.route.Routes>()
|
||||
routeJson.routes.forEach { route ->
|
||||
val legs = mutableListOf<Leg>()
|
||||
val waypoints = mutableListOf<List<Double>>()
|
||||
val legs = mutableListOf<Leg>()
|
||||
var stepIndex = 0
|
||||
var points = listOf<List<Double>>()
|
||||
val summary = Summary(
|
||||
route.summary.travelTimeInSeconds.toDouble(),
|
||||
route.summary.lengthInMeters.toDouble() / 1000
|
||||
route.summary.lengthInMeters.toDouble()
|
||||
)
|
||||
route.legs.forEach { leg ->
|
||||
points = decodePolyline(leg.encodedPolyline, leg.encodedPolylinePrecision)
|
||||
waypoints.addAll(points)
|
||||
}
|
||||
route.guidance.instructions.forEach { instruction ->
|
||||
instruction.exitNumber
|
||||
// val maneuver = RouteManeuver(
|
||||
// // bearingBefore = step.maneuver.bearingBefore,
|
||||
// //bearingAfter = step.maneuver.bearingAfter,
|
||||
// type = convertType(instruction.maneuver),
|
||||
// waypoints = points.subList(section.startPointIndex, section.endPointIndex + 1),
|
||||
// exit = instruction.exitNumber.toInt(),
|
||||
// location = location(
|
||||
// instruction.point.longitude, instruction.point.latitude
|
||||
// )
|
||||
// )
|
||||
}
|
||||
var stepDistance = 0.0
|
||||
var stepDuration = 0.0
|
||||
val allIntersections = mutableListOf<Intersection>()
|
||||
val steps = mutableListOf<Step>()
|
||||
for (index in 0..< route.guidance.instructions.size) {
|
||||
val instruction = route.guidance.instructions[index]
|
||||
val nextPointIndex = nextPointIndex(index, route)
|
||||
val maneuver = RouteManeuver(
|
||||
bearingBefore = 0,
|
||||
bearingAfter = 0,
|
||||
type = convertType(instruction.maneuver),
|
||||
waypoints = points.subList(
|
||||
instruction.pointIndex,
|
||||
route.guidance.instructions[nextPointIndex].pointIndex
|
||||
),
|
||||
exit = exitNumber(instruction),
|
||||
location = location(
|
||||
instruction.point.longitude, instruction.point.latitude
|
||||
),
|
||||
)
|
||||
|
||||
val intersections = mutableListOf<Intersection>()
|
||||
route.sections.forEach { section ->
|
||||
|
||||
|
||||
val lanes = mutableListOf<Lane>()
|
||||
var startIndex = 0
|
||||
if (section.startPointIndex <= instruction.pointIndex - 3
|
||||
&& instruction.pointIndex <= section.endPointIndex
|
||||
) {
|
||||
section.lanes?.forEach { itLane ->
|
||||
val lane = Lane(
|
||||
location(
|
||||
waypoints[section.startPointIndex][0],
|
||||
waypoints[section.startPointIndex][1]
|
||||
),
|
||||
itLane.directions.first() == itLane.follow,
|
||||
itLane.directions
|
||||
)
|
||||
startIndex = section.startPointIndex
|
||||
lanes.add(lane)
|
||||
}
|
||||
intersections.add(Intersection(waypoints[startIndex], lanes))
|
||||
}
|
||||
}
|
||||
allIntersections.addAll(intersections)
|
||||
stepDistance = route.guidance.instructions[nextPointIndex].routeOffsetInMeters - stepDistance
|
||||
stepDuration = route.guidance.instructions[nextPointIndex].travelTimeInSeconds - stepDuration
|
||||
val name = instruction.street
|
||||
val step = Step(
|
||||
index = stepIndex,
|
||||
name = name,
|
||||
distance = stepDistance,
|
||||
duration = stepDuration,
|
||||
maneuver = maneuver,
|
||||
intersection = intersections
|
||||
)
|
||||
stepDistance = route.guidance.instructions[nextPointIndex].routeOffsetInMeters.toDouble()
|
||||
stepDuration = route.guidance.instructions[nextPointIndex].travelTimeInSeconds.toDouble()
|
||||
steps.add(step)
|
||||
stepIndex += 1
|
||||
}
|
||||
legs.add(Leg(steps, allIntersections))
|
||||
val routeGeoJson = createLineStringCollection(waypoints)
|
||||
val centerLocation = createCenterLocation(createLineStringCollection(waypoints))
|
||||
val newRoute = com.kouros.navigation.data.route.Routes(
|
||||
legs,
|
||||
summary,
|
||||
routeGeoJson,
|
||||
centerLocation = centerLocation,
|
||||
waypoints = waypoints
|
||||
)
|
||||
routes.add(newRoute)
|
||||
}
|
||||
builder
|
||||
.routeType(RouteEngine.TOMTOM.ordinal)
|
||||
.routes(routes)
|
||||
}
|
||||
|
||||
private fun nextPointIndex(index: Int, route: com.kouros.navigation.data.tomtom.Route): Int {
|
||||
val nextPointIndex = if (index < route.guidance.instructions.size - 1) {
|
||||
index + 1
|
||||
} else {
|
||||
index + 0
|
||||
}
|
||||
println(routeJson)
|
||||
return nextPointIndex
|
||||
}
|
||||
|
||||
fun convertType(type: String): Int {
|
||||
@@ -66,7 +123,73 @@ class TomTomRoute {
|
||||
"DEPART" -> {
|
||||
newType = androidx.car.app.navigation.model.Maneuver.TYPE_DEPART
|
||||
}
|
||||
|
||||
"ARRIVE" -> {
|
||||
newType = androidx.car.app.navigation.model.Maneuver.TYPE_DESTINATION
|
||||
}
|
||||
|
||||
"ARRIVE_LEFT" -> {
|
||||
newType = androidx.car.app.navigation.model.Maneuver.TYPE_DESTINATION_LEFT
|
||||
}
|
||||
|
||||
"ARRIVE_RIGHT" -> {
|
||||
newType = androidx.car.app.navigation.model.Maneuver.TYPE_DESTINATION_RIGHT
|
||||
}
|
||||
|
||||
"STRAIGHT", "FOLLOW" -> {
|
||||
newType = androidx.car.app.navigation.model.Maneuver.TYPE_STRAIGHT
|
||||
}
|
||||
|
||||
"KEEP_RIGHT" -> {
|
||||
newType = androidx.car.app.navigation.model.Maneuver.TYPE_KEEP_RIGHT
|
||||
}
|
||||
|
||||
"BEAR_RIGHT" -> {
|
||||
newType = androidx.car.app.navigation.model.Maneuver.TYPE_TURN_SLIGHT_RIGHT
|
||||
}
|
||||
"BEAR_LEFT" -> {
|
||||
newType = androidx.car.app.navigation.model.Maneuver.TYPE_TURN_SLIGHT_LEFT
|
||||
}
|
||||
|
||||
"KEEP_LEFT" -> {
|
||||
newType = androidx.car.app.navigation.model.Maneuver.TYPE_KEEP_LEFT
|
||||
}
|
||||
|
||||
"TURN_LEFT" -> {
|
||||
newType = androidx.car.app.navigation.model.Maneuver.TYPE_TURN_NORMAL_LEFT
|
||||
}
|
||||
|
||||
"TURN_RIGHT" -> {
|
||||
newType = androidx.car.app.navigation.model.Maneuver.TYPE_TURN_NORMAL_RIGHT
|
||||
}
|
||||
|
||||
"SHARP_LEFT" -> {
|
||||
newType = androidx.car.app.navigation.model.Maneuver.TYPE_TURN_SHARP_LEFT
|
||||
}
|
||||
|
||||
"SHARP_RIGHT" -> {
|
||||
newType = androidx.car.app.navigation.model.Maneuver.TYPE_TURN_SHARP_RIGHT
|
||||
}
|
||||
|
||||
"ROUNDABOUT_RIGHT" -> {
|
||||
newType = androidx.car.app.navigation.model.Maneuver.TYPE_ROUNDABOUT_ENTER_CCW
|
||||
}
|
||||
|
||||
"ROUNDABOUT_LEFT" -> {
|
||||
newType = androidx.car.app.navigation.model.Maneuver.TYPE_ROUNDABOUT_ENTER_CW
|
||||
}
|
||||
}
|
||||
return newType
|
||||
}
|
||||
}
|
||||
|
||||
private fun exitNumber(
|
||||
instruction: Instruction
|
||||
): Int {
|
||||
return if ( instruction.exitNumber == null
|
||||
|| instruction.exitNumber.isEmpty()) {
|
||||
0
|
||||
} else {
|
||||
instruction.exitNumber.toInt()
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,7 @@ package com.kouros.navigation.data.valhalla
|
||||
import androidx.car.app.navigation.model.Maneuver
|
||||
import com.kouros.data.R
|
||||
import com.kouros.navigation.data.Route
|
||||
import com.kouros.navigation.data.RouteEngine
|
||||
import com.kouros.navigation.data.route.Leg
|
||||
import com.kouros.navigation.data.route.Maneuver as RouteManeuver
|
||||
import com.kouros.navigation.data.route.Step
|
||||
@@ -13,7 +14,7 @@ import com.kouros.navigation.utils.location
|
||||
|
||||
class ValhallaRoute {
|
||||
|
||||
fun mapJsonToValhalla(routeJson: ValhallaResponse, builder: Route.Builder) {
|
||||
fun mapToRoute(routeJson: ValhallaResponse, builder: Route.Builder) {
|
||||
val waypoints = decodePolyline(routeJson.legs[0].shape)
|
||||
val summary = Summary(routeJson.summaryValhalla.time, routeJson.summaryValhalla.length)
|
||||
val steps = mutableListOf<Step>()
|
||||
@@ -38,12 +39,9 @@ class ValhallaRoute {
|
||||
stepIndex += 1
|
||||
}
|
||||
builder
|
||||
.routeType(1)
|
||||
.routeType(RouteEngine.VALHALLA.ordinal)
|
||||
// TODO
|
||||
.routes(emptyList())
|
||||
//.summary(summary)
|
||||
//.routeGeoJson(createLineStringCollection(waypoints))
|
||||
//.waypoints(waypoints)
|
||||
}
|
||||
|
||||
fun convertType(maneuver: Maneuvers): Int {
|
||||
|
||||
@@ -6,6 +6,10 @@ import android.graphics.BitmapFactory
|
||||
import android.graphics.Canvas
|
||||
import android.graphics.Matrix
|
||||
import android.location.Location
|
||||
import androidx.annotation.DrawableRes
|
||||
import androidx.car.app.CarContext
|
||||
import androidx.car.app.model.CarIcon
|
||||
import androidx.car.app.navigation.model.LaneDirection
|
||||
import androidx.car.app.navigation.model.Maneuver
|
||||
import androidx.car.app.navigation.model.Step
|
||||
import androidx.core.graphics.createBitmap
|
||||
@@ -15,6 +19,7 @@ import com.kouros.navigation.data.Constants.NEXT_STEP_THRESHOLD
|
||||
import com.kouros.navigation.data.Constants.ROUTING_ENGINE
|
||||
import com.kouros.navigation.data.Place
|
||||
import com.kouros.navigation.data.Route
|
||||
import com.kouros.navigation.data.RouteEngine
|
||||
import com.kouros.navigation.data.StepData
|
||||
import com.kouros.navigation.data.route.Intersection
|
||||
import com.kouros.navigation.data.route.Lane
|
||||
@@ -30,6 +35,7 @@ import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import java.util.Collections
|
||||
import java.util.Locale
|
||||
import java.util.concurrent.TimeUnit
|
||||
import kotlin.math.absoluteValue
|
||||
import kotlin.math.roundToInt
|
||||
@@ -45,9 +51,10 @@ open class RouteModel() {
|
||||
var lastSpeedLocation: Location = location(0.0, 0.0)
|
||||
var lastSpeedIndex: Int = 0
|
||||
var maxSpeed: Int = 0
|
||||
|
||||
var location: Location = location(0.0, 0.0)
|
||||
var lastLocation: Location = location(0.0, 0.0)
|
||||
var bearing: Float = 0F
|
||||
var routeBearing: Float = 0F
|
||||
|
||||
var currentRouteIndex = 0
|
||||
val curRoute: Routes
|
||||
@@ -62,8 +69,14 @@ open class RouteModel() {
|
||||
.routeEngine(routeEngine)
|
||||
.route(routeString)
|
||||
.build()
|
||||
if (hasLegs()) {
|
||||
navigating = true
|
||||
}
|
||||
}
|
||||
|
||||
private fun hasLegs(): Boolean {
|
||||
return route.routes.isNotEmpty() && route.routes[0].legs.isNotEmpty()
|
||||
}
|
||||
|
||||
fun stopNavigation() {
|
||||
route = Route.Builder().buildEmpty()
|
||||
@@ -76,24 +89,24 @@ open class RouteModel() {
|
||||
@OptIn(DelicateCoroutinesApi::class)
|
||||
fun updateLocation(curLocation: Location, viewModel: ViewModel) {
|
||||
location = curLocation
|
||||
findStep(location)
|
||||
updateSpeedLimit(location, viewModel)
|
||||
findStep(curLocation)
|
||||
updateSpeedLimit(curLocation, viewModel)
|
||||
lastLocation = location
|
||||
}
|
||||
|
||||
private fun findStep(location: Location) {
|
||||
var nearestDistance = 100000.0f
|
||||
var nearestDistance = 100000f
|
||||
for ((index, step) in curLeg.steps.withIndex()) {
|
||||
if (index >= route.currentStep) {
|
||||
if (index >= route.currentStepIndex) {
|
||||
for ((wayIndex, waypoint) in step.maneuver.waypoints.withIndex()) {
|
||||
if (wayIndex >= step.waypointIndex) {
|
||||
val distance = location.distanceTo(location(waypoint[0], waypoint[1]))
|
||||
if (distance < nearestDistance) {
|
||||
nearestDistance = distance
|
||||
route.currentStep = step.index
|
||||
route.currentStepIndex = step.index
|
||||
step.waypointIndex = wayIndex
|
||||
step.wayPointLocation = location(waypoint[0], waypoint[1])
|
||||
lastLocation = location
|
||||
bearing = lastLocation.bearingTo(location)
|
||||
routeBearing = lastLocation.bearingTo(location)
|
||||
}
|
||||
}
|
||||
if (nearestDistance == 0F) {
|
||||
@@ -108,6 +121,21 @@ open class RouteModel() {
|
||||
}
|
||||
|
||||
private fun currentLanes(location: Location): List<Lane> {
|
||||
var lanes = emptyList<Lane>()
|
||||
if (route.legs().isNotEmpty()) {
|
||||
route.legs().first().intersection.forEach { it ->
|
||||
if (it.lane.isNotEmpty()) {
|
||||
val distance = lastLocation.distanceTo(location(it.location[0], it.location[1]))
|
||||
val sectionBearing =
|
||||
lastLocation.bearingTo(location(it.location[0], it.location[1]))
|
||||
if (distance < 500 && (routeBearing.absoluteValue - sectionBearing.absoluteValue).absoluteValue < 10) {
|
||||
lanes = it.lane
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return lanes
|
||||
|
||||
var inter = Intersection()
|
||||
var nearestDistance = 100000.0f
|
||||
route.currentStep().intersection.forEach {
|
||||
@@ -117,7 +145,9 @@ open class RouteModel() {
|
||||
if (distance < nearestDistance) {
|
||||
nearestDistance = distance
|
||||
if (distance <= NEXT_STEP_THRESHOLD * 3) {
|
||||
if ((interBearing.absoluteValue - route.currentStep().maneuver.bearingAfter.absoluteValue).absoluteValue < 20) {
|
||||
if (route.routeEngine == RouteEngine.TOMTOM.ordinal
|
||||
|| (interBearing.absoluteValue - route.currentStep().maneuver.bearingAfter.absoluteValue).absoluteValue < 20
|
||||
) {
|
||||
inter = it
|
||||
}
|
||||
}
|
||||
@@ -129,12 +159,13 @@ open class RouteModel() {
|
||||
|
||||
fun updateSpeedLimit(location: Location, viewModel: ViewModel) = runBlocking {
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
if (isNavigating()) {
|
||||
val instruction = currentStep().instruction
|
||||
val levenshtein = Levenshtein()
|
||||
// speed limit
|
||||
val distance = lastSpeedLocation.distanceTo(location)
|
||||
if (distance > 500 || lastSpeedIndex < route.currentStep) {
|
||||
lastSpeedIndex = route.currentStep
|
||||
if (distance > 500 || lastSpeedIndex < route.currentStepIndex) {
|
||||
lastSpeedIndex = route.currentStepIndex
|
||||
val elements = viewModel.getMaxSpeed(location)
|
||||
elements.forEach {
|
||||
if (it.tags.name != null) {
|
||||
@@ -152,34 +183,16 @@ open class RouteModel() {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun currentStep(): StepData {
|
||||
val currentStep = route.currentStep()
|
||||
// Determine if we should display the current or the next maneuver
|
||||
val distanceToNextStep = leftStepDistance()
|
||||
val isNearNextManeuver = distanceToNextStep in 0.0..NEXT_STEP_THRESHOLD
|
||||
val shouldAdvance =
|
||||
isNearNextManeuver && route.currentStep < (route.legs().first().steps.size)
|
||||
|
||||
// Determine the maneuver type and corresponding icon
|
||||
var curManeuverType = if (hasArrived(currentStep.maneuver.type)) {
|
||||
currentStep.maneuver.type
|
||||
} else {
|
||||
Maneuver.TYPE_STRAIGHT
|
||||
}
|
||||
// Get the single, correct maneuver for this state
|
||||
val relevantStep = if (shouldAdvance) {
|
||||
route.nextStep() // This advances the route's state
|
||||
} else {
|
||||
route.currentStep()
|
||||
}
|
||||
val currentStep = route.nextStep(1) // This advances the route's state
|
||||
// Safely get the street name from the maneuver
|
||||
val streetName = relevantStep.name
|
||||
var exitNumber = currentStep.maneuver.exit
|
||||
if (shouldAdvance) {
|
||||
curManeuverType = relevantStep.maneuver.type
|
||||
exitNumber = relevantStep.maneuver.exit
|
||||
}
|
||||
val streetName = currentStep.name
|
||||
val curManeuverType = currentStep.maneuver.type
|
||||
val exitNumber = currentStep.maneuver.exit
|
||||
val maneuverIcon = maneuverIcon(curManeuverType)
|
||||
maneuverType = curManeuverType
|
||||
|
||||
@@ -198,9 +211,8 @@ open class RouteModel() {
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
fun nextStep(): StepData {
|
||||
val step = route.nextStep()
|
||||
val step = route.nextStep(2)
|
||||
val maneuverType = step.maneuver.type
|
||||
val distanceLeft = leftStepDistance()
|
||||
var text = ""
|
||||
@@ -226,14 +238,13 @@ open class RouteModel() {
|
||||
travelLeftDistance(),
|
||||
listOf(Lane(location(0.0, 0.0), valid = false, indications = emptyList())),
|
||||
step.maneuver.exit
|
||||
|
||||
)
|
||||
}
|
||||
|
||||
fun travelLeftTime(): Double {
|
||||
var timeLeft = 0.0
|
||||
// time for next step until end step
|
||||
for (i in route.currentStep + 1..<curLeg.steps.size) {
|
||||
for (i in route.currentStepIndex + 1..<curLeg.steps.size) {
|
||||
val step = curLeg.steps[i]
|
||||
timeLeft += step.duration
|
||||
}
|
||||
@@ -270,14 +281,14 @@ open class RouteModel() {
|
||||
return (leftDistance / 10.0).roundToInt() * 10.0
|
||||
}
|
||||
|
||||
/** Returns the left distance in km. */
|
||||
/** Returns the left distance in m. */
|
||||
fun travelLeftDistance(): Double {
|
||||
var leftDistance = 0.0
|
||||
for (i in route.currentStep + 1..<curLeg.steps.size) {
|
||||
for (i in route.currentStepIndex + 1..<curLeg.steps.size) {
|
||||
val step = route.legs()[0].steps[i]
|
||||
leftDistance += step.distance
|
||||
}
|
||||
leftDistance += leftStepDistance() / 1000
|
||||
leftDistance += leftStepDistance()
|
||||
return leftDistance
|
||||
}
|
||||
|
||||
@@ -343,6 +354,100 @@ open class RouteModel() {
|
||||
|| type == ManeuverType.DestinationLeft.value
|
||||
}
|
||||
|
||||
fun addLanes(stepData: StepData) {
|
||||
stepData.lane.forEach {
|
||||
if (it.indications.isNotEmpty() && it.valid) {
|
||||
Collections.sort<String>(it.indications)
|
||||
var direction = ""
|
||||
it.indications.forEach { it2 ->
|
||||
direction = if (direction.isEmpty()) {
|
||||
it2.trim()
|
||||
} else {
|
||||
"${direction}_${it2.trim()}"
|
||||
}
|
||||
}
|
||||
val laneDirection = addLanes(direction, stepData)
|
||||
println(laneDirection)
|
||||
// TODO:
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun addLanes(direction: String, stepData: StepData): Int {
|
||||
|
||||
val laneDirection = when (direction.lowercase(Locale.getDefault())) {
|
||||
"left_straight" -> {
|
||||
when (stepData.currentManeuverType) {
|
||||
Maneuver.TYPE_TURN_NORMAL_LEFT -> LaneDirection.SHAPE_NORMAL_LEFT
|
||||
Maneuver.TYPE_STRAIGHT -> LaneDirection.SHAPE_STRAIGHT
|
||||
else
|
||||
-> LaneDirection.SHAPE_UNKNOWN
|
||||
}
|
||||
}
|
||||
|
||||
"left", "slight_left" -> {
|
||||
when (stepData.currentManeuverType) {
|
||||
Maneuver.TYPE_TURN_NORMAL_LEFT -> LaneDirection.SHAPE_NORMAL_LEFT
|
||||
else
|
||||
-> LaneDirection.SHAPE_UNKNOWN
|
||||
}
|
||||
}
|
||||
|
||||
"straight" -> {
|
||||
when (stepData.currentManeuverType) {
|
||||
Maneuver.TYPE_STRAIGHT -> LaneDirection.SHAPE_STRAIGHT
|
||||
else
|
||||
-> LaneDirection.SHAPE_UNKNOWN
|
||||
}
|
||||
}
|
||||
|
||||
"right" -> {
|
||||
when (stepData.currentManeuverType) {
|
||||
Maneuver.TYPE_TURN_NORMAL_RIGHT -> LaneDirection.SHAPE_NORMAL_RIGHT
|
||||
else
|
||||
-> LaneDirection.SHAPE_UNKNOWN
|
||||
}
|
||||
}
|
||||
|
||||
"right_straight" -> {
|
||||
when (stepData.currentManeuverType) {
|
||||
Maneuver.TYPE_TURN_NORMAL_RIGHT -> LaneDirection.SHAPE_NORMAL_RIGHT
|
||||
Maneuver.TYPE_STRAIGHT -> LaneDirection.SHAPE_STRAIGHT
|
||||
else
|
||||
-> LaneDirection.SHAPE_UNKNOWN
|
||||
}
|
||||
}
|
||||
|
||||
"left_slight", "slight_left" -> {
|
||||
when (stepData.currentManeuverType) {
|
||||
Maneuver.TYPE_TURN_SLIGHT_LEFT -> LaneDirection.SHAPE_SLIGHT_LEFT
|
||||
else
|
||||
-> LaneDirection.SHAPE_UNKNOWN
|
||||
}
|
||||
}
|
||||
|
||||
"right_slight", "slight_right" -> {
|
||||
when (stepData.currentManeuverType) {
|
||||
Maneuver.TYPE_TURN_SLIGHT_RIGHT -> LaneDirection.SHAPE_NORMAL_RIGHT
|
||||
else
|
||||
-> LaneDirection.SHAPE_UNKNOWN
|
||||
}
|
||||
}
|
||||
|
||||
else -> {
|
||||
LaneDirection.SHAPE_UNKNOWN
|
||||
}
|
||||
}
|
||||
return laneDirection
|
||||
}
|
||||
|
||||
fun createCarIcon(iconCompat: IconCompat): CarIcon {
|
||||
return CarIcon.Builder(iconCompat).build()
|
||||
}
|
||||
|
||||
fun createCarIconx(carContext: Context, @DrawableRes iconRes: Int): CarIcon {
|
||||
return CarIcon.Builder(IconCompat.createWithResource(carContext, iconRes)).build()
|
||||
}
|
||||
|
||||
fun createLaneIcon(context: Context, stepData: StepData): IconCompat {
|
||||
val bitmaps = mutableListOf<Bitmap>()
|
||||
@@ -396,6 +501,7 @@ open class RouteModel() {
|
||||
"${direction}_${it.trim()}"
|
||||
}
|
||||
}
|
||||
direction = direction.lowercase()
|
||||
return when (direction) {
|
||||
"left_straight" -> {
|
||||
when (stepData.currentManeuverType) {
|
||||
@@ -419,8 +525,8 @@ open class RouteModel() {
|
||||
"right" -> if (stepData.currentManeuverType == Maneuver.TYPE_TURN_NORMAL_RIGHT) "${direction}_o" else "${direction}_x"
|
||||
"left" -> if (stepData.currentManeuverType == Maneuver.TYPE_TURN_NORMAL_LEFT) "${direction}_o" else "${direction}_x"
|
||||
"straight" -> if (stepData.currentManeuverType == Maneuver.TYPE_STRAIGHT) "${direction}_o" else "${direction}_x"
|
||||
"right_slight" -> if (stepData.currentManeuverType == Maneuver.TYPE_TURN_SLIGHT_RIGHT) "${direction}_o" else "${direction}_x"
|
||||
"left_slight" -> if (stepData.currentManeuverType == Maneuver.TYPE_TURN_SLIGHT_LEFT) "${direction}_o" else "${direction}_x"
|
||||
"right_slight", "slight_right" -> if (stepData.currentManeuverType == Maneuver.TYPE_TURN_SLIGHT_RIGHT) "${direction}_o" else "${direction}_x"
|
||||
"left_slight", "slight_left" -> if (stepData.currentManeuverType == Maneuver.TYPE_TURN_SLIGHT_LEFT) "${direction}_o" else "${direction}_x"
|
||||
else -> {
|
||||
""
|
||||
}
|
||||
@@ -438,6 +544,7 @@ open class RouteModel() {
|
||||
"right_o" -> R.drawable.right_o
|
||||
"slight_right_x" -> R.drawable.slight_right_x
|
||||
"slight_right_o" -> R.drawable.slight_right_o
|
||||
"slight_left_x" -> R.drawable.left_x
|
||||
"straight_x" -> R.drawable.straight_x
|
||||
"right_o_straight_x" -> R.drawable.right_o_straight_x
|
||||
"right_x_straight_x" -> R.drawable.right_x_straight_x
|
||||
@@ -446,7 +553,7 @@ open class RouteModel() {
|
||||
"left_o_straight_x" -> R.drawable.left_o_straight_x
|
||||
"left_x_straight_o" -> R.drawable.left_x_straight_o
|
||||
else -> {
|
||||
R.drawable.ic_close_white_24dp
|
||||
R.drawable.left_x
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -280,6 +280,7 @@ class ViewModel(private val repository: NavigationRepository) : ViewModel() {
|
||||
fun searchPlaces(search: String, location: Location) {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
val placesJson = repository.searchPlaces(search, location)
|
||||
if (placesJson.isNotEmpty()) {
|
||||
val gson = GsonBuilder().serializeNulls().create()
|
||||
val places = gson.fromJson(placesJson, Search::class.java)
|
||||
val distPlaces = mutableListOf<SearchResult>()
|
||||
@@ -294,6 +295,7 @@ class ViewModel(private val repository: NavigationRepository) : ViewModel() {
|
||||
searchPlaces.postValue(sortedList)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun reverseAddress(location: Location): String {
|
||||
val address = repository.reverseAddress(location)
|
||||
|
||||
@@ -90,6 +90,7 @@ object GeoUtils {
|
||||
}
|
||||
|
||||
fun createLineStringCollection(lineCoordinates: List<List<Double>>): String {
|
||||
// return createPointCollection(lineCoordinates, "Route")
|
||||
val lineString = buildLineString {
|
||||
lineCoordinates.forEach {
|
||||
add(org.maplibre.spatialk.geojson.Point(
|
||||
|
||||
11
common/data/src/main/res/drawable/arrow_back_24px.xml
Normal file
11
common/data/src/main/res/drawable/arrow_back_24px.xml
Normal file
@@ -0,0 +1,11 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="960"
|
||||
android:viewportHeight="960"
|
||||
android:tint="?attr/colorControlNormal"
|
||||
android:autoMirrored="true">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M313,520L537,744L480,800L160,480L480,160L537,216L313,440L800,440L800,520L313,520Z"/>
|
||||
</vector>
|
||||
10
common/data/src/main/res/drawable/menu_24px.xml
Normal file
10
common/data/src/main/res/drawable/menu_24px.xml
Normal file
@@ -0,0 +1,10 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="960"
|
||||
android:viewportHeight="960"
|
||||
android:tint="?attr/colorControlNormal">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M120,720L120,640L840,640L840,720L120,720ZM120,520L120,440L840,440L840,520L120,520ZM120,320L120,240L840,240L840,320L120,320Z"/>
|
||||
</vector>
|
||||
@@ -24,7 +24,7 @@
|
||||
},
|
||||
{
|
||||
"key": "departAt",
|
||||
"value": "2026-01-29T08:43:35.397Z"
|
||||
"value": "2026-02-06T09:09:59.054Z"
|
||||
},
|
||||
{
|
||||
"key": "guidanceVersion",
|
||||
@@ -44,7 +44,7 @@
|
||||
},
|
||||
{
|
||||
"key": "locations",
|
||||
"value": "48.18565,11.57928:48.11830,11.59485"
|
||||
"value": "48.18575,11.57939:48.11654,11.59449"
|
||||
},
|
||||
{
|
||||
"key": "maxAlternatives",
|
||||
@@ -60,11 +60,11 @@
|
||||
},
|
||||
{
|
||||
"key": "sectionType",
|
||||
"value": "lanes"
|
||||
"value": "traffic"
|
||||
},
|
||||
{
|
||||
"key": "sectionType",
|
||||
"value": "traffic"
|
||||
"value": "lanes"
|
||||
},
|
||||
{
|
||||
"key": "traffic",
|
||||
@@ -119,46 +119,28 @@
|
||||
"routes": [
|
||||
{
|
||||
"summary": {
|
||||
"lengthInMeters": 10879,
|
||||
"travelTimeInSeconds": 1170,
|
||||
"trafficDelayInSeconds": 76,
|
||||
"trafficLengthInMeters": 1727,
|
||||
"departureTime": "2026-01-29T09:43:35+01:00",
|
||||
"arrivalTime": "2026-01-29T10:03:05+01:00"
|
||||
"lengthInMeters": 11116,
|
||||
"travelTimeInSeconds": 1148,
|
||||
"trafficDelayInSeconds": 0,
|
||||
"trafficLengthInMeters": 0,
|
||||
"departureTime": "2026-02-06T10:09:59+01:00",
|
||||
"arrivalTime": "2026-02-06T10:29:07+01:00"
|
||||
},
|
||||
"legs": [
|
||||
{
|
||||
"summary": {
|
||||
"lengthInMeters": 10879,
|
||||
"travelTimeInSeconds": 1170,
|
||||
"trafficDelayInSeconds": 76,
|
||||
"trafficLengthInMeters": 1727,
|
||||
"departureTime": "2026-01-29T09:43:35+01:00",
|
||||
"arrivalTime": "2026-01-29T10:03:05+01:00"
|
||||
"lengthInMeters": 11116,
|
||||
"travelTimeInSeconds": 1148,
|
||||
"trafficDelayInSeconds": 0,
|
||||
"trafficLengthInMeters": 0,
|
||||
"departureTime": "2026-02-06T10:09:59+01:00",
|
||||
"arrivalTime": "2026-02-06T10:29:07+01:00"
|
||||
},
|
||||
"encodedPolyline": "sfbeHmqteAEjDQEy@GQ?wDQFkEH{G?M?sA@kB?_FAeC?o@?[@_@\\Ab@CVAz@CJA@?dBGhAGjAExAGlBEvBKdCKTAfCKLAv@ELA|AGnAGt@ClCKjDQpBIf@BXDPDPBF@ZB?S@SAYAUKi@Go@Cc@BgBBs@Bg@JyAJiAXqBDWNs@TaA\\mAFa@Po@`BwF?YL]FSFSl@iB^kALc@L]Ro@f@yAf@{AFQNe@dAoCdBgEx@qBTi@BITe@L[L_@^}@HSXu@pB}El@eAb@e@f@[h@QZCRAL?j@HRFh@Vf@XLJhAn@lAv@TLtAz@r@`@bAn@pAv@f@X~@j@z@h@vBpA`@VHDFFJFf@X`CzApAh@f@L`@Fz@@f@AXEVEVEhA[h@Yn@e@PQFEJKRWV[PW`@w@t@}AHQN]~BiFP]`AoBh@aADGTa@t@aAt@{@PQJKJGFG@Cd@]XSxDmCf@a@n@o@TY\\g@LQHMJSLUP[f@iAPg@b@yAFODMNi@FS|@qCVaAHUHUn@wBHYh@eBpAkEjBiGfAeDj@yADMFQd@sAf@kAJUh@qAf@eAt@sAn@iALSN[p@kAVc@JOLSj@w@z@}@x@q@pAu@p@_@j@Sl@MLCRCb@E`@?^?L@`ABz@?N@~AFdADJ@rAH`DVpCVrAJd@BfHp@zGl@pAJ|ALnGp@jEh@fBJpAFF?P@N@ZCtC]r@GJCFCLCD?TEVEXGhAYzAg@NGv@]`@QJEvB_AXMVK\\Qb@Qn@QJCNAZC^ENA`@FnBb@xEtA^H^JnCl@z@r@`@Pr@TtBlA~C`Bn@\\xAl@PF`@LrAVlCh@bBLl@BlBJdG\\RDjCHn@?pB?xB?R@`@@GxAC^?ZInBIfCAvC?r@@dD@n@@b@@^D`C?TDxAFbBHdB@VHp@RjAJb@NNH`@VlBFf@PzARhBFd@@LRbBFh@\\nC@FNhAb@lEj@tDPpABTBRZlBTdBXjBn@xEBLDTRpAR~@Fb@",
|
||||
"encodedPolyline": "sfbeHarteAE~DQEy@GQ?wDQFkEH{G?M?sA@kB?_FAeC?o@?[@_@\\Ab@CVAz@CJA@?dBGhAGjAExAGlBEvBKdCKTAfCKLAv@ELA|AGnAGt@ClCKjDQpBIf@BXDPDPBF@ZB?S@SAYAUKi@Go@Cc@BgBBs@Bg@JyAJiAXqBDWNs@TaA\\mAFa@Po@`BwF?YL]FSFSl@iB^kALc@L]Ro@f@yAf@{AFQNe@dAoCdBgEx@qBTi@BITe@L[L_@^}@HSXu@pB}El@eAb@e@f@[h@QZCRAL?j@HRFh@Vf@XLJhAn@lAv@TLtAz@r@`@bAn@pAv@f@X~@j@z@h@vBpA`@VHDFFJFf@X`CzApAh@f@L`@Fz@@f@AXEVEVEhA[h@Yn@e@PQFEJKRWV[PW`@w@t@}AHQN]~BiFP]`AoBh@aADGTa@t@aAt@{@PQJKJGFG@Cd@]XSxDmCf@a@n@o@TY\\g@LQHMJSLUP[f@iAPg@b@yAFODMNi@FS|@qCVaAHUHUn@wBHYh@eBpAkEjBiGfAeDj@yADMFQd@sAf@kAJUh@qAf@eAt@sAn@iALSN[p@kAVc@JOLSj@w@z@}@x@q@pAu@p@_@j@Sl@MLCRCb@E`@?^?L@`ABz@?N@~AFdADJ@rAH`DVpCVrAJd@BfHp@zGl@pAJ|ALnGp@jEh@fBJpAFF?P@N@ZCtC]r@GJCFCLCD?TEVEXGhAYzAg@NGv@]`@QJEvB_AXMVK\\Qb@Qn@QJCNAZC^ENA`@FnBb@xEtA^H^JnCl@z@r@`@Pr@TtBlA~C`Bn@\\xAl@PF`@LrAVlCh@bBLl@BlBJdG\\RDjCHn@?pB?xB?R@`@@GxAC^?ZInBIfCAvC?r@@dD@n@@b@@^D`C?TDxAFbBHdB@VHp@RjAJb@NNH`@VlBFf@PzARhBFd@@LRbBFh@\\nC@FNhAb@lEj@tDPpABTBRZlBTdBXjBn@xEBLDTRpAR~@l@jDj@Qv@IrEP",
|
||||
"encodedPolylinePrecision": 5
|
||||
}
|
||||
],
|
||||
"sections": [
|
||||
{
|
||||
"startPointIndex": 83,
|
||||
"endPointIndex": 147,
|
||||
"sectionType": "TRAFFIC",
|
||||
"simpleCategory": "JAM",
|
||||
"effectiveSpeedInKmh": 35,
|
||||
"delayInSeconds": 76,
|
||||
"magnitudeOfDelay": 1,
|
||||
"tec": {
|
||||
"causes": [
|
||||
{
|
||||
"mainCauseCode": 1
|
||||
}
|
||||
],
|
||||
"effectCode": 4
|
||||
},
|
||||
"eventId": "TTL41048054144049000"
|
||||
},
|
||||
{
|
||||
"lanes": [
|
||||
{
|
||||
@@ -349,9 +331,8 @@
|
||||
"travelTimeInSeconds": 0,
|
||||
"point": {
|
||||
"latitude": 48.18554,
|
||||
"longitude": 11.57927
|
||||
"longitude": 11.57937
|
||||
},
|
||||
"exitNumber": "",
|
||||
"pointIndex": 0,
|
||||
"instructionType": "LOCATION_DEPARTURE",
|
||||
"street": "Vogelhartstraße",
|
||||
@@ -362,8 +343,8 @@
|
||||
"message": "Leave from Vogelhartstraße"
|
||||
},
|
||||
{
|
||||
"routeOffsetInMeters": 64,
|
||||
"travelTimeInSeconds": 14,
|
||||
"routeOffsetInMeters": 72,
|
||||
"travelTimeInSeconds": 16,
|
||||
"point": {
|
||||
"latitude": 48.18557,
|
||||
"longitude": 11.57841
|
||||
@@ -380,8 +361,8 @@
|
||||
"message": "Turn right onto Silcherstraße"
|
||||
},
|
||||
{
|
||||
"routeOffsetInMeters": 218,
|
||||
"travelTimeInSeconds": 57,
|
||||
"routeOffsetInMeters": 226,
|
||||
"travelTimeInSeconds": 60,
|
||||
"point": {
|
||||
"latitude": 48.18696,
|
||||
"longitude": 11.57857
|
||||
@@ -398,8 +379,8 @@
|
||||
"message": "Turn right onto Schmalkaldener Straße"
|
||||
},
|
||||
{
|
||||
"routeOffsetInMeters": 650,
|
||||
"travelTimeInSeconds": 131,
|
||||
"routeOffsetInMeters": 658,
|
||||
"travelTimeInSeconds": 134,
|
||||
"point": {
|
||||
"latitude": 48.18686,
|
||||
"longitude": 11.58437
|
||||
@@ -419,8 +400,8 @@
|
||||
"message": "Turn right onto Ingolstädter Straße/B13"
|
||||
},
|
||||
{
|
||||
"routeOffsetInMeters": 1713,
|
||||
"travelTimeInSeconds": 266,
|
||||
"routeOffsetInMeters": 1720,
|
||||
"travelTimeInSeconds": 267,
|
||||
"point": {
|
||||
"latitude": 48.17733,
|
||||
"longitude": 11.58503
|
||||
@@ -441,8 +422,8 @@
|
||||
"combinedMessage": "Turn left onto Schenkendorfstraße/B2R then keep left at Schenkendorfstraße/B2R toward Messe / ICM"
|
||||
},
|
||||
{
|
||||
"routeOffsetInMeters": 2067,
|
||||
"travelTimeInSeconds": 309,
|
||||
"routeOffsetInMeters": 2075,
|
||||
"travelTimeInSeconds": 307,
|
||||
"point": {
|
||||
"latitude": 48.17678,
|
||||
"longitude": 11.58957
|
||||
@@ -464,8 +445,8 @@
|
||||
"combinedMessage": "Keep left at Schenkendorfstraße/B2R toward Messe / ICM then keep left at Schenkendorfstraße/B2R toward Passau"
|
||||
},
|
||||
{
|
||||
"routeOffsetInMeters": 2419,
|
||||
"travelTimeInSeconds": 332,
|
||||
"routeOffsetInMeters": 2426,
|
||||
"travelTimeInSeconds": 329,
|
||||
"point": {
|
||||
"latitude": 48.17518,
|
||||
"longitude": 11.59363
|
||||
@@ -486,8 +467,8 @@
|
||||
"message": "Keep left at Schenkendorfstraße/B2R toward Passau"
|
||||
},
|
||||
{
|
||||
"routeOffsetInMeters": 2774,
|
||||
"travelTimeInSeconds": 357,
|
||||
"routeOffsetInMeters": 2781,
|
||||
"travelTimeInSeconds": 353,
|
||||
"point": {
|
||||
"latitude": 48.17329,
|
||||
"longitude": 11.59747
|
||||
@@ -506,8 +487,8 @@
|
||||
"message": "Follow Isarring/B2R toward München-Ost"
|
||||
},
|
||||
{
|
||||
"routeOffsetInMeters": 8425,
|
||||
"travelTimeInSeconds": 806,
|
||||
"routeOffsetInMeters": 8433,
|
||||
"travelTimeInSeconds": 734,
|
||||
"point": {
|
||||
"latitude": 48.13017,
|
||||
"longitude": 11.61541
|
||||
@@ -524,8 +505,8 @@
|
||||
"message": "Bear right at Ampfingstraße"
|
||||
},
|
||||
{
|
||||
"routeOffsetInMeters": 9487,
|
||||
"travelTimeInSeconds": 953,
|
||||
"routeOffsetInMeters": 9495,
|
||||
"travelTimeInSeconds": 884,
|
||||
"point": {
|
||||
"latitude": 48.12089,
|
||||
"longitude": 11.61285
|
||||
@@ -543,8 +524,8 @@
|
||||
"combinedMessage": "Turn right onto Anzinger Straße then keep straight on at Sankt-Martin-Straße"
|
||||
},
|
||||
{
|
||||
"routeOffsetInMeters": 9983,
|
||||
"travelTimeInSeconds": 1044,
|
||||
"routeOffsetInMeters": 9991,
|
||||
"travelTimeInSeconds": 974,
|
||||
"point": {
|
||||
"latitude": 48.12087,
|
||||
"longitude": 11.60621
|
||||
@@ -561,20 +542,39 @@
|
||||
"message": "Keep straight on at Sankt-Martin-Straße"
|
||||
},
|
||||
{
|
||||
"routeOffsetInMeters": 10879,
|
||||
"travelTimeInSeconds": 1170,
|
||||
"routeOffsetInMeters": 10941,
|
||||
"travelTimeInSeconds": 1103,
|
||||
"point": {
|
||||
"latitude": 48.1183,
|
||||
"longitude": 11.59485
|
||||
"latitude": 48.11811,
|
||||
"longitude": 11.59417
|
||||
},
|
||||
"pointIndex": 335,
|
||||
"instructionType": "TURN",
|
||||
"street": "Hohenwaldeckstraße",
|
||||
"countryCode": "DEU",
|
||||
"junctionType": "REGULAR",
|
||||
"turnAngleInDecimalDegrees": -90,
|
||||
"possibleCombineWithNext": true,
|
||||
"drivingSide": "RIGHT",
|
||||
"maneuver": "TURN_LEFT",
|
||||
"message": "Turn left onto Hohenwaldeckstraße",
|
||||
"combinedMessage": "Turn left onto Hohenwaldeckstraße then you have arrived at Hohenwaldeckstraße. Your destination is on the left"
|
||||
},
|
||||
{
|
||||
"routeOffsetInMeters": 11116,
|
||||
"travelTimeInSeconds": 1148,
|
||||
"point": {
|
||||
"latitude": 48.11655,
|
||||
"longitude": 11.59422
|
||||
},
|
||||
"pointIndex": 338,
|
||||
"instructionType": "LOCATION_ARRIVAL",
|
||||
"street": "Sankt-Martin-Straße",
|
||||
"street": "Hohenwaldeckstraße",
|
||||
"countryCode": "DEU",
|
||||
"possibleCombineWithNext": false,
|
||||
"drivingSide": "RIGHT",
|
||||
"maneuver": "ARRIVE",
|
||||
"message": "You have arrived at Sankt-Martin-Straße"
|
||||
"maneuver": "ARRIVE_LEFT",
|
||||
"message": "You have arrived at Hohenwaldeckstraße. Your destination is on the left"
|
||||
}
|
||||
],
|
||||
"instructionGroups": [
|
||||
@@ -582,19 +582,25 @@
|
||||
"firstInstructionIndex": 0,
|
||||
"lastInstructionIndex": 3,
|
||||
"groupMessage": "Leave from Vogelhartstraße. Take the Ingolstädter Straße/B13",
|
||||
"groupLengthInMeters": 1713
|
||||
"groupLengthInMeters": 1720
|
||||
},
|
||||
{
|
||||
"firstInstructionIndex": 4,
|
||||
"lastInstructionIndex": 7,
|
||||
"groupMessage": "Take the Schenkendorfstraße, Isarring/B2R toward Messe / ICM, Passau, München-Ost",
|
||||
"groupLengthInMeters": 6712
|
||||
"groupLengthInMeters": 6713
|
||||
},
|
||||
{
|
||||
"firstInstructionIndex": 8,
|
||||
"lastInstructionIndex": 11,
|
||||
"groupMessage": "Take the Ampfingstraße, Anzinger Straße. Continue to your destination at Sankt-Martin-Straße",
|
||||
"groupLengthInMeters": 2454
|
||||
"lastInstructionIndex": 10,
|
||||
"groupMessage": "Take the Ampfingstraße, Anzinger Straße, Sankt-Martin-Straße",
|
||||
"groupLengthInMeters": 2508
|
||||
},
|
||||
{
|
||||
"firstInstructionIndex": 11,
|
||||
"lastInstructionIndex": 12,
|
||||
"groupMessage": "Get to your destination at Hohenwaldeckstraße",
|
||||
"groupLengthInMeters": 175
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -27,7 +27,7 @@
|
||||
<string name="reject_action_title" msgid="6730366705938402668">"Ablehnen"</string>
|
||||
<string name="ok_action_title" msgid="7128494973966098611">"OK"</string>
|
||||
<string name="favorites" msgid="522064494016370117">"Favoriten"</string>
|
||||
<string name="display_settings" msgid="5726880972110281095">"Einstellungen für die Anzeige"</string>
|
||||
<string name="display_settings" msgid="5726880972110281095">"Anzeige"</string>
|
||||
<string name="arrived_exclamation_msg" msgid="9132265698563096988">"Angekommen!"</string>
|
||||
<string name="category_title" msgid="4783851267093259949">"Kategorien"</string>
|
||||
<string name="no_places" msgid="7246005909846715898">"Keine Orte"</string>
|
||||
@@ -42,12 +42,13 @@
|
||||
<string name="contacts">Kontakte</string>
|
||||
<string name="route_preview">Route Vorschau</string>
|
||||
<string name="display">Anzeige</string>
|
||||
<string name="navigation_settings">Navigations Einstellungen</string>
|
||||
<string name="navigation_settings">Navigation</string>
|
||||
<string name="fuel_station">Tankstelle</string>
|
||||
<string name="pharmacy">Apotheke</string>
|
||||
<string name="charging_station">Ladestation</string>
|
||||
<string name="speed_camera">Speed camera</string>
|
||||
<string name="use_car_location">Auto GPS verwenden</string>
|
||||
<string name="tomtom">TomTom\t</string>
|
||||
<string name="options">Optionen</string>
|
||||
|
||||
</resources>
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
<string name="off_action_title">Off</string>
|
||||
<string name="use_telephon_settings">Use telephon settings</string>
|
||||
<string name="dark_mode">Dark mode</string>
|
||||
<string name="display_settings">Display settings</string>
|
||||
<string name="display_settings">Display</string>
|
||||
<string name="threed_building">3D building</string>
|
||||
<string name="arrived_exclamation_msg">Arrived!</string>
|
||||
<string name="drive_now">Drive now</string>
|
||||
@@ -24,7 +24,7 @@
|
||||
<string name="recent_Item_deleted">Recent item deleted</string>
|
||||
<string name="route_preview">Route preview</string>
|
||||
<string name="display">Display</string>
|
||||
<string name="navigation_settings">Navigation settings</string>
|
||||
<string name="navigation_settings">Navigation</string>
|
||||
<string name="settings_action_title">Settings</string>
|
||||
<string name="accept_action_title">Accept</string>
|
||||
<string name="reject_action_title">Reject</string>
|
||||
@@ -35,4 +35,5 @@
|
||||
<string name="routing_engine" translatable="false">Routing engine</string>
|
||||
<string name="use_car_location">Use car location</string>
|
||||
<string name="tomtom">TomTom\t</string>
|
||||
<string name="options">Options</string>
|
||||
</resources>
|
||||
@@ -38,6 +38,7 @@ material3WindowSizeClass = "1.4.0"
|
||||
uiGraphics = "1.10.0"
|
||||
window = "1.5.1"
|
||||
foundationLayout = "1.10.0"
|
||||
foundationLayoutVersion = "1.10.1"
|
||||
|
||||
|
||||
[libraries]
|
||||
@@ -79,6 +80,7 @@ androidx-app-automotive = { module = "androidx.car.app:app-automotive", version.
|
||||
androidx-compose-ui-graphics = { group = "androidx.compose.ui", name = "ui-graphics", version.ref = "uiGraphics" }
|
||||
androidx-window = { group = "androidx.window", name = "window", version.ref = "window" }
|
||||
androidx-compose-foundation-layout = { group = "androidx.compose.foundation", name = "foundation-layout", version.ref = "foundationLayout" }
|
||||
androidx-foundation-layout = { group = "androidx.compose.foundation", name = "foundation-layout", version.ref = "foundationLayoutVersion" }
|
||||
|
||||
[plugins]
|
||||
android-application = { id = "com.android.application", version.ref = "agp" }
|
||||
|
||||
Reference in New Issue
Block a user