Files
Europa/Nuvolari
Alby96andClaude Opus 5 06ab816ebe Add saved places and optional device location
A saved place is a named point the user keeps: it frames the map now, and once
the worker publishes station data it is what the nearest-station readings and,
later, the rain notifications will hang off. Free points rather than stations,
because people think in terms of home and work, not in terms of which weather
station happens to represent them.

Everything stays on the device. Places live in SharedPreferences under a
versioned key, and there is no account to attach them to and no server that
would accept them. That is what keeps the Data safety declaration able to say
no location is collected, and it must survive the notification work: the device
will subscribe to the topic for the cell containing a place, so the link
between a person and a place never leaves their phone.

Location is coarse only, and that took enforcing. geolocator declares
ACCESS_FINE_LOCATION in its own manifest and the merger pulls it in, so the
system dialog offered "Precise" despite the app asking for nothing of the sort;
the manifest now removes it with tools:node="remove", and the dialog reads
"approximate location" with no choice offered. Requests also go through the
platform LocationManager rather than the Play Services fused provider, which
prompts about Location Accuracy and, when declined, returns no fix at all —
an absurd outcome for an app that only ever wanted an approximate one, and one
that also tied location to Play Services being present.

The prominent disclosure comes before the system dialog, as Play requires, and
is repeated in Settings so someone who already answered can still read what the
permission is for. Declining leaves the app fully usable.

Two more defects found by running it and by a test:

- MapLibreMap leaves cameraPosition null unless trackCameraPosition is set, so
  "save the map centre" silently saved the region default rather than what the
  user was looking at.
- Place ids came straight from the microsecond clock, so two places saved in
  the same microsecond shared an id and rename, remove and the duplicate-name
  check all acted on the wrong one. A test caught it on a fast machine.

Saving refuses points outside the region rather than accepting them: a place in
Rome would look like it worked and then show nothing forever.

Also corrects CLAUDE.md, which still said ARPA states no licence, and records
the ARPA realtime API there with the property that governs how it may be used —
it lags about 4.5 hours, so it is an observation archive and must never sit
next to 5-minute radar looking current.

Verified: analyze clean, 158 tests passing, and on the emulator the disclosure
precedes the system dialog, the dialog asks only for approximate location, a
place survives restart and reinstall, and tapping one moves the map onto it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-10 19:39:03 +02:00
..

Nuvolari

Precipitation radar for Piedmont, Italy. Android first, iOS later, one Flutter codebase.

Radar imagery comes from the public Radar-DPC platform and weather alerts from the ARPA Piemonte XML-CAP bulletin, drawn over an OpenStreetMap base map. The app is free, with no advertising and no accounts.

Scope: radar, official alerts, rain notifications. No forecasts, no lightning, no home-screen widget. See CLAUDE.md.

Nuvolari is an independent app. It is not affiliated with, endorsed by, or operated by ARPA Piemonte or the Dipartimento della Protezione Civile. For civil-protection purposes the official channels always prevail.

Layout

app/       Flutter application (Dart package "nuvolari")
backend/   Python worker: fetches DPC rasters, crops, reprojects, renders PNG frames
docs/      architecture, data sources, licenses, stack decisions, roadmap, privacy
tool/      verification scripts

Start with docs/architecture.md, then docs/roadmap.md for what is built and what is next.

Toolchain

Tool Version
Flutter 3.47.3 stable (Dart 3.13.3)
Android SDK platform 36, build-tools 36.0.0, platform-tools
JDK 21
Python 3.12+ (3.14 works; rasterio ships wheels for it)
Android NDK 28.2.13676358 — pinned by maplibre_gl, not by this project

A full toolchain plus a warm Gradle cache needs roughly 14 GB of disk: Flutter 3 GB, Android SDK 2.5 GB (2 GB of which is the NDK the map plugin pins), the Gradle cache 5 GB, and build output around 2 GB. flutter clean reclaims the last of those.

Configuration

Secrets never enter the repository. Copy the template and fill it in:

cp env.example.json env.json     # env.json is git-ignored
Key Purpose
MAP_STYLE_URL MapLibre style URL. Empty uses the free OpenFreeMap style; offline forces the bundled no-network style.
RADAR_MANIFEST_URL Base URL of the published manifest.json.
RADAR_SOURCE mock (offline demo), dpc (live), arpa (disabled stub).
REGION_ID Which region config to load. Defaults to piemonte.

Running

cd app
flutter run --dart-define-from-file=../env.json

With no env.json the app starts in demo mode: mock radar frames from assets on the free OpenFreeMap base map. No credentials are needed for anything — OpenFreeMap has no API key and no signup.

The radar frames work with no network at all. The base map needs one; run with --dart-define=MAP_STYLE_URL=offline to drop it too and work entirely offline.

Debugging in VS Code

Open the Nuvolari folder as the workspace root — the configurations in .vscode/ are relative to it. Then pick one in Run and Debug and press F5:

Configuration Use it for
Nuvolari — debug (emulatore) Everyday work. Demo mode, no env.json needed, hot reload on save.
Nuvolari — profile Judging animation smoothness. Debug builds run the Dart VM unoptimised, so the radar timeline always looks worse than it is — never assess it in debug.
Nuvolari — debug con env.json Pointing the app at a live radar manifest or a different base map style.
Nuvolari — debug su Windows Fast UI iteration with no emulator. The native map does not render here.
Test — tutti / Test — file corrente Debugging tests with breakpoints.

Every emulator configuration runs tool/start_emulator.ps1 first, so F5 works from a cold machine. The script exits immediately when a device is already attached, so pressing F5 twice does not start two emulators.

Ctrl+Shift+B runs the full verification. Other tasks live under Terminal → Run Task: quick verification, regenerating the demo radar frames, and shutting the emulator down.

Verifying

.\tool\verify.ps1              # format, analyze, test, build, lint, pytest
.\tool\verify.ps1 -SkipBuild   # fast inner loop

Stages whose target does not exist yet are skipped, so this runs from day one.

Attribution

Radar-DPC (CC BY-SA) · Arpa Piemonte · © OpenStreetMap contributors (ODbL) · © OpenMapTiles · OpenFreeMap.

See docs/licenses.md for the obligations these carry — in particular, the rendered radar frames are a derived product of CC BY-SA data and inherit share-alike, and the OpenFreeMap style ships no attribution field, so the app has to render the map credits itself.