The AVD's quickboot snapshot had grown to 2.6 GB, which took this machine down to 1.4 GB free. Saving a minute of boot time is not worth that here, so cold boot is now the default and the snapshot directory is gone. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
164 lines
6.9 KiB
Markdown
164 lines
6.9 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.
|
|
|
|
**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.
|