Compare commits

..

3 Commits

Author SHA1 Message Date
zjs81 4239fb11ed Fix radio settings to only send repeat byte when the current state is known 2026-02-18 00:07:08 -07:00
zjs81 5fae2e5f73 fix formatting 2026-02-17 23:50:11 -07:00
zjs81 947fafbbb7 Refactor radio settings and localization updates
fixes #72

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