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

9.6 KiB
Raw Blame History

Architektur- & Implementierungsplan: Emergency App

Status: entscheidungsreif (alle offenen Fragen aus der Architektur-Review geklärt am 2026-07-24)

Basis: ../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:

{
  "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.