autoconnect on fly mode (does not work yet)

This commit is contained in:
Constantin Leue
2026-08-02 08:25:23 +02:00
parent 5e9ef43f2f
commit 607dbb6584
13 changed files with 430 additions and 29 deletions
@@ -6,14 +6,19 @@ sealed class MlrsNetworkEvent {
const MlrsNetworkEvent();
}
/// Das Netz ist verfuegbar (Nutzer hat den Systemdialog bestaetigt).
/// Das Netz ist verfuegbar (Nutzer hat den Systemdialog bestaetigt, oder
/// Android hat eine zuvor bewilligte exakte SSID still erneut verbunden).
/// [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).
/// Verkehr gelernt wurde (Doku Abschnitt 3). [ssid] ist die tatsaechliche
/// SSID des verbundenen Netzes, falls ermittelbar - wird gemerkt, damit
/// kuenftige Verbindungen exakt (statt per Praefix) angefragt werden koennen
/// (siehe [MlrsNetworkController.requestNetwork]).
class MlrsNetworkAvailable extends MlrsNetworkEvent {
const MlrsNetworkAvailable({this.gatewayAddress});
const MlrsNetworkAvailable({this.gatewayAddress, this.ssid});
final String? gatewayAddress;
final String? ssid;
}
/// Die Anfrage konnte nicht erfuellt werden (Nutzer hat den Systemdialog
@@ -79,9 +84,13 @@ abstract class MlrsNetworkController {
Stream<MlrsNetworkEvent> get events;
/// Loest `ConnectivityManager.requestNetwork()` mit einem
/// `WifiNetworkSpecifier` aus, dessen SSID-Muster auf [ssidPrefix] passt.
/// `WifiNetworkSpecifier` aus. Ist [preferredSsid] gesetzt (zuvor
/// erfolgreich verbundene, gemerkte SSID), wird exakt danach gefragt -
/// Android kann das dann ohne erneuten Systemdialog automatisch bewilligen
/// (Doku Abschnitt 8: "Verbindung automatisch herstellen"). Sonst wird
/// [ssidPrefix] als Praefix-Muster verwendet (zeigt immer den Dialog).
/// Das Ergebnis kommt asynchron ueber [events] (available/unavailable).
Future<void> requestNetwork(String ssidPrefix);
Future<void> requestNetwork(String ssidPrefix, {String? preferredSsid});
/// Meldet den Callback ab und geht damit von der aktuellen Anfrage
/// zurueck - keine weiteren [events] danach, bis erneut angefragt wird.
@@ -139,6 +148,7 @@ class MethodChannelMlrsNetworkController implements MlrsNetworkController {
return switch (map['type']) {
'available' => MlrsNetworkAvailable(
gatewayAddress: map['gatewayAddress'] as String?,
ssid: map['ssid'] as String?,
),
'lost' => const MlrsNetworkLost(),
_ => const MlrsNetworkUnavailable(),
@@ -155,8 +165,11 @@ class MethodChannelMlrsNetworkController implements MlrsNetworkController {
}
@override
Future<void> requestNetwork(String ssidPrefix) => _methodChannel
.invokeMethod('requestNetwork', {'ssidPrefix': ssidPrefix});
Future<void> requestNetwork(String ssidPrefix, {String? preferredSsid}) =>
_methodChannel.invokeMethod('requestNetwork', {
'ssidPrefix': ssidPrefix,
'exactSsid': preferredSsid,
});
@override
Future<void> releaseNetwork() => _methodChannel.invokeMethod('releaseNetwork');
+33 -3
View File
@@ -32,16 +32,22 @@ class UdpTransport implements LinkTransport {
required Future<String?> Function() getHost,
required Future<int> Function() getPort,
required Future<String> Function() getSsidPrefix,
required Future<String?> Function() getRememberedSsid,
required Future<void> Function(String ssid) rememberSsid,
MlrsNetworkController? networkController,
}) : _getHost = getHost,
_getPort = getPort,
_getSsidPrefix = getSsidPrefix,
_getRememberedSsid = getRememberedSsid,
_rememberSsid = rememberSsid,
_networkController =
networkController ?? MethodChannelMlrsNetworkController();
final Future<String?> Function() _getHost;
final Future<int> Function() _getPort;
final Future<String> Function() _getSsidPrefix;
final Future<String?> Function() _getRememberedSsid;
final Future<void> Function(String ssid) _rememberSsid;
final MlrsNetworkController _networkController;
final _incomingController = StreamController<Uint8List>.broadcast();
@@ -54,6 +60,7 @@ class UdpTransport implements LinkTransport {
int? _boundPort;
bool _socketOpen = false;
bool? _staConcurrencySupported;
String? _connectedSsid;
LinkState _state = LinkState.disconnected;
LinkErrorReason? _lastErrorReason;
@@ -85,6 +92,13 @@ class UdpTransport implements LinkTransport {
/// nicht ermittelbar (API < 30, Doku Abschnitt 4/8).
bool? get staConcurrencySupported => _staConcurrencySupported;
/// Tatsaechliche SSID des verbundenen Netzes, sobald von Android
/// ermittelbar (Doku Abschnitt 8: "verbundenes WLAN anzeigen"). Wird bei
/// jedem erfolgreichen [connect] auch dauerhaft gemerkt (siehe
/// [_rememberSsid]), damit kuenftige Verbindungen exakt statt per Praefix
/// angefragt werden koennen.
String? get connectedSsid => _connectedSsid;
@override
Future<void> connect() async {
_lastErrorReason = null;
@@ -92,13 +106,17 @@ class UdpTransport implements LinkTransport {
_setState(LinkState.connecting);
final ssidPrefix = await _getSsidPrefix();
final rememberedSsid = await _getRememberedSsid();
final firstEvent = Completer<MlrsNetworkEvent>();
final requestSub = _networkController.events.listen((event) {
if (!firstEvent.isCompleted) firstEvent.complete(event);
});
try {
await _networkController.requestNetwork(ssidPrefix);
await _networkController.requestNetwork(
ssidPrefix,
preferredSsid: rememberedSsid,
);
final event = await firstEvent.future;
await requestSub.cancel();
@@ -115,6 +133,11 @@ class UdpTransport implements LinkTransport {
// unten, nicht erst danach (Doku Abschnitt 8).
_networkLossSub = _networkController.events.listen(_onNetworkEvent);
_connectedSsid = available.ssid;
if (available.ssid != null) {
await _rememberSsid(available.ssid!);
}
final configuredHost = await _getHost();
final configuredPort = await _getPort();
final prefillHost = (configuredHost != null && configuredHost.isNotEmpty)
@@ -170,6 +193,7 @@ class UdpTransport implements LinkTransport {
_packetSub?.cancel();
_packetSub = null;
_boundPort = null;
_connectedSsid = null;
if (_socketOpen) {
_socketOpen = false;
unawaited(_networkController.closeSocket());
@@ -193,6 +217,7 @@ class UdpTransport implements LinkTransport {
await _packetSub?.cancel();
_packetSub = null;
_boundPort = null;
_connectedSsid = null;
if (_socketOpen) {
_socketOpen = false;
await _networkController.closeSocket();
@@ -220,9 +245,14 @@ class UdpTransport implements LinkTransport {
_packetSub?.cancel();
if (_socketOpen) {
_socketOpen = false;
unawaited(_networkController.closeSocket());
// dispose() darf nie eine unbehandelte Future-Exception hinterlassen -
// z.B. wirft der Platform-Channel MissingPluginException, wenn
// ueberhaupt kein natives Gegenstueck existiert (Tests, Web/Desktop-
// UI-Vorschau). Der Aufrufer kann an dieser Stelle ohnehin nicht mehr
// reagieren.
unawaited(_networkController.closeSocket().catchError((_) {}));
}
unawaited(_networkController.releaseNetwork());
unawaited(_networkController.releaseNetwork().catchError((_) {}));
_networkController.dispose();
_incomingController.close();
_stateController.close();