Creates the app skeleton and the seam everything else in the project hangs off: region-specific data lives in an asset file, not in code, so adding a region later is a new JSON file rather than a refactor. Notable choices: - applicationId and namespace are it.nuvolari.app rather than the doubled it.nuvolari.nuvolari that `flutter create` produces, with the Kotlin package and the iOS bundle identifiers moved to match. - SDK levels are pinned instead of inherited from `flutter.*`. Google Play requires API 36, and that is a release blocker rather than something to let a Flutter upgrade change silently. minSdk 24 is the highest floor the planned dependencies impose. - Riverpod and Dio-free for now, no code generation: see docs/stack-decisions.md. - Italian is the l10n source language, so app_it.arb is the template rather than a translation of an English original. The region parser rejects rather than repairs. An inverted bounding box, a map centre outside its own bounds, an unknown adapter name, a duplicate zone code or an empty attribution list all throw with the offending field named. Each of those would otherwise fail silently and visibly wrong: a swapped latitude and longitude renders the radar in the wrong place, an unknown adapter falling back to mock would show demo frames where live data was expected, and a missing attribution is a licence violation rather than a cosmetic gap. Tests run against the asset that actually ships and against a captured copy of the live ARPA CAP feed, so the eleven zone codes in the config are checked against the eleven the feed really emits rather than against a list retyped from documentation. Verified: dart format clean, flutter analyze 0 issues, 50 tests passing, flutter build appbundle --debug produces an AAB with applicationId it.nuvolari.app, minSdk 24, targetSdk 36. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
118 lines
4.8 KiB
Markdown
118 lines
4.8 KiB
Markdown
# 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.
|
|
|
|
> 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.
|
|
|
|
## 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 |
|