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
+14 -1
View File
@@ -84,6 +84,13 @@ class AppSettingsTable extends Table {
TextColumn get connectionWifiSsidPrefix =>
text().withDefault(const Constant(kDefaultWifiSsidPrefix))();
/// Exakte SSID der zuletzt erfolgreich verbundenen mLRS-Bruecke (Doku
/// Abschnitt 8: "verbundenes WLAN merken") - null, solange noch nie
/// erfolgreich verbunden wurde. Wird fuer kuenftige Verbindungsversuche
/// als exakter WifiNetworkSpecifier statt Praefix verwendet, damit Android
/// ohne erneuten Systemdialog automatisch verbinden kann.
TextColumn get connectionWifiLastSsid => text().nullable()();
@override
Set<Column> get primaryKey => {id};
}
@@ -95,7 +102,7 @@ class AppDatabase extends _$AppDatabase {
AppDatabase.forTesting(super.executor);
@override
int get schemaVersion => 5;
int get schemaVersion => 6;
@override
MigrationStrategy get migration => MigrationStrategy(
@@ -133,6 +140,12 @@ class AppDatabase extends _$AppDatabase {
appSettingsTable.connectionWifiSsidPrefix,
);
}
if (from < 6) {
await m.addColumn(
appSettingsTable,
appSettingsTable.connectionWifiLastSsid,
);
}
},
);
+96 -3
View File
@@ -1394,6 +1394,17 @@ class $AppSettingsTableTable extends AppSettingsTable
requiredDuringInsert: false,
defaultValue: const Constant(kDefaultWifiSsidPrefix),
);
static const VerificationMeta _connectionWifiLastSsidMeta =
const VerificationMeta('connectionWifiLastSsid');
@override
late final GeneratedColumn<String> connectionWifiLastSsid =
GeneratedColumn<String>(
'connection_wifi_last_ssid',
aliasedName,
true,
type: DriftSqlType.string,
requiredDuringInsert: false,
);
@override
List<GeneratedColumn> get $columns => [
id,
@@ -1405,6 +1416,7 @@ class $AppSettingsTableTable extends AppSettingsTable
connectionWifiHost,
connectionWifiPort,
connectionWifiSsidPrefix,
connectionWifiLastSsid,
];
@override
String get aliasedName => _alias ?? actualTableName;
@@ -1493,6 +1505,15 @@ class $AppSettingsTableTable extends AppSettingsTable
),
);
}
if (data.containsKey('connection_wifi_last_ssid')) {
context.handle(
_connectionWifiLastSsidMeta,
connectionWifiLastSsid.isAcceptableOrUnknown(
data['connection_wifi_last_ssid']!,
_connectionWifiLastSsidMeta,
),
);
}
return context;
}
@@ -1538,6 +1559,10 @@ class $AppSettingsTableTable extends AppSettingsTable
DriftSqlType.string,
data['${effectivePrefix}connection_wifi_ssid_prefix'],
)!,
connectionWifiLastSsid: attachedDatabase.typeMapping.read(
DriftSqlType.string,
data['${effectivePrefix}connection_wifi_last_ssid'],
),
);
}
@@ -1578,6 +1603,13 @@ class AppSettingsTableData extends DataClass
/// `WifiNetworkSpecifier` (Doku Kommunikationsschicht v2 Abschnitt 4/8),
/// Standard "mLRS-", ueberschreibbar.
final String connectionWifiSsidPrefix;
/// Exakte SSID der zuletzt erfolgreich verbundenen mLRS-Bruecke (Doku
/// Abschnitt 8: "verbundenes WLAN merken") - null, solange noch nie
/// erfolgreich verbunden wurde. Wird fuer kuenftige Verbindungsversuche
/// als exakter WifiNetworkSpecifier statt Praefix verwendet, damit Android
/// ohne erneuten Systemdialog automatisch verbinden kann.
final String? connectionWifiLastSsid;
const AppSettingsTableData({
required this.id,
this.activeDroneProfileId,
@@ -1588,6 +1620,7 @@ class AppSettingsTableData extends DataClass
this.connectionWifiHost,
required this.connectionWifiPort,
required this.connectionWifiSsidPrefix,
this.connectionWifiLastSsid,
});
@override
Map<String, Expression> toColumns(bool nullToAbsent) {
@@ -1613,6 +1646,11 @@ class AppSettingsTableData extends DataClass
map['connection_wifi_ssid_prefix'] = Variable<String>(
connectionWifiSsidPrefix,
);
if (!nullToAbsent || connectionWifiLastSsid != null) {
map['connection_wifi_last_ssid'] = Variable<String>(
connectionWifiLastSsid,
);
}
return map;
}
@@ -1635,6 +1673,9 @@ class AppSettingsTableData extends DataClass
: Value(connectionWifiHost),
connectionWifiPort: Value(connectionWifiPort),
connectionWifiSsidPrefix: Value(connectionWifiSsidPrefix),
connectionWifiLastSsid: connectionWifiLastSsid == null && nullToAbsent
? const Value.absent()
: Value(connectionWifiLastSsid),
);
}
@@ -1663,6 +1704,9 @@ class AppSettingsTableData extends DataClass
connectionWifiSsidPrefix: serializer.fromJson<String>(
json['connectionWifiSsidPrefix'],
),
connectionWifiLastSsid: serializer.fromJson<String?>(
json['connectionWifiLastSsid'],
),
);
}
@override
@@ -1682,6 +1726,9 @@ class AppSettingsTableData extends DataClass
'connectionWifiSsidPrefix': serializer.toJson<String>(
connectionWifiSsidPrefix,
),
'connectionWifiLastSsid': serializer.toJson<String?>(
connectionWifiLastSsid,
),
};
}
@@ -1695,6 +1742,7 @@ class AppSettingsTableData extends DataClass
Value<String?> connectionWifiHost = const Value.absent(),
int? connectionWifiPort,
String? connectionWifiSsidPrefix,
Value<String?> connectionWifiLastSsid = const Value.absent(),
}) => AppSettingsTableData(
id: id ?? this.id,
activeDroneProfileId: activeDroneProfileId.present
@@ -1714,6 +1762,9 @@ class AppSettingsTableData extends DataClass
connectionWifiPort: connectionWifiPort ?? this.connectionWifiPort,
connectionWifiSsidPrefix:
connectionWifiSsidPrefix ?? this.connectionWifiSsidPrefix,
connectionWifiLastSsid: connectionWifiLastSsid.present
? connectionWifiLastSsid.value
: this.connectionWifiLastSsid,
);
AppSettingsTableData copyWithCompanion(AppSettingsTableCompanion data) {
return AppSettingsTableData(
@@ -1742,6 +1793,9 @@ class AppSettingsTableData extends DataClass
connectionWifiSsidPrefix: data.connectionWifiSsidPrefix.present
? data.connectionWifiSsidPrefix.value
: this.connectionWifiSsidPrefix,
connectionWifiLastSsid: data.connectionWifiLastSsid.present
? data.connectionWifiLastSsid.value
: this.connectionWifiLastSsid,
);
}
@@ -1756,7 +1810,8 @@ class AppSettingsTableData extends DataClass
..write('connectionType: $connectionType, ')
..write('connectionWifiHost: $connectionWifiHost, ')
..write('connectionWifiPort: $connectionWifiPort, ')
..write('connectionWifiSsidPrefix: $connectionWifiSsidPrefix')
..write('connectionWifiSsidPrefix: $connectionWifiSsidPrefix, ')
..write('connectionWifiLastSsid: $connectionWifiLastSsid')
..write(')'))
.toString();
}
@@ -1772,6 +1827,7 @@ class AppSettingsTableData extends DataClass
connectionWifiHost,
connectionWifiPort,
connectionWifiSsidPrefix,
connectionWifiLastSsid,
);
@override
bool operator ==(Object other) =>
@@ -1785,7 +1841,8 @@ class AppSettingsTableData extends DataClass
other.connectionType == this.connectionType &&
other.connectionWifiHost == this.connectionWifiHost &&
other.connectionWifiPort == this.connectionWifiPort &&
other.connectionWifiSsidPrefix == this.connectionWifiSsidPrefix);
other.connectionWifiSsidPrefix == this.connectionWifiSsidPrefix &&
other.connectionWifiLastSsid == this.connectionWifiLastSsid);
}
class AppSettingsTableCompanion extends UpdateCompanion<AppSettingsTableData> {
@@ -1798,6 +1855,7 @@ class AppSettingsTableCompanion extends UpdateCompanion<AppSettingsTableData> {
final Value<String?> connectionWifiHost;
final Value<int> connectionWifiPort;
final Value<String> connectionWifiSsidPrefix;
final Value<String?> connectionWifiLastSsid;
const AppSettingsTableCompanion({
this.id = const Value.absent(),
this.activeDroneProfileId = const Value.absent(),
@@ -1808,6 +1866,7 @@ class AppSettingsTableCompanion extends UpdateCompanion<AppSettingsTableData> {
this.connectionWifiHost = const Value.absent(),
this.connectionWifiPort = const Value.absent(),
this.connectionWifiSsidPrefix = const Value.absent(),
this.connectionWifiLastSsid = const Value.absent(),
});
AppSettingsTableCompanion.insert({
this.id = const Value.absent(),
@@ -1819,6 +1878,7 @@ class AppSettingsTableCompanion extends UpdateCompanion<AppSettingsTableData> {
this.connectionWifiHost = const Value.absent(),
this.connectionWifiPort = const Value.absent(),
this.connectionWifiSsidPrefix = const Value.absent(),
this.connectionWifiLastSsid = const Value.absent(),
});
static Insertable<AppSettingsTableData> custom({
Expression<int>? id,
@@ -1830,6 +1890,7 @@ class AppSettingsTableCompanion extends UpdateCompanion<AppSettingsTableData> {
Expression<String>? connectionWifiHost,
Expression<int>? connectionWifiPort,
Expression<String>? connectionWifiSsidPrefix,
Expression<String>? connectionWifiLastSsid,
}) {
return RawValuesInsertable({
if (id != null) 'id': id,
@@ -1847,6 +1908,8 @@ class AppSettingsTableCompanion extends UpdateCompanion<AppSettingsTableData> {
'connection_wifi_port': connectionWifiPort,
if (connectionWifiSsidPrefix != null)
'connection_wifi_ssid_prefix': connectionWifiSsidPrefix,
if (connectionWifiLastSsid != null)
'connection_wifi_last_ssid': connectionWifiLastSsid,
});
}
@@ -1860,6 +1923,7 @@ class AppSettingsTableCompanion extends UpdateCompanion<AppSettingsTableData> {
Value<String?>? connectionWifiHost,
Value<int>? connectionWifiPort,
Value<String>? connectionWifiSsidPrefix,
Value<String?>? connectionWifiLastSsid,
}) {
return AppSettingsTableCompanion(
id: id ?? this.id,
@@ -1874,6 +1938,8 @@ class AppSettingsTableCompanion extends UpdateCompanion<AppSettingsTableData> {
connectionWifiPort: connectionWifiPort ?? this.connectionWifiPort,
connectionWifiSsidPrefix:
connectionWifiSsidPrefix ?? this.connectionWifiSsidPrefix,
connectionWifiLastSsid:
connectionWifiLastSsid ?? this.connectionWifiLastSsid,
);
}
@@ -1915,6 +1981,11 @@ class AppSettingsTableCompanion extends UpdateCompanion<AppSettingsTableData> {
connectionWifiSsidPrefix.value,
);
}
if (connectionWifiLastSsid.present) {
map['connection_wifi_last_ssid'] = Variable<String>(
connectionWifiLastSsid.value,
);
}
return map;
}
@@ -1929,7 +2000,8 @@ class AppSettingsTableCompanion extends UpdateCompanion<AppSettingsTableData> {
..write('connectionType: $connectionType, ')
..write('connectionWifiHost: $connectionWifiHost, ')
..write('connectionWifiPort: $connectionWifiPort, ')
..write('connectionWifiSsidPrefix: $connectionWifiSsidPrefix')
..write('connectionWifiSsidPrefix: $connectionWifiSsidPrefix, ')
..write('connectionWifiLastSsid: $connectionWifiLastSsid')
..write(')'))
.toString();
}
@@ -2574,6 +2646,7 @@ typedef $$AppSettingsTableTableCreateCompanionBuilder =
Value<String?> connectionWifiHost,
Value<int> connectionWifiPort,
Value<String> connectionWifiSsidPrefix,
Value<String?> connectionWifiLastSsid,
});
typedef $$AppSettingsTableTableUpdateCompanionBuilder =
AppSettingsTableCompanion Function({
@@ -2586,6 +2659,7 @@ typedef $$AppSettingsTableTableUpdateCompanionBuilder =
Value<String?> connectionWifiHost,
Value<int> connectionWifiPort,
Value<String> connectionWifiSsidPrefix,
Value<String?> connectionWifiLastSsid,
});
class $$AppSettingsTableTableFilterComposer
@@ -2641,6 +2715,11 @@ class $$AppSettingsTableTableFilterComposer
column: $table.connectionWifiSsidPrefix,
builder: (column) => ColumnFilters(column),
);
ColumnFilters<String> get connectionWifiLastSsid => $composableBuilder(
column: $table.connectionWifiLastSsid,
builder: (column) => ColumnFilters(column),
);
}
class $$AppSettingsTableTableOrderingComposer
@@ -2696,6 +2775,11 @@ class $$AppSettingsTableTableOrderingComposer
column: $table.connectionWifiSsidPrefix,
builder: (column) => ColumnOrderings(column),
);
ColumnOrderings<String> get connectionWifiLastSsid => $composableBuilder(
column: $table.connectionWifiLastSsid,
builder: (column) => ColumnOrderings(column),
);
}
class $$AppSettingsTableTableAnnotationComposer
@@ -2749,6 +2833,11 @@ class $$AppSettingsTableTableAnnotationComposer
column: $table.connectionWifiSsidPrefix,
builder: (column) => column,
);
GeneratedColumn<String> get connectionWifiLastSsid => $composableBuilder(
column: $table.connectionWifiLastSsid,
builder: (column) => column,
);
}
class $$AppSettingsTableTableTableManager
@@ -2797,6 +2886,7 @@ class $$AppSettingsTableTableTableManager
Value<String?> connectionWifiHost = const Value.absent(),
Value<int> connectionWifiPort = const Value.absent(),
Value<String> connectionWifiSsidPrefix = const Value.absent(),
Value<String?> connectionWifiLastSsid = const Value.absent(),
}) => AppSettingsTableCompanion(
id: id,
activeDroneProfileId: activeDroneProfileId,
@@ -2807,6 +2897,7 @@ class $$AppSettingsTableTableTableManager
connectionWifiHost: connectionWifiHost,
connectionWifiPort: connectionWifiPort,
connectionWifiSsidPrefix: connectionWifiSsidPrefix,
connectionWifiLastSsid: connectionWifiLastSsid,
),
createCompanionCallback:
({
@@ -2819,6 +2910,7 @@ class $$AppSettingsTableTableTableManager
Value<String?> connectionWifiHost = const Value.absent(),
Value<int> connectionWifiPort = const Value.absent(),
Value<String> connectionWifiSsidPrefix = const Value.absent(),
Value<String?> connectionWifiLastSsid = const Value.absent(),
}) => AppSettingsTableCompanion.insert(
id: id,
activeDroneProfileId: activeDroneProfileId,
@@ -2829,6 +2921,7 @@ class $$AppSettingsTableTableTableManager
connectionWifiHost: connectionWifiHost,
connectionWifiPort: connectionWifiPort,
connectionWifiSsidPrefix: connectionWifiSsidPrefix,
connectionWifiLastSsid: connectionWifiLastSsid,
),
withReferenceMapper: (p0) => p0
.map((e) => (e.readTable(table), BaseReferences(db, table, e)))
@@ -23,6 +23,7 @@ class AppSettingsRepository {
String? connectionWifiHost,
int connectionWifiPort,
String connectionWifiSsidPrefix,
String? connectionWifiLastSsid,
})> load() async {
final row = await (_db.select(_db.appSettingsTable)
..where((s) => s.id.equals(_rowId)))
@@ -40,6 +41,7 @@ class AppSettingsRepository {
connectionWifiPort: row?.connectionWifiPort ?? kDefaultWifiPort,
connectionWifiSsidPrefix:
row?.connectionWifiSsidPrefix ?? kDefaultWifiSsidPrefix,
connectionWifiLastSsid: row?.connectionWifiLastSsid,
);
}
@@ -119,6 +121,18 @@ class AppSettingsRepository {
));
}
/// Exakte SSID der zuletzt erfolgreich verbundenen mLRS-Bruecke (Doku
/// Abschnitt 8), oder null solange noch nie erfolgreich verbunden wurde.
Future<String?> loadConnectionWifiLastSsid() async =>
(await load()).connectionWifiLastSsid;
Future<void> setConnectionWifiLastSsid(String? ssid) async {
await _upsert(AppSettingsTableCompanion(
id: const Value(_rowId),
connectionWifiLastSsid: Value(ssid),
));
}
Future<void> _upsert(AppSettingsTableCompanion companion) async {
await _db.into(_db.appSettingsTable).insertOnConflictUpdate(companion);
}
@@ -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();
+2 -2
View File
@@ -9,6 +9,6 @@ abstract final class DmcColors {
static const liveGreen = Color(0xFF3ECF8E);
static const warnRed = Color(0xFFE2574A);
static const pafAmber = Color(0xFFF0C86E);
static const editModeGreen = Color(0xFF50C878);
static const flyModeRed = Color(0xFFE2574A);
static const planModeColor = Color(0xFFE2574A);
static const flyModeColor = Color(0xFF50C878);
}
@@ -16,6 +16,8 @@ final wifiTransportProvider = Provider<UdpTransport>((ref) {
getHost: settingsRepo.loadConnectionWifiHost,
getPort: settingsRepo.loadConnectionWifiPort,
getSsidPrefix: settingsRepo.loadConnectionWifiSsidPrefix,
getRememberedSsid: settingsRepo.loadConnectionWifiLastSsid,
rememberSsid: settingsRepo.setConnectionWifiLastSsid,
);
ref.onDispose(transport.dispose);
return transport;
@@ -134,9 +136,10 @@ class WifiConnectionLogNotifier
final message = switch (linkState) {
LinkState.disconnected => 'Disconnected',
LinkState.connecting => 'Requesting mLRS network...',
LinkState.connected =>
'Network available, listening on port ${transport.boundPort}'
' (STA concurrency: ${_formatStaConcurrency(transport.staConcurrencySupported)})',
LinkState.connected => 'Connected to '
'${transport.connectedSsid ?? 'network'}, listening on port '
'${transport.boundPort} (STA concurrency: '
'${_formatStaConcurrency(transport.staConcurrencySupported)})',
LinkState.error => 'Error: '
'${transport.lastErrorMessage ?? transport.lastErrorReason?.name ?? 'unknown'}',
};
@@ -150,6 +150,12 @@ class _WifiPanelState extends ConsumerState<_WifiPanel> {
final connectionSettings = ref.watch(connectionSettingsProvider).value;
final log = ref.watch(wifiConnectionLogProvider);
final packetCount = ref.watch(wifiConnectionLogProvider.notifier).packetCount;
// Kein eigener Reaktivitaets-Provider fuer connectedSsid noetig - der
// Rebuild wird bereits durch das Beobachten von wifiLinkStateProvider
// oben ausgeloest, und das Feld wird synchron gesetzt, bevor der
// Transport auf LinkState.connected wechselt (siehe UdpTransport.connect).
final connectedSsid = ref.read(wifiTransportProvider).connectedSsid;
final rememberedSsid = connectionSettings?.connectionWifiLastSsid;
// Controller einmalig aus den gespeicherten Einstellungen befuellen -
// nicht bei jedem Rebuild, sonst wird waehrend der Nutzereingabe
@@ -188,6 +194,23 @@ class _WifiPanelState extends ConsumerState<_WifiPanel> {
'break maps/weather.',
style: TextStyle(fontSize: 12, color: Colors.grey),
),
const SizedBox(height: 8),
if (connectedSsid != null)
Text(
'Connected to: $connectedSsid',
style: const TextStyle(fontSize: 12, color: DmcColors.liveGreen),
)
else if (rememberedSsid != null)
Text(
'Remembered network: $rememberedSsid',
style: const TextStyle(fontSize: 12, color: Colors.grey),
)
else
const Text(
'No network remembered yet - connect once to remember it for '
'automatic reconnection.',
style: TextStyle(fontSize: 12, color: Colors.grey),
),
if (errorReason != null) ...[
const SizedBox(height: 12),
_WifiErrorHint(reason: errorReason),
@@ -253,6 +276,22 @@ class _WifiPanelState extends ConsumerState<_WifiPanel> {
),
],
),
const SizedBox(height: 8),
SwitchListTile(
contentPadding: EdgeInsets.zero,
title: const Text('Auto-connect when entering Fly mode'),
subtitle: const Text(
'Only once a network has been remembered (see above).',
style: TextStyle(fontSize: 11),
),
value: connectionSettings?.connectionAutoConnect ?? true,
onChanged: (value) async {
await ref
.read(appSettingsRepositoryProvider)
.setConnectionAutoConnect(value);
ref.invalidate(connectionSettingsProvider);
},
),
const SizedBox(height: 16),
Text(
'Connection log ($packetCount packets received)',
+91 -3
View File
@@ -1,3 +1,4 @@
import 'dart:async';
import 'dart:math' as math;
import 'package:flutter/material.dart';
@@ -5,8 +6,12 @@ import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../../app_mode/app_mode_cubit.dart';
import '../../transport/connection_type.dart';
import '../../transport/link_transport.dart';
import '../dmc_colors.dart';
import '../providers/connection_settings_provider.dart';
import '../providers/flight_mode_provider.dart';
import '../providers/wifi_connection_provider.dart';
import 'flight_mode_pill.dart';
import 'fly_map_controls.dart';
import 'header_wind_pill.dart';
@@ -35,7 +40,7 @@ class TopModeBar extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final isFly = context.select((AppModeCubit c) => c.state.mode == AppMode.fly);
final activeTint = isFly ? DmcColors.flyModeRed : DmcColors.editModeGreen;
final activeTint = isFly ? DmcColors.flyModeColor : DmcColors.planModeColor;
// Kein SafeArea: das wendet links/rechts die jeweils tatsaechlichen
// (oft asymmetrischen) Insets an, wodurch die Leiste sichtbar von der
@@ -72,7 +77,7 @@ class TopModeBar extends ConsumerWidget {
_ModeButton(
label: 'Plan',
isActive: !isFly,
inactiveColor: DmcColors.editModeGreen,
inactiveColor: DmcColors.planModeColor,
cornerRadius: const BorderRadius.only(
bottomLeft: Radius.circular(26),
),
@@ -82,6 +87,8 @@ class TopModeBar extends ConsumerWidget {
const SizedBox(width: 8),
const HeaderWindPill(),
const SizedBox(width: 8),
const _WifiConnectionProgressPill(),
const SizedBox(width: 8),
const Expanded(child: FlightModePill()),
const SizedBox(width: 8),
const FlyMapControls(),
@@ -96,7 +103,7 @@ class TopModeBar extends ConsumerWidget {
_ModeButton(
label: 'Fly',
isActive: isFly,
inactiveColor: DmcColors.flyModeRed,
inactiveColor: DmcColors.flyModeColor,
cornerRadius: const BorderRadius.only(
bottomRight: Radius.circular(26),
),
@@ -105,6 +112,7 @@ class TopModeBar extends ConsumerWidget {
// Eintritt in den Fly-Modus setzt immer auf Waypoint
// zurueck (Doku 3.11).
ref.read(flightModeProvider.notifier).reset();
_maybeAutoConnectWifi(ref);
},
),
],
@@ -115,6 +123,86 @@ class TopModeBar extends ConsumerWidget {
}
}
/// Verbindet automatisch zur mLRS-Bruecke, wenn in den Fly-Modus gewechselt
/// wird (Doku Kommunikationsschicht v2 Abschnitt 8: "Verbindung automatisch
/// herstellen bei Wechsel in Fly, wenn WLAN gemerkt"). Voraussetzungen:
/// Verbindungsart ist WLAN, "Auto-connect"-Einstellung ist an, und es gibt
/// bereits eine gemerkte SSID aus einer frueheren erfolgreichen Verbindung -
/// ohne gemerkte SSID muesste ohnehin der Systemdialog erscheinen, das ist
/// dann kein "automatisches" Verbinden mehr. Der Fortschritt ist ueber
/// [_WifiConnectionProgressPill] sichtbar.
Future<void> _maybeAutoConnectWifi(WidgetRef ref) async {
final settings = ref.read(connectionSettingsProvider).value;
if (settings == null) return;
if (settings.connectionType != ConnectionType.wifi) return;
if (!settings.connectionAutoConnect) return;
if (settings.connectionWifiLastSsid == null) return;
final transport = ref.read(wifiTransportProvider);
if (transport.currentState == LinkState.connected ||
transport.currentState == LinkState.connecting) {
return;
}
try {
await transport.connect();
} on LinkConnectException {
// Grund/Nachricht sind bereits ueber den State-Stream gemeldet, siehe
// _WifiConnectionProgressPill.
}
}
/// Kompakte Fortschrittsanzeige fuer den automatischen WLAN-Verbindungsaufbau
/// (siehe [_maybeAutoConnectWifi]) - nur sichtbar, solange die Verbindungsart
/// WLAN ist und gerade etwas passiert bzw. ein Fehler vorliegt. Bleibt im
/// Ruhezustand (disconnected, kein vorheriger Fehler) unsichtbar, damit die
/// Kopfleiste nicht dauerhaft mit einer Statuspille zugestellt ist.
class _WifiConnectionProgressPill extends ConsumerWidget {
const _WifiConnectionProgressPill();
@override
Widget build(BuildContext context, WidgetRef ref) {
final connectionType = ref.watch(connectionSettingsProvider).value?.connectionType;
final linkState = ref.watch(wifiLinkStateProvider).value;
if (connectionType != ConnectionType.wifi) return const SizedBox.shrink();
if (linkState == null || linkState == LinkState.disconnected) {
return const SizedBox.shrink();
}
final (label, color) = switch (linkState) {
LinkState.connecting => ('Connecting…', DmcColors.pafAmber),
LinkState.connected => ('WiFi connected', DmcColors.liveGreen),
LinkState.error => ('WiFi error', DmcColors.warnRed),
LinkState.disconnected => ('', Colors.grey),
};
return Padding(
padding: const EdgeInsets.only(right: 8),
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4),
decoration: BoxDecoration(
color: color.withValues(alpha: 0.2),
borderRadius: BorderRadius.circular(12),
border: Border.all(color: color),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
if (linkState == LinkState.connecting) ...[
SizedBox(
width: 10,
height: 10,
child: CircularProgressIndicator(strokeWidth: 1.5, color: color),
),
const SizedBox(width: 6),
],
Text(label, style: TextStyle(color: color, fontSize: 11)),
],
),
),
);
}
}
class _ModeButton extends StatelessWidget {
const _ModeButton({
required this.label,