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>
86 lines
3.1 KiB
Dart
86 lines
3.1 KiB
Dart
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();
|
||
}
|
||
}
|