mirror of
https://github.com/zjs81/meshcore-open.git
synced 2026-04-20 22:13:48 +00:00
allow disable repeater adverts
Adds checkbox to disable adverts and flood adverts Also updates flood avert range to new max of 168 hours
This commit is contained in:
parent
90ce46392a
commit
ede3142d40
1 changed files with 178 additions and 63 deletions
|
|
@ -41,7 +41,8 @@ class _RepeaterSettingsScreenState extends State<RepeaterSettingsScreen> {
|
|||
// Basic settings
|
||||
final TextEditingController _nameController = TextEditingController();
|
||||
final TextEditingController _passwordController = TextEditingController();
|
||||
final TextEditingController _guestPasswordController = TextEditingController();
|
||||
final TextEditingController _guestPasswordController =
|
||||
TextEditingController();
|
||||
|
||||
// Radio settings
|
||||
final TextEditingController _freqController = TextEditingController();
|
||||
|
|
@ -60,7 +61,9 @@ class _RepeaterSettingsScreenState extends State<RepeaterSettingsScreen> {
|
|||
bool _privacyMode = false;
|
||||
|
||||
// Advertisement settings
|
||||
bool _advertEnable = true;
|
||||
int _advertInterval = 120; // minutes/2
|
||||
bool _floodAdvertEnable = true;
|
||||
int _floodAdvertInterval = 12; // hours
|
||||
int _privAdvertInterval = 60; // minutes
|
||||
|
||||
|
|
@ -146,7 +149,10 @@ class _RepeaterSettingsScreenState extends State<RepeaterSettingsScreen> {
|
|||
if (_fetchedSettings.isEmpty) return;
|
||||
|
||||
final appLog = Provider.of<AppDebugLogService>(context, listen: false);
|
||||
appLog.info('Updating UI with keys: ${_fetchedSettings.keys.toList()}', tag: 'RadioSettings');
|
||||
appLog.info(
|
||||
'Updating UI with keys: ${_fetchedSettings.keys.toList()}',
|
||||
tag: 'RadioSettings',
|
||||
);
|
||||
|
||||
setState(() {
|
||||
// Update name
|
||||
|
|
@ -161,7 +167,10 @@ class _RepeaterSettingsScreenState extends State<RepeaterSettingsScreen> {
|
|||
final radioStr = _fetchedSettings['radio']!;
|
||||
appLog.info('Raw radio string: "$radioStr"', tag: 'RadioSettings');
|
||||
final parts = radioStr.split(',');
|
||||
appLog.info('Split into ${parts.length} parts: $parts', tag: 'RadioSettings');
|
||||
appLog.info(
|
||||
'Split into ${parts.length} parts: $parts',
|
||||
tag: 'RadioSettings',
|
||||
);
|
||||
|
||||
if (parts.isNotEmpty) {
|
||||
final freqText = parts[0].replaceAll(RegExp(r'[^0-9.]'), '').trim();
|
||||
|
|
@ -193,7 +202,10 @@ class _RepeaterSettingsScreenState extends State<RepeaterSettingsScreen> {
|
|||
appLog.info('CR text: "$crText"', tag: 'RadioSettings');
|
||||
_codingRate = int.tryParse(crText) ?? _codingRate;
|
||||
}
|
||||
appLog.info('Final values: freq=${_freqController.text}, bw=$_bandwidth, sf=$_spreadingFactor, cr=$_codingRate', tag: 'RadioSettings');
|
||||
appLog.info(
|
||||
'Final values: freq=${_freqController.text}, bw=$_bandwidth, sf=$_spreadingFactor, cr=$_codingRate',
|
||||
tag: 'RadioSettings',
|
||||
);
|
||||
}
|
||||
|
||||
if (_fetchedSettings.containsKey('tx')) {
|
||||
|
|
@ -207,11 +219,17 @@ class _RepeaterSettingsScreenState extends State<RepeaterSettingsScreen> {
|
|||
}
|
||||
|
||||
if (_fetchedSettings.containsKey('lat')) {
|
||||
appLog.info('Setting lat to: "${_fetchedSettings['lat']}"', tag: 'RadioSettings');
|
||||
appLog.info(
|
||||
'Setting lat to: "${_fetchedSettings['lat']}"',
|
||||
tag: 'RadioSettings',
|
||||
);
|
||||
_latController.text = _fetchedSettings['lat']!;
|
||||
}
|
||||
if (_fetchedSettings.containsKey('lon')) {
|
||||
appLog.info('Setting lon to: "${_fetchedSettings['lon']}"', tag: 'RadioSettings');
|
||||
appLog.info(
|
||||
'Setting lon to: "${_fetchedSettings['lon']}"',
|
||||
tag: 'RadioSettings',
|
||||
);
|
||||
_lonController.text = _fetchedSettings['lon']!;
|
||||
}
|
||||
|
||||
|
|
@ -268,7 +286,10 @@ class _RepeaterSettingsScreenState extends State<RepeaterSettingsScreen> {
|
|||
|
||||
void _applySettingResponse(String command, String response) {
|
||||
final appLog = Provider.of<AppDebugLogService>(context, listen: false);
|
||||
appLog.info('Command: "$command", Raw response: "$response"', tag: 'RadioSettings');
|
||||
appLog.info(
|
||||
'Command: "$command", Raw response: "$response"',
|
||||
tag: 'RadioSettings',
|
||||
);
|
||||
final value = _extractCliValue(response);
|
||||
appLog.info('Extracted value: "$value"', tag: 'RadioSettings');
|
||||
if (value == null) return;
|
||||
|
|
@ -280,7 +301,10 @@ class _RepeaterSettingsScreenState extends State<RepeaterSettingsScreen> {
|
|||
// Validate response content matches expected format for the command
|
||||
// This prevents mismatched responses over LoRa where order isn't guaranteed
|
||||
if (!_validateResponseForCommand(key, value)) {
|
||||
appLog.warn('Response "$value" does not match expected format for "$key", ignoring', tag: 'RadioSettings');
|
||||
appLog.warn(
|
||||
'Response "$value" does not match expected format for "$key", ignoring',
|
||||
tag: 'RadioSettings',
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -311,7 +335,9 @@ class _RepeaterSettingsScreenState extends State<RepeaterSettingsScreen> {
|
|||
// Must have at least 3 commas and start with a frequency-like number
|
||||
final parts = value.split(',');
|
||||
if (parts.length < 4) return false;
|
||||
final freq = double.tryParse(parts[0].replaceAll(RegExp(r'[^0-9.]'), ''));
|
||||
final freq = double.tryParse(
|
||||
parts[0].replaceAll(RegExp(r'[^0-9.]'), ''),
|
||||
);
|
||||
// Frequency should be in reasonable LoRa range (300-2500 MHz)
|
||||
return freq != null && freq >= 300 && freq <= 2500;
|
||||
|
||||
|
|
@ -339,7 +365,16 @@ class _RepeaterSettingsScreenState extends State<RepeaterSettingsScreen> {
|
|||
case 'privacy':
|
||||
// Boolean values: on/off/true/false/1/0/enabled/disabled
|
||||
final lower = value.toLowerCase().trim();
|
||||
return ['on', 'off', 'true', 'false', '1', '0', 'enabled', 'disabled'].contains(lower);
|
||||
return [
|
||||
'on',
|
||||
'off',
|
||||
'true',
|
||||
'false',
|
||||
'1',
|
||||
'0',
|
||||
'enabled',
|
||||
'disabled',
|
||||
].contains(lower);
|
||||
|
||||
case 'advert.interval':
|
||||
case 'flood.advert.interval':
|
||||
|
|
@ -354,7 +389,8 @@ class _RepeaterSettingsScreenState extends State<RepeaterSettingsScreen> {
|
|||
if (value.isEmpty) return false;
|
||||
// If it has 3+ commas and looks like numbers, probably radio data
|
||||
final commaCount = ','.allMatches(value).length;
|
||||
if (commaCount >= 3 && RegExp(r'^[\d.,\s]+$').hasMatch(value)) return false;
|
||||
if (commaCount >= 3 && RegExp(r'^[\d.,\s]+$').hasMatch(value))
|
||||
return false;
|
||||
return true;
|
||||
|
||||
default:
|
||||
|
|
@ -551,7 +587,9 @@ class _RepeaterSettingsScreenState extends State<RepeaterSettingsScreen> {
|
|||
final freqMHz = double.tryParse(_freqController.text);
|
||||
if (freqMHz != null) {
|
||||
final bwKHz = _bandwidth! / 1000;
|
||||
commands.add('set radio ${freqMHz.toStringAsFixed(1)} $bwKHz $_spreadingFactor $_codingRate');
|
||||
commands.add(
|
||||
'set radio ${freqMHz.toStringAsFixed(1)} $bwKHz $_spreadingFactor $_codingRate',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -590,7 +628,9 @@ class _RepeaterSettingsScreenState extends State<RepeaterSettingsScreen> {
|
|||
timestampSeconds: timestampSeconds,
|
||||
);
|
||||
await connector.sendFrame(frame);
|
||||
await Future.delayed(const Duration(milliseconds: 200)); // Delay between commands
|
||||
await Future.delayed(
|
||||
const Duration(milliseconds: 200),
|
||||
); // Delay between commands
|
||||
}
|
||||
|
||||
setState(() {
|
||||
|
|
@ -614,7 +654,9 @@ class _RepeaterSettingsScreenState extends State<RepeaterSettingsScreen> {
|
|||
if (mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(context.l10n.repeater_errorSavingSettings(e.toString())),
|
||||
content: Text(
|
||||
context.l10n.repeater_errorSavingSettings(e.toString()),
|
||||
),
|
||||
backgroundColor: Colors.red,
|
||||
),
|
||||
);
|
||||
|
|
@ -699,7 +741,10 @@ class _RepeaterSettingsScreenState extends State<RepeaterSettingsScreen> {
|
|||
Text(l10n.repeater_settingsTitle),
|
||||
Text(
|
||||
repeater.name,
|
||||
style: const TextStyle(fontSize: 14, fontWeight: FontWeight.normal),
|
||||
style: const TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.normal,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
|
@ -723,12 +768,20 @@ class _RepeaterSettingsScreenState extends State<RepeaterSettingsScreen> {
|
|||
value: 'auto',
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(Icons.auto_mode, size: 20, color: !isFloodMode ? Theme.of(context).primaryColor : null),
|
||||
Icon(
|
||||
Icons.auto_mode,
|
||||
size: 20,
|
||||
color: !isFloodMode
|
||||
? Theme.of(context).primaryColor
|
||||
: null,
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Text(
|
||||
l10n.repeater_autoUseSavedPath,
|
||||
style: TextStyle(
|
||||
fontWeight: !isFloodMode ? FontWeight.bold : FontWeight.normal,
|
||||
fontWeight: !isFloodMode
|
||||
? FontWeight.bold
|
||||
: FontWeight.normal,
|
||||
),
|
||||
),
|
||||
],
|
||||
|
|
@ -738,12 +791,20 @@ class _RepeaterSettingsScreenState extends State<RepeaterSettingsScreen> {
|
|||
value: 'flood',
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(Icons.waves, size: 20, color: isFloodMode ? Theme.of(context).primaryColor : null),
|
||||
Icon(
|
||||
Icons.waves,
|
||||
size: 20,
|
||||
color: isFloodMode
|
||||
? Theme.of(context).primaryColor
|
||||
: null,
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Text(
|
||||
l10n.repeater_forceFloodMode,
|
||||
style: TextStyle(
|
||||
fontWeight: isFloodMode ? FontWeight.bold : FontWeight.normal,
|
||||
fontWeight: isFloodMode
|
||||
? FontWeight.bold
|
||||
: FontWeight.normal,
|
||||
),
|
||||
),
|
||||
],
|
||||
|
|
@ -754,7 +815,8 @@ class _RepeaterSettingsScreenState extends State<RepeaterSettingsScreen> {
|
|||
IconButton(
|
||||
icon: const Icon(Icons.timeline),
|
||||
tooltip: l10n.repeater_pathManagement,
|
||||
onPressed: () => PathManagementDialog.show(context, contact: repeater),
|
||||
onPressed: () =>
|
||||
PathManagementDialog.show(context, contact: repeater),
|
||||
),
|
||||
if (_hasChanges)
|
||||
TextButton.icon(
|
||||
|
|
@ -865,7 +927,9 @@ class _RepeaterSettingsScreenState extends State<RepeaterSettingsScreen> {
|
|||
border: const OutlineInputBorder(),
|
||||
suffixText: 'MHz',
|
||||
),
|
||||
keyboardType: const TextInputType.numberWithOptions(decimal: true),
|
||||
keyboardType: const TextInputType.numberWithOptions(
|
||||
decimal: true,
|
||||
),
|
||||
onChanged: (_) => _markChanged(),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
|
|
@ -923,10 +987,7 @@ class _RepeaterSettingsScreenState extends State<RepeaterSettingsScreen> {
|
|||
border: const OutlineInputBorder(),
|
||||
),
|
||||
items: _spreadingFactorOptions.map((sf) {
|
||||
return DropdownMenuItem(
|
||||
value: sf,
|
||||
child: Text('SF$sf'),
|
||||
);
|
||||
return DropdownMenuItem(value: sf, child: Text('SF$sf'));
|
||||
}).toList(),
|
||||
onChanged: (value) {
|
||||
if (value != null) {
|
||||
|
|
@ -945,10 +1006,7 @@ class _RepeaterSettingsScreenState extends State<RepeaterSettingsScreen> {
|
|||
border: const OutlineInputBorder(),
|
||||
),
|
||||
items: _codingRateOptions.map((cr) {
|
||||
return DropdownMenuItem(
|
||||
value: cr,
|
||||
child: Text('4/$cr'),
|
||||
);
|
||||
return DropdownMenuItem(value: cr, child: Text('4/$cr'));
|
||||
}).toList(),
|
||||
onChanged: (value) {
|
||||
if (value != null) {
|
||||
|
|
@ -988,7 +1046,10 @@ class _RepeaterSettingsScreenState extends State<RepeaterSettingsScreen> {
|
|||
helperText: l10n.repeater_latitudeHelper,
|
||||
border: const OutlineInputBorder(),
|
||||
),
|
||||
keyboardType: const TextInputType.numberWithOptions(decimal: true, signed: true),
|
||||
keyboardType: const TextInputType.numberWithOptions(
|
||||
decimal: true,
|
||||
signed: true,
|
||||
),
|
||||
onChanged: (_) => _markChanged(),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
|
|
@ -999,7 +1060,10 @@ class _RepeaterSettingsScreenState extends State<RepeaterSettingsScreen> {
|
|||
helperText: l10n.repeater_longitudeHelper,
|
||||
border: const OutlineInputBorder(),
|
||||
),
|
||||
keyboardType: const TextInputType.numberWithOptions(decimal: true, signed: true),
|
||||
keyboardType: const TextInputType.numberWithOptions(
|
||||
decimal: true,
|
||||
signed: true,
|
||||
),
|
||||
onChanged: (_) => _markChanged(),
|
||||
),
|
||||
],
|
||||
|
|
@ -1018,11 +1082,17 @@ class _RepeaterSettingsScreenState extends State<RepeaterSettingsScreen> {
|
|||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Icon(Icons.toggle_on, color: Theme.of(context).textTheme.headlineSmall?.color),
|
||||
Icon(
|
||||
Icons.toggle_on,
|
||||
color: Theme.of(context).textTheme.headlineSmall?.color,
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Text(
|
||||
l10n.repeater_features,
|
||||
style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
|
||||
style: const TextStyle(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
|
@ -1102,7 +1172,7 @@ class _RepeaterSettingsScreenState extends State<RepeaterSettingsScreen> {
|
|||
width: 18,
|
||||
height: 18,
|
||||
child: CircularProgressIndicator(strokeWidth: 2),
|
||||
)
|
||||
)
|
||||
: const Icon(Icons.refresh, size: 20),
|
||||
onPressed: isRefreshing ? null : onRefresh,
|
||||
tooltip: refreshTooltip,
|
||||
|
|
@ -1130,40 +1200,72 @@ class _RepeaterSettingsScreenState extends State<RepeaterSettingsScreen> {
|
|||
const Divider(),
|
||||
ListTile(
|
||||
title: Text(l10n.repeater_localAdvertInterval),
|
||||
subtitle: Text(l10n.repeater_localAdvertIntervalMinutes(_advertInterval)),
|
||||
trailing: Text(l10n.repeater_localAdvertIntervalMinutes(_advertInterval)),
|
||||
subtitle: Text(
|
||||
l10n.repeater_localAdvertIntervalMinutes(_advertInterval),
|
||||
),
|
||||
trailing: Checkbox(
|
||||
value: _advertEnable,
|
||||
onChanged: (value) {
|
||||
setState(() {
|
||||
_advertInterval = value! ? 60 : 0;
|
||||
_advertEnable = value;
|
||||
});
|
||||
_markChanged();
|
||||
},
|
||||
),
|
||||
),
|
||||
Slider(
|
||||
value: _advertInterval.toDouble(),
|
||||
value: _advertInterval == 0
|
||||
? 60.toDouble()
|
||||
: _advertInterval.toDouble(),
|
||||
min: 60,
|
||||
max: 240,
|
||||
divisions: 18,
|
||||
label: l10n.repeater_localAdvertIntervalMinutes(_advertInterval),
|
||||
onChanged: (value) {
|
||||
setState(() {
|
||||
_advertInterval = value.toInt();
|
||||
});
|
||||
_markChanged();
|
||||
},
|
||||
onChanged: _advertEnable
|
||||
? (value) {
|
||||
setState(() {
|
||||
_advertInterval = value.toInt();
|
||||
});
|
||||
_markChanged();
|
||||
}
|
||||
: null,
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
ListTile(
|
||||
title: Text(l10n.repeater_floodAdvertInterval),
|
||||
subtitle: Text(l10n.repeater_floodAdvertIntervalHours(_floodAdvertInterval)),
|
||||
trailing: Text(l10n.repeater_floodAdvertIntervalHours(_floodAdvertInterval)),
|
||||
subtitle: Text(
|
||||
l10n.repeater_floodAdvertIntervalHours(_floodAdvertInterval),
|
||||
),
|
||||
trailing: Checkbox(
|
||||
value: _floodAdvertEnable,
|
||||
onChanged: (value) {
|
||||
setState(() {
|
||||
_floodAdvertInterval = value! ? 3 : 0;
|
||||
_floodAdvertEnable = value;
|
||||
});
|
||||
_markChanged();
|
||||
},
|
||||
),
|
||||
),
|
||||
Slider(
|
||||
value: _floodAdvertInterval.toDouble(),
|
||||
value: _floodAdvertInterval == 0
|
||||
? 3.toDouble()
|
||||
: _floodAdvertInterval.toDouble(),
|
||||
min: 3,
|
||||
max: 48,
|
||||
divisions: 45,
|
||||
label: l10n.repeater_floodAdvertIntervalHours(_floodAdvertInterval),
|
||||
onChanged: (value) {
|
||||
setState(() {
|
||||
_floodAdvertInterval = value.toInt();
|
||||
});
|
||||
_markChanged();
|
||||
},
|
||||
max: 168,
|
||||
divisions: 165,
|
||||
label: l10n.repeater_floodAdvertIntervalHours(
|
||||
_floodAdvertInterval,
|
||||
),
|
||||
onChanged: _floodAdvertEnable
|
||||
? (value) {
|
||||
setState(() {
|
||||
_floodAdvertInterval = value.toInt();
|
||||
});
|
||||
_markChanged();
|
||||
}
|
||||
: null,
|
||||
),
|
||||
// Encrypted advertisement interval - hidden until privacy mode is implemented
|
||||
// if (_privacyMode) ...[
|
||||
|
|
@ -1220,10 +1322,15 @@ class _RepeaterSettingsScreenState extends State<RepeaterSettingsScreen> {
|
|||
const Divider(),
|
||||
ListTile(
|
||||
leading: Icon(Icons.refresh, color: colorScheme.onErrorContainer),
|
||||
title: Text(l10n.repeater_rebootRepeater, style: TextStyle(color: colorScheme.onErrorContainer)),
|
||||
title: Text(
|
||||
l10n.repeater_rebootRepeater,
|
||||
style: TextStyle(color: colorScheme.onErrorContainer),
|
||||
),
|
||||
subtitle: Text(
|
||||
l10n.repeater_rebootRepeaterSubtitle,
|
||||
style: TextStyle(color: colorScheme.onErrorContainer.withValues(alpha: 0.8)),
|
||||
style: TextStyle(
|
||||
color: colorScheme.onErrorContainer.withValues(alpha: 0.8),
|
||||
),
|
||||
),
|
||||
onTap: () => _confirmAction(
|
||||
l10n.repeater_rebootRepeater,
|
||||
|
|
@ -1246,11 +1353,19 @@ class _RepeaterSettingsScreenState extends State<RepeaterSettingsScreen> {
|
|||
// ),
|
||||
// ),
|
||||
ListTile(
|
||||
leading: Icon(Icons.delete_forever, color: colorScheme.onErrorContainer),
|
||||
title: Text(l10n.repeater_eraseFileSystem, style: TextStyle(color: colorScheme.onErrorContainer)),
|
||||
leading: Icon(
|
||||
Icons.delete_forever,
|
||||
color: colorScheme.onErrorContainer,
|
||||
),
|
||||
title: Text(
|
||||
l10n.repeater_eraseFileSystem,
|
||||
style: TextStyle(color: colorScheme.onErrorContainer),
|
||||
),
|
||||
subtitle: Text(
|
||||
l10n.repeater_eraseFileSystemSubtitle,
|
||||
style: TextStyle(color: colorScheme.onErrorContainer.withValues(alpha: 0.8)),
|
||||
style: TextStyle(
|
||||
color: colorScheme.onErrorContainer.withValues(alpha: 0.8),
|
||||
),
|
||||
),
|
||||
onTap: () => _confirmAction(
|
||||
l10n.repeater_eraseFileSystem,
|
||||
|
|
@ -1272,9 +1387,9 @@ class _RepeaterSettingsScreenState extends State<RepeaterSettingsScreen> {
|
|||
|
||||
if (command == 'erase') {
|
||||
if (mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text(l10n.repeater_eraseSerialOnly)),
|
||||
);
|
||||
ScaffoldMessenger.of(
|
||||
context,
|
||||
).showSnackBar(SnackBar(content: Text(l10n.repeater_eraseSerialOnly)));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue