# Roadmap Each milestone ends with `tool/verify.ps1` green and one commit. A milestone is not done until its acceptance criteria hold. Legend: โœ… done ยท ๐Ÿ”จ in progress ยท โ›” blocked on credentials from the project owner --- ## M0 โ€” Toolchain and repository hygiene โœ… Flutter 3.47.3 stable, Android SDK (platform-tools, platform 36, build-tools 36.0.0), JDK 21, environment variables. Commit the removal of the old Xamarin skeleton. **Accepts when:** `flutter doctor -v` reports no blocking Android toolchain error and `git status` is clean. **Done.** `flutter doctor -v` reports "No issues found!". Installed: Flutter 3.47.3 at `C:\src\flutter` (telemetry disabled), Android SDK at `C:\Android\Sdk` with platform 36, build-tools 36.0.0, platform-tools and all licences accepted, and `JAVA_HOME` pointing at the JDK 21 a previous Visual Studio install had already left on the machine. ## M1 โ€” Scaffold, Italian l10n, region config, CI โœ… `flutter create` with applicationId `it.nuvolari.app`; `flutter_localizations` + `intl` with `app_it.arb` as template and no hardcoded UI strings; `RegionConfig` loaded from `assets/regions/piemonte.json` with parsing tests; `.gitignore`, `env.example.json`, `tool/verify.ps1`, `.gitea/workflows/ci.yml`. **Accepts when:** `flutter analyze` reports 0 issues, `flutter test` passes, `flutter build appbundle --debug` succeeds. **Done.** All four stages green; the AAB carries applicationId `it.nuvolari.app`, minSdk 24, targetSdk 36. 50 tests, including assertions against the shipped Piemonte asset and a captured copy of the live ARPA CAP feed. > The Gitea instance may have no Actions runner. The workflow file is written to be > GitHub-Actions compatible, but `tool/verify.ps1` is the verification that must pass. ## M2 โ€” Map, attribution, Sources screen โœ… MapLibre with the style from `MAP_STYLE_URL`, falling back to a local minimal style. Permanent OSM/ODbL attribution. Sources / Licenses / Disclaimer screen covering Radar-DPC (CC BY-SA), MET Norway, ARPA, OSM, with the explicit "not an official app" disclaimer. **Accepts when:** the map opens centred on Piedmont, attribution is visible at all times, and the Sources screen is reachable and complete. **Done**, with one gap. The fallback style is generated from the region bounding box rather than loaded from an asset, so it needs no third-party boundary dataset and no network. The attribution bar lists only the sources actually rendered โ€” crediting OpenStreetMap while showing the fallback would be a false attribution โ€” and sits below the map rather than floating over it so no map control can occlude it. **Verified on an emulator** (`nuvolari`, API 36.1, x86_64, GPU host). Running it caught two things the unit tests could not: the configured initial zoom put the viewport entirely inside the region so the map read as a blank expanse, and the attribution bar sat behind the system gesture pill. Both fixed โ€” the camera now fits the region bounds at runtime, which works on any screen size, and the bar is inside a SafeArea. ## M3 โ€” Animation and timeline ๐Ÿ”จ `RadarSource` with `MockRadarSource` (synthetic frames in assets) and `DpcRadarSource`. Timeline scrubber, play/pause, adjacent-frame prefetch, `FrameCache` LRU, animation suspended in background, graceful degradation with a data-age banner. **Accepts when:** animation runs smoothly in demo mode **with the network off**, and prefetch, LRU eviction and the fallback paths are covered by tests. ## M4 โ€” Forecast `MetNoForecastSource` with the mandatory identifying User-Agent, honouring `Expires` and `If-Modified-Since`. `IconIt2Source` as a flagged fallback. Hourly and daily views. **Accepts when:** a forecast renders for a Piedmont location, caching respects the response headers, and fixture-based tests pass. ## M5 โ€” UMP consent and AdMob โ›” UMP consent form before any ad request; non-personalised ads when consent is declined. Anchored adaptive banner โ€” never over the map, never an interstitial during animation. **Test ad unit IDs only** until real ones are supplied. **Blocked on:** AdMob App ID and ad unit IDs. ## M6 โ€” Rain notifications and alerts โ›” `ArpaCapAlertSource` reading the CAP feed through the backend; zones `Piem-A`โ€ฆ`Piem-M` with levels shown verbatim and a link to the official bulletin. Rain notifications via FCM topics per geographic cell, subscribed from the device. **Blocked on:** Firebase project and `google-services.json`. ## M7 โ€” Android home widget Glance widget showing the latest frame and the next rain, refreshed by WorkManager. **Accepts when:** the widget renders a real frame on the home screen and updates. ## M8 โ€” Backend worker โ›” `dpc_client` (with the `origin` header), `crop` (bbox + reproject to EPSG:3857), `palette` (dBZ colormap, legend exported into the manifest), `render` (RGBA PNG, transparent below threshold), `manifest`, and `publisher/` with `LocalPublisher` and `S3Publisher`. WebSocket trigger with cron fallback. pysteps nowcast present but disabled behind a flag. **Accepts when:** `python -m pytest` passes and one full run produces PNGs plus a `manifest.json` that the app consumes from a local server. **Blocked on (publishing only):** VPS / object storage endpoint and credentials. Development proceeds against `LocalPublisher`. ## M9 โ€” Play Store release preparation โ›” Signing config reading `key.properties`, release AAB, target API 36, privacy policy draft, store listing copy, Data safety draft, prominent disclosure for location. **Blocked on:** upload keystore and Play Console account. --- ## Pending inputs from the project owner | Needed for | Item | |---|---| | M2 (quality) | MapTiler API key โ€” until then the local fallback style is used | | M5 | AdMob App ID and ad unit IDs | | M6 | Firebase project and `google-services.json` | | M8 | VPS / object storage endpoint and credentials | | M9 | Play Console account and upload keystore | --- ## Verifying on the emulator An AVD named `nuvolari` is set up on this machine: API 36.1, `google_apis`, x86_64, GPU passthrough to the host. It uses a 2 GB data partition and no SD card because disk is tight, and quickboot snapshots are disabled โ€” they cost 2.6 GB to save a few seconds of boot, which is the wrong trade here. Expect a cold boot of a minute or so. ```bash # Boot it "$ANDROID_HOME/emulator/emulator" -avd nuvolari -no-audio -no-boot-anim -gpu host # Build, install, launch cd app flutter build apk --debug adb install -r build/app/outputs/flutter-apk/app-debug.apk adb shell am start -n it.nuvolari.app/.MainActivity # Capture what it looks like adb exec-out screencap -p > screen.png ``` `flutter run -d emulator-5554` works too and gives hot reload; the steps above are what a non-interactive session uses. > An unrelated AVD, `pixel_7_-_api_35`, is present but broken โ€” its system image > (`android-35/google_apis_playstore`) is not installed, so it cannot boot. It occupies > about 4.8 GB. It predates this project and has been left alone.