Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a686f7625e | ||
|
|
f434a68155 | ||
|
|
aa91b457ed | ||
|
|
b4b8d089e6 | ||
|
|
06ab816ebe | ||
|
|
e14308bbb6 | ||
|
|
f3a0e3794a | ||
|
|
91f9ac0fbf | ||
|
|
8ff8a903b8 | ||
|
|
75c4c55f81 | ||
|
|
7c0a1bd180 | ||
|
|
47049b39e7 | ||
|
|
71557a02d7 | ||
|
|
4142b67a8d | ||
|
|
d84095b5a8 | ||
|
|
85b2c99949 | ||
|
|
3fcbb9f1c4 | ||
|
|
3b0fce2516 |
@@ -0,0 +1,23 @@
|
||||
# Normalise text to LF in the repository; Git checks out native line endings.
|
||||
* text=auto
|
||||
|
||||
# Shell scripts must keep LF even on Windows checkouts or they fail to run.
|
||||
*.sh text eol=lf
|
||||
gradlew text eol=lf
|
||||
|
||||
# Xcode project files are line-ending sensitive.
|
||||
*.pbxproj text eol=lf
|
||||
|
||||
# Binary assets — never diffed, never normalised.
|
||||
*.png binary
|
||||
*.jpg binary
|
||||
*.jpeg binary
|
||||
*.webp binary
|
||||
*.ico binary
|
||||
*.ttf binary
|
||||
*.otf binary
|
||||
*.woff binary
|
||||
*.woff2 binary
|
||||
*.tif binary
|
||||
*.jks binary
|
||||
*.keystore binary
|
||||
@@ -0,0 +1,78 @@
|
||||
# Gitea Actions workflow, written in GitHub Actions syntax so it runs unchanged on
|
||||
# either host. If the Gitea instance has no runner configured this file is inert and
|
||||
# tool/verify.ps1 is the verification that must pass locally.
|
||||
name: CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
paths: ['Nuvolari/**']
|
||||
pull_request:
|
||||
paths: ['Nuvolari/**']
|
||||
|
||||
defaults:
|
||||
run:
|
||||
working-directory: Nuvolari
|
||||
|
||||
jobs:
|
||||
app:
|
||||
name: Flutter app
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- uses: subosito/flutter-action@v2
|
||||
with:
|
||||
flutter-version: '3.47.3'
|
||||
channel: stable
|
||||
cache: true
|
||||
|
||||
- name: Install dependencies
|
||||
working-directory: Nuvolari/app
|
||||
run: flutter pub get
|
||||
|
||||
- name: Check formatting
|
||||
working-directory: Nuvolari/app
|
||||
run: dart format --set-exit-if-changed .
|
||||
|
||||
- name: Analyze
|
||||
working-directory: Nuvolari/app
|
||||
run: flutter analyze
|
||||
|
||||
- name: Test
|
||||
working-directory: Nuvolari/app
|
||||
run: flutter test
|
||||
|
||||
backend:
|
||||
name: Python worker
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: '3.13'
|
||||
|
||||
- name: Skip when the worker does not exist yet
|
||||
id: guard
|
||||
run: |
|
||||
if [ -f backend/pyproject.toml ]; then
|
||||
echo "exists=true" >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
echo "exists=false" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
|
||||
- name: Install dependencies
|
||||
if: steps.guard.outputs.exists == 'true'
|
||||
working-directory: Nuvolari/backend
|
||||
run: pip install -e ".[dev]"
|
||||
|
||||
- name: Lint
|
||||
if: steps.guard.outputs.exists == 'true'
|
||||
working-directory: Nuvolari/backend
|
||||
run: ruff check .
|
||||
|
||||
- name: Test
|
||||
if: steps.guard.outputs.exists == 'true'
|
||||
working-directory: Nuvolari/backend
|
||||
run: pytest -q
|
||||
@@ -0,0 +1,67 @@
|
||||
# ---------------------------------------------------------------------------
|
||||
# Nuvolari — Flutter app + Python worker
|
||||
# The repository root (Europa/.gitignore) only covers Visual Studio artifacts.
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
# Secrets — never commit. Use env.example.json as the template and pass the
|
||||
# real file with `flutter run --dart-define-from-file=env.json`.
|
||||
env.json
|
||||
.env
|
||||
.env.*
|
||||
!.env.example
|
||||
key.properties
|
||||
*.jks
|
||||
*.keystore
|
||||
google-services.json
|
||||
GoogleService-Info.plist
|
||||
service-account*.json
|
||||
|
||||
# Dart / Flutter
|
||||
.dart_tool/
|
||||
.flutter-plugins
|
||||
.flutter-plugins-dependencies
|
||||
.packages
|
||||
.pub-cache/
|
||||
.pub/
|
||||
build/
|
||||
**/doc/api/
|
||||
*.iml
|
||||
migrate_working_dir/
|
||||
coverage/
|
||||
|
||||
# Android
|
||||
app/android/local.properties
|
||||
app/android/.gradle/
|
||||
app/android/captures/
|
||||
app/android/key.properties
|
||||
*.apk
|
||||
*.aab
|
||||
|
||||
# iOS / macOS
|
||||
app/ios/Pods/
|
||||
app/ios/.symlinks/
|
||||
app/ios/Flutter/Flutter.framework
|
||||
app/ios/Flutter/Flutter.podspec
|
||||
app/ios/Flutter/Generated.xcconfig
|
||||
app/ios/Flutter/flutter_export_environment.sh
|
||||
**/.DS_Store
|
||||
|
||||
# Python worker
|
||||
.venv/
|
||||
venv/
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
.pytest_cache/
|
||||
.ruff_cache/
|
||||
.mypy_cache/
|
||||
*.egg-info/
|
||||
|
||||
# Worker output — generated radar frames and manifests, never versioned
|
||||
backend/out/
|
||||
backend/.cache/
|
||||
|
||||
# Editors
|
||||
.idea/
|
||||
|
||||
# Downloaded source archives for tool/ generators.
|
||||
tool/.cache/
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"recommendations": [
|
||||
"dart-code.flutter",
|
||||
"dart-code.dart-code",
|
||||
"ms-vscode.powershell"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
{
|
||||
// Debug configurations for the Run and Debug panel (F5).
|
||||
//
|
||||
// Open the "Nuvolari" folder as the VS Code workspace root — these paths are
|
||||
// relative to it, not to the repository root.
|
||||
//
|
||||
// Every configuration boots the Android emulator first via a preLaunchTask,
|
||||
// so F5 works from a cold machine with no manual step. The task returns
|
||||
// immediately if a device is already attached.
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
// The everyday one: demo mode, no env.json needed, hot reload on save.
|
||||
"name": "Nuvolari — debug (emulatore)",
|
||||
"type": "dart",
|
||||
"request": "launch",
|
||||
"program": "${workspaceFolder}/app/lib/main.dart",
|
||||
"cwd": "${workspaceFolder}/app",
|
||||
"deviceId": "emulator-5554",
|
||||
"flutterMode": "debug",
|
||||
"preLaunchTask": "Avvia emulatore Android"
|
||||
},
|
||||
{
|
||||
// Debug builds run the Dart VM unoptimised, so animation always looks
|
||||
// worse than it is. Judge the radar timeline here, never in debug.
|
||||
"name": "Nuvolari — profile (misura le prestazioni)",
|
||||
"type": "dart",
|
||||
"request": "launch",
|
||||
"program": "${workspaceFolder}/app/lib/main.dart",
|
||||
"cwd": "${workspaceFolder}/app",
|
||||
"deviceId": "emulator-5554",
|
||||
"flutterMode": "profile",
|
||||
"preLaunchTask": "Avvia emulatore Android"
|
||||
},
|
||||
{
|
||||
// Same as the first, but pointed at a live radar manifest. Requires
|
||||
// env.json, which is git-ignored — copy env.example.json and fill it in.
|
||||
"name": "Nuvolari — debug con env.json",
|
||||
"type": "dart",
|
||||
"request": "launch",
|
||||
"program": "${workspaceFolder}/app/lib/main.dart",
|
||||
"cwd": "${workspaceFolder}/app",
|
||||
"deviceId": "emulator-5554",
|
||||
"flutterMode": "debug",
|
||||
"toolArgs": ["--dart-define-from-file=${workspaceFolder}/env.json"],
|
||||
"preLaunchTask": "Avvia emulatore Android"
|
||||
},
|
||||
{
|
||||
// Runs on the workstation instead of a device: no emulator, no Android
|
||||
// build, seconds to start. Good for UI work; the native map will not
|
||||
// render here, so it is useless for anything map-related.
|
||||
"name": "Nuvolari — debug su Windows (senza mappa)",
|
||||
"type": "dart",
|
||||
"request": "launch",
|
||||
"program": "${workspaceFolder}/app/lib/main.dart",
|
||||
"cwd": "${workspaceFolder}/app",
|
||||
"deviceId": "windows",
|
||||
"flutterMode": "debug"
|
||||
},
|
||||
{
|
||||
"name": "Test — tutti",
|
||||
"type": "dart",
|
||||
"request": "launch",
|
||||
"program": "${workspaceFolder}/app/test",
|
||||
"cwd": "${workspaceFolder}/app"
|
||||
},
|
||||
{
|
||||
"name": "Test — file corrente",
|
||||
"type": "dart",
|
||||
"request": "launch",
|
||||
"program": "${file}",
|
||||
"cwd": "${workspaceFolder}/app"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
{
|
||||
// Forward slashes on purpose: VS Code accepts them on Windows and they avoid
|
||||
// the JSON backslash-escaping trap.
|
||||
"dart.flutterSdkPath": "C:/src/flutter",
|
||||
|
||||
// Format and fix imports on save; `dart format` is enforced by tool/verify.ps1
|
||||
// anyway, so catching it here avoids a failed verification later.
|
||||
"[dart]": {
|
||||
"editor.formatOnSave": true,
|
||||
"editor.rulers": [80],
|
||||
"editor.selectionHighlight": false,
|
||||
"editor.tabCompletion": "onlySnippets",
|
||||
"editor.wordBasedSuggestions": "off"
|
||||
},
|
||||
|
||||
// Generated from lib/l10n/*.arb during pub get, and git-ignored.
|
||||
"dart.analysisExcludedFolders": ["app/build", "app/.dart_tool"],
|
||||
|
||||
"files.exclude": {
|
||||
"**/.dart_tool": true,
|
||||
"**/build": true,
|
||||
"**/.gradle": true
|
||||
},
|
||||
|
||||
// Radar frames are binary and numerous; keeping them out of search results
|
||||
// makes finding code faster.
|
||||
"search.exclude": {
|
||||
"**/app/assets/mock/frames": true,
|
||||
"**/app/lib/l10n/app_localizations*.dart": true
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
{
|
||||
"version": "2.0.0",
|
||||
"tasks": [
|
||||
{
|
||||
// preLaunchTask for the emulator debug configurations. Idempotent: it
|
||||
// exits at once when a device is already attached, so pressing F5 twice
|
||||
// does not start two emulators.
|
||||
"label": "Avvia emulatore Android",
|
||||
"type": "shell",
|
||||
"command": "powershell",
|
||||
"args": [
|
||||
"-NoProfile",
|
||||
"-ExecutionPolicy", "Bypass",
|
||||
"-File", "${workspaceFolder}/tool/start_emulator.ps1"
|
||||
],
|
||||
"problemMatcher": [],
|
||||
"presentation": {
|
||||
"reveal": "silent",
|
||||
"panel": "shared",
|
||||
"clear": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"label": "Verifica (format, analyze, test, build)",
|
||||
"type": "shell",
|
||||
"command": "powershell",
|
||||
"args": [
|
||||
"-NoProfile",
|
||||
"-ExecutionPolicy", "Bypass",
|
||||
"-File", "${workspaceFolder}/tool/verify.ps1"
|
||||
],
|
||||
"group": { "kind": "build", "isDefault": true },
|
||||
"problemMatcher": ["$dart-build_runner"]
|
||||
},
|
||||
{
|
||||
"label": "Verifica rapida (senza build)",
|
||||
"type": "shell",
|
||||
"command": "powershell",
|
||||
"args": [
|
||||
"-NoProfile",
|
||||
"-ExecutionPolicy", "Bypass",
|
||||
"-File", "${workspaceFolder}/tool/verify.ps1",
|
||||
"-SkipBuild"
|
||||
],
|
||||
"problemMatcher": []
|
||||
},
|
||||
{
|
||||
"label": "Rigenera i frame radar dimostrativi",
|
||||
"type": "shell",
|
||||
"command": "python",
|
||||
"args": ["${workspaceFolder}/tool/generate_mock_frames.py"],
|
||||
"problemMatcher": []
|
||||
},
|
||||
{
|
||||
"label": "Spegni emulatore",
|
||||
"type": "shell",
|
||||
"command": "adb",
|
||||
"args": ["emu", "kill"],
|
||||
"problemMatcher": []
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.nuvolari" android:versionCode="1">
|
||||
<application android:allowBackup="true" android:icon="@mipmap/appicon" android:label="Nuvolari" android:supportsRtl="true"></application>
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
</manifest>
|
||||
@@ -0,0 +1,98 @@
|
||||
# CLAUDE.md — Nuvolari
|
||||
|
||||
## Project
|
||||
Cross-platform precipitation-radar app for the Piedmont region (Italy). Android first,
|
||||
iOS later, single Flutter codebase. Region-scoped now (Piemonte) but extensible to other
|
||||
regions via configuration.
|
||||
|
||||
## Scope
|
||||
**Radar on a map, plus official alerts and rain notifications. Nothing else.**
|
||||
|
||||
In scope:
|
||||
- Animated precipitation radar over an OpenStreetMap base map
|
||||
- Saved places, municipality and coordinate search, and optional device location with a
|
||||
live position marker; used to frame the map and later to anchor notifications
|
||||
- Ground-station rain accumulations and 72-hour temperature from the ARPA realtime API,
|
||||
published through the worker
|
||||
- ARPA Piemonte official alert bulletin (XML-CAP), republished verbatim
|
||||
- "Rain incoming" notifications by geographic cell
|
||||
|
||||
Explicitly out of scope — do not add these back without being asked:
|
||||
- Weather forecasts of any kind
|
||||
- Lightning
|
||||
- Home-screen widget
|
||||
- Advertising and consent flows
|
||||
|
||||
The app is currently free with **no advertising**. Ads may return later, so prefer data
|
||||
sources that permit commercial use; do not adopt a non-commercial-only source on the
|
||||
grounds that there are no ads today.
|
||||
|
||||
## Golden rules
|
||||
- Code, identifiers and commit messages in English. UI text in Italian via ARB localization.
|
||||
- Never commit secrets. Use .env + --dart-define; keep .gitignore updated.
|
||||
- Never poll ARPA/DPC servers directly from the app: always go through our backend/CDN.
|
||||
- Never store precise user locations server-side: rain notifications use geographic CELLS.
|
||||
- Location is COARSE only. geolocator injects ACCESS_FINE_LOCATION; the app manifest
|
||||
removes it with `tools:node="remove"`. Do not add it back without a feature that needs
|
||||
it and a matching Data safety update. Saved places live in SharedPreferences on the
|
||||
device and never leave it.
|
||||
- Do not use ARPA/DPC name, logo or the word "ufficiale" in a way implying an official app.
|
||||
|
||||
## Architecture
|
||||
- Monorepo: /app (Flutter), /backend (Python worker), /docs (detailed docs), /tool (scripts).
|
||||
- Radar data via RadarSource interface with adapters:
|
||||
DpcRadarSource (live, reads our CDN), ArpaRadarSource (stub, awaits access),
|
||||
MockRadarSource (offline/demo). Active source chosen by region config + runtime flag.
|
||||
- App reads PNG frames + manifest.json from CDN produced by the backend worker
|
||||
(crop to Piemonte bbox, reproject to EPSG:3857).
|
||||
|
||||
## Data sources & licenses (attribution is mandatory on the Sources screen)
|
||||
- Radar (active): Radar-DPC — base https://radar-api.protezionecivile.it/ ,
|
||||
GET /findLastProductByType?type=VMI , POST /downloadProduct (GeoTIFF via presigned S3).
|
||||
REQUIRE header `origin: https://radar.protezionecivile.it`. License CC BY-SA — credit
|
||||
"Radar-DPC"; derivative data products must stay CC BY-SA. Docs: dpc-radar.readthedocs.io.
|
||||
The rasters are on a **custom projection centred on Italy**, not EPSG:4326 or 3857, and
|
||||
their GeoKeys are internally inconsistent — read the CRS from each file, never hardcode it.
|
||||
- Radar (future): ARPA Piemonte (HDF5 ODIM, 5-minute volumes). The real-time access link
|
||||
**must be requested by email** at info.meteo@arpa.piemonte.it — that request is the
|
||||
project owner's to make. Adapter stays a disabled stub until it exists.
|
||||
- Ground stations: ARPA realtime API, https://utility.arpa.piemonte.it/api_realtime —
|
||||
no key, no registration. `/pie_anag` gives 374 stations with coordinates (286 with a
|
||||
rain gauge); `/data_pie` gives hourly `cum_rain_1h/3h/6h/12h/24h`, temperature, wind,
|
||||
snow and hydrometric level for the last 3 days. **It lags ~4.5 hours** — an observation
|
||||
archive, not a live feed, and it must never be shown as current next to 5-minute radar.
|
||||
Fetched by the worker, never by the app.
|
||||
- ARPA licence: CC BY 4.0 per https://www.arpa.piemonte.it/note-legali, commercial use
|
||||
permitted, credit "Fonte: Arpa Piemonte - www.arpa.piemonte.it". Both REST APIs link
|
||||
that notice from their OpenAPI description. The radar page does not repeat it, so ask
|
||||
ARPA to confirm it covers the radar volumes in the same email.
|
||||
- Alerts: ARPA Piemonte XML-CAP bulletin at
|
||||
https://www.arpa.piemonte.it/export/xmlcap/allerta.xml — reproduce alert levels WITHOUT
|
||||
reinterpreting; link the official channel. Six level values, not four: VERDE, GIALLO,
|
||||
ARANCIONE, ROSSO, plus BIANCO (avalanche scale out of season) and "-" (no data).
|
||||
- Base map: OpenFreeMap (https://openfreemap.org), free OSM vector tiles with no API key
|
||||
and no request limits. Mandatory credits: "© OpenMapTiles" and
|
||||
"© OpenStreetMap contributors" (ODbL). OpenFreeMap's own credit is optional.
|
||||
- Municipalities: 1180 Piedmont comuni bundled from Istat boundary shapefiles (CC BY 4.0),
|
||||
generated by tool/generate_places.py. Offline search, no geocoding service. The source
|
||||
DBF is UTF-8 despite appearances and the geometry is UTM 32N — see docs/data-sources.md
|
||||
before regenerating.
|
||||
- Launcher icon: a white cloud on the seed blue #1F6FB2, drawn by tool/generate_icon.py
|
||||
and expanded by flutter_launcher_icons. Read the "Launcher icon" section of
|
||||
docs/architecture.md before regenerating: the tool corrupts an Xcode build setting.
|
||||
|
||||
## Verification loop (run after each milestone)
|
||||
- `dart format .` ; `flutter analyze` ; `flutter test` ; `flutter build appbundle`
|
||||
- Backend: `python -m pytest` ; lint. Commit frequently with clear messages.
|
||||
- `tool/verify.ps1` runs all of the above and skips stages that do not exist yet.
|
||||
- Verify visually on the `nuvolari` AVD before calling a milestone done. Twice now,
|
||||
running the app has caught defects that passing tests did not.
|
||||
|
||||
## Store / compliance targets
|
||||
- Google Play: target API 36 (Android 16); closed testing 12 testers / 14 days;
|
||||
Data safety section; prominent disclosure for location; signed AAB.
|
||||
- No advertising, so no CMP and no IAB TCF obligations while that holds.
|
||||
- iOS later: keep platform abstractions clean (privacy nutrition label to add).
|
||||
|
||||
## Docs to maintain in /docs
|
||||
architecture.md, data-sources.md, licenses.md, stack-decisions.md, roadmap.md, privacy.md
|
||||
@@ -1,90 +0,0 @@
|
||||
using Android.App;
|
||||
using Android.OS;
|
||||
using Android.Gms.Maps;
|
||||
using Android.Gms.Maps.Model;
|
||||
using Android.Widget;
|
||||
using Android.Locations;
|
||||
using System.Threading.Tasks;
|
||||
using System.Linq;
|
||||
|
||||
/*
|
||||
* IDEE:
|
||||
* Fare una versione molto base dell'app senza troppe funzionalità, giusto per mostrare una mappa.
|
||||
* Fare poi una versione con più funzionalità, ma con pubblicità per guadagnare qualcosa da quello.
|
||||
*/
|
||||
|
||||
namespace Nuvolari
|
||||
{
|
||||
[Activity(Label = "@string/app_name", MainLauncher = true)]
|
||||
public class MainActivity : Activity, IOnMapReadyCallback
|
||||
{
|
||||
private MapFragment? mapFragment;
|
||||
private GoogleMap? googleMap;
|
||||
private SearchView? searchView;
|
||||
|
||||
protected override void OnCreate(Bundle? savedInstanceState)
|
||||
{
|
||||
base.OnCreate(savedInstanceState);
|
||||
|
||||
// Usa il layout personalizzato con SearchView e MapFragment
|
||||
SetContentView(Resource.Layout.activity_main);
|
||||
|
||||
// Ottieni riferimenti ai componenti UI
|
||||
searchView = FindViewById<SearchView>(Resource.Id.searchView);
|
||||
mapFragment = FragmentManager.FindFragmentById(Resource.Id.map) as MapFragment;
|
||||
|
||||
mapFragment?.GetMapAsync(this);
|
||||
|
||||
// Gestione ricerca
|
||||
if (searchView != null)
|
||||
{
|
||||
searchView.QueryTextSubmit += async (s, e) =>
|
||||
{
|
||||
var query = e?.Query ?? string.Empty;
|
||||
await SearchAndMoveToLocationAsync(query);
|
||||
searchView.ClearFocus();
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public void OnMapReady(GoogleMap map)
|
||||
{
|
||||
googleMap = map;
|
||||
// Imposta solo la navigazione base (scroll/zoom)
|
||||
googleMap.UiSettings.CompassEnabled = false;
|
||||
googleMap.UiSettings.MapToolbarEnabled = false;
|
||||
googleMap.UiSettings.MyLocationButtonEnabled = false;
|
||||
|
||||
// Posizione iniziale: Milano
|
||||
var position = new LatLng(45.4642, 9.19);
|
||||
googleMap.MoveCamera(CameraUpdateFactory.NewLatLngZoom(position, 10));
|
||||
}
|
||||
|
||||
private async Task SearchAndMoveToLocationAsync(string query)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(query) || googleMap == null)
|
||||
return;
|
||||
|
||||
var geocoder = new Geocoder(this);
|
||||
#pragma warning disable CA1422 // Suppress warning for obsolete API on Android 33+
|
||||
var addresses = await Task.Run(() => geocoder.GetFromLocationName(query, 1));
|
||||
#pragma warning restore CA1422
|
||||
var address = addresses?.FirstOrDefault();
|
||||
if (address != null)
|
||||
{
|
||||
var latLng = new LatLng(address.Latitude, address.Longitude);
|
||||
RunOnUiThread(() =>
|
||||
{
|
||||
googleMap?.AnimateCamera(CameraUpdateFactory.NewLatLngZoom(latLng, 10));
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
RunOnUiThread(() =>
|
||||
{
|
||||
Toast.MakeText(this, "Luogo non trovato", ToastLength.Short).Show();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net9.0-android</TargetFramework>
|
||||
<SupportedOSPlatformVersion>21</SupportedOSPlatformVersion>
|
||||
<OutputType>Exe</OutputType>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<ApplicationId>com.companyname.Nuvolari</ApplicationId>
|
||||
<ApplicationVersion>1</ApplicationVersion>
|
||||
<ApplicationDisplayVersion>1.0</ApplicationDisplayVersion>
|
||||
<!--
|
||||
Enables trim analyzers and full trimming during Release mode.
|
||||
To learn more, see: https://learn.microsoft.com/dotnet/core/deploying/trimming/trimming-options#trimming-granularity
|
||||
-->
|
||||
<TrimMode>full</TrimMode>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Xamarin.GooglePlayServices.Maps" Version="119.2.0.1" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -1,25 +0,0 @@
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 17
|
||||
VisualStudioVersion = 17.13.35931.197 d17.13
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Nuvolari", "Nuvolari.csproj", "{B6CEEA40-2867-4F58-96EA-BB986DE9489C}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Release|Any CPU = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{B6CEEA40-2867-4F58-96EA-BB986DE9489C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{B6CEEA40-2867-4F58-96EA-BB986DE9489C}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{B6CEEA40-2867-4F58-96EA-BB986DE9489C}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{B6CEEA40-2867-4F58-96EA-BB986DE9489C}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {646B2C8A-0EA4-4133-AB5A-3C6DD1D72D94}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
@@ -0,0 +1,114 @@
|
||||
# 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](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](docs/architecture.md), then
|
||||
[docs/roadmap.md](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:
|
||||
|
||||
```bash
|
||||
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
|
||||
|
||||
```bash
|
||||
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
|
||||
|
||||
```powershell
|
||||
.\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.
|
||||
|
||||
The credits are on the **Sources screen**, reached from the settings button over the
|
||||
map. There is no permanent credit line on the map itself: the OSMF attribution
|
||||
guidelines allow the licence information to sit behind an "About option in a menu" so
|
||||
long as it stays findable, and a test asserts that route still exists.
|
||||
|
||||
See [docs/licenses.md](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.
|
||||
@@ -1,44 +0,0 @@
|
||||
Images, layout descriptions, binary blobs and string dictionaries can be included
|
||||
in your application as resource files. Various Android APIs are designed to
|
||||
operate on the resource IDs instead of dealing with images, strings or binary blobs
|
||||
directly.
|
||||
|
||||
For example, a sample Android app that contains a user interface layout (main.xml),
|
||||
an internationalization string table (strings.xml) and some icons (drawable-XXX/icon.png)
|
||||
would keep its resources in the "Resources" directory of the application:
|
||||
|
||||
Resources/
|
||||
drawable/
|
||||
icon.png
|
||||
|
||||
layout/
|
||||
main.xml
|
||||
|
||||
values/
|
||||
strings.xml
|
||||
|
||||
In order to get the build system to recognize Android resources, set the build action to
|
||||
"AndroidResource". The native Android APIs do not operate directly with filenames, but
|
||||
instead operate on resource IDs. When you compile an Android application that uses resources,
|
||||
the build system will package the resources for distribution and generate a class called "Resource"
|
||||
(this is an Android convention) that contains the tokens for each one of the resources
|
||||
included. For example, for the above Resources layout, this is what the Resource class would expose:
|
||||
|
||||
public class Resource {
|
||||
public class Drawable {
|
||||
public const int icon = 0x123;
|
||||
}
|
||||
|
||||
public class Layout {
|
||||
public const int main = 0x456;
|
||||
}
|
||||
|
||||
public class Strings {
|
||||
public const int first_string = 0xabc;
|
||||
public const int second_string = 0xbcd;
|
||||
}
|
||||
}
|
||||
|
||||
You would then use Resource.Drawable.icon to reference the drawable/icon.png file, or
|
||||
Resource.Layout.main to reference the layout/main.xml file, or Resource.Strings.first_string
|
||||
to reference the first string in the dictionary file values/strings.xml.
|
||||
@@ -1,17 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:orientation="vertical"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
<SearchView
|
||||
android:id="@+id/searchView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:queryHint="Cerca città o luogo" />
|
||||
<fragment
|
||||
android:id="@+id/map"
|
||||
android:name="com.google.android.gms.maps.MapFragment"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1" />
|
||||
</LinearLayout>
|
||||
@@ -1,13 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_centerInParent="true"
|
||||
android:text="@string/app_text"
|
||||
/>
|
||||
</RelativeLayout>
|
||||
@@ -1,4 +0,0 @@
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@mipmap/appicon_background" />
|
||||
<foreground android:drawable="@mipmap/appicon_foreground" />
|
||||
</adaptive-icon>
|
||||
@@ -1,4 +0,0 @@
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@mipmap/appicon_background" />
|
||||
<foreground android:drawable="@mipmap/appicon_foreground" />
|
||||
</adaptive-icon>
|
||||
|
Before Width: | Height: | Size: 2.1 KiB |
|
Before Width: | Height: | Size: 97 B |
|
Before Width: | Height: | Size: 1.2 KiB |
|
Before Width: | Height: | Size: 1.5 KiB |
|
Before Width: | Height: | Size: 92 B |
|
Before Width: | Height: | Size: 1.2 KiB |
|
Before Width: | Height: | Size: 3.0 KiB |
|
Before Width: | Height: | Size: 100 B |
|
Before Width: | Height: | Size: 1.8 KiB |
|
Before Width: | Height: | Size: 4.6 KiB |
|
Before Width: | Height: | Size: 108 B |
|
Before Width: | Height: | Size: 1.9 KiB |
|
Before Width: | Height: | Size: 6.7 KiB |
|
Before Width: | Height: | Size: 117 B |
|
Before Width: | Height: | Size: 2.7 KiB |
@@ -1,4 +0,0 @@
|
||||
<resources>
|
||||
<string name="app_name">Nuvolari</string>
|
||||
<string name="app_text">Hello, Android!</string>
|
||||
</resources>
|
||||
@@ -0,0 +1,51 @@
|
||||
# Miscellaneous
|
||||
*.class
|
||||
*.log
|
||||
*.pyc
|
||||
*.swp
|
||||
.DS_Store
|
||||
.atom/
|
||||
.build/
|
||||
.buildlog/
|
||||
.history
|
||||
.svn/
|
||||
.swiftpm/
|
||||
migrate_working_dir/
|
||||
|
||||
# IntelliJ related
|
||||
*.iml
|
||||
*.ipr
|
||||
*.iws
|
||||
.idea/
|
||||
|
||||
# The .vscode folder contains launch configuration and tasks you configure in
|
||||
# VS Code which you may wish to be included in version control, so this line
|
||||
# is commented out by default.
|
||||
#.vscode/
|
||||
|
||||
# Flutter/Dart/Pub related
|
||||
**/doc/api/
|
||||
**/ios/Flutter/.last_build_id
|
||||
.dart_tool/
|
||||
.flutter-plugins-dependencies
|
||||
.pub-cache/
|
||||
.pub/
|
||||
/build/
|
||||
/coverage/
|
||||
|
||||
# Symbolication related
|
||||
app.*.symbols
|
||||
|
||||
# Obfuscation related
|
||||
app.*.map.json
|
||||
|
||||
# Android Studio will place build artifacts here
|
||||
/android/app/debug
|
||||
/android/app/profile
|
||||
/android/app/release
|
||||
|
||||
# Widget Preview related
|
||||
.widget_preview/
|
||||
|
||||
# Generated from lib/l10n/*.arb by the Flutter tool during `flutter pub get`.
|
||||
/lib/l10n/app_localizations*.dart
|
||||
@@ -0,0 +1,33 @@
|
||||
# This file tracks properties of this Flutter project.
|
||||
# Used by Flutter tool to assess capabilities and perform upgrades etc.
|
||||
#
|
||||
# This file should be version controlled and should not be manually edited.
|
||||
|
||||
version:
|
||||
revision: "e8113bf45620cbeb8aff64947ee4c93e16adb4cf"
|
||||
channel: "stable"
|
||||
|
||||
project_type: app
|
||||
|
||||
# Tracks metadata for the flutter migrate command
|
||||
migration:
|
||||
platforms:
|
||||
- platform: root
|
||||
create_revision: e8113bf45620cbeb8aff64947ee4c93e16adb4cf
|
||||
base_revision: e8113bf45620cbeb8aff64947ee4c93e16adb4cf
|
||||
- platform: android
|
||||
create_revision: e8113bf45620cbeb8aff64947ee4c93e16adb4cf
|
||||
base_revision: e8113bf45620cbeb8aff64947ee4c93e16adb4cf
|
||||
- platform: ios
|
||||
create_revision: e8113bf45620cbeb8aff64947ee4c93e16adb4cf
|
||||
base_revision: e8113bf45620cbeb8aff64947ee4c93e16adb4cf
|
||||
|
||||
# User provided section
|
||||
|
||||
# List of Local paths (relative to this file) that should be
|
||||
# ignored by the migrate tool.
|
||||
#
|
||||
# Files that are not part of the templates will be ignored by default.
|
||||
unmanaged_files:
|
||||
- 'lib/main.dart'
|
||||
- 'ios/Runner.xcodeproj/project.pbxproj'
|
||||
@@ -0,0 +1,48 @@
|
||||
include: package:flutter_lints/flutter.yaml
|
||||
|
||||
analyzer:
|
||||
exclude:
|
||||
- build/**
|
||||
- android/**
|
||||
- ios/**
|
||||
# Generated from lib/l10n/*.arb by the Flutter tool.
|
||||
- lib/l10n/app_localizations*.dart
|
||||
|
||||
errors:
|
||||
# UI text must come from the ARB files. A literal string reaching a widget
|
||||
# is untranslatable, so treat it as an error rather than a hint.
|
||||
prefer_const_constructors: warning
|
||||
todo: ignore
|
||||
|
||||
language:
|
||||
strict-casts: true
|
||||
strict-inference: true
|
||||
strict-raw-types: true
|
||||
|
||||
linter:
|
||||
rules:
|
||||
# Correctness
|
||||
- always_declare_return_types
|
||||
- avoid_dynamic_calls
|
||||
- avoid_slow_async_io
|
||||
- cancel_subscriptions
|
||||
- close_sinks
|
||||
- comment_references
|
||||
- literal_only_boolean_expressions
|
||||
- no_adjacent_strings_in_list
|
||||
- test_types_in_equals
|
||||
- throw_in_finally
|
||||
- unnecessary_statements
|
||||
|
||||
# Async correctness — the radar timeline is full of races between
|
||||
# prefetching, scrubbing and lifecycle changes.
|
||||
- discarded_futures
|
||||
- unawaited_futures
|
||||
|
||||
# Style consistency
|
||||
- directives_ordering
|
||||
- prefer_final_locals
|
||||
- prefer_single_quotes
|
||||
- sort_child_properties_last
|
||||
- unnecessary_parenthesis
|
||||
- use_string_buffers
|
||||
@@ -0,0 +1,14 @@
|
||||
gradle-wrapper.jar
|
||||
/.gradle
|
||||
/captures/
|
||||
/gradlew
|
||||
/gradlew.bat
|
||||
/local.properties
|
||||
GeneratedPluginRegistrant.java
|
||||
.cxx/
|
||||
|
||||
# Remember to never publicly share your keystore.
|
||||
# See https://flutter.dev/to/reference-keystore
|
||||
key.properties
|
||||
**/*.keystore
|
||||
**/*.jks
|
||||
@@ -0,0 +1,60 @@
|
||||
plugins {
|
||||
id("com.android.application")
|
||||
// The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins.
|
||||
id("dev.flutter.flutter-gradle-plugin")
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "it.nuvolari.app"
|
||||
|
||||
// SDK levels are pinned rather than inherited from `flutter.*` so that upgrading
|
||||
// Flutter cannot silently move targetSdk. Google Play requires API 36 as of
|
||||
// 2026-08-31, and that requirement is a release blocker, not a default.
|
||||
compileSdk = 36
|
||||
|
||||
// Required even though this project has no native sources of its own:
|
||||
// maplibre_gl pins ndkVersion 28.2.13676358 in its own build file, and
|
||||
// AGP needs every module to agree on one NDK. Costs ~2 GB of SDK; do not
|
||||
// drop it while the map plugin is a dependency.
|
||||
ndkVersion = flutter.ndkVersion
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility = JavaVersion.VERSION_17
|
||||
targetCompatibility = JavaVersion.VERSION_17
|
||||
}
|
||||
|
||||
defaultConfig {
|
||||
applicationId = "it.nuvolari.app"
|
||||
|
||||
// minSdk 24 is the highest floor imposed by the planned dependencies:
|
||||
// maplibre_gl needs 21, google_mobile_ads and firebase_messaging need 23.
|
||||
minSdk = 24
|
||||
targetSdk = 36
|
||||
|
||||
// Uses the version code from pubspec.yaml. When using split APKs, 1000 * ABI_VERSION
|
||||
// is added automatically by Flutter. (https://developer.android.com/studio/build/configure-apk-splits#configure-APK-versions)
|
||||
// You can force using the value of versionCode by specifying the `-P force-version-code-ignoring-abi=true`
|
||||
// flag during build.
|
||||
versionCode = flutter.versionCode
|
||||
versionName = flutter.versionName
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
// Signed with the debug key so `flutter run --release` works during
|
||||
// development. The real signing config is wired up in milestone 9 and
|
||||
// reads from key.properties, which is git-ignored.
|
||||
signingConfig = signingConfigs.getByName("debug")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
kotlin {
|
||||
compilerOptions {
|
||||
jvmTarget = org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_17
|
||||
}
|
||||
}
|
||||
|
||||
flutter {
|
||||
source = "../.."
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools">
|
||||
<!-- Flutter adds this to the debug and profile manifests only, so a
|
||||
release build needs it declared here or every request fails. -->
|
||||
<uses-permission android:name="android.permission.INTERNET"/>
|
||||
|
||||
<!-- Coarse only, deliberately. The app centres a map and picks the
|
||||
nearest weather station, and stations are kilometres apart, so
|
||||
street-level precision would buy nothing and cost a more
|
||||
invasive permission. Never add ACCESS_FINE_LOCATION without a
|
||||
feature that genuinely needs it. -->
|
||||
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
|
||||
|
||||
<!-- geolocator declares ACCESS_FINE_LOCATION in its own manifest, and the
|
||||
merger pulls it in. Left alone the system dialog offers "Precise",
|
||||
Play Services nags about Location Accuracy, and the Data safety form
|
||||
has to declare precise location — all for accuracy this app never
|
||||
uses. Removing it here keeps the declaration honest. -->
|
||||
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"
|
||||
tools:node="remove"/>
|
||||
|
||||
<application
|
||||
android:label="Nuvolari"
|
||||
android:name="${applicationName}"
|
||||
android:icon="@mipmap/ic_launcher">
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
android:exported="true"
|
||||
android:launchMode="singleTop"
|
||||
android:taskAffinity=""
|
||||
android:theme="@style/LaunchTheme"
|
||||
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
|
||||
android:hardwareAccelerated="true"
|
||||
android:windowSoftInputMode="adjustResize">
|
||||
<!-- Specifies an Android theme to apply to this Activity as soon as
|
||||
the Android process has started. This theme is visible to the user
|
||||
while the Flutter UI initializes. After that, this theme continues
|
||||
to determine the Window background behind the Flutter UI. -->
|
||||
<meta-data
|
||||
android:name="io.flutter.embedding.android.NormalTheme"
|
||||
android:resource="@style/NormalTheme"
|
||||
/>
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN"/>
|
||||
<category android:name="android.intent.category.LAUNCHER"/>
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<!-- Don't delete the meta-data below.
|
||||
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
|
||||
<meta-data
|
||||
android:name="flutterEmbedding"
|
||||
android:value="2" />
|
||||
</application>
|
||||
<!-- Required to query activities that can process text, see:
|
||||
https://developer.android.com/training/package-visibility and
|
||||
https://developer.android.com/reference/android/content/Intent#ACTION_PROCESS_TEXT.
|
||||
|
||||
In particular, this is used by the Flutter engine in io.flutter.plugin.text.ProcessTextPlugin. -->
|
||||
<queries>
|
||||
<intent>
|
||||
<action android:name="android.intent.action.PROCESS_TEXT"/>
|
||||
<data android:mimeType="text/plain"/>
|
||||
</intent>
|
||||
</queries>
|
||||
</manifest>
|
||||
@@ -0,0 +1,5 @@
|
||||
package it.nuvolari.app
|
||||
|
||||
import io.flutter.embedding.android.FlutterActivity
|
||||
|
||||
class MainActivity : FlutterActivity()
|
||||
|
After Width: | Height: | Size: 1.9 KiB |
|
After Width: | Height: | Size: 1.9 KiB |
|
After Width: | Height: | Size: 974 B |
|
After Width: | Height: | Size: 974 B |
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Modify this file to customize your launch splash screen -->
|
||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:drawable="?android:colorBackground" />
|
||||
|
||||
<!-- You can insert your own image assets here -->
|
||||
<!-- <item>
|
||||
<bitmap
|
||||
android:gravity="center"
|
||||
android:src="@mipmap/launch_image" />
|
||||
</item> -->
|
||||
</layer-list>
|
||||
|
After Width: | Height: | Size: 1.9 KiB |
|
After Width: | Height: | Size: 1.9 KiB |
|
After Width: | Height: | Size: 2.9 KiB |
|
After Width: | Height: | Size: 2.9 KiB |
|
After Width: | Height: | Size: 4.1 KiB |
|
After Width: | Height: | Size: 4.1 KiB |
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Modify this file to customize your launch splash screen -->
|
||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:drawable="@android:color/white" />
|
||||
|
||||
<!-- You can insert your own image assets here -->
|
||||
<!-- <item>
|
||||
<bitmap
|
||||
android:gravity="center"
|
||||
android:src="@mipmap/launch_image" />
|
||||
</item> -->
|
||||
</layer-list>
|
||||
@@ -0,0 +1,14 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@color/ic_launcher_background"/>
|
||||
<foreground>
|
||||
<inset
|
||||
android:drawable="@drawable/ic_launcher_foreground"
|
||||
android:inset="0%" />
|
||||
</foreground>
|
||||
<monochrome>
|
||||
<inset
|
||||
android:drawable="@drawable/ic_launcher_monochrome"
|
||||
android:inset="0%" />
|
||||
</monochrome>
|
||||
</adaptive-icon>
|
||||
|
After Width: | Height: | Size: 1.3 KiB |
|
After Width: | Height: | Size: 776 B |
|
After Width: | Height: | Size: 1.5 KiB |
|
After Width: | Height: | Size: 2.3 KiB |
|
After Width: | Height: | Size: 2.9 KiB |
@@ -0,0 +1,18 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is on -->
|
||||
<style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar">
|
||||
<!-- Show a splash screen on the activity. Automatically removed when
|
||||
the Flutter engine draws its first frame -->
|
||||
<item name="android:windowBackground">@drawable/launch_background</item>
|
||||
</style>
|
||||
<!-- Theme applied to the Android Window as soon as the process has started.
|
||||
This theme determines the color of the Android Window while your
|
||||
Flutter UI initializes, as well as behind your Flutter UI while its
|
||||
running.
|
||||
|
||||
This Theme is only used starting with V2 of Flutter's Android embedding. -->
|
||||
<style name="NormalTheme" parent="@android:style/Theme.Black.NoTitleBar">
|
||||
<item name="android:windowBackground">?android:colorBackground</item>
|
||||
</style>
|
||||
</resources>
|
||||
@@ -1,4 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<color name="ic_launcher_background">#2C3E50</color>
|
||||
<color name="ic_launcher_background">#1F6FB2</color>
|
||||
</resources>
|
||||
@@ -0,0 +1,18 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is off -->
|
||||
<style name="LaunchTheme" parent="@android:style/Theme.Light.NoTitleBar">
|
||||
<!-- Show a splash screen on the activity. Automatically removed when
|
||||
the Flutter engine draws its first frame -->
|
||||
<item name="android:windowBackground">@drawable/launch_background</item>
|
||||
</style>
|
||||
<!-- Theme applied to the Android Window as soon as the process has started.
|
||||
This theme determines the color of the Android Window while your
|
||||
Flutter UI initializes, as well as behind your Flutter UI while its
|
||||
running.
|
||||
|
||||
This Theme is only used starting with V2 of Flutter's Android embedding. -->
|
||||
<style name="NormalTheme" parent="@android:style/Theme.Light.NoTitleBar">
|
||||
<item name="android:windowBackground">?android:colorBackground</item>
|
||||
</style>
|
||||
</resources>
|
||||
@@ -0,0 +1,7 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<!-- The INTERNET permission is required for development. Specifically,
|
||||
the Flutter tool needs it to communicate with the running application
|
||||
to allow setting breakpoints, to provide hot reload, etc.
|
||||
-->
|
||||
<uses-permission android:name="android.permission.INTERNET"/>
|
||||
</manifest>
|
||||
@@ -0,0 +1,24 @@
|
||||
allprojects {
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
}
|
||||
}
|
||||
|
||||
val newBuildDir: Directory =
|
||||
rootProject.layout.buildDirectory
|
||||
.dir("../../build")
|
||||
.get()
|
||||
rootProject.layout.buildDirectory.value(newBuildDir)
|
||||
|
||||
subprojects {
|
||||
val newSubprojectBuildDir: Directory = newBuildDir.dir(project.name)
|
||||
project.layout.buildDirectory.value(newSubprojectBuildDir)
|
||||
}
|
||||
subprojects {
|
||||
project.evaluationDependsOn(":app")
|
||||
}
|
||||
|
||||
tasks.register<Delete>("clean") {
|
||||
delete(rootProject.layout.buildDirectory)
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
org.gradle.jvmargs=-Xmx8G -XX:MaxMetaspaceSize=4G -XX:ReservedCodeCacheSize=512m -XX:+HeapDumpOnOutOfMemoryError
|
||||
android.useAndroidX=true
|
||||
# This newDsl flag was added by the Flutter template
|
||||
android.newDsl=false
|
||||
# This builtInKotlin flag was added by the Flutter template
|
||||
android.builtInKotlin=false
|
||||
@@ -0,0 +1,5 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-9.3.1-all.zip
|
||||
@@ -0,0 +1,26 @@
|
||||
pluginManagement {
|
||||
val flutterSdkPath =
|
||||
run {
|
||||
val properties = java.util.Properties()
|
||||
file("local.properties").inputStream().use { properties.load(it) }
|
||||
val flutterSdkPath = properties.getProperty("flutter.sdk")
|
||||
require(flutterSdkPath != null) { "flutter.sdk not set in local.properties" }
|
||||
flutterSdkPath
|
||||
}
|
||||
|
||||
includeBuild("$flutterSdkPath/packages/flutter_tools/gradle")
|
||||
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
gradlePluginPortal()
|
||||
}
|
||||
}
|
||||
|
||||
plugins {
|
||||
id("dev.flutter.flutter-plugin-loader") version "1.0.0"
|
||||
id("com.android.application") version "9.1.0" apply false
|
||||
id("org.jetbrains.kotlin.android") version "2.4.0" apply false
|
||||
}
|
||||
|
||||
include(":app")
|
||||
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 9.1 KiB |
|
After Width: | Height: | Size: 1.8 KiB |
|
After Width: | Height: | Size: 1.9 KiB |
|
After Width: | Height: | Size: 2.1 KiB |
|
After Width: | Height: | Size: 2.4 KiB |
|
After Width: | Height: | Size: 2.7 KiB |
|
After Width: | Height: | Size: 2.9 KiB |
|
After Width: | Height: | Size: 3.1 KiB |
|
After Width: | Height: | Size: 3.3 KiB |
|
After Width: | Height: | Size: 3.3 KiB |
|
After Width: | Height: | Size: 3.3 KiB |
|
After Width: | Height: | Size: 3.4 KiB |
|
After Width: | Height: | Size: 3.5 KiB |
|
After Width: | Height: | Size: 3.4 KiB |
|
After Width: | Height: | Size: 3.4 KiB |
|
After Width: | Height: | Size: 3.3 KiB |
|
After Width: | Height: | Size: 3.3 KiB |
|
After Width: | Height: | Size: 3.2 KiB |
|
After Width: | Height: | Size: 3.1 KiB |
|
After Width: | Height: | Size: 3.0 KiB |
|
After Width: | Height: | Size: 2.9 KiB |
|
After Width: | Height: | Size: 2.6 KiB |
|
After Width: | Height: | Size: 2.5 KiB |
|
After Width: | Height: | Size: 2.4 KiB |
|
After Width: | Height: | Size: 2.3 KiB |
@@ -0,0 +1,148 @@
|
||||
{
|
||||
"region": "piemonte",
|
||||
"product": "VMI",
|
||||
"generatedAt": 1768485300000,
|
||||
"bbox": [
|
||||
6.55,
|
||||
43.95,
|
||||
9.3,
|
||||
46.55
|
||||
],
|
||||
"crs": "EPSG:3857",
|
||||
"frames": [
|
||||
{
|
||||
"ts": 1768478400000,
|
||||
"url": "frames/1768478400000.png"
|
||||
},
|
||||
{
|
||||
"ts": 1768478700000,
|
||||
"url": "frames/1768478700000.png"
|
||||
},
|
||||
{
|
||||
"ts": 1768479000000,
|
||||
"url": "frames/1768479000000.png"
|
||||
},
|
||||
{
|
||||
"ts": 1768479300000,
|
||||
"url": "frames/1768479300000.png"
|
||||
},
|
||||
{
|
||||
"ts": 1768479600000,
|
||||
"url": "frames/1768479600000.png"
|
||||
},
|
||||
{
|
||||
"ts": 1768479900000,
|
||||
"url": "frames/1768479900000.png"
|
||||
},
|
||||
{
|
||||
"ts": 1768480200000,
|
||||
"url": "frames/1768480200000.png"
|
||||
},
|
||||
{
|
||||
"ts": 1768480500000,
|
||||
"url": "frames/1768480500000.png"
|
||||
},
|
||||
{
|
||||
"ts": 1768480800000,
|
||||
"url": "frames/1768480800000.png"
|
||||
},
|
||||
{
|
||||
"ts": 1768481100000,
|
||||
"url": "frames/1768481100000.png"
|
||||
},
|
||||
{
|
||||
"ts": 1768481400000,
|
||||
"url": "frames/1768481400000.png"
|
||||
},
|
||||
{
|
||||
"ts": 1768481700000,
|
||||
"url": "frames/1768481700000.png"
|
||||
},
|
||||
{
|
||||
"ts": 1768482000000,
|
||||
"url": "frames/1768482000000.png"
|
||||
},
|
||||
{
|
||||
"ts": 1768482300000,
|
||||
"url": "frames/1768482300000.png"
|
||||
},
|
||||
{
|
||||
"ts": 1768482600000,
|
||||
"url": "frames/1768482600000.png"
|
||||
},
|
||||
{
|
||||
"ts": 1768482900000,
|
||||
"url": "frames/1768482900000.png"
|
||||
},
|
||||
{
|
||||
"ts": 1768483200000,
|
||||
"url": "frames/1768483200000.png"
|
||||
},
|
||||
{
|
||||
"ts": 1768483500000,
|
||||
"url": "frames/1768483500000.png"
|
||||
},
|
||||
{
|
||||
"ts": 1768483800000,
|
||||
"url": "frames/1768483800000.png"
|
||||
},
|
||||
{
|
||||
"ts": 1768484100000,
|
||||
"url": "frames/1768484100000.png"
|
||||
},
|
||||
{
|
||||
"ts": 1768484400000,
|
||||
"url": "frames/1768484400000.png"
|
||||
},
|
||||
{
|
||||
"ts": 1768484700000,
|
||||
"url": "frames/1768484700000.png"
|
||||
},
|
||||
{
|
||||
"ts": 1768485000000,
|
||||
"url": "frames/1768485000000.png"
|
||||
},
|
||||
{
|
||||
"ts": 1768485300000,
|
||||
"url": "frames/1768485300000.png"
|
||||
}
|
||||
],
|
||||
"legend": {
|
||||
"unit": "dBZ",
|
||||
"stops": [
|
||||
{
|
||||
"value": 5,
|
||||
"color": "#4FA3D1"
|
||||
},
|
||||
{
|
||||
"value": 10,
|
||||
"color": "#2E7DBE"
|
||||
},
|
||||
{
|
||||
"value": 20,
|
||||
"color": "#35A64A"
|
||||
},
|
||||
{
|
||||
"value": 30,
|
||||
"color": "#E9D22B"
|
||||
},
|
||||
{
|
||||
"value": 35,
|
||||
"color": "#EC8B2A"
|
||||
},
|
||||
{
|
||||
"value": 40,
|
||||
"color": "#D63B28"
|
||||
},
|
||||
{
|
||||
"value": 45,
|
||||
"color": "#A61E1E"
|
||||
},
|
||||
{
|
||||
"value": 50,
|
||||
"color": "#B028B0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"attribution": "Dati dimostrativi generati da tool/generate_mock_frames.py"
|
||||
}
|
||||
@@ -0,0 +1,173 @@
|
||||
{
|
||||
"id": "piemonte",
|
||||
"displayName": "Piemonte",
|
||||
"timeZone": "Europe/Rome",
|
||||
"bbox": [
|
||||
6.55,
|
||||
43.95,
|
||||
9.3,
|
||||
46.55
|
||||
],
|
||||
"map": {
|
||||
"center": [
|
||||
7.95,
|
||||
45.25
|
||||
],
|
||||
"zoom": {
|
||||
"min": 6.0,
|
||||
"max": 13.0,
|
||||
"initial": 7.2
|
||||
}
|
||||
},
|
||||
"sources": {
|
||||
"radar": {
|
||||
"defaultAdapter": "mock",
|
||||
"availableAdapters": [
|
||||
"mock",
|
||||
"dpc"
|
||||
],
|
||||
"product": "VMI",
|
||||
"frameIntervalMinutes": 5
|
||||
},
|
||||
"alerts": {
|
||||
"defaultAdapter": "arpaCap",
|
||||
"availableAdapters": [
|
||||
"arpaCap"
|
||||
],
|
||||
"officialBulletinUrl": "https://www.arpa.piemonte.it/rischi_naturali/boll/bollettino_allerta.pdf"
|
||||
}
|
||||
},
|
||||
"alertZones": [
|
||||
{
|
||||
"code": "Piem-A",
|
||||
"name": "Toce",
|
||||
"provinces": [
|
||||
"NO",
|
||||
"VB"
|
||||
]
|
||||
},
|
||||
{
|
||||
"code": "Piem-B",
|
||||
"name": "Val Sesia, Cervo e Chiusella",
|
||||
"provinces": [
|
||||
"BI",
|
||||
"NO",
|
||||
"TO",
|
||||
"VC"
|
||||
]
|
||||
},
|
||||
{
|
||||
"code": "Piem-C",
|
||||
"name": "Valli Orco, Lanzo, bassa Val Susa e Sangone",
|
||||
"provinces": [
|
||||
"TO"
|
||||
]
|
||||
},
|
||||
{
|
||||
"code": "Piem-D",
|
||||
"name": "Alta Val Susa, Valli Chisone, Pellice e Po",
|
||||
"provinces": [
|
||||
"CN",
|
||||
"TO"
|
||||
]
|
||||
},
|
||||
{
|
||||
"code": "Piem-E",
|
||||
"name": "Valli Varaita, Maira e Stura",
|
||||
"provinces": [
|
||||
"CN"
|
||||
]
|
||||
},
|
||||
{
|
||||
"code": "Piem-F",
|
||||
"name": "Valle Tanaro",
|
||||
"provinces": [
|
||||
"CN"
|
||||
]
|
||||
},
|
||||
{
|
||||
"code": "Piem-G",
|
||||
"name": "Belbo e Bormida",
|
||||
"provinces": [
|
||||
"AL",
|
||||
"AT",
|
||||
"CN"
|
||||
]
|
||||
},
|
||||
{
|
||||
"code": "Piem-H",
|
||||
"name": "Scrivia",
|
||||
"provinces": [
|
||||
"AL"
|
||||
]
|
||||
},
|
||||
{
|
||||
"code": "Piem-I",
|
||||
"name": "Pianura Settentrionale",
|
||||
"provinces": [
|
||||
"AL",
|
||||
"AT",
|
||||
"BI",
|
||||
"NO",
|
||||
"TO",
|
||||
"VC"
|
||||
]
|
||||
},
|
||||
{
|
||||
"code": "Piem-L",
|
||||
"name": "Pianura Torinese e Colline",
|
||||
"provinces": [
|
||||
"AL",
|
||||
"AT",
|
||||
"CN",
|
||||
"TO"
|
||||
]
|
||||
},
|
||||
{
|
||||
"code": "Piem-M",
|
||||
"name": "Pianura Cuneese",
|
||||
"provinces": [
|
||||
"CN",
|
||||
"TO"
|
||||
]
|
||||
}
|
||||
],
|
||||
"attributions": [
|
||||
{
|
||||
"id": "dpc",
|
||||
"text": "Radar-DPC — Dipartimento della Protezione Civile",
|
||||
"license": "CC BY-SA",
|
||||
"url": "https://radar.protezionecivile.it"
|
||||
},
|
||||
{
|
||||
"id": "arpa",
|
||||
"text": "Arpa Piemonte",
|
||||
"license": null,
|
||||
"url": "https://www.arpa.piemonte.it"
|
||||
},
|
||||
{
|
||||
"id": "osm",
|
||||
"text": "© OpenStreetMap contributors",
|
||||
"license": "ODbL",
|
||||
"url": "https://www.openstreetmap.org/copyright"
|
||||
},
|
||||
{
|
||||
"id": "openmaptiles",
|
||||
"text": "© OpenMapTiles",
|
||||
"license": "BSD 3-Clause",
|
||||
"url": "https://openmaptiles.org/"
|
||||
},
|
||||
{
|
||||
"id": "openfreemap",
|
||||
"text": "OpenFreeMap",
|
||||
"license": "MIT",
|
||||
"url": "https://openfreemap.org/"
|
||||
},
|
||||
{
|
||||
"id": "istat",
|
||||
"text": "Istat — Confini delle unità amministrative",
|
||||
"license": "CC BY 4.0",
|
||||
"url": "https://www.istat.it/notizia/confini-delle-unita-amministrative-a-fini-statistici/"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
**/dgph
|
||||
*.mode1v3
|
||||
*.mode2v3
|
||||
*.moved-aside
|
||||
*.pbxuser
|
||||
*.perspectivev3
|
||||
**/*sync/
|
||||
.sconsign.dblite
|
||||
.tags*
|
||||
**/.vagrant/
|
||||
**/DerivedData/
|
||||
Icon?
|
||||
**/Pods/
|
||||
**/.symlinks/
|
||||
profile
|
||||
xcuserdata
|
||||
**/.generated/
|
||||
Flutter/App.framework
|
||||
Flutter/Flutter.framework
|
||||
Flutter/Flutter.podspec
|
||||
Flutter/Generated.xcconfig
|
||||
Flutter/ephemeral/
|
||||
Flutter/app.flx
|
||||
Flutter/app.zip
|
||||
Flutter/flutter_assets/
|
||||
Flutter/flutter_export_environment.sh
|
||||
ServiceDefinitions.json
|
||||
Runner/GeneratedPluginRegistrant.*
|
||||
|
||||
# Exceptions to above rules.
|
||||
!default.mode1v3
|
||||
!default.mode2v3
|
||||
!default.pbxuser
|
||||
!default.perspectivev3
|
||||
@@ -0,0 +1,24 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>en</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>App</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>io.flutter.flutter.app</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>App</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>FMWK</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.0</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>1.0</string>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -0,0 +1 @@
|
||||
#include "Generated.xcconfig"
|
||||
@@ -0,0 +1 @@
|
||||
#include "Generated.xcconfig"
|
||||