mirror of
https://github.com/zjs81/meshcore-open.git
synced 2026-06-14 22:55:12 +10:00
fix(settings): preserve preset across off-grid repeat
This commit is contained in:
@@ -102,6 +102,22 @@ class RepeaterBatterySnapshot {
|
||||
});
|
||||
}
|
||||
|
||||
class MeshCoreRadioStateSnapshot {
|
||||
final int freqHz;
|
||||
final int bwHz;
|
||||
final int sf;
|
||||
final int cr;
|
||||
final int txPowerDbm;
|
||||
|
||||
const MeshCoreRadioStateSnapshot({
|
||||
required this.freqHz,
|
||||
required this.bwHz,
|
||||
required this.sf,
|
||||
required this.cr,
|
||||
required this.txPowerDbm,
|
||||
});
|
||||
}
|
||||
|
||||
class MeshCoreConnector extends ChangeNotifier {
|
||||
// Message windowing to limit memory usage
|
||||
static const int _messageWindowSize = 200;
|
||||
@@ -167,6 +183,7 @@ class MeshCoreConnector extends ChangeNotifier {
|
||||
int? _currentSf;
|
||||
int? _currentCr;
|
||||
bool? _clientRepeat;
|
||||
MeshCoreRadioStateSnapshot? _rememberedNonRepeatRadioState;
|
||||
int? _firmwareVerCode;
|
||||
int _pathHashByteWidth = 1;
|
||||
CompanionRadioStats? _latestRadioStats;
|
||||
@@ -366,6 +383,8 @@ class MeshCoreConnector extends ChangeNotifier {
|
||||
int? get currentBwHz => _currentBwHz;
|
||||
int? get currentSf => _currentSf;
|
||||
int? get currentCr => _currentCr;
|
||||
MeshCoreRadioStateSnapshot? get rememberedNonRepeatRadioState =>
|
||||
_rememberedNonRepeatRadioState;
|
||||
bool? get autoAddUsers => _autoAddUsers;
|
||||
bool? get autoAddRepeaters => _autoAddRepeaters;
|
||||
bool? get autoAddRoomServers => _autoAddRoomServers;
|
||||
@@ -377,6 +396,10 @@ class MeshCoreConnector extends ChangeNotifier {
|
||||
int get advertLocationPolicy => _advertLocPolicy;
|
||||
int get multiAcks => _multiAcks;
|
||||
bool? get clientRepeat => _clientRepeat;
|
||||
void rememberNonRepeatRadioState(MeshCoreRadioStateSnapshot snapshot) {
|
||||
_rememberedNonRepeatRadioState = snapshot;
|
||||
}
|
||||
|
||||
int? get firmwareVerCode => _firmwareVerCode;
|
||||
Map<String, String>? get currentCustomVars => _currentCustomVars;
|
||||
int? get batteryMillivolts => _batteryMillivolts;
|
||||
@@ -2152,6 +2175,7 @@ class MeshCoreConnector extends ChangeNotifier {
|
||||
_selfLatitude = null;
|
||||
_selfLongitude = null;
|
||||
_clientRepeat = null;
|
||||
_rememberedNonRepeatRadioState = null;
|
||||
_firmwareVerCode = null;
|
||||
_batteryMillivolts = null;
|
||||
_repeaterBatterySnapshots.clear();
|
||||
|
||||
@@ -8,6 +8,7 @@ import '../connector/meshcore_connector.dart';
|
||||
import '../connector/meshcore_protocol.dart';
|
||||
import '../l10n/l10n.dart';
|
||||
import '../models/radio_settings.dart';
|
||||
import '../services/app_debug_log_service.dart';
|
||||
import '../widgets/app_bar.dart';
|
||||
import 'app_settings_screen.dart';
|
||||
import 'app_debug_log_screen.dart';
|
||||
@@ -1089,6 +1090,10 @@ class _RadioSettingsDialogState extends State<_RadioSettingsDialog> {
|
||||
final _txPowerController = TextEditingController(text: '20');
|
||||
bool _clientRepeat = false;
|
||||
int? _selectedPresetIndex;
|
||||
_RadioSettingsSnapshot? _lastNonRepeatSnapshot;
|
||||
|
||||
AppDebugLogService get _appLog =>
|
||||
Provider.of<AppDebugLogService>(context, listen: false);
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
@@ -1141,6 +1146,23 @@ class _RadioSettingsDialogState extends State<_RadioSettingsDialog> {
|
||||
|
||||
_clientRepeat = widget.connector.clientRepeat ?? false;
|
||||
_selectedPresetIndex = _findMatchingPresetIndex();
|
||||
_lastNonRepeatSnapshot = _currentSnapshot();
|
||||
if (_clientRepeat) {
|
||||
_lastNonRepeatSnapshot =
|
||||
_sessionRememberedNonRepeatSnapshot() ??
|
||||
_inferNonRepeatSnapshotForRepeatEnabled();
|
||||
_selectedPresetIndex = _findMatchingPresetIndexForSnapshot(
|
||||
_lastNonRepeatSnapshot!,
|
||||
);
|
||||
} else {
|
||||
_lastNonRepeatSnapshot =
|
||||
_sessionRememberedNonRepeatSnapshot() ??
|
||||
_nonRepeatSnapshotForCurrentSelection();
|
||||
}
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
if (!mounted) return;
|
||||
_logRadioSettingsState('Dialog initialized');
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -1150,35 +1172,60 @@ class _RadioSettingsDialogState extends State<_RadioSettingsDialog> {
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void _applyPreset(RadioSettings preset) {
|
||||
void _applyPreset(int index) {
|
||||
setState(() {
|
||||
_frequencyController.text = preset.frequencyMHz.toString();
|
||||
_bandwidth = preset.bandwidth;
|
||||
_spreadingFactor = preset.spreadingFactor;
|
||||
_codingRate = preset.codingRate;
|
||||
_txPowerController.text = preset.txPowerDbm.toString();
|
||||
_applyPresetState(index);
|
||||
});
|
||||
_logRadioSettingsState(
|
||||
'Applied preset ${RadioSettings.presets[index].$1} (#$index)',
|
||||
);
|
||||
}
|
||||
|
||||
int? _findMatchingPresetIndex() {
|
||||
final freqMHz = double.tryParse(_frequencyController.text);
|
||||
final txPower = int.tryParse(_txPowerController.text);
|
||||
if (freqMHz == null || txPower == null) return null;
|
||||
return _findMatchingPresetIndexForSnapshot(_currentSnapshot());
|
||||
}
|
||||
|
||||
int? _findMatchingPresetIndexForSnapshot(_RadioSettingsSnapshot snapshot) {
|
||||
const epsilon = 0.001;
|
||||
for (var i = 0; i < RadioSettings.presets.length; i++) {
|
||||
for (final i in _visiblePresetIndexes()) {
|
||||
final preset = RadioSettings.presets[i].$2;
|
||||
if ((preset.frequencyMHz - freqMHz).abs() < epsilon &&
|
||||
preset.bandwidth == _bandwidth &&
|
||||
preset.spreadingFactor == _spreadingFactor &&
|
||||
preset.codingRate == _codingRate &&
|
||||
preset.txPowerDbm == txPower) {
|
||||
if ((preset.frequencyMHz - snapshot.frequencyMHz).abs() < epsilon &&
|
||||
preset.bandwidth == snapshot.bandwidth &&
|
||||
preset.spreadingFactor == snapshot.spreadingFactor &&
|
||||
preset.codingRate == snapshot.codingRate &&
|
||||
preset.txPowerDbm == snapshot.txPowerDbm) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
Iterable<int> _visiblePresetIndexes() sync* {
|
||||
for (var i = 0; i < RadioSettings.presets.length; i++) {
|
||||
if (_isOffGridPresetIndex(i)) {
|
||||
continue;
|
||||
}
|
||||
yield i;
|
||||
}
|
||||
}
|
||||
|
||||
_RadioSettingsSnapshot _currentSnapshot() {
|
||||
final frequencyMHz = double.tryParse(_frequencyController.text) ?? 915.0;
|
||||
final txPowerDbm = int.tryParse(_txPowerController.text) ?? 20;
|
||||
return _RadioSettingsSnapshot(
|
||||
frequencyMHz: frequencyMHz,
|
||||
bandwidth: _bandwidth,
|
||||
spreadingFactor: _spreadingFactor,
|
||||
codingRate: _codingRate,
|
||||
txPowerDbm: txPowerDbm,
|
||||
);
|
||||
}
|
||||
|
||||
bool _isOffGridPresetIndex(int? index) {
|
||||
if (index == null) return false;
|
||||
return RadioSettings.presets[index].$1.startsWith('Off-Grid ');
|
||||
}
|
||||
|
||||
double _offGridFrequencyForBaseFrequency(double baseFrequencyMHz) {
|
||||
if (baseFrequencyMHz < 500) return 433.0;
|
||||
if (baseFrequencyMHz < 900) return 869.0;
|
||||
@@ -1191,22 +1238,182 @@ class _RadioSettingsDialogState extends State<_RadioSettingsDialog> {
|
||||
return 915.8;
|
||||
}
|
||||
|
||||
_RadioSettingsSnapshot _fallbackNonRepeatSnapshot(
|
||||
double currentFrequencyMHz,
|
||||
) {
|
||||
return _RadioSettingsSnapshot(
|
||||
frequencyMHz: _normalFrequencyForBand(currentFrequencyMHz),
|
||||
bandwidth: _bandwidth,
|
||||
spreadingFactor: _spreadingFactor,
|
||||
codingRate: _codingRate,
|
||||
txPowerDbm: int.tryParse(_txPowerController.text) ?? 20,
|
||||
);
|
||||
}
|
||||
|
||||
_RadioSettingsSnapshot _nonRepeatSnapshotForCurrentSelection() {
|
||||
final current = _currentSnapshot();
|
||||
if (!_isOffGridPresetIndex(_selectedPresetIndex)) {
|
||||
return current;
|
||||
}
|
||||
return _fallbackNonRepeatSnapshot(current.frequencyMHz);
|
||||
}
|
||||
|
||||
_RadioSettingsSnapshot? _sessionRememberedNonRepeatSnapshot() {
|
||||
final snapshot = widget.connector.rememberedNonRepeatRadioState;
|
||||
if (snapshot == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
final bandwidth = LoRaBandwidth.values
|
||||
.where((bw) => bw.hz == snapshot.bwHz)
|
||||
.firstOrNull;
|
||||
final spreadingFactor = LoRaSpreadingFactor.values
|
||||
.where((sf) => sf.value == snapshot.sf)
|
||||
.firstOrNull;
|
||||
final codingRate = LoRaCodingRate.values
|
||||
.where((cr) => cr.value == _toUiCodingRate(snapshot.cr))
|
||||
.firstOrNull;
|
||||
|
||||
if (bandwidth == null || spreadingFactor == null || codingRate == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return _RadioSettingsSnapshot(
|
||||
frequencyMHz: snapshot.freqHz / 1000.0,
|
||||
bandwidth: bandwidth,
|
||||
spreadingFactor: spreadingFactor,
|
||||
codingRate: codingRate,
|
||||
txPowerDbm: snapshot.txPowerDbm,
|
||||
);
|
||||
}
|
||||
|
||||
_RadioSettingsSnapshot _inferNonRepeatSnapshotForRepeatEnabled() {
|
||||
final current = _currentSnapshot();
|
||||
const epsilon = 0.001;
|
||||
for (final i in _visiblePresetIndexes()) {
|
||||
final preset = RadioSettings.presets[i].$2;
|
||||
final offGridFrequencyMHz = _offGridFrequencyForBaseFrequency(
|
||||
preset.frequencyMHz,
|
||||
);
|
||||
if ((offGridFrequencyMHz - current.frequencyMHz).abs() < epsilon &&
|
||||
preset.bandwidth == current.bandwidth &&
|
||||
preset.spreadingFactor == current.spreadingFactor &&
|
||||
preset.codingRate == current.codingRate &&
|
||||
preset.txPowerDbm == current.txPowerDbm) {
|
||||
return _RadioSettingsSnapshot(
|
||||
frequencyMHz: preset.frequencyMHz,
|
||||
bandwidth: preset.bandwidth,
|
||||
spreadingFactor: preset.spreadingFactor,
|
||||
codingRate: preset.codingRate,
|
||||
txPowerDbm: preset.txPowerDbm,
|
||||
);
|
||||
}
|
||||
}
|
||||
return _fallbackNonRepeatSnapshot(current.frequencyMHz);
|
||||
}
|
||||
|
||||
void _applySnapshot(_RadioSettingsSnapshot snapshot) {
|
||||
_frequencyController.text = snapshot.frequencyMHz.toStringAsFixed(3);
|
||||
_bandwidth = snapshot.bandwidth;
|
||||
_spreadingFactor = snapshot.spreadingFactor;
|
||||
_codingRate = snapshot.codingRate;
|
||||
_txPowerController.text = snapshot.txPowerDbm.toString();
|
||||
}
|
||||
|
||||
void _applyPresetState(int index) {
|
||||
final preset = RadioSettings.presets[index].$2;
|
||||
final baseSnapshot = _RadioSettingsSnapshot(
|
||||
frequencyMHz: preset.frequencyMHz,
|
||||
bandwidth: preset.bandwidth,
|
||||
spreadingFactor: preset.spreadingFactor,
|
||||
codingRate: preset.codingRate,
|
||||
txPowerDbm: preset.txPowerDbm,
|
||||
);
|
||||
final frequencyMHz = _clientRepeat
|
||||
? _offGridFrequencyForBaseFrequency(baseSnapshot.frequencyMHz)
|
||||
: baseSnapshot.frequencyMHz;
|
||||
_frequencyController.text = frequencyMHz.toString();
|
||||
_bandwidth = preset.bandwidth;
|
||||
_spreadingFactor = preset.spreadingFactor;
|
||||
_codingRate = preset.codingRate;
|
||||
_txPowerController.text = preset.txPowerDbm.toString();
|
||||
_selectedPresetIndex = index;
|
||||
_lastNonRepeatSnapshot = baseSnapshot;
|
||||
}
|
||||
|
||||
void _syncPresetSelection() {
|
||||
final previousPresetIndex = _selectedPresetIndex;
|
||||
final previousLastNonRepeat = _lastNonRepeatSnapshot;
|
||||
if (_clientRepeat) {
|
||||
final baseSnapshot =
|
||||
previousLastNonRepeat ?? _inferNonRepeatSnapshotForRepeatEnabled();
|
||||
if (_bandwidth != baseSnapshot.bandwidth ||
|
||||
_spreadingFactor != baseSnapshot.spreadingFactor ||
|
||||
_codingRate != baseSnapshot.codingRate ||
|
||||
(int.tryParse(_txPowerController.text) ?? 20) !=
|
||||
baseSnapshot.txPowerDbm) {
|
||||
_lastNonRepeatSnapshot = _RadioSettingsSnapshot(
|
||||
frequencyMHz: baseSnapshot.frequencyMHz,
|
||||
bandwidth: _bandwidth,
|
||||
spreadingFactor: _spreadingFactor,
|
||||
codingRate: _codingRate,
|
||||
txPowerDbm: int.tryParse(_txPowerController.text) ?? 20,
|
||||
);
|
||||
}
|
||||
_selectedPresetIndex = _findMatchingPresetIndexForSnapshot(
|
||||
_lastNonRepeatSnapshot ?? baseSnapshot,
|
||||
);
|
||||
if (previousPresetIndex != _selectedPresetIndex ||
|
||||
previousLastNonRepeat != _lastNonRepeatSnapshot) {
|
||||
_logRadioSettingsState(
|
||||
'Preset match updated while repeat enabled: ${_presetLabel(previousPresetIndex)} -> ${_presetLabel(_selectedPresetIndex)}',
|
||||
);
|
||||
}
|
||||
return;
|
||||
}
|
||||
_lastNonRepeatSnapshot = _nonRepeatSnapshotForCurrentSelection();
|
||||
_selectedPresetIndex = _findMatchingPresetIndexForSnapshot(
|
||||
_lastNonRepeatSnapshot!,
|
||||
);
|
||||
if (previousPresetIndex != _selectedPresetIndex ||
|
||||
previousLastNonRepeat != _lastNonRepeatSnapshot) {
|
||||
_logRadioSettingsState(
|
||||
'Preset sync updated state from ${_presetLabel(previousPresetIndex)} to ${_presetLabel(_selectedPresetIndex)}',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void _handleManualSettingsChanged(String source) {
|
||||
_logRadioSettingsState('Manual settings edit: $source');
|
||||
setState(_syncPresetSelection);
|
||||
}
|
||||
|
||||
void _handleClientRepeatChanged(bool enabled) {
|
||||
_logRadioSettingsState(
|
||||
'Off-grid repeat toggle requested: $_clientRepeat -> $enabled',
|
||||
);
|
||||
setState(() {
|
||||
_clientRepeat = enabled;
|
||||
final currentSnapshot = _currentSnapshot();
|
||||
if (enabled) {
|
||||
if (!_clientRepeat) {
|
||||
_syncPresetSelection();
|
||||
}
|
||||
final baseSnapshot = _lastNonRepeatSnapshot ?? currentSnapshot;
|
||||
_clientRepeat = true;
|
||||
_frequencyController.text = _offGridFrequencyForBaseFrequency(
|
||||
baseSnapshot.frequencyMHz,
|
||||
).toStringAsFixed(3);
|
||||
return;
|
||||
}
|
||||
|
||||
final baseFrequencyMHz = _selectedPresetIndex != null
|
||||
? RadioSettings.presets[_selectedPresetIndex!].$2.frequencyMHz
|
||||
: (double.tryParse(_frequencyController.text) ?? 915.0);
|
||||
|
||||
final nextFrequencyMHz = enabled
|
||||
? _offGridFrequencyForBaseFrequency(baseFrequencyMHz)
|
||||
: (_selectedPresetIndex != null
|
||||
? RadioSettings.presets[_selectedPresetIndex!].$2.frequencyMHz
|
||||
: _normalFrequencyForBand(baseFrequencyMHz));
|
||||
|
||||
_frequencyController.text = nextFrequencyMHz.toStringAsFixed(3);
|
||||
_clientRepeat = false;
|
||||
_applySnapshot(
|
||||
_lastNonRepeatSnapshot ??
|
||||
_fallbackNonRepeatSnapshot(currentSnapshot.frequencyMHz),
|
||||
);
|
||||
_syncPresetSelection();
|
||||
});
|
||||
_logRadioSettingsState('Off-grid repeat toggle applied');
|
||||
}
|
||||
|
||||
Future<void> _saveSettings() async {
|
||||
@@ -1254,6 +1461,24 @@ class _RadioSettingsDialogState extends State<_RadioSettingsDialog> {
|
||||
}
|
||||
|
||||
try {
|
||||
final rememberedSnapshot = _clientRepeat
|
||||
? _lastNonRepeatSnapshot
|
||||
: _currentSnapshot();
|
||||
if (rememberedSnapshot != null) {
|
||||
widget.connector.rememberNonRepeatRadioState(
|
||||
MeshCoreRadioStateSnapshot(
|
||||
freqHz: (rememberedSnapshot.frequencyMHz * 1000).round(),
|
||||
bwHz: rememberedSnapshot.bandwidth.hz,
|
||||
sf: rememberedSnapshot.spreadingFactor.value,
|
||||
cr: _toDeviceCodingRate(
|
||||
rememberedSnapshot.codingRate.value,
|
||||
widget.connector.currentCr,
|
||||
),
|
||||
txPowerDbm: rememberedSnapshot.txPowerDbm,
|
||||
),
|
||||
);
|
||||
}
|
||||
_logRadioSettingsState('Saving radio settings');
|
||||
await widget.connector.sendFrame(
|
||||
buildSetRadioParamsFrame(
|
||||
freqHz,
|
||||
@@ -1268,10 +1493,12 @@ class _RadioSettingsDialogState extends State<_RadioSettingsDialog> {
|
||||
|
||||
if (!mounted) return;
|
||||
Navigator.pop(context);
|
||||
_logRadioSettingsState('Radio settings saved successfully');
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text(l10n.settings_radioSettingsUpdated)),
|
||||
);
|
||||
} catch (e) {
|
||||
_appLog.warn('Radio settings save failed: $e', tag: 'RadioSettings');
|
||||
if (!mounted) return;
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text(l10n.settings_error(e.toString()))),
|
||||
@@ -1290,6 +1517,39 @@ class _RadioSettingsDialogState extends State<_RadioSettingsDialog> {
|
||||
return uiCr;
|
||||
}
|
||||
|
||||
String _presetLabel(int? index) {
|
||||
if (index == null) {
|
||||
return 'custom';
|
||||
}
|
||||
return '${RadioSettings.presets[index].$1} (#$index)';
|
||||
}
|
||||
|
||||
String _formatSnapshot(_RadioSettingsSnapshot? snapshot) {
|
||||
if (snapshot == null) {
|
||||
return 'null';
|
||||
}
|
||||
return '${snapshot.frequencyMHz.toStringAsFixed(3)}MHz/'
|
||||
'${snapshot.bandwidth.label}/'
|
||||
'${snapshot.spreadingFactor.label}/'
|
||||
'${snapshot.codingRate.label}/'
|
||||
'${snapshot.txPowerDbm}dBm';
|
||||
}
|
||||
|
||||
void _logRadioSettingsState(String message) {
|
||||
_appLog.info(
|
||||
'$message | '
|
||||
'freq=${_frequencyController.text}MHz '
|
||||
'bw=${_bandwidth.label} '
|
||||
'sf=${_spreadingFactor.label} '
|
||||
'cr=${_codingRate.label} '
|
||||
'tx=${_txPowerController.text}dBm '
|
||||
'repeat=$_clientRepeat '
|
||||
'preset=${_presetLabel(_selectedPresetIndex)} '
|
||||
'lastNonRepeat=${_formatSnapshot(_lastNonRepeatSnapshot)}',
|
||||
tag: 'RadioSettings',
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final l10n = context.l10n;
|
||||
@@ -1301,13 +1561,14 @@ class _RadioSettingsDialogState extends State<_RadioSettingsDialog> {
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
DropdownButtonFormField<int>(
|
||||
key: ValueKey<int?>(_selectedPresetIndex),
|
||||
initialValue: _selectedPresetIndex,
|
||||
decoration: InputDecoration(
|
||||
labelText: l10n.settings_presets,
|
||||
border: const OutlineInputBorder(),
|
||||
),
|
||||
items: [
|
||||
for (var i = 0; i < RadioSettings.presets.length; i++)
|
||||
for (final i in _visiblePresetIndexes())
|
||||
DropdownMenuItem(
|
||||
value: i,
|
||||
child: Text(RadioSettings.presets[i].$1),
|
||||
@@ -1315,14 +1576,14 @@ class _RadioSettingsDialogState extends State<_RadioSettingsDialog> {
|
||||
],
|
||||
onChanged: (index) {
|
||||
if (index != null) {
|
||||
_selectedPresetIndex = index;
|
||||
_applyPreset(RadioSettings.presets[index].$2);
|
||||
_applyPreset(index);
|
||||
}
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
TextField(
|
||||
controller: _frequencyController,
|
||||
onChanged: (_) => _handleManualSettingsChanged('frequency'),
|
||||
decoration: InputDecoration(
|
||||
labelText: l10n.settings_frequency,
|
||||
border: const OutlineInputBorder(),
|
||||
@@ -1345,7 +1606,13 @@ class _RadioSettingsDialogState extends State<_RadioSettingsDialog> {
|
||||
)
|
||||
.toList(),
|
||||
onChanged: (value) {
|
||||
if (value != null) setState(() => _bandwidth = value);
|
||||
if (value != null) {
|
||||
setState(() {
|
||||
_bandwidth = value;
|
||||
_syncPresetSelection();
|
||||
});
|
||||
_logRadioSettingsState('Manual settings edit: bandwidth');
|
||||
}
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
@@ -1361,7 +1628,15 @@ class _RadioSettingsDialogState extends State<_RadioSettingsDialog> {
|
||||
)
|
||||
.toList(),
|
||||
onChanged: (value) {
|
||||
if (value != null) setState(() => _spreadingFactor = value);
|
||||
if (value != null) {
|
||||
setState(() {
|
||||
_spreadingFactor = value;
|
||||
_syncPresetSelection();
|
||||
});
|
||||
_logRadioSettingsState(
|
||||
'Manual settings edit: spreading factor',
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
@@ -1377,12 +1652,19 @@ class _RadioSettingsDialogState extends State<_RadioSettingsDialog> {
|
||||
)
|
||||
.toList(),
|
||||
onChanged: (value) {
|
||||
if (value != null) setState(() => _codingRate = 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(),
|
||||
@@ -1415,3 +1697,38 @@ class _RadioSettingsDialogState extends State<_RadioSettingsDialog> {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _RadioSettingsSnapshot {
|
||||
final double frequencyMHz;
|
||||
final LoRaBandwidth bandwidth;
|
||||
final LoRaSpreadingFactor spreadingFactor;
|
||||
final LoRaCodingRate codingRate;
|
||||
final int txPowerDbm;
|
||||
|
||||
const _RadioSettingsSnapshot({
|
||||
required this.frequencyMHz,
|
||||
required this.bandwidth,
|
||||
required this.spreadingFactor,
|
||||
required this.codingRate,
|
||||
required this.txPowerDbm,
|
||||
});
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return other is _RadioSettingsSnapshot &&
|
||||
frequencyMHz == other.frequencyMHz &&
|
||||
bandwidth == other.bandwidth &&
|
||||
spreadingFactor == other.spreadingFactor &&
|
||||
codingRate == other.codingRate &&
|
||||
txPowerDbm == other.txPowerDbm;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => Object.hash(
|
||||
frequencyMHz,
|
||||
bandwidth,
|
||||
spreadingFactor,
|
||||
codingRate,
|
||||
txPowerDbm,
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user