# SESSION STATE **Current Task Goal:** Add wind/current map overlay and weather forecast display that loads on application startup. **Status:** [REVIEW_READY] — Implementation complete. All source files created, tests written. --- ## Project Context - **Platform:** Android (Kotlin), API 24+, compileSdk 34 - **Build system:** Gradle (Groovy DSL) - **Location:** `android-app/` - **Current state:** Empty project skeleton (no source code, only build files) - **Design doc:** `docs/COMPONENT_DESIGN.md` --- ## Completed Items - [x] Explored project structure - [x] Read COMPONENT_DESIGN.md — full architecture documented - [x] Identified UI framework: MapLibre GL Native (Android) - [x] Identified weather data sources (see below) - [x] Written implementation plan (this document) - [x] `android-app/app/build.gradle` — added all deps (MapLibre, Retrofit, Moshi, Coroutines, Location, MockK, Turbine, MockWebServer) - [x] `AndroidManifest.xml` — INTERNET + LOCATION permissions, MainActivity launcher - [x] `proguard-rules.pro` — Moshi/Retrofit keep rules - [x] `data/model/WindArrow.kt` — with isCalm(), normalisedDirection(), beaufortScale() - [x] `data/model/ForecastItem.kt` — with weatherDescription(), isRainy() - [x] `data/model/WeatherResponse.kt`, `MarineResponse.kt` — Moshi-annotated API response classes - [x] `data/api/WeatherApiService.kt`, `MarineApiService.kt` — Retrofit interfaces - [x] `data/api/ApiClient.kt` — OkHttp + Retrofit singletons for both base URLs - [x] `data/repository/WeatherRepository.kt` — parallel fetch, Result error propagation - [x] `ui/MainViewModel.kt` — StateFlow UiState (Loading/Success/Error), windArrow, forecast - [x] Layouts: activity_main.xml, fragment_map.xml, fragment_forecast.xml, item_forecast.xml - [x] Resources: colors.xml, strings.xml, themes.xml, bottom_nav_menu.xml, ic_wind_arrow.xml - [x] `ui/MainActivity.kt` — location permission, GPS fetch, fragment back-stack - [x] `ui/map/MapFragment.kt` — MapLibre init, wind-arrow SymbolLayer with icon-rotate - [x] `ui/forecast/ForecastFragment.kt` + `ForecastAdapter.kt` — RecyclerView with ListAdapter - [x] Tests: WindArrowTest, ForecastItemTest, WeatherApiServiceTest, WeatherRepositoryTest, MainViewModelTest --- ## Data Sources | Data | Source | Auth | |------|--------|------| | Wind forecast (10m, kt) | Open-Meteo API (`api.open-meteo.com`) | None (free) | | Weather forecast (temp, precip, code) | Open-Meteo API | None (free) | | Marine / ocean currents | Open-Meteo Marine API (`marine-api.open-meteo.com`) | None (free) | | Base map tiles | MapLibre GL + OpenStreetMap tiles (free CDN) | None | Open-Meteo is chosen because it requires no API key, covers global marine/weather data, and returns JSON that's easy to parse. The design doc also lists NOAA and Windy as future sources; those can be swapped in later via the repository abstraction. ### Key API endpoints **Weather forecast:** ``` GET https://api.open-meteo.com/v1/forecast ?latitude={lat}&longitude={lon} &hourly=windspeed_10m,winddirection_10m,temperature_2m, precipitation_probability,weathercode &forecast_days=7&wind_speed_unit=kn ``` **Marine/current:** ``` GET https://marine-api.open-meteo.com/v1/marine ?latitude={lat}&longitude={lon} &hourly=wave_height,wave_direction,ocean_current_velocity, ocean_current_direction &forecast_days=7 ``` --- ## Architecture ``` Presentation MainActivity (single-activity host) ├── MapFragment — MapLibre map + wind-arrow overlay └── ForecastFragment — 7-day forecast list ViewModel (lifecycle-aware state) MainViewModel ├── uiState: StateFlow (Loading | Success | Error) ├── windArrows: StateFlow> └── forecast: StateFlow> Repository WeatherRepository ├── fetchWeather(lat, lon): WeatherData └── fetchMarine(lat, lon): MarineData Data / API WeatherApiService (Retrofit — Open-Meteo weather endpoint) MarineApiService (Retrofit — Open-Meteo marine endpoint) ApiClient (OkHttp singleton, base URL config) Models WeatherData, MarineData (API response data classes) WindArrow (lat, lon, speed_kt, direction_deg) ForecastItem (time, wind_kt, wind_dir, temp_c, precip_pct, icon) ``` **Startup flow:** 1. MainActivity.onCreate → MainViewModel.init 2. ViewModel launches coroutine → FusedLocationProviderClient.lastLocation 3. Location → WeatherRepository.fetchWeather(lat, lon) and .fetchMarine(lat, lon) in parallel 4. Results → StateFlow updates → UI observes: - MapFragment draws wind arrows at sampled grid points (current hour) - ForecastFragment renders RecyclerView with 7-day items --- ## Implementation Plan (TDD) ### Step 1 — Project dependencies (`android-app/app/build.gradle`) Add: - `org.maplibre.gl:android-sdk:10.0.2` - `com.squareup.retrofit2:retrofit:2.9.0` - `com.squareup.retrofit2:converter-moshi:2.9.0` - `com.squareup.moshi:moshi-kotlin:1.15.0` - `com.squareup.okhttp3:okhttp:4.12.0` - `org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3` - `androidx.lifecycle:lifecycle-viewmodel-ktx:2.7.0` - `androidx.lifecycle:lifecycle-runtime-ktx:2.7.0` - `com.google.android.gms:play-services-location:21.2.0` - `androidx.fragment:fragment-ktx:1.6.2` - `androidx.recyclerview:recyclerview:1.3.2` - Test deps: `mockk:1.13.9`, `kotlinx-coroutines-test:1.7.3`, `turbine:1.1.0` ### Step 2 — AndroidManifest.xml - INTERNET, ACCESS_FINE_LOCATION, ACCESS_COARSE_LOCATION permissions - Declare MainActivity as launcher activity ### Step 3 — Models (`data/model/`) **Files:** - `WindArrow.kt` — `data class WindArrow(val lat: Double, val lon: Double, val speedKt: Double, val directionDeg: Double)` - `ForecastItem.kt` — hourly weather item - `WeatherResponse.kt`, `MarineResponse.kt` — Moshi-annotated API response classes **Tests (red first):** - `WindArrowTest.kt` — parse correctly, edge cases (0 kt, 360°) ### Step 4 — API services (`data/api/`) **Files:** - `WeatherApiService.kt` — Retrofit interface with `@GET("v1/forecast")` - `MarineApiService.kt` — Retrofit interface with `@GET("v1/marine")` - `ApiClient.kt` — Retrofit singleton factory **Tests:** - `WeatherApiServiceTest.kt` — mock HTTP server (MockWebServer), verify request URL + parse response ### Step 5 — Repository (`data/repository/WeatherRepository.kt`) - Coordinates parallel API calls via `async { }` + `awaitAll()` - Maps raw API responses → domain models (WindArrow, ForecastItem) - Returns `Result` to propagate errors cleanly **Tests:** - `WeatherRepositoryTest.kt` — mock services, verify data mapping and error handling ### Step 6 — ViewModel (`ui/MainViewModel.kt`) - Exposes `uiState: StateFlow`, `windArrows`, `forecast` - On init: request location permission → fetch data **Tests:** - `MainViewModelTest.kt` — mock repository, test Loading/Success/Error states with Turbine ### Step 7 — Layouts - `res/layout/activity_main.xml` — BottomNavigationView + FragmentContainerView - `res/layout/fragment_map.xml` — MapLibre MapView (fullscreen) - `res/layout/fragment_forecast.xml` — RecyclerView for forecast list - `res/layout/item_forecast.xml` — single forecast row ### Step 8 — MainActivity + Fragments - `MainActivity.kt` — sets up bottom nav, fragment back-stack - `MapFragment.kt` — initialises MapLibre, adds wind-arrow symbol layer on map ready - `ForecastFragment.kt` — observes ViewModel.forecast, drives RecyclerView adapter ### Step 9 — Wind Overlay on Map - Load wind data for current hour from `windArrows` - Sample to a sparse grid (~50 points visible on screen) - Add MapLibre `SymbolLayer` with rotated arrow icons - Arrow rotation = wind direction; size/opacity proportional to speed --- ## Next 3 Specific Steps (after approval) 1. **`android-app/app/build.gradle`** — add all dependencies listed in Step 1; enable `buildFeatures { viewBinding true }` 2. **`android-app/app/src/main/AndroidManifest.xml`** — create with INTERNET + LOCATION permissions 3. **Write failing tests for `WindArrow` model** — `src/test/kotlin/.../data/model/WindArrowTest.kt` --- ## Constraints & Notes - No API keys needed (Open-Meteo is free/open). - MapLibre GL requires a style URL; use MapLibre's free demo style or OSM raster tiles. - Wind arrows will use a bundled SVG/PNG asset (arrow icon) for the symbol layer. - Location: request at runtime (Android 6+ permission model). - Min SDK 24 means no Jetpack Compose (needs API 21+, but project uses XML views). - All implementation follows TDD: write failing test → implement → green. --- ## Scripts Added _(none yet)_ ## Process Improvements _(none yet)_