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"
|
applicationId = "com.kouros.navigation"
|
||||||
minSdk = 33
|
minSdk = 33
|
||||||
targetSdk = 36
|
targetSdk = 36
|
||||||
versionCode = 32
|
versionCode = 36
|
||||||
versionName = "0.1.3.32"
|
versionName = "0.2.0.36"
|
||||||
base.archivesName = "navi-$versionName"
|
base.archivesName = "navi-$versionName"
|
||||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||||
}
|
}
|
||||||
@@ -95,6 +95,12 @@ dependencies {
|
|||||||
implementation(libs.androidx.window)
|
implementation(libs.androidx.window)
|
||||||
implementation(libs.androidx.compose.foundation.layout)
|
implementation(libs.androidx.compose.foundation.layout)
|
||||||
implementation("com.github.ticofab:android-gpx-parser:2.3.1")
|
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)
|
testImplementation(libs.junit)
|
||||||
androidTestImplementation(libs.androidx.junit)
|
androidTestImplementation(libs.androidx.junit)
|
||||||
androidTestImplementation(libs.androidx.espresso.core)
|
androidTestImplementation(libs.androidx.espresso.core)
|
||||||
|
|||||||
@@ -48,8 +48,8 @@ class MockLocation (private var locationManager: LocationManager) {
|
|||||||
this.latitude = latitude
|
this.latitude = latitude
|
||||||
this.longitude = longitude
|
this.longitude = longitude
|
||||||
this.altitude = 0.0
|
this.altitude = 0.0
|
||||||
this.accuracy = 1.0f
|
this.accuracy = 0f
|
||||||
this.speed = 10f
|
this.speed = 0f
|
||||||
this.time = System.currentTimeMillis()
|
this.time = System.currentTimeMillis()
|
||||||
this.elapsedRealtimeNanos = SystemClock.elapsedRealtimeNanos()
|
this.elapsedRealtimeNanos = SystemClock.elapsedRealtimeNanos()
|
||||||
|
|
||||||
@@ -71,8 +71,8 @@ class MockLocation (private var locationManager: LocationManager) {
|
|||||||
this.latitude = latitude
|
this.latitude = latitude
|
||||||
this.longitude = longitude
|
this.longitude = longitude
|
||||||
this.altitude = 0.0
|
this.altitude = 0.0
|
||||||
this.accuracy = 1.0f
|
this.accuracy = 0f
|
||||||
this.speed = 10f
|
this.speed = 0f
|
||||||
this.time = System.currentTimeMillis()
|
this.time = System.currentTimeMillis()
|
||||||
this.elapsedRealtimeNanos = SystemClock.elapsedRealtimeNanos()
|
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.annotation.RequiresPermission
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.size
|
||||||
import androidx.compose.material3.BottomSheetScaffold
|
import androidx.compose.material3.BottomSheetScaffold
|
||||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
|
import androidx.compose.material3.FloatingActionButton
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.SnackbarHost
|
import androidx.compose.material3.SnackbarHost
|
||||||
import androidx.compose.material3.SnackbarHostState
|
import androidx.compose.material3.SnackbarHostState
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
@@ -33,17 +37,23 @@ import androidx.compose.runtime.rememberCoroutineScope
|
|||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
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.dp
|
||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
import androidx.lifecycle.MutableLiveData
|
import androidx.lifecycle.MutableLiveData
|
||||||
import androidx.lifecycle.Observer
|
import androidx.lifecycle.Observer
|
||||||
|
import 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.FusedLocationProviderClient
|
||||||
import com.google.android.gms.location.LocationServices
|
import com.google.android.gms.location.LocationServices
|
||||||
import com.kouros.data.R
|
import com.kouros.data.R
|
||||||
import com.kouros.navigation.MainApplication.Companion.navigationViewModel
|
import com.kouros.navigation.MainApplication.Companion.navigationViewModel
|
||||||
import com.kouros.navigation.data.Constants
|
import com.kouros.navigation.data.Constants
|
||||||
import com.kouros.navigation.data.Constants.DESTINATION_ARRIVAL_DISTANCE
|
import com.kouros.navigation.data.Constants.DESTINATION_ARRIVAL_DISTANCE
|
||||||
import com.kouros.navigation.data.Constants.homeHohenwaldeck
|
|
||||||
import com.kouros.navigation.data.Constants.homeVogelhart
|
import com.kouros.navigation.data.Constants.homeVogelhart
|
||||||
import com.kouros.navigation.data.StepData
|
import com.kouros.navigation.data.StepData
|
||||||
import com.kouros.navigation.model.BaseStyleModel
|
import com.kouros.navigation.model.BaseStyleModel
|
||||||
@@ -78,6 +88,7 @@ class MainActivity : ComponentActivity() {
|
|||||||
val routeModel = RouteModel()
|
val routeModel = RouteModel()
|
||||||
var tilt = 50.0
|
var tilt = 50.0
|
||||||
val useMock = false
|
val useMock = false
|
||||||
|
val type = 1 // simulate 2 test 3 gpx
|
||||||
|
|
||||||
var currentIndex = 0
|
var currentIndex = 0
|
||||||
val stepData: MutableLiveData<StepData> by lazy {
|
val stepData: MutableLiveData<StepData> by lazy {
|
||||||
@@ -92,9 +103,13 @@ class MainActivity : ComponentActivity() {
|
|||||||
routeModel.startNavigation(newRoute, applicationContext)
|
routeModel.startNavigation(newRoute, applicationContext)
|
||||||
routeData.value = routeModel.curRoute.routeGeoJson
|
routeData.value = routeModel.curRoute.routeGeoJson
|
||||||
if (useMock) {
|
if (useMock) {
|
||||||
simulate()
|
when (type) {
|
||||||
//test()
|
1 -> simulate()
|
||||||
///gpx(applicationContext)
|
2 -> test()
|
||||||
|
3 -> gpx(
|
||||||
|
context = applicationContext
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -114,9 +129,6 @@ class MainActivity : ComponentActivity() {
|
|||||||
|
|
||||||
lateinit var baseStyle: BaseStyle.Json
|
lateinit var baseStyle: BaseStyle.Json
|
||||||
|
|
||||||
init {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@RequiresPermission(allOf = [Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.ACCESS_COARSE_LOCATION])
|
@RequiresPermission(allOf = [Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.ACCESS_COARSE_LOCATION])
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
@@ -156,24 +168,28 @@ class MainActivity : ComponentActivity() {
|
|||||||
permissions = permissions,
|
permissions = permissions,
|
||||||
requiredPermissions = listOf(permissions.first()),
|
requiredPermissions = listOf(permissions.first()),
|
||||||
onGranted = {
|
onGranted = {
|
||||||
Content()
|
App()
|
||||||
// auto navigate
|
// auto navigate
|
||||||
if (useMock) {
|
if (useMock) {
|
||||||
navigationViewModel.loadRoute(
|
// navigationViewModel.loadRoute(
|
||||||
applicationContext,
|
// applicationContext,
|
||||||
homeVogelhart,
|
// homeVogelhart,
|
||||||
homeHohenwaldeck,
|
// homeHohenwaldeck,
|
||||||
0F
|
// 0F
|
||||||
)
|
// )
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressLint("AutoboxingStateCreation")
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun Content() {
|
fun StartScreen(
|
||||||
|
navController: NavHostController
|
||||||
|
|
||||||
|
) {
|
||||||
|
|
||||||
val scaffoldState = rememberBottomSheetScaffoldState()
|
val scaffoldState = rememberBottomSheetScaffoldState()
|
||||||
val snackbarHostState = remember { SnackbarHostState() }
|
val snackbarHostState = remember { SnackbarHostState() }
|
||||||
val scope = rememberCoroutineScope()
|
val scope = rememberCoroutineScope()
|
||||||
@@ -226,6 +242,42 @@ class MainActivity : ComponentActivity() {
|
|||||||
baseStyle
|
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()) {
|
if (isNavigating()) {
|
||||||
updateLocation(currentLocation, navigationViewModel)
|
updateLocation(currentLocation, navigationViewModel)
|
||||||
stepData.value = currentStep()
|
stepData.value = currentStep()
|
||||||
if (route.currentStep + 1 <= curLeg.steps.size) {
|
if (route.currentStepIndex + 1 <= curLeg.steps.size) {
|
||||||
nextStepData.value = nextStep()
|
nextStepData.value = nextStep()
|
||||||
}
|
}
|
||||||
if (maneuverType in 39..42
|
if (maneuverType in 39..42
|
||||||
@@ -338,8 +390,8 @@ class MainActivity : ComponentActivity() {
|
|||||||
for ((index, waypoint) in routeModel.curRoute.waypoints.withIndex()) {
|
for ((index, waypoint) in routeModel.curRoute.waypoints.withIndex()) {
|
||||||
if (routeModel.isNavigating()) {
|
if (routeModel.isNavigating()) {
|
||||||
val deviation = 0.0
|
val deviation = 0.0
|
||||||
if (index in 0..routeModel.curRoute.waypoints.size) {
|
if (index in 300..routeModel.curRoute.waypoints.size) {
|
||||||
mock.setMockLocation(waypoint[1] + deviation, waypoint[0])
|
mock.setMockLocation(waypoint[1], waypoint[0])
|
||||||
delay(500L) //
|
delay(500L) //
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -349,13 +401,14 @@ class MainActivity : ComponentActivity() {
|
|||||||
|
|
||||||
fun test() {
|
fun test() {
|
||||||
for ((index, step) in routeModel.curLeg.steps.withIndex()) {
|
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()) {
|
for ((windex, waypoint) in step.maneuver.waypoints.withIndex()) {
|
||||||
routeModel.updateLocation(
|
routeModel.updateLocation(
|
||||||
location(waypoint[0], waypoint[1]),
|
location(waypoint[0], waypoint[1]),
|
||||||
navigationViewModel
|
navigationViewModel
|
||||||
)
|
)
|
||||||
val step = routeModel.currentStep()
|
val step = routeModel.currentStep()
|
||||||
|
println("Step: ${step}")
|
||||||
if (step.leftStepDistance == 70.0) {
|
if (step.leftStepDistance == 70.0) {
|
||||||
println("")
|
println("")
|
||||||
}
|
}
|
||||||
@@ -363,7 +416,7 @@ class MainActivity : ComponentActivity() {
|
|||||||
//nextStepData.value = routeModel.nextStep()
|
//nextStepData.value = routeModel.nextStep()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
//}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -377,7 +430,7 @@ class MainActivity : ComponentActivity() {
|
|||||||
fun gpx(context: Context) {
|
fun gpx(context: Context) {
|
||||||
CoroutineScope(Dispatchers.IO).launch {
|
CoroutineScope(Dispatchers.IO).launch {
|
||||||
val parser = GPXParser()
|
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
|
val parsedGpx: Gpx? = parser.parse(input) // consider using a background thread
|
||||||
parsedGpx?.let {
|
parsedGpx?.let {
|
||||||
val tracks = parsedGpx.tracks
|
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.res.painterResource
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
|
import androidx.core.graphics.drawable.IconCompat
|
||||||
import com.kouros.data.R
|
import com.kouros.data.R
|
||||||
import com.kouros.navigation.data.Constants.NEXT_STEP_THRESHOLD
|
import com.kouros.navigation.data.Constants.NEXT_STEP_THRESHOLD
|
||||||
import com.kouros.navigation.data.StepData
|
import com.kouros.navigation.data.StepData
|
||||||
@@ -30,14 +31,11 @@ fun NavigationSheet(
|
|||||||
stopNavigation: () -> Unit,
|
stopNavigation: () -> Unit,
|
||||||
simulateNavigation: () -> Unit,
|
simulateNavigation: () -> Unit,
|
||||||
) {
|
) {
|
||||||
val distance = step.leftDistance.round(1)
|
val distance = (step.leftDistance / 1000).round(1)
|
||||||
|
|
||||||
step.lane.forEach {
|
if (step.lane.isNotEmpty()) {
|
||||||
if (it.indications.isNotEmpty()) {
|
routeModel.addLanes( step)
|
||||||
routeModel.createLaneIcon(applicationContext, step)
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
Column {
|
Column {
|
||||||
FlowRow(horizontalArrangement = Arrangement.SpaceEvenly) {
|
FlowRow(horizontalArrangement = Arrangement.SpaceEvenly) {
|
||||||
@@ -45,6 +43,7 @@ fun NavigationSheet(
|
|||||||
Spacer(Modifier.size(30.dp))
|
Spacer(Modifier.size(30.dp))
|
||||||
Text("$distance km", fontSize = 22.sp)
|
Text("$distance km", fontSize = 22.sp)
|
||||||
}
|
}
|
||||||
|
|
||||||
HorizontalDivider()
|
HorizontalDivider()
|
||||||
FlowRow(horizontalArrangement = Arrangement.SpaceEvenly) {
|
FlowRow(horizontalArrangement = Arrangement.SpaceEvenly) {
|
||||||
if (routeModel.isNavigating()) {
|
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
|
import androidx.compose.ui.graphics.Color
|
||||||
|
|
||||||
val Purple80 = Color(0xFFD0BCFF)
|
//val Purple80 = Color(0xFFD0BCFF)
|
||||||
val PurpleGrey80 = Color(0xFFCCC2DC)
|
//val PurpleGrey80 = Color(0xFFCCC2DC)
|
||||||
val Pink80 = Color(0xFFEFB8C8)
|
//val Pink80 = Color(0xFFEFB8C8)
|
||||||
|
//
|
||||||
|
//val Purple40 = Color(0xFF6650a4)
|
||||||
|
//val PurpleGrey40 = Color(0xFF625b71)
|
||||||
|
//val Pink40 = Color(0xFF7D5260)
|
||||||
|
|
||||||
val Purple40 = Color(0xFF6650a4)
|
val md_theme_light_primary = Color(0xFF825500)
|
||||||
val PurpleGrey40 = Color(0xFF625b71)
|
val md_theme_light_onPrimary = Color(0xFFFFFFFF)
|
||||||
val Pink40 = Color(0xFF7D5260)
|
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
|
package com.kouros.navigation.ui.theme
|
||||||
|
|
||||||
|
import android.app.Activity
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import androidx.compose.foundation.isSystemInDarkTheme
|
import androidx.compose.foundation.isSystemInDarkTheme
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.MaterialTheme.colorScheme
|
||||||
import androidx.compose.material3.darkColorScheme
|
import androidx.compose.material3.darkColorScheme
|
||||||
import androidx.compose.material3.dynamicDarkColorScheme
|
import androidx.compose.material3.dynamicDarkColorScheme
|
||||||
import androidx.compose.material3.dynamicLightColorScheme
|
import androidx.compose.material3.dynamicLightColorScheme
|
||||||
import androidx.compose.material3.lightColorScheme
|
import androidx.compose.material3.lightColorScheme
|
||||||
import androidx.compose.runtime.Composable
|
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.LocalContext
|
||||||
|
import androidx.compose.ui.platform.LocalView
|
||||||
|
import androidx.core.view.WindowCompat
|
||||||
|
|
||||||
private val DarkColorScheme = darkColorScheme(
|
private val LightColors = lightColorScheme(
|
||||||
primary = Purple80,
|
primary = md_theme_light_primary,
|
||||||
secondary = PurpleGrey80,
|
onPrimary = md_theme_light_onPrimary,
|
||||||
tertiary = Pink80
|
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
|
private val DarkColors = darkColorScheme(
|
||||||
background = Color(0xFFFFFBFE),
|
primary = md_theme_dark_primary,
|
||||||
surface = Color(0xFFFFFBFE),
|
onPrimary = md_theme_dark_onPrimary,
|
||||||
onPrimary = Color.White,
|
primaryContainer = md_theme_dark_primaryContainer,
|
||||||
onSecondary = Color.White,
|
onPrimaryContainer = md_theme_dark_onPrimaryContainer,
|
||||||
onTertiary = Color.White,
|
secondary = md_theme_dark_secondary,
|
||||||
onBackground = Color(0xFF1C1B1F),
|
onSecondary = md_theme_dark_onSecondary,
|
||||||
onSurface = Color(0xFF1C1B1F),
|
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
|
@Composable
|
||||||
fun NavigationTheme(
|
fun NavigationTheme(
|
||||||
darkTheme: Boolean = isSystemInDarkTheme(),
|
useDarkTheme: Boolean = isSystemInDarkTheme(),
|
||||||
// Dynamic color is available on Android 12+
|
|
||||||
dynamicColor: Boolean = true,
|
|
||||||
content: @Composable () -> Unit
|
content: @Composable () -> Unit
|
||||||
) {
|
) {
|
||||||
val colorScheme = when {
|
|
||||||
dynamicColor -> {
|
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context)
|
val colors = run {
|
||||||
|
if (useDarkTheme) dynamicDarkColorScheme(context)
|
||||||
|
else dynamicLightColorScheme(context)
|
||||||
}
|
}
|
||||||
|
|
||||||
darkTheme -> DarkColorScheme
|
val view = LocalView.current
|
||||||
else -> LightColorScheme
|
if (!view.isInEditMode) {
|
||||||
|
SideEffect {
|
||||||
|
val window = (view.context as Activity).window
|
||||||
|
window.statusBarColor = colors.primary.toArgb()
|
||||||
|
WindowCompat.getInsetsController(window, view).isAppearanceLightStatusBars =
|
||||||
|
useDarkTheme
|
||||||
}
|
}
|
||||||
|
|
||||||
MaterialTheme(
|
MaterialTheme(
|
||||||
colorScheme = colorScheme,
|
colorScheme = colorScheme,
|
||||||
typography = Typography,
|
typography = typography,
|
||||||
content = content
|
content = content,
|
||||||
|
shapes = shapes,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
@@ -7,28 +7,35 @@ import androidx.compose.ui.text.font.FontWeight
|
|||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
|
|
||||||
// Set of Material typography styles to start with
|
// 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(
|
bodyLarge = TextStyle(
|
||||||
fontFamily = FontFamily.Default,
|
|
||||||
fontWeight = FontWeight.Normal,
|
fontWeight = FontWeight.Normal,
|
||||||
fontSize = 16.sp,
|
fontSize = 16.sp,
|
||||||
lineHeight = 24.sp,
|
lineHeight = 24.sp,
|
||||||
letterSpacing = 0.5.sp
|
letterSpacing = 0.15.sp
|
||||||
)
|
|
||||||
/* Other default text styles to override
|
|
||||||
titleLarge = TextStyle(
|
|
||||||
fontFamily = FontFamily.Default,
|
|
||||||
fontWeight = FontWeight.Normal,
|
|
||||||
fontSize = 22.sp,
|
|
||||||
lineHeight = 28.sp,
|
|
||||||
letterSpacing = 0.sp
|
|
||||||
),
|
),
|
||||||
labelSmall = TextStyle(
|
bodyMedium = TextStyle(
|
||||||
fontFamily = FontFamily.Default,
|
|
||||||
fontWeight = FontWeight.Medium,
|
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,
|
lineHeight = 16.sp,
|
||||||
letterSpacing = 0.5.sp
|
letterSpacing = 0.5.sp
|
||||||
)
|
)
|
||||||
*/
|
|
||||||
)
|
)
|
||||||
@@ -213,7 +213,7 @@ class SurfaceRenderer(
|
|||||||
DrawNavigationImages(
|
DrawNavigationImages(
|
||||||
paddingValues,
|
paddingValues,
|
||||||
currentSpeed,
|
currentSpeed,
|
||||||
routeModel.maxSpeed,
|
routeModel,
|
||||||
width,
|
width,
|
||||||
height
|
height
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ package com.kouros.navigation.car.map
|
|||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.location.Location
|
import android.location.Location
|
||||||
import androidx.car.app.connection.CarConnection
|
|
||||||
import androidx.compose.foundation.Canvas
|
import androidx.compose.foundation.Canvas
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.PaddingValues
|
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
|
||||||
import com.kouros.navigation.data.Constants.SHOW_THREED_BUILDING
|
import com.kouros.navigation.data.Constants.SHOW_THREED_BUILDING
|
||||||
import com.kouros.navigation.data.NavigationColor
|
import com.kouros.navigation.data.NavigationColor
|
||||||
import com.kouros.navigation.data.ObjectBox
|
|
||||||
import com.kouros.navigation.data.RouteColor
|
import com.kouros.navigation.data.RouteColor
|
||||||
import com.kouros.navigation.data.SpeedColor
|
import com.kouros.navigation.data.SpeedColor
|
||||||
import com.kouros.navigation.data.tomtom.TrafficData
|
import com.kouros.navigation.model.RouteModel
|
||||||
import com.kouros.navigation.utils.NavigationUtils.getBooleanKeyValue
|
import com.kouros.navigation.utils.NavigationUtils.getBooleanKeyValue
|
||||||
import org.maplibre.compose.camera.CameraPosition
|
import org.maplibre.compose.camera.CameraPosition
|
||||||
import org.maplibre.compose.camera.CameraState
|
import org.maplibre.compose.camera.CameraState
|
||||||
@@ -112,6 +110,7 @@ fun MapLibre(
|
|||||||
AmenityLayer(route)
|
AmenityLayer(route)
|
||||||
} else {
|
} else {
|
||||||
RouteLayer(route, traffic!!)
|
RouteLayer(route, traffic!!)
|
||||||
|
//RouteLayerPoint(route )
|
||||||
}
|
}
|
||||||
SpeedCameraLayer(speedCameras)
|
SpeedCameraLayer(speedCameras)
|
||||||
}
|
}
|
||||||
@@ -164,9 +163,9 @@ fun RouteLayer(routeData: String?, trafficData: Map<String, String>) {
|
|||||||
type = exponential(1.2f),
|
type = exponential(1.2f),
|
||||||
input = zoom(),
|
input = zoom(),
|
||||||
5 to const(0.4.dp),
|
5 to const(0.4.dp),
|
||||||
6 to const(0.8.dp),
|
6 to const(0.6.dp),
|
||||||
7 to const(2.0.dp),
|
7 to const(1.8.dp),
|
||||||
20 to const(24.dp),
|
20 to const(20.dp),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
LineLayer(
|
LineLayer(
|
||||||
@@ -178,15 +177,41 @@ fun RouteLayer(routeData: String?, trafficData: Map<String, String>) {
|
|||||||
type = exponential(1.2f),
|
type = exponential(1.2f),
|
||||||
input = zoom(),
|
input = zoom(),
|
||||||
5 to const(0.4.dp),
|
5 to const(0.4.dp),
|
||||||
6 to const(0.7.dp),
|
6 to const(0.5.dp),
|
||||||
7 to const(1.75.dp),
|
7 to const(1.6.dp),
|
||||||
20 to const(22.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> {
|
fun trafficColor(key: String): Expression<ColorValue> {
|
||||||
when (key) {
|
when (key) {
|
||||||
"queuing" -> return const(Color(0xFFD24417))
|
"queuing" -> return const(Color(0xFFD24417))
|
||||||
@@ -269,17 +294,18 @@ fun BuildingLayer(tiles: Source) {
|
|||||||
fun DrawNavigationImages(
|
fun DrawNavigationImages(
|
||||||
padding: PaddingValues,
|
padding: PaddingValues,
|
||||||
speed: Float?,
|
speed: Float?,
|
||||||
maxSpeed: Int,
|
routeModel: RouteModel,
|
||||||
width: Int,
|
width: Int,
|
||||||
height: Int
|
height: Int
|
||||||
) {
|
) {
|
||||||
NavigationImage(padding, width, height)
|
NavigationImage(padding, width, height)
|
||||||
if (speed != null) {
|
if (speed != null) {
|
||||||
CurrentSpeed(width, height, speed, maxSpeed)
|
CurrentSpeed(width, height, speed, routeModel.maxSpeed)
|
||||||
}
|
}
|
||||||
if (speed != null && maxSpeed > 0 && (speed * 3.6) > maxSpeed) {
|
if (speed != null && routeModel.maxSpeed > 0 && (speed * 3.6) > routeModel.maxSpeed) {
|
||||||
MaxSpeed(width, height, maxSpeed)
|
MaxSpeed(width, height, routeModel.maxSpeed)
|
||||||
}
|
}
|
||||||
|
//DebugInfo(width, height, routeModel)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@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
|
@Composable
|
||||||
fun rememberBaseStyle(baseStyle: BaseStyle.Json): BaseStyle.Json {
|
fun rememberBaseStyle(baseStyle: BaseStyle.Json): BaseStyle.Json {
|
||||||
val rememberBaseStyle by remember() {
|
val rememberBaseStyle by remember() {
|
||||||
@@ -436,7 +501,6 @@ fun rememberBaseStyle( baseStyle : BaseStyle.Json): BaseStyle.Json {
|
|||||||
return rememberBaseStyle
|
return rememberBaseStyle
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
fun getPaddingValues(height: Int, viewStyle: ViewStyle): PaddingValues {
|
fun getPaddingValues(height: Int, viewStyle: ViewStyle): PaddingValues {
|
||||||
return when (viewStyle) {
|
return when (viewStyle) {
|
||||||
ViewStyle.VIEW, ViewStyle.PAN_VIEW -> PaddingValues(
|
ViewStyle.VIEW, ViewStyle.PAN_VIEW -> PaddingValues(
|
||||||
|
|||||||
@@ -16,7 +16,6 @@
|
|||||||
package com.kouros.navigation.car.navigation
|
package com.kouros.navigation.car.navigation
|
||||||
|
|
||||||
import android.text.SpannableString
|
import android.text.SpannableString
|
||||||
import android.text.Spanned
|
|
||||||
import androidx.annotation.DrawableRes
|
import androidx.annotation.DrawableRes
|
||||||
import androidx.annotation.StringRes
|
import androidx.annotation.StringRes
|
||||||
import androidx.car.app.AppManager
|
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.AlertCallback
|
||||||
import androidx.car.app.model.CarColor
|
import androidx.car.app.model.CarColor
|
||||||
import androidx.car.app.model.CarIcon
|
import androidx.car.app.model.CarIcon
|
||||||
import androidx.car.app.model.CarIconSpan
|
|
||||||
import androidx.car.app.model.CarText
|
import androidx.car.app.model.CarText
|
||||||
import androidx.car.app.model.DateTimeWithZone
|
import androidx.car.app.model.DateTimeWithZone
|
||||||
import androidx.car.app.model.Distance
|
import androidx.car.app.model.Distance
|
||||||
@@ -42,8 +40,9 @@ import androidx.core.graphics.drawable.IconCompat
|
|||||||
import com.kouros.data.R
|
import com.kouros.data.R
|
||||||
import com.kouros.navigation.data.StepData
|
import com.kouros.navigation.data.StepData
|
||||||
import com.kouros.navigation.model.RouteModel
|
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.Collections
|
||||||
|
import java.util.Locale
|
||||||
import java.util.TimeZone
|
import java.util.TimeZone
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
|
|
||||||
@@ -59,7 +58,8 @@ class RouteCarModel() : RouteModel() {
|
|||||||
val maneuver = Maneuver.Builder(stepData.currentManeuverType)
|
val maneuver = Maneuver.Builder(stepData.currentManeuverType)
|
||||||
.setIcon(createCarIcon(carContext, stepData.icon))
|
.setIcon(createCarIcon(carContext, stepData.icon))
|
||||||
if (stepData.currentManeuverType == TYPE_ROUNDABOUT_ENTER_AND_EXIT_CW
|
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)
|
maneuver.setRoundaboutExitNumber(stepData.exitNumber)
|
||||||
}
|
}
|
||||||
val step =
|
val step =
|
||||||
@@ -84,7 +84,8 @@ class RouteCarModel() : RouteModel() {
|
|||||||
val maneuver = Maneuver.Builder(stepData.currentManeuverType)
|
val maneuver = Maneuver.Builder(stepData.currentManeuverType)
|
||||||
.setIcon(createCarIcon(carContext, stepData.icon))
|
.setIcon(createCarIcon(carContext, stepData.icon))
|
||||||
if (stepData.currentManeuverType == TYPE_ROUNDABOUT_ENTER_AND_EXIT_CW
|
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)
|
maneuver.setRoundaboutExitNumber(stepData.exitNumber)
|
||||||
}
|
}
|
||||||
val step =
|
val step =
|
||||||
@@ -100,13 +101,13 @@ class RouteCarModel() : RouteModel() {
|
|||||||
val timeLeft = travelLeftTime()
|
val timeLeft = travelLeftTime()
|
||||||
val timeToDestinationMillis =
|
val timeToDestinationMillis =
|
||||||
TimeUnit.SECONDS.toMillis(timeLeft.toLong())
|
TimeUnit.SECONDS.toMillis(timeLeft.toLong())
|
||||||
val leftDistance = travelLeftDistance()
|
val leftDistance = travelLeftDistance() / 1000
|
||||||
val displayUnit = if (leftDistance > 1.0) {
|
val displayUnit = if (leftDistance > 1.0) {
|
||||||
Distance.UNIT_KILOMETERS
|
Distance.UNIT_KILOMETERS
|
||||||
} else {
|
} else {
|
||||||
Distance.UNIT_METERS
|
Distance.UNIT_METERS
|
||||||
}
|
}
|
||||||
val arivalTime = DateTimeWithZone.create(
|
val arrivalTime = DateTimeWithZone.create(
|
||||||
arrivalTime(),
|
arrivalTime(),
|
||||||
TimeZone.getTimeZone("Europe/Berlin")
|
TimeZone.getTimeZone("Europe/Berlin")
|
||||||
)
|
)
|
||||||
@@ -115,7 +116,7 @@ class RouteCarModel() : RouteModel() {
|
|||||||
leftDistance,
|
leftDistance,
|
||||||
displayUnit
|
displayUnit
|
||||||
), // Arrival time at the destination with the destination time zone.
|
), // Arrival time at the destination with the destination time zone.
|
||||||
arivalTime
|
arrivalTime
|
||||||
)
|
)
|
||||||
.setRemainingTimeSeconds(
|
.setRemainingTimeSeconds(
|
||||||
TimeUnit.MILLISECONDS.toSeconds(
|
TimeUnit.MILLISECONDS.toSeconds(
|
||||||
@@ -145,62 +146,7 @@ class RouteCarModel() : RouteModel() {
|
|||||||
"${direction}_${it2.trim()}"
|
"${direction}_${it2.trim()}"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
val laneDirection = when (direction) {
|
val laneDirection = addLanes(direction, stepData)
|
||||||
"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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (laneDirection != LaneDirection.SHAPE_UNKNOWN) {
|
if (laneDirection != LaneDirection.SHAPE_UNKNOWN) {
|
||||||
if (!laneImageAdded) {
|
if (!laneImageAdded) {
|
||||||
step.setLanesImage(createCarIcon(createLaneIcon(carContext, stepData)))
|
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(
|
fun createString(
|
||||||
text: String
|
text: String
|
||||||
): SpannableString {
|
): SpannableString {
|
||||||
@@ -243,18 +177,23 @@ class RouteCarModel() : RouteModel() {
|
|||||||
return CarIcon.Builder(IconCompat.createWithResource(carContext, iconRes)).build()
|
return CarIcon.Builder(IconCompat.createWithResource(carContext, iconRes)).build()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun createCarIcon(iconCompat: IconCompat): CarIcon {
|
// fun createCarIcon(iconCompat: IconCompat): CarIcon {
|
||||||
return CarIcon.Builder(iconCompat).build()
|
// return CarIcon.Builder(iconCompat).build()
|
||||||
}
|
// }
|
||||||
|
|
||||||
fun showSpeedCamera(carContext: CarContext, distance: Double, maxSpeed: String) {
|
fun showSpeedCamera(carContext: CarContext, distance: Double, maxSpeed: String) {
|
||||||
carContext.getCarService<AppManager?>(AppManager::class.java)
|
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(
|
fun createAlert(
|
||||||
carContext: CarContext,
|
carContext: CarContext,
|
||||||
distance: Double,
|
|
||||||
maxSpeed: String?,
|
maxSpeed: String?,
|
||||||
icon: CarIcon
|
icon: CarIcon
|
||||||
): Alert {
|
): Alert {
|
||||||
|
|||||||
@@ -489,7 +489,6 @@ class NavigationScreen(
|
|||||||
lastTrafficDate = current
|
lastTrafficDate = current
|
||||||
viewModel.loadTraffic(carContext, location, surfaceRenderer.carOrientation)
|
viewModel.loadTraffic(carContext, location, surfaceRenderer.carOrientation)
|
||||||
}
|
}
|
||||||
//updateTraffic(location)
|
|
||||||
updateSpeedCamera(location)
|
updateSpeedCamera(location)
|
||||||
with(routeModel) {
|
with(routeModel) {
|
||||||
updateLocation(location, viewModel)
|
updateLocation(location, viewModel)
|
||||||
@@ -524,7 +523,7 @@ class NavigationScreen(
|
|||||||
val updatedCameras = mutableListOf<Elements>()
|
val updatedCameras = mutableListOf<Elements>()
|
||||||
speedCameras.forEach {
|
speedCameras.forEach {
|
||||||
val plLocation =
|
val plLocation =
|
||||||
location(longitude = it.lon!!, latitude = it.lat!!)
|
location(longitude = it.lon, latitude = it.lat)
|
||||||
val distance = plLocation.distanceTo(location)
|
val distance = plLocation.distanceTo(location)
|
||||||
it.distance = distance.toDouble()
|
it.distance = distance.toDouble()
|
||||||
updatedCameras.add(it)
|
updatedCameras.add(it)
|
||||||
@@ -533,7 +532,11 @@ class NavigationScreen(
|
|||||||
val camera = sortedList.first()
|
val camera = sortedList.first()
|
||||||
val bearingRoute = surfaceRenderer.lastLocation.bearingTo(location)
|
val bearingRoute = surfaceRenderer.lastLocation.bearingTo(location)
|
||||||
val bearingSpeedCamera = if (camera.tags.direction != null) {
|
val bearingSpeedCamera = if (camera.tags.direction != null) {
|
||||||
|
try {
|
||||||
camera.tags.direction!!.toFloat()
|
camera.tags.direction!!.toFloat()
|
||||||
|
} catch ( e: Exception) {
|
||||||
|
0F
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
location.bearingTo(location(camera.lon, camera.lat)).absoluteValue
|
location.bearingTo(location(camera.lon, camera.lat)).absoluteValue
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ class SearchScreen(
|
|||||||
|
|
||||||
var categories: List<Category> = listOf(
|
var categories: List<Category> = listOf(
|
||||||
Category(id = Constants.RECENT, name = carContext.getString(R.string.recent_destinations)),
|
Category(id = Constants.RECENT, name = carContext.getString(R.string.recent_destinations)),
|
||||||
Category(id = Constants.CONTACTS, name = carContext.getString(R.string.contacts)),
|
// Category(id = Constants.CONTACTS, name = carContext.getString(R.string.contacts)),
|
||||||
Category(id = Constants.CATEGORIES, name = carContext.getString(R.string.category_title)),
|
Category(id = Constants.CATEGORIES, name = carContext.getString(R.string.category_title)),
|
||||||
Category(id = Constants.FAVORITES, name = carContext.getString(R.string.favorites))
|
Category(id = Constants.FAVORITES, name = carContext.getString(R.string.favorites))
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,11 +1,5 @@
|
|||||||
package com.kouros.navigation.car
|
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.data.valhalla.ValhallaRepository
|
||||||
import com.kouros.navigation.model.RouteModel
|
import com.kouros.navigation.model.RouteModel
|
||||||
import com.kouros.navigation.model.ViewModel
|
import com.kouros.navigation.model.ViewModel
|
||||||
@@ -24,16 +18,16 @@ class ViewModelTest {
|
|||||||
@Test
|
@Test
|
||||||
fun routeViewModelTest() {
|
fun routeViewModelTest() {
|
||||||
|
|
||||||
val fromLocation = Location(LocationManager.GPS_PROVIDER)
|
// val fromLocation = Location(LocationManager.GPS_PROVIDER)
|
||||||
fromLocation.isMock = true
|
// fromLocation.isMock = true
|
||||||
fromLocation.latitude = homeLocation.latitude
|
// fromLocation.latitude = homeLocation.latitude
|
||||||
fromLocation.longitude = homeLocation.longitude
|
// fromLocation.longitude = homeLocation.longitude
|
||||||
val toLocation = Location(LocationManager.GPS_PROVIDER)
|
// val toLocation = Location(LocationManager.GPS_PROVIDER)
|
||||||
toLocation.isMock = true
|
// toLocation.isMock = true
|
||||||
toLocation.latitude = home2Location.latitude
|
// toLocation.latitude = home2Location.latitude
|
||||||
toLocation.longitude = home2Location.longitude
|
// toLocation.longitude = home2Location.longitude
|
||||||
|
//
|
||||||
val route = repo.getRoute(fromLocation, toLocation, SearchFilter())
|
// val route = repo.getRoute(fromLocation, toLocation, SearchFilter())
|
||||||
//model.startNavigation(route)
|
//model.startNavigation(route)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ dependencies {
|
|||||||
|
|
||||||
implementation(libs.kotlinx.serialization.json)
|
implementation(libs.kotlinx.serialization.json)
|
||||||
implementation(libs.maplibre.compose)
|
implementation(libs.maplibre.compose)
|
||||||
|
implementation("androidx.compose.material:material-icons-extended:1.7.8")
|
||||||
testImplementation(libs.junit)
|
testImplementation(libs.junit)
|
||||||
androidTestImplementation(libs.androidx.junit)
|
androidTestImplementation(libs.androidx.junit)
|
||||||
androidTestImplementation(libs.androidx.espresso.core)
|
androidTestImplementation(libs.androidx.espresso.core)
|
||||||
|
|||||||
@@ -29,11 +29,11 @@ import java.net.URL
|
|||||||
|
|
||||||
abstract class NavigationRepository {
|
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"
|
private val tomtomUrl = "https://api.tomtom.com/traffic/services/5/incidentDetails"
|
||||||
|
|
||||||
@@ -55,18 +55,19 @@ abstract class NavigationRepository {
|
|||||||
searchFilter: SearchFilter,
|
searchFilter: SearchFilter,
|
||||||
context: Context
|
context: Context
|
||||||
): Double {
|
): Double {
|
||||||
val route = getRoute(context, currentLocation, location, carOrientation, searchFilter)
|
//val route = getRoute(context, currentLocation, location, carOrientation, searchFilter)
|
||||||
val routeModel = RouteModel()
|
//val routeModel = RouteModel()
|
||||||
routeModel.startNavigation(route, context)
|
//routeModel.startNavigation(route, context)
|
||||||
return routeModel.curRoute.summary.distance
|
// return routeModel.curRoute.summary.distance
|
||||||
|
return 0.0
|
||||||
}
|
}
|
||||||
|
|
||||||
fun searchPlaces(search: String, location: Location): String {
|
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}"
|
val viewbox = "&bounded=1&viewbox=${box}"
|
||||||
return fetchUrl(
|
return fetchUrl(
|
||||||
"${nominatimUrl}search?q=$search&format=jsonv2&addressdetails=true$viewbox",
|
"${nominatimUrl}search?q=$search&format=jsonv2&addressdetails=true$viewbox",
|
||||||
false
|
true
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ data class Route(
|
|||||||
|
|
||||||
val routeEngine: Int,
|
val routeEngine: Int,
|
||||||
val routes: List<com.kouros.navigation.data.route.Routes>,
|
val routes: List<com.kouros.navigation.data.route.Routes>,
|
||||||
var currentStep: Int = 0,
|
var currentStepIndex: Int = 0,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
data class Builder(
|
data class Builder(
|
||||||
@@ -51,15 +51,15 @@ data class Route(
|
|||||||
jsonObject["trip"].toString(),
|
jsonObject["trip"].toString(),
|
||||||
ValhallaResponse::class.java
|
ValhallaResponse::class.java
|
||||||
)
|
)
|
||||||
ValhallaRoute().mapJsonToValhalla(routeJson, this)
|
ValhallaRoute().mapToRoute(routeJson, this)
|
||||||
}
|
}
|
||||||
RouteEngine.OSRM.ordinal -> {
|
RouteEngine.OSRM.ordinal -> {
|
||||||
val osrmJson = gson.fromJson(route, OsrmResponse::class.java)
|
val osrmJson = gson.fromJson(route, OsrmResponse::class.java)
|
||||||
OsrmRoute().mapToOsrm(osrmJson, this)
|
OsrmRoute().mapToRoute(osrmJson, this)
|
||||||
}
|
}
|
||||||
else -> {
|
else -> {
|
||||||
val tomtomJson = gson.fromJson(route, TomTomResponse::class.java)
|
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 {
|
fun currentStep(): Step {
|
||||||
|
return if (isRouteValid()) {
|
||||||
return if (routes.isNotEmpty() && legs().isNotEmpty()) {
|
legs().first().steps[currentStepIndex]
|
||||||
legs().first().steps[currentStep]
|
|
||||||
} else {
|
} else {
|
||||||
Step(maneuver = Maneuver(waypoints = emptyList(), location = location(0.0, 0.0)))
|
Step(maneuver = Maneuver(waypoints = emptyList(), location = location(0.0, 0.0)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun nextStep(): Step {
|
fun nextStep(steps : Int): Step {
|
||||||
val nextIndex = currentStep + 1
|
val nextIndex = currentStepIndex + steps
|
||||||
return if (nextIndex < legs().first().steps.size) {
|
return if (isRouteValid() && nextIndex < legs().first().steps.size) {
|
||||||
legs().first().steps[nextIndex]
|
legs().first().steps[nextIndex]
|
||||||
} else {
|
} else {
|
||||||
throw IndexOutOfBoundsException("No next maneuver available.")
|
currentStep()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import com.google.gson.annotations.SerializedName
|
|||||||
|
|
||||||
data class Legs (
|
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("weight" ) var weight : Double = 0.0,
|
||||||
@SerializedName("summary" ) var summary : String = "",
|
@SerializedName("summary" ) var summary : String = "",
|
||||||
@SerializedName("duration" ) var duration : Double = 0.0,
|
@SerializedName("duration" ) var duration : Double = 0.0,
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package com.kouros.navigation.data.osrm
|
package com.kouros.navigation.data.osrm
|
||||||
|
|
||||||
import com.kouros.navigation.data.Route
|
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.Intersection
|
||||||
import com.kouros.navigation.data.route.Lane
|
import com.kouros.navigation.data.route.Lane
|
||||||
import com.kouros.navigation.data.route.Leg
|
import com.kouros.navigation.data.route.Leg
|
||||||
@@ -14,7 +15,7 @@ import com.kouros.navigation.utils.location
|
|||||||
|
|
||||||
class OsrmRoute {
|
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>()
|
val routes = mutableListOf<com.kouros.navigation.data.route.Routes>()
|
||||||
var stepIndex = 0
|
var stepIndex = 0
|
||||||
@@ -61,7 +62,7 @@ class OsrmRoute {
|
|||||||
distance = step.distance / 1000,
|
distance = step.distance / 1000,
|
||||||
duration = step.duration,
|
duration = step.duration,
|
||||||
maneuver = maneuver,
|
maneuver = maneuver,
|
||||||
intersection = intersections
|
//intersection = intersections
|
||||||
)
|
)
|
||||||
steps.add(step)
|
steps.add(step)
|
||||||
stepIndex += 1
|
stepIndex += 1
|
||||||
@@ -80,7 +81,7 @@ class OsrmRoute {
|
|||||||
routes.add(newRoute)
|
routes.add(newRoute)
|
||||||
}
|
}
|
||||||
builder
|
builder
|
||||||
.routeType(1)
|
.routeType(RouteEngine.OSRM.ordinal)
|
||||||
.routes(routes)
|
.routes(routes)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,6 @@ package com.kouros.navigation.data.route
|
|||||||
import java.util.Collections
|
import java.util.Collections
|
||||||
|
|
||||||
data class Intersection(
|
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>(),
|
val lane : List<Lane> = Collections.emptyList<Lane>(),
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package com.kouros.navigation.data.route
|
package com.kouros.navigation.data.route
|
||||||
|
|
||||||
data class Leg(
|
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
|
package com.kouros.navigation.data.tomtom
|
||||||
|
|
||||||
import com.kouros.navigation.data.Route
|
import com.kouros.navigation.data.Route
|
||||||
import com.kouros.navigation.data.osrm.OsrmResponse
|
import com.kouros.navigation.data.RouteEngine
|
||||||
import com.kouros.navigation.data.osrm.OsrmRoute.ManeuverType
|
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.Leg
|
||||||
import com.kouros.navigation.data.route.Maneuver
|
|
||||||
import com.kouros.navigation.data.route.Step
|
import com.kouros.navigation.data.route.Step
|
||||||
import com.kouros.navigation.data.route.Summary
|
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.GeoUtils.decodePolyline
|
||||||
import com.kouros.navigation.utils.location
|
import com.kouros.navigation.utils.location
|
||||||
import com.kouros.navigation.data.route.Maneuver as RouteManeuver
|
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 {
|
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 ->
|
routeJson.routes.forEach { route ->
|
||||||
val legs = mutableListOf<Leg>()
|
|
||||||
val waypoints = mutableListOf<List<Double>>()
|
val waypoints = mutableListOf<List<Double>>()
|
||||||
|
val legs = mutableListOf<Leg>()
|
||||||
|
var stepIndex = 0
|
||||||
var points = listOf<List<Double>>()
|
var points = listOf<List<Double>>()
|
||||||
val summary = Summary(
|
val summary = Summary(
|
||||||
route.summary.travelTimeInSeconds.toDouble(),
|
route.summary.travelTimeInSeconds.toDouble(),
|
||||||
route.summary.lengthInMeters.toDouble() / 1000
|
route.summary.lengthInMeters.toDouble()
|
||||||
)
|
)
|
||||||
route.legs.forEach { leg ->
|
route.legs.forEach { leg ->
|
||||||
points = decodePolyline(leg.encodedPolyline, leg.encodedPolylinePrecision)
|
points = decodePolyline(leg.encodedPolyline, leg.encodedPolylinePrecision)
|
||||||
waypoints.addAll(points)
|
waypoints.addAll(points)
|
||||||
}
|
}
|
||||||
route.guidance.instructions.forEach { instruction ->
|
var stepDistance = 0.0
|
||||||
instruction.exitNumber
|
var stepDuration = 0.0
|
||||||
// val maneuver = RouteManeuver(
|
val allIntersections = mutableListOf<Intersection>()
|
||||||
// // bearingBefore = step.maneuver.bearingBefore,
|
val steps = mutableListOf<Step>()
|
||||||
// //bearingAfter = step.maneuver.bearingAfter,
|
for (index in 0..< route.guidance.instructions.size) {
|
||||||
// type = convertType(instruction.maneuver),
|
val instruction = route.guidance.instructions[index]
|
||||||
// waypoints = points.subList(section.startPointIndex, section.endPointIndex + 1),
|
val nextPointIndex = nextPointIndex(index, route)
|
||||||
// exit = instruction.exitNumber.toInt(),
|
val maneuver = RouteManeuver(
|
||||||
// location = location(
|
bearingBefore = 0,
|
||||||
// instruction.point.longitude, instruction.point.latitude
|
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 ->
|
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 {
|
fun convertType(type: String): Int {
|
||||||
@@ -66,7 +123,73 @@ class TomTomRoute {
|
|||||||
"DEPART" -> {
|
"DEPART" -> {
|
||||||
newType = androidx.car.app.navigation.model.Maneuver.TYPE_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
|
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 androidx.car.app.navigation.model.Maneuver
|
||||||
import com.kouros.data.R
|
import com.kouros.data.R
|
||||||
import com.kouros.navigation.data.Route
|
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.Leg
|
||||||
import com.kouros.navigation.data.route.Maneuver as RouteManeuver
|
import com.kouros.navigation.data.route.Maneuver as RouteManeuver
|
||||||
import com.kouros.navigation.data.route.Step
|
import com.kouros.navigation.data.route.Step
|
||||||
@@ -13,7 +14,7 @@ import com.kouros.navigation.utils.location
|
|||||||
|
|
||||||
class ValhallaRoute {
|
class ValhallaRoute {
|
||||||
|
|
||||||
fun mapJsonToValhalla(routeJson: ValhallaResponse, builder: Route.Builder) {
|
fun mapToRoute(routeJson: ValhallaResponse, builder: Route.Builder) {
|
||||||
val waypoints = decodePolyline(routeJson.legs[0].shape)
|
val waypoints = decodePolyline(routeJson.legs[0].shape)
|
||||||
val summary = Summary(routeJson.summaryValhalla.time, routeJson.summaryValhalla.length)
|
val summary = Summary(routeJson.summaryValhalla.time, routeJson.summaryValhalla.length)
|
||||||
val steps = mutableListOf<Step>()
|
val steps = mutableListOf<Step>()
|
||||||
@@ -38,12 +39,9 @@ class ValhallaRoute {
|
|||||||
stepIndex += 1
|
stepIndex += 1
|
||||||
}
|
}
|
||||||
builder
|
builder
|
||||||
.routeType(1)
|
.routeType(RouteEngine.VALHALLA.ordinal)
|
||||||
// TODO
|
// TODO
|
||||||
.routes(emptyList())
|
.routes(emptyList())
|
||||||
//.summary(summary)
|
|
||||||
//.routeGeoJson(createLineStringCollection(waypoints))
|
|
||||||
//.waypoints(waypoints)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun convertType(maneuver: Maneuvers): Int {
|
fun convertType(maneuver: Maneuvers): Int {
|
||||||
|
|||||||
@@ -6,6 +6,10 @@ import android.graphics.BitmapFactory
|
|||||||
import android.graphics.Canvas
|
import android.graphics.Canvas
|
||||||
import android.graphics.Matrix
|
import android.graphics.Matrix
|
||||||
import android.location.Location
|
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.Maneuver
|
||||||
import androidx.car.app.navigation.model.Step
|
import androidx.car.app.navigation.model.Step
|
||||||
import androidx.core.graphics.createBitmap
|
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.Constants.ROUTING_ENGINE
|
||||||
import com.kouros.navigation.data.Place
|
import com.kouros.navigation.data.Place
|
||||||
import com.kouros.navigation.data.Route
|
import com.kouros.navigation.data.Route
|
||||||
|
import com.kouros.navigation.data.RouteEngine
|
||||||
import com.kouros.navigation.data.StepData
|
import com.kouros.navigation.data.StepData
|
||||||
import com.kouros.navigation.data.route.Intersection
|
import com.kouros.navigation.data.route.Intersection
|
||||||
import com.kouros.navigation.data.route.Lane
|
import com.kouros.navigation.data.route.Lane
|
||||||
@@ -30,6 +35,7 @@ import kotlinx.coroutines.Dispatchers
|
|||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
import java.util.Collections
|
import java.util.Collections
|
||||||
|
import java.util.Locale
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
import kotlin.math.absoluteValue
|
import kotlin.math.absoluteValue
|
||||||
import kotlin.math.roundToInt
|
import kotlin.math.roundToInt
|
||||||
@@ -45,9 +51,10 @@ open class RouteModel() {
|
|||||||
var lastSpeedLocation: Location = location(0.0, 0.0)
|
var lastSpeedLocation: Location = location(0.0, 0.0)
|
||||||
var lastSpeedIndex: Int = 0
|
var lastSpeedIndex: Int = 0
|
||||||
var maxSpeed: Int = 0
|
var maxSpeed: Int = 0
|
||||||
|
|
||||||
var location: Location = location(0.0, 0.0)
|
var location: Location = location(0.0, 0.0)
|
||||||
var lastLocation: 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
|
var currentRouteIndex = 0
|
||||||
val curRoute: Routes
|
val curRoute: Routes
|
||||||
@@ -62,8 +69,14 @@ open class RouteModel() {
|
|||||||
.routeEngine(routeEngine)
|
.routeEngine(routeEngine)
|
||||||
.route(routeString)
|
.route(routeString)
|
||||||
.build()
|
.build()
|
||||||
|
if (hasLegs()) {
|
||||||
navigating = true
|
navigating = true
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun hasLegs(): Boolean {
|
||||||
|
return route.routes.isNotEmpty() && route.routes[0].legs.isNotEmpty()
|
||||||
|
}
|
||||||
|
|
||||||
fun stopNavigation() {
|
fun stopNavigation() {
|
||||||
route = Route.Builder().buildEmpty()
|
route = Route.Builder().buildEmpty()
|
||||||
@@ -76,24 +89,24 @@ open class RouteModel() {
|
|||||||
@OptIn(DelicateCoroutinesApi::class)
|
@OptIn(DelicateCoroutinesApi::class)
|
||||||
fun updateLocation(curLocation: Location, viewModel: ViewModel) {
|
fun updateLocation(curLocation: Location, viewModel: ViewModel) {
|
||||||
location = curLocation
|
location = curLocation
|
||||||
findStep(location)
|
findStep(curLocation)
|
||||||
updateSpeedLimit(location, viewModel)
|
updateSpeedLimit(curLocation, viewModel)
|
||||||
|
lastLocation = location
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun findStep(location: Location) {
|
private fun findStep(location: Location) {
|
||||||
var nearestDistance = 100000.0f
|
var nearestDistance = 100000f
|
||||||
for ((index, step) in curLeg.steps.withIndex()) {
|
for ((index, step) in curLeg.steps.withIndex()) {
|
||||||
if (index >= route.currentStep) {
|
if (index >= route.currentStepIndex) {
|
||||||
for ((wayIndex, waypoint) in step.maneuver.waypoints.withIndex()) {
|
for ((wayIndex, waypoint) in step.maneuver.waypoints.withIndex()) {
|
||||||
if (wayIndex >= step.waypointIndex) {
|
if (wayIndex >= step.waypointIndex) {
|
||||||
val distance = location.distanceTo(location(waypoint[0], waypoint[1]))
|
val distance = location.distanceTo(location(waypoint[0], waypoint[1]))
|
||||||
if (distance < nearestDistance) {
|
if (distance < nearestDistance) {
|
||||||
nearestDistance = distance
|
nearestDistance = distance
|
||||||
route.currentStep = step.index
|
route.currentStepIndex = step.index
|
||||||
step.waypointIndex = wayIndex
|
step.waypointIndex = wayIndex
|
||||||
step.wayPointLocation = location(waypoint[0], waypoint[1])
|
step.wayPointLocation = location(waypoint[0], waypoint[1])
|
||||||
lastLocation = location
|
routeBearing = lastLocation.bearingTo(location)
|
||||||
bearing = lastLocation.bearingTo(location)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (nearestDistance == 0F) {
|
if (nearestDistance == 0F) {
|
||||||
@@ -108,6 +121,21 @@ open class RouteModel() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun currentLanes(location: Location): List<Lane> {
|
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 inter = Intersection()
|
||||||
var nearestDistance = 100000.0f
|
var nearestDistance = 100000.0f
|
||||||
route.currentStep().intersection.forEach {
|
route.currentStep().intersection.forEach {
|
||||||
@@ -117,7 +145,9 @@ open class RouteModel() {
|
|||||||
if (distance < nearestDistance) {
|
if (distance < nearestDistance) {
|
||||||
nearestDistance = distance
|
nearestDistance = distance
|
||||||
if (distance <= NEXT_STEP_THRESHOLD * 3) {
|
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
|
inter = it
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -129,12 +159,13 @@ open class RouteModel() {
|
|||||||
|
|
||||||
fun updateSpeedLimit(location: Location, viewModel: ViewModel) = runBlocking {
|
fun updateSpeedLimit(location: Location, viewModel: ViewModel) = runBlocking {
|
||||||
CoroutineScope(Dispatchers.IO).launch {
|
CoroutineScope(Dispatchers.IO).launch {
|
||||||
|
if (isNavigating()) {
|
||||||
val instruction = currentStep().instruction
|
val instruction = currentStep().instruction
|
||||||
val levenshtein = Levenshtein()
|
val levenshtein = Levenshtein()
|
||||||
// speed limit
|
// speed limit
|
||||||
val distance = lastSpeedLocation.distanceTo(location)
|
val distance = lastSpeedLocation.distanceTo(location)
|
||||||
if (distance > 500 || lastSpeedIndex < route.currentStep) {
|
if (distance > 500 || lastSpeedIndex < route.currentStepIndex) {
|
||||||
lastSpeedIndex = route.currentStep
|
lastSpeedIndex = route.currentStepIndex
|
||||||
val elements = viewModel.getMaxSpeed(location)
|
val elements = viewModel.getMaxSpeed(location)
|
||||||
elements.forEach {
|
elements.forEach {
|
||||||
if (it.tags.name != null) {
|
if (it.tags.name != null) {
|
||||||
@@ -152,34 +183,16 @@ open class RouteModel() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun currentStep(): StepData {
|
fun currentStep(): StepData {
|
||||||
val currentStep = route.currentStep()
|
|
||||||
// Determine if we should display the current or the next maneuver
|
|
||||||
val distanceToNextStep = leftStepDistance()
|
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
|
// Determine the maneuver type and corresponding icon
|
||||||
var curManeuverType = if (hasArrived(currentStep.maneuver.type)) {
|
val currentStep = route.nextStep(1) // This advances the route's state
|
||||||
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()
|
|
||||||
}
|
|
||||||
// Safely get the street name from the maneuver
|
// Safely get the street name from the maneuver
|
||||||
val streetName = relevantStep.name
|
val streetName = currentStep.name
|
||||||
var exitNumber = currentStep.maneuver.exit
|
val curManeuverType = currentStep.maneuver.type
|
||||||
if (shouldAdvance) {
|
val exitNumber = currentStep.maneuver.exit
|
||||||
curManeuverType = relevantStep.maneuver.type
|
|
||||||
exitNumber = relevantStep.maneuver.exit
|
|
||||||
}
|
|
||||||
val maneuverIcon = maneuverIcon(curManeuverType)
|
val maneuverIcon = maneuverIcon(curManeuverType)
|
||||||
maneuverType = curManeuverType
|
maneuverType = curManeuverType
|
||||||
|
|
||||||
@@ -198,9 +211,8 @@ open class RouteModel() {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
fun nextStep(): StepData {
|
fun nextStep(): StepData {
|
||||||
val step = route.nextStep()
|
val step = route.nextStep(2)
|
||||||
val maneuverType = step.maneuver.type
|
val maneuverType = step.maneuver.type
|
||||||
val distanceLeft = leftStepDistance()
|
val distanceLeft = leftStepDistance()
|
||||||
var text = ""
|
var text = ""
|
||||||
@@ -226,14 +238,13 @@ open class RouteModel() {
|
|||||||
travelLeftDistance(),
|
travelLeftDistance(),
|
||||||
listOf(Lane(location(0.0, 0.0), valid = false, indications = emptyList())),
|
listOf(Lane(location(0.0, 0.0), valid = false, indications = emptyList())),
|
||||||
step.maneuver.exit
|
step.maneuver.exit
|
||||||
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun travelLeftTime(): Double {
|
fun travelLeftTime(): Double {
|
||||||
var timeLeft = 0.0
|
var timeLeft = 0.0
|
||||||
// time for next step until end step
|
// 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]
|
val step = curLeg.steps[i]
|
||||||
timeLeft += step.duration
|
timeLeft += step.duration
|
||||||
}
|
}
|
||||||
@@ -270,14 +281,14 @@ open class RouteModel() {
|
|||||||
return (leftDistance / 10.0).roundToInt() * 10.0
|
return (leftDistance / 10.0).roundToInt() * 10.0
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Returns the left distance in km. */
|
/** Returns the left distance in m. */
|
||||||
fun travelLeftDistance(): Double {
|
fun travelLeftDistance(): Double {
|
||||||
var leftDistance = 0.0
|
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]
|
val step = route.legs()[0].steps[i]
|
||||||
leftDistance += step.distance
|
leftDistance += step.distance
|
||||||
}
|
}
|
||||||
leftDistance += leftStepDistance() / 1000
|
leftDistance += leftStepDistance()
|
||||||
return leftDistance
|
return leftDistance
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -343,6 +354,100 @@ open class RouteModel() {
|
|||||||
|| type == ManeuverType.DestinationLeft.value
|
|| 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 {
|
fun createLaneIcon(context: Context, stepData: StepData): IconCompat {
|
||||||
val bitmaps = mutableListOf<Bitmap>()
|
val bitmaps = mutableListOf<Bitmap>()
|
||||||
@@ -396,6 +501,7 @@ open class RouteModel() {
|
|||||||
"${direction}_${it.trim()}"
|
"${direction}_${it.trim()}"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
direction = direction.lowercase()
|
||||||
return when (direction) {
|
return when (direction) {
|
||||||
"left_straight" -> {
|
"left_straight" -> {
|
||||||
when (stepData.currentManeuverType) {
|
when (stepData.currentManeuverType) {
|
||||||
@@ -419,8 +525,8 @@ open class RouteModel() {
|
|||||||
"right" -> if (stepData.currentManeuverType == Maneuver.TYPE_TURN_NORMAL_RIGHT) "${direction}_o" else "${direction}_x"
|
"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"
|
"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"
|
"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"
|
"right_slight", "slight_right" -> 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"
|
"left_slight", "slight_left" -> if (stepData.currentManeuverType == Maneuver.TYPE_TURN_SLIGHT_LEFT) "${direction}_o" else "${direction}_x"
|
||||||
else -> {
|
else -> {
|
||||||
""
|
""
|
||||||
}
|
}
|
||||||
@@ -438,6 +544,7 @@ open class RouteModel() {
|
|||||||
"right_o" -> R.drawable.right_o
|
"right_o" -> R.drawable.right_o
|
||||||
"slight_right_x" -> R.drawable.slight_right_x
|
"slight_right_x" -> R.drawable.slight_right_x
|
||||||
"slight_right_o" -> R.drawable.slight_right_o
|
"slight_right_o" -> R.drawable.slight_right_o
|
||||||
|
"slight_left_x" -> R.drawable.left_x
|
||||||
"straight_x" -> R.drawable.straight_x
|
"straight_x" -> R.drawable.straight_x
|
||||||
"right_o_straight_x" -> R.drawable.right_o_straight_x
|
"right_o_straight_x" -> R.drawable.right_o_straight_x
|
||||||
"right_x_straight_x" -> R.drawable.right_x_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_o_straight_x" -> R.drawable.left_o_straight_x
|
||||||
"left_x_straight_o" -> R.drawable.left_x_straight_o
|
"left_x_straight_o" -> R.drawable.left_x_straight_o
|
||||||
else -> {
|
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) {
|
fun searchPlaces(search: String, location: Location) {
|
||||||
viewModelScope.launch(Dispatchers.IO) {
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
val placesJson = repository.searchPlaces(search, location)
|
val placesJson = repository.searchPlaces(search, location)
|
||||||
|
if (placesJson.isNotEmpty()) {
|
||||||
val gson = GsonBuilder().serializeNulls().create()
|
val gson = GsonBuilder().serializeNulls().create()
|
||||||
val places = gson.fromJson(placesJson, Search::class.java)
|
val places = gson.fromJson(placesJson, Search::class.java)
|
||||||
val distPlaces = mutableListOf<SearchResult>()
|
val distPlaces = mutableListOf<SearchResult>()
|
||||||
@@ -294,6 +295,7 @@ class ViewModel(private val repository: NavigationRepository) : ViewModel() {
|
|||||||
searchPlaces.postValue(sortedList)
|
searchPlaces.postValue(sortedList)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun reverseAddress(location: Location): String {
|
fun reverseAddress(location: Location): String {
|
||||||
val address = repository.reverseAddress(location)
|
val address = repository.reverseAddress(location)
|
||||||
|
|||||||
@@ -90,6 +90,7 @@ object GeoUtils {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun createLineStringCollection(lineCoordinates: List<List<Double>>): String {
|
fun createLineStringCollection(lineCoordinates: List<List<Double>>): String {
|
||||||
|
// return createPointCollection(lineCoordinates, "Route")
|
||||||
val lineString = buildLineString {
|
val lineString = buildLineString {
|
||||||
lineCoordinates.forEach {
|
lineCoordinates.forEach {
|
||||||
add(org.maplibre.spatialk.geojson.Point(
|
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",
|
"key": "departAt",
|
||||||
"value": "2026-01-29T08:43:35.397Z"
|
"value": "2026-02-06T09:09:59.054Z"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"key": "guidanceVersion",
|
"key": "guidanceVersion",
|
||||||
@@ -44,7 +44,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"key": "locations",
|
"key": "locations",
|
||||||
"value": "48.18565,11.57928:48.11830,11.59485"
|
"value": "48.18575,11.57939:48.11654,11.59449"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"key": "maxAlternatives",
|
"key": "maxAlternatives",
|
||||||
@@ -60,11 +60,11 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"key": "sectionType",
|
"key": "sectionType",
|
||||||
"value": "lanes"
|
"value": "traffic"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"key": "sectionType",
|
"key": "sectionType",
|
||||||
"value": "traffic"
|
"value": "lanes"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"key": "traffic",
|
"key": "traffic",
|
||||||
@@ -119,46 +119,28 @@
|
|||||||
"routes": [
|
"routes": [
|
||||||
{
|
{
|
||||||
"summary": {
|
"summary": {
|
||||||
"lengthInMeters": 10879,
|
"lengthInMeters": 11116,
|
||||||
"travelTimeInSeconds": 1170,
|
"travelTimeInSeconds": 1148,
|
||||||
"trafficDelayInSeconds": 76,
|
"trafficDelayInSeconds": 0,
|
||||||
"trafficLengthInMeters": 1727,
|
"trafficLengthInMeters": 0,
|
||||||
"departureTime": "2026-01-29T09:43:35+01:00",
|
"departureTime": "2026-02-06T10:09:59+01:00",
|
||||||
"arrivalTime": "2026-01-29T10:03:05+01:00"
|
"arrivalTime": "2026-02-06T10:29:07+01:00"
|
||||||
},
|
},
|
||||||
"legs": [
|
"legs": [
|
||||||
{
|
{
|
||||||
"summary": {
|
"summary": {
|
||||||
"lengthInMeters": 10879,
|
"lengthInMeters": 11116,
|
||||||
"travelTimeInSeconds": 1170,
|
"travelTimeInSeconds": 1148,
|
||||||
"trafficDelayInSeconds": 76,
|
"trafficDelayInSeconds": 0,
|
||||||
"trafficLengthInMeters": 1727,
|
"trafficLengthInMeters": 0,
|
||||||
"departureTime": "2026-01-29T09:43:35+01:00",
|
"departureTime": "2026-02-06T10:09:59+01:00",
|
||||||
"arrivalTime": "2026-01-29T10:03:05+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
|
"encodedPolylinePrecision": 5
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"sections": [
|
"sections": [
|
||||||
{
|
|
||||||
"startPointIndex": 83,
|
|
||||||
"endPointIndex": 147,
|
|
||||||
"sectionType": "TRAFFIC",
|
|
||||||
"simpleCategory": "JAM",
|
|
||||||
"effectiveSpeedInKmh": 35,
|
|
||||||
"delayInSeconds": 76,
|
|
||||||
"magnitudeOfDelay": 1,
|
|
||||||
"tec": {
|
|
||||||
"causes": [
|
|
||||||
{
|
|
||||||
"mainCauseCode": 1
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"effectCode": 4
|
|
||||||
},
|
|
||||||
"eventId": "TTL41048054144049000"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"lanes": [
|
"lanes": [
|
||||||
{
|
{
|
||||||
@@ -349,9 +331,8 @@
|
|||||||
"travelTimeInSeconds": 0,
|
"travelTimeInSeconds": 0,
|
||||||
"point": {
|
"point": {
|
||||||
"latitude": 48.18554,
|
"latitude": 48.18554,
|
||||||
"longitude": 11.57927
|
"longitude": 11.57937
|
||||||
},
|
},
|
||||||
"exitNumber": "",
|
|
||||||
"pointIndex": 0,
|
"pointIndex": 0,
|
||||||
"instructionType": "LOCATION_DEPARTURE",
|
"instructionType": "LOCATION_DEPARTURE",
|
||||||
"street": "Vogelhartstraße",
|
"street": "Vogelhartstraße",
|
||||||
@@ -362,8 +343,8 @@
|
|||||||
"message": "Leave from Vogelhartstraße"
|
"message": "Leave from Vogelhartstraße"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"routeOffsetInMeters": 64,
|
"routeOffsetInMeters": 72,
|
||||||
"travelTimeInSeconds": 14,
|
"travelTimeInSeconds": 16,
|
||||||
"point": {
|
"point": {
|
||||||
"latitude": 48.18557,
|
"latitude": 48.18557,
|
||||||
"longitude": 11.57841
|
"longitude": 11.57841
|
||||||
@@ -380,8 +361,8 @@
|
|||||||
"message": "Turn right onto Silcherstraße"
|
"message": "Turn right onto Silcherstraße"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"routeOffsetInMeters": 218,
|
"routeOffsetInMeters": 226,
|
||||||
"travelTimeInSeconds": 57,
|
"travelTimeInSeconds": 60,
|
||||||
"point": {
|
"point": {
|
||||||
"latitude": 48.18696,
|
"latitude": 48.18696,
|
||||||
"longitude": 11.57857
|
"longitude": 11.57857
|
||||||
@@ -398,8 +379,8 @@
|
|||||||
"message": "Turn right onto Schmalkaldener Straße"
|
"message": "Turn right onto Schmalkaldener Straße"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"routeOffsetInMeters": 650,
|
"routeOffsetInMeters": 658,
|
||||||
"travelTimeInSeconds": 131,
|
"travelTimeInSeconds": 134,
|
||||||
"point": {
|
"point": {
|
||||||
"latitude": 48.18686,
|
"latitude": 48.18686,
|
||||||
"longitude": 11.58437
|
"longitude": 11.58437
|
||||||
@@ -419,8 +400,8 @@
|
|||||||
"message": "Turn right onto Ingolstädter Straße/B13"
|
"message": "Turn right onto Ingolstädter Straße/B13"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"routeOffsetInMeters": 1713,
|
"routeOffsetInMeters": 1720,
|
||||||
"travelTimeInSeconds": 266,
|
"travelTimeInSeconds": 267,
|
||||||
"point": {
|
"point": {
|
||||||
"latitude": 48.17733,
|
"latitude": 48.17733,
|
||||||
"longitude": 11.58503
|
"longitude": 11.58503
|
||||||
@@ -441,8 +422,8 @@
|
|||||||
"combinedMessage": "Turn left onto Schenkendorfstraße/B2R then keep left at Schenkendorfstraße/B2R toward Messe / ICM"
|
"combinedMessage": "Turn left onto Schenkendorfstraße/B2R then keep left at Schenkendorfstraße/B2R toward Messe / ICM"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"routeOffsetInMeters": 2067,
|
"routeOffsetInMeters": 2075,
|
||||||
"travelTimeInSeconds": 309,
|
"travelTimeInSeconds": 307,
|
||||||
"point": {
|
"point": {
|
||||||
"latitude": 48.17678,
|
"latitude": 48.17678,
|
||||||
"longitude": 11.58957
|
"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"
|
"combinedMessage": "Keep left at Schenkendorfstraße/B2R toward Messe / ICM then keep left at Schenkendorfstraße/B2R toward Passau"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"routeOffsetInMeters": 2419,
|
"routeOffsetInMeters": 2426,
|
||||||
"travelTimeInSeconds": 332,
|
"travelTimeInSeconds": 329,
|
||||||
"point": {
|
"point": {
|
||||||
"latitude": 48.17518,
|
"latitude": 48.17518,
|
||||||
"longitude": 11.59363
|
"longitude": 11.59363
|
||||||
@@ -486,8 +467,8 @@
|
|||||||
"message": "Keep left at Schenkendorfstraße/B2R toward Passau"
|
"message": "Keep left at Schenkendorfstraße/B2R toward Passau"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"routeOffsetInMeters": 2774,
|
"routeOffsetInMeters": 2781,
|
||||||
"travelTimeInSeconds": 357,
|
"travelTimeInSeconds": 353,
|
||||||
"point": {
|
"point": {
|
||||||
"latitude": 48.17329,
|
"latitude": 48.17329,
|
||||||
"longitude": 11.59747
|
"longitude": 11.59747
|
||||||
@@ -506,8 +487,8 @@
|
|||||||
"message": "Follow Isarring/B2R toward München-Ost"
|
"message": "Follow Isarring/B2R toward München-Ost"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"routeOffsetInMeters": 8425,
|
"routeOffsetInMeters": 8433,
|
||||||
"travelTimeInSeconds": 806,
|
"travelTimeInSeconds": 734,
|
||||||
"point": {
|
"point": {
|
||||||
"latitude": 48.13017,
|
"latitude": 48.13017,
|
||||||
"longitude": 11.61541
|
"longitude": 11.61541
|
||||||
@@ -524,8 +505,8 @@
|
|||||||
"message": "Bear right at Ampfingstraße"
|
"message": "Bear right at Ampfingstraße"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"routeOffsetInMeters": 9487,
|
"routeOffsetInMeters": 9495,
|
||||||
"travelTimeInSeconds": 953,
|
"travelTimeInSeconds": 884,
|
||||||
"point": {
|
"point": {
|
||||||
"latitude": 48.12089,
|
"latitude": 48.12089,
|
||||||
"longitude": 11.61285
|
"longitude": 11.61285
|
||||||
@@ -543,8 +524,8 @@
|
|||||||
"combinedMessage": "Turn right onto Anzinger Straße then keep straight on at Sankt-Martin-Straße"
|
"combinedMessage": "Turn right onto Anzinger Straße then keep straight on at Sankt-Martin-Straße"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"routeOffsetInMeters": 9983,
|
"routeOffsetInMeters": 9991,
|
||||||
"travelTimeInSeconds": 1044,
|
"travelTimeInSeconds": 974,
|
||||||
"point": {
|
"point": {
|
||||||
"latitude": 48.12087,
|
"latitude": 48.12087,
|
||||||
"longitude": 11.60621
|
"longitude": 11.60621
|
||||||
@@ -561,20 +542,39 @@
|
|||||||
"message": "Keep straight on at Sankt-Martin-Straße"
|
"message": "Keep straight on at Sankt-Martin-Straße"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"routeOffsetInMeters": 10879,
|
"routeOffsetInMeters": 10941,
|
||||||
"travelTimeInSeconds": 1170,
|
"travelTimeInSeconds": 1103,
|
||||||
"point": {
|
"point": {
|
||||||
"latitude": 48.1183,
|
"latitude": 48.11811,
|
||||||
"longitude": 11.59485
|
"longitude": 11.59417
|
||||||
},
|
},
|
||||||
"pointIndex": 335,
|
"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",
|
"instructionType": "LOCATION_ARRIVAL",
|
||||||
"street": "Sankt-Martin-Straße",
|
"street": "Hohenwaldeckstraße",
|
||||||
"countryCode": "DEU",
|
"countryCode": "DEU",
|
||||||
"possibleCombineWithNext": false,
|
"possibleCombineWithNext": false,
|
||||||
"drivingSide": "RIGHT",
|
"drivingSide": "RIGHT",
|
||||||
"maneuver": "ARRIVE",
|
"maneuver": "ARRIVE_LEFT",
|
||||||
"message": "You have arrived at Sankt-Martin-Straße"
|
"message": "You have arrived at Hohenwaldeckstraße. Your destination is on the left"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"instructionGroups": [
|
"instructionGroups": [
|
||||||
@@ -582,19 +582,25 @@
|
|||||||
"firstInstructionIndex": 0,
|
"firstInstructionIndex": 0,
|
||||||
"lastInstructionIndex": 3,
|
"lastInstructionIndex": 3,
|
||||||
"groupMessage": "Leave from Vogelhartstraße. Take the Ingolstädter Straße/B13",
|
"groupMessage": "Leave from Vogelhartstraße. Take the Ingolstädter Straße/B13",
|
||||||
"groupLengthInMeters": 1713
|
"groupLengthInMeters": 1720
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"firstInstructionIndex": 4,
|
"firstInstructionIndex": 4,
|
||||||
"lastInstructionIndex": 7,
|
"lastInstructionIndex": 7,
|
||||||
"groupMessage": "Take the Schenkendorfstraße, Isarring/B2R toward Messe / ICM, Passau, München-Ost",
|
"groupMessage": "Take the Schenkendorfstraße, Isarring/B2R toward Messe / ICM, Passau, München-Ost",
|
||||||
"groupLengthInMeters": 6712
|
"groupLengthInMeters": 6713
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"firstInstructionIndex": 8,
|
"firstInstructionIndex": 8,
|
||||||
"lastInstructionIndex": 11,
|
"lastInstructionIndex": 10,
|
||||||
"groupMessage": "Take the Ampfingstraße, Anzinger Straße. Continue to your destination at Sankt-Martin-Straße",
|
"groupMessage": "Take the Ampfingstraße, Anzinger Straße, Sankt-Martin-Straße",
|
||||||
"groupLengthInMeters": 2454
|
"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="reject_action_title" msgid="6730366705938402668">"Ablehnen"</string>
|
||||||
<string name="ok_action_title" msgid="7128494973966098611">"OK"</string>
|
<string name="ok_action_title" msgid="7128494973966098611">"OK"</string>
|
||||||
<string name="favorites" msgid="522064494016370117">"Favoriten"</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="arrived_exclamation_msg" msgid="9132265698563096988">"Angekommen!"</string>
|
||||||
<string name="category_title" msgid="4783851267093259949">"Kategorien"</string>
|
<string name="category_title" msgid="4783851267093259949">"Kategorien"</string>
|
||||||
<string name="no_places" msgid="7246005909846715898">"Keine Orte"</string>
|
<string name="no_places" msgid="7246005909846715898">"Keine Orte"</string>
|
||||||
@@ -42,12 +42,13 @@
|
|||||||
<string name="contacts">Kontakte</string>
|
<string name="contacts">Kontakte</string>
|
||||||
<string name="route_preview">Route Vorschau</string>
|
<string name="route_preview">Route Vorschau</string>
|
||||||
<string name="display">Anzeige</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="fuel_station">Tankstelle</string>
|
||||||
<string name="pharmacy">Apotheke</string>
|
<string name="pharmacy">Apotheke</string>
|
||||||
<string name="charging_station">Ladestation</string>
|
<string name="charging_station">Ladestation</string>
|
||||||
<string name="speed_camera">Speed camera</string>
|
<string name="speed_camera">Speed camera</string>
|
||||||
<string name="use_car_location">Auto GPS verwenden</string>
|
<string name="use_car_location">Auto GPS verwenden</string>
|
||||||
<string name="tomtom">TomTom\t</string>
|
<string name="tomtom">TomTom\t</string>
|
||||||
|
<string name="options">Optionen</string>
|
||||||
|
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -10,7 +10,7 @@
|
|||||||
<string name="off_action_title">Off</string>
|
<string name="off_action_title">Off</string>
|
||||||
<string name="use_telephon_settings">Use telephon settings</string>
|
<string name="use_telephon_settings">Use telephon settings</string>
|
||||||
<string name="dark_mode">Dark mode</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="threed_building">3D building</string>
|
||||||
<string name="arrived_exclamation_msg">Arrived!</string>
|
<string name="arrived_exclamation_msg">Arrived!</string>
|
||||||
<string name="drive_now">Drive now</string>
|
<string name="drive_now">Drive now</string>
|
||||||
@@ -24,7 +24,7 @@
|
|||||||
<string name="recent_Item_deleted">Recent item deleted</string>
|
<string name="recent_Item_deleted">Recent item deleted</string>
|
||||||
<string name="route_preview">Route preview</string>
|
<string name="route_preview">Route preview</string>
|
||||||
<string name="display">Display</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="settings_action_title">Settings</string>
|
||||||
<string name="accept_action_title">Accept</string>
|
<string name="accept_action_title">Accept</string>
|
||||||
<string name="reject_action_title">Reject</string>
|
<string name="reject_action_title">Reject</string>
|
||||||
@@ -35,4 +35,5 @@
|
|||||||
<string name="routing_engine" translatable="false">Routing engine</string>
|
<string name="routing_engine" translatable="false">Routing engine</string>
|
||||||
<string name="use_car_location">Use car location</string>
|
<string name="use_car_location">Use car location</string>
|
||||||
<string name="tomtom">TomTom\t</string>
|
<string name="tomtom">TomTom\t</string>
|
||||||
|
<string name="options">Options</string>
|
||||||
</resources>
|
</resources>
|
||||||
@@ -38,6 +38,7 @@ material3WindowSizeClass = "1.4.0"
|
|||||||
uiGraphics = "1.10.0"
|
uiGraphics = "1.10.0"
|
||||||
window = "1.5.1"
|
window = "1.5.1"
|
||||||
foundationLayout = "1.10.0"
|
foundationLayout = "1.10.0"
|
||||||
|
foundationLayoutVersion = "1.10.1"
|
||||||
|
|
||||||
|
|
||||||
[libraries]
|
[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-compose-ui-graphics = { group = "androidx.compose.ui", name = "ui-graphics", version.ref = "uiGraphics" }
|
||||||
androidx-window = { group = "androidx.window", name = "window", version.ref = "window" }
|
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-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]
|
[plugins]
|
||||||
android-application = { id = "com.android.application", version.ref = "agp" }
|
android-application = { id = "com.android.application", version.ref = "agp" }
|
||||||
|
|||||||
Reference in New Issue
Block a user