feat(#18): inline Radio Settings form into its pane

Bring radio config up a level: render the form directly in the Radio Settings pane with a full-width Save button, instead of a tile that opens a dialog. _RadioSettingsDialog -> _RadioSettingsForm; build returns an inline Column of the (verbatim) fields + Save; _saveSettings drops the Navigator.pop (no dialog), which also resolves the unguarded-pop-after-await finding. Removed _showRadioSettings. Field widgets, controllers, preset matching, and off-grid snapshot logic unchanged.

Key the form on the device's current radio values (freq/bw/sf/cr/txpower/clientRepeat) so a device-reported change re-seeds it -- the dialog got this for free by reopening; the persistent inline form needs the key.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
pull/36/head
Strycher 1 month ago
parent eb2888162e
commit 413e47563e

@ -138,21 +138,33 @@ class _SettingsScreenState extends State<SettingsScreen> {
padding: const EdgeInsets.all(16), padding: const EdgeInsets.all(16),
children: [ children: [
Card( Card(
child: Column( child: Padding(
crossAxisAlignment: CrossAxisAlignment.start, padding: const EdgeInsets.all(16),
children: [ child: Column(
ListTile( crossAxisAlignment: CrossAxisAlignment.start,
leading: const Icon(Icons.radio), children: [
title: Text(l10n.settings_radioSettings), Text(
subtitle: Text(l10n.settings_radioSettingsSubtitle), l10n.settings_radioSettings,
trailing: const Icon(Icons.chevron_right), style: const TextStyle(
onTap: () => _showRadioSettings(context, connector), fontSize: 18,
), fontWeight: FontWeight.bold,
const Divider(height: 1), ),
_PathHashSizeTile(connector: connector), ),
], const SizedBox(height: 8),
_RadioSettingsForm(
key: ValueKey(
'${connector.currentFreqHz}-${connector.currentBwHz}-'
'${connector.currentSf}-${connector.currentCr}-'
'${connector.currentTxPower}-${connector.clientRepeat}',
),
connector: connector,
),
],
),
), ),
), ),
const SizedBox(height: 16),
Card(child: _PathHashSizeTile(connector: connector)),
], ],
); );
} }
@ -663,13 +675,6 @@ class _SettingsScreenState extends State<SettingsScreen> {
); );
} }
void _showRadioSettings(BuildContext context, MeshCoreConnector connector) {
showDialog(
context: context,
builder: (context) => _RadioSettingsDialog(connector: connector),
);
}
void _editLocation(BuildContext context, MeshCoreConnector connector) { void _editLocation(BuildContext context, MeshCoreConnector connector) {
final l10n = context.l10n; final l10n = context.l10n;
final latController = TextEditingController(); final latController = TextEditingController();
@ -1367,16 +1372,16 @@ class _AutoAddSectionState extends State<_AutoAddSection> {
} }
} }
class _RadioSettingsDialog extends StatefulWidget { class _RadioSettingsForm extends StatefulWidget {
final MeshCoreConnector connector; final MeshCoreConnector connector;
const _RadioSettingsDialog({required this.connector}); const _RadioSettingsForm({super.key, required this.connector});
@override @override
State<_RadioSettingsDialog> createState() => _RadioSettingsDialogState(); State<_RadioSettingsForm> createState() => _RadioSettingsFormState();
} }
class _RadioSettingsDialogState extends State<_RadioSettingsDialog> { class _RadioSettingsFormState extends State<_RadioSettingsForm> {
final _frequencyController = TextEditingController(); final _frequencyController = TextEditingController();
LoRaBandwidth _bandwidth = LoRaBandwidth.bw125; LoRaBandwidth _bandwidth = LoRaBandwidth.bw125;
LoRaSpreadingFactor _spreadingFactor = LoRaSpreadingFactor.sf7; LoRaSpreadingFactor _spreadingFactor = LoRaSpreadingFactor.sf7;
@ -1763,7 +1768,6 @@ class _RadioSettingsDialogState extends State<_RadioSettingsDialog> {
content: Text(l10n.settings_error(e.toString())), content: Text(l10n.settings_error(e.toString())),
); );
} }
Navigator.pop(context);
} }
String _presetLabel(int? index) { String _presetLabel(int? index) {
@ -1803,146 +1807,133 @@ class _RadioSettingsDialogState extends State<_RadioSettingsDialog> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final l10n = context.l10n; final l10n = context.l10n;
return AlertDialog( return Column(
title: Text(l10n.settings_radioSettings), mainAxisSize: MainAxisSize.min,
content: SingleChildScrollView( crossAxisAlignment: CrossAxisAlignment.start,
child: Column( children: [
mainAxisSize: MainAxisSize.min, DropdownButtonFormField<int>(
crossAxisAlignment: CrossAxisAlignment.start, key: ValueKey<int?>(_selectedPresetIndex),
children: [ initialValue: _selectedPresetIndex,
DropdownButtonFormField<int>( decoration: InputDecoration(
key: ValueKey<int?>(_selectedPresetIndex), labelText: l10n.settings_presets,
initialValue: _selectedPresetIndex, border: const OutlineInputBorder(),
decoration: InputDecoration( ),
labelText: l10n.settings_presets, items: [
border: const OutlineInputBorder(), for (final i in _visiblePresetIndexes())
), DropdownMenuItem(
items: [ value: i,
for (final i in _visiblePresetIndexes()) child: Text(RadioSettings.presets[i].$1),
DropdownMenuItem(
value: i,
child: Text(RadioSettings.presets[i].$1),
),
],
onChanged: (index) {
if (index != null) {
_applyPreset(index);
}
},
),
const SizedBox(height: 16),
TextField(
controller: _frequencyController,
onChanged: (_) => _handleManualSettingsChanged('frequency'),
decoration: InputDecoration(
labelText: l10n.settings_frequency,
border: const OutlineInputBorder(),
helperText: l10n.settings_frequencyHelper,
),
keyboardType: const TextInputType.numberWithOptions(
decimal: true,
),
),
const SizedBox(height: 16),
DropdownButtonFormField<LoRaBandwidth>(
initialValue: _bandwidth,
decoration: InputDecoration(
labelText: l10n.settings_bandwidth,
border: const OutlineInputBorder(),
),
items: LoRaBandwidth.values
.map(
(bw) => DropdownMenuItem(value: bw, child: Text(bw.label)),
)
.toList(),
onChanged: (value) {
if (value != null) {
setState(() {
_bandwidth = value;
_syncPresetSelection();
});
_logRadioSettingsState('Manual settings edit: bandwidth');
}
},
),
const SizedBox(height: 16),
DropdownButtonFormField<LoRaSpreadingFactor>(
initialValue: _spreadingFactor,
decoration: InputDecoration(
labelText: l10n.settings_spreadingFactor,
border: const OutlineInputBorder(),
),
items: LoRaSpreadingFactor.values
.map(
(sf) => DropdownMenuItem(value: sf, child: Text(sf.label)),
)
.toList(),
onChanged: (value) {
if (value != null) {
setState(() {
_spreadingFactor = value;
_syncPresetSelection();
});
_logRadioSettingsState(
'Manual settings edit: spreading factor',
);
}
},
),
const SizedBox(height: 16),
DropdownButtonFormField<LoRaCodingRate>(
initialValue: _codingRate,
decoration: InputDecoration(
labelText: l10n.settings_codingRate,
border: const OutlineInputBorder(),
),
items: LoRaCodingRate.values
.map(
(cr) => DropdownMenuItem(value: cr, child: Text(cr.label)),
)
.toList(),
onChanged: (value) {
if (value != null) {
setState(() {
_codingRate = value;
_syncPresetSelection();
});
_logRadioSettingsState('Manual settings edit: coding rate');
}
},
),
const SizedBox(height: 16),
TextField(
controller: _txPowerController,
onChanged: (_) => _handleManualSettingsChanged('tx power'),
decoration: InputDecoration(
labelText: l10n.settings_txPower,
border: const OutlineInputBorder(),
helperText: widget.connector.maxTxPower != null
? '${l10n.settings_txPowerHelper} (max: ${widget.connector.maxTxPower} dBm)'
: l10n.settings_txPowerHelper,
),
keyboardType: TextInputType.number,
),
if (widget.connector.clientRepeat != null) ...[
const SizedBox(height: 16),
SwitchListTile(
title: Text(l10n.settings_clientRepeat),
subtitle: Text(l10n.settings_clientRepeatSubtitle),
value: _clientRepeat,
onChanged: _handleClientRepeatChanged,
contentPadding: EdgeInsets.zero,
), ),
],
], ],
onChanged: (index) {
if (index != null) {
_applyPreset(index);
}
},
), ),
), const SizedBox(height: 16),
actions: [ TextField(
TextButton( controller: _frequencyController,
onPressed: () => Navigator.pop(context), onChanged: (_) => _handleManualSettingsChanged('frequency'),
child: Text(l10n.common_cancel), decoration: InputDecoration(
labelText: l10n.settings_frequency,
border: const OutlineInputBorder(),
helperText: l10n.settings_frequencyHelper,
),
keyboardType: const TextInputType.numberWithOptions(decimal: true),
),
const SizedBox(height: 16),
DropdownButtonFormField<LoRaBandwidth>(
initialValue: _bandwidth,
decoration: InputDecoration(
labelText: l10n.settings_bandwidth,
border: const OutlineInputBorder(),
),
items: LoRaBandwidth.values
.map((bw) => DropdownMenuItem(value: bw, child: Text(bw.label)))
.toList(),
onChanged: (value) {
if (value != null) {
setState(() {
_bandwidth = value;
_syncPresetSelection();
});
_logRadioSettingsState('Manual settings edit: bandwidth');
}
},
),
const SizedBox(height: 16),
DropdownButtonFormField<LoRaSpreadingFactor>(
initialValue: _spreadingFactor,
decoration: InputDecoration(
labelText: l10n.settings_spreadingFactor,
border: const OutlineInputBorder(),
),
items: LoRaSpreadingFactor.values
.map((sf) => DropdownMenuItem(value: sf, child: Text(sf.label)))
.toList(),
onChanged: (value) {
if (value != null) {
setState(() {
_spreadingFactor = value;
_syncPresetSelection();
});
_logRadioSettingsState('Manual settings edit: spreading factor');
}
},
),
const SizedBox(height: 16),
DropdownButtonFormField<LoRaCodingRate>(
initialValue: _codingRate,
decoration: InputDecoration(
labelText: l10n.settings_codingRate,
border: const OutlineInputBorder(),
),
items: LoRaCodingRate.values
.map((cr) => DropdownMenuItem(value: cr, child: Text(cr.label)))
.toList(),
onChanged: (value) {
if (value != null) {
setState(() {
_codingRate = value;
_syncPresetSelection();
});
_logRadioSettingsState('Manual settings edit: coding rate');
}
},
),
const SizedBox(height: 16),
TextField(
controller: _txPowerController,
onChanged: (_) => _handleManualSettingsChanged('tx power'),
decoration: InputDecoration(
labelText: l10n.settings_txPower,
border: const OutlineInputBorder(),
helperText: widget.connector.maxTxPower != null
? '${l10n.settings_txPowerHelper} (max: ${widget.connector.maxTxPower} dBm)'
: l10n.settings_txPowerHelper,
),
keyboardType: TextInputType.number,
),
if (widget.connector.clientRepeat != null) ...[
const SizedBox(height: 16),
SwitchListTile(
title: Text(l10n.settings_clientRepeat),
subtitle: Text(l10n.settings_clientRepeatSubtitle),
value: _clientRepeat,
onChanged: _handleClientRepeatChanged,
contentPadding: EdgeInsets.zero,
),
],
const SizedBox(height: 16),
SizedBox(
width: double.infinity,
child: FilledButton.icon(
onPressed: _saveSettings,
icon: const Icon(Icons.save_outlined),
label: Text(l10n.common_save),
),
), ),
FilledButton(onPressed: _saveSettings, child: Text(l10n.common_save)),
], ],
); );
} }

Loading…
Cancel
Save

Powered by TurnKey Linux.