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>
4.8 KiB
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.ps1is 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 |