diff --git a/lib/connector/meshcore_connector.dart b/lib/connector/meshcore_connector.dart index f2d8040c..59ef4e6f 100644 --- a/lib/connector/meshcore_connector.dart +++ b/lib/connector/meshcore_connector.dart @@ -2571,7 +2571,14 @@ class MeshCoreConnector extends ChangeNotifier { Future setPathHashMode(int mode) async { if (!isConnected) return; - await sendFrame(buildSetPathHashModeFrame(mode.clamp(0, 2))); + final clampedMode = mode.clamp(0, 2).toInt(); + await sendFrame(buildSetPathHashModeFrame(clampedMode)); + final nextWidth = clampedMode + 1; + if (_pathHashByteWidth != nextWidth) { + _pathHashByteWidth = nextWidth; + _directRepeaters.clear(); + notifyListeners(); + } } Future refreshDeviceInfo() async { diff --git a/lib/screens/settings_screen.dart b/lib/screens/settings_screen.dart index 5679f757..3dd8c812 100644 --- a/lib/screens/settings_screen.dart +++ b/lib/screens/settings_screen.dart @@ -298,6 +298,15 @@ class _SettingsScreenState extends State { onTap: () => pushCompanionRadioStatsScreen(context), ), const Divider(height: 1), + ListTile( + leading: const Icon(Icons.route_outlined), + title: Text(l10n.repeater_pathHashMode), + subtitle: Text(_pathHashModeSubtitle(connector.pathHashByteWidth)), + trailing: const Icon(Icons.chevron_right), + enabled: connector.isConnected, + onTap: () => _editPathHashMode(context, connector), + ), + const Divider(height: 1), ListTile( leading: const Icon(Icons.location_on_outlined), title: Text(l10n.settings_location), @@ -338,6 +347,13 @@ class _SettingsScreenState extends State { ); } + String _pathHashModeSubtitle(int pathHashByteWidth) { + final width = pathHashByteWidth.clamp(1, 3).toInt(); + final mode = width - 1; + final unit = width == 1 ? 'byte' : 'bytes'; + return 'Mode $mode - $width $unit per hop'; + } + Widget _buildActionsCard(BuildContext context, MeshCoreConnector connector) { final l10n = context.l10n; return Card( @@ -545,6 +561,74 @@ class _SettingsScreenState extends State { ); } + void _editPathHashMode(BuildContext context, MeshCoreConnector connector) { + final l10n = context.l10n; + var selectedMode = (connector.pathHashByteWidth - 1).clamp(0, 2).toInt(); + + showDialog( + context: context, + builder: (dialogContext) => StatefulBuilder( + builder: (context, setDialogState) => AlertDialog( + title: Text(l10n.repeater_pathHashMode), + content: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + DropdownButtonFormField( + initialValue: selectedMode, + decoration: InputDecoration( + labelText: l10n.repeater_pathHashMode, + border: const OutlineInputBorder(), + ), + items: const [ + DropdownMenuItem(value: 0, child: Text('0 - 1 byte')), + DropdownMenuItem(value: 1, child: Text('1 - 2 bytes')), + DropdownMenuItem(value: 2, child: Text('2 - 3 bytes')), + ], + onChanged: (value) { + if (value == null) return; + setDialogState(() => selectedMode = value); + }, + ), + const SizedBox(height: 12), + Text( + l10n.repeater_pathHashModeHelper, + style: Theme.of(context).textTheme.bodySmall, + ), + ], + ), + actions: [ + TextButton( + onPressed: () => Navigator.pop(dialogContext), + child: Text(l10n.common_cancel), + ), + FilledButton( + onPressed: () async { + Navigator.pop(dialogContext); + try { + await connector.setPathHashMode(selectedMode); + await connector.refreshDeviceInfo(); + if (!context.mounted) return; + showDismissibleSnackBar( + context, + content: Text(l10n.repeater_settingsSaved), + ); + } catch (e) { + if (!context.mounted) return; + showDismissibleSnackBar( + context, + content: Text(l10n.settings_error(e.toString())), + ); + } + }, + child: Text(l10n.common_save), + ), + ], + ), + ), + ); + } + void _editLocation(BuildContext context, MeshCoreConnector connector) { final l10n = context.l10n; final latController = TextEditingController();