Drop Xamarin skeleton and add project guidelines

The Nuvolari app is being rebuilt as a Flutter project targeting Android
first and iOS later on a single codebase, so the original Xamarin.Android
skeleton no longer applies. Its files were already removed from the working
tree; this records the removal.

Adds CLAUDE.md with the golden rules that constrain the rewrite: English
code and commit messages with Italian UI via ARB, no secrets in the repo,
AdMob test IDs only, no direct polling of ARPA/DPC from the app, and no
server-side precise user locations.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-10 10:36:57 +02:00
co-authored by Claude Opus 5
parent 7a45880059
commit 3b0fce2516
27 changed files with 50 additions and 230 deletions
-5
View File
@@ -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>
+50
View File
@@ -0,0 +1,50 @@
# CLAUDE.md — Radar Meteo Piemonte
## Project
Cross-platform weather-radar app for the Piedmont region (Italy). Android first, iOS later,
single Flutter codebase. Free app monetized with AdMob (GDPR consent via UMP).
Region-scoped now (Piemonte) but extensible to other regions via configuration.
## 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.
- Use ONLY AdMob TEST ad unit IDs in development.
- 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.
- 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).
- Radar data via RadarSource interface with adapters:
DpcRadarSource (default, public), ArpaRadarSource (stub, awaits authorization),
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 (MVP): 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.
- Radar (future): ARPA Piemonte (HDF5 ODIM), access via email info.meteo@arpa.piemonte.it.
License CC BY 4.0 — credit "Fonte: Arpa Piemonte - www.arpa.piemonte.it".
- Forecast: MET Norway api.met.no (CC BY 4.0, commercial OK) — MANDATORY identifying
User-Agent (app name + contact). Fallback: ItaliaMeteo ICON-2I via MeteoHub (CC BY 4.0,
credit "ItaliaMeteo-ARPAE"). Do NOT use Open-Meteo free tier (non-commercial only).
- Alerts: ARPA Piemonte XML-CAP bulletin — reproduce alert levels WITHOUT reinterpreting;
link the official channel.
- Base map: OSM data via PMTiles/MapTiler — show OSM/ODbL attribution.
- Lightning: DO NOT use Blitzortung (commercial use prohibited).
## 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.
## Store / compliance targets
- Google Play: target API 36 (Android 16) by 2026-08-31; closed testing 12 testers / 14 days;
Data safety section; prominent disclosure for location; signed AAB.
- Consent: Google-certified CMP (UMP), IAB TCF 2.3, non-personalized ads if user declines.
- iOS later: keep platform abstractions clean (ATT + privacy nutrition label to add).
## Docs to maintain in /docs
architecture.md, data-sources.md, licenses.md, stack-decisions.md, roadmap.md, privacy.md
-90
View File
@@ -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();
});
}
}
}
}
-20
View File
@@ -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>
-25
View File
@@ -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
-44
View File
@@ -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>
Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 97 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 92 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 100 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 108 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 117 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.7 KiB

@@ -1,4 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="ic_launcher_background">#2C3E50</color>
</resources>
-4
View File
@@ -1,4 +0,0 @@
<resources>
<string name="app_name">Nuvolari</string>
<string name="app_text">Hello, Android!</string>
</resources>