# 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//` 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` 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`) - `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` 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