Compare commits

..

28 Commits

Author SHA1 Message Date
copilot-swe-agent[bot] 3a06c36ec4 Wrap title Column in Expanded to prevent horizontal overflow in AppBarTitle
Co-authored-by: wel97459 <12990640+wel97459@users.noreply.github.com>
2026-02-18 23:51:49 +00:00
copilot-swe-agent[bot] 302589f9f4 Initial plan 2026-02-18 23:49:50 +00:00
Winston Lowe b8acedd03e Refactor path handling in ChannelMessagePathScreen to improve logic for outgoing messages and channel messages 2026-02-18 10:25:06 -08:00
Winston Lowe 17a9db0f0e Refactor ranking calculation for direct repeaters and update path handling in channel message screens 2026-02-18 09:28:25 -08:00
Winston Lowe 0a01ecde38 Fix typo in variable name for second direct repeater in path management dialog 2026-02-16 15:07:31 -08:00
Winston Lowe 14cec533ac Adjust ranking calculation for direct repeaters by adding offset to SNR for improved accuracy 2026-02-16 12:58:55 -08:00
Winston Lowe fdfc1f6d25 Refactor localization keys for "neighbors" terminology across multiple languages
- Updated localization keys from "neighbours" to "neighbors" in the following files:
  - app_localizations_bg.dart
  - app_localizations_de.dart
  - app_localizations_en.dart
  - app_localizations_es.dart
  - app_localizations_fr.dart
  - app_localizations_it.dart
  - app_localizations_nl.dart
  - app_localizations_pl.dart
  - app_localizations_pt.dart
  - app_localizations_ru.dart
  - app_localizations_sk.dart
  - app_localizations_sl.dart
  - app_localizations_sv.dart
  - app_localizations_uk.dart
  - app_localizations_zh.dart
