Initial Flutter-Projekt-Setup (AP0)

Grundgerüst für die Emergency-App: Flutter-Projekt für Android/iOS,
Kern-Packages (url_launcher, geolocator, geocoding, permission_handler,
provider, shared_preferences, intl), Ordnerstruktur laut docs/plan.md
und l10n-Grundgerüst.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
root
2026-07-24 09:45:40 +02:00
co-authored by Claude Sonnet 5
commit 40e95cc495
91 changed files with 3348 additions and 0 deletions
+174
View File
@@ -0,0 +1,174 @@
# Architektur- & Implementierungsplan: Emergency App
Status: **entscheidungsreif** (alle offenen Fragen aus der Architektur-Review geklärt am 2026-07-24)
Basis: [`../anforderung.md`](../anforderung.md)
## 1. Kontext
Flutter-App (Android + iOS) mit sicherheitskritischer Kernfunktion: **Notruf auslösen**. Die App ermittelt das aktuelle Land, zeigt einen oder mehrere große Buttons mit den landesspezifischen Notrufnummern (Allgemein / Feuerwehr / Polizei / Rettung), ein Tap öffnet die native Telefon-App mit vorgewählter Nummer.
**Grundsatz:** Diese App ist ein Sicherheitswerkzeug. Sie muss **offline funktionieren**, **schnell starten**, und darf **niemals** in einem Zustand landen, in dem keine Notrufnummer erreichbar ist. Jede Architekturentscheidung ordnet sich diesem Grundsatz unter.
## 2. Getroffene Entscheidungen
| Thema | Entscheidung | Begründung |
|---|---|---|
| Persistenz | `shared_preferences` (kein Hive) | Nur 1-2 Strings zu cachen (letzter Ländercode, manuelle Auswahl); Notrufnummern sind read-only JSON-Asset. Kein build_runner/TypeAdapter-Overhead nötig. |
| Ländererkennung | **SIM/Netz-Ländercode zuerst**, GPS+Reverse-Geocoding als Fallback | Offline verfügbar; zeigt an, in welchem Mobilfunknetz man eingebucht ist genau dieses Netz wickelt den Notruf ab. Bei Konflikt (Reisender mit Heimat-SIM) gewinnt automatisch die SIM-Quelle, kein manueller Auswahldialog nötig. |
| Notruf-Auslösung | `tel:`-Scheme öffnet Wähler, **kein Auto-Dial** | Sicherer gegen Fehlauslösung; identisches Verhalten auf Android und iOS (Auto-Dial auf iOS technisch ohnehin nicht möglich, auf Android nur mit heikler `CALL_PHONE`-Permission). |
| Panikmodus | Globaler 112/911-Button **immer sichtbar**, auch während Ländererkennung läuft | Kein Ladezustand darf ohne funktionierenden Notruf-Button sein. |
| Lokalisierung | Sprache an erkanntes Land gekoppelt | Automatische UI-Sprache passend zum Land statt nur DE/EN. Mehr ARB-Dateien/Übersetzungsaufwand bereits in v1 eingeplant. |
| Disclaimer | Permanent sichtbar/abrufbar (z. B. Info-/Einstellungsbereich) | Haftungsrisiko bei falschen/veralteten Nummern; Hinweis „im Zweifel 112 wählen". |
| Datenpflege | Statisches JSON-Asset, **kein** Remote-Update-Mechanismus | Passt zum Offline-Grundsatz; Aktualisierung nur über App-Updates. |
## 3. Projekt-Setup
```
flutter create --org co.selfhost.hhml --platforms=android,ios emergency
```
Danach `git init`.
### Packages (`pubspec.yaml`)
| Package | Zweck |
|---|---|
| `url_launcher` | `tel:`-Scheme → native Telefon-App |
| `geolocator` | GPS-Position (Fallback-Quelle) |
| `geocoding` | Reverse-Geocoding lat/lng → ISO-Ländercode (braucht ggf. Netz) |
| Platform-Channel bzw. Package für SIM-/Netz-Ländercode (Android: `TelephonyManager.getNetworkCountryIso()`; iOS: CoreTelephony, ab iOS 16 eingeschränkt zuverlässig **Rechercheposten in AP4**) | Primärquelle Ländererkennung |
| `permission_handler` | Standort-Berechtigungs-UX |
| `provider` | State Management (ChangeNotifier) |
| `shared_preferences` | Cache: letzter Ländercode, manuelle Auswahl |
| `flutter_localizations` / `intl` | Lokalisierung, gekoppelt an erkanntes Land |
## 4. Datenmodell & Datenhaltung
**Quelle:** Wikipedia „List of emergency telephone numbers" als Ausgangsbasis, Gegenprüfung via ITU-T/offizielle Behördenseiten. Einmalig aufbereitet, als JSON versioniert, kein Live-Fetch.
**Format** (`assets/data/emergency_numbers.json`), ISO 3166-1 alpha-2 als Schlüssel:
```jsonc
{
"DE": {
"name_en": "Germany", "name_de": "Deutschland", "flag": "🇩🇪",
"numbers": [
{ "type": "general", "number": "112", "label_de": "Notruf", "label_en": "Emergency" },
{ "type": "police", "number": "110", "label_de": "Polizei", "label_en": "Police" }
]
}
}
```
`type`-Enum: `general | police | fire | ambulance | sea_rescue | mountain_rescue` (für typspezifische Icons/Farben).
**Dart-Modelle:** `EmergencyNumber`, `Country` (mit `fromJson`).
**Pflege:** JSON im Repo, reviewbar per Diff. Optionales Hilfsskript (`tool/`) zur Generierung aus CSV, kein App-Bestandteil.
## 5. App-Architektur
**State Management:** Ein `EmergencyProvider` (ChangeNotifier).
```
EmergencyProvider
├─ state: EmergencyState { status, country, numbers, errorType }
│ status ∈ { initializing, locating, ready, permissionDenied,
│ countryUnknown, error }
├─ init() → startet Ermittlungskette
├─ retry()
└─ setCountryManually()
```
### Ordnerstruktur
```
lib/
├─ main.dart
├─ app.dart
├─ models/
│ ├─ country.dart
│ ├─ emergency_number.dart
│ └─ emergency_type.dart
├─ repositories/
│ └─ emergency_number_repository.dart
├─ services/
│ ├─ location_service.dart # Ermittlungskette
│ ├─ country_code_sources/ # SIM, GPS, cached, manual
│ └─ dialer_service.dart
├─ providers/
│ └─ emergency_provider.dart
├─ features/
│ └─ home/
│ ├─ home_screen.dart
│ └─ widgets/
│ ├─ emergency_button.dart # inkl. globaler Panik-Button
│ ├─ country_header.dart
│ └─ status_banner.dart
├─ l10n/
│ └─ app_<lang>.arb # pro unterstütztem Land/Sprache
└─ core/
├─ constants.dart # Fallback-Nummern, Default-Land
└─ result.dart
assets/data/emergency_numbers.json
```
**Schichtentrennung:** UI kennt nur den Provider; Provider orchestriert Services + Repository ohne Flutter-Context; Services/Repository sind plattformnah bzw. datennah und gut testbar.
## 6. Ablauf
```
App-Start
→ EmergencyProvider.init()
→ globaler 112/911-Button sofort sichtbar (Panikmodus)
→ LocationService.resolveCountryCode():
1. SIM/Netz-Ländercode (offline, primär)
2. GPS + Reverse-Geocoding (Fallback, ggf. Netz nötig)
3. zuletzt gespeicherter Code (shared_preferences)
4. sonst → status = countryUnknown
→ Repository.lookup(isoCode) → Country + numbers
→ Cache: isoCode in shared_preferences speichern
→ UI-Sprache an erkanntes Land koppeln
→ status = ready
→ HomeScreen rendert länderspezifische EmergencyButtons zusätzlich zum Panik-Button
→ Tap → DialerService.call(number) → url_launcher launchUrl(Uri(scheme:'tel', path:number))
→ Telefon-App öffnet sich mit vorgewählter Nummer, Nutzer bestätigt selbst
```
## 7. Fehlerfälle / Edge Cases
| Fall | Verhalten |
|---|---|
| Standort-Berechtigung verweigert | App bleibt voll nutzbar; SIM-Ländercode/Cache/manuelle Auswahl, nicht-blockierender Banner. |
| GPS aus / kein Fix | Kette fällt auf SIM/Cache/manuell zurück. |
| Kein Internet | Kernfunktion voll erhalten (JSON-Asset lokal, SIM-Code offline, Wähler braucht kein Netz); nur Reverse-Geocoding kann ausfallen. |
| Land nicht in Datenbasis | Fallback auf 112 (GSM-weit) und 911, mit Hinweis. |
| Land nicht ermittelbar | `countryUnknown`-Screen: Panik-Button + Länder-Suchfeld. |
| Mehrere Nummern pro Land | Liste von Buttons, `general` zuoberst/am größten. |
| `tel:` nicht öffnbar (z. B. Tablet) | `canLaunchUrl`-Check; sonst Nummer groß anzeigen + Copy-Button. |
| Ladezustand | Panik-Button von Anfang an sichtbar (siehe Ablauf). |
### Plattformspezifika
- **Android:** `ACCESS_FINE/COARSE_LOCATION`-Permissions; `<queries>`-Eintrag für `tel:`-Scheme (Android 11+ Package Visibility).
- **iOS:** `NSLocationWhenInUseUsageDescription`; `LSApplicationQueriesSchemes` mit `tel`. SIM-Ländercode über CoreTelephony ab iOS 16 unzuverlässig → GPS-Fallback wichtiger auf iOS.
- Kein Web/Desktop (Scope laut Anforderung nur Android/iOS).
## 8. Schritt-für-Schritt-Implementierungsplan
1. **AP0 Setup:** `flutter create`, Git-Init, Packages, Ordnerstruktur, l10n-Grundgerüst.
2. **AP1 Datenbasis:** Notrufnummern recherchieren/aufbereiten → `emergency_numbers.json`; Modelle; `EmergencyNumberRepository` + Unit-Tests.
3. **AP2 Dialer:** `DialerService` mit `url_launcher`, `canLaunchUrl`-Guard; Manifest/Plist-Einträge.
4. **AP3 Statisches UI:** `HomeScreen` + `EmergencyButton` inkl. Panik-Button, zunächst mit hartkodiertem Land, damit der Notruf-Flow früh end-to-end testbar ist.
5. **AP4 Ländererkennung:** `LocationService` mit priorisierter Kette (SIM → GPS/Geocoding → Cache → unknown). **Rechercheposten:** verlässliches Package/Platform-Channel für SIM-Ländercode je Plattform, insbesondere iOS-16-Einschränkung.
6. **AP5 State-Integration:** `EmergencyProvider`, Verdrahtung Service↔Repository↔UI, alle `status`-Zustände.
7. **AP6 Fallbacks & manuelle Auswahl:** `CountryPickerScreen`, Cache via `shared_preferences`, globale 112/911-Fallbacks.
8. **AP7 Lokalisierung:** UI-Sprache an erkanntes Land koppeln, ARB-Dateien für Zielsprachen.
9. **AP8 Disclaimer & Feinschliff:** Permanent abrufbarer Haftungsausschluss, Barrierefreiheit (große Touch-Ziele, hoher Kontrast, Screenreader-Labels), Datenstand-Hinweis, App-Icon/Name.
10. **AP9 Tests & Härtung:** Unit-Tests Repository/Service-Kette, Widget-Tests HomeScreen-Zustände, manuelle Tests auf realen Geräten (Flugmodus, ohne SIM, Ausland-Simulation).
11. **AP10 Store-Vorbereitung:** Datenschutzerklärung (Standortnutzung), Store-Texte, Screenshots.
## 9. Verbleibendes Risiko
- **SIM-Ländercode auf iOS 16+:** `CTCarrier.isoCountryCode` ist deprecated und liefert teils unzuverlässige Werte. Konkrete Lösung (Package-Wahl oder Eigenimplementierung via Platform-Channel) muss in AP4 recherchiert/prototypisch verifiziert werden.
- **Rechtliche Absicherung des Disclaimers:** Formulierung sollte vor Store-Veröffentlichung juristisch geprüft werden.