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>
74 lines
2.6 KiB
Dart
74 lines
2.6 KiB
Dart
import 'dart:convert';
|
||
|
||
import 'package:flutter/services.dart' show rootBundle;
|
||
|
||
import '../models/country.dart';
|
||
|
||
/// Lädt und cacht die statische Notrufnummern-Datenbasis aus
|
||
/// `assets/data/emergency_numbers.json`.
|
||
///
|
||
/// Die Datei wird nur beim ersten [load]-Aufruf eingelesen und geparst;
|
||
/// danach liefert [lookup] aus dem In-Memory-Cache.
|
||
class EmergencyNumberRepository {
|
||
EmergencyNumberRepository({this.assetPath = 'assets/data/emergency_numbers.json'});
|
||
|
||
final String assetPath;
|
||
|
||
Map<String, Country>? _countriesByIsoCode;
|
||
|
||
/// True, sobald [load] erfolgreich durchgelaufen ist.
|
||
bool get isLoaded => _countriesByIsoCode != null;
|
||
|
||
/// Liest und parst die JSON-Datei einmalig ein.
|
||
///
|
||
/// Wird [load] mehrfach aufgerufen, geschieht nach dem ersten
|
||
/// erfolgreichen Durchlauf nichts mehr (Cache bleibt bestehen).
|
||
///
|
||
/// Wirft eine [FormatException], wenn das JSON fehlerhaft ist – das ist
|
||
/// ein Programmierfehler (kaputtes Asset) und soll beim Start auffallen,
|
||
/// nicht still verschluckt werden.
|
||
Future<void> load() async {
|
||
if (isLoaded) return;
|
||
|
||
final raw = await rootBundle.loadString(assetPath);
|
||
final decoded = jsonDecode(raw);
|
||
|
||
if (decoded is! Map<String, dynamic>) {
|
||
throw const FormatException(
|
||
'emergency_numbers.json: erwartetes Top-Level-Objekt fehlt',
|
||
);
|
||
}
|
||
|
||
final parsed = <String, Country>{};
|
||
for (final entry in decoded.entries) {
|
||
final isoCode = entry.key.toUpperCase();
|
||
parsed[isoCode] = Country.fromJson(isoCode, entry.value as Map<String, dynamic>);
|
||
}
|
||
_countriesByIsoCode = parsed;
|
||
}
|
||
|
||
/// Sucht ein Land anhand seines ISO 3166-1 alpha-2 Codes (case-insensitive).
|
||
///
|
||
/// Gibt `null` zurück, wenn [load] noch nicht aufgerufen wurde oder das
|
||
/// Land nicht in der Datenbasis vorhanden ist – bewusst keine Exception,
|
||
/// damit Aufrufer den Fallback-Pfad (z. B. globaler 112/911-Button) ohne
|
||
/// try/catch abbilden können.
|
||
Country? lookup(String isoCode) {
|
||
final countries = _countriesByIsoCode;
|
||
if (countries == null) return null;
|
||
return countries[isoCode.toUpperCase()];
|
||
}
|
||
|
||
/// Alle Länder der Datenbasis, alphabetisch nach [Country.nameDe] sortiert
|
||
/// (`docs/plan.md`, Abschnitt 8, AP6: Grundlage für den `CountryPickerScreen`).
|
||
///
|
||
/// Liefert eine leere Liste, wenn [load] noch nicht (erfolgreich)
|
||
/// durchgelaufen ist – bewusst kein Fehler, analog zu [lookup].
|
||
List<Country> get allCountries {
|
||
final countries = _countriesByIsoCode;
|
||
if (countries == null) return const [];
|
||
final sorted = countries.values.toList()..sort((a, b) => a.nameDe.compareTo(b.nameDe));
|
||
return sorted;
|
||
}
|
||
}
|