settings ui clean up
This commit is contained in:
@@ -53,37 +53,29 @@ class _ConnectionSection extends ConsumerWidget {
|
|||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Text('Connection', style: Theme.of(context).textTheme.titleMedium),
|
// Fuer WLAN rendert _WifiPanel das Typ-Dropdown selbst als Teil
|
||||||
const SizedBox(height: 12),
|
// seiner kompakten ersten Zeile (zusammen mit Connect/Disconnect
|
||||||
DropdownButtonFormField<ConnectionType>(
|
// und den Status-Pillen) - fuer die anderen Verbindungsarten
|
||||||
initialValue: selectedType,
|
// bleibt die bisherige, einfachere Kopfzeile bestehen.
|
||||||
decoration: const InputDecoration(
|
if (selectedType != ConnectionType.wifi) ...[
|
||||||
labelText: 'Type',
|
Text('Connection', style: Theme.of(context).textTheme.titleMedium),
|
||||||
isDense: true,
|
const SizedBox(height: 12),
|
||||||
border: OutlineInputBorder(),
|
DropdownButtonFormField<ConnectionType>(
|
||||||
|
initialValue: selectedType,
|
||||||
|
decoration: const InputDecoration(
|
||||||
|
labelText: 'Type',
|
||||||
|
isDense: true,
|
||||||
|
border: OutlineInputBorder(),
|
||||||
|
),
|
||||||
|
items: _connectionTypeItems,
|
||||||
|
onChanged: (type) async {
|
||||||
|
if (type == null) return;
|
||||||
|
await ref.read(appSettingsRepositoryProvider).setConnectionType(type);
|
||||||
|
ref.invalidate(connectionSettingsProvider);
|
||||||
|
},
|
||||||
),
|
),
|
||||||
items: const [
|
const SizedBox(height: 16),
|
||||||
DropdownMenuItem(value: ConnectionType.wifi, child: Text('WiFi')),
|
],
|
||||||
DropdownMenuItem(
|
|
||||||
value: ConnectionType.cellular5g,
|
|
||||||
child: Text('5G (coming soon)'),
|
|
||||||
),
|
|
||||||
DropdownMenuItem(
|
|
||||||
value: ConnectionType.bluetooth,
|
|
||||||
child: Text('Bluetooth'),
|
|
||||||
),
|
|
||||||
DropdownMenuItem(
|
|
||||||
value: ConnectionType.usb,
|
|
||||||
child: Text('USB (coming soon)'),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
onChanged: (type) async {
|
|
||||||
if (type == null) return;
|
|
||||||
await ref.read(appSettingsRepositoryProvider).setConnectionType(type);
|
|
||||||
ref.invalidate(connectionSettingsProvider);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
const SizedBox(height: 16),
|
|
||||||
switch (selectedType) {
|
switch (selectedType) {
|
||||||
ConnectionType.wifi => const _WifiPanel(),
|
ConnectionType.wifi => const _WifiPanel(),
|
||||||
ConnectionType.bluetooth => const _BluetoothPanel(),
|
ConnectionType.bluetooth => const _BluetoothPanel(),
|
||||||
@@ -99,6 +91,17 @@ class _ConnectionSection extends ConsumerWidget {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Geteilt zwischen der kompakten WLAN-Zeile ([_WifiPanel]) und der
|
||||||
|
/// generischen Kopfzeile fuer die anderen Verbindungsarten
|
||||||
|
/// ([_ConnectionSection]), damit die Beschriftungen an beiden Stellen
|
||||||
|
/// gleich lauten.
|
||||||
|
const _connectionTypeItems = [
|
||||||
|
DropdownMenuItem(value: ConnectionType.wifi, child: Text('WiFi')),
|
||||||
|
DropdownMenuItem(value: ConnectionType.cellular5g, child: Text('5G (soon)')),
|
||||||
|
DropdownMenuItem(value: ConnectionType.bluetooth, child: Text('Bluetooth')),
|
||||||
|
DropdownMenuItem(value: ConnectionType.usb, child: Text('USB (soon)')),
|
||||||
|
];
|
||||||
|
|
||||||
class _NotAvailablePanel extends StatelessWidget {
|
class _NotAvailablePanel extends StatelessWidget {
|
||||||
const _NotAvailablePanel({required this.label});
|
const _NotAvailablePanel({required this.label});
|
||||||
|
|
||||||
@@ -122,7 +125,6 @@ class _WifiPanel extends ConsumerStatefulWidget {
|
|||||||
|
|
||||||
class _WifiPanelState extends ConsumerState<_WifiPanel> {
|
class _WifiPanelState extends ConsumerState<_WifiPanel> {
|
||||||
late final TextEditingController _ssidPrefixController;
|
late final TextEditingController _ssidPrefixController;
|
||||||
late final TextEditingController _hostController;
|
|
||||||
late final TextEditingController _portController;
|
late final TextEditingController _portController;
|
||||||
bool _controllersSeeded = false;
|
bool _controllersSeeded = false;
|
||||||
|
|
||||||
@@ -130,14 +132,12 @@ class _WifiPanelState extends ConsumerState<_WifiPanel> {
|
|||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
_ssidPrefixController = TextEditingController();
|
_ssidPrefixController = TextEditingController();
|
||||||
_hostController = TextEditingController();
|
|
||||||
_portController = TextEditingController();
|
_portController = TextEditingController();
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
_ssidPrefixController.dispose();
|
_ssidPrefixController.dispose();
|
||||||
_hostController.dispose();
|
|
||||||
_portController.dispose();
|
_portController.dispose();
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
@@ -155,7 +155,6 @@ class _WifiPanelState extends ConsumerState<_WifiPanel> {
|
|||||||
// oben ausgeloest, und das Feld wird synchron gesetzt, bevor der
|
// oben ausgeloest, und das Feld wird synchron gesetzt, bevor der
|
||||||
// Transport auf LinkState.connected wechselt (siehe UdpTransport.connect).
|
// Transport auf LinkState.connected wechselt (siehe UdpTransport.connect).
|
||||||
final connectedSsid = ref.read(wifiTransportProvider).connectedSsid;
|
final connectedSsid = ref.read(wifiTransportProvider).connectedSsid;
|
||||||
final rememberedSsid = connectionSettings?.connectionWifiLastSsid;
|
|
||||||
|
|
||||||
// Controller einmalig aus den gespeicherten Einstellungen befuellen -
|
// Controller einmalig aus den gespeicherten Einstellungen befuellen -
|
||||||
// nicht bei jedem Rebuild, sonst wird waehrend der Nutzereingabe
|
// nicht bei jedem Rebuild, sonst wird waehrend der Nutzereingabe
|
||||||
@@ -163,7 +162,6 @@ class _WifiPanelState extends ConsumerState<_WifiPanel> {
|
|||||||
if (!_controllersSeeded && connectionSettings != null) {
|
if (!_controllersSeeded && connectionSettings != null) {
|
||||||
_controllersSeeded = true;
|
_controllersSeeded = true;
|
||||||
_ssidPrefixController.text = connectionSettings.connectionWifiSsidPrefix;
|
_ssidPrefixController.text = connectionSettings.connectionWifiSsidPrefix;
|
||||||
_hostController.text = connectionSettings.connectionWifiHost ?? '';
|
|
||||||
_portController.text = connectionSettings.connectionWifiPort.toString();
|
_portController.text = connectionSettings.connectionWifiPort.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -172,50 +170,36 @@ class _WifiPanelState extends ConsumerState<_WifiPanel> {
|
|||||||
children: [
|
children: [
|
||||||
Row(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
Text('WiFi (UDP)', style: Theme.of(context).textTheme.titleSmall),
|
SizedBox(
|
||||||
|
width: 130,
|
||||||
|
child: DropdownButtonFormField<ConnectionType>(
|
||||||
|
initialValue: ConnectionType.wifi,
|
||||||
|
decoration: const InputDecoration(isDense: true, border: OutlineInputBorder()),
|
||||||
|
items: _connectionTypeItems,
|
||||||
|
onChanged: (type) async {
|
||||||
|
if (type == null) return;
|
||||||
|
await ref.read(appSettingsRepositoryProvider).setConnectionType(type);
|
||||||
|
ref.invalidate(connectionSettingsProvider);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 8),
|
||||||
|
_ConnectToggleButton(
|
||||||
|
state: linkState,
|
||||||
|
onConnect: () => _connect(ref),
|
||||||
|
onDisconnect: () => ref.read(wifiTransportProvider).disconnect(),
|
||||||
|
),
|
||||||
const Spacer(),
|
const Spacer(),
|
||||||
_StreamActivityPill(isReceiving: isReceiving),
|
_StreamActivityPill(isReceiving: isReceiving),
|
||||||
const SizedBox(width: 6),
|
const SizedBox(width: 6),
|
||||||
_StatePill(state: linkState),
|
_StatePill(state: linkState, connectedLabel: connectedSsid),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
const SizedBox(height: 4),
|
|
||||||
const Text(
|
|
||||||
'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),
|
|
||||||
),
|
|
||||||
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) ...[
|
if (errorReason != null) ...[
|
||||||
const SizedBox(height: 12),
|
const SizedBox(height: 12),
|
||||||
_WifiErrorHint(reason: errorReason),
|
_WifiErrorHint(reason: errorReason),
|
||||||
],
|
],
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 12),
|
||||||
Row(
|
Row(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
@@ -250,38 +234,11 @@ 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: [
|
|
||||||
ElevatedButton(
|
|
||||||
onPressed: () => _connect(ref),
|
|
||||||
child: const Text('Connect'),
|
|
||||||
),
|
|
||||||
const SizedBox(width: 8),
|
|
||||||
OutlinedButton(
|
|
||||||
onPressed: () => ref.read(wifiTransportProvider).disconnect(),
|
|
||||||
child: const Text('Disconnect'),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
const SizedBox(height: 8),
|
|
||||||
SwitchListTile(
|
SwitchListTile(
|
||||||
contentPadding: EdgeInsets.zero,
|
contentPadding: EdgeInsets.zero,
|
||||||
title: const Text('Auto-connect when entering Fly mode'),
|
title: const Text('Auto-connect when entering Fly mode'),
|
||||||
subtitle: const Text(
|
subtitle: const Text(
|
||||||
'Only once a network has been remembered (see above).',
|
'Only once a network has been remembered.',
|
||||||
style: TextStyle(fontSize: 11),
|
style: TextStyle(fontSize: 11),
|
||||||
),
|
),
|
||||||
value: connectionSettings?.connectionAutoConnect ?? true,
|
value: connectionSettings?.connectionAutoConnect ?? true,
|
||||||
@@ -292,7 +249,7 @@ class _WifiPanelState extends ConsumerState<_WifiPanel> {
|
|||||||
ref.invalidate(connectionSettingsProvider);
|
ref.invalidate(connectionSettingsProvider);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 8),
|
||||||
Text(
|
Text(
|
||||||
'Connection log ($packetCount packets received)',
|
'Connection log ($packetCount packets received)',
|
||||||
style: Theme.of(context).textTheme.titleSmall,
|
style: Theme.of(context).textTheme.titleSmall,
|
||||||
@@ -330,14 +287,6 @@ class _WifiPanelState extends ConsumerState<_WifiPanel> {
|
|||||||
ref.invalidate(connectionSettingsProvider);
|
ref.invalidate(connectionSettingsProvider);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _saveHost(String value) async {
|
|
||||||
final trimmed = value.trim();
|
|
||||||
await ref
|
|
||||||
.read(appSettingsRepositoryProvider)
|
|
||||||
.setConnectionWifiHost(trimmed.isEmpty ? null : trimmed);
|
|
||||||
ref.invalidate(connectionSettingsProvider);
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> _savePort(String value) async {
|
Future<void> _savePort(String value) async {
|
||||||
final port = int.tryParse(value);
|
final port = int.tryParse(value);
|
||||||
if (port == null || port <= 0 || port > 65535) return;
|
if (port == null || port <= 0 || port > 65535) return;
|
||||||
@@ -358,6 +307,30 @@ class _WifiPanelState extends ConsumerState<_WifiPanel> {
|
|||||||
'${t.hour.toString().padLeft(2, '0')}:${t.minute.toString().padLeft(2, '0')}:${t.second.toString().padLeft(2, '0')}';
|
'${t.hour.toString().padLeft(2, '0')}:${t.minute.toString().padLeft(2, '0')}:${t.second.toString().padLeft(2, '0')}';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Runder Connect/Disconnect-Umschalter fuer die kompakte erste Zeile des
|
||||||
|
/// WLAN-Panels (Doku: "kompakter gestalten") - ein Knopf statt zwei, der je
|
||||||
|
/// nach [state] Beschriftung und Aktion wechselt.
|
||||||
|
class _ConnectToggleButton extends StatelessWidget {
|
||||||
|
const _ConnectToggleButton({
|
||||||
|
required this.state,
|
||||||
|
required this.onConnect,
|
||||||
|
required this.onDisconnect,
|
||||||
|
});
|
||||||
|
|
||||||
|
final LinkState? state;
|
||||||
|
final VoidCallback onConnect;
|
||||||
|
final VoidCallback onDisconnect;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final isActive =
|
||||||
|
state == LinkState.connected || state == LinkState.connecting;
|
||||||
|
return isActive
|
||||||
|
? OutlinedButton(onPressed: onDisconnect, child: const Text('Disconnect'))
|
||||||
|
: ElevatedButton(onPressed: onConnect, child: const Text('Connect'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class _WifiErrorHint extends StatelessWidget {
|
class _WifiErrorHint extends StatelessWidget {
|
||||||
const _WifiErrorHint({required this.reason});
|
const _WifiErrorHint({required this.reason});
|
||||||
|
|
||||||
@@ -570,14 +543,19 @@ class _StreamActivityPill extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class _StatePill extends StatelessWidget {
|
class _StatePill extends StatelessWidget {
|
||||||
const _StatePill({required this.state});
|
const _StatePill({required this.state, this.connectedLabel});
|
||||||
|
|
||||||
final LinkState? state;
|
final LinkState? state;
|
||||||
|
|
||||||
|
/// Ueberschreibt die Beschriftung im verbundenen Zustand - z.B. der
|
||||||
|
/// WLAN-Netzname statt des generischen "Connected" (Doku: "die connected
|
||||||
|
/// pill soll den WiFi-Namen anzeigen").
|
||||||
|
final String? connectedLabel;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final (label, color) = switch (state) {
|
final (label, color) = switch (state) {
|
||||||
LinkState.connected => ('Connected', DmcColors.liveGreen),
|
LinkState.connected => (connectedLabel ?? 'Connected', DmcColors.liveGreen),
|
||||||
LinkState.connecting => ('Connecting…', DmcColors.pafAmber),
|
LinkState.connecting => ('Connecting…', DmcColors.pafAmber),
|
||||||
LinkState.error => ('Error', DmcColors.warnRed),
|
LinkState.error => ('Error', DmcColors.warnRed),
|
||||||
LinkState.disconnected || null => ('Disconnected', Colors.grey),
|
LinkState.disconnected || null => ('Disconnected', Colors.grey),
|
||||||
|
|||||||
Reference in New Issue
Block a user