wifi connection established by app, process socket binding, primary os connection untouched

This commit is contained in:
Constantin Leue
2026-07-31 15:25:45 +02:00
parent 222d8743fb
commit 3b3451b8a2
16 changed files with 1188 additions and 89 deletions
@@ -121,6 +121,7 @@ class _WifiPanel extends ConsumerStatefulWidget {
}
class _WifiPanelState extends ConsumerState<_WifiPanel> {
late final TextEditingController _ssidPrefixController;
late final TextEditingController _hostController;
late final TextEditingController _portController;
bool _controllersSeeded = false;
@@ -128,12 +129,14 @@ class _WifiPanelState extends ConsumerState<_WifiPanel> {
@override
void initState() {
super.initState();
_ssidPrefixController = TextEditingController();
_hostController = TextEditingController();
_portController = TextEditingController();
}
@override
void dispose() {
_ssidPrefixController.dispose();
_hostController.dispose();
_portController.dispose();
super.dispose();
@@ -153,6 +156,7 @@ class _WifiPanelState extends ConsumerState<_WifiPanel> {
// ueberschrieben.
if (!_controllersSeeded && connectionSettings != null) {
_controllersSeeded = true;
_ssidPrefixController.text = connectionSettings.connectionWifiSsidPrefix;
_hostController.text = connectionSettings.connectionWifiHost ?? '';
_portController.text = connectionSettings.connectionWifiPort.toString();
}
@@ -171,9 +175,17 @@ class _WifiPanelState extends ConsumerState<_WifiPanel> {
),
const SizedBox(height: 4),
const Text(
'Opens a local UDP socket and listens for the mLRS bridge '
'(port 14550 by default). The peer address is learned from the '
'first incoming packet.',
'The app connects to the mLRS bridge WiFi itself (a system dialog '
'will ask you to confirm) so your normal mobile data connection '
'keeps working for maps and weather. The peer address is learned '
'from the first incoming packet.',
style: TextStyle(fontSize: 12, color: Colors.grey),
),
const SizedBox(height: 4),
const Text(
'Do not manually connect to or save the mLRS network in your '
'system WiFi settings - that would make it the default route and '
'break maps/weather.',
style: TextStyle(fontSize: 12, color: Colors.grey),
),
if (errorReason != null) ...[
@@ -187,15 +199,15 @@ class _WifiPanelState extends ConsumerState<_WifiPanel> {
Expanded(
flex: 2,
child: TextField(
controller: _hostController,
controller: _ssidPrefixController,
decoration: const InputDecoration(
labelText: 'Host (optional)',
hintText: 'auto (learned from traffic)',
labelText: 'SSID prefix',
isDense: true,
border: OutlineInputBorder(),
),
onSubmitted: (value) => _saveHost(value),
onEditingComplete: () => _saveHost(_hostController.text),
onSubmitted: (value) => _saveSsidPrefix(value),
onEditingComplete: () =>
_saveSsidPrefix(_ssidPrefixController.text),
),
),
const SizedBox(width: 8),
@@ -215,6 +227,18 @@ class _WifiPanelState extends ConsumerState<_WifiPanel> {
),
],
),
const SizedBox(height: 8),
TextField(
controller: _hostController,
decoration: const InputDecoration(
labelText: 'Host (optional)',
hintText: 'auto (network gateway, then learned from traffic)',
isDense: true,
border: OutlineInputBorder(),
),
onSubmitted: (value) => _saveHost(value),
onEditingComplete: () => _saveHost(_hostController.text),
),
const SizedBox(height: 16),
Row(
children: [
@@ -258,6 +282,15 @@ class _WifiPanelState extends ConsumerState<_WifiPanel> {
);
}
Future<void> _saveSsidPrefix(String value) async {
final trimmed = value.trim();
if (trimmed.isEmpty) return;
await ref
.read(appSettingsRepositoryProvider)
.setConnectionWifiSsidPrefix(trimmed);
ref.invalidate(connectionSettingsProvider);
}
Future<void> _saveHost(String value) async {
final trimmed = value.trim();
await ref
@@ -293,16 +326,25 @@ class _WifiErrorHint extends StatelessWidget {
@override
Widget build(BuildContext context) {
final text = switch (reason) {
LinkErrorReason.networkUnavailable =>
'No matching WiFi network found, or the system connection dialog '
'was dismissed. Make sure the mLRS bridge is powered on and not '
'saved/connected in system WiFi settings, then tap "Connect" '
'again.',
LinkErrorReason.networkLost =>
'The mLRS WiFi network was lost (out of range or bridge powered '
'off). Tap "Connect" to reconnect.',
LinkErrorReason.connectionFailed => 'Connection to the bridge failed.',
_ => 'Connection error: ${reason.name}.',
};
return Container(
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(
color: DmcColors.warnRed.withValues(alpha: 0.1),
borderRadius: BorderRadius.circular(8),
),
child: Text(
'Connection error: ${reason.name}.',
style: const TextStyle(fontSize: 12),
),
child: Text(text, style: const TextStyle(fontSize: 12)),
);
}
}
@@ -543,7 +585,13 @@ class _ErrorHint extends StatelessWidget {
'Connection to the device failed.',
null,
),
LinkErrorReason.unknown => ('Unknown connection error.', null),
// networkUnavailable/networkLost betreffen nur den WLAN-Transport
// (siehe _WifiErrorHint) - hier nur der Vollstaendigkeit halber, damit
// der gemeinsame LinkErrorReason-Enum exhaustiv bleibt.
LinkErrorReason.networkUnavailable ||
LinkErrorReason.networkLost ||
LinkErrorReason.unknown =>
('Unknown connection error.', null),
};
return Container(
padding: const EdgeInsets.all(8),