import 'package:flutter/services.dart'; /// Ereignis der per [MlrsNetworkController.requestNetwork] angeforderten /// mLRS-WLAN-Verbindung (Doku Kommunikationsschicht v2, Abschnitt 4). sealed class MlrsNetworkEvent { const MlrsNetworkEvent(); } /// Das Netz ist verfuegbar (Nutzer hat den Systemdialog bestaetigt). /// [gatewayAddress] ist die Gateway-Adresse des Netzes, falls ermittelbar - /// dient als Vorbelegung der Zieladresse, bevor eine Gegenstelle aus dem /// Verkehr gelernt wurde (Doku Abschnitt 3). class MlrsNetworkAvailable extends MlrsNetworkEvent { const MlrsNetworkAvailable({this.gatewayAddress}); final String? gatewayAddress; } /// Die Anfrage konnte nicht erfuellt werden (Nutzer hat den Systemdialog /// abgelehnt, keine passende SSID in Reichweite, oder Systemtimeout). class MlrsNetworkUnavailable extends MlrsNetworkEvent { const MlrsNetworkUnavailable(); } /// Ein zuvor verfuegbares Netz wurde waehrend einer bestehenden Verbindung /// getrennt (Doku Abschnitt 8: "Bindung haftet am Socket ... hoeren bewusst /// auf zu funktionieren"). class MlrsNetworkLost extends MlrsNetworkEvent { const MlrsNetworkLost(); } /// Plattform-Bruecke zum app-initiierten WLAN-Verbindungsaufbau /// (`WifiNetworkSpecifier`/`ConnectivityManager`, Doku Kommunikationsschicht /// v2 Abschnitt 4) - kapselt den einzigen Platform-Channel-Zugriff dieses /// Features. [UdpTransport] programmiert ausschliesslich gegen dieses /// Interface, damit es in Tests durch eine Fake-Implementierung ersetzt /// werden kann, ohne einen echten Android-Kanal zu brauchen. abstract class MlrsNetworkController { /// Ergebnisse zur zuletzt per [requestNetwork] gestellten Anfrage - /// broadcast, damit sowohl [UdpTransport.connect] (wartet auf das erste /// Ereignis) als auch ein laufender Verbindungsueberwacher (reagiert auf /// ein spaeteres [MlrsNetworkLost]) gleichzeitig zuhoeren koennen. Stream get events; /// Loest `ConnectivityManager.requestNetwork()` mit einem /// `WifiNetworkSpecifier` aus, dessen SSID-Muster auf [ssidPrefix] passt. /// Das Ergebnis kommt asynchron ueber [events] (available/unavailable). Future requestNetwork(String ssidPrefix); /// Meldet den Callback ab und geht damit von der aktuellen Anfrage /// zurueck - keine weiteren [events] danach, bis erneut angefragt wird. Future releaseNetwork(); /// Bindet den App-Prozess kurzzeitig an das zuletzt verfuegbare Netz /// (Doku Abschnitt 4: "Bindungsfenster"). Liefert false, wenn aktuell kein /// Netz verfuegbar ist. Future bindProcessToNetwork(); /// Loest die Prozessbindung wieder - muss unmittelbar nach dem Erzeugen /// des UDP-Sockets aufgerufen werden, sonst laufen auch Karten/Wetter /// ueber das mLRS-Netz statt der Standardroute. Future unbindProcessFromNetwork(); /// Ergebnis von /// `WifiManager.isStaConcurrencyForLocalOnlyConnectionsSupported()` (Doku /// Abschnitt 4/8) - null falls nicht ermittelbar (API < 30). Nur zur /// Anzeige/zum Verbindungsprotokoll, nicht handlungsrelevant. Future isStaConcurrencySupported(); void dispose(); } /// Echte Implementierung ueber MethodChannel/EventChannel gegen /// `MlrsNetworkPlugin.kt` (android/app/.../MlrsNetworkPlugin.kt). class MethodChannelMlrsNetworkController implements MlrsNetworkController { MethodChannelMlrsNetworkController() : _methodChannel = const MethodChannel('com.dmc.dmc_app/mlrs_network'), _eventChannel = const EventChannel('com.dmc.dmc_app/mlrs_network_events'); final MethodChannel _methodChannel; final EventChannel _eventChannel; @override late final Stream events = _eventChannel.receiveBroadcastStream().map(_toEvent); MlrsNetworkEvent _toEvent(dynamic raw) { final map = Map.from(raw as Map); return switch (map['type']) { 'available' => MlrsNetworkAvailable( gatewayAddress: map['gatewayAddress'] as String?, ), 'lost' => const MlrsNetworkLost(), _ => const MlrsNetworkUnavailable(), }; } @override Future requestNetwork(String ssidPrefix) => _methodChannel .invokeMethod('requestNetwork', {'ssidPrefix': ssidPrefix}); @override Future releaseNetwork() => _methodChannel.invokeMethod('releaseNetwork'); @override Future bindProcessToNetwork() async => (await _methodChannel.invokeMethod('bindProcessToNetwork')) ?? false; @override Future unbindProcessFromNetwork() => _methodChannel.invokeMethod('unbindProcessFromNetwork'); @override Future isStaConcurrencySupported() => _methodChannel.invokeMethod('isStaConcurrencySupported'); @override void dispose() { // Keine eigenen Ressourcen - Abonnenten von [events] verwalten ihre // StreamSubscription selbst (siehe UdpTransport.dispose). } }