mirror of
https://github.com/dz0ny/meshcore-sar.git
synced 2026-08-11 08:20:36 +00:00
Add nearest relay fallback toggle
This commit is contained in:
@@ -98,6 +98,9 @@ class AppProvider with ChangeNotifier {
|
||||
bool _clearPathOnMaxRetry =
|
||||
MessagingRoutePreferences.defaultClearPathOnMaxRetry;
|
||||
bool get clearPathOnMaxRetry => _clearPathOnMaxRetry;
|
||||
bool _nearestRelayFallbackEnabled =
|
||||
MessagingRoutePreferences.defaultNearestRelayFallbackEnabled;
|
||||
bool get nearestRelayFallbackEnabled => _nearestRelayFallbackEnabled;
|
||||
final PathHistoryService _pathHistoryService = PathHistoryService();
|
||||
final NearestRouterSelector _nearestRouterSelector =
|
||||
const NearestRouterSelector();
|
||||
@@ -458,6 +461,8 @@ class AppProvider with ChangeNotifier {
|
||||
await MessagingRoutePreferences.getAutoRouteRotationEnabled();
|
||||
_clearPathOnMaxRetry =
|
||||
await MessagingRoutePreferences.getClearPathOnMaxRetry();
|
||||
_nearestRelayFallbackEnabled =
|
||||
await MessagingRoutePreferences.getNearestRelayFallbackEnabled();
|
||||
notifyListeners();
|
||||
} catch (e) {
|
||||
debugPrint('Error loading messaging route settings: $e');
|
||||
@@ -484,6 +489,16 @@ class AppProvider with ChangeNotifier {
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> toggleNearestRelayFallbackEnabled(bool enabled) async {
|
||||
try {
|
||||
_nearestRelayFallbackEnabled = enabled;
|
||||
await MessagingRoutePreferences.setNearestRelayFallbackEnabled(enabled);
|
||||
notifyListeners();
|
||||
} catch (e) {
|
||||
debugPrint('Error saving nearest relay fallback setting: $e');
|
||||
}
|
||||
}
|
||||
|
||||
/// Initialize location tracking service
|
||||
Future<void> _initializeLocationTracking() async {
|
||||
try {
|
||||
@@ -713,6 +728,19 @@ class AppProvider with ChangeNotifier {
|
||||
final receptionDetailsSnapshot = _buildReceptionDetailsSnapshot(
|
||||
enrichedMessage,
|
||||
);
|
||||
final receivedPathBytes = receptionDetailsSnapshot?.pathBytes;
|
||||
if (senderContact != null &&
|
||||
enrichedMessage.isChannelMessage &&
|
||||
(enrichedMessage.channelIdx ?? 0) == 0 &&
|
||||
receivedPathBytes != null &&
|
||||
receivedPathBytes.isNotEmpty) {
|
||||
unawaited(
|
||||
_learnPathFromPublicMessage(
|
||||
contact: senderContact,
|
||||
pathBytes: receivedPathBytes,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// Check if message is a drawing broadcast
|
||||
if (DrawingMessageParser.isDrawingMessage(enrichedMessage.text)) {
|
||||
@@ -1379,6 +1407,10 @@ class AppProvider with ChangeNotifier {
|
||||
required Contact contact,
|
||||
required Message message,
|
||||
}) async {
|
||||
if (!_nearestRelayFallbackEnabled) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final latestContact =
|
||||
contactsProvider.findContactByKey(contact.publicKey) ?? contact;
|
||||
final session =
|
||||
@@ -1491,6 +1523,38 @@ class AppProvider with ChangeNotifier {
|
||||
return bytes.map((byte) => byte.toRadixString(16).padLeft(2, '0')).join();
|
||||
}
|
||||
|
||||
Future<void> _learnPathFromPublicMessage({
|
||||
required Contact contact,
|
||||
required List<int> pathBytes,
|
||||
}) async {
|
||||
final preferred = await RouteHashPreferences.getHashSize();
|
||||
final inferredHashSize = _inferReceivedPathHashSize(
|
||||
pathBytes,
|
||||
preferredHashSize: preferred,
|
||||
);
|
||||
await _pathHistoryService.recordReceivedBytePath(
|
||||
contact.publicKeyHex,
|
||||
pathBytes,
|
||||
inferredHashSize,
|
||||
);
|
||||
}
|
||||
|
||||
int _inferReceivedPathHashSize(
|
||||
List<int> pathBytes, {
|
||||
required int preferredHashSize,
|
||||
}) {
|
||||
final preferred = preferredHashSize;
|
||||
final candidates = {preferred, 3, 2, 1}.toList();
|
||||
for (final candidate in candidates) {
|
||||
if (candidate >= 1 &&
|
||||
candidate <= 3 &&
|
||||
pathBytes.length % candidate == 0) {
|
||||
return candidate;
|
||||
}
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
/// Initialize the app (load contacts, sync time, etc.)
|
||||
Future<void> initialize() async {
|
||||
if (!connectionProvider.deviceInfo.isConnected) return;
|
||||
|
||||
@@ -988,6 +988,19 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
||||
},
|
||||
),
|
||||
),
|
||||
Consumer<AppProvider>(
|
||||
builder: (context, appProvider, child) => SwitchListTile(
|
||||
secondary: const Icon(Icons.route),
|
||||
title: const Text('Nearest repeater fallback'),
|
||||
subtitle: const Text(
|
||||
'After normal retries fail, try one final resend through the nearest repeater',
|
||||
),
|
||||
value: appProvider.nearestRelayFallbackEnabled,
|
||||
onChanged: (value) async {
|
||||
await appProvider.toggleNearestRelayFallbackEnabled(value);
|
||||
},
|
||||
),
|
||||
),
|
||||
Consumer<AppProvider>(
|
||||
builder: (context, appProvider, child) => SwitchListTile(
|
||||
secondary: const Icon(Icons.route),
|
||||
|
||||
@@ -3,11 +3,14 @@ import 'package:shared_preferences/shared_preferences.dart';
|
||||
class MessagingRoutePreferences {
|
||||
static const bool defaultAutoRouteRotationEnabled = false;
|
||||
static const bool defaultClearPathOnMaxRetry = false;
|
||||
static const bool defaultNearestRelayFallbackEnabled = true;
|
||||
|
||||
static const String _autoRouteRotationKey =
|
||||
'messaging_auto_route_rotation_enabled';
|
||||
static const String _clearPathOnMaxRetryKey =
|
||||
'messaging_clear_path_on_max_retry';
|
||||
static const String _nearestRelayFallbackKey =
|
||||
'messaging_nearest_relay_fallback_enabled';
|
||||
|
||||
static Future<bool> getAutoRouteRotationEnabled() async {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
@@ -29,4 +32,15 @@ class MessagingRoutePreferences {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
await prefs.setBool(_clearPathOnMaxRetryKey, enabled);
|
||||
}
|
||||
|
||||
static Future<bool> getNearestRelayFallbackEnabled() async {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
return prefs.getBool(_nearestRelayFallbackKey) ??
|
||||
defaultNearestRelayFallbackEnabled;
|
||||
}
|
||||
|
||||
static Future<void> setNearestRelayFallbackEnabled(bool enabled) async {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
await prefs.setBool(_nearestRelayFallbackKey, enabled);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -67,6 +67,45 @@ class PathHistoryService {
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> recordReceivedBytePath(
|
||||
String contactPublicKeyHex,
|
||||
List<int> pathBytes,
|
||||
int hashSize,
|
||||
) async {
|
||||
await initialize();
|
||||
if (pathBytes.isEmpty) {
|
||||
return;
|
||||
}
|
||||
if (hashSize < 1 || hashSize > 3) {
|
||||
return;
|
||||
}
|
||||
if (pathBytes.length % hashSize != 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
final history = _historyFor(contactPublicKeyHex);
|
||||
final signature = pathBytes
|
||||
.map((byte) => byte.toRadixString(16).padLeft(2, '0'))
|
||||
.join();
|
||||
final existing = _findDirectPath(history.directPaths, signature);
|
||||
final updated = PathRecord(
|
||||
pathBytes: List<int>.from(pathBytes),
|
||||
hopCount: pathBytes.length ~/ hashSize,
|
||||
hashSize: hashSize,
|
||||
successCount: existing?.successCount ?? 0,
|
||||
failureCount: existing?.failureCount ?? 0,
|
||||
lastRoundTripTimeMs: existing?.lastRoundTripTimeMs ?? 0,
|
||||
lastUsedAt: DateTime.now(),
|
||||
);
|
||||
|
||||
await _saveHistory(
|
||||
contactPublicKeyHex,
|
||||
history.copyWith(
|
||||
directPaths: _upsertDirectPath(history.directPaths, updated),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<PathSelection> getSelectionForContact(
|
||||
Contact contact, {
|
||||
required bool autoRouteRotationEnabled,
|
||||
|
||||
@@ -196,6 +196,8 @@ class _ContactRouteDialogState extends State<ContactRouteDialog> {
|
||||
});
|
||||
},
|
||||
autoRouteRotationEnabled: appProvider.autoRouteRotationEnabled,
|
||||
nearestRelayFallbackEnabled:
|
||||
appProvider.nearestRelayFallbackEnabled,
|
||||
clearPathOnMaxRetry: appProvider.clearPathOnMaxRetry,
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
@@ -264,12 +266,14 @@ class _AutomationRoutingInfo extends StatelessWidget {
|
||||
final bool isExpanded;
|
||||
final VoidCallback onToggle;
|
||||
final bool autoRouteRotationEnabled;
|
||||
final bool nearestRelayFallbackEnabled;
|
||||
final bool clearPathOnMaxRetry;
|
||||
|
||||
const _AutomationRoutingInfo({
|
||||
required this.isExpanded,
|
||||
required this.onToggle,
|
||||
required this.autoRouteRotationEnabled,
|
||||
required this.nearestRelayFallbackEnabled,
|
||||
required this.clearPathOnMaxRetry,
|
||||
});
|
||||
|
||||
@@ -336,6 +340,12 @@ class _AutomationRoutingInfo extends StatelessWidget {
|
||||
: 'Auto route rotation off',
|
||||
icon: Icons.swap_horiz,
|
||||
),
|
||||
_InfoChip(
|
||||
label: nearestRelayFallbackEnabled
|
||||
? 'Nearest repeater fallback on'
|
||||
: 'Nearest repeater fallback off',
|
||||
icon: Icons.router,
|
||||
),
|
||||
_InfoChip(
|
||||
label: clearPathOnMaxRetry
|
||||
? 'Clear path on max retry on'
|
||||
|
||||
@@ -16,16 +16,25 @@ void main() {
|
||||
isFalse,
|
||||
);
|
||||
expect(await MessagingRoutePreferences.getClearPathOnMaxRetry(), isFalse);
|
||||
expect(
|
||||
await MessagingRoutePreferences.getNearestRelayFallbackEnabled(),
|
||||
isTrue,
|
||||
);
|
||||
});
|
||||
|
||||
test('route preferences persist changes', () async {
|
||||
await MessagingRoutePreferences.setAutoRouteRotationEnabled(true);
|
||||
await MessagingRoutePreferences.setClearPathOnMaxRetry(true);
|
||||
await MessagingRoutePreferences.setNearestRelayFallbackEnabled(false);
|
||||
|
||||
expect(
|
||||
await MessagingRoutePreferences.getAutoRouteRotationEnabled(),
|
||||
isTrue,
|
||||
);
|
||||
expect(await MessagingRoutePreferences.getClearPathOnMaxRetry(), isTrue);
|
||||
expect(
|
||||
await MessagingRoutePreferences.getNearestRelayFallbackEnabled(),
|
||||
isFalse,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -127,4 +127,16 @@ void main() {
|
||||
|
||||
expect(selection.mode, PathSelectionMode.flood);
|
||||
});
|
||||
|
||||
test('received public byte path is added to history', () async {
|
||||
final service = PathHistoryService();
|
||||
await service.initialize();
|
||||
await service.recordReceivedBytePath('abc123', [0x01, 0x02, 0x03], 3);
|
||||
|
||||
final history = service.historyFor('abc123');
|
||||
expect(history.directPaths, hasLength(1));
|
||||
expect(history.directPaths.single.pathBytes, [0x01, 0x02, 0x03]);
|
||||
expect(history.directPaths.single.hashSize, 3);
|
||||
expect(history.directPaths.single.hopCount, 1);
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user