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:
root
2026-07-24 11:29:38 +02:00
co-authored by Claude Sonnet 5
parent 40e95cc495
commit e3aa088bc2
54 changed files with 7550 additions and 145 deletions
+13
View File
@@ -0,0 +1,13 @@
{
"@@locale": "de",
"appTitle": "Notruf",
"selectCountryButton": "Land manuell wählen",
"retryButton": "Erneut versuchen",
"errorGeneric": "Unbekannter Fehler beim Laden der Notrufnummern.",
"countryUnknownMessage": "Land konnte nicht ermittelt werden.",
"countryNotInDatabaseMessage": "Land nicht in der Notrufnummern-Datenbasis hinterlegt. Es steht nur der globale Notruf zur Verfügung.",
"pickerTitle": "Land auswählen",
"pickerSearchHint": "Land suchen",
"pickerNoResults": "Kein Land gefunden.",
"emergencyButtonSemanticLabel": "{label}, {number} anrufen"
}
+49 -1
View File
@@ -1,3 +1,51 @@
{
"@@locale": "en"
"@@locale": "en",
"appTitle": "Emergency",
"@appTitle": {
"description": "AppBar title of the home screen"
},
"selectCountryButton": "Select country manually",
"@selectCountryButton": {
"description": "Label of the button/tooltip used to open the manual country picker"
},
"retryButton": "Try again",
"@retryButton": {
"description": "Label of the button that retries country/number detection after an error"
},
"errorGeneric": "Unknown error while loading the emergency numbers.",
"@errorGeneric": {
"description": "Fallback error message shown when the provider does not report a specific error"
},
"countryUnknownMessage": "Country could not be determined.",
"@countryUnknownMessage": {
"description": "Shown when the country could not be detected automatically (and no location permission info is shown separately)"
},
"countryNotInDatabaseMessage": "Country not listed in the emergency number database. Only the global emergency number is available.",
"@countryNotInDatabaseMessage": {
"description": "Shown when the detected country has no entry in the emergency number database"
},
"pickerTitle": "Select country",
"@pickerTitle": {
"description": "AppBar title of the manual country picker screen"
},
"pickerSearchHint": "Search country",
"@pickerSearchHint": {
"description": "Hint text of the search field in the manual country picker screen"
},
"pickerNoResults": "No country found.",
"@pickerNoResults": {
"description": "Empty state shown when the search filter matches no country"
},
"emergencyButtonSemanticLabel": "{label}, call {number}",
"@emergencyButtonSemanticLabel": {
"description": "Semantics/tooltip label of an emergency button, combining the number's label and the number itself",
"placeholders": {
"label": {
"type": "String"
},
"number": {
"type": "String"
}
}
}
}
+13
View File
@@ -0,0 +1,13 @@
{
"@@locale": "es",
"appTitle": "Emergencia",
"selectCountryButton": "Elegir país manualmente",
"retryButton": "Reintentar",
"errorGeneric": "Error desconocido al cargar los números de emergencia.",
"countryUnknownMessage": "No se pudo determinar el país.",
"countryNotInDatabaseMessage": "País no disponible en la base de datos de números de emergencia. Solo está disponible el número de emergencia global.",
"pickerTitle": "Seleccionar país",
"pickerSearchHint": "Buscar país",
"pickerNoResults": "No se encontró ningún país.",
"emergencyButtonSemanticLabel": "{label}, llamar al {number}"
}
+13
View File
@@ -0,0 +1,13 @@
{
"@@locale": "fr",
"appTitle": "Urgence",
"selectCountryButton": "Choisir le pays manuellement",
"retryButton": "Réessayer",
"errorGeneric": "Erreur inconnue lors du chargement des numéros d'urgence.",
"countryUnknownMessage": "Impossible de déterminer le pays.",
"countryNotInDatabaseMessage": "Pays non répertorié dans la base de données des numéros d'urgence. Seul le numéro d'urgence global est disponible.",
"pickerTitle": "Sélectionner un pays",
"pickerSearchHint": "Rechercher un pays",
"pickerNoResults": "Aucun pays trouvé.",
"emergencyButtonSemanticLabel": "{label}, appeler le {number}"
}
+76 -2
View File
@@ -5,7 +5,10 @@ import 'package:flutter/widgets.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:intl/intl.dart' as intl;
import 'app_localizations_de.dart';
import 'app_localizations_en.dart';
import 'app_localizations_es.dart';
import 'app_localizations_fr.dart';
// ignore_for_file: type=lint
@@ -92,7 +95,72 @@ abstract class AppLocalizations {
];
/// A list of this localizations delegate's supported locales.
static const List<Locale> supportedLocales = <Locale>[Locale('en')];
static const List<Locale> supportedLocales = <Locale>[
Locale('de'),
Locale('en'),
Locale('es'),
Locale('fr'),
];
/// AppBar title of the home screen
///
/// In en, this message translates to:
/// **'Emergency'**
String get appTitle;
/// Label of the button/tooltip used to open the manual country picker
///
/// In en, this message translates to:
/// **'Select country manually'**
String get selectCountryButton;
/// Label of the button that retries country/number detection after an error
///
/// In en, this message translates to:
/// **'Try again'**
String get retryButton;
/// Fallback error message shown when the provider does not report a specific error
///
/// In en, this message translates to:
/// **'Unknown error while loading the emergency numbers.'**
String get errorGeneric;
/// Shown when the country could not be detected automatically (and no location permission info is shown separately)
///
/// In en, this message translates to:
/// **'Country could not be determined.'**
String get countryUnknownMessage;
/// Shown when the detected country has no entry in the emergency number database
///
/// In en, this message translates to:
/// **'Country not listed in the emergency number database. Only the global emergency number is available.'**
String get countryNotInDatabaseMessage;
/// AppBar title of the manual country picker screen
///
/// In en, this message translates to:
/// **'Select country'**
String get pickerTitle;
/// Hint text of the search field in the manual country picker screen
///
/// In en, this message translates to:
/// **'Search country'**
String get pickerSearchHint;
/// Empty state shown when the search filter matches no country
///
/// In en, this message translates to:
/// **'No country found.'**
String get pickerNoResults;
/// Semantics/tooltip label of an emergency button, combining the number's label and the number itself
///
/// In en, this message translates to:
/// **'{label}, call {number}'**
String emergencyButtonSemanticLabel(String label, String number);
}
class _AppLocalizationsDelegate
@@ -106,7 +174,7 @@ class _AppLocalizationsDelegate
@override
bool isSupported(Locale locale) =>
<String>['en'].contains(locale.languageCode);
<String>['de', 'en', 'es', 'fr'].contains(locale.languageCode);
@override
bool shouldReload(_AppLocalizationsDelegate old) => false;
@@ -115,8 +183,14 @@ class _AppLocalizationsDelegate
AppLocalizations lookupAppLocalizations(Locale locale) {
// Lookup logic when only language code is specified.
switch (locale.languageCode) {
case 'de':
return AppLocalizationsDe();
case 'en':
return AppLocalizationsEn();
case 'es':
return AppLocalizationsEs();
case 'fr':
return AppLocalizationsFr();
}
throw FlutterError(
+43
View File
@@ -0,0 +1,43 @@
// ignore: unused_import
import 'package:intl/intl.dart' as intl;
import 'app_localizations.dart';
// ignore_for_file: type=lint
/// The translations for German (`de`).
class AppLocalizationsDe extends AppLocalizations {
AppLocalizationsDe([String locale = 'de']) : super(locale);
@override
String get appTitle => 'Notruf';
@override
String get selectCountryButton => 'Land manuell wählen';
@override
String get retryButton => 'Erneut versuchen';
@override
String get errorGeneric => 'Unbekannter Fehler beim Laden der Notrufnummern.';
@override
String get countryUnknownMessage => 'Land konnte nicht ermittelt werden.';
@override
String get countryNotInDatabaseMessage =>
'Land nicht in der Notrufnummern-Datenbasis hinterlegt. Es steht nur der globale Notruf zur Verfügung.';
@override
String get pickerTitle => 'Land auswählen';
@override
String get pickerSearchHint => 'Land suchen';
@override
String get pickerNoResults => 'Kein Land gefunden.';
@override
String emergencyButtonSemanticLabel(String label, String number) {
return '$label, $number anrufen';
}
}
+34
View File
@@ -7,4 +7,38 @@ import 'app_localizations.dart';
/// The translations for English (`en`).
class AppLocalizationsEn extends AppLocalizations {
AppLocalizationsEn([String locale = 'en']) : super(locale);
@override
String get appTitle => 'Emergency';
@override
String get selectCountryButton => 'Select country manually';
@override
String get retryButton => 'Try again';
@override
String get errorGeneric =>
'Unknown error while loading the emergency numbers.';
@override
String get countryUnknownMessage => 'Country could not be determined.';
@override
String get countryNotInDatabaseMessage =>
'Country not listed in the emergency number database. Only the global emergency number is available.';
@override
String get pickerTitle => 'Select country';
@override
String get pickerSearchHint => 'Search country';
@override
String get pickerNoResults => 'No country found.';
@override
String emergencyButtonSemanticLabel(String label, String number) {
return '$label, call $number';
}
}
+44
View File
@@ -0,0 +1,44 @@
// ignore: unused_import
import 'package:intl/intl.dart' as intl;
import 'app_localizations.dart';
// ignore_for_file: type=lint
/// The translations for Spanish Castilian (`es`).
class AppLocalizationsEs extends AppLocalizations {
AppLocalizationsEs([String locale = 'es']) : super(locale);
@override
String get appTitle => 'Emergencia';
@override
String get selectCountryButton => 'Elegir país manualmente';
@override
String get retryButton => 'Reintentar';
@override
String get errorGeneric =>
'Error desconocido al cargar los números de emergencia.';
@override
String get countryUnknownMessage => 'No se pudo determinar el país.';
@override
String get countryNotInDatabaseMessage =>
'País no disponible en la base de datos de números de emergencia. Solo está disponible el número de emergencia global.';
@override
String get pickerTitle => 'Seleccionar país';
@override
String get pickerSearchHint => 'Buscar país';
@override
String get pickerNoResults => 'No se encontró ningún país.';
@override
String emergencyButtonSemanticLabel(String label, String number) {
return '$label, llamar al $number';
}
}
+44
View File
@@ -0,0 +1,44 @@
// ignore: unused_import
import 'package:intl/intl.dart' as intl;
import 'app_localizations.dart';
// ignore_for_file: type=lint
/// The translations for French (`fr`).
class AppLocalizationsFr extends AppLocalizations {
AppLocalizationsFr([String locale = 'fr']) : super(locale);
@override
String get appTitle => 'Urgence';
@override
String get selectCountryButton => 'Choisir le pays manuellement';
@override
String get retryButton => 'Réessayer';
@override
String get errorGeneric =>
'Erreur inconnue lors du chargement des numéros d\'urgence.';
@override
String get countryUnknownMessage => 'Impossible de déterminer le pays.';
@override
String get countryNotInDatabaseMessage =>
'Pays non répertorié dans la base de données des numéros d\'urgence. Seul le numéro d\'urgence global est disponible.';
@override
String get pickerTitle => 'Sélectionner un pays';
@override
String get pickerSearchHint => 'Rechercher un pays';
@override
String get pickerNoResults => 'Aucun pays trouvé.';
@override
String emergencyButtonSemanticLabel(String label, String number) {
return '$label, appeler le $number';
}
}