Add advanced path management, debug logging, and fix channel sync

New features:
- In-app debug log viewer with copy/clear functionality
- Advanced path management UI with history and custom path builder
- Battery indicator widget with voltage/percentage toggle
- Contact/channel filtering and sorting improvements
- Repeater command ACK tracking with path history integration

Fixes:
- Switch channel sync from parallel to sequential to prevent timeouts
- Preserve path overrides when contacts refresh from device
- Fix ACK hash computation for SMAZ-encoded messages
- Proper cleanup of pending operations on disconnect
This commit is contained in:
zach
2026-01-02 14:22:39 -07:00
parent 361dfb7808
commit ad911a1d80
32 changed files with 2914 additions and 849 deletions
+102 -103
View File
@@ -32,6 +32,8 @@ class AppSettingsScreen extends StatelessWidget {
_buildBatteryCard(context, settingsService, connector),
const SizedBox(height: 16),
_buildMapSettingsCard(context, settingsService),
const SizedBox(height: 16),
_buildDebugCard(context, settingsService),
],
);
},
@@ -383,43 +385,31 @@ class AppSettingsScreen extends StatelessWidget {
context: context,
builder: (context) => AlertDialog(
title: const Text('Theme'),
content: Column(
mainAxisSize: MainAxisSize.min,
children: [
RadioListTile<String>(
title: const Text('System default'),
value: 'system',
groupValue: settingsService.settings.themeMode,
onChanged: (value) {
if (value != null) {
settingsService.setThemeMode(value);
Navigator.pop(context);
}
},
),
RadioListTile<String>(
title: const Text('Light'),
value: 'light',
groupValue: settingsService.settings.themeMode,
onChanged: (value) {
if (value != null) {
settingsService.setThemeMode(value);
Navigator.pop(context);
}
},
),
RadioListTile<String>(
title: const Text('Dark'),
value: 'dark',
groupValue: settingsService.settings.themeMode,
onChanged: (value) {
if (value != null) {
settingsService.setThemeMode(value);
Navigator.pop(context);
}
},
),
],
content: RadioGroup<String>(
groupValue: settingsService.settings.themeMode,
onChanged: (value) {
if (value != null) {
settingsService.setThemeMode(value);
Navigator.pop(context);
}
},
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
RadioListTile<String>(
title: const Text('System default'),
value: 'system',
),
RadioListTile<String>(
title: const Text('Light'),
value: 'light',
),
RadioListTile<String>(
title: const Text('Dark'),
value: 'dark',
),
],
),
),
actions: [
TextButton(
@@ -447,77 +437,51 @@ class AppSettingsScreen extends StatelessWidget {
context: context,
builder: (context) => AlertDialog(
title: const Text('Map Time Filter'),
content: Column(
mainAxisSize: MainAxisSize.min,
children: [
const Text('Show nodes discovered within:'),
const SizedBox(height: 16),
ListTile(
title: const Text('All time'),
leading: Radio<double>(
value: 0,
groupValue: settingsService.settings.mapTimeFilterHours,
onChanged: (value) {
if (value != null) {
settingsService.setMapTimeFilterHours(value);
Navigator.pop(context);
}
},
content: RadioGroup<double>(
groupValue: settingsService.settings.mapTimeFilterHours,
onChanged: (value) {
if (value != null) {
settingsService.setMapTimeFilterHours(value);
Navigator.pop(context);
}
},
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
const Text('Show nodes discovered within:'),
const SizedBox(height: 16),
ListTile(
title: const Text('All time'),
leading: Radio<double>(
value: 0,
),
),
),
ListTile(
title: const Text('Last hour'),
leading: Radio<double>(
value: 1,
groupValue: settingsService.settings.mapTimeFilterHours,
onChanged: (value) {
if (value != null) {
settingsService.setMapTimeFilterHours(value);
Navigator.pop(context);
}
},
ListTile(
title: const Text('Last hour'),
leading: Radio<double>(
value: 1,
),
),
),
ListTile(
title: const Text('Last 6 hours'),
leading: Radio<double>(
value: 6,
groupValue: settingsService.settings.mapTimeFilterHours,
onChanged: (value) {
if (value != null) {
settingsService.setMapTimeFilterHours(value);
Navigator.pop(context);
}
},
ListTile(
title: const Text('Last 6 hours'),
leading: Radio<double>(
value: 6,
),
),
),
ListTile(
title: const Text('Last 24 hours'),
leading: Radio<double>(
value: 24,
groupValue: settingsService.settings.mapTimeFilterHours,
onChanged: (value) {
if (value != null) {
settingsService.setMapTimeFilterHours(value);
Navigator.pop(context);
}
},
ListTile(
title: const Text('Last 24 hours'),
leading: Radio<double>(
value: 24,
),
),
),
ListTile(
title: const Text('Last week'),
leading: Radio<double>(
value: 168,
groupValue: settingsService.settings.mapTimeFilterHours,
onChanged: (value) {
if (value != null) {
settingsService.setMapTimeFilterHours(value);
Navigator.pop(context);
}
},
ListTile(
title: const Text('Last week'),
leading: Radio<double>(
value: 168,
),
),
),
],
],
),
),
actions: [
TextButton(
@@ -528,4 +492,39 @@ class AppSettingsScreen extends StatelessWidget {
),
);
}
Widget _buildDebugCard(BuildContext context, AppSettingsService settingsService) {
return Card(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Padding(
padding: EdgeInsets.fromLTRB(16, 16, 16, 8),
child: Text(
'Debug',
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
),
),
SwitchListTile(
secondary: const Icon(Icons.bug_report_outlined),
title: const Text('App Debug Logging'),
subtitle: const Text('Log app debug messages for troubleshooting'),
value: settingsService.settings.appDebugLogEnabled,
onChanged: (value) async {
await settingsService.setAppDebugLogEnabled(value);
if (!context.mounted) return;
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(value
? 'App debug logging enabled'
: 'App debug logging disabled'),
duration: const Duration(seconds: 2),
),
);
},
),
],
),
);
}
}