- Updated corresponding ARB files to reflect the changes in keys.
- Renamed the NeighboursScreen to NeighborsScreen in the chat and repeater hub screens for consistency.
2026-02-16 12:58:37 -08:00
Winston Lowe 42eb293d1c Implement ranking system for direct repeaters based on SNR and recency; update related UI components to reflect changes 2026-02-16 11:58:44 -08:00
Winston Lowe 36401210ce Prevent notifications for chat and sensor adverts without a valid path 2026-02-15 21:16:42 -08:00
Winston Lowe a68e1dd428 Sort direct repeaters by last updated time and SNR; limit to top three for improved path management dialog 2026-02-15 21:09:30 -08:00
Winston Lowe 52a578777d Refactor AppBarTitle widget to remove unused style parameter; update related screens to reflect changes
Improve SNR handling by adding validation for spreading factor range in snrUiFromSNR function
Update contact handling in MeshCoreConnector to fix variable naming and improve readability
Stop parsing unsupported LPP types in CayenneLpp to avoid misalignment
2026-02-15 19:38:34 -08:00
Winston Lowe 71152bd3eb Throw an exception for unsupported LPP types in CayenneLpp class 2026-02-15 17:20:35 -08:00
Winston Lowe 940a1be203 Handle loading state and error parsing in PathTraceMapScreen; update SNR indicator dialog content layout 2026-02-15 17:16:56 -08:00
Winston Lowe 75a7f437f6 Add SNR indicator localization and update UI references for nearby repeaters 2026-02-15 17:04:54 -08:00
Winston Lowe e6814b4f48 Refactor packet handling to skip only the RSSI byte for improved reliability 2026-02-15 17:04:10 -08:00
Winston Lowe 1589883c88 Update lib/helpers/cayenne_lpp.dart
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-02-15 16:58:44 -08:00
Winston Lowe 03b3533675 Update lib/widgets/battery_indicator.dart
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-02-15 16:17:13 -08:00
Winston Lowe 608b6fb539 Update lib/screens/path_trace_map.dart
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-02-15 16:16:05 -08:00
Winston Lowe 04f5c44ed9 Update lib/connector/meshcore_connector.dart
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-02-15 12:59:04 -08:00
Winston Lowe 4019741a81 Update lib/connector/meshcore_connector.dart
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-02-15 12:58:34 -08:00
Winston Lowe 152d5f8bb5 Update lib/models/contact.dart
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-02-15 12:57:49 -08:00
Winston Lowe 3f80ae1cf7 Remove unused import from SNR indicator widget 2026-02-15 12:51:56 -08:00
Winston Lowe 246cf99415 Enhance path management dialog to display direct repeaters with color coding based on signal strength 2026-02-15 12:45:43 -08:00
Winston Lowe 5751cddaa1 Add SNRIndicator to AppBar and refactor BatteryIndicator layout 2026-02-15 11:45:48 -08:00
Winston Lowe fa5a0932ee Refactor PathTraceData to use List<double> for snrData and adjust data mapping in PathTraceMapScreen 2026-02-15 11:45:25 -08:00
Winston Lowe cdda232006 Ignore advertisements from self in MeshCoreConnector 2026-02-15 11:44:49 -08:00
Winston Lowe 63aa515f52 Fix trace route bytes generation logic in Contact model 2026-02-15 11:44:35 -08:00
Winston Lowe aed3b0157a Refactor Cayenne LPP parsing with error handling and logging
- Added error handling and logging to the Cayenne LPP parsing methods to manage malformed data gracefully.
- Improved the structure of the parsing logic for better readability and maintainability.
- Updated the Contact model to include error handling during frame parsing.
- Refactored Channels, Contacts, Map, and Neighbours screens to utilize a new AppBarTitle widget for consistent app bar design.
- Enhanced the BatteryIndicator widget to display SNR information for direct repeaters.
- Introduced SNRUi class for better management of SNR icon and text representation.
- Improved error handling in PathTraceMap and Neighbours screens to log errors appropriately.
2026-02-14 14:19:09 -08:00
51 changed files with 2010 additions and 1446 deletions
+292 -15
View File
@@ -37,6 +37,42 @@ class MeshCoreUuids {
static const String txCharacteristic = "6e400003-b5a3-f393-e0a9-e50e24dcca9e"; static const String txCharacteristic = "6e400003-b5a3-f393-e0a9-e50e24dcca9e";
} }
class DirectRepeater {
static const int maxAgeMinutes = 30; // Max age for direct repeater info
final int pubkeyFirstByte;
double snr;
DateTime lastUpdated;
DirectRepeater({
required this.pubkeyFirstByte,
required this.snr,
DateTime? lastUpdated,
}) : lastUpdated = lastUpdated ?? DateTime.now();
void update(double newSNR) {
snr = newSNR;
lastUpdated = DateTime.now();
}
int get ranking {
if (isStale()) {
return -1; // Stale repeaters get lowest rank
}
// Higher SNR gets higher rank and recency within maxAgeMinutes breaks ties.
final ageMs =
DateTime.now().millisecondsSinceEpoch -
lastUpdated.millisecondsSinceEpoch;
final maxAgeMs = maxAgeMinutes * 60 * 1000;
final recencyScore = (maxAgeMs - ageMs).clamp(0, maxAgeMs);
return ((snr - 31.75) * 1000).round() + recencyScore;
}
bool isStale() {
return DateTime.now().difference(lastUpdated) >
const Duration(minutes: maxAgeMinutes);
}
}
enum MeshCoreConnectionState { enum MeshCoreConnectionState {
disconnected, disconnected,
scanning, scanning,
@@ -90,11 +126,10 @@ class MeshCoreConnector extends ChangeNotifier {
int? _currentBwHz; int? _currentBwHz;
int? _currentSf; int? _currentSf;
int? _currentCr; int? _currentCr;
bool? _clientRepeat;
int? _firmwareVerCode;
int? _batteryMillivolts; int? _batteryMillivolts;
double? _selfLatitude; double? _selfLatitude;
double? _selfLongitude; double? _selfLongitude;
final List<DirectRepeater> _directRepeaters = List.empty(growable: true);
bool _isLoadingContacts = false; bool _isLoadingContacts = false;
bool _isLoadingChannels = false; bool _isLoadingChannels = false;
bool _hasLoadedChannels = false; bool _hasLoadedChannels = false;
@@ -196,14 +231,13 @@ class MeshCoreConnector extends ChangeNotifier {
String? get selfName => _selfName; String? get selfName => _selfName;
double? get selfLatitude => _selfLatitude; double? get selfLatitude => _selfLatitude;
double? get selfLongitude => _selfLongitude; double? get selfLongitude => _selfLongitude;
List<DirectRepeater> get directRepeaters => _directRepeaters;
int? get currentTxPower => _currentTxPower; int? get currentTxPower => _currentTxPower;
int? get maxTxPower => _maxTxPower; int? get maxTxPower => _maxTxPower;
int? get currentFreqHz => _currentFreqHz; int? get currentFreqHz => _currentFreqHz;
int? get currentBwHz => _currentBwHz; int? get currentBwHz => _currentBwHz;
int? get currentSf => _currentSf; int? get currentSf => _currentSf;
int? get currentCr => _currentCr; int? get currentCr => _currentCr;
bool? get clientRepeat => _clientRepeat;
int? get firmwareVerCode => _firmwareVerCode;
Map<String, String>? get currentCustomVars => _currentCustomVars; Map<String, String>? get currentCustomVars => _currentCustomVars;
int? get batteryMillivolts => _batteryMillivolts; int? get batteryMillivolts => _batteryMillivolts;
int get maxContacts => _maxContacts; int get maxContacts => _maxContacts;
@@ -920,8 +954,6 @@ class MeshCoreConnector extends ChangeNotifier {
_selfName = null; _selfName = null;
_selfLatitude = null; _selfLatitude = null;
_selfLongitude = null; _selfLongitude = null;
_clientRepeat = null;
_firmwareVerCode = null;
_batteryMillivolts = null; _batteryMillivolts = null;
_batteryRequested = false; _batteryRequested = false;
_awaitingSelfInfo = false; _awaitingSelfInfo = false;
@@ -1696,6 +1728,11 @@ class MeshCoreConnector extends ChangeNotifier {
_isLoadingContacts = true; _isLoadingContacts = true;
notifyListeners(); notifyListeners();
break; break;
case pushCodeNewAdvert:
debugPrint('Got New CONTACT');
// It's the same format as respCodeContact, so we can reuse the handler
_handleContact(frame);
break;
case respCodeContact: case respCodeContact:
debugPrint('Got CONTACT'); debugPrint('Got CONTACT');
_handleContact(frame); _handleContact(frame);
@@ -1740,6 +1777,7 @@ class MeshCoreConnector extends ChangeNotifier {
case pushCodeStatusResponse: case pushCodeStatusResponse:
break; break;
case pushCodeLogRxData: case pushCodeLogRxData:
_handleRxData(frame);
_handleLogRxData(frame); _handleLogRxData(frame);
break; break;
case respCodeChannelInfo: case respCodeChannelInfo:
@@ -1753,6 +1791,7 @@ class MeshCoreConnector extends ChangeNotifier {
break; break;
case respCodeCustomVars: case respCodeCustomVars:
_handleCustomVars(frame); _handleCustomVars(frame);
break;
default: default:
debugPrint('Unknown frame code: $code'); debugPrint('Unknown frame code: $code');
} }
@@ -1826,13 +1865,6 @@ class MeshCoreConnector extends ChangeNotifier {
void _handleDeviceInfo(Uint8List frame) { void _handleDeviceInfo(Uint8List frame) {
if (frame.length < 4) return; if (frame.length < 4) return;
_firmwareVerCode = frame[1];
// Parse client_repeat from firmware v9+ (byte 80)
if (frame.length >= 81) {
_clientRepeat = frame[80] != 0;
}
// Firmware reports MAX_CONTACTS / 2 for v3+ device info. // Firmware reports MAX_CONTACTS / 2 for v3+ device info.
final reportedContacts = frame[2]; final reportedContacts = frame[2];
final reportedChannels = frame[3]; final reportedChannels = frame[3];
@@ -1853,8 +1885,8 @@ class MeshCoreConnector extends ChangeNotifier {
unawaited(getChannels(maxChannels: nextMaxChannels)); unawaited(getChannels(maxChannels: nextMaxChannels));
} }
} }
notifyListeners();
} }
notifyListeners();
} }
void _handleNoMoreMessages() { void _handleNoMoreMessages() {
@@ -2015,6 +2047,80 @@ class MeshCoreConnector extends ChangeNotifier {
} }
} }
void _handleContactAdvert(Contact contact) {
if (listEquals(contact.publicKey, _selfPublicKey)) {
return;
}
if (contact.type == advTypeRepeater) {
_contactUnreadCount.remove(contact.publicKeyHex);
_unreadStore.saveContactUnreadCount(
Map<String, int>.from(_contactUnreadCount),
);
}
// Check if this is a new contact
final isNewContact = !_knownContactKeys.contains(contact.publicKeyHex);
final existingIndex = _contacts.indexWhere(
(c) => c.publicKeyHex == contact.publicKeyHex,
);
if (existingIndex >= 0) {
final existing = _contacts[existingIndex];
final mergedLastMessageAt =
existing.lastMessageAt.isAfter(contact.lastMessageAt)
? existing.lastMessageAt
: contact.lastMessageAt;
appLogger.info(
'Refreshing contact ${contact.name}: devicePath=${contact.pathLength}, existingOverride=${existing.pathOverride}',
tag: 'Connector',
);
// CRITICAL: Preserve user's path override when contact is refreshed from device
_contacts[existingIndex] = contact.copyWith(
lastMessageAt: mergedLastMessageAt,
pathOverride: existing.pathOverride, // Preserve user's path choice
pathOverrideBytes: existing.pathOverrideBytes,
);
appLogger.info(
'After merge: pathOverride=${_contacts[existingIndex].pathOverride}, devicePath=${_contacts[existingIndex].pathLength}',
tag: 'Connector',
);
} else {
_contacts.add(contact);
appLogger.info(
'Added new contact ${contact.name}: pathLen=${contact.pathLength}',
tag: 'Connector',
);
}
_knownContactKeys.add(contact.publicKeyHex);
_loadMessagesForContact(contact.publicKeyHex);
// Add path to history if we have a valid path
if (_pathHistoryService != null && contact.pathLength >= 0) {
_pathHistoryService!.handlePathUpdated(contact);
}
notifyListeners();
// Show notification for new contact (advertisement)
if (isNewContact && _appSettingsService != null) {
final settings = _appSettingsService!.settings;
if (settings.notificationsEnabled && settings.notifyOnNewAdvert) {
_notificationService.showAdvertNotification(
contactName: contact.name,
contactType: contact.typeLabel,
contactId: contact.publicKeyHex,
);
}
}
if (!_isLoadingContacts) {
unawaited(_persistContacts());
}
}
Future<void> _persistContacts() async { Future<void> _persistContacts() async {
await _contactStore.saveContacts(_contacts); await _contactStore.saveContacts(_contacts);
} }
@@ -3274,7 +3380,11 @@ class MeshCoreConnector extends ChangeNotifier {
void _handleCustomVars(Uint8List frame) { void _handleCustomVars(Uint8List frame) {
final buf = BufferReader(frame.sublist(1)); final buf = BufferReader(frame.sublist(1));
_currentCustomVars = _parseKeyValueString(buf.readString()); try {
_currentCustomVars = _parseKeyValueString(buf.readString());
} catch (e) {
appLogger.warn('Malformed custom vars frame: $e', tag: 'Connector');
}
} }
void _setState(MeshCoreConnectionState newState) { void _setState(MeshCoreConnectionState newState) {
@@ -3298,6 +3408,173 @@ class MeshCoreConnector extends ChangeNotifier {
super.dispose(); super.dispose();
} }
void _handleRxData(Uint8List frame) {
final packet = BufferReader(frame);
try {
packet.skipBytes(1); // Skip frame type byte
final snr = packet.readInt8() / 4.0;
packet.skipBytes(1); // Skip RSSI byte
//final rssi = packet.readByte();
final header = packet.readByte();
final routeType = header & 0x03;
final payloadType = (header >> 2) & 0x0F;
//final payloadVer = (header >> 6) & 0x03;
final pathLen = packet.readByte();
final pathBytes = packet.readBytes(pathLen);
final payload = packet.readBytes(packet.remaining);
switch (payloadType) {
case payloadTypeADVERT:
_handlePayloadAdvertReceived(payload, pathBytes, routeType, snr);
break;
default:
}
} catch (e) {
appLogger.warn('Malformed RX frame: $e', tag: 'Connector');
}
}
void _handlePayloadAdvertReceived(
Uint8List frame,
Uint8List path,
int routeType,
double snr,
) {
final advert = BufferReader(frame);
double latitude = 0.0;
double longitude = 0.0;
String name = '';
String contactKeyHex = '';
Uint8List publicKey = Uint8List(0);
int type = 0;
int timestamp = 0;
bool hasLocation = false;
bool hasName = false;
try {
publicKey = advert.readBytes(32);
contactKeyHex = publicKey
.map((b) => b.toRadixString(16).padLeft(2, '0'))
.join();
timestamp = advert.readInt32LE();
//TODO add signature verification
advert.skipBytes(64); // Skip signature for now
final flags = advert.readByte();
type = flags & 0x0F;
hasLocation = (flags & 0x10) != 0;
// For future use:
//final hasFeature1 = (flags & 0x20) != 0;
//final hasFeature2 = (flags & 0x40) != 0;
hasName = (flags & 0x80) != 0;
if (hasLocation && advert.remaining >= 8) {
latitude = advert.readInt32LE() / 1e6;
longitude = advert.readInt32LE() / 1e6;
}
if (hasName && advert.remaining > 0) {
name = advert.readString();
}
} catch (e) {
appLogger.warn('Malformed advert frame: $e', tag: 'Connector');
return;
}
if (listEquals(publicKey, _selfPublicKey)) {
return;
}
// Check if this is a new contact
final isNewContact = !_knownContactKeys.contains(contactKeyHex);
if (isNewContact) {
final newContact = Contact(
publicKey: publicKey,
name: name,
type: type,
pathLength: path.length,
path: Uint8List.fromList(
path.reversed.toList(),
), // Store path in reverse for easier use in outgoing messages
latitude: latitude,
longitude: longitude,
lastSeen: DateTime.fromMillisecondsSinceEpoch(timestamp * 1000),
);
_handleContactAdvert(newContact);
_updateDirectRepeater(newContact, snr, path);
return;
}
final existingIndex = _contacts.indexWhere(
(c) => c.publicKeyHex == contactKeyHex,
);
if (existingIndex >= 0) {
final existing = _contacts[existingIndex];
final mergedLastMessageAt = existing.lastMessageAt.isAfter(DateTime.now())
? DateTime.now()
: existing.lastMessageAt;
appLogger.info(
'Refreshing contact ${existing.name}: devicePath=${existing.pathLength}, existingOverride=${existing.pathOverride}',
tag: 'Connector',
);
// CRITICAL: Preserve user's path override when contact is refreshed from device
_contacts[existingIndex] = existing.copyWith(
latitude: hasLocation ? latitude : existing.latitude,
longitude: hasLocation ? longitude : existing.longitude,
name: hasName ? name : existing.name,
path: Uint8List.fromList(path.reversed.toList()),
pathLength: path.length,
lastMessageAt: mergedLastMessageAt,
lastSeen: DateTime.fromMillisecondsSinceEpoch(timestamp * 1000),
pathOverride: existing.pathOverride, // Preserve user's path choice
pathOverrideBytes: existing.pathOverrideBytes,
);
// Add path to history if we have a valid path
if (_pathHistoryService != null &&
_contacts[existingIndex].pathLength >= 0) {
_pathHistoryService!.handlePathUpdated(_contacts[existingIndex]);
}
_updateDirectRepeater(_contacts[existingIndex], snr, path);
appLogger.info(
'After merge: pathOverride=${_contacts[existingIndex].pathOverride}, devicePath=${_contacts[existingIndex].pathLength}',
tag: 'Connector',
);
}
}
void _updateDirectRepeater(Contact contact, double snr, Uint8List path) {
final pubkeyFirstByte = path.isNotEmpty
? path.last
: contact.publicKey.first;
_directRepeaters.removeWhere((r) => r.isStale());
//We can use adverts from chat and sensor nodes, but only if the advert has a path to get the last hop.
if ((contact.type == advTypeChat || contact.type == advTypeSensor) &&
path.isEmpty) {
notifyListeners();
return;
}
final isTracked = _directRepeaters.where(
(r) => r.pubkeyFirstByte == pubkeyFirstByte,
);
if (isTracked.isNotEmpty) {
final repeater = isTracked.first;
repeater.update(snr);
} else if (_directRepeaters.length < 5) {
_directRepeaters.add(
DirectRepeater(pubkeyFirstByte: pubkeyFirstByte, snr: snr),
);
}
notifyListeners();
}
} }
const int _phRouteMask = 0x03; const int _phRouteMask = 0x03;
+57 -14
View File
@@ -13,12 +13,22 @@ class BufferReader {
int readByte() => readBytes(1)[0]; int readByte() => readBytes(1)[0];
Uint8List readBytes(int count) { Uint8List readBytes(int count) {
if (_pointer + count > _buffer.length) {
throw RangeError(
'Attempted to read $count bytes at offset $_pointer, but only $remaining bytes remaining in buffer of length ${_buffer.length}',
);
}
final data = _buffer.sublist(_pointer, _pointer + count); final data = _buffer.sublist(_pointer, _pointer + count);
_pointer += count; _pointer += count;
return data; return data;
} }
void skipBytes(int count) { void skipBytes(int count) {
if (_pointer + count > _buffer.length) {
throw RangeError(
'Attempted to skip $count bytes at offset $_pointer, but only $remaining bytes remaining in buffer of length ${_buffer.length}',
);
}
_pointer += count; _pointer += count;
} }
@@ -151,6 +161,7 @@ const int cmdGetContactByKey = 30;
const int cmdGetChannel = 31; const int cmdGetChannel = 31;
const int cmdSetChannel = 32; const int cmdSetChannel = 32;
const int cmdSendTracePath = 36; const int cmdSendTracePath = 36;
const int cmdSetOtherParams = 38;
const int cmdGetRadioSettings = 57; const int cmdGetRadioSettings = 57;
const int cmdGetTelemetryReq = 39; const int cmdGetTelemetryReq = 39;
const int cmdGetCustomVar = 40; const int cmdGetCustomVar = 40;
@@ -166,7 +177,7 @@ const int reqTypeGetStatus = 0x01;
const int reqTypeKeepAlive = 0x02; const int reqTypeKeepAlive = 0x02;
const int reqTypeGetTelemetry = 0x03; const int reqTypeGetTelemetry = 0x03;
const int reqTypeGetAccessList = 0x05; const int reqTypeGetAccessList = 0x05;
const int reqTypeGetNeighbours = 0x06; const int reqTypeGetNeighbors = 0x06;
// Repeater response codes // Repeater response codes
const int respServerLoginOk = 0; const int respServerLoginOk = 0;
@@ -212,6 +223,30 @@ const int advTypeRepeater = 2;
const int advTypeRoom = 3; const int advTypeRoom = 3;
const int advTypeSensor = 4; const int advTypeSensor = 4;
// Payload Types
const int payloadTypeREQ =
0x00; // request (prefixed with dest/src hashes, MAC) (enc data: timestamp, blob)
const int payloadTypeRESPONSE =
0x01; // response to REQ or ANON_REQ (prefixed with dest/src hashes, MAC) (enc data: timestamp, blob)
const int payloadTypeTXTMSG =
0x02; // a plain text message (prefixed with dest/src hashes, MAC) (enc data: timestamp, text)
const int payloadTypeACK = 0x03; // a simple ack
const int payloadTypeADVERT = 0x04; // a node advertising its Identity
const int payloadTypeGRPTXT =
0x05; // an (unverified) group text message (prefixed with channel hash, MAC) (enc data: timestamp, "name: msg")
const int payloadTypeGRPDATA =
0x06; // an (unverified) group datagram (prefixed with channel hash, MAC) (enc data: timestamp, blob)
const int payloadTypeANONREQ =
0x07; // generic request (prefixed with dest_hash, ephemeral pub_key, MAC) (enc data: ...)
const int payloadTypePATH =
0x08; // returned path (prefixed with dest/src hashes, MAC) (enc data: path, extra)
const int payloadTypeTRACE = 0x09; // trace a path, collecting SNI for each hop
const int payloadTypeMULTIPART = 0x0A; // packet is one of a set of packets
const int payloadTypeCONTROL = 0x0B; // a control/discovery packet
//...
const int payloadTypeRawCustom =
0x0F; // custom packet as raw bytes, for applications with custom encryption, payloads, etc
// Sizes // Sizes
const int pubKeySize = 32; const int pubKeySize = 32;
const int maxPathSize = 64; const int maxPathSize = 64;
@@ -550,29 +585,18 @@ Uint8List buildSetChannelFrame(int channelIndex, String name, Uint8List psk) {
} }
// Build CMD_SET_RADIO_PARAMS frame // Build CMD_SET_RADIO_PARAMS frame
// Format: [cmd][freq x4][bw x4][sf][cr] (pre-v9) // Format: [cmd][freq x4][bw x4][sf][cr]
// [cmd][freq x4][bw x4][sf][cr][repeat] (firmware v9+)
// freq: frequency in Hz (300000-2500000) // freq: frequency in Hz (300000-2500000)
// bw: bandwidth in Hz (7000-500000) // bw: bandwidth in Hz (7000-500000)
// sf: spreading factor (5-12) // sf: spreading factor (5-12)
// cr: coding rate (5-8) // cr: coding rate (5-8)
// clientRepeat: enable off-grid packet repeat (firmware v9+, omit for older) Uint8List buildSetRadioParamsFrame(int freqHz, int bwHz, int sf, int cr) {
Uint8List buildSetRadioParamsFrame(
int freqHz,
int bwHz,
int sf,
int cr, {
bool? clientRepeat,
}) {
final writer = BufferWriter(); final writer = BufferWriter();
writer.writeByte(cmdSetRadioParams); writer.writeByte(cmdSetRadioParams);
writer.writeUInt32LE(freqHz); writer.writeUInt32LE(freqHz);
writer.writeUInt32LE(bwHz); writer.writeUInt32LE(bwHz);
writer.writeByte(sf); writer.writeByte(sf);
writer.writeByte(cr); writer.writeByte(cr);
if (clientRepeat != null) {
writer.writeByte(clientRepeat ? 1 : 0);
}
return writer.toBytes(); return writer.toBytes();
} }
@@ -788,3 +812,22 @@ Uint8List buildZeroHopContact(Uint8List pubKey) {
writer.writeBytes(pubKey); writer.writeBytes(pubKey);
return writer.toBytes(); return writer.toBytes();
} }
// Build CMD_SET_OTHER_PARAMS frame
// Format: [cmd][allowAutoAddContacts][allowTelemetryFlags][advertLocationPolicy][multiAcks]
Uint8List buildSetOtherParamsFrame(
bool allowAutoAddContacts,
int allowTelemetryFlags,
int advertLocationPolicy,
int multiAcks,
) {
final writer = BufferWriter();
writer.writeByte(cmdSetOtherParams);
writer.writeByte(
allowAutoAddContacts ? 0x00 : 0x01,
); // Allow Auto Add Contacts
writer.writeByte(allowTelemetryFlags); // Allow Telemetry Flags
writer.writeByte(advertLocationPolicy); // Advertisement Location Policy
writer.writeByte(multiAcks); // Multi Acknowledgements
return writer.toBytes();
}
+175 -161
View File
@@ -1,4 +1,6 @@
import 'dart:typed_data'; import 'dart:typed_data';
import 'package:meshcore_open/utils/app_logger.dart';
import '../connector/meshcore_protocol.dart'; import '../connector/meshcore_protocol.dart';
class CayenneLpp { class CayenneLpp {
@@ -84,180 +86,192 @@ class CayenneLpp {
static List<Map<String, dynamic>> parse(Uint8List bytes) { static List<Map<String, dynamic>> parse(Uint8List bytes) {
final buffer = BufferReader(bytes); final buffer = BufferReader(bytes);
final telemetry = <Map<String, dynamic>>[]; final telemetry = <Map<String, dynamic>>[];
try {
while (buffer.remaining >= 2) {
final channel = buffer.readUInt8();
final type = buffer.readUInt8();
while (buffer.remaining >= 2) { if (channel == 0 && type == 0) {
final channel = buffer.readUInt8(); break;
final type = buffer.readUInt8(); }
if (channel == 0 && type == 0) { switch (type) {
break; case lppGenericSensor:
} telemetry.add({
'channel': channel,
switch (type) { 'type': type,
case lppGenericSensor: 'value': buffer.readUInt32BE(),
telemetry.add({ });
'channel': channel, break;
'type': type, case lppLuminosity:
'value': buffer.readUInt32BE(), telemetry.add({
}); 'channel': channel,
break; 'type': type,
case lppLuminosity: 'value': buffer.readUInt16BE(),
telemetry.add({ });
'channel': channel, break;
'type': type, case lppPresence:
'value': buffer.readUInt16BE(), telemetry.add({
}); 'channel': channel,
break; 'type': type,
case lppPresence: 'value': buffer.readUInt8(),
telemetry.add({ });
'channel': channel, break;
'type': type, case lppTemperature:
'value': buffer.readUInt8(), telemetry.add({
}); 'channel': channel,
break; 'type': type,
case lppTemperature: 'value': buffer.readInt16BE() / 10,
telemetry.add({ });
'channel': channel, break;
'type': type, case lppRelativeHumidity:
'value': buffer.readInt16BE() / 10, telemetry.add({
}); 'channel': channel,
break; 'type': type,
case lppRelativeHumidity: 'value': buffer.readUInt8() / 2,
telemetry.add({ });
'channel': channel, break;
'type': type, case lppBarometricPressure:
'value': buffer.readUInt8() / 2, telemetry.add({
}); 'channel': channel,
break; 'type': type,
case lppBarometricPressure: 'value': buffer.readUInt16BE() / 10,
telemetry.add({ });
'channel': channel, break;
'type': type, case lppVoltage:
'value': buffer.readUInt16BE() / 10, telemetry.add({
}); 'channel': channel,
break; 'type': type,
case lppVoltage: 'value': buffer.readInt16BE() / 100,
telemetry.add({ });
'channel': channel, break;
'type': type, case lppCurrent:
'value': buffer.readInt16BE() / 100, telemetry.add({
}); 'channel': channel,
break; 'type': type,
case lppCurrent: 'value': buffer.readInt16BE() / 1000,
telemetry.add({ });
'channel': channel, break;
'type': type, case lppPercentage:
'value': buffer.readInt16BE() / 1000, telemetry.add({
}); 'channel': channel,
break; 'type': type,
case lppPercentage: 'value': buffer.readUInt8(),
telemetry.add({ });
'channel': channel, break;
'type': type, case lppConcentration:
'value': buffer.readUInt8(), telemetry.add({
}); 'channel': channel,
break; 'type': type,
case lppConcentration: 'value': buffer.readUInt16BE(),
telemetry.add({ });
'channel': channel, break;
'type': type, case lppPower:
'value': buffer.readUInt16BE(), telemetry.add({
}); 'channel': channel,
break; 'type': type,
case lppPower: 'value': buffer.readUInt16BE(),
telemetry.add({ });
'channel': channel, break;
'type': type, case lppGps:
'value': buffer.readUInt16BE(), telemetry.add({
}); 'channel': channel,
break; 'type': type,
case lppGps: 'value': {
telemetry.add({ 'latitude': buffer.readInt24BE() / 10000,
'channel': channel, 'longitude': buffer.readInt24BE() / 10000,
'type': type, 'altitude': buffer.readInt24BE() / 100,
'value': { },
'latitude': buffer.readInt24BE() / 10000, });
'longitude': buffer.readInt24BE() / 10000, break;
'altitude': buffer.readInt24BE() / 100, default:
}, return telemetry;
}); }
break;
default:
return telemetry;
} }
return telemetry;
} catch (e) {
// Handle parsing errors, possibly due to malformed data
appLogger.error('Error parsing Cayenne LPP data: $e');
// Return any telemetry parsed so far to preserve partial data
return telemetry;
} }
return telemetry;
} }
static List<Map<String, dynamic>> parseByChannel(Uint8List bytes) { static List<Map<String, dynamic>> parseByChannel(Uint8List bytes) {
final buffer = BufferReader(bytes); final buffer = BufferReader(bytes);
final Map<int, Map<String, dynamic>> channels = {}; final Map<int, Map<String, dynamic>> channels = {};
try {
while (buffer.remaining >= 2) {
final channel = buffer.readUInt8();
final type = buffer.readUInt8();
while (buffer.remaining >= 2) { // Optional: stop on padding (00 00)
final channel = buffer.readUInt8(); if (channel == 0 && type == 0) {
final type = buffer.readUInt8(); break;
}
// Optional: stop on padding (00 00) final channelData = channels.putIfAbsent(
if (channel == 0 && type == 0) { channel,
break; () => {'channel': channel, 'values': <String, dynamic>{}},
);
switch (type) {
case lppGenericSensor:
channelData['values']['generic'] = buffer.readUInt32BE();
break;
case lppLuminosity:
channelData['values']['luminosity'] = buffer.readUInt16BE();
break;
case lppPresence:
channelData['values']['presence'] = buffer.readUInt8() != 0;
break;
case lppTemperature:
channelData['values']['temperature'] = buffer.readInt16BE() / 10.0;
break;
case lppRelativeHumidity:
channelData['values']['humidity'] = buffer.readUInt8() / 2.0;
break;
case lppBarometricPressure:
channelData['values']['pressure'] = buffer.readUInt16BE() / 10.0;
break;
case lppVoltage:
channelData['values']['voltage'] = buffer.readInt16BE() / 100.0;
break;
case lppCurrent:
channelData['values']['current'] = buffer.readInt16BE() / 1000.0;
break;
case lppPercentage:
channelData['values']['percentage'] = buffer.readUInt8();
break;
case lppConcentration:
channelData['values']['concentration'] = buffer.readUInt16BE();
break;
case lppPower:
channelData['values']['power'] = buffer.readUInt16BE();
break;
case lppGps:
channelData['values']['gps'] = {
'latitude': buffer.readInt24BE() / 10000.0,
'longitude': buffer.readInt24BE() / 10000.0,
'altitude': buffer.readInt24BE() / 100.0,
};
break;
// Add more types as needed...
default:
//Stopped parsing to avoid misalignment
return channels.values.toList();
}
} }
final channelData = channels.putIfAbsent( final List<Map<String, dynamic>> channelsOut = channels.values.toList();
channel, channelsOut.sort((a, b) => a['channel'].compareTo(b['channel']));
() => {'channel': channel, 'values': <String, dynamic>{}}, return channelsOut;
); } catch (e) {
// Handle parsing errors, possibly due to malformed data
switch (type) { appLogger.error('Error parsing Cayenne LPP data: $e');
case lppGenericSensor: return <
channelData['values']['generic'] = buffer.readUInt32BE(); Map<String, dynamic>
break; >[]; // Return an empty list on error to avoid crashing the app
case lppLuminosity:
channelData['values']['luminosity'] = buffer.readUInt16BE();
break;
case lppPresence:
channelData['values']['presence'] = buffer.readUInt8() != 0;
break;
case lppTemperature:
channelData['values']['temperature'] = buffer.readInt16BE() / 10.0;
break;
case lppRelativeHumidity:
channelData['values']['humidity'] = buffer.readUInt8() / 2.0;
break;
case lppBarometricPressure:
channelData['values']['pressure'] = buffer.readUInt16BE() / 10.0;
break;
case lppVoltage:
channelData['values']['voltage'] = buffer.readInt16BE() / 100.0;
break;
case lppCurrent:
channelData['values']['current'] = buffer.readInt16BE() / 1000.0;
break;
case lppPercentage:
channelData['values']['percentage'] = buffer.readUInt8();
break;
case lppConcentration:
channelData['values']['concentration'] = buffer.readUInt16BE();
break;
case lppPower:
channelData['values']['power'] = buffer.readUInt16BE();
break;
case lppGps:
channelData['values']['gps'] = {
'latitude': buffer.readInt24BE() / 10000.0,
'longitude': buffer.readInt24BE() / 10000.0,
'altitude': buffer.readInt24BE() / 100.0,
};
break;
// Add more types as needed...
default:
// Unknown type: skip or handle error?
continue;
}
} }
final List<Map<String, dynamic>> channelsOut = channels.values.toList();
channelsOut.sort((a, b) => a['channel'].compareTo(b['channel']));
return channelsOut;
} }
} }
+10 -6
View File
@@ -131,6 +131,9 @@
"settings_infoContactsCount": "Брой контакти", "settings_infoContactsCount": "Брой контакти",
"settings_infoChannelCount": "Брой канали", "settings_infoChannelCount": "Брой канали",
"settings_presets": "Предварителни настройки", "settings_presets": "Предварителни настройки",
"settings_preset915Mhz": "915 MHz",
"settings_preset868Mhz": "868 MHz",
"settings_preset433Mhz": "433 MHz",
"settings_frequency": "Честота (MHz)", "settings_frequency": "Честота (MHz)",
"settings_frequencyHelper": "300.0 - 2500.0", "settings_frequencyHelper": "300.0 - 2500.0",
"settings_frequencyInvalid": "Невалидна честота (300-2500 MHz)", "settings_frequencyInvalid": "Невалидна честота (300-2500 MHz)",
@@ -140,6 +143,8 @@
"settings_txPower": "TX Мощност (dBm)", "settings_txPower": "TX Мощност (dBm)",
"settings_txPowerHelper": "0 - 22", "settings_txPowerHelper": "0 - 22",
"settings_txPowerInvalid": "Невалидна мощност на TX (0-22 dBm)", "settings_txPowerInvalid": "Невалидна мощност на TX (0-22 dBm)",
"settings_longRange": "Дълъг обхват",
"settings_fastSpeed": "Бърза скорост",
"settings_error": "Грешка: {message}", "settings_error": "Грешка: {message}",
"@settings_error": { "@settings_error": {
"placeholders": { "placeholders": {
@@ -1351,12 +1356,12 @@
} }
} }
}, },
"repeater_neighboursSubtitle": "Преглед на съседни възли с нулев скок.", "repeater_neighborsSubtitle": "Преглед на съседни възли с нулев скок.",
"repeater_neighbours": "Съседи", "repeater_neighbors": "Съседи",
"neighbors_receivedData": "Получени данни за съседи", "neighbors_receivedData": "Получени данни за съседи",
"neighbors_requestTimedOut": "Съседите поискат изтичане на време.", "neighbors_requestTimedOut": "Съседите поискат изтичане на време.",
"neighbors_errorLoading": "Грешка при зареждане на съседи: {error}", "neighbors_errorLoading": "Грешка при зареждане на съседи: {error}",
"neighbors_repeatersNeighbours": "Повторители Съседи", "neighbors_repeatersNeighbors": "Повторители Съседи",
"neighbors_noData": "Няма налични данни за съседи.", "neighbors_noData": "Няма налични данни за съседи.",
"channels_createPrivateChannel": "Създай Частен Канал", "channels_createPrivateChannel": "Създай Частен Канал",
"channels_joinPrivateChannel": "Присъедини се към Частен Канал", "channels_joinPrivateChannel": "Присъедини се към Частен Канал",
@@ -1594,7 +1599,6 @@
"scanner_bluetoothOff": "Bluetooth е изключен.", "scanner_bluetoothOff": "Bluetooth е изключен.",
"scanner_enableBluetooth": "Активирайте Bluetooth", "scanner_enableBluetooth": "Активирайте Bluetooth",
"scanner_bluetoothOffMessage": "Моля, активирайте Bluetooth, за да сканирате за устройства.", "scanner_bluetoothOffMessage": "Моля, активирайте Bluetooth, за да сканирате за устройства.",
"settings_clientRepeatSubtitle": "Позволете на това устройство да предава пакети към мрежата за други устройства.", "snrIndicator_lastSeen": "Последно видян",
"settings_clientRepeatFreqWarning": "За повторение извън мрежата са необходими честоти от 433, 869 или 918 MHz.", "snrIndicator_nearByRepeaters": "Близки повтарящи се устройства"
"settings_clientRepeat": "Без електричество – повторение"
} }
+10 -6
View File
@@ -131,6 +131,9 @@
"settings_infoContactsCount": "Anzahl Kontakte", "settings_infoContactsCount": "Anzahl Kontakte",
"settings_infoChannelCount": "Anzahl Kanäle", "settings_infoChannelCount": "Anzahl Kanäle",
"settings_presets": "Voreinstellungen", "settings_presets": "Voreinstellungen",
"settings_preset915Mhz": "915 MHz",
"settings_preset868Mhz": "868 MHz",
"settings_preset433Mhz": "433 MHz",
"settings_frequency": "Frequenz (MHz)", "settings_frequency": "Frequenz (MHz)",
"settings_frequencyHelper": "300,00 - 2.500,00", "settings_frequencyHelper": "300,00 - 2.500,00",
"settings_frequencyInvalid": "Ungültige Frequenz (300-2500 MHz)", "settings_frequencyInvalid": "Ungültige Frequenz (300-2500 MHz)",
@@ -140,6 +143,8 @@
"settings_txPower": "TX-Leistung (dBm)", "settings_txPower": "TX-Leistung (dBm)",
"settings_txPowerHelper": "0 - 22", "settings_txPowerHelper": "0 - 22",
"settings_txPowerInvalid": "Ungültige TX-Leistung (0-22 dBm)", "settings_txPowerInvalid": "Ungültige TX-Leistung (0-22 dBm)",
"settings_longRange": "Grosse Reichweite",
"settings_fastSpeed": "Schnelle Geschwindigkeit",
"settings_error": "Fehler: {message}", "settings_error": "Fehler: {message}",
"@settings_error": { "@settings_error": {
"placeholders": { "placeholders": {
@@ -1351,12 +1356,12 @@
} }
} }
}, },
"repeater_neighbours": "Nachbarn", "repeater_neighbors": "Nachbarn",
"repeater_neighboursSubtitle": "Anzahl der Hop-Nachbarn anzeigen.", "repeater_neighborsSubtitle": "Anzahl der Hop-Nachbarn anzeigen.",
"neighbors_receivedData": "Empfangene Nachbarsdaten", "neighbors_receivedData": "Empfangene Nachbarsdaten",
"neighbors_requestTimedOut": "Anfrage durch Timeout fehlgeschlagen.", "neighbors_requestTimedOut": "Anfrage durch Timeout fehlgeschlagen.",
"neighbors_errorLoading": "Fehler beim Laden der Nachbarn: {error}", "neighbors_errorLoading": "Fehler beim Laden der Nachbarn: {error}",
"neighbors_repeatersNeighbours": "Nachbarn", "neighbors_repeatersNeighbors": "Nachbarn",
"neighbors_noData": "Keine Nachbarsdaten verfügbar.", "neighbors_noData": "Keine Nachbarsdaten verfügbar.",
"channels_joinPrivateChannel": "Treten Sie einem privaten Kanal bei", "channels_joinPrivateChannel": "Treten Sie einem privaten Kanal bei",
"channels_joinPrivateChannelDesc": "Manuelle Eingabe eines geheimen Schlüssels.", "channels_joinPrivateChannelDesc": "Manuelle Eingabe eines geheimen Schlüssels.",
@@ -1622,7 +1627,6 @@
"scanner_bluetoothOffMessage": "Bitte aktivieren Sie Bluetooth, um nach Geräten zu suchen.", "scanner_bluetoothOffMessage": "Bitte aktivieren Sie Bluetooth, um nach Geräten zu suchen.",
"scanner_bluetoothOff": "Bluetooth ist deaktiviert.", "scanner_bluetoothOff": "Bluetooth ist deaktiviert.",
"scanner_enableBluetooth": "Bluetooth aktivieren", "scanner_enableBluetooth": "Bluetooth aktivieren",
"settings_clientRepeat": "Wiederholung, ohne Stromanschluss", "snrIndicator_lastSeen": "Zuletzt gesehen",
"settings_clientRepeatFreqWarning": "Die Kommunikation ohne Stromversorgung erfordert Frequenzen von 433, 869 oder 918 MHz.", "snrIndicator_nearByRepeaters": "In der Nähe befindliche Repeater"
"settings_clientRepeatSubtitle": "Ermöglichen Sie diesem Gerät, Mesh-Pakete für andere zu wiederholen."
} }
+178 -405
View File
File diff suppressed because it is too large Load Diff
+10 -6
View File
@@ -131,6 +131,9 @@
"settings_infoContactsCount": "Número de contactos", "settings_infoContactsCount": "Número de contactos",
"settings_infoChannelCount": "Número de canales", "settings_infoChannelCount": "Número de canales",
"settings_presets": "Preajustes", "settings_presets": "Preajustes",
"settings_preset915Mhz": "915 MHz",
"settings_preset868Mhz": "868 MHz",
"settings_preset433Mhz": "433 MHz",
"settings_frequency": "Frecuencia (MHz)", "settings_frequency": "Frecuencia (MHz)",
"settings_frequencyHelper": "300,0 - 2500,0", "settings_frequencyHelper": "300,0 - 2500,0",
"settings_frequencyInvalid": "Frecuencia inválida (300-2500 MHz)", "settings_frequencyInvalid": "Frecuencia inválida (300-2500 MHz)",
@@ -140,6 +143,8 @@
"settings_txPower": "TX Potencia (dBm)", "settings_txPower": "TX Potencia (dBm)",
"settings_txPowerHelper": "0 - 22", "settings_txPowerHelper": "0 - 22",
"settings_txPowerInvalid": "Potencia de TX inválida (0-22 dBm)", "settings_txPowerInvalid": "Potencia de TX inválida (0-22 dBm)",
"settings_longRange": "Largo Alcance",
"settings_fastSpeed": "Velocidad Rápida",
"settings_error": "Error: {message}", "settings_error": "Error: {message}",
"@settings_error": { "@settings_error": {
"placeholders": { "placeholders": {
@@ -1351,12 +1356,12 @@
} }
} }
}, },
"repeater_neighbours": "Vecinos", "repeater_neighbors": "Vecinos",
"repeater_neighboursSubtitle": "Ver vecinos de salto cero.", "repeater_neighborsSubtitle": "Ver vecinos de salto cero.",
"neighbors_receivedData": "Recibidas Datos de Vecinos", "neighbors_receivedData": "Recibidas Datos de Vecinos",
"neighbors_requestTimedOut": "Los vecinos solicitan que se desconecte.", "neighbors_requestTimedOut": "Los vecinos solicitan que se desconecte.",
"neighbors_errorLoading": "Error al cargar vecinos: {error}", "neighbors_errorLoading": "Error al cargar vecinos: {error}",
"neighbors_repeatersNeighbours": "Repetidores Vecinos", "neighbors_repeatersNeighbors": "Repetidores Vecinos",
"neighbors_noData": "No hay datos de vecinos disponibles.", "neighbors_noData": "No hay datos de vecinos disponibles.",
"channels_joinPrivateChannel": "Únete a un Canal Privado", "channels_joinPrivateChannel": "Únete a un Canal Privado",
"channels_createPrivateChannel": "Crear un Canal Privado", "channels_createPrivateChannel": "Crear un Canal Privado",
@@ -1622,7 +1627,6 @@
"scanner_bluetoothOffMessage": "Por favor, active el Bluetooth para escanear dispositivos.", "scanner_bluetoothOffMessage": "Por favor, active el Bluetooth para escanear dispositivos.",
"scanner_bluetoothOff": "Bluetooth está desactivado.", "scanner_bluetoothOff": "Bluetooth está desactivado.",
"scanner_enableBluetooth": "Habilitar Bluetooth", "scanner_enableBluetooth": "Habilitar Bluetooth",
"settings_clientRepeatFreqWarning": "Para la comunicación fuera de la red, se requiere una frecuencia de 433, 869 o 918 MHz.", "snrIndicator_nearByRepeaters": "Repetidores cercanos",
"settings_clientRepeat": "Repetir sin conexión", "snrIndicator_lastSeen": "Visto por última vez"
"settings_clientRepeatSubtitle": "Permita que este dispositivo repita los paquetes de red para otros usuarios."
} }
+10 -6
View File
@@ -131,6 +131,9 @@
"settings_infoContactsCount": "Nombre de contacts", "settings_infoContactsCount": "Nombre de contacts",
"settings_infoChannelCount": "Nombre de canaux", "settings_infoChannelCount": "Nombre de canaux",
"settings_presets": "Préréglages", "settings_presets": "Préréglages",
"settings_preset915Mhz": "915 MHz",
"settings_preset868Mhz": "868 MHz",
"settings_preset433Mhz": "433 MHz",
"settings_frequency": "Fréquence (MHz)", "settings_frequency": "Fréquence (MHz)",
"settings_frequencyHelper": "300,0 - 2 500,0", "settings_frequencyHelper": "300,0 - 2 500,0",
"settings_frequencyInvalid": "Fréquence invalide (300-2500 MHz)", "settings_frequencyInvalid": "Fréquence invalide (300-2500 MHz)",
@@ -140,6 +143,8 @@
"settings_txPower": "TX Puissance (dBm)", "settings_txPower": "TX Puissance (dBm)",
"settings_txPowerHelper": "0 - 22", "settings_txPowerHelper": "0 - 22",
"settings_txPowerInvalid": "Puissance TX invalide (0-22 dBm)", "settings_txPowerInvalid": "Puissance TX invalide (0-22 dBm)",
"settings_longRange": "Portée Longue",
"settings_fastSpeed": "Vitesse Rapide",
"settings_error": "Erreur : {message}", "settings_error": "Erreur : {message}",
"@settings_error": { "@settings_error": {
"placeholders": { "placeholders": {
@@ -1351,12 +1356,12 @@
} }
} }
}, },
"repeater_neighbours": "Voisins", "repeater_neighbors": "Voisins",
"repeater_neighboursSubtitle": "Afficher les voisins de saut nuls.", "repeater_neighborsSubtitle": "Afficher les voisins de saut nuls.",
"neighbors_receivedData": "Données des voisins reçues", "neighbors_receivedData": "Données des voisins reçues",
"neighbors_requestTimedOut": "Les voisins demandent un délai.", "neighbors_requestTimedOut": "Les voisins demandent un délai.",
"neighbors_errorLoading": "Erreur lors du chargement des voisins : {error}", "neighbors_errorLoading": "Erreur lors du chargement des voisins : {error}",
"neighbors_repeatersNeighbours": "Répéteurs Voisins", "neighbors_repeatersNeighbors": "Répéteurs Voisins",
"neighbors_noData": "Aucune donnée concernant les voisins disponible.", "neighbors_noData": "Aucune donnée concernant les voisins disponible.",
"channels_createPrivateChannelDesc": "Sécurisé avec une clé secrète.", "channels_createPrivateChannelDesc": "Sécurisé avec une clé secrète.",
"channels_joinPrivateChannel": "Rejoindre un Canal Privé", "channels_joinPrivateChannel": "Rejoindre un Canal Privé",
@@ -1594,7 +1599,6 @@
"scanner_bluetoothOffMessage": "Veuillez activer le Bluetooth pour rechercher des appareils.", "scanner_bluetoothOffMessage": "Veuillez activer le Bluetooth pour rechercher des appareils.",
"scanner_bluetoothOff": "Le Bluetooth est désactivé.", "scanner_bluetoothOff": "Le Bluetooth est désactivé.",
"scanner_enableBluetooth": "Activer le Bluetooth", "scanner_enableBluetooth": "Activer le Bluetooth",
"settings_clientRepeatFreqWarning": "Pour les transmissions hors réseau, il est nécessaire d'utiliser les fréquences de 433, 869 ou 918 MHz.", "snrIndicator_lastSeen": "Dernière fois vu",
"settings_clientRepeatSubtitle": "Permettez à cet appareil de répéter les paquets de données pour les autres.", "snrIndicator_nearByRepeaters": "Répéteurs à proximité"
"settings_clientRepeat": "Répétition hors réseau"
} }
+10 -6
View File
@@ -131,6 +131,9 @@
"settings_infoContactsCount": "Numero contatti", "settings_infoContactsCount": "Numero contatti",
"settings_infoChannelCount": "Numero Canale", "settings_infoChannelCount": "Numero Canale",
"settings_presets": "Preset", "settings_presets": "Preset",
"settings_preset915Mhz": "915 MHz",
"settings_preset868Mhz": "868 MHz",
"settings_preset433Mhz": "433 MHz",
"settings_frequency": "Frequenza (MHz)", "settings_frequency": "Frequenza (MHz)",
"settings_frequencyHelper": "300,0 - 2500,0", "settings_frequencyHelper": "300,0 - 2500,0",
"settings_frequencyInvalid": "Frequenza non valida (300-2500 MHz)", "settings_frequencyInvalid": "Frequenza non valida (300-2500 MHz)",
@@ -140,6 +143,8 @@
"settings_txPower": "TX Potenza (dBm)", "settings_txPower": "TX Potenza (dBm)",
"settings_txPowerHelper": "0 - 22", "settings_txPowerHelper": "0 - 22",
"settings_txPowerInvalid": "Potere TX non valido (0-22 dBm)", "settings_txPowerInvalid": "Potere TX non valido (0-22 dBm)",
"settings_longRange": "Lungo Raggio",
"settings_fastSpeed": "Velocità Rapida",
"settings_error": "Errore: {message}", "settings_error": "Errore: {message}",
"@settings_error": { "@settings_error": {
"placeholders": { "placeholders": {
@@ -1351,12 +1356,12 @@
} }
} }
}, },
"repeater_neighbours": "Vicini", "repeater_neighbors": "Vicini",
"repeater_neighboursSubtitle": "Visualizza vicini di salto pari a zero.", "repeater_neighborsSubtitle": "Visualizza vicini di salto pari a zero.",
"neighbors_receivedData": "Ricevute dati vicini", "neighbors_receivedData": "Ricevute dati vicini",
"neighbors_requestTimedOut": "I vicini richiedono un timeout.", "neighbors_requestTimedOut": "I vicini richiedono un timeout.",
"neighbors_errorLoading": "Errore nel caricamento dei vicini: {error}", "neighbors_errorLoading": "Errore nel caricamento dei vicini: {error}",
"neighbors_repeatersNeighbours": "Ripetitori Vicini", "neighbors_repeatersNeighbors": "Ripetitori Vicini",
"neighbors_noData": "Nessun dato sugli vicini disponibile.", "neighbors_noData": "Nessun dato sugli vicini disponibile.",
"channels_createPrivateChannel": "Crea un Canale Privato", "channels_createPrivateChannel": "Crea un Canale Privato",
"channels_createPrivateChannelDesc": "Protetta con una chiave segreta.", "channels_createPrivateChannelDesc": "Protetta con una chiave segreta.",
@@ -1594,7 +1599,6 @@
"scanner_bluetoothOff": "Il Bluetooth è disattivato.", "scanner_bluetoothOff": "Il Bluetooth è disattivato.",
"scanner_bluetoothOffMessage": "Si prega di attivare il Bluetooth per effettuare la scansione dei dispositivi.", "scanner_bluetoothOffMessage": "Si prega di attivare il Bluetooth per effettuare la scansione dei dispositivi.",
"scanner_enableBluetooth": "Abilita il Bluetooth", "scanner_enableBluetooth": "Abilita il Bluetooth",
"settings_clientRepeat": "Ripetizione \"fuori dalla rete\"", "snrIndicator_nearByRepeaters": "Ripetitori vicini",
"settings_clientRepeatFreqWarning": "Per la comunicazione fuori rete, è necessario utilizzare frequenze di 433, 869 o 918 MHz.", "snrIndicator_lastSeen": "Ultimo accesso"
"settings_clientRepeatSubtitle": "Permetti a questo dispositivo di ripetere i pacchetti di rete per gli altri."
} }
+46 -22
View File
@@ -748,6 +748,24 @@ abstract class AppLocalizations {
/// **'Presets'** /// **'Presets'**
String get settings_presets; String get settings_presets;
/// No description provided for @settings_preset915Mhz.
///
/// In en, this message translates to:
/// **'915 MHz'**
String get settings_preset915Mhz;
/// No description provided for @settings_preset868Mhz.
///
/// In en, this message translates to:
/// **'868 MHz'**
String get settings_preset868Mhz;
/// No description provided for @settings_preset433Mhz.
///
/// In en, this message translates to:
/// **'433 MHz'**
String get settings_preset433Mhz;
/// No description provided for @settings_frequency. /// No description provided for @settings_frequency.
/// ///
/// In en, this message translates to: /// In en, this message translates to:
@@ -802,23 +820,17 @@ abstract class AppLocalizations {
/// **'Invalid TX power (0-22 dBm)'** /// **'Invalid TX power (0-22 dBm)'**
String get settings_txPowerInvalid; String get settings_txPowerInvalid;
/// No description provided for @settings_clientRepeat. /// No description provided for @settings_longRange.
/// ///
/// In en, this message translates to: /// In en, this message translates to:
/// **'Off-Grid Repeat'** /// **'Long Range'**
String get settings_clientRepeat; String get settings_longRange;
/// No description provided for @settings_clientRepeatSubtitle. /// No description provided for @settings_fastSpeed.
/// ///
/// In en, this message translates to: /// In en, this message translates to:
/// **'Allow this device to repeat mesh packets for others'** /// **'Fast Speed'**
String get settings_clientRepeatSubtitle; String get settings_fastSpeed;
/// No description provided for @settings_clientRepeatFreqWarning.
///
/// In en, this message translates to:
/// **'Off-grid repeat requires 433, 869, or 918 MHz frequency'**
String get settings_clientRepeatFreqWarning;
/// No description provided for @settings_error. /// No description provided for @settings_error.
/// ///
@@ -3027,17 +3039,17 @@ abstract class AppLocalizations {
/// **'Send commands to the repeater'** /// **'Send commands to the repeater'**
String get repeater_cliSubtitle; String get repeater_cliSubtitle;
/// No description provided for @repeater_neighbours. /// No description provided for @repeater_neighbors.
/// ///
/// In en, this message translates to: /// In en, this message translates to:
/// **'Neighbors'** /// **'Neighbors'**
String get repeater_neighbours; String get repeater_neighbors;
/// No description provided for @repeater_neighboursSubtitle. /// No description provided for @repeater_neighborsSubtitle.
/// ///
/// In en, this message translates to: /// In en, this message translates to:
/// **'View zero hop neighbors.'** /// **'View zero hop neighbors.'**
String get repeater_neighboursSubtitle; String get repeater_neighborsSubtitle;
/// No description provided for @repeater_settings. /// No description provided for @repeater_settings.
/// ///
@@ -4181,13 +4193,13 @@ abstract class AppLocalizations {
/// No description provided for @neighbors_receivedData. /// No description provided for @neighbors_receivedData.
/// ///
/// In en, this message translates to: /// In en, this message translates to:
/// **'Received Neighbours Data'** /// **'Received Neighbors Data'**
String get neighbors_receivedData; String get neighbors_receivedData;
/// No description provided for @neighbors_requestTimedOut. /// No description provided for @neighbors_requestTimedOut.
/// ///
/// In en, this message translates to: /// In en, this message translates to:
/// **'Neighbours request timed out.'** /// **'Neighbors request timed out.'**
String get neighbors_requestTimedOut; String get neighbors_requestTimedOut;
/// No description provided for @neighbors_errorLoading. /// No description provided for @neighbors_errorLoading.
@@ -4196,16 +4208,16 @@ abstract class AppLocalizations {
/// **'Error loading neighbors: {error}'** /// **'Error loading neighbors: {error}'**
String neighbors_errorLoading(String error); String neighbors_errorLoading(String error);
/// No description provided for @neighbors_repeatersNeighbours. /// No description provided for @neighbors_repeatersNeighbors.
/// ///
/// In en, this message translates to: /// In en, this message translates to:
/// **'Repeaters Neighbours'** /// **'Repeaters Neighbors'**
String get neighbors_repeatersNeighbours; String get neighbors_repeatersNeighbors;
/// No description provided for @neighbors_noData. /// No description provided for @neighbors_noData.
/// ///
/// In en, this message translates to: /// In en, this message translates to:
/// **'No neighbours data available.'** /// **'No neighbors data available.'**
String get neighbors_noData; String get neighbors_noData;
/// No description provided for @neighbors_unknownContact. /// No description provided for @neighbors_unknownContact.
@@ -5023,6 +5035,18 @@ abstract class AppLocalizations {
/// In en, this message translates to: /// In en, this message translates to:
/// **'meshcore-open GPX map data export'** /// **'meshcore-open GPX map data export'**
String get settings_gpxExportShareSubject; String get settings_gpxExportShareSubject;
/// No description provided for @snrIndicator_nearByRepeaters.
///
/// In en, this message translates to:
/// **'Nearby Repeaters'**
String get snrIndicator_nearByRepeaters;
/// No description provided for @snrIndicator_lastSeen.
///
/// In en, this message translates to:
/// **'Last seen'**
String get snrIndicator_lastSeen;
} }
class _AppLocalizationsDelegate class _AppLocalizationsDelegate
+20 -10
View File
@@ -350,6 +350,15 @@ class AppLocalizationsBg extends AppLocalizations {
@override @override
String get settings_presets => 'Предварителни настройки'; String get settings_presets => 'Предварителни настройки';
@override
String get settings_preset915Mhz => '915 MHz';
@override
String get settings_preset868Mhz => '868 MHz';
@override
String get settings_preset433Mhz => '433 MHz';
@override @override
String get settings_frequency => 'Честота (MHz)'; String get settings_frequency => 'Честота (MHz)';
@@ -378,15 +387,10 @@ class AppLocalizationsBg extends AppLocalizations {
String get settings_txPowerInvalid => 'Невалидна мощност на TX (0-22 dBm)'; String get settings_txPowerInvalid => 'Невалидна мощност на TX (0-22 dBm)';
@override @override
String get settings_clientRepeat => 'Без електричество – повторение'; String get settings_longRange => 'Дълъг обхват';
@override @override
String get settings_clientRepeatSubtitle => String get settings_fastSpeed => 'Бърза скорост';
'Позволете на това устройство да предава пакети към мрежата за други устройства.';
@override
String get settings_clientRepeatFreqWarning =>
'За повторение извън мрежата са необходими честоти от 433, 869 или 918 MHz.';
@override @override
String settings_error(String message) { String settings_error(String message) {
@@ -1677,10 +1681,10 @@ class AppLocalizationsBg extends AppLocalizations {
String get repeater_cliSubtitle => 'Изпрати команди към ретранслатора'; String get repeater_cliSubtitle => 'Изпрати команди към ретранслатора';
@override @override
String get repeater_neighbours => 'Съседи'; String get repeater_neighbors => 'Съседи';
@override @override
String get repeater_neighboursSubtitle => String get repeater_neighborsSubtitle =>
'Преглед на съседни възли с нулев скок.'; 'Преглед на съседни възли с нулев скок.';
@override @override
@@ -2380,7 +2384,7 @@ class AppLocalizationsBg extends AppLocalizations {
} }
@override @override
String get neighbors_repeatersNeighbours => 'Повторители Съседи'; String get neighbors_repeatersNeighbors => 'Повторители Съседи';
@override @override
String get neighbors_noData => 'Няма налични данни за съседи.'; String get neighbors_noData => 'Няма налични данни за съседи.';
@@ -2890,4 +2894,10 @@ class AppLocalizationsBg extends AppLocalizations {
@override @override
String get settings_gpxExportShareSubject => String get settings_gpxExportShareSubject =>
'meshcore-open износ на данни за карта в формат GPX'; 'meshcore-open износ на данни за карта в формат GPX';
@override
String get snrIndicator_nearByRepeaters => 'Близки повтарящи се устройства';
@override
String get snrIndicator_lastSeen => 'Последно видян';
} }
+20 -10
View File
@@ -344,6 +344,15 @@ class AppLocalizationsDe extends AppLocalizations {
@override @override
String get settings_presets => 'Voreinstellungen'; String get settings_presets => 'Voreinstellungen';
@override
String get settings_preset915Mhz => '915 MHz';
@override
String get settings_preset868Mhz => '868 MHz';
@override
String get settings_preset433Mhz => '433 MHz';
@override @override
String get settings_frequency => 'Frequenz (MHz)'; String get settings_frequency => 'Frequenz (MHz)';
@@ -372,15 +381,10 @@ class AppLocalizationsDe extends AppLocalizations {
String get settings_txPowerInvalid => 'Ungültige TX-Leistung (0-22 dBm)'; String get settings_txPowerInvalid => 'Ungültige TX-Leistung (0-22 dBm)';
@override @override
String get settings_clientRepeat => 'Wiederholung, ohne Stromanschluss'; String get settings_longRange => 'Grosse Reichweite';
@override @override
String get settings_clientRepeatSubtitle => String get settings_fastSpeed => 'Schnelle Geschwindigkeit';
'Ermöglichen Sie diesem Gerät, Mesh-Pakete für andere zu wiederholen.';
@override
String get settings_clientRepeatFreqWarning =>
'Die Kommunikation ohne Stromversorgung erfordert Frequenzen von 433, 869 oder 918 MHz.';
@override @override
String settings_error(String message) { String settings_error(String message) {
@@ -1676,10 +1680,10 @@ class AppLocalizationsDe extends AppLocalizations {
String get repeater_cliSubtitle => 'Sende Befehle an den Repeater'; String get repeater_cliSubtitle => 'Sende Befehle an den Repeater';
@override @override
String get repeater_neighbours => 'Nachbarn'; String get repeater_neighbors => 'Nachbarn';
@override @override
String get repeater_neighboursSubtitle => 'Anzahl der Hop-Nachbarn anzeigen.'; String get repeater_neighborsSubtitle => 'Anzahl der Hop-Nachbarn anzeigen.';
@override @override
String get repeater_settings => 'Einstellungen'; String get repeater_settings => 'Einstellungen';
@@ -2382,7 +2386,7 @@ class AppLocalizationsDe extends AppLocalizations {
} }
@override @override
String get neighbors_repeatersNeighbours => 'Nachbarn'; String get neighbors_repeatersNeighbors => 'Nachbarn';
@override @override
String get neighbors_noData => 'Keine Nachbarsdaten verfügbar.'; String get neighbors_noData => 'Keine Nachbarsdaten verfügbar.';
@@ -2898,4 +2902,10 @@ class AppLocalizationsDe extends AppLocalizations {
@override @override
String get settings_gpxExportShareSubject => String get settings_gpxExportShareSubject =>
'GPX-Kartendaten aus meshcore-open exportieren'; 'GPX-Kartendaten aus meshcore-open exportieren';
@override
String get snrIndicator_nearByRepeaters => 'In der Nähe befindliche Repeater';
@override
String get snrIndicator_lastSeen => 'Zuletzt gesehen';
} }
+23 -13
View File
@@ -342,6 +342,15 @@ class AppLocalizationsEn extends AppLocalizations {
@override @override
String get settings_presets => 'Presets'; String get settings_presets => 'Presets';
@override
String get settings_preset915Mhz => '915 MHz';
@override
String get settings_preset868Mhz => '868 MHz';
@override
String get settings_preset433Mhz => '433 MHz';
@override @override
String get settings_frequency => 'Frequency (MHz)'; String get settings_frequency => 'Frequency (MHz)';
@@ -370,15 +379,10 @@ class AppLocalizationsEn extends AppLocalizations {
String get settings_txPowerInvalid => 'Invalid TX power (0-22 dBm)'; String get settings_txPowerInvalid => 'Invalid TX power (0-22 dBm)';
@override @override
String get settings_clientRepeat => 'Off-Grid Repeat'; String get settings_longRange => 'Long Range';
@override @override
String get settings_clientRepeatSubtitle => String get settings_fastSpeed => 'Fast Speed';
'Allow this device to repeat mesh packets for others';
@override
String get settings_clientRepeatFreqWarning =>
'Off-grid repeat requires 433, 869, or 918 MHz frequency';
@override @override
String settings_error(String message) { String settings_error(String message) {
@@ -1650,10 +1654,10 @@ class AppLocalizationsEn extends AppLocalizations {
String get repeater_cliSubtitle => 'Send commands to the repeater'; String get repeater_cliSubtitle => 'Send commands to the repeater';
@override @override
String get repeater_neighbours => 'Neighbors'; String get repeater_neighbors => 'Neighbors';
@override @override
String get repeater_neighboursSubtitle => 'View zero hop neighbors.'; String get repeater_neighborsSubtitle => 'View zero hop neighbors.';
@override @override
String get repeater_settings => 'Settings'; String get repeater_settings => 'Settings';
@@ -2329,10 +2333,10 @@ class AppLocalizationsEn extends AppLocalizations {
} }
@override @override
String get neighbors_receivedData => 'Received Neighbours Data'; String get neighbors_receivedData => 'Received Neighbors Data';
@override @override
String get neighbors_requestTimedOut => 'Neighbours request timed out.'; String get neighbors_requestTimedOut => 'Neighbors request timed out.';
@override @override
String neighbors_errorLoading(String error) { String neighbors_errorLoading(String error) {
@@ -2340,10 +2344,10 @@ class AppLocalizationsEn extends AppLocalizations {
} }
@override @override
String get neighbors_repeatersNeighbours => 'Repeaters Neighbours'; String get neighbors_repeatersNeighbors => 'Repeaters Neighbors';
@override @override
String get neighbors_noData => 'No neighbours data available.'; String get neighbors_noData => 'No neighbors data available.';
@override @override
String neighbors_unknownContact(String pubkey) { String neighbors_unknownContact(String pubkey) {
@@ -2845,4 +2849,10 @@ class AppLocalizationsEn extends AppLocalizations {
@override @override
String get settings_gpxExportShareSubject => String get settings_gpxExportShareSubject =>
'meshcore-open GPX map data export'; 'meshcore-open GPX map data export';
@override
String get snrIndicator_nearByRepeaters => 'Nearby Repeaters';
@override
String get snrIndicator_lastSeen => 'Last seen';
} }
+20 -10
View File
@@ -347,6 +347,15 @@ class AppLocalizationsEs extends AppLocalizations {
@override @override
String get settings_presets => 'Preajustes'; String get settings_presets => 'Preajustes';
@override
String get settings_preset915Mhz => '915 MHz';
@override
String get settings_preset868Mhz => '868 MHz';
@override
String get settings_preset433Mhz => '433 MHz';
@override @override
String get settings_frequency => 'Frecuencia (MHz)'; String get settings_frequency => 'Frecuencia (MHz)';
@@ -375,15 +384,10 @@ class AppLocalizationsEs extends AppLocalizations {
String get settings_txPowerInvalid => 'Potencia de TX inválida (0-22 dBm)'; String get settings_txPowerInvalid => 'Potencia de TX inválida (0-22 dBm)';
@override @override
String get settings_clientRepeat => 'Repetir sin conexión'; String get settings_longRange => 'Largo Alcance';
@override @override
String get settings_clientRepeatSubtitle => String get settings_fastSpeed => 'Velocidad Rápida';
'Permita que este dispositivo repita los paquetes de red para otros usuarios.';
@override
String get settings_clientRepeatFreqWarning =>
'Para la comunicación fuera de la red, se requiere una frecuencia de 433, 869 o 918 MHz.';
@override @override
String settings_error(String message) { String settings_error(String message) {
@@ -1674,10 +1678,10 @@ class AppLocalizationsEs extends AppLocalizations {
String get repeater_cliSubtitle => 'Enviar comandos al repetidor'; String get repeater_cliSubtitle => 'Enviar comandos al repetidor';
@override @override
String get repeater_neighbours => 'Vecinos'; String get repeater_neighbors => 'Vecinos';
@override @override
String get repeater_neighboursSubtitle => 'Ver vecinos de salto cero.'; String get repeater_neighborsSubtitle => 'Ver vecinos de salto cero.';
@override @override
String get repeater_settings => 'Configuración'; String get repeater_settings => 'Configuración';
@@ -2376,7 +2380,7 @@ class AppLocalizationsEs extends AppLocalizations {
} }
@override @override
String get neighbors_repeatersNeighbours => 'Repetidores Vecinos'; String get neighbors_repeatersNeighbors => 'Repetidores Vecinos';
@override @override
String get neighbors_noData => 'No hay datos de vecinos disponibles.'; String get neighbors_noData => 'No hay datos de vecinos disponibles.';
@@ -2889,4 +2893,10 @@ class AppLocalizationsEs extends AppLocalizations {
@override @override
String get settings_gpxExportShareSubject => String get settings_gpxExportShareSubject =>
'meshcore-open exportación de datos de mapa GPX'; 'meshcore-open exportación de datos de mapa GPX';
@override
String get snrIndicator_nearByRepeaters => 'Repetidores cercanos';
@override
String get snrIndicator_lastSeen => 'Visto por última vez';
} }
+20 -11
View File
@@ -348,6 +348,15 @@ class AppLocalizationsFr extends AppLocalizations {
@override @override
String get settings_presets => 'Préréglages'; String get settings_presets => 'Préréglages';
@override
String get settings_preset915Mhz => '915 MHz';
@override
String get settings_preset868Mhz => '868 MHz';
@override
String get settings_preset433Mhz => '433 MHz';
@override @override
String get settings_frequency => 'Fréquence (MHz)'; String get settings_frequency => 'Fréquence (MHz)';
@@ -376,15 +385,10 @@ class AppLocalizationsFr extends AppLocalizations {
String get settings_txPowerInvalid => 'Puissance TX invalide (0-22 dBm)'; String get settings_txPowerInvalid => 'Puissance TX invalide (0-22 dBm)';
@override @override
String get settings_clientRepeat => 'Répétition hors réseau'; String get settings_longRange => 'Portée Longue';
@override @override
String get settings_clientRepeatSubtitle => String get settings_fastSpeed => 'Vitesse Rapide';
'Permettez à cet appareil de répéter les paquets de données pour les autres.';
@override
String get settings_clientRepeatFreqWarning =>
'Pour les transmissions hors réseau, il est nécessaire d\'utiliser les fréquences de 433, 869 ou 918 MHz.';
@override @override
String settings_error(String message) { String settings_error(String message) {
@@ -1682,11 +1686,10 @@ class AppLocalizationsFr extends AppLocalizations {
String get repeater_cliSubtitle => 'Envoyer des commandes au répéteur'; String get repeater_cliSubtitle => 'Envoyer des commandes au répéteur';
@override @override
String get repeater_neighbours => 'Voisins'; String get repeater_neighbors => 'Voisins';
@override @override
String get repeater_neighboursSubtitle => String get repeater_neighborsSubtitle => 'Afficher les voisins de saut nuls.';
'Afficher les voisins de saut nuls.';
@override @override
String get repeater_settings => 'Paramètres'; String get repeater_settings => 'Paramètres';
@@ -2391,7 +2394,7 @@ class AppLocalizationsFr extends AppLocalizations {
} }
@override @override
String get neighbors_repeatersNeighbours => 'Répéteurs Voisins'; String get neighbors_repeatersNeighbors => 'Répéteurs Voisins';
@override @override
String get neighbors_noData => String get neighbors_noData =>
@@ -2913,4 +2916,10 @@ class AppLocalizationsFr extends AppLocalizations {
@override @override
String get settings_gpxExportShareSubject => String get settings_gpxExportShareSubject =>
'meshcore-open exporter les données de carte GPX'; 'meshcore-open exporter les données de carte GPX';
@override
String get snrIndicator_nearByRepeaters => 'Répéteurs à proximité';
@override
String get snrIndicator_lastSeen => 'Dernière fois vu';
} }
+20 -10
View File
@@ -346,6 +346,15 @@ class AppLocalizationsIt extends AppLocalizations {
@override @override
String get settings_presets => 'Preset'; String get settings_presets => 'Preset';
@override
String get settings_preset915Mhz => '915 MHz';
@override
String get settings_preset868Mhz => '868 MHz';
@override
String get settings_preset433Mhz => '433 MHz';
@override @override
String get settings_frequency => 'Frequenza (MHz)'; String get settings_frequency => 'Frequenza (MHz)';
@@ -374,15 +383,10 @@ class AppLocalizationsIt extends AppLocalizations {
String get settings_txPowerInvalid => 'Potere TX non valido (0-22 dBm)'; String get settings_txPowerInvalid => 'Potere TX non valido (0-22 dBm)';
@override @override
String get settings_clientRepeat => 'Ripetizione \"fuori dalla rete\"'; String get settings_longRange => 'Lungo Raggio';
@override @override
String get settings_clientRepeatSubtitle => String get settings_fastSpeed => 'Velocità Rapida';
'Permetti a questo dispositivo di ripetere i pacchetti di rete per gli altri.';
@override
String get settings_clientRepeatFreqWarning =>
'Per la comunicazione fuori rete, è necessario utilizzare frequenze di 433, 869 o 918 MHz.';
@override @override
String settings_error(String message) { String settings_error(String message) {
@@ -1672,10 +1676,10 @@ class AppLocalizationsIt extends AppLocalizations {
String get repeater_cliSubtitle => 'Invia comandi al ripetitore'; String get repeater_cliSubtitle => 'Invia comandi al ripetitore';
@override @override
String get repeater_neighbours => 'Vicini'; String get repeater_neighbors => 'Vicini';
@override @override
String get repeater_neighboursSubtitle => String get repeater_neighborsSubtitle =>
'Visualizza vicini di salto pari a zero.'; 'Visualizza vicini di salto pari a zero.';
@override @override
@@ -2376,7 +2380,7 @@ class AppLocalizationsIt extends AppLocalizations {
} }
@override @override
String get neighbors_repeatersNeighbours => 'Ripetitori Vicini'; String get neighbors_repeatersNeighbors => 'Ripetitori Vicini';
@override @override
String get neighbors_noData => 'Nessun dato sugli vicini disponibile.'; String get neighbors_noData => 'Nessun dato sugli vicini disponibile.';
@@ -2893,4 +2897,10 @@ class AppLocalizationsIt extends AppLocalizations {
@override @override
String get settings_gpxExportShareSubject => String get settings_gpxExportShareSubject =>
'meshcore-open esportazione dati mappa GPX'; 'meshcore-open esportazione dati mappa GPX';
@override
String get snrIndicator_nearByRepeaters => 'Ripetitori vicini';
@override
String get snrIndicator_lastSeen => 'Ultimo accesso';
} }
+20 -10
View File
@@ -344,6 +344,15 @@ class AppLocalizationsNl extends AppLocalizations {
@override @override
String get settings_presets => 'Presets'; String get settings_presets => 'Presets';
@override
String get settings_preset915Mhz => '915 MHz';
@override
String get settings_preset868Mhz => '868 MHz';
@override
String get settings_preset433Mhz => '433 MHz';
@override @override
String get settings_frequency => 'Frequentie (MHz)'; String get settings_frequency => 'Frequentie (MHz)';
@@ -372,15 +381,10 @@ class AppLocalizationsNl extends AppLocalizations {
String get settings_txPowerInvalid => 'Ongeldige TX-vermogen (0-22 dBm)'; String get settings_txPowerInvalid => 'Ongeldige TX-vermogen (0-22 dBm)';
@override @override
String get settings_clientRepeat => 'Herhalen: Afgekoppeld'; String get settings_longRange => 'Lange Afstand';
@override @override
String get settings_clientRepeatSubtitle => String get settings_fastSpeed => 'Hoge Snelheid';
'Laat dit apparaat de mesh-pakketten opnieuw verzenden voor andere apparaten.';
@override
String get settings_clientRepeatFreqWarning =>
'Om een signaal buiten het netwerk te versturen, zijn frequenties van 433, 869 of 918 MHz vereist.';
@override @override
String settings_error(String message) { String settings_error(String message) {
@@ -1668,10 +1672,10 @@ class AppLocalizationsNl extends AppLocalizations {
String get repeater_cliSubtitle => 'Verzend commando\'s naar de repeater'; String get repeater_cliSubtitle => 'Verzend commando\'s naar de repeater';
@override @override
String get repeater_neighbours => 'Buren'; String get repeater_neighbors => 'Buren';
@override @override
String get repeater_neighboursSubtitle => 'Bekijk nul hops buren.'; String get repeater_neighborsSubtitle => 'Bekijk nul hops buren.';
@override @override
String get repeater_settings => 'Instellingen'; String get repeater_settings => 'Instellingen';
@@ -2367,7 +2371,7 @@ class AppLocalizationsNl extends AppLocalizations {
} }
@override @override
String get neighbors_repeatersNeighbours => 'Herhalingen Buren'; String get neighbors_repeatersNeighbors => 'Herhalingen Buren';
@override @override
String get neighbors_noData => 'Geen gegevens van buren beschikbaar.'; String get neighbors_noData => 'Geen gegevens van buren beschikbaar.';
@@ -2881,4 +2885,10 @@ class AppLocalizationsNl extends AppLocalizations {
@override @override
String get settings_gpxExportShareSubject => String get settings_gpxExportShareSubject =>
'meshcore-open GPX kaartgegevens exporteren'; 'meshcore-open GPX kaartgegevens exporteren';
@override
String get snrIndicator_nearByRepeaters => 'Nabije herhalingseenheden';
@override
String get snrIndicator_lastSeen => 'Laatst gezien';
} }
+20 -10
View File
@@ -347,6 +347,15 @@ class AppLocalizationsPl extends AppLocalizations {
@override @override
String get settings_presets => 'Preset'; String get settings_presets => 'Preset';
@override
String get settings_preset915Mhz => '915 MHz';
@override
String get settings_preset868Mhz => '868 MHz';
@override
String get settings_preset433Mhz => '433 MHz';
@override @override
String get settings_frequency => 'Częstotliwość (MHz)'; String get settings_frequency => 'Częstotliwość (MHz)';
@@ -376,15 +385,10 @@ class AppLocalizationsPl extends AppLocalizations {
String get settings_txPowerInvalid => 'Nieprawidłowa moc TX (0-22 dBm)'; String get settings_txPowerInvalid => 'Nieprawidłowa moc TX (0-22 dBm)';
@override @override
String get settings_clientRepeat => 'Powtórzenie: Niezależne od sieci'; String get settings_longRange => 'Długi zasięg';
@override @override
String get settings_clientRepeatSubtitle => String get settings_fastSpeed => 'Szybka prędkość';
'Pozwól temu urządzeniu powtarzać pakiety danych dla innych urządzeń.';
@override
String get settings_clientRepeatFreqWarning =>
'Powtórka poza siecią wymaga częstotliwości 433, 869 lub 918 MHz.';
@override @override
String settings_error(String message) { String settings_error(String message) {
@@ -1676,10 +1680,10 @@ class AppLocalizationsPl extends AppLocalizations {
String get repeater_cliSubtitle => 'Wyślij polecenia do powielacza'; String get repeater_cliSubtitle => 'Wyślij polecenia do powielacza';
@override @override
String get repeater_neighbours => 'Sąsiedzi'; String get repeater_neighbors => 'Sąsiedzi';
@override @override
String get repeater_neighboursSubtitle => String get repeater_neighborsSubtitle =>
'Wyświetl sąsiedztwo zerowych hopów.'; 'Wyświetl sąsiedztwo zerowych hopów.';
@override @override
@@ -2375,7 +2379,7 @@ class AppLocalizationsPl extends AppLocalizations {
} }
@override @override
String get neighbors_repeatersNeighbours => 'Powtarzacze Sąsiedzi'; String get neighbors_repeatersNeighbors => 'Powtarzacze Sąsiedzi';
@override @override
String get neighbors_noData => 'Brak danych dotyczących sąsiadów.'; String get neighbors_noData => 'Brak danych dotyczących sąsiadów.';
@@ -2895,4 +2899,10 @@ class AppLocalizationsPl extends AppLocalizations {
@override @override
String get settings_gpxExportShareSubject => String get settings_gpxExportShareSubject =>
'Eksport danych mapy GPX meshcore-open'; 'Eksport danych mapy GPX meshcore-open';
@override
String get snrIndicator_nearByRepeaters => 'Nadajniki w pobliżu';
@override
String get snrIndicator_lastSeen => 'Ostatnio widziany';
} }
+20 -11
View File
@@ -348,6 +348,15 @@ class AppLocalizationsPt extends AppLocalizations {
@override @override
String get settings_presets => 'Presets'; String get settings_presets => 'Presets';
@override
String get settings_preset915Mhz => '915 MHz';
@override
String get settings_preset868Mhz => '868 MHz';
@override
String get settings_preset433Mhz => '433 MHz';
@override @override
String get settings_frequency => 'Frequência (MHz)'; String get settings_frequency => 'Frequência (MHz)';
@@ -376,15 +385,10 @@ class AppLocalizationsPt extends AppLocalizations {
String get settings_txPowerInvalid => 'Potência de TX inválida (0-22 dBm)'; String get settings_txPowerInvalid => 'Potência de TX inválida (0-22 dBm)';
@override @override
String get settings_clientRepeat => 'Repetição sem rede'; String get settings_longRange => 'Alcance Longo';
@override @override
String get settings_clientRepeatSubtitle => String get settings_fastSpeed => 'Velocidade Rápida';
'Permita que este dispositivo repita pacotes de rede para outros dispositivos.';
@override
String get settings_clientRepeatFreqWarning =>
'A repetição fora da rede requer frequências de 433, 869 ou 918 MHz.';
@override @override
String settings_error(String message) { String settings_error(String message) {
@@ -1674,11 +1678,10 @@ class AppLocalizationsPt extends AppLocalizations {
String get repeater_cliSubtitle => 'Enviar comandos ao repetidor'; String get repeater_cliSubtitle => 'Enviar comandos ao repetidor';
@override @override
String get repeater_neighbours => 'Vizinhos'; String get repeater_neighbors => 'Vizinhos';
@override @override
String get repeater_neighboursSubtitle => String get repeater_neighborsSubtitle => 'Visualizar vizinhos de salto zero.';
'Visualizar vizinhos de salto zero.';
@override @override
String get repeater_settings => 'Configurações'; String get repeater_settings => 'Configurações';
@@ -2377,7 +2380,7 @@ class AppLocalizationsPt extends AppLocalizations {
} }
@override @override
String get neighbors_repeatersNeighbours => 'Repetidores Vizinhos'; String get neighbors_repeatersNeighbors => 'Repetidores Vizinhos';
@override @override
String get neighbors_noData => 'Não estão disponíveis dados de vizinhos.'; String get neighbors_noData => 'Não estão disponíveis dados de vizinhos.';
@@ -2890,4 +2893,10 @@ class AppLocalizationsPt extends AppLocalizations {
@override @override
String get settings_gpxExportShareSubject => String get settings_gpxExportShareSubject =>
'meshcore-open exportação de dados de mapa GPX'; 'meshcore-open exportação de dados de mapa GPX';
@override
String get snrIndicator_nearByRepeaters => 'Repetidores Próximos';
@override
String get snrIndicator_lastSeen => 'Visto pela última vez';
} }
+20 -10
View File
@@ -345,6 +345,15 @@ class AppLocalizationsRu extends AppLocalizations {
@override @override
String get settings_presets => 'Пресеты'; String get settings_presets => 'Пресеты';
@override
String get settings_preset915Mhz => '915 МГц';
@override
String get settings_preset868Mhz => '868 МГц';
@override
String get settings_preset433Mhz => '433 МГц';
@override @override
String get settings_frequency => 'Частота (МГц)'; String get settings_frequency => 'Частота (МГц)';
@@ -374,15 +383,10 @@ class AppLocalizationsRu extends AppLocalizations {
'Недопустимая мощность передачи (0–22 дБм)'; 'Недопустимая мощность передачи (0–22 дБм)';
@override @override
String get settings_clientRepeat => 'Повторение \"вне сети\"'; String get settings_longRange => 'Дальний радиус';
@override @override
String get settings_clientRepeatSubtitle => String get settings_fastSpeed => 'Высокая скорость';
'Позвольте этому устройству повторять пакеты данных для других устройств.';
@override
String get settings_clientRepeatFreqWarning =>
'Для работы в режиме \"без подключения к сети\" требуется частота 433, 869 или 918 МГц.';
@override @override
String settings_error(String message) { String settings_error(String message) {
@@ -1676,10 +1680,10 @@ class AppLocalizationsRu extends AppLocalizations {
String get repeater_cliSubtitle => 'Отправка команд репитеру'; String get repeater_cliSubtitle => 'Отправка команд репитеру';
@override @override
String get repeater_neighbours => 'Соседи'; String get repeater_neighbors => 'Соседи';
@override @override
String get repeater_neighboursSubtitle => 'Просмотр соседей на нулевом хопе.'; String get repeater_neighborsSubtitle => 'Просмотр соседей на нулевом хопе.';
@override @override
String get repeater_settings => 'Настройки'; String get repeater_settings => 'Настройки';
@@ -2379,7 +2383,7 @@ class AppLocalizationsRu extends AppLocalizations {
} }
@override @override
String get neighbors_repeatersNeighbours => 'Соседи репитеров'; String get neighbors_repeatersNeighbors => 'Соседи репитеров';
@override @override
String get neighbors_noData => 'Данные о соседях недоступны.'; String get neighbors_noData => 'Данные о соседях недоступны.';
@@ -2901,4 +2905,10 @@ class AppLocalizationsRu extends AppLocalizations {
@override @override
String get settings_gpxExportShareSubject => String get settings_gpxExportShareSubject =>
'meshcore-open экспорт данных карты GPX'; 'meshcore-open экспорт данных карты GPX';
@override
String get snrIndicator_nearByRepeaters => 'Ближайшие ретрансляторы';
@override
String get snrIndicator_lastSeen => 'Последний раз видели';
} }
+20 -10
View File
@@ -344,6 +344,15 @@ class AppLocalizationsSk extends AppLocalizations {
@override @override
String get settings_presets => 'Prednastavenia'; String get settings_presets => 'Prednastavenia';
@override
String get settings_preset915Mhz => '915 MHz';
@override
String get settings_preset868Mhz => '868 MHz';
@override
String get settings_preset433Mhz => '433 MHz';
@override @override
String get settings_frequency => 'Frekvencia (MHz)'; String get settings_frequency => 'Frekvencia (MHz)';
@@ -372,15 +381,10 @@ class AppLocalizationsSk extends AppLocalizations {
String get settings_txPowerInvalid => 'Neplatná hodnota výkonu TX (0-22 dBm)'; String get settings_txPowerInvalid => 'Neplatná hodnota výkonu TX (0-22 dBm)';
@override @override
String get settings_clientRepeat => 'Opätovné použitie bez elektrickej siete'; String get settings_longRange => 'Dlhý dosah';
@override @override
String get settings_clientRepeatSubtitle => String get settings_fastSpeed => 'Rýchla rýchlosť';
'Umožnite, aby toto zariadenie opakovávalo siete pre ostatných.';
@override
String get settings_clientRepeatFreqWarning =>
'Použitie off-grid systému vyžaduje frekvencie 433, 869 alebo 918 MHz.';
@override @override
String settings_error(String message) { String settings_error(String message) {
@@ -1669,10 +1673,10 @@ class AppLocalizationsSk extends AppLocalizations {
String get repeater_cliSubtitle => 'Pošlite príkazy opakovaču'; String get repeater_cliSubtitle => 'Pošlite príkazy opakovaču';
@override @override
String get repeater_neighbours => 'Súsezný'; String get repeater_neighbors => 'Súsezný';
@override @override
String get repeater_neighboursSubtitle => 'Zobraziť susedné body bez skokov.'; String get repeater_neighborsSubtitle => 'Zobraziť susedné body bez skokov.';
@override @override
String get repeater_settings => 'Nastavenia'; String get repeater_settings => 'Nastavenia';
@@ -2363,7 +2367,7 @@ class AppLocalizationsSk extends AppLocalizations {
} }
@override @override
String get neighbors_repeatersNeighbours => 'Opakovadlá Súsezná'; String get neighbors_repeatersNeighbors => 'Opakovadlá Súsezná';
@override @override
String get neighbors_noData => String get neighbors_noData =>
@@ -2877,4 +2881,10 @@ class AppLocalizationsSk extends AppLocalizations {
@override @override
String get settings_gpxExportShareSubject => String get settings_gpxExportShareSubject =>
'meshcore-open export dát GPX mapových údajov'; 'meshcore-open export dát GPX mapových údajov';
@override
String get snrIndicator_nearByRepeaters => 'Miestne opakovače';
@override
String get snrIndicator_lastSeen => 'Naposledy videný';
} }
+20 -10
View File
@@ -343,6 +343,15 @@ class AppLocalizationsSl extends AppLocalizations {
@override @override
String get settings_presets => 'Prednastavitve'; String get settings_presets => 'Prednastavitve';
@override
String get settings_preset915Mhz => '915 MHz';
@override
String get settings_preset868Mhz => '868 MHz';
@override
String get settings_preset433Mhz => '433 MHz';
@override @override
String get settings_frequency => 'Frekvenca (MHz)'; String get settings_frequency => 'Frekvenca (MHz)';
@@ -371,15 +380,10 @@ class AppLocalizationsSl extends AppLocalizations {
String get settings_txPowerInvalid => 'Neveljavna TX moč (0-22 dBm)'; String get settings_txPowerInvalid => 'Neveljavna TX moč (0-22 dBm)';
@override @override
String get settings_clientRepeat => 'Neovadno ponavljanje'; String get settings_longRange => 'DDolg doseg';
@override @override
String get settings_clientRepeatSubtitle => String get settings_fastSpeed => 'Visoka hitrost';
'Omogočite temu naprave, da ponavlja paketne sporočila za druge.';
@override
String get settings_clientRepeatFreqWarning =>
'Za ponovni prenos na brezžični način so potrebne frekvence 433, 869 ali 918 MHz.';
@override @override
String settings_error(String message) { String settings_error(String message) {
@@ -1668,10 +1672,10 @@ class AppLocalizationsSl extends AppLocalizations {
'Pošlji ukazne povelje na ponovitveno enoto.'; 'Pošlji ukazne povelje na ponovitveno enoto.';
@override @override
String get repeater_neighbours => 'Sosedi'; String get repeater_neighbors => 'Sosedi';
@override @override
String get repeater_neighboursSubtitle => 'Pogledati nič sosednjih hopjev.'; String get repeater_neighborsSubtitle => 'Pogledati nič sosednjih hopjev.';
@override @override
String get repeater_settings => 'Nastavitve'; String get repeater_settings => 'Nastavitve';
@@ -2367,7 +2371,7 @@ class AppLocalizationsSl extends AppLocalizations {
} }
@override @override
String get neighbors_repeatersNeighbours => 'Ponovitve Sosedi'; String get neighbors_repeatersNeighbors => 'Ponovitve Sosedi';
@override @override
String get neighbors_noData => 'Niso na voljo podatki o sosedih.'; String get neighbors_noData => 'Niso na voljo podatki o sosedih.';
@@ -2882,4 +2886,10 @@ class AppLocalizationsSl extends AppLocalizations {
@override @override
String get settings_gpxExportShareSubject => String get settings_gpxExportShareSubject =>
'meshcore-open izvoz podatkov GPX karte'; 'meshcore-open izvoz podatkov GPX karte';
@override
String get snrIndicator_nearByRepeaters => 'Bližnji ponovitelji';
@override
String get snrIndicator_lastSeen => 'Zadnjič videno';
} }
+20 -10
View File
@@ -341,6 +341,15 @@ class AppLocalizationsSv extends AppLocalizations {
@override @override
String get settings_presets => 'Fördefinierade inställningar'; String get settings_presets => 'Fördefinierade inställningar';
@override
String get settings_preset915Mhz => '915 MHz';
@override
String get settings_preset868Mhz => '868 MHz';
@override
String get settings_preset433Mhz => '433 MHz';
@override @override
String get settings_frequency => 'Frekvens (MHz)'; String get settings_frequency => 'Frekvens (MHz)';
@@ -369,15 +378,10 @@ class AppLocalizationsSv extends AppLocalizations {
String get settings_txPowerInvalid => 'Ogiltig TX-effekt (0-22 dBm)'; String get settings_txPowerInvalid => 'Ogiltig TX-effekt (0-22 dBm)';
@override @override
String get settings_clientRepeat => 'Upprepa utan elnät'; String get settings_longRange => 'Lång räckvidd';
@override @override
String get settings_clientRepeatSubtitle => String get settings_fastSpeed => 'Snabb hastighet';
'Låt enheten repetera nätpaket för andra användare.';
@override
String get settings_clientRepeatFreqWarning =>
'För att kunna kommunicera utanför elnätet krävs frekvenserna 433, 869 eller 918 MHz.';
@override @override
String settings_error(String message) { String settings_error(String message) {
@@ -1658,10 +1662,10 @@ class AppLocalizationsSv extends AppLocalizations {
String get repeater_cliSubtitle => 'Skicka kommandon till repetitorn'; String get repeater_cliSubtitle => 'Skicka kommandon till repetitorn';
@override @override
String get repeater_neighbours => 'Grannar'; String get repeater_neighbors => 'Grannar';
@override @override
String get repeater_neighboursSubtitle => 'Visa noll hoppgrannar.'; String get repeater_neighborsSubtitle => 'Visa noll hoppgrannar.';
@override @override
String get repeater_settings => 'Inställningar'; String get repeater_settings => 'Inställningar';
@@ -2352,7 +2356,7 @@ class AppLocalizationsSv extends AppLocalizations {
} }
@override @override
String get neighbors_repeatersNeighbours => 'Upprepar grannar'; String get neighbors_repeatersNeighbors => 'Upprepar grannar';
@override @override
String get neighbors_noData => 'Inga grannuppgifter finns tillgängliga.'; String get neighbors_noData => 'Inga grannuppgifter finns tillgängliga.';
@@ -2862,4 +2866,10 @@ class AppLocalizationsSv extends AppLocalizations {
@override @override
String get settings_gpxExportShareSubject => String get settings_gpxExportShareSubject =>
'meshcore-open export av GPX-kartdata'; 'meshcore-open export av GPX-kartdata';
@override
String get snrIndicator_nearByRepeaters => 'Närliggande uppreparstationer';
@override
String get snrIndicator_lastSeen => 'Senast sedd';
} }
+20 -10
View File
@@ -346,6 +346,15 @@ class AppLocalizationsUk extends AppLocalizations {
@override @override
String get settings_presets => 'Попередні налаштування'; String get settings_presets => 'Попередні налаштування';
@override
String get settings_preset915Mhz => '915 МГц';
@override
String get settings_preset868Mhz => '868 МГц';
@override
String get settings_preset433Mhz => '433 МГц';
@override @override
String get settings_frequency => 'Частота (МГц)'; String get settings_frequency => 'Частота (МГц)';
@@ -374,15 +383,10 @@ class AppLocalizationsUk extends AppLocalizations {
String get settings_txPowerInvalid => 'Некоректна потужність TX (0-22 дБм)'; String get settings_txPowerInvalid => 'Некоректна потужність TX (0-22 дБм)';
@override @override
String get settings_clientRepeat => 'Автономна система'; String get settings_longRange => 'Дальній діапазон';
@override @override
String get settings_clientRepeatSubtitle => String get settings_fastSpeed => 'Висока швидкість';
'Дозвольте цьому пристрою повторювати пакети даних для інших пристроїв.';
@override
String get settings_clientRepeatFreqWarning =>
'Повтор без підключення до мережі вимагає частоти 433, 869 або 918 МГц.';
@override @override
String settings_error(String message) { String settings_error(String message) {
@@ -1675,10 +1679,10 @@ class AppLocalizationsUk extends AppLocalizations {
String get repeater_cliSubtitle => 'Надіслати команди ретранслятору'; String get repeater_cliSubtitle => 'Надіслати команди ретранслятору';
@override @override
String get repeater_neighbours => 'Сусіди'; String get repeater_neighbors => 'Сусіди';
@override @override
String get repeater_neighboursSubtitle => String get repeater_neighborsSubtitle =>
'Показати сусідів нульового стрибка.'; 'Показати сусідів нульового стрибка.';
@override @override
@@ -2380,7 +2384,7 @@ class AppLocalizationsUk extends AppLocalizations {
} }
@override @override
String get neighbors_repeatersNeighbours => 'Ретранслятори-сусіди'; String get neighbors_repeatersNeighbors => 'Ретранслятори-сусіди';
@override @override
String get neighbors_noData => 'Дані про сусідів недоступні.'; String get neighbors_noData => 'Дані про сусідів недоступні.';
@@ -2907,4 +2911,10 @@ class AppLocalizationsUk extends AppLocalizations {
@override @override
String get settings_gpxExportShareSubject => String get settings_gpxExportShareSubject =>
'експорт даних карти meshcore-open у форматі GPX'; 'експорт даних карти meshcore-open у форматі GPX';
@override
String get snrIndicator_nearByRepeaters => 'Ближні ретранслятори';
@override
String get snrIndicator_lastSeen => 'Останній раз бачили';
} }
+20 -9
View File
@@ -331,6 +331,15 @@ class AppLocalizationsZh extends AppLocalizations {
@override @override
String get settings_presets => '预设'; String get settings_presets => '预设';
@override
String get settings_preset915Mhz => '915 兆赫';
@override
String get settings_preset868Mhz => '868 兆赫';
@override
String get settings_preset433Mhz => '433 兆赫';
@override @override
String get settings_frequency => '频率 (MHz)'; String get settings_frequency => '频率 (MHz)';
@@ -359,14 +368,10 @@ class AppLocalizationsZh extends AppLocalizations {
String get settings_txPowerInvalid => '无效的发射功率(0-22 dBm'; String get settings_txPowerInvalid => '无效的发射功率(0-22 dBm';
@override @override
String get settings_clientRepeat => '网重复'; String get settings_longRange => '远距';
@override @override
String get settings_clientRepeatSubtitle => '允许此设备重复发送网状数据包给其他设备'; String get settings_fastSpeed => '高速';
@override
String get settings_clientRepeatFreqWarning =>
'离网重复通信需要使用 433、869 或 918 兆赫兹的频率。';
@override @override
String settings_error(String message) { String settings_error(String message) {
@@ -1596,10 +1601,10 @@ class AppLocalizationsZh extends AppLocalizations {
String get repeater_cliSubtitle => '向复用器发送指令'; String get repeater_cliSubtitle => '向复用器发送指令';
@override @override
String get repeater_neighbours => '邻居'; String get repeater_neighbors => '邻居';
@override @override
String get repeater_neighboursSubtitle => '查看邻居节点(无需中间节点)。'; String get repeater_neighborsSubtitle => '查看邻居节点(无需中间节点)。';
@override @override
String get repeater_settings => '设置'; String get repeater_settings => '设置';
@@ -2246,7 +2251,7 @@ class AppLocalizationsZh extends AppLocalizations {
} }
@override @override
String get neighbors_repeatersNeighbours => '重复使用的邻居'; String get neighbors_repeatersNeighbors => '重复使用的邻居';
@override @override
String get neighbors_noData => '没有可用的邻居信息。'; String get neighbors_noData => '没有可用的邻居信息。';
@@ -2714,4 +2719,10 @@ class AppLocalizationsZh extends AppLocalizations {
@override @override
String get settings_gpxExportShareSubject => 'meshcore-open GPX 地图数据导出'; String get settings_gpxExportShareSubject => 'meshcore-open GPX 地图数据导出';
@override
String get snrIndicator_nearByRepeaters => '附近的重复器';
@override
String get snrIndicator_lastSeen => '最近访问';
} }
+10 -6
View File
@@ -131,6 +131,9 @@
"settings_infoContactsCount": "Aantal Contacten", "settings_infoContactsCount": "Aantal Contacten",
"settings_infoChannelCount": "Aantal Kanalen", "settings_infoChannelCount": "Aantal Kanalen",
"settings_presets": "Presets", "settings_presets": "Presets",
"settings_preset915Mhz": "915 MHz",
"settings_preset868Mhz": "868 MHz",
"settings_preset433Mhz": "433 MHz",
"settings_frequency": "Frequentie (MHz)", "settings_frequency": "Frequentie (MHz)",
"settings_frequencyHelper": "300,0 - 2500,0", "settings_frequencyHelper": "300,0 - 2500,0",
"settings_frequencyInvalid": "Ongeldige frequentie (300-2500 MHz)", "settings_frequencyInvalid": "Ongeldige frequentie (300-2500 MHz)",
@@ -140,6 +143,8 @@
"settings_txPower": "TX Vermogen (dBm)", "settings_txPower": "TX Vermogen (dBm)",
"settings_txPowerHelper": "0 - 22", "settings_txPowerHelper": "0 - 22",
"settings_txPowerInvalid": "Ongeldige TX-vermogen (0-22 dBm)", "settings_txPowerInvalid": "Ongeldige TX-vermogen (0-22 dBm)",
"settings_longRange": "Lange Afstand",
"settings_fastSpeed": "Hoge Snelheid",
"settings_error": "Fout: {message}", "settings_error": "Fout: {message}",
"@settings_error": { "@settings_error": {
"placeholders": { "placeholders": {
@@ -1351,12 +1356,12 @@
} }
} }
}, },
"repeater_neighbours": "Buren", "repeater_neighbors": "Buren",
"repeater_neighboursSubtitle": "Bekijk nul hops buren.", "repeater_neighborsSubtitle": "Bekijk nul hops buren.",
"neighbors_receivedData": "Ontvangen Buurdata", "neighbors_receivedData": "Ontvangen Buurdata",
"neighbors_requestTimedOut": "Buren vragen om tijdelijk uitgeschakeld.", "neighbors_requestTimedOut": "Buren vragen om tijdelijk uitgeschakeld.",
"neighbors_errorLoading": "Fout bij het laden van buren: {error}", "neighbors_errorLoading": "Fout bij het laden van buren: {error}",
"neighbors_repeatersNeighbours": "Herhalingen Buren", "neighbors_repeatersNeighbors": "Herhalingen Buren",
"neighbors_noData": "Geen gegevens van buren beschikbaar.", "neighbors_noData": "Geen gegevens van buren beschikbaar.",
"channels_createPrivateChannelDesc": "Beveiligd met een geheime sleutel.", "channels_createPrivateChannelDesc": "Beveiligd met een geheime sleutel.",
"channels_createPrivateChannel": "Maak een Privé Kanaal", "channels_createPrivateChannel": "Maak een Privé Kanaal",
@@ -1594,7 +1599,6 @@
"scanner_enableBluetooth": "Activeer Bluetooth", "scanner_enableBluetooth": "Activeer Bluetooth",
"scanner_bluetoothOffMessage": "Zorg ervoor dat Bluetooth is ingeschakeld om naar apparaten te zoeken.", "scanner_bluetoothOffMessage": "Zorg ervoor dat Bluetooth is ingeschakeld om naar apparaten te zoeken.",
"scanner_bluetoothOff": "Bluetooth is uitgeschakeld", "scanner_bluetoothOff": "Bluetooth is uitgeschakeld",
"settings_clientRepeat": "Herhalen: Afgekoppeld", "snrIndicator_lastSeen": "Laatst gezien",
"settings_clientRepeatSubtitle": "Laat dit apparaat de mesh-pakketten opnieuw verzenden voor andere apparaten.", "snrIndicator_nearByRepeaters": "Nabije herhalingseenheden"
"settings_clientRepeatFreqWarning": "Om een signaal buiten het netwerk te versturen, zijn frequenties van 433, 869 of 918 MHz vereist."
} }
+10 -6
View File
@@ -131,6 +131,9 @@
"settings_infoContactsCount": "Liczba kontaktów", "settings_infoContactsCount": "Liczba kontaktów",
"settings_infoChannelCount": "Liczba kanałów", "settings_infoChannelCount": "Liczba kanałów",
"settings_presets": "Preset", "settings_presets": "Preset",
"settings_preset915Mhz": "915 MHz",
"settings_preset868Mhz": "868 MHz",
"settings_preset433Mhz": "433 MHz",
"settings_frequency": "Częstotliwość (MHz)", "settings_frequency": "Częstotliwość (MHz)",
"settings_frequencyHelper": "300,0 - 2500,0", "settings_frequencyHelper": "300,0 - 2500,0",
"settings_frequencyInvalid": "Nieprawidłowa częstotliwość (300-2500 MHz)", "settings_frequencyInvalid": "Nieprawidłowa częstotliwość (300-2500 MHz)",
@@ -140,6 +143,8 @@
"settings_txPower": "TX Moc (dBm)", "settings_txPower": "TX Moc (dBm)",
"settings_txPowerHelper": "0 - 22", "settings_txPowerHelper": "0 - 22",
"settings_txPowerInvalid": "Nieprawidłowa moc TX (0-22 dBm)", "settings_txPowerInvalid": "Nieprawidłowa moc TX (0-22 dBm)",
"settings_longRange": "Długi zasięg",
"settings_fastSpeed": "Szybka prędkość",
"settings_error": "Błąd: {message}", "settings_error": "Błąd: {message}",
"@settings_error": { "@settings_error": {
"placeholders": { "placeholders": {
@@ -1351,12 +1356,12 @@
} }
} }
}, },
"repeater_neighbours": "Sąsiedzi", "repeater_neighbors": "Sąsiedzi",
"repeater_neighboursSubtitle": "Wyświetl sąsiedztwo zerowych hopów.", "repeater_neighborsSubtitle": "Wyświetl sąsiedztwo zerowych hopów.",
"neighbors_receivedData": "Otrzymano dane sąsiedztwa", "neighbors_receivedData": "Otrzymano dane sąsiedztwa",
"neighbors_requestTimedOut": "Sąsiedzi proszą o wyłączenie timingu.", "neighbors_requestTimedOut": "Sąsiedzi proszą o wyłączenie timingu.",
"neighbors_errorLoading": "Błąd podczas ładowania sąsiadów: {error}", "neighbors_errorLoading": "Błąd podczas ładowania sąsiadów: {error}",
"neighbors_repeatersNeighbours": "Powtarzacze Sąsiedzi", "neighbors_repeatersNeighbors": "Powtarzacze Sąsiedzi",
"neighbors_noData": "Brak danych dotyczących sąsiadów.", "neighbors_noData": "Brak danych dotyczących sąsiadów.",
"channels_joinPrivateChannelDesc": "Ręcznie wprowadź klucz tajny.", "channels_joinPrivateChannelDesc": "Ręcznie wprowadź klucz tajny.",
"channels_createPrivateChannel": "Utwórz Prywatny Kanał", "channels_createPrivateChannel": "Utwórz Prywatny Kanał",
@@ -1594,7 +1599,6 @@
"scanner_bluetoothOffMessage": "Prosimy włączyć Bluetooth, aby przeskanować urządzenia.", "scanner_bluetoothOffMessage": "Prosimy włączyć Bluetooth, aby przeskanować urządzenia.",
"scanner_bluetoothOff": "Bluetooth jest wyłączony", "scanner_bluetoothOff": "Bluetooth jest wyłączony",
"scanner_enableBluetooth": "Włącz Bluetooth", "scanner_enableBluetooth": "Włącz Bluetooth",
"settings_clientRepeatSubtitle": "Pozwól temu urządzeniu powtarzać pakiety danych dla innych urządzeń.", "snrIndicator_lastSeen": "Ostatnio widziany",
"settings_clientRepeat": "Powtórzenie: Niezależne od sieci", "snrIndicator_nearByRepeaters": "Nadajniki w pobliżu"
"settings_clientRepeatFreqWarning": "Powtórka poza siecią wymaga częstotliwości 433, 869 lub 918 MHz."
} }
+10 -6
View File
@@ -131,6 +131,9 @@
"settings_infoContactsCount": "Número de Contatos", "settings_infoContactsCount": "Número de Contatos",
"settings_infoChannelCount": "Número do Canal", "settings_infoChannelCount": "Número do Canal",
"settings_presets": "Presets", "settings_presets": "Presets",
"settings_preset915Mhz": "915 MHz",
"settings_preset868Mhz": "868 MHz",
"settings_preset433Mhz": "433 MHz",
"settings_frequency": "Frequência (MHz)", "settings_frequency": "Frequência (MHz)",
"settings_frequencyHelper": "300,0 - 2500,0", "settings_frequencyHelper": "300,0 - 2500,0",
"settings_frequencyInvalid": "Frequência inválida (300-2500 MHz)", "settings_frequencyInvalid": "Frequência inválida (300-2500 MHz)",
@@ -140,6 +143,8 @@
"settings_txPower": "TX Potência (dBm)", "settings_txPower": "TX Potência (dBm)",
"settings_txPowerHelper": "0 - 22", "settings_txPowerHelper": "0 - 22",
"settings_txPowerInvalid": "Potência de TX inválida (0-22 dBm)", "settings_txPowerInvalid": "Potência de TX inválida (0-22 dBm)",
"settings_longRange": "Alcance Longo",
"settings_fastSpeed": "Velocidade Rápida",
"settings_error": "Erro: {message}", "settings_error": "Erro: {message}",
"@settings_error": { "@settings_error": {
"placeholders": { "placeholders": {
@@ -1351,12 +1356,12 @@
} }
} }
}, },
"repeater_neighbours": "Vizinhos", "repeater_neighbors": "Vizinhos",
"neighbors_receivedData": "Dados dos Vizinhos Recebidos", "neighbors_receivedData": "Dados dos Vizinhos Recebidos",
"repeater_neighboursSubtitle": "Visualizar vizinhos de salto zero.", "repeater_neighborsSubtitle": "Visualizar vizinhos de salto zero.",
"neighbors_requestTimedOut": "Vizinhos solicitam tempo limite esgotado.", "neighbors_requestTimedOut": "Vizinhos solicitam tempo limite esgotado.",
"neighbors_errorLoading": "Erro ao carregar vizinhos: {error}", "neighbors_errorLoading": "Erro ao carregar vizinhos: {error}",
"neighbors_repeatersNeighbours": "Repetidores Vizinhos", "neighbors_repeatersNeighbors": "Repetidores Vizinhos",
"neighbors_noData": "Não estão disponíveis dados de vizinhos.", "neighbors_noData": "Não estão disponíveis dados de vizinhos.",
"channels_createPrivateChannelDesc": "Protegido com uma chave secreta.", "channels_createPrivateChannelDesc": "Protegido com uma chave secreta.",
"channels_joinPrivateChannelDesc": "Inserir uma chave secreta manualmente.", "channels_joinPrivateChannelDesc": "Inserir uma chave secreta manualmente.",
@@ -1594,7 +1599,6 @@
"scanner_enableBluetooth": "Ative o Bluetooth", "scanner_enableBluetooth": "Ative o Bluetooth",
"scanner_bluetoothOff": "Bluetooth está desativado", "scanner_bluetoothOff": "Bluetooth está desativado",
"scanner_bluetoothOffMessage": "Por favor, ative o Bluetooth para escanear por dispositivos.", "scanner_bluetoothOffMessage": "Por favor, ative o Bluetooth para escanear por dispositivos.",
"settings_clientRepeatFreqWarning": "A repetição fora da rede requer frequências de 433, 869 ou 918 MHz.", "snrIndicator_nearByRepeaters": "Repetidores Próximos",
"settings_clientRepeat": "Repetição sem rede", "snrIndicator_lastSeen": "Visto pela última vez"
"settings_clientRepeatSubtitle": "Permita que este dispositivo repita pacotes de rede para outros dispositivos."
} }
+10 -6
View File
@@ -101,6 +101,9 @@
"settings_infoContactsCount": "Количество контактов", "settings_infoContactsCount": "Количество контактов",
"settings_infoChannelCount": "Количество каналов", "settings_infoChannelCount": "Количество каналов",
"settings_presets": "Пресеты", "settings_presets": "Пресеты",
"settings_preset915Mhz": "915 МГц",
"settings_preset868Mhz": "868 МГц",
"settings_preset433Mhz": "433 МГц",
"settings_frequency": "Частота (МГц)", "settings_frequency": "Частота (МГц)",
"settings_frequencyHelper": "300.0 2500.0", "settings_frequencyHelper": "300.0 2500.0",
"settings_frequencyInvalid": "Недопустимая частота (300–2500 МГц)", "settings_frequencyInvalid": "Недопустимая частота (300–2500 МГц)",
@@ -110,6 +113,8 @@
"settings_txPower": "Мощность передачи (дБм)", "settings_txPower": "Мощность передачи (дБм)",
"settings_txPowerHelper": "0 22", "settings_txPowerHelper": "0 22",
"settings_txPowerInvalid": "Недопустимая мощность передачи (0–22 дБм)", "settings_txPowerInvalid": "Недопустимая мощность передачи (0–22 дБм)",
"settings_longRange": "Дальний радиус",
"settings_fastSpeed": "Высокая скорость",
"settings_error": "Ошибка: {message}", "settings_error": "Ошибка: {message}",
"appSettings_title": "Настройки приложения", "appSettings_title": "Настройки приложения",
"appSettings_appearance": "Внешний вид", "appSettings_appearance": "Внешний вид",
@@ -467,8 +472,8 @@
"repeater_telemetrySubtitle": "Просмотр телеметрии датчиков и системной статистики", "repeater_telemetrySubtitle": "Просмотр телеметрии датчиков и системной статистики",
"repeater_cli": "CLI", "repeater_cli": "CLI",
"repeater_cliSubtitle": "Отправка команд репитеру", "repeater_cliSubtitle": "Отправка команд репитеру",
"repeater_neighbours": "Соседи", "repeater_neighbors": "Соседи",
"repeater_neighboursSubtitle": "Просмотр соседей на нулевом хопе.", "repeater_neighborsSubtitle": "Просмотр соседей на нулевом хопе.",
"repeater_settings": "Настройки", "repeater_settings": "Настройки",
"repeater_settingsSubtitle": "Настройка параметров репитера", "repeater_settingsSubtitle": "Настройка параметров репитера",
"repeater_statusTitle": "Статус репитера", "repeater_statusTitle": "Статус репитера",
@@ -661,7 +666,7 @@
"neighbors_receivedData": "Полученные данные о соседях", "neighbors_receivedData": "Полученные данные о соседях",
"neighbors_requestTimedOut": "Время ожидания данных о соседях истекло.", "neighbors_requestTimedOut": "Время ожидания данных о соседях истекло.",
"neighbors_errorLoading": "Ошибка загрузки соседей: {error}", "neighbors_errorLoading": "Ошибка загрузки соседей: {error}",
"neighbors_repeatersNeighbours": "Соседи репитеров", "neighbors_repeatersNeighbors": "Соседи репитеров",
"neighbors_noData": "Данные о соседях недоступны.", "neighbors_noData": "Данные о соседях недоступны.",
"neighbors_unknownContact": "Неизвестный {pubkey}", "neighbors_unknownContact": "Неизвестный {pubkey}",
"neighbors_heardA ago": "Слышали: {time} назад", "neighbors_heardA ago": "Слышали: {time} назад",
@@ -834,7 +839,6 @@
"scanner_enableBluetooth": "Включите Bluetooth", "scanner_enableBluetooth": "Включите Bluetooth",
"scanner_bluetoothOff": "Bluetooth выключен", "scanner_bluetoothOff": "Bluetooth выключен",
"scanner_bluetoothOffMessage": "Пожалуйста, включите Bluetooth, чтобы найти устройства.", "scanner_bluetoothOffMessage": "Пожалуйста, включите Bluetooth, чтобы найти устройства.",
"settings_clientRepeatFreqWarning": "Для работы в режиме \"без подключения к сети\" требуется частота 433, 869 или 918 МГц.", "snrIndicator_nearByRepeaters": "Ближайшие ретрансляторы",
"settings_clientRepeatSubtitle": "Позвольте этому устройству повторять пакеты данных для других устройств.", "snrIndicator_lastSeen": "Последний раз видели"
"settings_clientRepeat": "Повторение \"вне сети\""
} }
+10 -6
View File
@@ -131,6 +131,9 @@
"settings_infoContactsCount": "Počet kontaktov", "settings_infoContactsCount": "Počet kontaktov",
"settings_infoChannelCount": "Počet kanálov", "settings_infoChannelCount": "Počet kanálov",
"settings_presets": "Prednastavenia", "settings_presets": "Prednastavenia",
"settings_preset915Mhz": "915 MHz",
"settings_preset868Mhz": "868 MHz",
"settings_preset433Mhz": "433 MHz",
"settings_frequency": "Frekvencia (MHz)", "settings_frequency": "Frekvencia (MHz)",
"settings_frequencyHelper": "300,0 2500,0", "settings_frequencyHelper": "300,0 2500,0",
"settings_frequencyInvalid": "Neplatná frekvencia (300-2500 MHz)", "settings_frequencyInvalid": "Neplatná frekvencia (300-2500 MHz)",
@@ -140,6 +143,8 @@
"settings_txPower": "TX Výkon (dBm)", "settings_txPower": "TX Výkon (dBm)",
"settings_txPowerHelper": "0 - 22", "settings_txPowerHelper": "0 - 22",
"settings_txPowerInvalid": "Neplatná hodnota výkonu TX (0-22 dBm)", "settings_txPowerInvalid": "Neplatná hodnota výkonu TX (0-22 dBm)",
"settings_longRange": "Dlhý dosah",
"settings_fastSpeed": "Rýchla rýchlosť",
"settings_error": "Chyba: {message}", "settings_error": "Chyba: {message}",
"@settings_error": { "@settings_error": {
"placeholders": { "placeholders": {
@@ -1351,12 +1356,12 @@
} }
} }
}, },
"repeater_neighboursSubtitle": "Zobraziť susedné body bez skokov.", "repeater_neighborsSubtitle": "Zobraziť susedné body bez skokov.",
"neighbors_requestTimedOut": "Súďia žiadajú o časové ukončenie.", "neighbors_requestTimedOut": "Súďia žiadajú o časové ukončenie.",
"neighbors_receivedData": "Obdielo dáta suseda", "neighbors_receivedData": "Obdielo dáta suseda",
"repeater_neighbours": "Súsezný", "repeater_neighbors": "Súsezný",
"neighbors_errorLoading": "Chyba pri načítaní susedov: {error}", "neighbors_errorLoading": "Chyba pri načítaní susedov: {error}",
"neighbors_repeatersNeighbours": "Opakovadlá Súsezná", "neighbors_repeatersNeighbors": "Opakovadlá Súsezná",
"neighbors_noData": "Nie je dostupná žiadna informácia o susedoch.", "neighbors_noData": "Nie je dostupná žiadna informácia o susedoch.",
"channels_createPrivateChannel": "Vytvorte súkromný kanál", "channels_createPrivateChannel": "Vytvorte súkromný kanál",
"channels_joinPrivateChannel": "Pripojiť sa k súkromnému kanálu", "channels_joinPrivateChannel": "Pripojiť sa k súkromnému kanálu",
@@ -1594,7 +1599,6 @@
"scanner_bluetoothOffMessage": "Prosím, zapnite Bluetooth, aby ste mohli skenovať pre zariadenia.", "scanner_bluetoothOffMessage": "Prosím, zapnite Bluetooth, aby ste mohli skenovať pre zariadenia.",
"scanner_bluetoothOff": "Bluetooth je vypnutý", "scanner_bluetoothOff": "Bluetooth je vypnutý",
"scanner_enableBluetooth": "Povolte Bluetooth", "scanner_enableBluetooth": "Povolte Bluetooth",
"settings_clientRepeat": "Opätovné použitie bez elektrickej siete", "snrIndicator_lastSeen": "Naposledy videný",
"settings_clientRepeatFreqWarning": "Použitie off-grid systému vyžaduje frekvencie 433, 869 alebo 918 MHz.", "snrIndicator_nearByRepeaters": "Miestne opakovače"
"settings_clientRepeatSubtitle": "Umožnite, aby toto zariadenie opakovávalo siete pre ostatných."
} }
+10 -6
View File
@@ -131,6 +131,9 @@
"settings_infoContactsCount": "Število stikov", "settings_infoContactsCount": "Število stikov",
"settings_infoChannelCount": "Število kanalov", "settings_infoChannelCount": "Število kanalov",
"settings_presets": "Prednastavitve", "settings_presets": "Prednastavitve",
"settings_preset915Mhz": "915 MHz",
"settings_preset868Mhz": "868 MHz",
"settings_preset433Mhz": "433 MHz",
"settings_frequency": "Frekvenca (MHz)", "settings_frequency": "Frekvenca (MHz)",
"settings_frequencyHelper": "300,00 - 2500,00", "settings_frequencyHelper": "300,00 - 2500,00",
"settings_frequencyInvalid": "Neveljavna frekvenca (300-2500 MHz)", "settings_frequencyInvalid": "Neveljavna frekvenca (300-2500 MHz)",
@@ -140,6 +143,8 @@
"settings_txPower": "TX Moč (dBm)", "settings_txPower": "TX Moč (dBm)",
"settings_txPowerHelper": "0 - 22", "settings_txPowerHelper": "0 - 22",
"settings_txPowerInvalid": "Neveljavna TX moč (0-22 dBm)", "settings_txPowerInvalid": "Neveljavna TX moč (0-22 dBm)",
"settings_longRange": "DDolg doseg",
"settings_fastSpeed": "Visoka hitrost",
"settings_error": "Napaka: {message}", "settings_error": "Napaka: {message}",
"@settings_error": { "@settings_error": {
"placeholders": { "placeholders": {
@@ -1351,12 +1356,12 @@
} }
} }
}, },
"repeater_neighboursSubtitle": "Pogledati nič sosednjih hopjev.", "repeater_neighborsSubtitle": "Pogledati nič sosednjih hopjev.",
"repeater_neighbours": "Sosedi", "repeater_neighbors": "Sosedi",
"neighbors_receivedData": "Prejeto podatke o sosedih", "neighbors_receivedData": "Prejeto podatke o sosedih",
"neighbors_requestTimedOut": "Sosedi zahtevajo izklop po dogovoru.", "neighbors_requestTimedOut": "Sosedi zahtevajo izklop po dogovoru.",
"neighbors_errorLoading": "Napaka pri obnašanju sosedov: {error}", "neighbors_errorLoading": "Napaka pri obnašanju sosedov: {error}",
"neighbors_repeatersNeighbours": "Ponovitve Sosedi", "neighbors_repeatersNeighbors": "Ponovitve Sosedi",
"neighbors_noData": "Niso na voljo podatki o sosedih.", "neighbors_noData": "Niso na voljo podatki o sosedih.",
"channels_joinPrivateChannel": "Pridružite se zasebni skupini", "channels_joinPrivateChannel": "Pridružite se zasebni skupini",
"channels_createPrivateChannelDesc": "Varno zaklenjeno s skrivnim ključem.", "channels_createPrivateChannelDesc": "Varno zaklenjeno s skrivnim ključem.",
@@ -1594,7 +1599,6 @@
"scanner_enableBluetooth": "Omogočite Bluetooth", "scanner_enableBluetooth": "Omogočite Bluetooth",
"scanner_bluetoothOffMessage": "Prosimo, vklopite Bluetooth, da lahko poiščete naprave.", "scanner_bluetoothOffMessage": "Prosimo, vklopite Bluetooth, da lahko poiščete naprave.",
"scanner_bluetoothOff": "Bluetooth je izklopljen", "scanner_bluetoothOff": "Bluetooth je izklopljen",
"settings_clientRepeatFreqWarning": "Za ponovni prenos na brezžični način so potrebne frekvence 433, 869 ali 918 MHz.", "snrIndicator_lastSeen": "Zadnjič videno",
"settings_clientRepeatSubtitle": "Omogočite temu naprave, da ponavlja paketne sporočila za druge.", "snrIndicator_nearByRepeaters": "Bližnji ponovitelji"
"settings_clientRepeat": "Neovadno ponavljanje"
} }
+10 -6
View File
@@ -131,6 +131,9 @@
"settings_infoContactsCount": "Kontakterantal", "settings_infoContactsCount": "Kontakterantal",
"settings_infoChannelCount": "Kanalantal", "settings_infoChannelCount": "Kanalantal",
"settings_presets": "Fördefinierade inställningar", "settings_presets": "Fördefinierade inställningar",
"settings_preset915Mhz": "915 MHz",
"settings_preset868Mhz": "868 MHz",
"settings_preset433Mhz": "433 MHz",
"settings_frequency": "Frekvens (MHz)", "settings_frequency": "Frekvens (MHz)",
"settings_frequencyHelper": "300,0 - 2500,0", "settings_frequencyHelper": "300,0 - 2500,0",
"settings_frequencyInvalid": "Ogiltig frekvens (300-2500 MHz)", "settings_frequencyInvalid": "Ogiltig frekvens (300-2500 MHz)",
@@ -140,6 +143,8 @@
"settings_txPower": "TX-effekt (dBm)", "settings_txPower": "TX-effekt (dBm)",
"settings_txPowerHelper": "0 - 22", "settings_txPowerHelper": "0 - 22",
"settings_txPowerInvalid": "Ogiltig TX-effekt (0-22 dBm)", "settings_txPowerInvalid": "Ogiltig TX-effekt (0-22 dBm)",
"settings_longRange": "Lång räckvidd",
"settings_fastSpeed": "Snabb hastighet",
"settings_error": "Fel: {message}", "settings_error": "Fel: {message}",
"@settings_error": { "@settings_error": {
"placeholders": { "placeholders": {
@@ -1351,12 +1356,12 @@
} }
} }
}, },
"repeater_neighbours": "Grannar", "repeater_neighbors": "Grannar",
"repeater_neighboursSubtitle": "Visa noll hoppgrannar.", "repeater_neighborsSubtitle": "Visa noll hoppgrannar.",
"neighbors_receivedData": "Mottagna grannars data", "neighbors_receivedData": "Mottagna grannars data",
"neighbors_requestTimedOut": "Grannar begär tidsinställd utskick.", "neighbors_requestTimedOut": "Grannar begär tidsinställd utskick.",
"neighbors_errorLoading": "Fel vid inläsning av grannar: {error}", "neighbors_errorLoading": "Fel vid inläsning av grannar: {error}",
"neighbors_repeatersNeighbours": "Upprepar grannar", "neighbors_repeatersNeighbors": "Upprepar grannar",
"neighbors_noData": "Inga grannuppgifter finns tillgängliga.", "neighbors_noData": "Inga grannuppgifter finns tillgängliga.",
"channels_createPrivateChannel": "Skapa en privat kanal", "channels_createPrivateChannel": "Skapa en privat kanal",
"channels_joinPrivateChannel": "Gå med i en Privat Kanal", "channels_joinPrivateChannel": "Gå med i en Privat Kanal",
@@ -1594,7 +1599,6 @@
"scanner_enableBluetooth": "Aktivera Bluetooth", "scanner_enableBluetooth": "Aktivera Bluetooth",
"scanner_bluetoothOffMessage": "Vänligen aktivera Bluetooth för att söka efter enheter.", "scanner_bluetoothOffMessage": "Vänligen aktivera Bluetooth för att söka efter enheter.",
"scanner_bluetoothOff": "Bluetooth är avstängt", "scanner_bluetoothOff": "Bluetooth är avstängt",
"settings_clientRepeatSubtitle": "Låt enheten repetera nätpaket för andra användare.", "snrIndicator_lastSeen": "Senast sedd",
"settings_clientRepeat": "Upprepa utan elnät", "snrIndicator_nearByRepeaters": "Närliggande uppreparstationer"
"settings_clientRepeatFreqWarning": "För att kunna kommunicera utanför elnätet krävs frekvenserna 433, 869 eller 918 MHz."
} }
+10 -6
View File
@@ -131,6 +131,9 @@
"settings_infoContactsCount": "Кількість контактів", "settings_infoContactsCount": "Кількість контактів",
"settings_infoChannelCount": "Кількість каналів", "settings_infoChannelCount": "Кількість каналів",
"settings_presets": "Попередні налаштування", "settings_presets": "Попередні налаштування",
"settings_preset915Mhz": "915 МГц",
"settings_preset868Mhz": "868 МГц",
"settings_preset433Mhz": "433 МГц",
"settings_frequency": "Частота (МГц)", "settings_frequency": "Частота (МГц)",
"settings_frequencyHelper": "300.0 - 2500.0", "settings_frequencyHelper": "300.0 - 2500.0",
"settings_frequencyInvalid": "Некоректна частота (300-2500 МГц)", "settings_frequencyInvalid": "Некоректна частота (300-2500 МГц)",
@@ -140,6 +143,8 @@
"settings_txPower": "Потужність TX (дБм)", "settings_txPower": "Потужність TX (дБм)",
"settings_txPowerHelper": "0 - 22", "settings_txPowerHelper": "0 - 22",
"settings_txPowerInvalid": "Некоректна потужність TX (0-22 дБм)", "settings_txPowerInvalid": "Некоректна потужність TX (0-22 дБм)",
"settings_longRange": "Дальній діапазон",
"settings_fastSpeed": "Висока швидкість",
"settings_error": "Помилка: {message}", "settings_error": "Помилка: {message}",
"@settings_error": { "@settings_error": {
"placeholders": { "placeholders": {
@@ -1352,12 +1357,12 @@
} }
} }
}, },
"repeater_neighbours": "Сусіди", "repeater_neighbors": "Сусіди",
"repeater_neighboursSubtitle": "Показати сусідів нульового стрибка.", "repeater_neighborsSubtitle": "Показати сусідів нульового стрибка.",
"neighbors_receivedData": "Дані сусідів отримано", "neighbors_receivedData": "Дані сусідів отримано",
"neighbors_requestTimedOut": "Час запиту сусідів вичерпано.", "neighbors_requestTimedOut": "Час запиту сусідів вичерпано.",
"neighbors_errorLoading": "Помилка завантаження сусідів: {error}", "neighbors_errorLoading": "Помилка завантаження сусідів: {error}",
"neighbors_repeatersNeighbours": "Ретранслятори-сусіди", "neighbors_repeatersNeighbors": "Ретранслятори-сусіди",
"neighbors_noData": "Дані про сусідів недоступні.", "neighbors_noData": "Дані про сусідів недоступні.",
"channels_createPrivateChannelDesc": "Захищено секретним ключем.", "channels_createPrivateChannelDesc": "Захищено секретним ключем.",
"channels_joinPrivateChannel": "Приєднатися до приватного каналу", "channels_joinPrivateChannel": "Приєднатися до приватного каналу",
@@ -1594,7 +1599,6 @@
"scanner_enableBluetooth": "Увімкніть Bluetooth", "scanner_enableBluetooth": "Увімкніть Bluetooth",
"scanner_bluetoothOffMessage": "Будь ласка, увімкніть Bluetooth, щоб сканувати пристрої.", "scanner_bluetoothOffMessage": "Будь ласка, увімкніть Bluetooth, щоб сканувати пристрої.",
"scanner_bluetoothOff": "Bluetooth вимкнено", "scanner_bluetoothOff": "Bluetooth вимкнено",
"settings_clientRepeatFreqWarning": "Повтор без підключення до мережі вимагає частоти 433, 869 або 918 МГц.", "snrIndicator_lastSeen": "Останній раз бачили",
"settings_clientRepeatSubtitle": "Дозвольте цьому пристрою повторювати пакети даних для інших пристроїв.", "snrIndicator_nearByRepeaters": "Ближні ретранслятори"
"settings_clientRepeat": "Автономна система"
} }
+10 -6
View File
@@ -136,6 +136,9 @@
"settings_infoContactsCount": "联系人数量", "settings_infoContactsCount": "联系人数量",
"settings_infoChannelCount": "通道数量", "settings_infoChannelCount": "通道数量",
"settings_presets": "预设", "settings_presets": "预设",
"settings_preset915Mhz": "915 兆赫",
"settings_preset868Mhz": "868 兆赫",
"settings_preset433Mhz": "433 兆赫",
"settings_frequency": "频率 (MHz)", "settings_frequency": "频率 (MHz)",
"settings_frequencyHelper": "300.0 - 2500.0", "settings_frequencyHelper": "300.0 - 2500.0",
"settings_frequencyInvalid": "无效频率(300-2500 MHz", "settings_frequencyInvalid": "无效频率(300-2500 MHz",
@@ -145,6 +148,8 @@
"settings_txPower": "TX 功率(dBm", "settings_txPower": "TX 功率(dBm",
"settings_txPowerHelper": "0 - 22", "settings_txPowerHelper": "0 - 22",
"settings_txPowerInvalid": "无效的发射功率(0-22 dBm", "settings_txPowerInvalid": "无效的发射功率(0-22 dBm",
"settings_longRange": "远距离",
"settings_fastSpeed": "高速",
"settings_error": "[保存:{message}]\n错误:{message}", "settings_error": "[保存:{message}]\n错误:{message}",
"@settings_error": { "@settings_error": {
"placeholders": { "placeholders": {
@@ -895,8 +900,8 @@
"repeater_telemetrySubtitle": "查看传感器和系统状态的数据。", "repeater_telemetrySubtitle": "查看传感器和系统状态的数据。",
"repeater_cli": "命令行界面", "repeater_cli": "命令行界面",
"repeater_cliSubtitle": "向复用器发送指令", "repeater_cliSubtitle": "向复用器发送指令",
"repeater_neighbours": "邻居", "repeater_neighbors": "邻居",
"repeater_neighboursSubtitle": "查看邻居节点(无需中间节点)。", "repeater_neighborsSubtitle": "查看邻居节点(无需中间节点)。",
"repeater_settings": "设置", "repeater_settings": "设置",
"repeater_settingsSubtitle": "配置重复器参数", "repeater_settingsSubtitle": "配置重复器参数",
"repeater_statusTitle": "重复器状态", "repeater_statusTitle": "重复器状态",
@@ -1266,7 +1271,7 @@
} }
} }
}, },
"neighbors_repeatersNeighbours": "重复使用的邻居", "neighbors_repeatersNeighbors": "重复使用的邻居",
"neighbors_noData": "没有可用的邻居信息。", "neighbors_noData": "没有可用的邻居信息。",
"neighbors_unknownContact": "Unknown {pubkey}", "neighbors_unknownContact": "Unknown {pubkey}",
"@neighbors_unknownContact": { "@neighbors_unknownContact": {
@@ -1594,7 +1599,6 @@
"scanner_bluetoothOffMessage": "请打开蓝牙功能,以便搜索设备。", "scanner_bluetoothOffMessage": "请打开蓝牙功能,以便搜索设备。",
"scanner_bluetoothOff": "蓝牙已关闭", "scanner_bluetoothOff": "蓝牙已关闭",
"scanner_enableBluetooth": "启用蓝牙", "scanner_enableBluetooth": "启用蓝牙",
"settings_clientRepeat": "离网重复", "snrIndicator_lastSeen": "最近访问",
"settings_clientRepeatSubtitle": "允许此设备重复发送网状数据包给其他设备", "snrIndicator_nearByRepeaters": "附近的重复器"
"settings_clientRepeatFreqWarning": "离网重复通信需要使用 433、869 或 918 兆赫兹的频率。"
} }
+38 -34
View File
@@ -119,7 +119,7 @@ class Contact {
final pathBytes = _pathBytesForDisplay; final pathBytes = _pathBytesForDisplay;
Uint8List? traceBytes; Uint8List? traceBytes;
if (pathLength <= 0) { if (pathBytes.isEmpty) {
traceBytes = Uint8List(1); traceBytes = Uint8List(1);
traceBytes[0] = publicKey[0]; traceBytes[0] = publicKey[0];
return traceBytes; return traceBytes;
@@ -160,43 +160,47 @@ class Contact {
} }
static Contact? fromFrame(Uint8List data) { static Contact? fromFrame(Uint8List data) {
if (data.length < contactFrameSize) return null; if (data.isEmpty) return null;
if (data[0] != respCodeContact) return null; if (data[0] != respCodeContact) return null;
try {
final pubKey = Uint8List.fromList(
data.sublist(contactPubKeyOffset, contactPubKeyOffset + pubKeySize),
);
final type = data[contactTypeOffset];
final pathLen = data[contactPathLenOffset].toSigned(8);
final safePathLen = pathLen > 0
? (pathLen > maxPathSize ? maxPathSize : pathLen)
: 0;
final pathBytes = safePathLen > 0
? Uint8List.fromList(
data.sublist(contactPathOffset, contactPathOffset + safePathLen),
)
: Uint8List(0);
final name = readCString(data, contactNameOffset, maxNameSize);
final lastmod = readUint32LE(data, contactLastmodOffset);
final pubKey = Uint8List.fromList( double? lat, lon;
data.sublist(contactPubKeyOffset, contactPubKeyOffset + pubKeySize), final latRaw = readInt32LE(data, contactLatOffset);
); final lonRaw = readInt32LE(data, contactLonOffset);
final type = data[contactTypeOffset]; if (latRaw != 0 || lonRaw != 0) {
final pathLen = data[contactPathLenOffset].toSigned(8); lat = latRaw / 1e6;
final safePathLen = pathLen > 0 lon = lonRaw / 1e6;
? (pathLen > maxPathSize ? maxPathSize : pathLen) }
: 0;
final pathBytes = safePathLen > 0
? Uint8List.fromList(
data.sublist(contactPathOffset, contactPathOffset + safePathLen),
)
: Uint8List(0);
final name = readCString(data, contactNameOffset, maxNameSize);
final lastmod = readUint32LE(data, contactLastmodOffset);
double? lat, lon; return Contact(
final latRaw = readInt32LE(data, contactLatOffset); publicKey: pubKey,
final lonRaw = readInt32LE(data, contactLonOffset); name: name.isEmpty ? 'Unknown' : name,
if (latRaw != 0 || lonRaw != 0) { type: type,
lat = latRaw / 1e6; pathLength: pathLen,
lon = lonRaw / 1e6; path: pathBytes,
latitude: lat,
longitude: lon,
lastSeen: DateTime.fromMillisecondsSinceEpoch(lastmod * 1000),
);
} catch (e) {
// If parsing fails, return null
return null;
} }
return Contact(
publicKey: pubKey,
name: name.isEmpty ? 'Unknown' : name,
type: type,
pathLength: pathLen,
path: pathBytes,
latitude: lat,
longitude: lon,
lastSeen: DateTime.fromMillisecondsSinceEpoch(lastmod * 1000),
);
} }
@override @override
+40 -194
View File
@@ -59,200 +59,46 @@ class RadioSettings {
required this.txPowerDbm, required this.txPowerDbm,
}); });
// Regional preset configurations // Preset configurations
static final List<(String, RadioSettings)> presets = [ static RadioSettings get preset915MHz => RadioSettings(
( frequencyMHz: 915.0,
'Australia', bandwidth: LoRaBandwidth.bw125,
RadioSettings( spreadingFactor: LoRaSpreadingFactor.sf7,
frequencyMHz: 915.8, codingRate: LoRaCodingRate.cr4_5,
bandwidth: LoRaBandwidth.bw250, txPowerDbm: 20,
spreadingFactor: LoRaSpreadingFactor.sf10, );
codingRate: LoRaCodingRate.cr4_5,
txPowerDbm: 20, static RadioSettings get preset868MHz => RadioSettings(
), frequencyMHz: 868.0,
), bandwidth: LoRaBandwidth.bw125,
( spreadingFactor: LoRaSpreadingFactor.sf7,
'Australia (Narrow)', codingRate: LoRaCodingRate.cr4_5,
RadioSettings( txPowerDbm: 14,
frequencyMHz: 916.575, );
bandwidth: LoRaBandwidth.bw62_5,
spreadingFactor: LoRaSpreadingFactor.sf7, static RadioSettings get preset433MHz => RadioSettings(
codingRate: LoRaCodingRate.cr4_5, frequencyMHz: 433.0,
txPowerDbm: 20, bandwidth: LoRaBandwidth.bw125,
), spreadingFactor: LoRaSpreadingFactor.sf7,
), codingRate: LoRaCodingRate.cr4_5,
( txPowerDbm: 20,
'Australia SA, WA, QLD', );
RadioSettings(
frequencyMHz: 923.125, static RadioSettings get presetLongRange => RadioSettings(
bandwidth: LoRaBandwidth.bw62_5, frequencyMHz: 915.0,
spreadingFactor: LoRaSpreadingFactor.sf8, bandwidth: LoRaBandwidth.bw125,
codingRate: LoRaCodingRate.cr4_5, spreadingFactor: LoRaSpreadingFactor.sf12,
txPowerDbm: 20, codingRate: LoRaCodingRate.cr4_8,
), txPowerDbm: 20,
), );
(
'Czech Republic', static RadioSettings get presetFastSpeed => RadioSettings(
RadioSettings( frequencyMHz: 915.0,
frequencyMHz: 869.432, bandwidth: LoRaBandwidth.bw500,
bandwidth: LoRaBandwidth.bw62_5, spreadingFactor: LoRaSpreadingFactor.sf7,
spreadingFactor: LoRaSpreadingFactor.sf7, codingRate: LoRaCodingRate.cr4_5,
codingRate: LoRaCodingRate.cr4_5, txPowerDbm: 20,
txPowerDbm: 14, );
),
),
(
'EU 433MHz',
RadioSettings(
frequencyMHz: 433.650,
bandwidth: LoRaBandwidth.bw250,
spreadingFactor: LoRaSpreadingFactor.sf11,
codingRate: LoRaCodingRate.cr4_5,
txPowerDbm: 20,
),
),
(
'EU/UK (Long Range)',
RadioSettings(
frequencyMHz: 869.525,
bandwidth: LoRaBandwidth.bw250,
spreadingFactor: LoRaSpreadingFactor.sf11,
codingRate: LoRaCodingRate.cr4_5,
txPowerDbm: 14,
),
),
(
'EU/UK (Medium Range)',
RadioSettings(
frequencyMHz: 869.525,
bandwidth: LoRaBandwidth.bw250,
spreadingFactor: LoRaSpreadingFactor.sf10,
codingRate: LoRaCodingRate.cr4_5,
txPowerDbm: 14,
),
),
(
'EU/UK (Narrow)',
RadioSettings(
frequencyMHz: 869.618,
bandwidth: LoRaBandwidth.bw62_5,
spreadingFactor: LoRaSpreadingFactor.sf8,
codingRate: LoRaCodingRate.cr4_5,
txPowerDbm: 14,
),
),
(
'New Zealand',
RadioSettings(
frequencyMHz: 917.375,
bandwidth: LoRaBandwidth.bw250,
spreadingFactor: LoRaSpreadingFactor.sf11,
codingRate: LoRaCodingRate.cr4_5,
txPowerDbm: 20,
),
),
(
'New Zealand (Narrow)',
RadioSettings(
frequencyMHz: 917.375,
bandwidth: LoRaBandwidth.bw62_5,
spreadingFactor: LoRaSpreadingFactor.sf7,
codingRate: LoRaCodingRate.cr4_5,
txPowerDbm: 20,
),
),
(
'Portugal 433',
RadioSettings(
frequencyMHz: 433.375,
bandwidth: LoRaBandwidth.bw62_5,
spreadingFactor: LoRaSpreadingFactor.sf9,
codingRate: LoRaCodingRate.cr4_5,
txPowerDbm: 20,
),
),
(
'Portugal 869',
RadioSettings(
frequencyMHz: 869.618,
bandwidth: LoRaBandwidth.bw62_5,
spreadingFactor: LoRaSpreadingFactor.sf7,
codingRate: LoRaCodingRate.cr4_5,
txPowerDbm: 14,
),
),
(
'Switzerland',
RadioSettings(
frequencyMHz: 869.618,
bandwidth: LoRaBandwidth.bw62_5,
spreadingFactor: LoRaSpreadingFactor.sf8,
codingRate: LoRaCodingRate.cr4_5,
txPowerDbm: 14,
),
),
(
'USA Arizona',
RadioSettings(
frequencyMHz: 908.205,
bandwidth: LoRaBandwidth.bw62_5,
spreadingFactor: LoRaSpreadingFactor.sf10,
codingRate: LoRaCodingRate.cr4_5,
txPowerDbm: 20,
),
),
(
'USA/Canada',
RadioSettings(
frequencyMHz: 910.525,
bandwidth: LoRaBandwidth.bw62_5,
spreadingFactor: LoRaSpreadingFactor.sf7,
codingRate: LoRaCodingRate.cr4_5,
txPowerDbm: 20,
),
),
(
'Vietnam',
RadioSettings(
frequencyMHz: 920.250,
bandwidth: LoRaBandwidth.bw250,
spreadingFactor: LoRaSpreadingFactor.sf11,
codingRate: LoRaCodingRate.cr4_5,
txPowerDbm: 20,
),
),
// Off-grid repeat presets (valid client_repeat frequencies)
(
'Off-Grid 433',
RadioSettings(
frequencyMHz: 433.0,
bandwidth: LoRaBandwidth.bw250,
spreadingFactor: LoRaSpreadingFactor.sf11,
codingRate: LoRaCodingRate.cr4_5,
txPowerDbm: 20,
),
),
(
'Off-Grid 869',
RadioSettings(
frequencyMHz: 869.0,
bandwidth: LoRaBandwidth.bw250,
spreadingFactor: LoRaSpreadingFactor.sf11,
codingRate: LoRaCodingRate.cr4_5,
txPowerDbm: 14,
),
),
(
'Off-Grid 918',
RadioSettings(
frequencyMHz: 918.0,
bandwidth: LoRaBandwidth.bw250,
spreadingFactor: LoRaSpreadingFactor.sf11,
codingRate: LoRaCodingRate.cr4_5,
txPowerDbm: 20,
),
),
];
int get frequencyHz => (frequencyMHz * 1000).round(); int get frequencyHz => (frequencyMHz * 1000).round();
int get bandwidthHz => bandwidth.hz; int get bandwidthHz => bandwidth.hz;
+2 -1
View File
@@ -901,7 +901,8 @@ class _ChannelChatScreenState extends State<ChannelChatScreen> {
Navigator.push( Navigator.push(
context, context,
MaterialPageRoute( MaterialPageRoute(
builder: (context) => ChannelMessagePathScreen(message: message), builder: (context) =>
ChannelMessagePathScreen(message: message, channelMessage: true),
), ),
); );
} }
+49 -11
View File
@@ -17,18 +17,27 @@ import '../models/contact.dart';
class ChannelMessagePathScreen extends StatelessWidget { class ChannelMessagePathScreen extends StatelessWidget {
final ChannelMessage message; final ChannelMessage message;
final bool channelMessage;
const ChannelMessagePathScreen({super.key, required this.message}); const ChannelMessagePathScreen({
super.key,
required this.message,
this.channelMessage = false,
});
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Consumer<MeshCoreConnector>( return Consumer<MeshCoreConnector>(
builder: (context, connector, _) { builder: (context, connector, _) {
final l10n = context.l10n; final l10n = context.l10n;
final primaryPath = _selectPrimaryPath( final primaryPathTmp = _selectPrimaryPath(
message.pathBytes, message.pathBytes,
message.pathVariants, message.pathVariants,
); );
final primaryPath = !channelMessage && !message.isOutgoing
? Uint8List.fromList(primaryPathTmp.reversed.toList())
: primaryPathTmp;
final hops = _buildPathHops(primaryPath, connector.contacts, l10n); final hops = _buildPathHops(primaryPath, connector.contacts, l10n);
final hasHopDetails = primaryPath.isNotEmpty; final hasHopDetails = primaryPath.isNotEmpty;
final observedLabel = _formatObservedHops( final observedLabel = _formatObservedHops(
@@ -37,7 +46,6 @@ class ChannelMessagePathScreen extends StatelessWidget {
l10n, l10n,
); );
final extraPaths = _otherPaths(primaryPath, message.pathVariants); final extraPaths = _otherPaths(primaryPath, message.pathVariants);
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
title: Text(l10n.channelPath_title), title: Text(l10n.channelPath_title),
@@ -50,9 +58,9 @@ class ChannelMessagePathScreen extends StatelessWidget {
MaterialPageRoute( MaterialPageRoute(
builder: (context) => PathTraceMapScreen( builder: (context) => PathTraceMapScreen(
title: context.l10n.contacts_repeaterPathTrace, title: context.l10n.contacts_repeaterPathTrace,
path: Uint8List.fromList(primaryPath), path: primaryPath,
flipPathRound: true, flipPathRound: true,
reversePathRound: true, reversePathRound: !message.isOutgoing && !channelMessage,
), ),
), ),
), ),
@@ -62,7 +70,7 @@ class ChannelMessagePathScreen extends StatelessWidget {
tooltip: l10n.channelPath_viewMap, tooltip: l10n.channelPath_viewMap,
onPressed: hasHopDetails onPressed: hasHopDetails
? () { ? () {
_openPathMap(context); _openPathMap(context, channelMessage: channelMessage);
} }
: null, : null,
), ),
@@ -157,7 +165,11 @@ class ChannelMessagePathScreen extends StatelessWidget {
), ),
subtitle: Text(_formatPathPrefixes(variants[i])), subtitle: Text(_formatPathPrefixes(variants[i])),
trailing: const Icon(Icons.map_outlined, size: 20), trailing: const Icon(Icons.map_outlined, size: 20),
onTap: () => _openPathMap(context, initialPath: variants[i]), onTap: () => _openPathMap(
context,
initialPath: variants[i],
channelMessage: channelMessage,
),
), ),
), ),
], ],
@@ -248,13 +260,18 @@ class ChannelMessagePathScreen extends StatelessWidget {
); );
} }
void _openPathMap(BuildContext context, {Uint8List? initialPath}) { void _openPathMap(
BuildContext context, {
Uint8List? initialPath,
bool channelMessage = false,
}) {
Navigator.push( Navigator.push(
context, context,
MaterialPageRoute( MaterialPageRoute(
builder: (context) => ChannelMessagePathMapScreen( builder: (context) => ChannelMessagePathMapScreen(
message: message, message: message,
initialPath: initialPath, initialPath: initialPath,
channelMessage: channelMessage,
), ),
), ),
); );
@@ -264,11 +281,13 @@ class ChannelMessagePathScreen extends StatelessWidget {
class ChannelMessagePathMapScreen extends StatefulWidget { class ChannelMessagePathMapScreen extends StatefulWidget {
final ChannelMessage message; final ChannelMessage message;
final Uint8List? initialPath; final Uint8List? initialPath;
final bool channelMessage;
const ChannelMessagePathMapScreen({ const ChannelMessagePathMapScreen({
super.key, super.key,
required this.message, required this.message,
this.initialPath, this.initialPath,
this.channelMessage = false,
}); });
@override @override
@@ -323,11 +342,18 @@ class _ChannelMessagePathMapScreenState
primaryPath, primaryPath,
widget.message.pathVariants, widget.message.pathVariants,
); );
final selectedPath = _resolveSelectedPath( final selectedPathTmp = _resolveSelectedPath(
_selectedPath, _selectedPath,
observedPaths, observedPaths,
primaryPath, primaryPath,
); );
final selectedPath =
((!widget.message.isOutgoing && !widget.channelMessage) ||
(widget.message.isOutgoing && widget.channelMessage))
? Uint8List.fromList(selectedPathTmp.reversed.toList())
: selectedPathTmp;
final selectedIndex = _indexForPath(selectedPath, observedPaths); final selectedIndex = _indexForPath(selectedPath, observedPaths);
final hops = _buildPathHops( final hops = _buildPathHops(
selectedPath, selectedPath,
@@ -336,12 +362,24 @@ class _ChannelMessagePathMapScreenState
); );
final points = <LatLng>[]; final points = <LatLng>[];
print(
'outgoing: ${widget.message.isOutgoing}, channelMsg: ${widget.channelMessage}',
);
if ((widget.message.isOutgoing && !widget.channelMessage) ||
(widget.message.isOutgoing && widget.channelMessage)) {
points.add(LatLng(connector.selfLatitude!, connector.selfLongitude!));
}
for (final hop in hops) { for (final hop in hops) {
if (hop.hasLocation) { if (hop.hasLocation) {
points.add(hop.position!); points.add(hop.position!);
} }
} }
points.add(LatLng(connector.selfLatitude!, connector.selfLongitude!));
if ((!widget.message.isOutgoing && !widget.channelMessage) ||
(!widget.message.isOutgoing && widget.channelMessage)) {
points.add(LatLng(connector.selfLatitude!, connector.selfLongitude!));
}
final polylines = points.length > 1 final polylines = points.length > 1
? [ ? [
+2 -3
View File
@@ -3,6 +3,7 @@ import 'dart:math';
import 'dart:typed_data'; import 'dart:typed_data';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:meshcore_open/widgets/app_bar.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:uuid/uuid.dart'; import 'package:uuid/uuid.dart';
@@ -14,7 +15,6 @@ import '../storage/community_store.dart';
import '../utils/dialog_utils.dart'; import '../utils/dialog_utils.dart';
import '../utils/disconnect_navigation_mixin.dart'; import '../utils/disconnect_navigation_mixin.dart';
import '../utils/route_transitions.dart'; import '../utils/route_transitions.dart';
import '../widgets/battery_indicator.dart';
import '../widgets/list_filter_widget.dart'; import '../widgets/list_filter_widget.dart';
import '../widgets/empty_state.dart'; import '../widgets/empty_state.dart';
import '../widgets/qr_code_display.dart'; import '../widgets/qr_code_display.dart';
@@ -116,8 +116,7 @@ class _ChannelsScreenState extends State<ChannelsScreen>
canPop: allowBack, canPop: allowBack,
child: Scaffold( child: Scaffold(
appBar: AppBar( appBar: AppBar(
leading: BatteryIndicator(connector: connector), title: AppBarTitle(context.l10n.channels_title),
title: Text(context.l10n.channels_title),
centerTitle: true, centerTitle: true,
automaticallyImplyLeading: false, automaticallyImplyLeading: false,
actions: [ actions: [
+40 -3
View File
@@ -437,6 +437,20 @@ class _ChatScreenState extends State<ChatScreen> {
builder: (context) => Consumer<PathHistoryService>( builder: (context) => Consumer<PathHistoryService>(
builder: (context, pathService, _) { builder: (context, pathService, _) {
final paths = pathService.getRecentPaths(widget.contact.publicKeyHex); final paths = pathService.getRecentPaths(widget.contact.publicKeyHex);
final repeatersList = List.of(connector.directRepeaters)
..sort((a, b) => b.ranking.compareTo(a.ranking));
final directRepeater = repeatersList.isEmpty
? null
: repeatersList.first;
final secondDirectRepeater = repeatersList.length < 2
? null
: repeatersList.elementAt(1);
final thirdDirectRepeater = repeatersList.length < 3
? null
: repeatersList.elementAt(2);
return AlertDialog( return AlertDialog(
title: Row( title: Row(
children: [ children: [
@@ -478,15 +492,38 @@ class _ChatScreenState extends State<ChatScreen> {
], ],
const SizedBox(height: 8), const SizedBox(height: 8),
...paths.map((path) { ...paths.map((path) {
final isDirectRepeater =
directRepeater != null &&
path.pathBytes.isNotEmpty &&
directRepeater.pubkeyFirstByte ==
path.pathBytes.first;
final isSecondDirectRepeater =
secondDirectRepeater != null &&
path.pathBytes.isNotEmpty &&
secondDirectRepeater.pubkeyFirstByte ==
path.pathBytes.first;
final isThirdDirectRepeater =
thirdDirectRepeater != null &&
path.pathBytes.isNotEmpty &&
thirdDirectRepeater.pubkeyFirstByte ==
path.pathBytes.first;
Color color = Colors.grey;
if (isDirectRepeater) {
color = Colors.green;
} else if (isSecondDirectRepeater) {
color = Colors.yellow;
} else if (isThirdDirectRepeater) {
color = Colors.red;
} else if (path.wasFloodDiscovery) {
color = Colors.blue;
}
return Card( return Card(
margin: const EdgeInsets.symmetric(vertical: 4), margin: const EdgeInsets.symmetric(vertical: 4),
child: ListTile( child: ListTile(
dense: true, dense: true,
leading: CircleAvatar( leading: CircleAvatar(
radius: 16, radius: 16,
backgroundColor: path.wasFloodDiscovery backgroundColor: color,
? Colors.blue
: Colors.green,
child: Text( child: Text(
'${path.hopCount}', '${path.hopCount}',
style: const TextStyle(fontSize: 12), style: const TextStyle(fontSize: 12),
+76 -66
View File
@@ -3,6 +3,8 @@ import 'dart:async';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:meshcore_open/screens/path_trace_map.dart'; import 'package:meshcore_open/screens/path_trace_map.dart';
import 'package:meshcore_open/utils/app_logger.dart';
import 'package:meshcore_open/widgets/app_bar.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import '../connector/meshcore_connector.dart'; import '../connector/meshcore_connector.dart';
@@ -16,7 +18,6 @@ import '../utils/dialog_utils.dart';
import '../utils/disconnect_navigation_mixin.dart'; import '../utils/disconnect_navigation_mixin.dart';
import '../utils/emoji_utils.dart'; import '../utils/emoji_utils.dart';
import '../utils/route_transitions.dart'; import '../utils/route_transitions.dart';
import '../widgets/battery_indicator.dart';
import '../widgets/list_filter_widget.dart'; import '../widgets/list_filter_widget.dart';
import '../widgets/empty_state.dart'; import '../widgets/empty_state.dart';
import '../widgets/quick_switch_bar.dart'; import '../widgets/quick_switch_bar.dart';
@@ -90,79 +91,90 @@ class _ContactsScreenState extends State<ContactsScreen>
_frameSubscription = connector.receivedFrames.listen((frame) { _frameSubscription = connector.receivedFrames.listen((frame) {
if (frame.isEmpty) return; if (frame.isEmpty) return;
final frameBuffer = BufferReader(frame); final frameBuffer = BufferReader(frame);
final code = frameBuffer.readUInt8(); try {
final code = frameBuffer.readUInt8();
if (code == respCodeExportContact) { if (code == respCodeExportContact) {
final advertPacket = frameBuffer.readRemainingBytes(); final advertPacket = frameBuffer.readRemainingBytes();
// Validate packet has expected minimum size (98+ bytes per protocol) // Validate packet has expected minimum size (98+ bytes per protocol)
if (advertPacket.length < 98) { if (advertPacket.length < 98) {
if (mounted) { if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(context.l10n.contacts_invalidAdvertFormat),
),
);
}
_pendingOperations.remove(ContactOperationType.export);
return;
}
final hexString = pubKeyToHex(advertPacket);
Clipboard.setData(ClipboardData(text: "meshcore://$hexString"));
}
if (code == respCodeOk) {
// Show a snackbar indicating success
if (!mounted) return;
if (_pendingOperations.contains(ContactOperationType.import)) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(context.l10n.contacts_contactImported)),
);
}
if (_pendingOperations.contains(ContactOperationType.zeroHopShare)) {
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(context).showSnackBar(
SnackBar( SnackBar(
content: Text(context.l10n.contacts_invalidAdvertFormat), content: Text(context.l10n.contacts_zeroHopContactAdvertSent),
), ),
); );
} }
_pendingOperations.remove(ContactOperationType.export);
return;
}
final hexString = pubKeyToHex(advertPacket);
Clipboard.setData(ClipboardData(text: "meshcore://$hexString"));
}
if (code == respCodeOk) { if (_pendingOperations.contains(ContactOperationType.export)) {
// Show a snackbar indicating success ScaffoldMessenger.of(context).showSnackBar(
if (!mounted) return; SnackBar(
content: Text(context.l10n.contacts_contactAdvertCopied),
),
);
}
if (_pendingOperations.contains(ContactOperationType.import)) { _pendingOperations.clear();
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(context.l10n.contacts_contactImported)),
);
} }
if (_pendingOperations.contains(ContactOperationType.zeroHopShare)) { if (code == respCodeErr) {
ScaffoldMessenger.of(context).showSnackBar( // Show a snackbar indicating failure
SnackBar( if (!mounted) return;
content: Text(context.l10n.contacts_zeroHopContactAdvertSent),
), if (_pendingOperations.contains(ContactOperationType.import)) {
); ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(context.l10n.contacts_contactImportFailed),
),
);
}
if (_pendingOperations.contains(ContactOperationType.zeroHopShare)) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(context.l10n.contacts_zeroHopContactAdvertFailed),
),
);
}
if (_pendingOperations.contains(ContactOperationType.export)) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(context.l10n.contacts_contactAdvertCopyFailed),
),
);
}
_pendingOperations.clear();
} }
} catch (e) {
if (_pendingOperations.contains(ContactOperationType.export)) { appLogger.error(
ScaffoldMessenger.of(context).showSnackBar( 'Error processing received frame: $e',
SnackBar(content: Text(context.l10n.contacts_contactAdvertCopied)), tag: 'ContactsScreen',
); );
}
_pendingOperations.clear();
}
if (code == respCodeErr) {
// Show a snackbar indicating failure
if (!mounted) return;
if (_pendingOperations.contains(ContactOperationType.import)) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(context.l10n.contacts_contactImportFailed)),
);
}
if (_pendingOperations.contains(ContactOperationType.zeroHopShare)) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(context.l10n.contacts_zeroHopContactAdvertFailed),
),
);
}
if (_pendingOperations.contains(ContactOperationType.export)) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(context.l10n.contacts_contactAdvertCopyFailed),
),
);
}
_pendingOperations.clear();
} }
}); });
} }
@@ -229,9 +241,7 @@ class _ContactsScreenState extends State<ContactsScreen>
canPop: allowBack, canPop: allowBack,
child: Scaffold( child: Scaffold(
appBar: AppBar( appBar: AppBar(
leading: BatteryIndicator(connector: connector), title: AppBarTitle(context.l10n.contacts_title),
title: Text(context.l10n.contacts_title),
centerTitle: true,
automaticallyImplyLeading: false, automaticallyImplyLeading: false,
actions: [ actions: [
PopupMenuButton( PopupMenuButton(
+7 -8
View File
@@ -5,6 +5,7 @@ import 'package:flutter/material.dart';
import 'package:flutter_map/flutter_map.dart'; import 'package:flutter_map/flutter_map.dart';
import 'package:latlong2/latlong.dart'; import 'package:latlong2/latlong.dart';
import 'package:meshcore_open/screens/path_trace_map.dart'; import 'package:meshcore_open/screens/path_trace_map.dart';
import 'package:meshcore_open/widgets/app_bar.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import '../connector/meshcore_connector.dart'; import '../connector/meshcore_connector.dart';
@@ -17,7 +18,6 @@ import '../services/map_marker_service.dart';
import '../services/map_tile_cache_service.dart'; import '../services/map_tile_cache_service.dart';
import '../utils/contact_search.dart'; import '../utils/contact_search.dart';
import '../utils/route_transitions.dart'; import '../utils/route_transitions.dart';
import '../widgets/battery_indicator.dart';
import '../widgets/quick_switch_bar.dart'; import '../widgets/quick_switch_bar.dart';
import 'channels_screen.dart'; import 'channels_screen.dart';
import 'chat_screen.dart'; import 'chat_screen.dart';
@@ -105,7 +105,7 @@ class _MapScreenState extends State<MapScreen> {
double _zoomFromStdDev(double latStdDev, double lonStdDev) { double _zoomFromStdDev(double latStdDev, double lonStdDev) {
final maxSpread = max(latStdDev, lonStdDev); final maxSpread = max(latStdDev, lonStdDev);
if (maxSpread <= 0) return 13.0; if (maxSpread <= 0) return 13.0;
// Approzimate: each zoom level halves the visible area // Approximate: each zoom level halves the visible area
// ~0.01 degrees spread -> zoom 13, ~0.1 -> zoom 10, ~1.0 -> zoom 7 // ~0.01 degrees spread -> zoom 13, ~0.1 -> zoom 10, ~1.0 -> zoom 7
final zoom = 10.0 - log(maxSpread * 10 + 1) / ln10 * 3; final zoom = 10.0 - log(maxSpread * 10 + 1) / ln10 * 3;
return zoom.clamp(4.0, 15.0); return zoom.clamp(4.0, 15.0);
@@ -262,8 +262,7 @@ class _MapScreenState extends State<MapScreen> {
canPop: allowBack, canPop: allowBack,
child: Scaffold( child: Scaffold(
appBar: AppBar( appBar: AppBar(
leading: BatteryIndicator(connector: connector), title: AppBarTitle(context.l10n.map_title),
title: Text(context.l10n.map_title),
centerTitle: true, centerTitle: true,
automaticallyImplyLeading: false, automaticallyImplyLeading: false,
actions: [ actions: [
@@ -384,8 +383,8 @@ class _MapScreenState extends State<MapScreen> {
connector.selfLatitude!, connector.selfLatitude!,
connector.selfLongitude!, connector.selfLongitude!,
), ),
width: 35, width: 40,
height: 35, height: 40,
child: Container( child: Container(
padding: const EdgeInsets.all(4), padding: const EdgeInsets.all(4),
decoration: BoxDecoration( decoration: BoxDecoration(
@@ -826,7 +825,7 @@ class _MapScreenState extends State<MapScreen> {
color: _getNodeColor(contact.type), color: _getNodeColor(contact.type),
), ),
const SizedBox(width: 8), const SizedBox(width: 8),
Expanded(child: Text(contact.name)), Expanded(child: SelectableText(contact.name)),
], ],
), ),
content: Column( content: Column(
@@ -997,7 +996,7 @@ class _MapScreenState extends State<MapScreen> {
), ),
), ),
const SizedBox(height: 2), const SizedBox(height: 2),
Text(value, style: const TextStyle(fontSize: 14)), SelectableText(value, style: const TextStyle(fontSize: 14)),
], ],
), ),
); );
@@ -1,6 +1,7 @@
import 'dart:async'; import 'dart:async';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:meshcore_open/utils/app_logger.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import '../l10n/l10n.dart'; import '../l10n/l10n.dart';
import '../models/contact.dart'; import '../models/contact.dart';
@@ -11,28 +12,28 @@ import '../services/repeater_command_service.dart';
import '../widgets/path_management_dialog.dart'; import '../widgets/path_management_dialog.dart';
import '../widgets/snr_indicator.dart'; import '../widgets/snr_indicator.dart';
class NeighboursScreen extends StatefulWidget { class NeighborsScreen extends StatefulWidget {
final Contact repeater; final Contact repeater;
final String password; final String password;
const NeighboursScreen({ const NeighborsScreen({
super.key, super.key,
required this.repeater, required this.repeater,
required this.password, required this.password,
}); });
@override @override
State<NeighboursScreen> createState() => _NeighboursScreenState(); State<NeighborsScreen> createState() => _NeighborsScreenState();
} }
class _NeighboursScreenState extends State<NeighboursScreen> { class _NeighborsScreenState extends State<NeighborsScreen> {
static const int _reqNeighboursKeyLen = 4; static const int _reqNeighborsKeyLen = 4;
static const int _statusPayloadOffset = 8; static const int _statusPayloadOffset = 8;
static const int _statusStatsSize = 52; static const int _statusStatsSize = 52;
static const int _statusResponseBytes = static const int _statusResponseBytes =
_statusPayloadOffset + _statusStatsSize; _statusPayloadOffset + _statusStatsSize;
Uint8List _tagData = Uint8List(4); Uint8List _tagData = Uint8List(4);
int _neighbourCount = 0; int _neighborCount = 0;
bool _isLoading = false; bool _isLoading = false;
bool _isLoaded = false; bool _isLoaded = false;
@@ -41,7 +42,7 @@ class _NeighboursScreenState extends State<NeighboursScreen> {
StreamSubscription<Uint8List>? _frameSubscription; StreamSubscription<Uint8List>? _frameSubscription;
RepeaterCommandService? _commandService; RepeaterCommandService? _commandService;
PathSelection? _pendingStatusSelection; PathSelection? _pendingStatusSelection;
List<Map<String, dynamic>>? _parsedNeighbours; List<Map<String, dynamic>>? _parsedNeighbors;
@override @override
void initState() { void initState() {
@@ -49,7 +50,7 @@ class _NeighboursScreenState extends State<NeighboursScreen> {
final connector = Provider.of<MeshCoreConnector>(context, listen: false); final connector = Provider.of<MeshCoreConnector>(context, listen: false);
_commandService = RepeaterCommandService(connector); _commandService = RepeaterCommandService(connector);
_setupMessageListener(); _setupMessageListener();
_loadNeighbours(); _loadNeighbors();
_hasData = false; _hasData = false;
} }
@@ -62,13 +63,12 @@ class _NeighboursScreenState extends State<NeighboursScreen> {
if (frame[0] == respCodeSent) { if (frame[0] == respCodeSent) {
_tagData = frame.sublist(2, 6); _tagData = frame.sublist(2, 6);
//_timeEstment = frame.buffer.asByteData().getUint32(6, Endian.little);
} }
// Check if it's a binary response // Check if it's a binary response
if (frame[0] == pushCodeBinaryResponse && if (frame[0] == pushCodeBinaryResponse &&
listEquals(frame.sublist(2, 6), _tagData)) { listEquals(frame.sublist(2, 6), _tagData)) {
_handleNeighboursResponse(connector, frame.sublist(6)); _handleNeighborsResponse(connector, frame.sublist(6));
} }
}); });
} }
@@ -91,65 +91,77 @@ class _NeighboursScreenState extends State<NeighboursScreen> {
return '${h}h ${m2}m'; return '${h}h ${m2}m';
} }
static List<Map<String, dynamic>> parseNeighboursData( static List<Map<String, dynamic>> parseNeighborsData(
BufferReader buffer, BufferReader buffer,
int resultsCount, int resultsCount,
) { ) {
final Map<int, Map<String, dynamic>> neighbours = {}; final Map<int, Map<String, dynamic>> neighbors = {};
for (var i = 0; i < resultsCount; i++) { try {
final neighbourData = neighbours.putIfAbsent( for (var i = 0; i < resultsCount; i++) {
i, final neighborData = neighbors.putIfAbsent(
() => { i,
'contact': null, () => {
'publicKey': <Uint8List>{}, 'contact': null,
'lastHeard': <int>{}, 'publicKey': <Uint8List>{},
'snr': <double>{}, 'lastHeard': <int>{},
}, 'snr': <double>{},
); },
neighbourData['publicKey'] = buffer.readBytes(_reqNeighboursKeyLen); );
neighbourData['lastHeard'] = buffer.readUInt32LE(); neighborData['publicKey'] = buffer.readBytes(_reqNeighborsKeyLen);
neighbourData['snr'] = buffer.readInt8() / 4.0; neighborData['lastHeard'] = buffer.readUInt32LE();
} neighborData['snr'] = buffer.readInt8() / 4.0;
}
return neighbours.values.toList(); return neighbors.values.toList();
} catch (e) {
appLogger.error(
'Error parsing neighbors data: $e',
tag: 'NeighborsScreen',
);
return [];
}
} }
void _handleNeighboursResponse(MeshCoreConnector connector, Uint8List frame) { void _handleNeighborsResponse(MeshCoreConnector connector, Uint8List frame) {
final buffer = BufferReader(frame); final buffer = BufferReader(frame);
final neighbourCount = buffer.readUInt16LE(); try {
final parsedNeighbours = parseNeighboursData(buffer, buffer.readUInt16LE()); final neighborCount = buffer.readUInt16LE();
connector.contacts.where((c) => c.type == advTypeRepeater).forEach(( final parsedNeighbors = parseNeighborsData(buffer, buffer.readUInt16LE());
repeater, connector.contacts.where((c) => c.type == advTypeRepeater).forEach((
) { repeater,
for (var neighbourData in parsedNeighbours) { ) {
final publicKey = neighbourData['publicKey']; for (var neighborData in parsedNeighbors) {
if (listEquals( final publicKey = neighborData['publicKey'];
repeater.publicKey.sublist(0, _reqNeighboursKeyLen), if (listEquals(
publicKey, repeater.publicKey.sublist(0, _reqNeighborsKeyLen),
)) { publicKey,
neighbourData['contact'] = repeater; )) {
neighborData['contact'] = repeater;
}
} }
} });
});
setState(() { setState(() {
_parsedNeighbours = parsedNeighbours; _parsedNeighbors = parsedNeighbors;
_neighbourCount = neighbourCount; _neighborCount = neighborCount;
}); });
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(context).showSnackBar(
SnackBar( SnackBar(
content: Text(context.l10n.neighbors_receivedData), content: Text(context.l10n.neighbors_receivedData),
backgroundColor: Colors.green, backgroundColor: Colors.green,
), ),
); );
_statusTimeout?.cancel(); _statusTimeout?.cancel();
if (!mounted) return; if (!mounted) return;
setState(() { setState(() {
_isLoading = false; _isLoading = false;
_isLoaded = true; _isLoaded = true;
_hasData = true; _hasData = true;
}); });
} catch (e) {
appLogger.error('Error handling neighbors response: $e');
}
} }
Contact _resolveRepeater(MeshCoreConnector connector) { Contact _resolveRepeater(MeshCoreConnector connector) {
@@ -159,7 +171,7 @@ class _NeighboursScreenState extends State<NeighboursScreen> {
); );
} }
Future<void> _loadNeighbours() async { Future<void> _loadNeighbors() async {
if (_commandService == null) return; if (_commandService == null) return;
setState(() { setState(() {
@@ -172,17 +184,17 @@ class _NeighboursScreenState extends State<NeighboursScreen> {
final selection = await connector.preparePathForContactSend(repeater); final selection = await connector.preparePathForContactSend(repeater);
_pendingStatusSelection = selection; _pendingStatusSelection = selection;
//[version][number of requested neighbours][offset_16bit][order by][len of public key] //[version][number of requested neighbors][offset_16bit][order by][len of public key]
final frame = buildSendBinaryReq( final frame = buildSendBinaryReq(
repeater.publicKey, repeater.publicKey,
payload: Uint8List.fromList([ payload: Uint8List.fromList([
reqTypeGetNeighbours, reqTypeGetNeighbors,
0x00, 0x00,
0x0F, 0x0F,
0x00, 0x00,
0x00, 0x00,
0x00, 0x00,
_reqNeighboursKeyLen, _reqNeighborsKeyLen,
]), ]),
); );
await connector.sendFrame(frame); await connector.sendFrame(frame);
@@ -258,7 +270,7 @@ class _NeighboursScreenState extends State<NeighboursScreen> {
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
Text( Text(
l10n.neighbors_repeatersNeighbours, l10n.neighbors_repeatersNeighbors,
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold), style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
), ),
Text( Text(
@@ -345,7 +357,7 @@ class _NeighboursScreenState extends State<NeighboursScreen> {
child: CircularProgressIndicator(strokeWidth: 2), child: CircularProgressIndicator(strokeWidth: 2),
) )
: const Icon(Icons.refresh), : const Icon(Icons.refresh),
onPressed: _isLoading ? null : _loadNeighbours, onPressed: _isLoading ? null : _loadNeighbors,
tooltip: l10n.repeater_refresh, tooltip: l10n.repeater_refresh,
), ),
], ],
@@ -353,13 +365,13 @@ class _NeighboursScreenState extends State<NeighboursScreen> {
body: SafeArea( body: SafeArea(
top: false, top: false,
child: RefreshIndicator( child: RefreshIndicator(
onRefresh: _loadNeighbours, onRefresh: _loadNeighbors,
child: ListView( child: ListView(
padding: const EdgeInsets.all(16), padding: const EdgeInsets.all(16),
children: [ children: [
if (!_isLoaded && if (!_isLoaded &&
!_hasData && !_hasData &&
(_parsedNeighbours == null || _parsedNeighbours!.isEmpty)) (_parsedNeighbors == null || _parsedNeighbors!.isEmpty))
Center( Center(
child: Text( child: Text(
l10n.neighbors_noData, l10n.neighbors_noData,
@@ -368,10 +380,9 @@ class _NeighboursScreenState extends State<NeighboursScreen> {
), ),
if (_isLoaded || if (_isLoaded ||
_hasData && _hasData &&
!(_parsedNeighbours == null || !(_parsedNeighbors == null || _parsedNeighbors!.isEmpty))
_parsedNeighbours!.isEmpty)) _buildNeighborsInfoCard(
_buildNeighboursInfoCard( "${l10n.repeater_neighbors} - $_neighborCount",
"${l10n.repeater_neighbours} - $_neighbourCount",
), ),
], ],
), ),
@@ -380,7 +391,7 @@ class _NeighboursScreenState extends State<NeighboursScreen> {
); );
} }
Widget _buildNeighboursInfoCard(String title) { Widget _buildNeighborsInfoCard(String title) {
final connector = Provider.of<MeshCoreConnector>(context, listen: false); final connector = Provider.of<MeshCoreConnector>(context, listen: false);
return Card( return Card(
child: Padding( child: Padding(
@@ -405,7 +416,7 @@ class _NeighboursScreenState extends State<NeighboursScreen> {
], ],
), ),
const Divider(), const Divider(),
for (final entry in _parsedNeighbours!.asMap().entries) for (final entry in _parsedNeighbors!.asMap().entries)
_buildInfoRow( _buildInfoRow(
entry.value['contact'] != null entry.value['contact'] != null
? entry.value['contact'].name ? entry.value['contact'].name
@@ -430,6 +441,7 @@ class _NeighboursScreenState extends State<NeighboursScreen> {
double snr, double snr,
int spreadingFactor, int spreadingFactor,
) { ) {
final snrUi = snrUiFromSNR(snr, spreadingFactor);
return Padding( return Padding(
padding: const EdgeInsets.symmetric(vertical: 3), padding: const EdgeInsets.symmetric(vertical: 3),
child: Row( child: Row(
@@ -443,9 +455,15 @@ class _NeighboursScreenState extends State<NeighboursScreen> {
style: const TextStyle(fontWeight: FontWeight.w500), style: const TextStyle(fontWeight: FontWeight.w500),
), ),
subtitle: Text(value), subtitle: Text(value),
trailing: SNRIcon( trailing: Column(
snr: snr, mainAxisAlignment: MainAxisAlignment.center,
snrLevels: getSNRfromSF(spreadingFactor), children: [
Icon(snrUi.icon, color: snrUi.color, size: 18.0),
Text(
snrUi.text,
style: TextStyle(fontSize: 10, color: snrUi.color),
),
],
), ),
), ),
), ),
+131 -81
View File
@@ -10,6 +10,7 @@ import 'package:meshcore_open/connector/meshcore_protocol.dart';
import 'package:meshcore_open/l10n/l10n.dart'; import 'package:meshcore_open/l10n/l10n.dart';
import 'package:meshcore_open/models/contact.dart'; import 'package:meshcore_open/models/contact.dart';
import 'package:meshcore_open/services/map_tile_cache_service.dart'; import 'package:meshcore_open/services/map_tile_cache_service.dart';
import 'package:meshcore_open/utils/app_logger.dart';
import 'package:meshcore_open/widgets/snr_indicator.dart'; import 'package:meshcore_open/widgets/snr_indicator.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
@@ -32,7 +33,7 @@ String formatDistance(double distanceMeters) {
class PathTraceData { class PathTraceData {
final Uint8List pathData; final Uint8List pathData;
final Uint8List snrData; final List<double> snrData;
final Map<int, Contact> pathContacts; final Map<int, Contact> pathContacts;
PathTraceData({ PathTraceData({
@@ -45,6 +46,7 @@ class PathTraceData {
class PathTraceMapScreen extends StatefulWidget { class PathTraceMapScreen extends StatefulWidget {
final String title; final String title;
final Uint8List path; final Uint8List path;
final int? repeaterId;
final bool flipPathRound; final bool flipPathRound;
final bool reversePathRound; final bool reversePathRound;
@@ -52,6 +54,7 @@ class PathTraceMapScreen extends StatefulWidget {
super.key, super.key,
required this.title, required this.title,
required this.path, required this.path,
this.repeaterId,
this.flipPathRound = false, this.flipPathRound = false,
this.reversePathRound = false, this.reversePathRound = false,
}); });
@@ -96,7 +99,7 @@ class _PathTraceMapScreenState extends State<PathTraceMapScreen> {
super.dispose(); super.dispose();
} }
Uint8List addReturnpath(Uint8List pathBytes) { Uint8List addReturnPath(Uint8List pathBytes) {
Uint8List? traceBytes; Uint8List? traceBytes;
final len = (pathBytes.length + pathBytes.length - 1); final len = (pathBytes.length + pathBytes.length - 1);
traceBytes = Uint8List(len); traceBytes = Uint8List(len);
@@ -124,11 +127,13 @@ class _PathTraceMapScreenState extends State<PathTraceMapScreen> {
: widget.path; : widget.path;
if (widget.flipPathRound) { if (widget.flipPathRound) {
path = addReturnpath(pathTmp); path = addReturnPath(pathTmp);
} else { } else {
path = pathTmp; path = pathTmp;
} }
print('Initiating path trace with path: ${_formatPathPrefixes(path)}');
final connector = Provider.of<MeshCoreConnector>(context, listen: false); final connector = Provider.of<MeshCoreConnector>(context, listen: false);
final frame = buildTraceReq( final frame = buildTraceReq(
DateTime.now().millisecondsSinceEpoch ~/ 1000, DateTime.now().millisecondsSinceEpoch ~/ 1000,
@@ -146,42 +151,54 @@ class _PathTraceMapScreenState extends State<PathTraceMapScreen> {
_frameSubscription = connector.receivedFrames.listen((frame) { _frameSubscription = connector.receivedFrames.listen((frame) {
if (frame.isEmpty) return; if (frame.isEmpty) return;
final frameBuffer = BufferReader(frame); final frameBuffer = BufferReader(frame);
final code = frameBuffer.readUInt8(); try {
final code = frameBuffer.readUInt8();
if (code == respCodeSent) { if (code == respCodeSent) {
frameBuffer.skipBytes(1); //reserved frameBuffer.skipBytes(1); //reserved
tagData = frameBuffer.readBytes(4); tagData = frameBuffer.readBytes(4);
final timeoutSeconds = frameBuffer.readUInt32LE(); final timeoutSeconds = frameBuffer.readUInt32LE();
// Start timeout timer for trace response // Start timeout timer for trace response
_timeoutTimer?.cancel(); _timeoutTimer?.cancel();
_timeoutTimer = Timer(Duration(milliseconds: timeoutSeconds), () { _timeoutTimer = Timer(Duration(milliseconds: timeoutSeconds), () {
if (!mounted) return;
setState(() {
_isLoading = false;
_failed2Loaded = true;
});
});
}
if (code == respCodeErr) {
_timeoutTimer?.cancel();
if (!mounted) return; if (!mounted) return;
setState(() { setState(() {
_isLoading = false; _isLoading = false;
_failed2Loaded = true; _failed2Loaded = true;
}); });
}); }
}
if (code == respCodeErr) { // Check if it's a binary response
if (frame.length > 8 &&
code == pushCodeTraceData &&
listEquals(frame.sublist(4, 8), tagData)) {
_timeoutTimer?.cancel();
if (!mounted) return;
frameBuffer.skipBytes(3); //reserved + path length + flag
if (listEquals(frameBuffer.readBytes(4), tagData)) {
_handleTraceResponse(frame);
}
}
} catch (e) {
_timeoutTimer?.cancel(); _timeoutTimer?.cancel();
if (!mounted) return; if (!mounted) return;
setState(() { setState(() {
_isLoading = false; _isLoading = false;
_failed2Loaded = true; _failed2Loaded = true;
}); });
} // Handle any parsing errors gracefully
// Check if it's a binary response appLogger.error('Error parsing frame: $e', tag: 'PathTraceMapScreen');
if (frame.length > 8 &&
code == pushCodeTraceData &&
listEquals(frame.sublist(4, 8), tagData)) {
_timeoutTimer?.cancel();
if (!mounted) return;
frameBuffer.skipBytes(3); //reserved + path length + flag
if (listEquals(frameBuffer.readBytes(4), tagData)) {
_handleTraceResponse(frame);
}
} }
}); });
} }
@@ -190,63 +207,80 @@ class _PathTraceMapScreenState extends State<PathTraceMapScreen> {
final connector = Provider.of<MeshCoreConnector>(context, listen: false); final connector = Provider.of<MeshCoreConnector>(context, listen: false);
final buffer = BufferReader(frame); final buffer = BufferReader(frame);
buffer.skipBytes(2); // Skip push code and reserved byte try {
int pathLength = buffer.readUInt8(); buffer.skipBytes(2); // Skip push code and reserved byte
buffer.skipBytes(5); // Skip Flag byte and tag data int pathLength = buffer.readUInt8();
buffer.skipBytes(4); // Skip auth code buffer.skipBytes(5); // Skip Flag byte and tag data
Uint8List pathData = buffer.readBytes(pathLength); buffer.skipBytes(4); // Skip auth code
Uint8List snrData = buffer.readRemainingBytes(); Uint8List pathData = buffer.readBytes(pathLength);
Uint8List snrData = buffer.readRemainingBytes();
Map<int, Contact> pathContacts = {}; Map<int, Contact> pathContacts = {};
connector.contacts.where((c) => c.type != advTypeChat).forEach((repeater) { connector.contacts.where((c) => c.type != advTypeChat).forEach((
for (var repeaterData in pathData) { repeater,
if (listEquals( ) {
repeater.publicKey.sublist(0, 1), for (var repeaterData in pathData) {
Uint8List.fromList([repeaterData]), if (listEquals(
)) { repeater.publicKey.sublist(0, 1),
pathContacts[repeaterData] = repeater; Uint8List.fromList([repeaterData]),
)) {
pathContacts[repeaterData] = repeater;
}
} }
} });
});
setState(() { setState(() {
_isLoading = false; _isLoading = false;
_hasData = true; _hasData = true;
_traceData = PathTraceData( _traceData = PathTraceData(
pathData: pathData, pathData: pathData,
snrData: snrData, snrData: snrData.map((e) => e.toSigned(8).toDouble() / 4).toList(),
pathContacts: pathContacts, pathContacts: pathContacts,
); );
_points = <LatLng>[]; _points = <LatLng>[];
_points.add(LatLng(connector.selfLatitude!, connector.selfLongitude!)); _points.add(LatLng(connector.selfLatitude!, connector.selfLongitude!));
for (final hop in _traceData!.pathData) { for (final hop in _traceData!.pathData) {
final contact = _traceData!.pathContacts[hop]; final contact = _traceData!.pathContacts[hop];
if (contact != null && if (contact != null &&
contact.hasLocation && contact.hasLocation &&
contact.latitude != null && contact.latitude != null &&
contact.longitude != null) { contact.longitude != null) {
_points.add(LatLng(contact.latitude!, contact.longitude!)); _points.add(LatLng(contact.latitude!, contact.longitude!));
}
} }
} _polylines = _points.length > 1
_polylines = _points.length > 1 ? [
? [ Polyline(
Polyline( points: _points,
points: _points, strokeWidth: 4,
strokeWidth: 4, color: Colors.blueAccent,
color: Colors.blueAccent, ),
), ]
] : <Polyline>[];
: <Polyline>[];
_initialCenter = _points.isNotEmpty ? _points.first : const LatLng(0, 0); _initialCenter = _points.isNotEmpty
_initialZoom = _points.isNotEmpty ? 13.0 : 2.0; ? _points.first
_bounds = _points.length > 1 ? LatLngBounds.fromPoints(_points) : null; : const LatLng(0, 0);
_mapKey = ValueKey( _initialZoom = _points.isNotEmpty ? 13.0 : 2.0;
'${context.l10n.pathTrace_you},${_formatPathPrefixes(_traceData!.pathData)}', _bounds = _points.length > 1 ? LatLngBounds.fromPoints(_points) : null;
_mapKey = ValueKey(
'${context.l10n.pathTrace_you},${_formatPathPrefixes(_traceData!.pathData)}',
);
_pathDistanceMeters = getPathDistanceMeters(_points);
});
} catch (e) {
appLogger.error(
'Error handling trace response: $e',
tag: 'PathTraceMapScreen',
); );
_pathDistanceMeters = getPathDistanceMeters(_points); if (mounted) {
}); setState(() {
_isLoading = false;
_failed2Loaded = true;
});
}
}
} }
@override @override
@@ -532,6 +566,12 @@ class _PathTraceMapScreenState extends State<PathTraceMapScreen> {
itemCount: pathTraceData.pathData.length + 1, itemCount: pathTraceData.pathData.length + 1,
separatorBuilder: (_, _) => const Divider(height: 1), separatorBuilder: (_, _) => const Divider(height: 1),
itemBuilder: (context, index) { itemBuilder: (context, index) {
final snrUi = snrUiFromSNR(
index < pathTraceData.snrData.length
? pathTraceData.snrData[index]
: null,
context.read<MeshCoreConnector>().currentSf,
);
return Column( return Column(
children: [ children: [
ListTile( ListTile(
@@ -550,12 +590,22 @@ class _PathTraceMapScreenState extends State<PathTraceMapScreen> {
), ),
style: const TextStyle(fontSize: 14), style: const TextStyle(fontSize: 14),
), ),
trailing: SNRIcon( trailing: Column(
snr: mainAxisAlignment: MainAxisAlignment.center,
pathTraceData.snrData[index].toSigned( children: [
8, Icon(
) / snrUi.icon,
4.0, color: snrUi.color,
size: 18.0,
),
Text(
snrUi.text,
style: TextStyle(
fontSize: 10,
color: snrUi.color,
),
),
],
), ),
onTap: () { onTap: () {
// Handle item tap // Handle item tap
+5 -7
View File
@@ -6,7 +6,7 @@ import 'repeater_status_screen.dart';
import 'repeater_cli_screen.dart'; import 'repeater_cli_screen.dart';
import 'repeater_settings_screen.dart'; import 'repeater_settings_screen.dart';
import 'telemetry_screen.dart'; import 'telemetry_screen.dart';
import 'neighbours_screen.dart'; import 'neighbors_screen.dart';
class RepeaterHubScreen extends StatelessWidget { class RepeaterHubScreen extends StatelessWidget {
final Contact repeater; final Contact repeater;
@@ -174,17 +174,15 @@ class RepeaterHubScreen extends StatelessWidget {
_buildManagementCard( _buildManagementCard(
context, context,
icon: Icons.group, icon: Icons.group,
title: l10n.repeater_neighbours, title: l10n.repeater_neighbors,
subtitle: l10n.repeater_neighboursSubtitle, subtitle: l10n.repeater_neighborsSubtitle,
color: Colors.orange, color: Colors.orange,
onTap: () { onTap: () {
Navigator.push( Navigator.push(
context, context,
MaterialPageRoute( MaterialPageRoute(
builder: (context) => NeighboursScreen( builder: (context) =>
repeater: repeater, NeighborsScreen(repeater: repeater, password: password),
password: password,
),
), ),
); );
}, },
+43 -52
View File
@@ -862,7 +862,6 @@ class _RadioSettingsDialogState extends State<_RadioSettingsDialog> {
LoRaSpreadingFactor _spreadingFactor = LoRaSpreadingFactor.sf7; LoRaSpreadingFactor _spreadingFactor = LoRaSpreadingFactor.sf7;
LoRaCodingRate _codingRate = LoRaCodingRate.cr4_5; LoRaCodingRate _codingRate = LoRaCodingRate.cr4_5;
final _txPowerController = TextEditingController(text: '20'); final _txPowerController = TextEditingController(text: '20');
bool _clientRepeat = false;
@override @override
void initState() { void initState() {
@@ -912,8 +911,6 @@ class _RadioSettingsDialogState extends State<_RadioSettingsDialog> {
if (widget.connector.currentTxPower != null) { if (widget.connector.currentTxPower != null) {
_txPowerController.text = widget.connector.currentTxPower.toString(); _txPowerController.text = widget.connector.currentTxPower.toString();
} }
_clientRepeat = widget.connector.clientRepeat ?? false;
} }
@override @override
@@ -963,29 +960,9 @@ class _RadioSettingsDialogState extends State<_RadioSettingsDialog> {
widget.connector.currentCr, widget.connector.currentCr,
); );
// if the client repeat isnt null then we know its supported
//otherwise we leave it out of the frame to avoid accidentally enabling
final knownRepeat = widget.connector.clientRepeat != null;
if (knownRepeat) {
const validRepeatFreqsKHz = {433000, 869000, 918000};
if (_clientRepeat && !validRepeatFreqsKHz.contains(freqHz)) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(l10n.settings_clientRepeatFreqWarning)),
);
return;
}
}
try { try {
await widget.connector.sendFrame( await widget.connector.sendFrame(
buildSetRadioParamsFrame( buildSetRadioParamsFrame(freqHz, bwHz, sf, cr),
freqHz,
bwHz,
sf,
cr,
clientRepeat: knownRepeat ? _clientRepeat : null,
),
); );
await widget.connector.sendFrame(buildSetRadioTxPowerFrame(txPower)); await widget.connector.sendFrame(buildSetRadioTxPowerFrame(txPower));
await widget.connector.refreshDeviceInfo(); await widget.connector.refreshDeviceInfo();
@@ -1024,25 +1001,37 @@ class _RadioSettingsDialogState extends State<_RadioSettingsDialog> {
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
DropdownButtonFormField<int>( Text(
decoration: InputDecoration( l10n.settings_presets,
labelText: l10n.settings_presets, style: const TextStyle(fontWeight: FontWeight.bold),
border: const OutlineInputBorder(),
),
items: [
for (var i = 0; i < RadioSettings.presets.length; i++)
DropdownMenuItem(
value: i,
child: Text(RadioSettings.presets[i].$1),
),
],
onChanged: (index) {
if (index != null) {
_applyPreset(RadioSettings.presets[index].$2);
}
},
), ),
const SizedBox(height: 16), const SizedBox(height: 8),
Wrap(
spacing: 8,
children: [
_PresetChip(
label: l10n.settings_preset915Mhz,
onTap: () => _applyPreset(RadioSettings.preset915MHz),
),
_PresetChip(
label: l10n.settings_preset868Mhz,
onTap: () => _applyPreset(RadioSettings.preset868MHz),
),
_PresetChip(
label: l10n.settings_preset433Mhz,
onTap: () => _applyPreset(RadioSettings.preset433MHz),
),
_PresetChip(
label: l10n.settings_longRange,
onTap: () => _applyPreset(RadioSettings.presetLongRange),
),
_PresetChip(
label: l10n.settings_fastSpeed,
onTap: () => _applyPreset(RadioSettings.presetFastSpeed),
),
],
),
const SizedBox(height: 24),
TextField( TextField(
controller: _frequencyController, controller: _frequencyController,
decoration: InputDecoration( decoration: InputDecoration(
@@ -1114,16 +1103,6 @@ class _RadioSettingsDialogState extends State<_RadioSettingsDialog> {
), ),
keyboardType: TextInputType.number, keyboardType: TextInputType.number,
), ),
if (widget.connector.clientRepeat != null) ...[
const SizedBox(height: 16),
SwitchListTile(
title: Text(l10n.settings_clientRepeat),
subtitle: Text(l10n.settings_clientRepeatSubtitle),
value: _clientRepeat,
onChanged: (value) => setState(() => _clientRepeat = value),
contentPadding: EdgeInsets.zero,
),
],
], ],
), ),
), ),
@@ -1137,3 +1116,15 @@ class _RadioSettingsDialogState extends State<_RadioSettingsDialog> {
); );
} }
} }
class _PresetChip extends StatelessWidget {
final String label;
final VoidCallback onTap;
const _PresetChip({required this.label, required this.onTap});
@override
Widget build(BuildContext context) {
return ActionChip(label: Text(label), onPressed: onTap);
}
}
+54
View File
@@ -0,0 +1,54 @@
import 'package:flutter/material.dart';
import 'package:meshcore_open/connector/meshcore_connector.dart';
import 'package:meshcore_open/widgets/battery_indicator.dart';
import 'package:provider/provider.dart';
import 'snr_indicator.dart';
class AppBarTitle extends StatelessWidget {
final String title;
final Widget? leading;
final Widget? trailing;
const AppBarTitle(this.title, {this.leading, this.trailing, super.key});
@override
Widget build(BuildContext context) {
final connector = context.watch<MeshCoreConnector>();
return Row(
mainAxisAlignment: MainAxisAlignment.start,
children: [
leading ?? const SizedBox.shrink(),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
leading ?? const SizedBox.shrink(),
Text(
title,
overflow: TextOverflow.ellipsis,
),
if (connector.isConnected && connector.selfName != null)
Center(
child: Text(
'(${connector.selfName})',
style: TextStyle(fontSize: 14, color: Colors.grey[600]),
overflow: TextOverflow.ellipsis,
),
),
],
),
),
const SizedBox(width: 8),
Row(
mainAxisSize: MainAxisSize.min,
children: [
BatteryIndicator(connector: connector),
SNRIndicator(connector: connector),
],
),
trailing ?? const SizedBox.shrink(),
],
);
}
}
+17 -13
View File
@@ -68,20 +68,24 @@ class _BatteryIndicatorState extends State<BatteryIndicator> {
child: Row( child: Row(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
Icon(batteryUi.icon, size: 18, color: batteryUi.color), Column(
const SizedBox(width: 2), mainAxisSize: MainAxisSize.min,
Flexible( children: [
child: Text( Icon(batteryUi.icon, size: 18, color: batteryUi.color),
displayText, const SizedBox(height: 2),
style: TextStyle( Flexible(
fontSize: 12, child: Text(
fontWeight: FontWeight.w500, displayText,
color: batteryUi.color, style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.w500,
color: batteryUi.color,
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
), ),
overflow: TextOverflow.visible, ],
maxLines: 1,
softWrap: false,
),
), ),
], ],
), ),
+39 -3
View File
@@ -134,6 +134,19 @@ class _PathManagementDialog extends StatelessWidget {
final currentContact = _resolveContact(connector); final currentContact = _resolveContact(connector);
final paths = pathService.getRecentPaths(currentContact.publicKeyHex); final paths = pathService.getRecentPaths(currentContact.publicKeyHex);
final repeatersList = List.of(connector.directRepeaters)
..sort((a, b) => b.ranking.compareTo(a.ranking));
final directRepeater = repeatersList.isEmpty
? null
: repeatersList.first;
final secondDirectRepeater = repeatersList.length < 2
? null
: repeatersList.elementAt(1);
final thirdDirectRepeater = repeatersList.length < 3
? null
: repeatersList.elementAt(2);
return AlertDialog( return AlertDialog(
title: Text(l10n.chat_pathManagement), title: Text(l10n.chat_pathManagement),
content: SingleChildScrollView( content: SingleChildScrollView(
@@ -174,15 +187,38 @@ class _PathManagementDialog extends StatelessWidget {
], ],
const SizedBox(height: 8), const SizedBox(height: 8),
...paths.map((path) { ...paths.map((path) {
final isDirectRepeater =
directRepeater != null &&
path.pathBytes.isNotEmpty &&
directRepeater.pubkeyFirstByte == path.pathBytes.first;
final isSecondDirectRepeater =
secondDirectRepeater != null &&
path.pathBytes.isNotEmpty &&
secondDirectRepeater.pubkeyFirstByte ==
path.pathBytes.first;
final isThirdDirectRepeater =
thirdDirectRepeater != null &&
path.pathBytes.isNotEmpty &&
thirdDirectRepeater.pubkeyFirstByte ==
path.pathBytes.first;
Color color = Colors.grey;
if (isDirectRepeater) {
color = Colors.green;
} else if (isSecondDirectRepeater) {
color = Colors.yellow;
} else if (isThirdDirectRepeater) {
color = Colors.red;
} else if (path.wasFloodDiscovery) {
color = Colors.blue;
}
return Card( return Card(
margin: const EdgeInsets.symmetric(vertical: 4), margin: const EdgeInsets.symmetric(vertical: 4),
child: ListTile( child: ListTile(
dense: true, dense: true,
leading: CircleAvatar( leading: CircleAvatar(
radius: 16, radius: 16,
backgroundColor: path.wasFloodDiscovery backgroundColor: color,
? Colors.blue
: Colors.green,
child: Text( child: Text(
'${path.hopCount}', '${path.hopCount}',
style: const TextStyle(fontSize: 12), style: const TextStyle(fontSize: 12),
+174 -31
View File
@@ -1,4 +1,13 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import '../connector/meshcore_connector.dart';
import '../l10n/l10n.dart';
class SNRUi {
final IconData icon;
final Color color;
final String text;
const SNRUi(this.icon, this.color, this.text);
}
List<double> getSNRfromSF(int spreadingFactor) { List<double> getSNRfromSF(int spreadingFactor) {
switch (spreadingFactor) { switch (spreadingFactor) {
@@ -19,44 +28,178 @@ List<double> getSNRfromSF(int spreadingFactor) {
} }
} }
class SNRIcon extends StatelessWidget { SNRUi snrUiFromSNR(double? snr, int? spreadingFactor) {
final double snr; if (snr == null ||
final List<double> snrLevels; spreadingFactor == null ||
spreadingFactor < 7 ||
spreadingFactor > 12) {
return const SNRUi(Icons.signal_cellular_off, Colors.grey, '');
}
const SNRIcon({ final snrLevels = getSNRfromSF(spreadingFactor);
super.key,
required this.snr, IconData icon;
this.snrLevels = const [4.0, -2.0, -4.0, -6.0], Color color;
}); String text = '${snr.toStringAsFixed(1)} dB';
if (snr >= snrLevels[0]) {
icon = Icons.signal_cellular_alt;
color = Colors.green;
} else if (snr >= snrLevels[1]) {
icon = Icons.signal_cellular_alt;
color = Colors.lightGreen;
} else if (snr >= snrLevels[2]) {
icon = Icons.signal_cellular_alt;
color = Colors.yellow;
} else if (snr >= snrLevels[3]) {
icon = Icons.signal_cellular_alt_2_bar;
color = Colors.orange;
} else {
icon = Icons.signal_cellular_alt_1_bar;
color = Colors.red;
}
return SNRUi(icon, color, text);
}
class SNRIndicator extends StatefulWidget {
final MeshCoreConnector connector;
const SNRIndicator({super.key, required this.connector});
@override
State<SNRIndicator> createState() => _SNRIndicatorState();
}
class _SNRIndicatorState extends State<SNRIndicator> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
IconData icon; final directRepeaters = widget.connector.directRepeaters;
Color color; final directBestRepeaters = List.of(directRepeaters)
..sort((a, b) => (b.ranking).compareTo(a.ranking));
final directRepeater = directBestRepeaters.isEmpty
? null
: directBestRepeaters.first;
if (snr >= snrLevels[0]) { final snrUi = snrUiFromSNR(
icon = Icons.signal_cellular_alt; directBestRepeaters.isNotEmpty ? directRepeater!.snr : null,
color = Colors.green; widget.connector.currentSf,
} else if (snr >= snrLevels[1]) { );
icon = Icons.signal_cellular_alt;
color = Colors.lightGreen; return InkWell(
} else if (snr >= snrLevels[2]) { onTap: () {
icon = Icons.signal_cellular_alt; if (directRepeater != null) {
color = Colors.yellow; _showFullPathDialog(context, directBestRepeaters);
} else if (snr >= snrLevels[3]) { }
icon = Icons.signal_cellular_alt_2_bar; },
color = Colors.orange; borderRadius: BorderRadius.circular(8),
} else { child: Padding(
icon = Icons.signal_cellular_alt_1_bar; padding: const EdgeInsets.symmetric(horizontal: 4, vertical: 8),
color = Colors.red; child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Column(
mainAxisSize: MainAxisSize.min,
children: [
Icon(snrUi.icon, size: 18, color: snrUi.color),
Text(
snrUi.text,
style: TextStyle(fontSize: 12, color: snrUi.color),
),
],
),
if (directRepeater != null)
Text(
'${directRepeaters.length}: ${directRepeater.pubkeyFirstByte.toRadixString(16).padLeft(2, '0')}: ${_formatLastUpdated(directRepeater.lastUpdated)}',
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.w500,
color: Colors.grey,
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
],
),
),
);
}
String _formatLastUpdated(DateTime lastSeen) {
final now = DateTime.now();
final diff = now.difference(lastSeen);
if (diff.isNegative) {
return "0s";
} }
if (diff.inMinutes < 1) {
return "${diff.inSeconds}s";
}
if (diff.inMinutes < 60) {
return "${diff.inMinutes}m";
}
if (diff.inHours < 24) {
final hours = diff.inHours;
return hours == 1 ? "1h" : "${hours}hs";
}
final days = diff.inDays;
return days == 1 ? "1d" : "${days}ds";
}
return Column( void _showFullPathDialog(
mainAxisAlignment: MainAxisAlignment.center, BuildContext context,
children: [ List<DirectRepeater> directBestRepeaters,
Icon(icon, color: color), ) {
Text('$snr dB', style: TextStyle(fontSize: 10, color: color)), final l10n = context.l10n;
],
showDialog(
context: context,
builder: (context) => AlertDialog(
title: Text(l10n.snrIndicator_nearByRepeaters),
content: SizedBox(
child: Scrollbar(
child: ListView.separated(
padding: const EdgeInsets.symmetric(vertical: 4),
itemCount: directBestRepeaters.length,
separatorBuilder: (_, _) => const Divider(height: 1),
itemBuilder: (context, index) {
final repeater = directBestRepeaters[index];
final snrUi = snrUiFromSNR(
repeater.snr,
widget.connector.currentSf,
);
final name = widget.connector.contacts
.where((c) => c.publicKey.first == repeater.pubkeyFirstByte)
.map((c) => c.name)
.firstOrNull;
return Column(
children: [
ListTile(
leading: Icon(snrUi.icon, color: snrUi.color),
title: Text(
name ??
repeater.pubkeyFirstByte
.toRadixString(16)
.padLeft(2, '0'),
),
subtitle: Text(
'SNR: ${repeater.snr.toStringAsFixed(1)} dB\n${l10n.snrIndicator_lastSeen}: ${_formatLastUpdated(repeater.lastUpdated)}',
),
),
],
);
},
),
),
),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: Text(l10n.common_close),
),
],
),
); );
} }
} }
+8 -8
View File
@@ -69,10 +69,10 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: characters name: characters
sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803 sha256: faf38497bda5ead2a8c7615f4f7939df04333478bf32e4173fcb06d428b5716b
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.4.0" version: "1.4.1"
checked_yaml: checked_yaml:
dependency: transitive dependency: transitive
description: description:
@@ -497,18 +497,18 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: matcher name: matcher
sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2 sha256: "12956d0ad8390bbcc63ca2e1469c0619946ccb52809807067a7020d57e647aa6"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.12.17" version: "0.12.18"
material_color_utilities: material_color_utilities:
dependency: transitive dependency: transitive
description: description:
name: material_color_utilities name: material_color_utilities
sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec sha256: "9c337007e82b1889149c82ed242ed1cb24a66044e30979c44912381e9be4c48b"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.11.1" version: "0.13.0"
meta: meta:
dependency: transitive dependency: transitive
description: description:
@@ -910,10 +910,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: test_api name: test_api
sha256: ab2726c1a94d3176a45960b6234466ec367179b87dd74f1611adb1f3b5fb9d55 sha256: "93167629bfc610f71560ab9312acdda4959de4df6fac7492c89ff0d3886f6636"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.7.7" version: "0.7.9"
timezone: timezone:
dependency: transitive dependency: transitive
description: description: