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
+6
View File
@@ -18,6 +18,7 @@ class AppSettings {
final bool notifyOnNewAdvert;
final bool autoRouteRotationEnabled;
final String themeMode;
final bool appDebugLogEnabled;
final Map<String, String> batteryChemistryByDeviceId;
AppSettings({
@@ -38,6 +39,7 @@ class AppSettings {
this.notifyOnNewAdvert = true,
this.autoRouteRotationEnabled = false,
this.themeMode = 'system',
this.appDebugLogEnabled = false,
Map<String, String>? batteryChemistryByDeviceId,
}) : batteryChemistryByDeviceId = batteryChemistryByDeviceId ?? {};
@@ -60,6 +62,7 @@ class AppSettings {
'notify_on_new_advert': notifyOnNewAdvert,
'auto_route_rotation_enabled': autoRouteRotationEnabled,
'theme_mode': themeMode,
'app_debug_log_enabled': appDebugLogEnabled,
'battery_chemistry_by_device_id': batteryChemistryByDeviceId,
};
}
@@ -86,6 +89,7 @@ class AppSettings {
notifyOnNewAdvert: json['notify_on_new_advert'] as bool? ?? true,
autoRouteRotationEnabled: json['auto_route_rotation_enabled'] as bool? ?? false,
themeMode: json['theme_mode'] as String? ?? 'system',
appDebugLogEnabled: json['app_debug_log_enabled'] as bool? ?? false,
batteryChemistryByDeviceId: (json['battery_chemistry_by_device_id'] as Map?)?.map(
(key, value) => MapEntry(key.toString(), value.toString()),
) ??
@@ -111,6 +115,7 @@ class AppSettings {
bool? notifyOnNewAdvert,
bool? autoRouteRotationEnabled,
String? themeMode,
bool? appDebugLogEnabled,
Map<String, String>? batteryChemistryByDeviceId,
}) {
return AppSettings(
@@ -133,6 +138,7 @@ class AppSettings {
notifyOnNewAdvert: notifyOnNewAdvert ?? this.notifyOnNewAdvert,
autoRouteRotationEnabled: autoRouteRotationEnabled ?? this.autoRouteRotationEnabled,
themeMode: themeMode ?? this.themeMode,
appDebugLogEnabled: appDebugLogEnabled ?? this.appDebugLogEnabled,
batteryChemistryByDeviceId: batteryChemistryByDeviceId ?? this.batteryChemistryByDeviceId,
);
}
+18 -4
View File
@@ -46,6 +46,11 @@ class Contact {
}
String get pathLabel {
if (pathOverride != null) {
if (pathOverride! < 0) return 'Flood (forced)';
if (pathOverride == 0) return 'Direct (forced)';
return '$pathOverride hops (forced)';
}
if (pathLength < 0) return 'Flood';
if (pathLength == 0) return 'Direct';
return '$pathLength hops';
@@ -83,12 +88,13 @@ class Contact {
}
String get pathIdList {
if (path.isEmpty) return '';
final pathBytes = _pathBytesForDisplay;
if (pathBytes.isEmpty) return '';
final parts = <String>[];
final groupSize = pathHashSize;
for (int i = 0; i < path.length; i += groupSize) {
final end = (i + groupSize) <= path.length ? (i + groupSize) : path.length;
final chunk = path.sublist(i, end);
for (int i = 0; i < pathBytes.length; i += groupSize) {
final end = (i + groupSize) <= pathBytes.length ? (i + groupSize) : pathBytes.length;
final chunk = pathBytes.sublist(i, end);
parts.add(
chunk.map((b) => b.toRadixString(16).padLeft(2, '0').toUpperCase()).join(),
);
@@ -96,6 +102,14 @@ class Contact {
return parts.join(',');
}
Uint8List get _pathBytesForDisplay {
if (pathOverride != null) {
if (pathOverride! < 0) return Uint8List(0);
return pathOverrideBytes ?? Uint8List(0);
}
return path;
}
static Contact? fromFrame(Uint8List data) {
if (data.length < contactFrameSize) return null;
if (data[0] != respCodeContact) return null;