Msg Retry fixes, channel message fixes. Notification fixes. Make more desktop friendly. Enhance retry algo. Fix predicted location clustering add retries to reactions and fix the reactions in private DMS centralize and cleanup code in var areas

This commit is contained in:
zjs81
2026-03-20 01:54:31 -07:00
parent 53caec3e14
commit 4962a48e64
61 changed files with 4509 additions and 900 deletions
+35
View File
@@ -32,6 +32,11 @@ class AppSettings {
final bool notifyOnNewChannelMessage;
final bool notifyOnNewAdvert;
final bool autoRouteRotationEnabled;
final double maxRouteWeight;
final double initialRouteWeight;
final double routeWeightSuccessIncrement;
final double routeWeightFailureDecrement;
final int maxMessageRetries;
final String themeMode;
final String? languageOverride; // null = system default
final bool appDebugLogEnabled;
@@ -62,6 +67,11 @@ class AppSettings {
this.notifyOnNewChannelMessage = true,
this.notifyOnNewAdvert = true,
this.autoRouteRotationEnabled = false,
this.maxRouteWeight = 5.0,
this.initialRouteWeight = 3.0,
this.routeWeightSuccessIncrement = 0.5,
this.routeWeightFailureDecrement = 0.2,
this.maxMessageRetries = 5,
this.themeMode = 'system',
this.languageOverride,
this.appDebugLogEnabled = false,
@@ -96,6 +106,11 @@ class AppSettings {
'notify_on_new_channel_message': notifyOnNewChannelMessage,
'notify_on_new_advert': notifyOnNewAdvert,
'auto_route_rotation_enabled': autoRouteRotationEnabled,
'max_route_weight': maxRouteWeight,
'initial_route_weight': initialRouteWeight,
'route_weight_success_increment': routeWeightSuccessIncrement,
'route_weight_failure_decrement': routeWeightFailureDecrement,
'max_message_retries': maxMessageRetries,
'theme_mode': themeMode,
'language_override': languageOverride,
'app_debug_log_enabled': appDebugLogEnabled,
@@ -142,6 +157,14 @@ class AppSettings {
notifyOnNewAdvert: json['notify_on_new_advert'] as bool? ?? true,
autoRouteRotationEnabled:
json['auto_route_rotation_enabled'] as bool? ?? false,
maxRouteWeight: (json['max_route_weight'] as num?)?.toDouble() ?? 5.0,
initialRouteWeight:
(json['initial_route_weight'] as num?)?.toDouble() ?? 3.0,
routeWeightSuccessIncrement:
(json['route_weight_success_increment'] as num?)?.toDouble() ?? 0.5,
routeWeightFailureDecrement:
(json['route_weight_failure_decrement'] as num?)?.toDouble() ?? 0.2,
maxMessageRetries: json['max_message_retries'] as int? ?? 5,
themeMode: json['theme_mode'] as String? ?? 'system',
languageOverride: json['language_override'] as String?,
appDebugLogEnabled: json['app_debug_log_enabled'] as bool? ?? false,
@@ -187,6 +210,11 @@ class AppSettings {
bool? notifyOnNewChannelMessage,
bool? notifyOnNewAdvert,
bool? autoRouteRotationEnabled,
double? maxRouteWeight,
double? initialRouteWeight,
double? routeWeightSuccessIncrement,
double? routeWeightFailureDecrement,
int? maxMessageRetries,
String? themeMode,
Object? languageOverride = _unset,
bool? appDebugLogEnabled,
@@ -222,6 +250,13 @@ class AppSettings {
notifyOnNewAdvert: notifyOnNewAdvert ?? this.notifyOnNewAdvert,
autoRouteRotationEnabled:
autoRouteRotationEnabled ?? this.autoRouteRotationEnabled,
maxRouteWeight: maxRouteWeight ?? this.maxRouteWeight,
initialRouteWeight: initialRouteWeight ?? this.initialRouteWeight,
routeWeightSuccessIncrement:
routeWeightSuccessIncrement ?? this.routeWeightSuccessIncrement,
routeWeightFailureDecrement:
routeWeightFailureDecrement ?? this.routeWeightFailureDecrement,
maxMessageRetries: maxMessageRetries ?? this.maxMessageRetries,
themeMode: themeMode ?? this.themeMode,
languageOverride: languageOverride == _unset
? this.languageOverride
+4
View File
@@ -36,6 +36,7 @@ class ChannelMessage {
final List<Uint8List> pathVariants;
final int? channelIndex;
final String messageId;
final String? packetHash;
final String? replyToMessageId;
final String? replyToSenderName;
final String? replyToText;
@@ -55,6 +56,7 @@ class ChannelMessage {
List<Uint8List>? pathVariants,
this.channelIndex,
String? messageId,
this.packetHash,
this.replyToMessageId,
this.replyToSenderName,
this.replyToText,
@@ -79,6 +81,7 @@ class ChannelMessage {
int? pathLength,
Uint8List? pathBytes,
List<Uint8List>? pathVariants,
String? packetHash,
String? replyToMessageId,
String? replyToSenderName,
String? replyToText,
@@ -98,6 +101,7 @@ class ChannelMessage {
pathVariants: pathVariants ?? this.pathVariants,
channelIndex: channelIndex,
messageId: messageId,
packetHash: packetHash ?? this.packetHash,
replyToMessageId: replyToMessageId ?? this.replyToMessageId,
replyToSenderName: replyToSenderName ?? this.replyToSenderName,
replyToText: replyToText ?? this.replyToText,
+13 -1
View File
@@ -157,6 +157,12 @@ class Contact {
return null;
}
final pubKey = reader.readBytes(pubKeySize);
// Guard: reject contacts with zeroed or mostly-zeroed public keys
// (indicates corrupt flash storage on the firmware side)
final zeroCount = pubKey.where((b) => b == 0).length;
if (zeroCount > pubKeySize ~/ 2) return null;
final type = reader.readByte();
final flags = reader.readByte();
final pathLen = reader.readByte();
@@ -166,6 +172,12 @@ class Contact {
final pathBytes = reader.readBytes(maxPathSize).sublist(0, safePathLen);
final name = reader.readCStringGreedy(maxNameSize);
// Guard: reject contacts with non-printable names (corrupt flash data)
if (name.isNotEmpty &&
name.codeUnits.every((c) => c < 0x20 || c == 0xFFFD)) {
return null;
}
final lastMod = reader.readUInt32LE();
double? lat, lon;
@@ -182,7 +194,7 @@ class Contact {
name: name.isEmpty ? 'Unknown' : name,
type: type,
flags: flags,
pathLength: pathLen > 0 ? (pathLen > maxPathSize ? -1 : pathLen) : -1,
pathLength: (pathLen == 0xFF || pathLen > maxPathSize) ? -1 : pathLen,
path: pathBytes,
latitude: lat,
longitude: lon,
+6 -1
View File
@@ -23,6 +23,7 @@ class Message {
final int? pathLength;
final Uint8List pathBytes;
final Map<String, int> reactions;
final Map<String, MessageStatus> reactionStatuses;
final Uint8List fourByteRoomContactKey;
Message({
@@ -43,9 +44,11 @@ class Message {
Uint8List? pathBytes,
Uint8List? fourByteRoomContactKey,
Map<String, int>? reactions,
Map<String, MessageStatus>? reactionStatuses,
}) : pathBytes = pathBytes ?? Uint8List(0),
fourByteRoomContactKey = fourByteRoomContactKey ?? Uint8List(0),
reactions = reactions ?? {};
reactions = reactions ?? {},
reactionStatuses = reactionStatuses ?? {};
String get senderKeyHex => pubKeyToHex(senderKey);
@@ -61,6 +64,7 @@ class Message {
Uint8List? pathBytes,
bool? isCli,
Map<String, int>? reactions,
Map<String, MessageStatus>? reactionStatuses,
Uint8List? fourByteRoomContactKey,
}) {
return Message(
@@ -80,6 +84,7 @@ class Message {
pathLength: pathLength ?? this.pathLength,
pathBytes: pathBytes ?? this.pathBytes,
reactions: reactions ?? this.reactions,
reactionStatuses: reactionStatuses ?? this.reactionStatuses,
fourByteRoomContactKey:
fourByteRoomContactKey ?? this.fourByteRoomContactKey,
);
+9 -3
View File
@@ -1,11 +1,12 @@
class PathRecord {
final int hopCount;
final int tripTimeMs;
final DateTime timestamp;
final DateTime? timestamp;
final bool wasFloodDiscovery;
final List<int> pathBytes;
final int successCount;
final int failureCount;
final double routeWeight;
PathRecord({
required this.hopCount,
@@ -15,6 +16,7 @@ class PathRecord {
required this.pathBytes,
required this.successCount,
required this.failureCount,
this.routeWeight = 1.0,
});
String get displayText =>
@@ -24,11 +26,12 @@ class PathRecord {
return {
'hop_count': hopCount,
'trip_time_ms': tripTimeMs,
'timestamp': timestamp.toIso8601String(),
'timestamp': timestamp?.toIso8601String(),
'was_flood': wasFloodDiscovery,
'path_bytes': pathBytes,
'success_count': successCount,
'failure_count': failureCount,
'route_weight': routeWeight,
};
}
@@ -36,12 +39,15 @@ class PathRecord {
return PathRecord(
hopCount: json['hop_count'] as int,
tripTimeMs: json['trip_time_ms'] as int,
timestamp: DateTime.parse(json['timestamp'] as String),
timestamp: json['timestamp'] != null
? DateTime.parse(json['timestamp'] as String)
: null,
wasFloodDiscovery: json['was_flood'] as bool,
pathBytes:
(json['path_bytes'] as List?)?.map((b) => b as int).toList() ?? [],
successCount: json['success_count'] as int? ?? 0,
failureCount: json['failure_count'] as int? ?? 0,
routeWeight: (json['route_weight'] as num?)?.toDouble() ?? 1.0,
);
}
}
+41
View File
@@ -1,3 +1,9 @@
import 'dart:typed_data';
import 'contact.dart';
const int recentAttemptDiversityWindow = 2;
class PathSelection {
final List<int> pathBytes;
final int hopCount;
@@ -9,3 +15,38 @@ class PathSelection {
required this.useFlood,
});
}
PathSelection resolvePathSelection(
Contact contact, {
PathSelection? selection,
bool forceFlood = false,
}) {
if (contact.pathOverride != null) {
if (contact.pathOverride! < 0) {
return const PathSelection(pathBytes: [], hopCount: -1, useFlood: true);
}
return PathSelection(
pathBytes: contact.pathOverrideBytes ?? Uint8List(0),
hopCount: contact.pathOverride!,
useFlood: false,
);
}
if (forceFlood || contact.pathLength < 0 || selection?.useFlood == true) {
return const PathSelection(pathBytes: [], hopCount: -1, useFlood: true);
}
if (selection != null && selection.pathBytes.isNotEmpty) {
return PathSelection(
pathBytes: selection.pathBytes,
hopCount: selection.hopCount,
useFlood: false,
);
}
return PathSelection(
pathBytes: contact.path,
hopCount: contact.pathLength,
useFlood: false,
);
}