Add localization support for German, Spanish, French, and Italian

- Updated localization files for French, Croatian, Italian, Slovenian to include translations for German, Spanish, French, and Italian.
- Added new methods for handling drawing messages sent to the public channel in multiple languages.
- Enhanced the Contact model to identify public channels using a dedicated method.
- Implemented echo detection for public channel messages, including tracking and reporting of echoes.
- Updated BLE response handling to support echo detection and tracking of sent messages.
- Modified UI components to reflect new localization strings and echo statuses.
This commit is contained in:
Janez T
2025-10-18 22:39:26 +02:00
parent da03592980
commit 52b2f4ede1
30 changed files with 1971 additions and 37 deletions

View File

@@ -0,0 +1,83 @@
import 'dart:typed_data';
/// Tracks sent public channel messages for echo detection
///
/// When a message is sent to the public channel, it's encrypted with AES128-ECB
/// which is deterministic. When another node receives and rebroadcasts it,
/// the raw packet will be byte-for-byte identical. We can detect these echoes
/// by comparing the raw packet data from PUSH_CODE_LOG_RX_DATA (0x88) against
/// packets we've sent.
class SentMessageTracker {
/// Unique identifier for the message (timestamp-based)
final String messageId;
/// SHA256 hash of the encrypted packet for fast O(1) lookup
final String packetHashHex;
/// Original raw encrypted packet bytes (for verification)
final Uint8List? rawPacket;
/// When the message was sent
final DateTime sentTime;
/// When this tracker expires (default: 5 minutes)
final DateTime expiryTime;
/// Number of times we've detected this message being rebroadcast
int echoCount;
/// Unique echo paths detected (SNR/RSSI signatures)
/// Format: "snr_rssi" e.g., "20_-56" means SNR=5.0dB (20/4), RSSI=-56dBm
final Set<String> uniqueEchoPaths;
/// Timestamps when echoes were detected
final List<DateTime> echoTimestamps;
SentMessageTracker({
required this.messageId,
required this.packetHashHex,
this.rawPacket,
required this.sentTime,
required this.expiryTime,
this.echoCount = 0,
Set<String>? uniqueEchoPaths,
List<DateTime>? echoTimestamps,
}) : uniqueEchoPaths = uniqueEchoPaths ?? {},
echoTimestamps = echoTimestamps ?? [];
/// Check if this tracker has expired
bool get isExpired => DateTime.now().isAfter(expiryTime);
/// Time until expiry
Duration get timeUntilExpiry => expiryTime.difference(DateTime.now());
/// Add an echo detection
void addEcho(int snrRaw, int rssiDbm) {
echoCount++;
uniqueEchoPaths.add('${snrRaw}_$rssiDbm');
echoTimestamps.add(DateTime.now());
}
/// Get the SNR in dB from raw value
static double snrRawToDb(int snrRaw) {
return snrRaw.toSigned(8) / 4.0;
}
/// Get formatted echo statistics
String get echoStats {
if (echoCount == 0) return 'No echoes detected';
if (echoCount == 1) return '1 echo from ${uniqueEchoPaths.length} path(s)';
return '$echoCount echoes from ${uniqueEchoPaths.length} path(s)';
}
/// Get average time to first echo
Duration? get timeToFirstEcho {
if (echoTimestamps.isEmpty) return null;
return echoTimestamps.first.difference(sentTime);
}
@override
String toString() {
return 'SentMessageTracker(id=$messageId, echoes=$echoCount, paths=${uniqueEchoPaths.length}, expired=$isExpired)';
}
}