Files
navigation/CLAUDE.md
T

258 lines
14 KiB
Markdown

# CLAUDE.md
This file provides guidance to Claude 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, Androidx DataStore for local persistence, and Koin for dependency injection.
## Build Commands
```bash
# Build the app (from repository root)
./gradlew :app:assembleDebug
# Build a specific flavor
./gradlew :app:assemblePlayDebug
./gradlew :app:assembleDemoDebug
./gradlew :app:assembleFullDebug
# Run unit tests
./gradlew test
# Run tests for a 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 (see `settings.gradle.kts`):
- **app/** - Main Android app with Jetpack Compose UI for phone (`com.kouros.navigation`)
- **common/data/** - Core data layer with routing logic, repositories, view models, persistence (`com.kouros.data`)
- **common/car/** - Android Auto/Automotive OS UI implementation
- **automotive/** - Placeholder for future native Automotive OS app (no Kotlin sources yet)
Dependencies flow: `app``common:car``common:data`
## Architecture
### Routing Providers (Pluggable System)
The app supports three routing engines that extend the `NavigationRepository` abstract class (`common/data/.../data/NavigationRepository.kt`):
1. **ValhallaRepository** - Valhalla routing engine (ordinal 0)
2. **OsrmRepository** - OSRM routing engine (ordinal 1)
3. **TomTomRepository** - TomTom routing engine (ordinal 2, default)
Selection is driven by `RouteEngine` enum order (see `Data.kt`). `NavigationRepository` also exposes shared HTTP helpers (`fetchUrl`, `searchPlaces`, `reverseAddress`) that use Nominatim for geocoding and `ApplicationConfig`-backed HTTP basic auth for protected endpoints.
Each provider has a corresponding mapper (`OsrmRoute`, `ValhallaRoute`, `TomTomRoute`) that converts provider-specific JSON into the universal `Route` model. The universal model is decomposed into a dedicated `data/route/` package: `Routes`, `Leg`, `Step`, `Maneuver`, `Intersection`, `Lane`, `Summary`.
**Adding a new routing provider:**
1. Create `NewProviderRepository` extending `NavigationRepository` under `common/data/src/main/java/com/kouros/navigation/data/<provider>/`
2. Implement `getRoute()` and `getTraffic()`
3. Create `NewProviderRoute.kt` with a `mapToRoute(response, builder)` function that populates a `Route.Builder`
4. Add a `RouteEngine` enum entry and provider branch in `Route.Builder.route()` (`data/Route.kt`)
5. Update `NavigationUtils.getViewModel()` (`utils/NavigationUtils.kt`) to return a `NavigationViewModel` wired to the new repository
### Data Flow
```
User action (search / select destination)
NavigationViewModel.loadRoute() [LiveData / Flow]
NavigationRepository.getRoute() [selected provider]
*Route.mapToRoute() [convert to universal Route]
RouteModel.startNavigation()
RouteCalculator.findStep() [on each location update]
NavigationState updated → UI observes and renders current step
```
### Key Classes
**Navigation logic** (`common/data/.../model/`):
- `RouteModel.kt` - Core navigation engine; tracks position, manages step progression, owns `NavigationState`
- `RouteCalculator.kt` - Step-finding algorithm: snaps current location to the nearest waypoint, computes leftover distance, handles snap correction and reroute thresholds
- `RouteCarModel.kt` (`common/car/.../navigation/`) - Extends RouteModel with Android Auto-specific formatting
- `NavigationViewModel.kt` - androidx ViewModel exposing route, traffic, places (Nominatim), amenities (Overpass), and fuel prices (Tankerkönig) as LiveData
- `SettingsViewModel.kt` - State holder for DataStore-backed settings (dark mode, 3D, routing engine, avoid preferences, etc.)
- `BaseStyleModel.kt` - Map style state
**Data models** (`common/data/.../data/`):
- `Route.kt` - Universal route wrapper with `Route.Builder` and provider dispatch
- `data/route/*` - Decomposed route components (`Routes`, `Leg`, `Step`, `Maneuver`, `Intersection`, `Lane`, `Summary`)
- `NavigationState.kt` - Immutable navigation state (route, flags, location, bearing, maneuver, destination)
- `Data.kt` - Shared types (`Place`, `StepData`, `SearchFilter`, `Locations`, `ValhallaLocation`) plus `object Constants` and the `RouteEngine`, `DarkMode`, `EngineType`, `ViewStyle`, `NavigationThemeColor` enums
- `ApplicationConfig.kt` - Loads `USER` / `PASSWORD` from `BuildConfig` for HTTP basic auth on protected endpoints
**Persistence** (`common/data/.../data/datastore/` and `common/data/.../repository/`):
- `DataStoreManager.kt` - Single source of truth for preference keys; exposes `Flow<T>` reads and `suspend` writes for each setting
- `SettingsRepository.kt` - Higher-level wrapper over `DataStoreManager`
**Repositories** (`common/data/.../data/`):
- `NavigationRepository.kt` - Abstract base class for routing providers; also handles Nominatim geocoding (search and reverse)
- `osrm/`, `valhalla/`, `tomtom/` - Provider implementations and JSON DTOs
- `fuel/FuelPrices.kt` - Tankerkönig fuel-price client (returns `List<Station>`)
- `overpass/` - Overpass API client for POIs and speed limits
**Android Auto / Automotive** (`common/car/`):
- `NavigationCarAppService.kt` - CarAppService entry point
- `CarSession.kt` (abstract) and concrete `NavigationSession.kt`, `ClusterSession.kt`
- `screen/NavigationScreen.kt` - Main navigation template; `NavigationType` enum drives template selection (VIEW, NAVIGATION, REROUTE, RECENT, ARRIVAL)
- `screen/SearchScreen.kt`, `RoutePreviewScreen.kt`, `PlaceListScreen.kt`, `CategoriesScreen.kt`, `CategoryScreen.kt`, `StopOverScreen.kt`, `RequestPermissionScreen.kt`
- `screen/settings/*` - Per-setting screens (RoutingSettings, NavigationSettings, DisplaySettings, DarkModeSettings, DistanceSettings, AudioSettings, CarSettings, PasswordSettings)
- `screen/observers/*` - Observer pattern that bridges LiveData to car screens. `NavigationObserverManager` orchestrates `RouteObserver`, `TrafficObserver`, `TrafficMessageObserver`, `PlaceSearchObserver`, `CategoryObserver`, `MaxSpeedObserver`, `SpeedCameraObserver`
- Supporting managers: `SurfaceRenderer.kt` (virtual display + map), `DeviceLocationManager.kt`, `CarSensorManager.kt`, `NavigationNotificationManager.kt` + `NavigationNotificationService.kt`, `TextToSpeechManager.kt`, `CustomLifecycleOwner.kt`
- `map/MapView.kt`, `map/LocationPuck.kt` - MapLibre rendering for the car surface
### External APIs
| Service | Purpose | URL |
|---------------|--------------------------|------------------------------------------------------------------------------|
| OSRM | Routing | `https://router.project-osrm.org/route/v1/driving/` |
| Valhalla | Routing | `https://kouros-online.de/valhalla/route?json=` (HTTP basic auth) |
| TomTom | Routing | `https://api.tomtom.com/routing/1/calculateRoute/` |
| TomTom | Traffic incidents | `https://api.tomtom.com/traffic/services/5/incidentDetails` |
| Nominatim | Geocoding (search/reverse) | `https://nominatim.openstreetmap.org/` |
| Overpass | POIs & speed limits | OpenStreetMap Overpass API (DEBUG builds use `https://kouros-online.de/api/interpreter`) |
| Tankerkönig | Fuel prices | `https://creativecommons.tankerkoenig.de/json/list.php?` (API key in `fuel/FuelPrices.kt`) |
In DEBUG builds, `TomTomRepository` and `FuelPrices` can be pointed at a local fixture (see `useLocal` flags) — TomTom can fall back to `R.raw.tomom_routing`, fuel falls back to a local JSON URL.
## Important Constants
Defined in `object Constants` inside `common/data/.../data/Data.kt`:
```kotlin
NEXT_STEP_THRESHOLD = 500.0 // Distance (m) to show next maneuver
DESTINATION_ARRIVAL_DISTANCE = 10.0 // Distance (m) to trigger arrival
MAXIMAL_SNAP_CORRECTION = 50.0 // Max distance (m) to snap to route
MAXIMAL_ROUTE_DEVIATION = 100.0 // Max deviation (m) before reroute
NEAREST_LOCATION_DISTANCE = 10F
SPEED_UPDATE_DISTANCE = 600F
INSTRUCTION_DISTANCE = 50
SPEED_BEARING_DEVIATION = 60
TILT = 60.0 // Map tilt in degrees during navigation
TANKER_KOENIG_DELAY = 300_000 // ms between fuel price refreshes
```
DataStore keys (see `DataStoreManager.PreferencesKeys`):
- `RoutingEngine` (Int) — `0=Valhalla`, `1=OSRM`, `2=TomTom` (default 2)
- `DarkMode` (Int), `Show3D` (Bool), `CarLocation` (Bool)
- `AvoidMotorway`, `AvoidTollway`, `AvoidFerry` (Bool)
- `LastRoute`, `RecentPlaces`, `FuelPrices`, `LastFuelPrices`
- `TomTomApiKey`, `DistanceMode`, `GuidanceAudio`, `Traffic`, `TripSuggestion`, `EngineType`, `AlternativeRoutes`
## Navigation Flow
1. **Route loading** — User searches via Nominatim → selects place → `NavigationViewModel.loadRoute()` calls the selected repository
2. **Route parsing** — Provider JSON → `*Route.mapToRoute()` populates `Route.Builder` → universal `Route``RouteModel.startNavigation()`
3. **Location tracking**`FusedLocationProviderClient` updates → `RouteModel.updateLocation()`
4. **Step calculation**`RouteCalculator.findStep()` snaps location to the nearest waypoint, returns `StepMatch`, and updates the current step index
5. **UI updates**`NavigationState` flows out via LiveData/Compose state; phone and car UIs render the current/next step (instruction, distance, icon, lanes)
6. **Arrival** — When distance < `DESTINATION_ARRIVAL_DISTANCE`, navigation ends
## Testing Navigation
The phone app supports mock locations for testing:
- Set `useMock = true` in `MainActivity`
- Enable "Mock location app" in Android Developer Options
- Choose mode in `model/Simulation.kt` / `model/MockLocation.kt`:
- `type = 1` — Simulate movement along the entire route
- `type = 2` — Test a specific step range
- `type = 3` — Replay a GPX track file
The car module has its own `navigation/Simulation.kt` for car-side simulation.
### Unit tests
- `common/data/src/test/.../model/RouteCalculatorTest.kt`
- `common/data/src/test/.../model/RouteModelTest.kt`
- `common/data/src/test/.../model/IconMapperTest.kt`
- `common/data/src/test/.../model/OverpassTest.kt`
- `common/data/src/test/.../utils/GeoUtilsTest.kt`
- `common/car/src/test/.../screen/NavigationScreenTest.kt`
- `common/car/src/test/.../screen/observers/CategoryObserverTest.kt`, `ObserversTest.kt`
## Persistence
All app preferences are stored via Androidx **DataStore Preferences** (`navigation_settings` data store). There is no Room/ObjectBox/SQL database — the only persisted entities are settings and serialized lists (recent places, last route, last fuel prices) kept as strings.
`DataStoreManager` exposes a `Flow<T>` per setting plus a matching `suspend set*` writer. `SettingsRepository` wraps it for higher-level access. `SettingsViewModel` and `AppViewModel` consume those flows for UI state.
## Compose UI Structure
**Phone app** (`app/src/main/java/com/kouros/navigation/`):
- `ui/MainActivity.kt` - Entry point with permission handling and Navigation Compose host
- `ui/MapView.kt` - MapLibre rendering with camera state management
- `ui/SheetLayout.kt` - Bottom sheet scaffold
- `ui/PermissionScreen.kt`
- `ui/navigation/AppNavGraph.kt` - Compose navigation graph
- `ui/navigation/NavigationScreen.kt`, `NavigationSheet.kt` - Turn-by-turn UI
- `ui/search/SearchScreen.kt`, `SearchSheet.kt`
- `ui/settings/SettingsScreen.kt`, `SettingsRoute.kt`, `DisplayScreen.kt`, `NavigationScreen.kt`, `CarScreen.kt`, `Settings.kt`
- `ui/components/SettingItem.kt`, `SettingSwitch.kt`, `RadioButtonSingleSelection.kt`, `SectionTitle.kt`
- `ui/app/AppViewModel.kt`, `AppViewModelProvider.kt`
- `ui/theme/{Color,Type,Shapes,Theme}.kt`
- `model/Simulation.kt`, `model/MockLocation.kt` - Mock location for testing
- `di/appModule.kt` - Koin module
- `MainApplication.kt` - Application class
**Android Auto** uses CarAppService templates (NavigationTemplate, MessageTemplate, MapWithContentTemplate). `NavigationType` (in `screen/NavigationScreen.kt`) controls which template to render. UI state is synchronized with `NavigationViewModel` through the observers in `screen/observers/`.
## Build Flavors
`app/build.gradle.kts` defines three product flavors under the `store` dimension:
- **play** - applicationIdSuffix `.play`
- **demo** - applicationIdSuffix `.demo`
- **full** - applicationIdSuffix `.full`
Base namespace is `com.kouros.navigation`; current versionName is `0.3.0.109`. Java/Kotlin target is 21 for the app module, 11 for `common:data`. compileSdk/targetSdk = 37, minSdk = 33.
`signing.properties` (root, gitignored) provides the keystore credentials for both debug and release builds. `local.properties` provides `USER` / `PASSWORD` build config fields consumed by `ApplicationConfig`.
## Common Patterns
**Dependency injection (Koin):**
```kotlin
single { OsrmRepository() }
viewModel { NavigationViewModel(get()) }
```
**LiveData observation:**
```kotlin
viewModel.route.observe(this) { routeJson ->
routeModel.startNavigation(routeJson, context)
}
```
**Step-finding algorithm:**
`RouteCalculator` iterates the current step's waypoints, calculates distance from the current location to each, and snaps to the nearest waypoint. Snap is rejected if the closest point exceeds `MAXIMAL_SNAP_CORRECTION`; deviation greater than `MAXIMAL_ROUTE_DEVIATION` triggers a reroute.
**Settings flow:**
```kotlin
val darkMode by viewModel.darkMode.collectAsStateWithLifecycle()
```
## Known Limitations
- Valhalla route mapping is incomplete in places (search for TODO comments in `data/valhalla/ValhallaRoute.kt`)
- Rerouting logic exists but needs more testing
- Speed-limit queries via Overpass API could be optimized for performance
- TomTom and Tankerkönig clients have DEBUG-only `useLocal` shortcuts that hit fixture URLs (`http://192.168.1.37/...` and `R.raw.tomom_routing`) — these need to be off for any real-network testing
- The `automotive` module is wired into Gradle but has no Kotlin source yet