Files
emergency/docs/plan.md
T
rootandClaude Sonnet 5 40e95cc495 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>
2026-07-24 09:45:40 +02:00

175 lines
9.6 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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.