Notrufnummern-Datenbasis, Ländererkennung, State-Management und Lokalisierung (AP1-AP7)
Implementiert den kompletten Kern-Flow der Emergency App gemäß docs/plan.md: - Notrufnummern-Datenbasis für 199 Länder + Modelle/Repository (AP1) - DialerService für tel:-Notrufe ohne Auto-Dial (AP2) - Statisches UI mit Panik-Button-Fallback (AP3) - Priorisierte Ländererkennung: SIM/Netz -> GPS/Geocoding -> Cache (AP4) - EmergencyProvider verdrahtet Repository/LocationService mit der UI (AP5) - Manuelle Länderauswahl als Fallback samt eigener Priorität (AP6) - UI-Lokalisierung (de/en/fr/es), an erkanntes Land gekoppelt (AP7) Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,125 @@
|
||||
import 'package:flutter/foundation.dart';
|
||||
|
||||
import '../models/country.dart';
|
||||
import '../repositories/emergency_number_repository.dart';
|
||||
import '../services/location_service.dart';
|
||||
import 'emergency_status.dart';
|
||||
|
||||
/// Orchestriert Repository + Ländererkennung für die UI
|
||||
/// (`docs/plan.md`, Abschnitt 5 + 6 + 8, AP5).
|
||||
///
|
||||
/// Kennt keinen `BuildContext`; die UI (`HomeScreen`) beobachtet [status]/
|
||||
/// [country]/[errorMessage] über `ChangeNotifier` und ruft [init]/[retry] auf.
|
||||
///
|
||||
/// **Entscheidung zu `EmergencyStatus.permissionDenied`:** `LocationService`
|
||||
/// liefert bewusst nur `String? isoCode` ohne Fehlergrund (AP4-Entscheidung,
|
||||
/// um die Ermittlungskette einfach zu halten). Eine zusätzliche, direkte
|
||||
/// Abfrage z. B. von `Geolocator.checkPermission()` hier im Provider würde
|
||||
/// eine plattformnahe Geolocator-Abhängigkeit in die State-Schicht ziehen,
|
||||
/// obwohl das reine Wissen um "GPS-Berechtigung verweigert" den eigentlichen
|
||||
/// Ermittlungs-Flow gar nicht beeinflusst (SIM/Cache können trotzdem einen
|
||||
/// Treffer liefern → dann ist der Status weiterhin `ready`, nicht
|
||||
/// `permissionDenied`). Das würde Zuständigkeiten vermischen, ohne den
|
||||
/// Nutzer:innen im Ladezustand einen Mehrwert zu bieten. Deshalb bleibt
|
||||
/// `permissionDenied` für AP5 unbenutzt; der Fall "GPS verweigert und keine
|
||||
/// andere Quelle liefert einen Code" wird korrekt (und ausreichend) über
|
||||
/// `countryUnknown` abgebildet. Eine gezieltere Unterscheidung kann bei
|
||||
/// Bedarf in einem späteren AP (Banner/UX-Feinschliff) ergänzt werden.
|
||||
class EmergencyProvider extends ChangeNotifier {
|
||||
// Bewusst keine initializing formals (`this._repository`): Die externen
|
||||
// Konstruktorparameter sollen lesbar `repository`/`locationService`
|
||||
// heißen, die Felder aber `_repository`/`_locationService` (privat).
|
||||
EmergencyProvider({required EmergencyNumberRepository repository, required LocationService locationService})
|
||||
: _repository = repository, // ignore: prefer_initializing_formals
|
||||
_locationService = locationService; // ignore: prefer_initializing_formals
|
||||
|
||||
final EmergencyNumberRepository _repository;
|
||||
final LocationService _locationService;
|
||||
|
||||
EmergencyStatus _status = EmergencyStatus.initializing;
|
||||
Country? _country;
|
||||
String? _errorMessage;
|
||||
bool _disposed = false;
|
||||
|
||||
EmergencyStatus get status => _status;
|
||||
Country? get country => _country;
|
||||
String? get errorMessage => _errorMessage;
|
||||
|
||||
/// Exponiert das Repository für die UI (`docs/plan.md`, Abschnitt 8, AP6):
|
||||
/// Der `CountryPickerScreen` braucht Zugriff auf die bereits geladene
|
||||
/// Länderliste (`EmergencyNumberRepository.allCountries`), ohne eine
|
||||
/// zweite, unabhängige Repository-Instanz zu erzeugen oder das Asset
|
||||
/// erneut zu laden (`load()` ist idempotent, aber unnötig).
|
||||
EmergencyNumberRepository get repository => _repository;
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_disposed = true;
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
/// Wie [notifyListeners], aber ein no-op, wenn der Provider inzwischen
|
||||
/// disposed wurde – kann passieren, wenn [init]/[retry] noch auf eine
|
||||
/// echte Asset-/Plugin-I/O wartet (z. B. asset load, GPS), während die
|
||||
/// UI (z. B. `HomeScreen`) längst nicht mehr im Widgetbaum ist.
|
||||
void _notifyIfActive() {
|
||||
if (!_disposed) notifyListeners();
|
||||
}
|
||||
|
||||
/// Startet die Ermittlungskette: Datenbasis laden → Ländercode ermitteln
|
||||
/// → Land nachschlagen. Wirft nie; Fehler landen im `error`-Status.
|
||||
Future<void> init() async {
|
||||
_status = EmergencyStatus.initializing;
|
||||
_country = null;
|
||||
_errorMessage = null;
|
||||
_notifyIfActive();
|
||||
|
||||
try {
|
||||
await _repository.load();
|
||||
} catch (e) {
|
||||
_status = EmergencyStatus.error;
|
||||
_errorMessage = e.toString();
|
||||
_notifyIfActive();
|
||||
return;
|
||||
}
|
||||
|
||||
_status = EmergencyStatus.locating;
|
||||
_notifyIfActive();
|
||||
|
||||
final isoCode = await _locationService.resolveCountryCode();
|
||||
if (isoCode == null) {
|
||||
_status = EmergencyStatus.countryUnknown;
|
||||
_country = null;
|
||||
_notifyIfActive();
|
||||
return;
|
||||
}
|
||||
|
||||
// country bleibt `null`, wenn der ermittelte isoCode nicht in der
|
||||
// Datenbasis vorhanden ist (Plan Abschnitt 7) – Status ist dennoch
|
||||
// `ready`, die UI fällt dann auf den globalen Panik-Button zurück.
|
||||
_country = _repository.lookup(isoCode);
|
||||
_status = EmergencyStatus.ready;
|
||||
_notifyIfActive();
|
||||
}
|
||||
|
||||
/// Wiederholt die komplette Ermittlungskette (kein separater Zustand nötig).
|
||||
Future<void> retry() => init();
|
||||
|
||||
/// Setzt die manuelle Länderauswahl des Nutzers (`docs/plan.md`,
|
||||
/// Abschnitt 8, AP6: `CountryPickerScreen`) und stößt die
|
||||
/// Ermittlungskette danach sofort erneut an, damit [isoCode] unmittelbar
|
||||
/// wirksam wird (die manuelle Auswahl hat in [LocationService] höchste
|
||||
/// Priorität vor SIM/GPS/Cache).
|
||||
Future<void> selectCountryManually(String isoCode) async {
|
||||
await _locationService.setManualCountryCode(isoCode);
|
||||
await init();
|
||||
}
|
||||
|
||||
/// Löscht die manuelle Länderauswahl wieder und ermittelt das Land danach
|
||||
/// erneut automatisch (SIM → GPS → Cache) – erlaubt dem Nutzer, zur
|
||||
/// automatischen Erkennung zurückzukehren (`docs/plan.md`, Abschnitt 8, AP6).
|
||||
Future<void> clearManualSelection() async {
|
||||
await _locationService.setManualCountryCode(null);
|
||||
await init();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
/// Zustände der Ländererkennungs-/Notrufnummern-Ermittlung
|
||||
/// (`docs/plan.md`, Abschnitt 5 + 8, AP5).
|
||||
///
|
||||
/// Siehe [EmergencyProvider] für die Zustandsübergänge.
|
||||
enum EmergencyStatus {
|
||||
/// [EmergencyProvider.init] wurde gerade gestartet, die Notrufnummern-
|
||||
/// Datenbasis wird geladen.
|
||||
initializing,
|
||||
|
||||
/// Die Datenbasis ist geladen, die Ländererkennungskette
|
||||
/// (SIM → GPS → Cache) läuft gerade.
|
||||
locating,
|
||||
|
||||
/// Ermittlung abgeschlossen. `country` ist gesetzt, wenn das ermittelte
|
||||
/// Land in der Datenbasis vorhanden ist; ist es `null`, wurde zwar ein
|
||||
/// Ländercode ermittelt, aber kein Eintrag dafür gefunden (Plan
|
||||
/// Abschnitt 7, "Land nicht in Datenbasis") – die UI zeigt dann nur den
|
||||
/// globalen Panik-Button.
|
||||
ready,
|
||||
|
||||
/// Aktuell unbenutzt (siehe Doc-Kommentar in [EmergencyProvider]): Die
|
||||
/// Standortberechtigung wurde verweigert. Dieser Fall wird zur Laufzeit
|
||||
/// bewusst nicht von [countryUnknown] unterschieden, ist als Status aber
|
||||
/// für eine spätere, gezieltere UI-Behandlung (z. B. Banner mit
|
||||
/// Berechtigungs-Hinweis) bereits vorbereitet.
|
||||
permissionDenied,
|
||||
|
||||
/// Kein Ländercode ermittelbar (alle Quellen der Ermittlungskette lieferten
|
||||
/// `null`, Plan Abschnitt 7, "Land nicht ermittelbar").
|
||||
countryUnknown,
|
||||
|
||||
/// Laden der Notrufnummern-Datenbasis ist fehlgeschlagen (z. B. kaputtes
|
||||
/// JSON-Asset). Sollte im Normalbetrieb nicht auftreten, siehe Doc-Kommentar
|
||||
/// in `EmergencyNumberRepository.load()`.
|
||||
error,
|
||||
}
|
||||
Reference in New Issue
Block a user