mirror of
https://github.com/zjs81/meshcore-open.git
synced 2026-06-24 03:14:30 +10:00
format dart files
formats all dart files using `dart format .` from the root project dir this makes the code style repeatable by new contributors and makes PR review easier
This commit is contained in:
@@ -1,10 +1,6 @@
|
||||
import 'package:flutter/foundation.dart';
|
||||
|
||||
enum AppDebugLogLevel {
|
||||
info,
|
||||
warning,
|
||||
error,
|
||||
}
|
||||
enum AppDebugLogLevel { info, warning, error }
|
||||
|
||||
class AppDebugLogEntry {
|
||||
final DateTime timestamp;
|
||||
@@ -51,7 +47,11 @@ class AppDebugLogService extends ChangeNotifier {
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
void log(String message, {String tag = 'App', AppDebugLogLevel level = AppDebugLogLevel.info}) {
|
||||
void log(
|
||||
String message, {
|
||||
String tag = 'App',
|
||||
AppDebugLogLevel level = AppDebugLogLevel.info,
|
||||
}) {
|
||||
if (!_enabled) return;
|
||||
|
||||
_entries.add(
|
||||
|
||||
@@ -82,10 +82,7 @@ class AppSettingsService extends ChangeNotifier {
|
||||
final safeMin = minZoom <= maxZoom ? minZoom : maxZoom;
|
||||
final safeMax = minZoom <= maxZoom ? maxZoom : minZoom;
|
||||
await updateSettings(
|
||||
_settings.copyWith(
|
||||
mapCacheMinZoom: safeMin,
|
||||
mapCacheMaxZoom: safeMax,
|
||||
),
|
||||
_settings.copyWith(mapCacheMinZoom: safeMin, mapCacheMaxZoom: safeMax),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -123,9 +120,16 @@ class AppSettingsService extends ChangeNotifier {
|
||||
appLogger.setEnabled(value);
|
||||
}
|
||||
|
||||
Future<void> setBatteryChemistryForDevice(String deviceId, String chemistry) async {
|
||||
final updated = Map<String, String>.from(_settings.batteryChemistryByDeviceId);
|
||||
Future<void> setBatteryChemistryForDevice(
|
||||
String deviceId,
|
||||
String chemistry,
|
||||
) async {
|
||||
final updated = Map<String, String>.from(
|
||||
_settings.batteryChemistryByDeviceId,
|
||||
);
|
||||
updated[deviceId] = chemistry;
|
||||
await updateSettings(_settings.copyWith(batteryChemistryByDeviceId: updated));
|
||||
await updateSettings(
|
||||
_settings.copyWith(batteryChemistryByDeviceId: updated),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -197,7 +197,7 @@ class BleDebugLogService extends ChangeNotifier {
|
||||
return 'RESP_CODE_CHANNEL_INFO';
|
||||
case respCodeRadioSettings:
|
||||
return 'RESP_CODE_RADIO_SETTINGS';
|
||||
case pushCodeTraceData:
|
||||
case pushCodeTraceData:
|
||||
return 'PUSH_CODE_TRACE_DATA';
|
||||
default:
|
||||
return null;
|
||||
|
||||
@@ -42,20 +42,21 @@ class MapTileCacheService {
|
||||
late final TileProvider tileProvider;
|
||||
|
||||
MapTileCacheService({BaseCacheManager? cacheManager})
|
||||
: cacheManager = cacheManager ??
|
||||
CacheManager(
|
||||
Config(
|
||||
cacheKey,
|
||||
stalePeriod: const Duration(days: 365),
|
||||
maxNrOfCacheObjects: 200000,
|
||||
),
|
||||
) {
|
||||
: cacheManager =
|
||||
cacheManager ??
|
||||
CacheManager(
|
||||
Config(
|
||||
cacheKey,
|
||||
stalePeriod: const Duration(days: 365),
|
||||
maxNrOfCacheObjects: 200000,
|
||||
),
|
||||
) {
|
||||
tileProvider = CachedNetworkTileProvider(cacheManager: this.cacheManager);
|
||||
}
|
||||
|
||||
Map<String, String> get defaultHeaders => {
|
||||
'User-Agent': 'flutter_map ($userAgentPackageName)',
|
||||
};
|
||||
'User-Agent': 'flutter_map ($userAgentPackageName)',
|
||||
};
|
||||
|
||||
Future<void> clearCache() async {
|
||||
await cacheManager.emptyCache();
|
||||
@@ -96,17 +97,21 @@ class MapTileCacheService {
|
||||
final future = cacheManager
|
||||
.downloadFile(url, key: url, authHeaders: authHeaders)
|
||||
.then((_) {
|
||||
completed += 1;
|
||||
}).catchError((_) {
|
||||
completed += 1;
|
||||
failed += 1;
|
||||
}).whenComplete(() {
|
||||
onProgress?.call(MapTileCacheProgress(
|
||||
completed: completed,
|
||||
total: total,
|
||||
failed: failed,
|
||||
));
|
||||
});
|
||||
completed += 1;
|
||||
})
|
||||
.catchError((_) {
|
||||
completed += 1;
|
||||
failed += 1;
|
||||
})
|
||||
.whenComplete(() {
|
||||
onProgress?.call(
|
||||
MapTileCacheProgress(
|
||||
completed: completed,
|
||||
total: total,
|
||||
failed: failed,
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
pending.add(future);
|
||||
if (pending.length >= safeConcurrency) {
|
||||
@@ -189,11 +194,9 @@ class MapTileCacheService {
|
||||
int _latToTileY(double lat, int zoom, int maxIndex) {
|
||||
final n = 1 << zoom;
|
||||
final rad = lat * math.pi / 180.0;
|
||||
final value = ((1 -
|
||||
math.log(math.tan(rad) + 1 / math.cos(rad)) / math.pi) /
|
||||
2 *
|
||||
n)
|
||||
.floor();
|
||||
final value =
|
||||
((1 - math.log(math.tan(rad) + 1 / math.cos(rad)) / math.pi) / 2 * n)
|
||||
.floor();
|
||||
return value.clamp(0, maxIndex);
|
||||
}
|
||||
|
||||
|
||||
@@ -25,10 +25,7 @@ class _AckHashMapping {
|
||||
final String messageId;
|
||||
final DateTime timestamp;
|
||||
|
||||
_AckHashMapping({
|
||||
required this.messageId,
|
||||
required this.timestamp,
|
||||
});
|
||||
_AckHashMapping({required this.messageId, required this.timestamp});
|
||||
}
|
||||
|
||||
class MessageRetryService extends ChangeNotifier {
|
||||
@@ -39,11 +36,16 @@ class MessageRetryService extends ChangeNotifier {
|
||||
final Map<String, Message> _pendingMessages = {};
|
||||
final Map<String, Contact> _pendingContacts = {};
|
||||
final Map<String, PathSelection> _pendingPathSelections = {};
|
||||
final Map<String, _AckHashMapping> _ackHashToMessageId = {}; // ackHashHex → messageId + timestamp for O(1) lookup
|
||||
final Map<String, List<Uint8List>> _expectedAckHashes = {}; // Track all expected ACKs for retries (for history)
|
||||
final List<_AckHistoryEntry> _ackHistory = []; // Rolling buffer of recent ACK hashes
|
||||
final Map<String, List<String>> _pendingMessageQueuePerContact = {}; // contactPubKeyHex → FIFO queue of messageIds (DEPRECATED - will be removed)
|
||||
final Map<String, String> _expectedHashToMessageId = {}; // expectedAckHashHex → messageId (for matching RESP_CODE_SENT by hash)
|
||||
final Map<String, _AckHashMapping> _ackHashToMessageId =
|
||||
{}; // ackHashHex → messageId + timestamp for O(1) lookup
|
||||
final Map<String, List<Uint8List>> _expectedAckHashes =
|
||||
{}; // Track all expected ACKs for retries (for history)
|
||||
final List<_AckHistoryEntry> _ackHistory =
|
||||
[]; // Rolling buffer of recent ACK hashes
|
||||
final Map<String, List<String>> _pendingMessageQueuePerContact =
|
||||
{}; // contactPubKeyHex → FIFO queue of messageIds (DEPRECATED - will be removed)
|
||||
final Map<String, String> _expectedHashToMessageId =
|
||||
{}; // expectedAckHashHex → messageId (for matching RESP_CODE_SENT by hash)
|
||||
|
||||
Function(Contact, String, int, int)? _sendMessageCallback;
|
||||
Function(String, Message)? _addMessageCallback;
|
||||
@@ -130,7 +132,8 @@ class MessageRetryService extends ChangeNotifier {
|
||||
final messagePathBytes =
|
||||
pathBytes ?? _resolveMessagePathBytes(contact, useFlood, pathSelection);
|
||||
final messagePathLength =
|
||||
pathLength ?? _resolveMessagePathLength(contact, useFlood, pathSelection);
|
||||
pathLength ??
|
||||
_resolveMessagePathLength(contact, useFlood, pathSelection);
|
||||
final message = Message(
|
||||
senderKey: contact.publicKey,
|
||||
text: text,
|
||||
@@ -167,15 +170,25 @@ class MessageRetryService extends ChangeNotifier {
|
||||
if (_setContactPathCallback != null && _clearContactPathCallback != null) {
|
||||
if (message.pathLength != null && message.pathLength! < 0) {
|
||||
// Flood mode - clear the path
|
||||
debugPrint('Setting flood mode for retry attempt ${message.retryCount}');
|
||||
debugPrint(
|
||||
'Setting flood mode for retry attempt ${message.retryCount}',
|
||||
);
|
||||
_clearContactPathCallback!(contact);
|
||||
} else if (message.pathLength != null && message.pathLength! >= 0) {
|
||||
// Specific path (including direct neighbor with pathLength=0)
|
||||
final pathStr = message.pathBytes.isEmpty
|
||||
? 'direct'
|
||||
: message.pathBytes.map((b) => b.toRadixString(16).padLeft(2, '0')).join(',');
|
||||
debugPrint('Setting path [$pathStr] (${message.pathLength} hops) for retry attempt ${message.retryCount}');
|
||||
await _setContactPathCallback!(contact, message.pathBytes, message.pathLength!);
|
||||
: message.pathBytes
|
||||
.map((b) => b.toRadixString(16).padLeft(2, '0'))
|
||||
.join(',');
|
||||
debugPrint(
|
||||
'Setting path [$pathStr] (${message.pathLength} hops) for retry attempt ${message.retryCount}',
|
||||
);
|
||||
await _setContactPathCallback!(
|
||||
contact,
|
||||
message.pathBytes,
|
||||
message.pathLength!,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -186,22 +199,30 @@ class MessageRetryService extends ChangeNotifier {
|
||||
// IMPORTANT: Use the transformed text (with SMAZ encoding if enabled) to match device's hash
|
||||
final selfPubKey = _getSelfPublicKeyCallback?.call();
|
||||
if (selfPubKey != null) {
|
||||
final outboundText = _prepareContactOutboundTextCallback?.call(contact, message.text) ?? message.text;
|
||||
final outboundText =
|
||||
_prepareContactOutboundTextCallback?.call(contact, message.text) ??
|
||||
message.text;
|
||||
final expectedHash = MessageRetryService.computeExpectedAckHash(
|
||||
timestampSeconds,
|
||||
attempt,
|
||||
outboundText,
|
||||
selfPubKey,
|
||||
);
|
||||
final expectedHashHex = expectedHash.map((b) => b.toRadixString(16).padLeft(2, '0')).join();
|
||||
final expectedHashHex = expectedHash
|
||||
.map((b) => b.toRadixString(16).padLeft(2, '0'))
|
||||
.join();
|
||||
_expectedHashToMessageId[expectedHashHex] = messageId;
|
||||
|
||||
final shortText = message.text.length > 20 ? '${message.text.substring(0, 20)}...' : message.text;
|
||||
final shortText = message.text.length > 20
|
||||
? '${message.text.substring(0, 20)}...'
|
||||
: message.text;
|
||||
_debugLogService?.info(
|
||||
'Sent "$shortText" to ${contact.name} → expect ACK hash $expectedHashHex (attempt $attempt)',
|
||||
tag: 'AckHash',
|
||||
);
|
||||
debugPrint('Computed expected ACK hash $expectedHashHex for message $messageId');
|
||||
debugPrint(
|
||||
'Computed expected ACK hash $expectedHashHex for message $messageId',
|
||||
);
|
||||
}
|
||||
|
||||
// DEPRECATED: Old queue-based matching (kept for fallback)
|
||||
@@ -209,17 +230,14 @@ class MessageRetryService extends ChangeNotifier {
|
||||
_pendingMessageQueuePerContact[contact.publicKeyHex]!.add(messageId);
|
||||
|
||||
if (_sendMessageCallback != null) {
|
||||
_sendMessageCallback!(
|
||||
contact,
|
||||
message.text,
|
||||
attempt,
|
||||
timestampSeconds,
|
||||
);
|
||||
_sendMessageCallback!(contact, message.text, attempt, timestampSeconds);
|
||||
}
|
||||
}
|
||||
|
||||
void updateMessageFromSent(Uint8List ackHash, int timeoutMs) {
|
||||
final ackHashHex = ackHash.map((b) => b.toRadixString(16).padLeft(2, '0')).join();
|
||||
final ackHashHex = ackHash
|
||||
.map((b) => b.toRadixString(16).padLeft(2, '0'))
|
||||
.join();
|
||||
|
||||
// NEW: Try hash-based matching first (fixes LoRa message drops causing mismatches)
|
||||
String? messageId = _expectedHashToMessageId.remove(ackHashHex);
|
||||
@@ -230,16 +248,21 @@ class MessageRetryService extends ChangeNotifier {
|
||||
final message = _pendingMessages[messageId];
|
||||
|
||||
if (contact != null && message != null) {
|
||||
final shortText = message.text.length > 20 ? '${message.text.substring(0, 20)}...' : message.text;
|
||||
final shortText = message.text.length > 20
|
||||
? '${message.text.substring(0, 20)}...'
|
||||
: message.text;
|
||||
_debugLogService?.info(
|
||||
'RESP_CODE_SENT received: ACK hash $ackHashHex ✓ matched "$shortText" to ${contact.name}',
|
||||
tag: 'AckHash',
|
||||
);
|
||||
debugPrint('Hash-based match: ACK hash $ackHashHex → message $messageId ✓');
|
||||
debugPrint(
|
||||
'Hash-based match: ACK hash $ackHashHex → message $messageId ✓',
|
||||
);
|
||||
|
||||
// Remove from old queue since we matched
|
||||
_pendingMessageQueuePerContact[contact.publicKeyHex]?.remove(messageId);
|
||||
if (_pendingMessageQueuePerContact[contact.publicKeyHex]?.isEmpty ?? false) {
|
||||
if (_pendingMessageQueuePerContact[contact.publicKeyHex]?.isEmpty ??
|
||||
false) {
|
||||
_pendingMessageQueuePerContact.remove(contact.publicKeyHex);
|
||||
}
|
||||
} else {
|
||||
@@ -259,7 +282,9 @@ class MessageRetryService extends ChangeNotifier {
|
||||
'RESP_CODE_SENT: ACK hash $ackHashHex not found in hash table, falling back to queue',
|
||||
tag: 'AckHash',
|
||||
);
|
||||
debugPrint('Hash-based match failed for $ackHashHex, falling back to queue-based matching');
|
||||
debugPrint(
|
||||
'Hash-based match failed for $ackHashHex, falling back to queue-based matching',
|
||||
);
|
||||
|
||||
for (var entry in _pendingMessageQueuePerContact.entries) {
|
||||
final contactKey = entry.key;
|
||||
@@ -271,7 +296,9 @@ class MessageRetryService extends ChangeNotifier {
|
||||
if (_pendingMessages.containsKey(candidateMessageId)) {
|
||||
messageId = candidateMessageId;
|
||||
contact = _pendingContacts[candidateMessageId];
|
||||
debugPrint('Queue-based match (fallback): $ackHashHex → message $messageId for $contactKey');
|
||||
debugPrint(
|
||||
'Queue-based match (fallback): $ackHashHex → message $messageId for $contactKey',
|
||||
);
|
||||
break;
|
||||
} else {
|
||||
debugPrint('Dequeued stale message $candidateMessageId - skipping');
|
||||
@@ -280,7 +307,9 @@ class MessageRetryService extends ChangeNotifier {
|
||||
if (_pendingMessages.containsKey(nextMessageId)) {
|
||||
messageId = nextMessageId;
|
||||
contact = _pendingContacts[nextMessageId];
|
||||
debugPrint('Queue-based match (fallback): $ackHashHex → message $messageId');
|
||||
debugPrint(
|
||||
'Queue-based match (fallback): $ackHashHex → message $messageId',
|
||||
);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -306,16 +335,22 @@ class MessageRetryService extends ChangeNotifier {
|
||||
final selection = _pendingPathSelections[messageId];
|
||||
|
||||
if (message == null) {
|
||||
debugPrint('Message $messageId no longer pending for ACK hash: $ackHashHex');
|
||||
debugPrint(
|
||||
'Message $messageId no longer pending for ACK hash: $ackHashHex',
|
||||
);
|
||||
_ackHashToMessageId.remove(ackHashHex);
|
||||
return;
|
||||
}
|
||||
|
||||
// Add this ACK hash to the list of expected ACKs for this message (for history)
|
||||
_expectedAckHashes[messageId] ??= [];
|
||||
if (!_expectedAckHashes[messageId]!.any((hash) => listEquals(hash, ackHash))) {
|
||||
if (!_expectedAckHashes[messageId]!.any(
|
||||
(hash) => listEquals(hash, ackHash),
|
||||
)) {
|
||||
_expectedAckHashes[messageId]!.add(Uint8List.fromList(ackHash));
|
||||
debugPrint('Added ACK hash $ackHashHex to message $messageId (total: ${_expectedAckHashes[messageId]!.length})');
|
||||
debugPrint(
|
||||
'Added ACK hash $ackHashHex to message $messageId (total: ${_expectedAckHashes[messageId]!.length})',
|
||||
);
|
||||
}
|
||||
|
||||
// Use device-provided timeout, or calculate from radio settings if timeout is 0 or invalid
|
||||
@@ -330,8 +365,13 @@ class MessageRetryService extends ChangeNotifier {
|
||||
} else {
|
||||
pathLengthValue = contact.pathLength;
|
||||
}
|
||||
actualTimeout = _calculateTimeoutCallback!(pathLengthValue, message.text.length);
|
||||
debugPrint('Using calculated timeout: ${actualTimeout}ms for path length $pathLengthValue');
|
||||
actualTimeout = _calculateTimeoutCallback!(
|
||||
pathLengthValue,
|
||||
message.text.length,
|
||||
);
|
||||
debugPrint(
|
||||
'Using calculated timeout: ${actualTimeout}ms for path length $pathLengthValue',
|
||||
);
|
||||
}
|
||||
|
||||
final updatedMessage = message.copyWith(
|
||||
@@ -364,16 +404,22 @@ class MessageRetryService extends ChangeNotifier {
|
||||
final selection = _pendingPathSelections[messageId];
|
||||
|
||||
if (message == null || contact == null) {
|
||||
debugPrint('Timeout fired but message $messageId no longer pending (likely already delivered)');
|
||||
debugPrint(
|
||||
'Timeout fired but message $messageId no longer pending (likely already delivered)',
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
final shortText = message.text.length > 20 ? '${message.text.substring(0, 20)}...' : message.text;
|
||||
final shortText = message.text.length > 20
|
||||
? '${message.text.substring(0, 20)}...'
|
||||
: message.text;
|
||||
_debugLogService?.warn(
|
||||
'Timeout: No ACK received for "$shortText" to ${contact.name} (attempt ${message.retryCount}) → retrying',
|
||||
tag: 'AckHash',
|
||||
);
|
||||
debugPrint('Timeout for message $messageId (retry ${message.retryCount}/${maxRetries - 1})');
|
||||
debugPrint(
|
||||
'Timeout for message $messageId (retry ${message.retryCount}/${maxRetries - 1})',
|
||||
);
|
||||
|
||||
if (message.retryCount < maxRetries - 1) {
|
||||
final backoffMs = 1000 * (1 << message.retryCount);
|
||||
@@ -402,7 +448,9 @@ class MessageRetryService extends ChangeNotifier {
|
||||
if (_pendingMessages.containsKey(messageId)) {
|
||||
_attemptSend(messageId);
|
||||
} else {
|
||||
debugPrint('Retry cancelled: message $messageId was delivered while waiting');
|
||||
debugPrint(
|
||||
'Retry cancelled: message $messageId was delivered while waiting',
|
||||
);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
@@ -420,7 +468,8 @@ class MessageRetryService extends ChangeNotifier {
|
||||
|
||||
// Clean up the queue entry for this contact
|
||||
_pendingMessageQueuePerContact[contact.publicKeyHex]?.remove(messageId);
|
||||
if (_pendingMessageQueuePerContact[contact.publicKeyHex]?.isEmpty ?? false) {
|
||||
if (_pendingMessageQueuePerContact[contact.publicKeyHex]?.isEmpty ??
|
||||
false) {
|
||||
_pendingMessageQueuePerContact.remove(contact.publicKeyHex);
|
||||
}
|
||||
|
||||
@@ -430,7 +479,13 @@ class MessageRetryService extends ChangeNotifier {
|
||||
_clearContactPathCallback!(contact);
|
||||
}
|
||||
|
||||
_recordPathResultFromMessage(contact.publicKeyHex, message, selection, false, null);
|
||||
_recordPathResultFromMessage(
|
||||
contact.publicKeyHex,
|
||||
message,
|
||||
selection,
|
||||
false,
|
||||
null,
|
||||
);
|
||||
|
||||
if (_updateMessageCallback != null) {
|
||||
_updateMessageCallback!(failedMessage);
|
||||
@@ -443,18 +498,22 @@ class MessageRetryService extends ChangeNotifier {
|
||||
void _moveAckHashesToHistory(String messageId) {
|
||||
final ackHashes = _expectedAckHashes.remove(messageId);
|
||||
if (ackHashes != null && ackHashes.isNotEmpty) {
|
||||
_ackHistory.add(_AckHistoryEntry(
|
||||
messageId: messageId,
|
||||
ackHashes: ackHashes,
|
||||
timestamp: DateTime.now(),
|
||||
));
|
||||
_ackHistory.add(
|
||||
_AckHistoryEntry(
|
||||
messageId: messageId,
|
||||
ackHashes: ackHashes,
|
||||
timestamp: DateTime.now(),
|
||||
),
|
||||
);
|
||||
|
||||
// Trim history to max size (rolling buffer)
|
||||
while (_ackHistory.length > maxAckHistorySize) {
|
||||
_ackHistory.removeAt(0);
|
||||
}
|
||||
|
||||
debugPrint('Moved ${ackHashes.length} ACK hashes to history for message $messageId (history size: ${_ackHistory.length})');
|
||||
debugPrint(
|
||||
'Moved ${ackHashes.length} ACK hashes to history for message $messageId (history size: ${_ackHistory.length})',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -462,7 +521,9 @@ class MessageRetryService extends ChangeNotifier {
|
||||
for (final entry in _ackHistory) {
|
||||
for (final expectedHash in entry.ackHashes) {
|
||||
if (listEquals(expectedHash, ackHash)) {
|
||||
debugPrint('Found ACK match in history: messageId=${entry.messageId}, age=${DateTime.now().difference(entry.timestamp).inSeconds}s');
|
||||
debugPrint(
|
||||
'Found ACK match in history: messageId=${entry.messageId}, age=${DateTime.now().difference(entry.timestamp).inSeconds}s',
|
||||
);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -472,7 +533,9 @@ class MessageRetryService extends ChangeNotifier {
|
||||
|
||||
void handleAckReceived(Uint8List ackHash, int tripTimeMs) {
|
||||
String? matchedMessageId;
|
||||
final ackHashHex = ackHash.map((b) => b.toRadixString(16).padLeft(2, '0')).join();
|
||||
final ackHashHex = ackHash
|
||||
.map((b) => b.toRadixString(16).padLeft(2, '0'))
|
||||
.join();
|
||||
|
||||
debugPrint('ACK received: $ackHashHex, trip time: ${tripTimeMs}ms');
|
||||
|
||||
@@ -502,7 +565,9 @@ class MessageRetryService extends ChangeNotifier {
|
||||
tag: 'AckHash',
|
||||
);
|
||||
// Fallback: Check against ALL expected ACK hashes (from all retry attempts)
|
||||
debugPrint('ACK not in mapping, checking _expectedAckHashes (${_expectedAckHashes.length} messages)');
|
||||
debugPrint(
|
||||
'ACK not in mapping, checking _expectedAckHashes (${_expectedAckHashes.length} messages)',
|
||||
);
|
||||
for (var entry in _expectedAckHashes.entries) {
|
||||
final messageId = entry.key;
|
||||
final expectedHashes = entry.value;
|
||||
@@ -510,7 +575,9 @@ class MessageRetryService extends ChangeNotifier {
|
||||
for (final expectedHash in expectedHashes) {
|
||||
if (listEquals(expectedHash, ackHash)) {
|
||||
matchedMessageId = messageId;
|
||||
debugPrint('Matched ACK to message via fallback: $matchedMessageId (attempt ${expectedHashes.indexOf(expectedHash)})');
|
||||
debugPrint(
|
||||
'Matched ACK to message via fallback: $matchedMessageId (attempt ${expectedHashes.indexOf(expectedHash)})',
|
||||
);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -524,7 +591,9 @@ class MessageRetryService extends ChangeNotifier {
|
||||
final contact = _pendingContacts[matchedMessageId];
|
||||
final selection = _pendingPathSelections[matchedMessageId];
|
||||
|
||||
final shortText = message.text.length > 20 ? '${message.text.substring(0, 20)}...' : message.text;
|
||||
final shortText = message.text.length > 20
|
||||
? '${message.text.substring(0, 20)}...'
|
||||
: message.text;
|
||||
_debugLogService?.info(
|
||||
'PUSH_CODE_SEND_CONFIRMED: ACK hash $ackHashHex ✓ "$shortText" delivered to ${contact?.name ?? "unknown"} in ${tripTimeMs}ms',
|
||||
tag: 'AckHash',
|
||||
@@ -549,8 +618,11 @@ class MessageRetryService extends ChangeNotifier {
|
||||
|
||||
// Clean up the queue entry for this contact (remove any remaining references to this message)
|
||||
if (contact != null) {
|
||||
_pendingMessageQueuePerContact[contact.publicKeyHex]?.remove(matchedMessageId);
|
||||
if (_pendingMessageQueuePerContact[contact.publicKeyHex]?.isEmpty ?? false) {
|
||||
_pendingMessageQueuePerContact[contact.publicKeyHex]?.remove(
|
||||
matchedMessageId,
|
||||
);
|
||||
if (_pendingMessageQueuePerContact[contact.publicKeyHex]?.isEmpty ??
|
||||
false) {
|
||||
_pendingMessageQueuePerContact.remove(contact.publicKeyHex);
|
||||
}
|
||||
}
|
||||
@@ -560,7 +632,13 @@ class MessageRetryService extends ChangeNotifier {
|
||||
}
|
||||
|
||||
if (contact != null) {
|
||||
_recordPathResultFromMessage(contact.publicKeyHex, message, selection, true, tripTimeMs);
|
||||
_recordPathResultFromMessage(
|
||||
contact.publicKeyHex,
|
||||
message,
|
||||
selection,
|
||||
true,
|
||||
tripTimeMs,
|
||||
);
|
||||
}
|
||||
|
||||
notifyListeners();
|
||||
@@ -663,7 +741,12 @@ class MessageRetryService extends ChangeNotifier {
|
||||
if (_recordPathResultCallback == null) return;
|
||||
final recordSelection = selection ?? _selectionFromMessage(message);
|
||||
if (recordSelection == null) return;
|
||||
_recordPathResultCallback!(contactKey, recordSelection, success, tripTimeMs);
|
||||
_recordPathResultCallback!(
|
||||
contactKey,
|
||||
recordSelection,
|
||||
success,
|
||||
tripTimeMs,
|
||||
);
|
||||
}
|
||||
|
||||
PathSelection? _selectionFromMessage(Message message) {
|
||||
|
||||
@@ -6,13 +6,16 @@ class NotificationService {
|
||||
factory NotificationService() => _instance;
|
||||
NotificationService._internal();
|
||||
|
||||
final FlutterLocalNotificationsPlugin _notifications = FlutterLocalNotificationsPlugin();
|
||||
final FlutterLocalNotificationsPlugin _notifications =
|
||||
FlutterLocalNotificationsPlugin();
|
||||
bool _isInitialized = false;
|
||||
|
||||
Future<void> initialize() async {
|
||||
if (_isInitialized) return;
|
||||
|
||||
const androidSettings = AndroidInitializationSettings('@mipmap/ic_launcher');
|
||||
const androidSettings = AndroidInitializationSettings(
|
||||
'@mipmap/ic_launcher',
|
||||
);
|
||||
const iosSettings = DarwinInitializationSettings(
|
||||
requestAlertPermission: true,
|
||||
requestBadgePermission: true,
|
||||
@@ -47,16 +50,20 @@ class NotificationService {
|
||||
}
|
||||
|
||||
// Request Android 13+ notification permission
|
||||
final androidPlugin = _notifications.resolvePlatformSpecificImplementation<
|
||||
AndroidFlutterLocalNotificationsPlugin>();
|
||||
final androidPlugin = _notifications
|
||||
.resolvePlatformSpecificImplementation<
|
||||
AndroidFlutterLocalNotificationsPlugin
|
||||
>();
|
||||
if (androidPlugin != null) {
|
||||
final granted = await androidPlugin.requestNotificationsPermission();
|
||||
return granted ?? false;
|
||||
}
|
||||
|
||||
// iOS permissions are requested during initialization
|
||||
final iosPlugin = _notifications.resolvePlatformSpecificImplementation<
|
||||
IOSFlutterLocalNotificationsPlugin>();
|
||||
final iosPlugin = _notifications
|
||||
.resolvePlatformSpecificImplementation<
|
||||
IOSFlutterLocalNotificationsPlugin
|
||||
>();
|
||||
if (iosPlugin != null) {
|
||||
final granted = await iosPlugin.requestPermissions(
|
||||
alert: true,
|
||||
@@ -204,9 +211,7 @@ class NotificationService {
|
||||
);
|
||||
|
||||
final preview = message.trim();
|
||||
final body = preview.isEmpty
|
||||
? 'Received new message'
|
||||
: preview;
|
||||
final body = preview.isEmpty ? 'Received new message' : preview;
|
||||
|
||||
await _notifications.show(
|
||||
channelIndex?.hashCode ?? DateTime.now().millisecondsSinceEpoch,
|
||||
|
||||
@@ -61,7 +61,10 @@ class PathHistoryService extends ChangeNotifier {
|
||||
int? tripTimeMs,
|
||||
}) {
|
||||
if (selection.useFlood) {
|
||||
final stats = _floodStats.putIfAbsent(contactPubKeyHex, () => _FloodStats());
|
||||
final stats = _floodStats.putIfAbsent(
|
||||
contactPubKeyHex,
|
||||
() => _FloodStats(),
|
||||
);
|
||||
if (success) {
|
||||
stats.successCount += 1;
|
||||
if (tripTimeMs != null) stats.lastTripTimeMs = tripTimeMs;
|
||||
@@ -88,23 +91,28 @@ class PathHistoryService extends ChangeNotifier {
|
||||
}
|
||||
|
||||
PathSelection getNextAutoPathSelection(String contactPubKeyHex) {
|
||||
final ranked = _getRankedPaths(contactPubKeyHex)
|
||||
.take(_autoRotationTopCount)
|
||||
.toList();
|
||||
final ranked = _getRankedPaths(
|
||||
contactPubKeyHex,
|
||||
).take(_autoRotationTopCount).toList();
|
||||
if (ranked.isEmpty) {
|
||||
return const PathSelection(pathBytes: [], hopCount: -1, useFlood: true);
|
||||
}
|
||||
|
||||
_trackAccess(contactPubKeyHex);
|
||||
|
||||
final selections = ranked
|
||||
.map((path) => PathSelection(
|
||||
pathBytes: path.pathBytes,
|
||||
hopCount: path.hopCount,
|
||||
useFlood: false,
|
||||
))
|
||||
.toList()
|
||||
..add(const PathSelection(pathBytes: [], hopCount: -1, useFlood: true));
|
||||
final selections =
|
||||
ranked
|
||||
.map(
|
||||
(path) => PathSelection(
|
||||
pathBytes: path.pathBytes,
|
||||
hopCount: path.hopCount,
|
||||
useFlood: false,
|
||||
),
|
||||
)
|
||||
.toList()
|
||||
..add(
|
||||
const PathSelection(pathBytes: [], hopCount: -1, useFlood: true),
|
||||
);
|
||||
|
||||
final currentIndex = _autoRotationIndex[contactPubKeyHex] ?? 0;
|
||||
final selection = selections[currentIndex % selections.length];
|
||||
@@ -241,7 +249,8 @@ class PathHistoryService extends ChangeNotifier {
|
||||
}
|
||||
|
||||
Future<ContactPathHistory?> _loadHistoryFromStorage(
|
||||
String contactPubKeyHex) async {
|
||||
String contactPubKeyHex,
|
||||
) async {
|
||||
return await _storage.loadPathHistory(contactPubKeyHex);
|
||||
}
|
||||
|
||||
@@ -308,8 +317,10 @@ class PathHistoryService extends ChangeNotifier {
|
||||
..removeWhere((p) => p.pathBytes.isEmpty);
|
||||
|
||||
ranked.sort((a, b) {
|
||||
final aRate = (a.successCount + 1) / (a.successCount + a.failureCount + 2);
|
||||
final bRate = (b.successCount + 1) / (b.successCount + b.failureCount + 2);
|
||||
final aRate =
|
||||
(a.successCount + 1) / (a.successCount + a.failureCount + 2);
|
||||
final bRate =
|
||||
(b.successCount + 1) / (b.successCount + b.failureCount + 2);
|
||||
if (aRate != bRate) return bRate.compareTo(aRate);
|
||||
if (a.successCount != b.successCount) {
|
||||
return b.successCount.compareTo(a.successCount);
|
||||
@@ -329,7 +340,10 @@ class PathHistoryService extends ChangeNotifier {
|
||||
}
|
||||
|
||||
void _updateFloodStats(String contactPubKeyHex) {
|
||||
final stats = _floodStats.putIfAbsent(contactPubKeyHex, () => _FloodStats());
|
||||
final stats = _floodStats.putIfAbsent(
|
||||
contactPubKeyHex,
|
||||
() => _FloodStats(),
|
||||
);
|
||||
stats.lastUsed = DateTime.now();
|
||||
}
|
||||
|
||||
|
||||
@@ -26,7 +26,9 @@ class RepeaterCommandService {
|
||||
int retries = maxRetries,
|
||||
}) async {
|
||||
final repeaterKey = repeater.publicKeyHex;
|
||||
final hasPending = _pendingCommands.keys.any((id) => id.startsWith(repeaterKey));
|
||||
final hasPending = _pendingCommands.keys.any(
|
||||
(id) => id.startsWith(repeaterKey),
|
||||
);
|
||||
if (hasPending) {
|
||||
throw Exception('Another command is still awaiting a response.');
|
||||
}
|
||||
@@ -84,7 +86,9 @@ class RepeaterCommandService {
|
||||
attempt: attempt,
|
||||
timestampSeconds: timestampSeconds,
|
||||
);
|
||||
final responseBytes = frame.length > maxFrameSize ? frame.length : maxFrameSize;
|
||||
final responseBytes = frame.length > maxFrameSize
|
||||
? frame.length
|
||||
: maxFrameSize;
|
||||
final timeoutMs = _connector.calculateTimeout(
|
||||
pathLength: pathLengthValue,
|
||||
messageBytes: responseBytes,
|
||||
@@ -97,7 +101,9 @@ class RepeaterCommandService {
|
||||
() {
|
||||
final completer = _pendingCommands[commandId];
|
||||
if (completer != null && !completer.isCompleted) {
|
||||
completer.completeError('Command timeout after $timeoutSeconds seconds');
|
||||
completer.completeError(
|
||||
'Command timeout after $timeoutSeconds seconds',
|
||||
);
|
||||
_cleanup(commandId);
|
||||
}
|
||||
},
|
||||
|
||||
@@ -8,7 +8,9 @@ class StorageService {
|
||||
static const String _repeaterPasswordsKey = 'repeater_passwords';
|
||||
|
||||
Future<void> savePathHistory(
|
||||
String contactPubKeyHex, ContactPathHistory history) async {
|
||||
String contactPubKeyHex,
|
||||
ContactPathHistory history,
|
||||
) async {
|
||||
final prefs = PrefsManager.instance;
|
||||
final key = '$_pathHistoryPrefix$contactPubKeyHex';
|
||||
final jsonStr = jsonEncode(history.toJson());
|
||||
@@ -39,8 +41,9 @@ class StorageService {
|
||||
Future<void> clearAllPathHistories() async {
|
||||
final prefs = PrefsManager.instance;
|
||||
final keys = prefs.getKeys();
|
||||
final pathHistoryKeys =
|
||||
keys.where((key) => key.startsWith(_pathHistoryPrefix));
|
||||
final pathHistoryKeys = keys.where(
|
||||
(key) => key.startsWith(_pathHistoryPrefix),
|
||||
);
|
||||
|
||||
for (final key in pathHistoryKeys) {
|
||||
await prefs.remove(key);
|
||||
@@ -74,7 +77,9 @@ class StorageService {
|
||||
|
||||
/// Save a repeater password by public key hex
|
||||
Future<void> saveRepeaterPassword(
|
||||
String repeaterPubKeyHex, String password) async {
|
||||
String repeaterPubKeyHex,
|
||||
String password,
|
||||
) async {
|
||||
final prefs = PrefsManager.instance;
|
||||
final passwords = await loadRepeaterPasswords();
|
||||
passwords[repeaterPubKeyHex] = password;
|
||||
|
||||
Reference in New Issue
Block a user