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:
+174
@@ -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.
|
||||
Reference in New Issue
Block a user