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,85 @@
|
||||
import 'package:carrier_info/carrier_info.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
|
||||
import 'country_code_source.dart';
|
||||
|
||||
/// Ermittelt den ISO-Ländercode aus SIM-/Netzwerk-Informationen via
|
||||
/// `carrier_info` (`docs/plan.md`, Abschnitt 2 + 3: primäre, offline
|
||||
/// verfügbare Quelle).
|
||||
///
|
||||
/// **Android:** Priorität liegt auf `networkCountryIso` des aktuell
|
||||
/// eingebuchten Mobilfunknetzes (aus `telephonyInfo`) – nicht auf dem
|
||||
/// Heimatland der SIM. Genau dieses Netz wickelt einen Notruf ab, ändert
|
||||
/// sich also korrekt bei Roaming (siehe Plan, Zeile "Bei Konflikt ...
|
||||
/// gewinnt automatisch die SIM-Quelle"). Fällt zurück auf
|
||||
/// `isoCountryCode` (SIM-Ausgabeland laut Telephony API) und zuletzt auf
|
||||
/// `countryIso` aus `subscriptionsInfo`.
|
||||
///
|
||||
/// **iOS:** Apple hat `CTCarrier` ab iOS 16 deprecated (`docs/plan.md`,
|
||||
/// Abschnitt 9 – verbleibendes Risiko). `carrier_info` liefert auf iOS
|
||||
/// 16+ für `isoCountryCode` verlässlich `null` zurück (siehe Paket-Doku
|
||||
/// `iOS_16_DEPRECATION_NOTICE.md`). Das ist eine bekannte Plattform-
|
||||
/// Einschränkung, kein Bug: Diese Quelle liefert dann einfach `null`,
|
||||
/// wodurch `LocationService` automatisch auf GPS/Reverse-Geocoding
|
||||
/// zurückfällt.
|
||||
///
|
||||
/// Alle Plugin-Aufrufe sind defensiv in try/catch gekapselt: Auf
|
||||
/// Emulatoren, Geräten ohne SIM oder bei Plugin-Fehlern kann
|
||||
/// `carrier_info` werfen oder leere/`null`-Werte liefern – in jedem Fall
|
||||
/// liefert [resolve] dann `null`, nie eine Exception.
|
||||
class SimCountryCodeSource implements CountryCodeSource {
|
||||
const SimCountryCodeSource();
|
||||
|
||||
@override
|
||||
Future<String?> resolve() async {
|
||||
try {
|
||||
switch (defaultTargetPlatform) {
|
||||
case TargetPlatform.android:
|
||||
return await _resolveAndroid();
|
||||
case TargetPlatform.iOS:
|
||||
return await _resolveIos();
|
||||
default:
|
||||
// Kein Mobilfunk-Stack auf Desktop/Web-Zielen (auch wenn diese
|
||||
// App laut Scope nur Android/iOS unterstützt, siehe Plan
|
||||
// Abschnitt 7 – defensiv trotzdem abgedeckt).
|
||||
return null;
|
||||
}
|
||||
} catch (_) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
Future<String?> _resolveAndroid() async {
|
||||
final info = await CarrierInfo.getAndroidInfo();
|
||||
if (info == null) return null;
|
||||
|
||||
for (final telephony in info.telephonyInfo) {
|
||||
final code = _normalize(telephony.networkCountryIso);
|
||||
if (code != null) return code;
|
||||
}
|
||||
for (final telephony in info.telephonyInfo) {
|
||||
final code = _normalize(telephony.isoCountryCode);
|
||||
if (code != null) return code;
|
||||
}
|
||||
for (final subscription in info.subscriptionsInfo) {
|
||||
final code = _normalize(subscription.countryIso);
|
||||
if (code != null) return code;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
Future<String?> _resolveIos() async {
|
||||
final info = await CarrierInfo.getIosInfo();
|
||||
for (final carrier in info.carrierData) {
|
||||
final code = _normalize(carrier.isoCountryCode);
|
||||
if (code != null) return code;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
String? _normalize(String? code) {
|
||||
final trimmed = code?.trim();
|
||||
if (trimmed == null || trimmed.isEmpty) return null;
|
||||
return trimmed.toUpperCase();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user