Files
emergency/lib/providers/emergency_provider.dart
T
rootandClaude Sonnet 5 e3aa088bc2 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>
2026-07-24 11:29:38 +02:00

126 lines
5.2 KiB
Dart
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.
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();
}
}