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