wifi connection established by app, process socket binding, primary os connection untouched
This commit is contained in:
@@ -0,0 +1,124 @@
|
||||
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<MlrsNetworkEvent> get events;
|
||||
|
||||
/// Loest `ConnectivityManager.requestNetwork()` mit einem
|
||||
/// `WifiNetworkSpecifier` aus, dessen SSID-Muster auf [ssidPrefix] passt.
|
||||
/// Das Ergebnis kommt asynchron ueber [events] (available/unavailable).
|
||||
Future<void> requestNetwork(String ssidPrefix);
|
||||
|
||||
/// Meldet den Callback ab und geht damit von der aktuellen Anfrage
|
||||
/// zurueck - keine weiteren [events] danach, bis erneut angefragt wird.
|
||||
Future<void> releaseNetwork();
|
||||
|
||||
/// Bindet den App-Prozess kurzzeitig an das zuletzt verfuegbare Netz
|
||||
/// (Doku Abschnitt 4: "Bindungsfenster"). Liefert false, wenn aktuell kein
|
||||
/// Netz verfuegbar ist.
|
||||
Future<bool> 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<void> unbindProcessFromNetwork();
|
||||
|
||||
/// Ergebnis von
|
||||
/// `WifiManager.isStaConcurrencyForLocalOnlyConnectionsSupported()` (Doku
|
||||
/// Abschnitt 4/8) - null falls nicht ermittelbar (API < 30). Nur zur
|
||||
/// Anzeige/zum Verbindungsprotokoll, nicht handlungsrelevant.
|
||||
Future<bool?> 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<MlrsNetworkEvent> events =
|
||||
_eventChannel.receiveBroadcastStream().map(_toEvent);
|
||||
|
||||
MlrsNetworkEvent _toEvent(dynamic raw) {
|
||||
final map = Map<Object?, Object?>.from(raw as Map);
|
||||
return switch (map['type']) {
|
||||
'available' => MlrsNetworkAvailable(
|
||||
gatewayAddress: map['gatewayAddress'] as String?,
|
||||
),
|
||||
'lost' => const MlrsNetworkLost(),
|
||||
_ => const MlrsNetworkUnavailable(),
|
||||
};
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> requestNetwork(String ssidPrefix) => _methodChannel
|
||||
.invokeMethod('requestNetwork', {'ssidPrefix': ssidPrefix});
|
||||
|
||||
@override
|
||||
Future<void> releaseNetwork() => _methodChannel.invokeMethod('releaseNetwork');
|
||||
|
||||
@override
|
||||
Future<bool> bindProcessToNetwork() async =>
|
||||
(await _methodChannel.invokeMethod<bool>('bindProcessToNetwork')) ??
|
||||
false;
|
||||
|
||||
@override
|
||||
Future<void> unbindProcessFromNetwork() =>
|
||||
_methodChannel.invokeMethod('unbindProcessFromNetwork');
|
||||
|
||||
@override
|
||||
Future<bool?> isStaConcurrencySupported() =>
|
||||
_methodChannel.invokeMethod<bool>('isStaConcurrencySupported');
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
// Keine eigenen Ressourcen - Abonnenten von [events] verwalten ihre
|
||||
// StreamSubscription selbst (siehe UdpTransport.dispose).
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user