mirror of
https://github.com/zjs81/meshcore-open.git
synced 2026-04-20 22:13:48 +00:00
Merge branch 'main' into feature/usb
This commit is contained in:
commit
ab2b509d6a
52 changed files with 3703 additions and 398 deletions
|
|
@ -231,6 +231,11 @@ If you find MeshCore Open useful and would like to support development, you can
|
|||
|
||||
**Solana Address:** `F15YanjZj96YTBtKJYgNa8RLQLCZkx5CEwogPWkqXeoQ`
|
||||
|
||||
|
||||
**Monero Address:** `453TxnpUqjkJtXxzdjMsrgERNkBRXEGamPbpC45ENrvKAk9tH7kZbxWF82Hz66etgDZyXFPEBU2JUEqhLeJyWt9kBvTVy5m`
|
||||
|
||||
**Bitcoin Address:** `bc1qh45x28v8dslcg4v4upmqd9g0mvc3lnyffmyzr5`
|
||||
|
||||
Your support helps maintain and improve this open-source project!
|
||||
|
||||
## Acknowledgments
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import 'dart:async';
|
|||
import 'dart:convert';
|
||||
|
||||
import 'package:crypto/crypto.dart' as crypto;
|
||||
import 'package:meshcore_open/models/discovery_contact.dart';
|
||||
import 'package:pointycastle/export.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter_blue_plus/flutter_blue_plus.dart';
|
||||
|
|
@ -25,6 +26,7 @@ import '../storage/channel_message_store.dart';
|
|||
import '../storage/channel_order_store.dart';
|
||||
import '../storage/channel_settings_store.dart';
|
||||
import '../storage/channel_store.dart';
|
||||
import '../storage/contact_discovery_store.dart';
|
||||
import '../storage/contact_settings_store.dart';
|
||||
import '../storage/contact_store.dart';
|
||||
import '../storage/message_store.dart';
|
||||
|
|
@ -120,6 +122,7 @@ class MeshCoreConnector extends ChangeNotifier {
|
|||
|
||||
final List<ScanResult> _scanResults = [];
|
||||
final List<Contact> _contacts = [];
|
||||
final List<DiscoveryContact> _discoveredContacts = [];
|
||||
final List<Channel> _channels = [];
|
||||
final Map<String, List<Message>> _conversations = {};
|
||||
final Map<int, List<ChannelMessage>> _channelMessages = {};
|
||||
|
|
@ -136,10 +139,13 @@ class MeshCoreConnector extends ChangeNotifier {
|
|||
StreamSubscription<List<ScanResult>>? _scanSubscription;
|
||||
StreamSubscription<BluetoothConnectionState>? _connectionSubscription;
|
||||
StreamSubscription<List<int>>? _notifySubscription;
|
||||
Timer? _notifyListenersTimer;
|
||||
Timer? _selfInfoRetryTimer;
|
||||
Timer? _reconnectTimer;
|
||||
Timer? _batteryPollTimer;
|
||||
int _reconnectAttempts = 0;
|
||||
bool _notifyListenersDirty = false;
|
||||
static const Duration _notifyListenersDebounce = Duration(milliseconds: 50);
|
||||
|
||||
final StreamController<Uint8List> _receivedFramesController =
|
||||
StreamController<Uint8List>.broadcast();
|
||||
|
|
@ -170,6 +176,18 @@ class MeshCoreConnector extends ChangeNotifier {
|
|||
bool _pendingDeferredChannelSyncAfterContacts = false;
|
||||
bool _webInitialHandshakeRequestSent = false;
|
||||
bool _preserveContactsOnRefresh = false;
|
||||
bool _autoAddUsers = false;
|
||||
bool _autoAddRepeaters = false;
|
||||
bool _autoAddRoomServers = false;
|
||||
bool _autoAddSensors = false;
|
||||
bool _overwriteOldest = false;
|
||||
bool _manualAddContacts = false;
|
||||
int _telemetryModeBase = 0;
|
||||
int _telemetryModeLoc = 0;
|
||||
int _telemetryModeEnv = 0;
|
||||
int _advertLocPolicy = 0;
|
||||
int _multiAcks = 0;
|
||||
|
||||
static const int _defaultMaxContacts = 32;
|
||||
static const int _defaultMaxChannels = 8;
|
||||
int _maxContacts = _defaultMaxContacts;
|
||||
|
|
@ -210,6 +228,7 @@ class MeshCoreConnector extends ChangeNotifier {
|
|||
final ChannelSettingsStore _channelSettingsStore = ChannelSettingsStore();
|
||||
final ContactSettingsStore _contactSettingsStore = ContactSettingsStore();
|
||||
final ContactStore _contactStore = ContactStore();
|
||||
final ContactDiscoveryStore _discoveryContactStore = ContactDiscoveryStore();
|
||||
final ChannelStore _channelStore = ChannelStore();
|
||||
final UnreadStore _unreadStore = UnreadStore();
|
||||
List<Channel> _cachedChannels = [];
|
||||
|
|
@ -265,6 +284,10 @@ class MeshCoreConnector extends ChangeNotifier {
|
|||
);
|
||||
}
|
||||
|
||||
List<DiscoveryContact> get discoveredContacts {
|
||||
return List.unmodifiable(_discoveredContacts);
|
||||
}
|
||||
|
||||
List<Channel> get channels => List.unmodifiable(_channels);
|
||||
bool get isConnected => _state == MeshCoreConnectionState.connected;
|
||||
bool get isLoadingContacts => _isLoadingContacts;
|
||||
|
|
@ -281,12 +304,18 @@ class MeshCoreConnector extends ChangeNotifier {
|
|||
int? get currentBwHz => _currentBwHz;
|
||||
int? get currentSf => _currentSf;
|
||||
int? get currentCr => _currentCr;
|
||||
bool? get autoAddUsers => _autoAddUsers;
|
||||
bool? get autoAddRepeaters => _autoAddRepeaters;
|
||||
bool? get autoAddRoomServers => _autoAddRoomServers;
|
||||
bool? get autoAddSensors => _autoAddSensors;
|
||||
bool? get autoAddOverwriteOldest => _overwriteOldest;
|
||||
bool? get clientRepeat => _clientRepeat;
|
||||
int? get firmwareVerCode => _firmwareVerCode;
|
||||
Map<String, String>? get currentCustomVars => _currentCustomVars;
|
||||
int? get batteryMillivolts => _batteryMillivolts;
|
||||
int get maxContacts => _maxContacts;
|
||||
int get maxChannels => _maxChannels;
|
||||
Set<String> get knownContactKeys => Set.unmodifiable(_knownContactKeys);
|
||||
bool get isSyncingQueuedMessages => _isSyncingQueuedMessages;
|
||||
bool get isSyncingChannels => _isSyncingChannels;
|
||||
int get channelSyncProgress =>
|
||||
|
|
@ -655,6 +684,13 @@ class MeshCoreConnector extends ChangeNotifier {
|
|||
}
|
||||
}
|
||||
|
||||
Future<void> loadDiscoveredContactCache() async {
|
||||
final cached = await _discoveryContactStore.loadContacts();
|
||||
_discoveredContacts
|
||||
..clear()
|
||||
..addAll(cached);
|
||||
}
|
||||
|
||||
Future<void> loadChannelSettings({int? maxChannels}) async {
|
||||
_channelSmazEnabled.clear();
|
||||
final channelCount = maxChannels ?? _maxChannels;
|
||||
|
|
@ -1083,7 +1119,7 @@ class MeshCoreConnector extends ChangeNotifier {
|
|||
_hasReceivedDeviceInfo = false;
|
||||
_pendingInitialChannelSync = true;
|
||||
}
|
||||
unawaited(Future<void>.microtask(() => _startBleInitialSync()));
|
||||
await _startBleInitialSync();
|
||||
} catch (e) {
|
||||
_appDebugLogService?.error('Connection error: $e', tag: 'BLE Connect');
|
||||
await disconnect(manual: false);
|
||||
|
|
@ -1132,22 +1168,7 @@ class MeshCoreConnector extends ChangeNotifier {
|
|||
|
||||
await _requestDeviceInfo();
|
||||
_startBatteryPolling();
|
||||
|
||||
if (PlatformInfo.isWeb) {
|
||||
// Keep Web BLE startup writes light while notifications settle.
|
||||
unawaited(
|
||||
Future<void>(() async {
|
||||
await Future<void>.delayed(const Duration(seconds: 5));
|
||||
if (!isConnected ||
|
||||
!PlatformInfo.isWeb ||
|
||||
_activeTransport != MeshCoreTransportType.bluetooth) {
|
||||
return;
|
||||
}
|
||||
await syncTime();
|
||||
}),
|
||||
);
|
||||
return;
|
||||
}
|
||||
unawaited(loadDiscoveredContactCache());
|
||||
|
||||
final gotSelfInfo = await _waitForSelfInfo(
|
||||
timeout: const Duration(seconds: 3),
|
||||
|
|
@ -1157,8 +1178,8 @@ class MeshCoreConnector extends ChangeNotifier {
|
|||
await _waitForSelfInfo(timeout: const Duration(seconds: 3));
|
||||
}
|
||||
|
||||
unawaited(syncTime());
|
||||
_pendingDeferredChannelSyncAfterContacts = true;
|
||||
await syncTime();
|
||||
unawaited(getChannels());
|
||||
}
|
||||
|
||||
void _resetConnectionHandshakeState() {
|
||||
|
|
@ -1280,6 +1301,7 @@ class MeshCoreConnector extends ChangeNotifier {
|
|||
_deviceDisplayName = null;
|
||||
_deviceId = null;
|
||||
_contacts.clear();
|
||||
_discoveredContacts.clear();
|
||||
_conversations.clear();
|
||||
_loadedConversationKeys.clear();
|
||||
_selfPublicKey = null;
|
||||
|
|
@ -1397,8 +1419,9 @@ class MeshCoreConnector extends ChangeNotifier {
|
|||
await sendFrame(buildDeviceQueryFrame());
|
||||
await sendFrame(buildAppStartFrame());
|
||||
await requestBatteryStatus(force: true);
|
||||
await sendFrame(buildGetRadioSettingsFrame());
|
||||
await sendFrame(buildGetCustomVarsFrame());
|
||||
await sendFrame(buildGetAutoAddFlagsFrame());
|
||||
|
||||
_scheduleSelfInfoRetry();
|
||||
}
|
||||
|
||||
|
|
@ -1420,7 +1443,7 @@ class MeshCoreConnector extends ChangeNotifier {
|
|||
await sendFrame(buildAppStartFrame());
|
||||
await sendFrame(buildGetCustomVarsFrame());
|
||||
await requestBatteryStatus();
|
||||
|
||||
await sendFrame(buildGetAutoAddFlagsFrame());
|
||||
_scheduleSelfInfoRetry();
|
||||
}
|
||||
|
||||
|
|
@ -1857,6 +1880,8 @@ class MeshCoreConnector extends ChangeNotifier {
|
|||
Future<void> removeContact(Contact contact) async {
|
||||
if (!isConnected) return;
|
||||
|
||||
_handleDiscovery(contact, Uint8List(0), noNotify: true);
|
||||
|
||||
await sendFrame(buildRemoveContactFrame(contact.publicKey));
|
||||
_contacts.removeWhere((c) => c.publicKeyHex == contact.publicKeyHex);
|
||||
_knownContactKeys.remove(contact.publicKeyHex);
|
||||
|
|
@ -1871,6 +1896,42 @@ class MeshCoreConnector extends ChangeNotifier {
|
|||
notifyListeners();
|
||||
}
|
||||
|
||||
Future<void> removeDiscoveredContact(DiscoveryContact contact) async {
|
||||
if (!isConnected) return;
|
||||
_discoveredContacts.removeWhere(
|
||||
(c) => c.publicKeyHex == contact.publicKeyHex,
|
||||
);
|
||||
unawaited(_persistDiscoveredContacts());
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
Future<void> importDiscoveredContact(DiscoveryContact contact) async {
|
||||
if (!isConnected) return;
|
||||
|
||||
await sendFrame(
|
||||
buildUpdateContactPathFrame(
|
||||
contact.publicKey,
|
||||
contact.path,
|
||||
contact.pathLength,
|
||||
type: contact.type,
|
||||
flags: 0,
|
||||
name: contact.name,
|
||||
),
|
||||
);
|
||||
|
||||
_handleContactAdvert(
|
||||
Contact(
|
||||
publicKey: contact.publicKey,
|
||||
name: contact.name,
|
||||
type: contact.type,
|
||||
pathLength: contact.pathLength,
|
||||
path: contact.path,
|
||||
lastSeen: DateTime.now(),
|
||||
),
|
||||
);
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
Future<void> clearContactPath(Contact contact) async {
|
||||
if (!isConnected) return;
|
||||
|
||||
|
|
@ -2284,8 +2345,9 @@ class MeshCoreConnector extends ChangeNotifier {
|
|||
case respCodeChannelInfo:
|
||||
_handleChannelInfo(frame);
|
||||
break;
|
||||
case respCodeRadioSettings:
|
||||
_handleRadioSettings(frame);
|
||||
case respCodeAutoAddConfig:
|
||||
_handleAutoAddConfig(frame);
|
||||
_checkManualAddContacts();
|
||||
break;
|
||||
case respCodeBattAndStorage:
|
||||
_handleBatteryAndStorage(frame);
|
||||
|
|
@ -2358,27 +2420,35 @@ class MeshCoreConnector extends ChangeNotifier {
|
|||
// [56] = sf
|
||||
// [57] = cr
|
||||
// [58+] = node_name
|
||||
if (frame.length < 4 + pubKeySize) return;
|
||||
|
||||
final wasAwaitingSelfInfo = _awaitingSelfInfo;
|
||||
final reader = BufferReader(frame);
|
||||
try {
|
||||
reader.skipBytes(2);
|
||||
_currentTxPower = reader.readByte();
|
||||
_maxTxPower = reader.readByte();
|
||||
_selfPublicKey = reader.readBytes(pubKeySize);
|
||||
_selfLatitude = reader.readInt32LE() / 1000000.0;
|
||||
_selfLongitude = reader.readInt32LE() / 1000000.0;
|
||||
_multiAcks = reader.readByte();
|
||||
_advertLocPolicy = reader.readByte();
|
||||
final telemetryFlag = reader.readByte();
|
||||
_telemetryModeBase = telemetryFlag & 0x03;
|
||||
_telemetryModeEnv = telemetryFlag >> 2 & 0x03;
|
||||
_telemetryModeLoc = telemetryFlag >> 4 & 0x03;
|
||||
|
||||
_currentTxPower = frame[2];
|
||||
_maxTxPower = frame[3];
|
||||
_selfPublicKey = Uint8List.fromList(frame.sublist(4, 4 + pubKeySize));
|
||||
_selfLatitude = readInt32LE(frame, 36) / 1000000.0;
|
||||
_selfLongitude = readInt32LE(frame, 40) / 1000000.0;
|
||||
_manualAddContacts = reader.readByte() & 0x01 == 0x00;
|
||||
|
||||
// Radio settings (if frame is long enough)
|
||||
if (frame.length >= 58) {
|
||||
_currentFreqHz = readUint32LE(frame, 48);
|
||||
_currentBwHz = readUint32LE(frame, 52);
|
||||
_currentSf = frame[56];
|
||||
_currentCr = frame[57];
|
||||
}
|
||||
_currentFreqHz = reader.readUInt32LE();
|
||||
_currentBwHz = reader.readUInt32LE();
|
||||
_currentSf = reader.readByte();
|
||||
_currentCr = reader.readByte();
|
||||
|
||||
// Node name starts at offset 58 if frame is long enough
|
||||
if (frame.length > 58) {
|
||||
_selfName = readCString(frame, 58, frame.length - 58);
|
||||
_selfName = reader.readString();
|
||||
} catch (e) {
|
||||
_appDebugLogService?.error(
|
||||
'Error parsing SELF_INFO frame: $e',
|
||||
tag: 'Connector',
|
||||
);
|
||||
}
|
||||
final selfName = _selfName?.trim();
|
||||
if (_activeTransport == MeshCoreTransportType.usb &&
|
||||
|
|
@ -2486,25 +2556,6 @@ class MeshCoreConnector extends ChangeNotifier {
|
|||
unawaited(_requestNextQueuedMessage());
|
||||
}
|
||||
|
||||
void _handleRadioSettings(Uint8List frame) {
|
||||
// Frame format from C++:
|
||||
// [0] = RESP_CODE_RADIO_SETTINGS
|
||||
// [1-4] = freq (uint32 LE, in Hz)
|
||||
// [5-8] = bw (uint32 LE, in Hz)
|
||||
// [9] = sf
|
||||
// [10] = cr
|
||||
if (frame.length >= 11) {
|
||||
_currentFreqHz = readUint32LE(frame, 1);
|
||||
_currentBwHz = readUint32LE(frame, 5);
|
||||
_currentSf = frame[9];
|
||||
_currentCr = frame[10];
|
||||
debugPrint(
|
||||
'Radio settings: freq=$_currentFreqHz bw=$_currentBwHz sf=$_currentSf cr=$_currentCr',
|
||||
);
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
|
||||
void _handleBatteryAndStorage(Uint8List frame) {
|
||||
// Frame format from C++:
|
||||
// [0] = RESP_CODE_BATT_AND_STORAGE
|
||||
|
|
@ -2522,6 +2573,32 @@ class MeshCoreConnector extends ChangeNotifier {
|
|||
}
|
||||
}
|
||||
|
||||
void _checkManualAddContacts() async {
|
||||
// If manual add contacts is enabled, set auto add config and other params.
|
||||
// and disable it after
|
||||
if (_manualAddContacts) {
|
||||
await sendFrame(
|
||||
buildSetAutoAddConfigFrame(
|
||||
autoAddChat: true,
|
||||
autoAddRepeater: true,
|
||||
autoAddRoomServer: true,
|
||||
autoAddSensor: true,
|
||||
overwriteOldest: _overwriteOldest,
|
||||
),
|
||||
);
|
||||
await sendFrame(
|
||||
buildSetOtherParamsFrame(
|
||||
(_telemetryModeEnv << 4) |
|
||||
(_telemetryModeLoc << 2) |
|
||||
(_telemetryModeBase),
|
||||
_advertLocPolicy,
|
||||
_multiAcks,
|
||||
),
|
||||
);
|
||||
_manualAddContacts = false;
|
||||
}
|
||||
}
|
||||
|
||||
/// Calculate timeout for a message based on radio settings and path length
|
||||
/// Returns timeout in milliseconds, considering number of hops
|
||||
int calculateTimeout({required int pathLength, int messageBytes = 100}) {
|
||||
|
|
@ -2705,6 +2782,10 @@ class MeshCoreConnector extends ChangeNotifier {
|
|||
await _contactStore.saveContacts(_contacts);
|
||||
}
|
||||
|
||||
Future<void> _persistDiscoveredContacts() async {
|
||||
await _discoveryContactStore.saveContacts(_discoveredContacts);
|
||||
}
|
||||
|
||||
int _latestContactLastmod() {
|
||||
if (_contacts.isEmpty) return 0;
|
||||
var latest = 0;
|
||||
|
|
@ -4111,12 +4192,47 @@ class MeshCoreConnector extends ChangeNotifier {
|
|||
}
|
||||
}
|
||||
|
||||
void markNotifyDirty() {
|
||||
if (_notifyListenersDirty && _notifyListenersTimer != null) {
|
||||
return;
|
||||
}
|
||||
|
||||
_notifyListenersDirty = true;
|
||||
_notifyListenersTimer ??= Timer(
|
||||
_notifyListenersDebounce,
|
||||
_flushBatchedNotify,
|
||||
);
|
||||
}
|
||||
|
||||
void _flushBatchedNotify() {
|
||||
_notifyListenersTimer = null;
|
||||
if (!_notifyListenersDirty) {
|
||||
return;
|
||||
}
|
||||
|
||||
_notifyListenersDirty = false;
|
||||
super.notifyListeners();
|
||||
|
||||
if (_notifyListenersDirty && _notifyListenersTimer == null) {
|
||||
_notifyListenersTimer = Timer(
|
||||
_notifyListenersDebounce,
|
||||
_flushBatchedNotify,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void notifyListeners() {
|
||||
markNotifyDirty();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_scanSubscription?.cancel();
|
||||
_connectionSubscription?.cancel();
|
||||
_usbFrameSubscription?.cancel();
|
||||
_notifySubscription?.cancel();
|
||||
_notifyListenersTimer?.cancel();
|
||||
_reconnectTimer?.cancel();
|
||||
_batteryPollTimer?.cancel();
|
||||
_receivedFramesController.close();
|
||||
|
|
@ -4151,22 +4267,99 @@ class MeshCoreConnector extends ChangeNotifier {
|
|||
appLogger.warn('Malformed RX frame: $e', tag: 'Connector');
|
||||
return;
|
||||
}
|
||||
|
||||
final rawPacket = frame.sublist(3);
|
||||
switch (payloadType) {
|
||||
case payloadTypeADVERT:
|
||||
_handlePayloadAdvertReceived(payload, pathBytes, routeType, snr);
|
||||
_handlePayloadAdvertReceived(
|
||||
rawPacket,
|
||||
payload,
|
||||
pathBytes,
|
||||
routeType,
|
||||
snr,
|
||||
);
|
||||
break;
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
void importContact(Uint8List frame) {
|
||||
final packet = BufferReader(frame);
|
||||
int payloadType = 0;
|
||||
Uint8List pathBytes = Uint8List(0);
|
||||
try {
|
||||
packet.skipBytes(1); // Skip frame type byte
|
||||
packet.skipBytes(1); // Skip SNR byte
|
||||
packet.skipBytes(1); // Skip RSSI byte
|
||||
final header = packet.readByte();
|
||||
payloadType = (header >> 2) & 0x0F;
|
||||
//final payloadVer = (header >> 6) & 0x03;
|
||||
final pathLen = packet.readByte();
|
||||
pathBytes = packet.readBytes(pathLen);
|
||||
} catch (e) {
|
||||
appLogger.warn('Malformed RX frame: $e', tag: 'Connector');
|
||||
return;
|
||||
}
|
||||
double latitude = 0.0;
|
||||
double longitude = 0.0;
|
||||
String name = '';
|
||||
Uint8List publicKey = Uint8List(0);
|
||||
int type = 0;
|
||||
int timestamp = 0;
|
||||
bool hasLocation = false;
|
||||
bool hasName = false;
|
||||
if (payloadType != payloadTypeADVERT) {
|
||||
appLogger.warn('Unexpected payload type: $payloadType', tag: 'Connector');
|
||||
return;
|
||||
}
|
||||
try {
|
||||
publicKey = packet.readBytes(32);
|
||||
timestamp = packet.readInt32LE();
|
||||
//TODO add signature verification
|
||||
packet.skipBytes(64); // Skip signature for now
|
||||
final flags = packet.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 && packet.remaining >= 8) {
|
||||
latitude = packet.readInt32LE() / 1e6;
|
||||
longitude = packet.readInt32LE() / 1e6;
|
||||
}
|
||||
if (hasName && packet.remaining > 0) {
|
||||
name = packet.readString();
|
||||
}
|
||||
} catch (e) {
|
||||
appLogger.warn('Malformed advert frame: $e', tag: 'Connector');
|
||||
return;
|
||||
}
|
||||
|
||||
importDiscoveredContact(
|
||||
DiscoveryContact(
|
||||
rawPacket: frame,
|
||||
publicKey: publicKey,
|
||||
name: name,
|
||||
type: type,
|
||||
pathLength: pathBytes.length,
|
||||
path: Uint8List.fromList(
|
||||
pathBytes.reversed.toList(),
|
||||
), // Store path in reverse for easier use in outgoing messages
|
||||
latitude: latitude,
|
||||
longitude: longitude,
|
||||
lastSeen: DateTime.fromMillisecondsSinceEpoch(timestamp * 1000),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _handlePayloadAdvertReceived(
|
||||
Uint8List frame,
|
||||
Uint8List rawPacket,
|
||||
Uint8List payload,
|
||||
Uint8List path,
|
||||
int routeType,
|
||||
double snr,
|
||||
) {
|
||||
final advert = BufferReader(frame);
|
||||
final advert = BufferReader(payload);
|
||||
double latitude = 0.0;
|
||||
double longitude = 0.0;
|
||||
String name = '';
|
||||
|
|
@ -4204,6 +4397,7 @@ class MeshCoreConnector extends ChangeNotifier {
|
|||
return;
|
||||
}
|
||||
|
||||
//We ignore our own adverts
|
||||
if (listEquals(publicKey, _selfPublicKey)) {
|
||||
return;
|
||||
}
|
||||
|
|
@ -4224,7 +4418,14 @@ class MeshCoreConnector extends ChangeNotifier {
|
|||
longitude: longitude,
|
||||
lastSeen: DateTime.fromMillisecondsSinceEpoch(timestamp * 1000),
|
||||
);
|
||||
_handleContactAdvert(newContact);
|
||||
if ((_autoAddUsers && type == advTypeChat) ||
|
||||
(_autoAddRepeaters && type == advTypeRepeater) ||
|
||||
(_autoAddRoomServers && type == advTypeRoom) ||
|
||||
(_autoAddSensors && type == advTypeSensor)) {
|
||||
_handleContactAdvert(newContact);
|
||||
} else {
|
||||
_handleDiscovery(newContact, rawPacket);
|
||||
}
|
||||
_updateDirectRepeater(newContact, snr, path);
|
||||
return;
|
||||
}
|
||||
|
|
@ -4312,6 +4513,84 @@ class MeshCoreConnector extends ChangeNotifier {
|
|||
}
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
void _handleAutoAddConfig(Uint8List frame) {
|
||||
final reader = BufferReader(frame);
|
||||
try {
|
||||
reader.skipBytes(1); // Skip the response code byte
|
||||
final flags = reader.readByte();
|
||||
_autoAddUsers = flags & autoAddChatFlag != 0;
|
||||
_autoAddRepeaters = flags & autoAddRepeaterFlag != 0;
|
||||
_autoAddRoomServers = flags & autoAddRoomServerFlag != 0;
|
||||
_autoAddSensors = flags & autoAddSensorFlag != 0;
|
||||
_overwriteOldest = flags & autoAddOverwriteOldestFlag != 0;
|
||||
} catch (e) {
|
||||
appLogger.error('Failed to parse auto-add config: $e', tag: 'Connector');
|
||||
}
|
||||
}
|
||||
|
||||
void _handleDiscovery(
|
||||
Contact contact,
|
||||
Uint8List rawPacket, {
|
||||
bool noNotify = false,
|
||||
}) {
|
||||
appLogger.info('Discovered new contact: ${contact.name}', tag: 'Connector');
|
||||
|
||||
final existingIndex = _discoveredContacts.indexWhere(
|
||||
(c) => c.publicKeyHex == contact.publicKeyHex,
|
||||
);
|
||||
|
||||
// Update existing contact
|
||||
if (existingIndex >= 0) {
|
||||
_discoveredContacts[existingIndex] = _discoveredContacts[existingIndex]
|
||||
.copyWith(
|
||||
rawPacket: rawPacket,
|
||||
name: contact.name,
|
||||
type: contact.type,
|
||||
pathLength: contact.pathLength,
|
||||
path: contact.path,
|
||||
latitude: contact.latitude,
|
||||
longitude: contact.longitude,
|
||||
lastSeen: contact.lastSeen,
|
||||
);
|
||||
notifyListeners();
|
||||
unawaited(_persistDiscoveredContacts());
|
||||
return;
|
||||
}
|
||||
|
||||
final disContact = DiscoveryContact(
|
||||
rawPacket: rawPacket,
|
||||
publicKey: contact.publicKey,
|
||||
name: contact.name,
|
||||
type: contact.type,
|
||||
pathLength: contact.pathLength,
|
||||
path: contact.path,
|
||||
latitude: contact.latitude,
|
||||
longitude: contact.longitude,
|
||||
lastSeen: contact.lastSeen,
|
||||
);
|
||||
_discoveredContacts.add(disContact);
|
||||
|
||||
unawaited(_persistDiscoveredContacts());
|
||||
|
||||
// Show notification for new contact (advertisement)
|
||||
if (_appSettingsService != null && !noNotify) {
|
||||
final settings = _appSettingsService!.settings;
|
||||
if (settings.notificationsEnabled && settings.notifyOnNewAdvert) {
|
||||
_notificationService.showAdvertNotification(
|
||||
contactName: contact.name,
|
||||
contactType: contact.typeLabel,
|
||||
contactId: contact.publicKeyHex,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void removeAllDiscoveredContacts() {
|
||||
_discoveredContacts.clear();
|
||||
unawaited(_persistDiscoveredContacts());
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
|
||||
const int _phRouteMask = 0x03;
|
||||
|
|
|
|||
|
|
@ -120,25 +120,27 @@ class BufferWriter {
|
|||
}
|
||||
|
||||
void writeHex(String hex) {
|
||||
// Validate hex string length is even and not empty
|
||||
if (hex.isEmpty || hex.length % 2 != 0) {
|
||||
throw FormatException('Invalid hex string length: ${hex.length}');
|
||||
}
|
||||
List<int> result = [];
|
||||
for (int i = 0; i < hex.length ~/ 2; i++) {
|
||||
final hexByte = hex.substring(i * 2, i * 2 + 2);
|
||||
final byte = int.tryParse(hexByte, radix: 16);
|
||||
if (byte == null) {
|
||||
throw FormatException(
|
||||
'Invalid hex characters at position $i: $hexByte',
|
||||
);
|
||||
}
|
||||
result.add(byte);
|
||||
}
|
||||
writeBytes(Uint8List.fromList(result));
|
||||
writeBytes(hex2Uint8List(hex));
|
||||
}
|
||||
}
|
||||
|
||||
Uint8List hex2Uint8List(String hex) {
|
||||
// Validate hex string length is even and not empty
|
||||
if (hex.isEmpty || hex.length % 2 != 0) {
|
||||
throw FormatException('Invalid hex string length: ${hex.length}');
|
||||
}
|
||||
List<int> result = [];
|
||||
for (int i = 0; i < hex.length ~/ 2; i++) {
|
||||
final hexByte = hex.substring(i * 2, i * 2 + 2);
|
||||
final byte = int.tryParse(hexByte, radix: 16);
|
||||
if (byte == null) {
|
||||
throw FormatException('Invalid hex characters at position $i: $hexByte');
|
||||
}
|
||||
result.add(byte);
|
||||
}
|
||||
return Uint8List.fromList(result);
|
||||
}
|
||||
|
||||
// Command codes (to device)
|
||||
const int cmdAppStart = 1;
|
||||
const int cmdSendTxtMsg = 2;
|
||||
|
|
@ -168,11 +170,13 @@ const int cmdGetChannel = 31;
|
|||
const int cmdSetChannel = 32;
|
||||
const int cmdSendTracePath = 36;
|
||||
const int cmdSetOtherParams = 38;
|
||||
const int cmdGetRadioSettings = 57;
|
||||
const int cmdSendAnonReq = 57;
|
||||
const int cmdGetTelemetryReq = 39;
|
||||
const int cmdGetCustomVar = 40;
|
||||
const int cmdSetCustomVar = 41;
|
||||
const int cmdSendBinaryReq = 50;
|
||||
const int cmdSetAutoAddConfig = 58;
|
||||
const int cmdGetAutoAddConfig = 59;
|
||||
|
||||
// Text message types
|
||||
const int txtTypePlain = 0;
|
||||
|
|
@ -206,8 +210,8 @@ const int respCodeDeviceInfo = 13;
|
|||
const int respCodeContactMsgRecvV3 = 16;
|
||||
const int respCodeChannelMsgRecvV3 = 17;
|
||||
const int respCodeChannelInfo = 18;
|
||||
const int respCodeRadioSettings = 25;
|
||||
const int respCodeCustomVars = 21;
|
||||
const int respCodeAutoAddConfig = 25;
|
||||
|
||||
// Push codes (async from device)
|
||||
const int pushCodeAdvert = 0x80;
|
||||
|
|
@ -253,6 +257,18 @@ const int payloadTypeCONTROL = 0x0B; // a control/discovery packet
|
|||
const int payloadTypeRawCustom =
|
||||
0x0F; // custom packet as raw bytes, for applications with custom encryption, payloads, etc
|
||||
|
||||
//auto-add flags
|
||||
const int autoAddOverwriteOldestFlag =
|
||||
1 << 0; // 0x01 - overwrite oldest non-favourite when full
|
||||
const int autoAddChatFlag =
|
||||
1 << 1; // 0x02 - auto-add Chat (Companion) (ADV_TYPE_CHAT)
|
||||
const int autoAddRepeaterFlag =
|
||||
1 << 2; // 0x04 - auto-add Repeater (ADV_TYPE_REPEATER)
|
||||
const int autoAddRoomServerFlag =
|
||||
1 << 3; // 0x08 - auto-add Room Server (ADV_TYPE_ROOM)
|
||||
const int autoAddSensorFlag =
|
||||
1 << 4; // 0x10 - auto-add Sensor (ADV_TYPE_SENSOR)
|
||||
|
||||
// Sizes
|
||||
const int pubKeySize = 32;
|
||||
const int maxPathSize = 64;
|
||||
|
|
@ -303,7 +319,7 @@ const int contactNameOffset = 100;
|
|||
const int contactTimestampOffset = 132;
|
||||
const int contactLatOffset = 136;
|
||||
const int contactLonOffset = 140;
|
||||
const int contactLastmodOffset = 144;
|
||||
const int contactLastModOffset = 144;
|
||||
const int contactFrameSize = 148;
|
||||
|
||||
// Message frame offsets
|
||||
|
|
@ -681,16 +697,15 @@ Uint8List buildGetContactByKeyFrame(Uint8List pubKey) {
|
|||
return writer.toBytes();
|
||||
}
|
||||
|
||||
// Build CMD_GET_RADIO_SETTINGS frame
|
||||
Uint8List buildGetRadioSettingsFrame() {
|
||||
return Uint8List.fromList([cmdGetRadioSettings]);
|
||||
}
|
||||
|
||||
//Build CMD_GET_CUSTOM_VARS frame
|
||||
Uint8List buildGetCustomVarsFrame() {
|
||||
return Uint8List.fromList([cmdGetCustomVar]);
|
||||
}
|
||||
|
||||
Uint8List buildGetAutoAddFlagsFrame() {
|
||||
return Uint8List.fromList([cmdGetAutoAddConfig]);
|
||||
}
|
||||
|
||||
// Calculate LoRa airtime for a packet
|
||||
// Based on Semtech SX127x datasheet formula
|
||||
// Returns airtime in milliseconds
|
||||
|
|
@ -815,10 +830,10 @@ Uint8List buildExportContactFrame(Uint8List pubKey) {
|
|||
|
||||
// Build a import contact frame
|
||||
// [cmd][contact_frame x98+]
|
||||
Uint8List buildImportContactFrame(String contactFrame) {
|
||||
Uint8List buildImportContactFrame(Uint8List contactFrame) {
|
||||
final writer = BufferWriter();
|
||||
writer.writeByte(cmdImportContact);
|
||||
writer.writeHex(contactFrame);
|
||||
writer.writeBytes(contactFrame);
|
||||
return writer.toBytes();
|
||||
}
|
||||
|
||||
|
|
@ -832,20 +847,40 @@ Uint8List buildZeroHopContact(Uint8List pubKey) {
|
|||
}
|
||||
|
||||
// Build CMD_SET_OTHER_PARAMS frame
|
||||
// Format: [cmd][allowAutoAddContacts][allowTelemetryFlags][advertLocationPolicy][multiAcks]
|
||||
// Format: [cmd][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
|
||||
//Going forward the app will just set Auto Add Contacts to disabled, and use the filter flags
|
||||
//Allow Auto Add Contacts use inverted logic (0x01 = disabled, 0x00 = enabled).
|
||||
writer.writeByte(0x01);
|
||||
writer.writeByte(allowTelemetryFlags); // Allow Telemetry Flags
|
||||
writer.writeByte(advertLocationPolicy); // Advertisement Location Policy
|
||||
writer.writeByte(multiAcks); // Multi Acknowledgements
|
||||
return writer.toBytes();
|
||||
}
|
||||
|
||||
// Build CMD_SET_AUTO_ADD_CONFIG frame
|
||||
// Format: [cmd][flags]
|
||||
Uint8List buildSetAutoAddConfigFrame({
|
||||
required bool autoAddChat,
|
||||
required bool autoAddRepeater,
|
||||
required bool autoAddRoomServer,
|
||||
required bool autoAddSensor,
|
||||
required bool overwriteOldest,
|
||||
}) {
|
||||
final writer = BufferWriter();
|
||||
writer.writeByte(cmdSetAutoAddConfig);
|
||||
int flags = 0;
|
||||
if (autoAddChat) flags |= autoAddChatFlag;
|
||||
if (autoAddRepeater) flags |= autoAddRepeaterFlag;
|
||||
if (autoAddRoomServer) flags |= autoAddRoomServerFlag;
|
||||
if (autoAddSensor) flags |= autoAddSensorFlag;
|
||||
if (overwriteOldest) flags |= autoAddOverwriteOldestFlag;
|
||||
writer.writeByte(flags);
|
||||
return writer.toBytes();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1802,23 +1802,31 @@
|
|||
"contacts_searchRepeaters": "Търсене на {number}{str} повтарящи се...",
|
||||
"contacts_searchContactsNoNumber": "Търси контакти...",
|
||||
"contacts_searchUsers": "Търсене на {number}{str} потребители...",
|
||||
"usbScreenSubtitle": "Изберете открит сериен уред и се свържете директно към вашия MeshCore възел.",
|
||||
"usbScreenStatus": "Изберете USB устройство",
|
||||
"usbScreenTitle": "Свързване чрез USB",
|
||||
"usbScreenNote": "USB серийната връзка е активна на поддържаните Android устройства и настолни платформи.",
|
||||
"usbScreenEmptyState": "Няма открити USB устройства. Включете едно и опитайте отново.",
|
||||
"usbErrorPermissionDenied": "Не беше разрешено достъпът през USB.",
|
||||
"usbErrorDeviceMissing": "Избраното USB устройство вече не е налично.",
|
||||
"usbErrorInvalidPort": "Изберете валидно USB устройство.",
|
||||
"usbErrorBusy": "Друг мол за свързване през USB вече е в процес на изпълнение.",
|
||||
"usbErrorNotConnected": "Няма свързано USB устройство.",
|
||||
"usbErrorOpenFailed": "Не успях да отворя избраното USB устройство.",
|
||||
"usbErrorConnectFailed": "Не успях да се свържа с избраното USB устройство.",
|
||||
"usbErrorUnsupported": "USB серийната комуникация не се поддържа на тази платформа.",
|
||||
"usbErrorAlreadyActive": "USB връзката вече е активирана.",
|
||||
"usbErrorNoDeviceSelected": "Няма избран USB устройство.",
|
||||
"usbErrorPortClosed": "USB връзката не е активна.",
|
||||
"usbErrorConnectTimedOut": "Изчаква се, но устройството не отговаря в рамките на зададения време.",
|
||||
"connectionChoiceBluetoothLabel": "Bluetooth",
|
||||
"connectionChoiceUsbLabel": "USB"
|
||||
"contactsSettings_title": "Настройки на контактите",
|
||||
"contactsSettings_autoAddTitle": "Автоматично откриване",
|
||||
"contactsSettings_autoAddUsersTitle": "Автоматично добавяне на потребители",
|
||||
"contactsSettings_otherTitle": "Други настройки свързани с контакти",
|
||||
"settings_contactSettingsSubtitle": "Настройки за добавяне на контакти.",
|
||||
"settings_contactSettings": "Настройки за контакти",
|
||||
"contactsSettings_autoAddSensorsTitle": "Автоматично добавяне на датчици",
|
||||
"contactsSettings_autoAddRoomServersTitle": "Автоматично добавяне на сървъри на стаите",
|
||||
"contactsSettings_autoAddRoomServersSubtitle": "Позволи на спътника да добавя автоматично откритите сървъри на стаите.",
|
||||
"contactsSettings_autoAddRepeatersTitle": "Автоматично добавяне на повтарящи се елементи",
|
||||
"contactsSettings_autoAddUsersSubtitle": "Позволи на спътника да добавя автоматично откритите потребители.",
|
||||
"contactsSettings_autoAddRepeatersSubtitle": "Позволи на спътника да добавя автоматично откритите повтарящи се устройства.",
|
||||
"contactsSettings_autoAddSensorsSubtitle": "Позволи на спътника да добавя автоматично откритите датчици.",
|
||||
"contactsSettings_overwriteOldestTitle": "Премахни най-старото",
|
||||
"discoveredContacts_Title": "Открити контакти",
|
||||
"discoveredContacts_searchHint": "Търсене на открити контакти",
|
||||
"discoveredContacts_noMatching": "Няма съвпадащи контакти",
|
||||
"discoveredContacts_contactAdded": "Контакт добавен",
|
||||
"discoveredContacts_copyContact": "Копирай контакт в клипборда",
|
||||
"discoveredContacts_deleteContact": "Изтрий контакт",
|
||||
"discoveredContacts_addContact": "Добави контакт",
|
||||
"contactsSettings_overwriteOldestSubtitle": "Когато списъкът с контакти е пълен, най-старият неключов контакт ще бъде заменен.",
|
||||
"discoveredContacts_deleteContactAll": "Изтриване на Всички Открити Контакти",
|
||||
"discoveredContacts_deleteContactAllContent": "Сигурни ли сте, че искате да изтриете всички открити контакти?",
|
||||
"common_deleteAll": "Изтрий всичко",
|
||||
"map_guessedLocation": "Предполагано местоположение",
|
||||
"map_showGuessedLocations": "Покажете местоположенията на предположените възли."
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1830,23 +1830,31 @@
|
|||
"contacts_searchFavorites": "Suche {number}{str} Favoriten...",
|
||||
"contacts_searchUsers": "Suche {number}{str} Benutzer...",
|
||||
"contacts_searchRoomServers": "Suche {number}{str} Raumserver...",
|
||||
"usbScreenSubtitle": "Wählen Sie ein erkannten serielles Gerät aus und verbinden Sie es direkt mit Ihrem MeshCore-Knoten.",
|
||||
"usbScreenTitle": "Über USB verbinden",
|
||||
"usbScreenNote": "USB-Serielle Schnittstelle ist auf unterstützten Android-Geräten und Desktop-Plattformen aktiv.",
|
||||
"usbScreenStatus": "Wählen Sie ein USB-Gerät aus",
|
||||
"usbScreenEmptyState": "Keine USB-Geräte gefunden. Schließen Sie eines an und aktualisieren Sie.",
|
||||
"usbErrorPermissionDenied": "Die USB-Berechtigung wurde abgelehnt.",
|
||||
"usbErrorDeviceMissing": "Das ausgewählte USB-Gerät ist nicht mehr verfügbar.",
|
||||
"usbErrorInvalidPort": "Wählen Sie ein gültiges USB-Gerät aus.",
|
||||
"usbErrorBusy": "Eine weitere Anfrage für eine USB-Verbindung ist bereits in Bearbeitung.",
|
||||
"usbErrorNotConnected": "Es ist kein USB-Gerät angeschlossen.",
|
||||
"usbErrorOpenFailed": "Fehlgeschlagen beim Öffnen des ausgewählten USB-Geräts.",
|
||||
"usbErrorConnectFailed": "Keine Verbindung zum ausgewählten USB-Gerät hergestellt.",
|
||||
"usbErrorUnsupported": "Die Unterstützung für USB-Seriellschnittstellen ist auf dieser Plattform nicht vorhanden.",
|
||||
"usbErrorAlreadyActive": "Eine USB-Verbindung ist bereits hergestellt.",
|
||||
"usbErrorNoDeviceSelected": "Kein USB-Gerät wurde ausgewählt.",
|
||||
"usbErrorPortClosed": "Die USB-Verbindung ist nicht aktiv.",
|
||||
"usbErrorConnectTimedOut": "Die Wartezeit ist abgelaufen, da keine Antwort vom Gerät empfangen wurde.",
|
||||
"connectionChoiceBluetoothLabel": "Bluetooth",
|
||||
"connectionChoiceUsbLabel": "USB"
|
||||
"settings_contactSettings": "Kontakteinstellungen",
|
||||
"contactsSettings_otherTitle": "Weitere Einstellungen zu Kontakten",
|
||||
"contactsSettings_title": "Kontakteinstellungen",
|
||||
"contactsSettings_autoAddTitle": "Automatische Erkennung",
|
||||
"contactsSettings_autoAddUsersTitle": "Automatische Hinzufügung von Benutzern",
|
||||
"settings_contactSettingsSubtitle": "Einstellungen für das Hinzufügen von Kontakten",
|
||||
"contactsSettings_autoAddSensorsTitle": "Automatisch Sensoren hinzufügen",
|
||||
"contactsSettings_autoAddUsersSubtitle": "Ermöglichen Sie dem Begleiter, automatisch entdeckte Benutzer hinzuzufügen",
|
||||
"contactsSettings_autoAddRoomServersTitle": "Automatisch Raumservers hinzufügen",
|
||||
"contactsSettings_autoAddRoomServersSubtitle": "Ermöglichen Sie dem Begleiter, entdeckte Raumserver automatisch hinzuzufügen",
|
||||
"contactsSettings_autoAddRepeatersTitle": "Automatisch Repeater hinzufügen",
|
||||
"contactsSettings_autoAddRepeatersSubtitle": "Ermöglichen Sie dem Begleiter, automatisch entdeckte Repeater hinzuzufügen.",
|
||||
"discoveredContacts_noMatching": "Keine passenden Kontakte",
|
||||
"discoveredContacts_searchHint": "Entdeckte Kontakte suchen",
|
||||
"discoveredContacts_addContact": "Kontakt hinzufügen",
|
||||
"discoveredContacts_contactAdded": "Kontakt hinzugefügt",
|
||||
"discoveredContacts_deleteContact": "Kontakt löschen",
|
||||
"discoveredContacts_Title": "Entdeckte Kontakte",
|
||||
"discoveredContacts_copyContact": "Kontakt in die Zwischenablage kopieren",
|
||||
"contactsSettings_overwriteOldestTitle": "Überschreiben des Ältesten",
|
||||
"contactsSettings_autoAddSensorsSubtitle": "Ermöglichen Sie dem Begleiter, automatisch entdeckte Sensoren hinzuzufügen",
|
||||
"contactsSettings_overwriteOldestSubtitle": "Wenn die Kontaktliste voll ist, wird der älteste nicht favorisierte Kontakt ersetzt.",
|
||||
"common_deleteAll": "Alles löschen",
|
||||
"discoveredContacts_deleteContactAllContent": "Sind Sie sicher, dass Sie alle gefundenen Kontakte löschen möchten?",
|
||||
"discoveredContacts_deleteContactAll": "Alle entdeckten Kontakte löschen",
|
||||
"map_showGuessedLocations": "Zeige die vermuteten Knotenpositionen",
|
||||
"map_guessedLocation": "Geschätzter Ort"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@
|
|||
"common_unknownDevice": "Unknown Device",
|
||||
"common_save": "Save",
|
||||
"common_delete": "Delete",
|
||||
"common_deleteAll": "Delete All",
|
||||
"common_close": "Close",
|
||||
"common_edit": "Edit",
|
||||
"common_add": "Add",
|
||||
|
|
@ -119,6 +120,8 @@
|
|||
"settings_locationIntervalInvalid": "Interval must be at least 60 seconds, and less than 86400 seconds.",
|
||||
"settings_latitude": "Latitude",
|
||||
"settings_longitude": "Longitude",
|
||||
"settings_contactSettings": "Contact Settings",
|
||||
"settings_contactSettingsSubtitle": "Settings for how contacts are added.",
|
||||
"settings_privacyMode": "Privacy Mode",
|
||||
"settings_privacyModeSubtitle": "Hide name/location in advertisements",
|
||||
"settings_privacyModeToggle": "Toggle privacy mode to hide your name and location in advertisements.",
|
||||
|
|
@ -791,6 +794,8 @@
|
|||
"map_publicKeyPrefix": "Public key prefix",
|
||||
"map_markers": "Markers",
|
||||
"map_showSharedMarkers": "Show shared markers",
|
||||
"map_showGuessedLocations": "Show guessed node locations",
|
||||
"map_guessedLocation": "Guessed location",
|
||||
"map_lastSeenTime": "Last Seen Time",
|
||||
"map_sharedPin": "Shared pin",
|
||||
"map_joinRoom": "Join Room",
|
||||
|
|
@ -1858,5 +1863,27 @@
|
|||
"settings_gpxExportShareText": "Map data exported from meshcore-open",
|
||||
"settings_gpxExportShareSubject": "meshcore-open GPX map data export",
|
||||
"snrIndicator_nearByRepeaters": "Nearby Repeaters",
|
||||
"snrIndicator_lastSeen": "Last seen"
|
||||
}
|
||||
"snrIndicator_lastSeen": "Last seen",
|
||||
"contactsSettings_title": "Contacts settings",
|
||||
"contactsSettings_autoAddTitle": "Automatic Discovery",
|
||||
"contactsSettings_otherTitle": "Other contact related settings",
|
||||
"contactsSettings_autoAddUsersTitle": "Auto-add users",
|
||||
"contactsSettings_autoAddUsersSubtitle": "Allow the companion to automatically add discovered users.",
|
||||
"contactsSettings_autoAddRepeatersTitle": "Auto-add repeaters",
|
||||
"contactsSettings_autoAddRepeatersSubtitle": "Allow the companion to automatically add discovered repeaters.",
|
||||
"contactsSettings_autoAddRoomServersTitle": "Auto-add room servers",
|
||||
"contactsSettings_autoAddRoomServersSubtitle": "Allow the companion to automatically add discovered room servers.",
|
||||
"contactsSettings_autoAddSensorsTitle": "Auto-add sensors",
|
||||
"contactsSettings_autoAddSensorsSubtitle": "Allow the companion to automatically add discovered sensors.",
|
||||
"contactsSettings_overwriteOldestTitle": "Overwrite Oldest",
|
||||
"contactsSettings_overwriteOldestSubtitle": "When the contact list is full, the oldest non-favorited contact will be replaced.",
|
||||
"discoveredContacts_Title": "Discovered Contacts",
|
||||
"discoveredContacts_noMatching": "No matching contacts",
|
||||
"discoveredContacts_searchHint": "Search discovered contacts",
|
||||
"discoveredContacts_contactAdded": "Contact added",
|
||||
"discoveredContacts_addContact": "Add Contact",
|
||||
"discoveredContacts_copyContact": "Copy Contact to clipboard",
|
||||
"discoveredContacts_deleteContact": "Delete Discovered Contact",
|
||||
"discoveredContacts_deleteContactAll": "Delete All Discovered Contacts",
|
||||
"discoveredContacts_deleteContactAllContent": "Are you sure you want to delete all discovered contacts?"
|
||||
}
|
||||
|
|
@ -1830,23 +1830,31 @@
|
|||
"contacts_searchUsers": "Buscar {number}{str} Usuarios...",
|
||||
"contacts_searchRepeaters": "Buscar {number}{str} Repetidores...",
|
||||
"contacts_searchRoomServers": "Buscar {number}{str} servidores de sala...",
|
||||
"usbScreenTitle": "Conecte mediante USB",
|
||||
"usbScreenNote": "La comunicación serial a través de USB está activa en dispositivos Android compatibles y en plataformas de escritorio.",
|
||||
"usbScreenStatus": "Seleccione un dispositivo USB",
|
||||
"usbScreenSubtitle": "Seleccione un dispositivo de serie detectado y conéctelo directamente a su nodo MeshCore.",
|
||||
"usbScreenEmptyState": "No se encontraron dispositivos USB. Conecte uno y vuelva a cargar.",
|
||||
"usbErrorPermissionDenied": "Se denegó el permiso de acceso a través de USB.",
|
||||
"usbErrorDeviceMissing": "El dispositivo USB seleccionado ya no está disponible.",
|
||||
"usbErrorInvalidPort": "Seleccione un dispositivo USB válido.",
|
||||
"usbErrorBusy": "Ya se ha iniciado una solicitud de conexión USB adicional.",
|
||||
"usbErrorNotConnected": "No hay ningún dispositivo USB conectado.",
|
||||
"usbErrorOpenFailed": "No se pudo abrir el dispositivo USB seleccionado.",
|
||||
"usbErrorConnectFailed": "No se pudo conectar con el dispositivo USB seleccionado.",
|
||||
"usbErrorUnsupported": "La comunicación serial mediante USB no está soportada en esta plataforma.",
|
||||
"usbErrorAlreadyActive": "La conexión USB ya está activa.",
|
||||
"usbErrorNoDeviceSelected": "No se ha seleccionado ningún dispositivo USB.",
|
||||
"usbErrorPortClosed": "La conexión USB no está activa.",
|
||||
"usbErrorConnectTimedOut": "Se ha producido un error debido a la espera prolongada para recibir una respuesta del dispositivo.",
|
||||
"connectionChoiceUsbLabel": "USB",
|
||||
"connectionChoiceBluetoothLabel": "Bluetooth"
|
||||
"contactsSettings_autoAddTitle": "Detección automática",
|
||||
"settings_contactSettings": "Configuración de contacto",
|
||||
"contactsSettings_autoAddUsersTitle": "Agregar usuarios automáticamente",
|
||||
"contactsSettings_otherTitle": "Otras configuraciones relacionadas con el contacto",
|
||||
"contactsSettings_autoAddUsersSubtitle": "Permitir que el compañero agregue automáticamente a los usuarios descubiertos.",
|
||||
"contactsSettings_autoAddRepeatersSubtitle": "Permitir que el compañero agregue automáticamente los repetidores descubiertos.",
|
||||
"contactsSettings_autoAddRoomServersSubtitle": "Permitir que el compañero agregue automáticamente los servidores de salas descubiertos.",
|
||||
"contactsSettings_autoAddSensorsTitle": "Agregar sensores automáticamente",
|
||||
"contactsSettings_title": "Configuración de contactos",
|
||||
"settings_contactSettingsSubtitle": "Configuración de cómo se agregan los contactos.",
|
||||
"contactsSettings_autoAddSensorsSubtitle": "Permitir que el compañero agregue automáticamente los sensores descubiertos.",
|
||||
"contactsSettings_autoAddRepeatersTitle": "Agregar repetidores automáticamente",
|
||||
"contactsSettings_overwriteOldestTitle": "Sobreescribir el más antiguo",
|
||||
"contactsSettings_autoAddRoomServersTitle": "Agregar automáticamente servidores de sala",
|
||||
"discoveredContacts_noMatching": "No se encontraron contactos coincidentes",
|
||||
"discoveredContacts_contactAdded": "Contacto agregado",
|
||||
"discoveredContacts_copyContact": "Copiar contacto al portapapeles",
|
||||
"discoveredContacts_deleteContact": "Eliminar contacto",
|
||||
"discoveredContacts_Title": "Contactos descubiertos",
|
||||
"discoveredContacts_searchHint": "Buscar contactos descubiertos",
|
||||
"discoveredContacts_addContact": "Agregar contacto",
|
||||
"contactsSettings_overwriteOldestSubtitle": "Cuando la lista de contactos esté llena, se reemplazará el contacto no favorito más antiguo.",
|
||||
"common_deleteAll": "Eliminar todo",
|
||||
"discoveredContacts_deleteContactAll": "Eliminar Todos los Contactos Descubiertos",
|
||||
"discoveredContacts_deleteContactAllContent": "¿Está seguro de que desea eliminar todos los contactos descubiertos!",
|
||||
"map_guessedLocation": "Ubicación estimada",
|
||||
"map_showGuessedLocations": "Mostrar las ubicaciones estimadas de los nodos."
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1802,23 +1802,31 @@
|
|||
"contacts_searchRoomServers": "Rechercher {number}{str} serveurs de salle...",
|
||||
"contacts_searchRepeaters": "Rechercher {number}{str} Répéteurs...",
|
||||
"contacts_searchContactsNoNumber": "Rechercher des contacts...",
|
||||
"usbScreenNote": "La communication série USB est active sur les appareils Android et les plateformes de bureau pris en charge.",
|
||||
"usbScreenSubtitle": "Sélectionnez un périphérique série détecté et connectez-vous directement à votre nœud MeshCore.",
|
||||
"usbScreenTitle": "Connectez via USB",
|
||||
"usbScreenStatus": "Sélectionnez un périphérique USB",
|
||||
"usbScreenEmptyState": "Aucun périphérique USB n'a été trouvé. Veuillez connecter un périphérique et rafraîchir la page.",
|
||||
"usbErrorPermissionDenied": "L'accès via USB a été refusé.",
|
||||
"usbErrorDeviceMissing": "Le périphérique USB sélectionné n'est plus disponible.",
|
||||
"usbErrorInvalidPort": "Sélectionnez un périphérique USB valide.",
|
||||
"usbErrorBusy": "Une autre demande de connexion USB est déjà en cours.",
|
||||
"usbErrorNotConnected": "Aucun appareil USB n'est connecté.",
|
||||
"usbErrorOpenFailed": "Impossible d'ouvrir l'appareil USB sélectionné.",
|
||||
"usbErrorConnectFailed": "Impossible de se connecter à l'appareil USB sélectionné.",
|
||||
"usbErrorUnsupported": "La communication série USB n'est pas prise en charge sur cette plateforme.",
|
||||
"usbErrorAlreadyActive": "Une connexion USB est déjà établie.",
|
||||
"usbErrorNoDeviceSelected": "Aucun appareil USB n'a été sélectionné.",
|
||||
"usbErrorPortClosed": "La connexion USB n'est pas établie.",
|
||||
"usbErrorConnectTimedOut": "Attente avec délai, en attendant une réponse de l'appareil.",
|
||||
"connectionChoiceBluetoothLabel": "Bluetooth",
|
||||
"connectionChoiceUsbLabel": "USB"
|
||||
"settings_contactSettings": "Paramètres de contact",
|
||||
"settings_contactSettingsSubtitle": "Paramètres pour l'ajout de contacts",
|
||||
"contactsSettings_autoAddRepeatersTitle": "Ajouter automatiquement les répéteurs",
|
||||
"contactsSettings_autoAddRepeatersSubtitle": "Autoriser le compagnon à ajouter automatiquement les répéteurs découverts",
|
||||
"contactsSettings_autoAddRoomServersTitle": "Ajouter automatiquement les serveurs de salle",
|
||||
"contactsSettings_autoAddRoomServersSubtitle": "Autoriser le compagnon à ajouter automatiquement les serveurs de salles découverts",
|
||||
"contactsSettings_otherTitle": "Autres paramètres liés aux contacts",
|
||||
"contactsSettings_title": "Paramètres des contacts",
|
||||
"contactsSettings_autoAddUsersTitle": "Ajouter automatiquement les utilisateurs",
|
||||
"contactsSettings_autoAddTitle": "Découverte automatique",
|
||||
"contactsSettings_autoAddSensorsTitle": "Ajouter automatiquement les capteurs",
|
||||
"contactsSettings_autoAddUsersSubtitle": "Autoriser le compagnon à ajouter automatiquement les utilisateurs découverts",
|
||||
"discoveredContacts_noMatching": "Aucun contact correspondant",
|
||||
"discoveredContacts_contactAdded": "Contact ajouté",
|
||||
"discoveredContacts_addContact": "Ajouter un contact",
|
||||
"discoveredContacts_copyContact": "Copier le contact dans le presse-papiers",
|
||||
"discoveredContacts_deleteContact": "Supprimer le contact",
|
||||
"contactsSettings_overwriteOldestTitle": "Écraser le plus ancien",
|
||||
"contactsSettings_autoAddSensorsSubtitle": "Autoriser le compagnon à ajouter automatiquement les capteurs découverts.",
|
||||
"discoveredContacts_Title": "Contacts découverts",
|
||||
"discoveredContacts_searchHint": "Rechercher des contacts découverts",
|
||||
"contactsSettings_overwriteOldestSubtitle": "Lorsque la liste de contacts est pleine, le contact le plus ancien non favori sera remplacé.",
|
||||
"common_deleteAll": "Supprimer tout",
|
||||
"discoveredContacts_deleteContactAll": "Supprimer tous les contacts découverts",
|
||||
"discoveredContacts_deleteContactAllContent": "Êtes-vous sûr de vouloir supprimer tous les contacts découverts ?",
|
||||
"map_showGuessedLocations": "Afficher les emplacements des nœuds estimés",
|
||||
"map_guessedLocation": "Lieu deviné"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1802,23 +1802,31 @@
|
|||
"contacts_unread": "Non letti",
|
||||
"contacts_searchRepeaters": "Cerca {number}{str} Ripetitori...",
|
||||
"contacts_searchRoomServers": "Cerca {number}{str} server Room...",
|
||||
"usbScreenNote": "La comunicazione seriale USB è attiva sui dispositivi Android supportati e sulle piattaforme desktop.",
|
||||
"usbScreenSubtitle": "Seleziona il dispositivo seriale rilevato e connettilo direttamente al tuo nodo MeshCore.",
|
||||
"usbScreenStatus": "Seleziona un dispositivo USB",
|
||||
"usbScreenTitle": "Connessione tramite USB",
|
||||
"usbScreenEmptyState": "Nessun dispositivo USB rilevato. Collegare uno e riavviare.",
|
||||
"usbErrorPermissionDenied": "È stato negato l'accesso tramite USB.",
|
||||
"usbErrorDeviceMissing": "Il dispositivo USB selezionato non è più disponibile.",
|
||||
"usbErrorInvalidPort": "Seleziona un dispositivo USB valido.",
|
||||
"usbErrorBusy": "Un'altra richiesta di connessione tramite USB è già in corso.",
|
||||
"usbErrorNotConnected": "Non è collegato alcun dispositivo USB.",
|
||||
"usbErrorOpenFailed": "Impossibile aprire il dispositivo USB selezionato.",
|
||||
"usbErrorConnectFailed": "Impossibile connettersi al dispositivo USB selezionato.",
|
||||
"usbErrorUnsupported": "La comunicazione seriale tramite USB non è supportata su questa piattaforma.",
|
||||
"usbErrorAlreadyActive": "La connessione USB è già attiva.",
|
||||
"usbErrorNoDeviceSelected": "Non è stato selezionato alcun dispositivo USB.",
|
||||
"usbErrorPortClosed": "La connessione USB non è attiva.",
|
||||
"usbErrorConnectTimedOut": "Attesa superata, in attesa di una risposta dal dispositivo.",
|
||||
"connectionChoiceUsbLabel": "USB",
|
||||
"connectionChoiceBluetoothLabel": "Bluetooth"
|
||||
"contactsSettings_title": "Impostazioni dei contatti",
|
||||
"settings_contactSettings": "Impostazioni di contatto",
|
||||
"contactsSettings_otherTitle": "Altre impostazioni relative ai contatti",
|
||||
"contactsSettings_autoAddUsersSubtitle": "Consenti al compagno di aggiungere automaticamente gli utenti scoperti.",
|
||||
"contactsSettings_autoAddRepeatersTitle": "Aggiungere ripetitori automaticamente",
|
||||
"contactsSettings_autoAddRoomServersSubtitle": "Consenti al compagno di aggiungere automaticamente i server delle stanze scoperte.",
|
||||
"contactsSettings_autoAddSensorsTitle": "Aggiungere automaticamente i sensori",
|
||||
"settings_contactSettingsSubtitle": "Impostazioni per l'aggiunta dei contatti",
|
||||
"contactsSettings_autoAddUsersTitle": "Aggiungere utenti automaticamente",
|
||||
"contactsSettings_autoAddTitle": "Scoperta automatica",
|
||||
"contactsSettings_autoAddSensorsSubtitle": "Consenti al compagno di aggiungere automaticamente i sensori scoperti",
|
||||
"discoveredContacts_noMatching": "Nessun contatto corrispondente",
|
||||
"contactsSettings_autoAddRepeatersSubtitle": "Consenti al compagno di aggiungere automaticamente i ripetitori scoperti.",
|
||||
"discoveredContacts_searchHint": "Cerca contatti scoperti",
|
||||
"contactsSettings_autoAddRoomServersTitle": "Aggiungere automaticamente i server delle stanze",
|
||||
"discoveredContacts_addContact": "Aggiungi contatto",
|
||||
"contactsSettings_overwriteOldestTitle": "Sostituisci il più vecchio",
|
||||
"discoveredContacts_Title": "Contatti scoperti",
|
||||
"discoveredContacts_contactAdded": "Contatto aggiunto",
|
||||
"discoveredContacts_deleteContact": "Elimina Contatto",
|
||||
"discoveredContacts_copyContact": "Copia contatto negli appunti",
|
||||
"contactsSettings_overwriteOldestSubtitle": "Quando l'elenco dei contatti è pieno, il contatto più vecchio non tra i preferiti verrà sostituito.",
|
||||
"common_deleteAll": "Elimina tutto",
|
||||
"discoveredContacts_deleteContactAllContent": "Sei sicuro di voler eliminare tutti i contatti scoperti?",
|
||||
"discoveredContacts_deleteContactAll": "Eliminare tutti i contatti scoperti",
|
||||
"map_guessedLocation": "Località indovinata",
|
||||
"map_showGuessedLocations": "Mostra le posizioni stimate dei nodi"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -184,6 +184,12 @@ abstract class AppLocalizations {
|
|||
/// **'Delete'**
|
||||
String get common_delete;
|
||||
|
||||
/// No description provided for @common_deleteAll.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Delete All'**
|
||||
String get common_deleteAll;
|
||||
|
||||
/// No description provided for @common_close.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
|
|
@ -670,6 +676,18 @@ abstract class AppLocalizations {
|
|||
/// **'Longitude'**
|
||||
String get settings_longitude;
|
||||
|
||||
/// No description provided for @settings_contactSettings.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Contact Settings'**
|
||||
String get settings_contactSettings;
|
||||
|
||||
/// No description provided for @settings_contactSettingsSubtitle.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Settings for how contacts are added.'**
|
||||
String get settings_contactSettingsSubtitle;
|
||||
|
||||
/// No description provided for @settings_privacyMode.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
|
|
@ -2734,6 +2752,18 @@ abstract class AppLocalizations {
|
|||
/// **'Show shared markers'**
|
||||
String get map_showSharedMarkers;
|
||||
|
||||
/// No description provided for @map_showGuessedLocations.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Show guessed node locations'**
|
||||
String get map_showGuessedLocations;
|
||||
|
||||
/// No description provided for @map_guessedLocation.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Guessed location'**
|
||||
String get map_guessedLocation;
|
||||
|
||||
/// No description provided for @map_lastSeenTime.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
|
|
@ -5506,6 +5536,138 @@ abstract class AppLocalizations {
|
|||
/// In en, this message translates to:
|
||||
/// **'Last seen'**
|
||||
String get snrIndicator_lastSeen;
|
||||
|
||||
/// No description provided for @contactsSettings_title.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Contacts settings'**
|
||||
String get contactsSettings_title;
|
||||
|
||||
/// No description provided for @contactsSettings_autoAddTitle.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Automatic Discovery'**
|
||||
String get contactsSettings_autoAddTitle;
|
||||
|
||||
/// No description provided for @contactsSettings_otherTitle.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Other contact related settings'**
|
||||
String get contactsSettings_otherTitle;
|
||||
|
||||
/// No description provided for @contactsSettings_autoAddUsersTitle.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Auto-add users'**
|
||||
String get contactsSettings_autoAddUsersTitle;
|
||||
|
||||
/// No description provided for @contactsSettings_autoAddUsersSubtitle.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Allow the companion to automatically add discovered users.'**
|
||||
String get contactsSettings_autoAddUsersSubtitle;
|
||||
|
||||
/// No description provided for @contactsSettings_autoAddRepeatersTitle.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Auto-add repeaters'**
|
||||
String get contactsSettings_autoAddRepeatersTitle;
|
||||
|
||||
/// No description provided for @contactsSettings_autoAddRepeatersSubtitle.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Allow the companion to automatically add discovered repeaters.'**
|
||||
String get contactsSettings_autoAddRepeatersSubtitle;
|
||||
|
||||
/// No description provided for @contactsSettings_autoAddRoomServersTitle.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Auto-add room servers'**
|
||||
String get contactsSettings_autoAddRoomServersTitle;
|
||||
|
||||
/// No description provided for @contactsSettings_autoAddRoomServersSubtitle.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Allow the companion to automatically add discovered room servers.'**
|
||||
String get contactsSettings_autoAddRoomServersSubtitle;
|
||||
|
||||
/// No description provided for @contactsSettings_autoAddSensorsTitle.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Auto-add sensors'**
|
||||
String get contactsSettings_autoAddSensorsTitle;
|
||||
|
||||
/// No description provided for @contactsSettings_autoAddSensorsSubtitle.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Allow the companion to automatically add discovered sensors.'**
|
||||
String get contactsSettings_autoAddSensorsSubtitle;
|
||||
|
||||
/// No description provided for @contactsSettings_overwriteOldestTitle.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Overwrite Oldest'**
|
||||
String get contactsSettings_overwriteOldestTitle;
|
||||
|
||||
/// No description provided for @contactsSettings_overwriteOldestSubtitle.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'When the contact list is full, the oldest non-favorited contact will be replaced.'**
|
||||
String get contactsSettings_overwriteOldestSubtitle;
|
||||
|
||||
/// No description provided for @discoveredContacts_Title.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Discovered Contacts'**
|
||||
String get discoveredContacts_Title;
|
||||
|
||||
/// No description provided for @discoveredContacts_noMatching.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'No matching contacts'**
|
||||
String get discoveredContacts_noMatching;
|
||||
|
||||
/// No description provided for @discoveredContacts_searchHint.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Search discovered contacts'**
|
||||
String get discoveredContacts_searchHint;
|
||||
|
||||
/// No description provided for @discoveredContacts_contactAdded.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Contact added'**
|
||||
String get discoveredContacts_contactAdded;
|
||||
|
||||
/// No description provided for @discoveredContacts_addContact.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Add Contact'**
|
||||
String get discoveredContacts_addContact;
|
||||
|
||||
/// No description provided for @discoveredContacts_copyContact.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Copy Contact to clipboard'**
|
||||
String get discoveredContacts_copyContact;
|
||||
|
||||
/// No description provided for @discoveredContacts_deleteContact.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Delete Discovered Contact'**
|
||||
String get discoveredContacts_deleteContact;
|
||||
|
||||
/// No description provided for @discoveredContacts_deleteContactAll.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Delete All Discovered Contacts'**
|
||||
String get discoveredContacts_deleteContactAll;
|
||||
|
||||
/// No description provided for @discoveredContacts_deleteContactAllContent.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Are you sure you want to delete all discovered contacts?'**
|
||||
String get discoveredContacts_deleteContactAllContent;
|
||||
}
|
||||
|
||||
class _AppLocalizationsDelegate
|
||||
|
|
|
|||
|
|
@ -38,6 +38,9 @@ class AppLocalizationsBg extends AppLocalizations {
|
|||
@override
|
||||
String get common_delete => 'Изтрий';
|
||||
|
||||
@override
|
||||
String get common_deleteAll => 'Изтрий всичко';
|
||||
|
||||
@override
|
||||
String get common_close => 'Затвори';
|
||||
|
||||
|
|
@ -307,6 +310,13 @@ class AppLocalizationsBg extends AppLocalizations {
|
|||
@override
|
||||
String get settings_longitude => 'Дължина';
|
||||
|
||||
@override
|
||||
String get settings_contactSettings => 'Настройки за контакти';
|
||||
|
||||
@override
|
||||
String get settings_contactSettingsSubtitle =>
|
||||
'Настройки за добавяне на контакти.';
|
||||
|
||||
@override
|
||||
String get settings_privacyMode => 'Режим на поверителност';
|
||||
|
||||
|
|
@ -1499,6 +1509,13 @@ class AppLocalizationsBg extends AppLocalizations {
|
|||
@override
|
||||
String get map_showSharedMarkers => 'Покажи споделени маркери';
|
||||
|
||||
@override
|
||||
String get map_showGuessedLocations =>
|
||||
'Покажете местоположенията на предположените възли.';
|
||||
|
||||
@override
|
||||
String get map_guessedLocation => 'Предполагано местоположение';
|
||||
|
||||
@override
|
||||
String get map_lastSeenTime => 'Последна видяна дата';
|
||||
|
||||
|
|
@ -3185,4 +3202,82 @@ class AppLocalizationsBg extends AppLocalizations {
|
|||
|
||||
@override
|
||||
String get snrIndicator_lastSeen => 'Последно видян';
|
||||
|
||||
@override
|
||||
String get contactsSettings_title => 'Настройки на контактите';
|
||||
|
||||
@override
|
||||
String get contactsSettings_autoAddTitle => 'Автоматично откриване';
|
||||
|
||||
@override
|
||||
String get contactsSettings_otherTitle =>
|
||||
'Други настройки свързани с контакти';
|
||||
|
||||
@override
|
||||
String get contactsSettings_autoAddUsersTitle =>
|
||||
'Автоматично добавяне на потребители';
|
||||
|
||||
@override
|
||||
String get contactsSettings_autoAddUsersSubtitle =>
|
||||
'Позволи на спътника да добавя автоматично откритите потребители.';
|
||||
|
||||
@override
|
||||
String get contactsSettings_autoAddRepeatersTitle =>
|
||||
'Автоматично добавяне на повтарящи се елементи';
|
||||
|
||||
@override
|
||||
String get contactsSettings_autoAddRepeatersSubtitle =>
|
||||
'Позволи на спътника да добавя автоматично откритите повтарящи се устройства.';
|
||||
|
||||
@override
|
||||
String get contactsSettings_autoAddRoomServersTitle =>
|
||||
'Автоматично добавяне на сървъри на стаите';
|
||||
|
||||
@override
|
||||
String get contactsSettings_autoAddRoomServersSubtitle =>
|
||||
'Позволи на спътника да добавя автоматично откритите сървъри на стаите.';
|
||||
|
||||
@override
|
||||
String get contactsSettings_autoAddSensorsTitle =>
|
||||
'Автоматично добавяне на датчици';
|
||||
|
||||
@override
|
||||
String get contactsSettings_autoAddSensorsSubtitle =>
|
||||
'Позволи на спътника да добавя автоматично откритите датчици.';
|
||||
|
||||
@override
|
||||
String get contactsSettings_overwriteOldestTitle => 'Премахни най-старото';
|
||||
|
||||
@override
|
||||
String get contactsSettings_overwriteOldestSubtitle =>
|
||||
'Когато списъкът с контакти е пълен, най-старият неключов контакт ще бъде заменен.';
|
||||
|
||||
@override
|
||||
String get discoveredContacts_Title => 'Открити контакти';
|
||||
|
||||
@override
|
||||
String get discoveredContacts_noMatching => 'Няма съвпадащи контакти';
|
||||
|
||||
@override
|
||||
String get discoveredContacts_searchHint => 'Търсене на открити контакти';
|
||||
|
||||
@override
|
||||
String get discoveredContacts_contactAdded => 'Контакт добавен';
|
||||
|
||||
@override
|
||||
String get discoveredContacts_addContact => 'Добави контакт';
|
||||
|
||||
@override
|
||||
String get discoveredContacts_copyContact => 'Копирай контакт в клипборда';
|
||||
|
||||
@override
|
||||
String get discoveredContacts_deleteContact => 'Изтрий контакт';
|
||||
|
||||
@override
|
||||
String get discoveredContacts_deleteContactAll =>
|
||||
'Изтриване на Всички Открити Контакти';
|
||||
|
||||
@override
|
||||
String get discoveredContacts_deleteContactAllContent =>
|
||||
'Сигурни ли сте, че искате да изтриете всички открити контакти?';
|
||||
}
|
||||
|
|
|
|||
|
|
@ -38,6 +38,9 @@ class AppLocalizationsDe extends AppLocalizations {
|
|||
@override
|
||||
String get common_delete => 'Löschen';
|
||||
|
||||
@override
|
||||
String get common_deleteAll => 'Alles löschen';
|
||||
|
||||
@override
|
||||
String get common_close => 'Schließen';
|
||||
|
||||
|
|
@ -308,6 +311,13 @@ class AppLocalizationsDe extends AppLocalizations {
|
|||
@override
|
||||
String get settings_longitude => 'Längengrad';
|
||||
|
||||
@override
|
||||
String get settings_contactSettings => 'Kontakteinstellungen';
|
||||
|
||||
@override
|
||||
String get settings_contactSettingsSubtitle =>
|
||||
'Einstellungen für das Hinzufügen von Kontakten';
|
||||
|
||||
@override
|
||||
String get settings_privacyMode => 'Privatsphäreeinstellung';
|
||||
|
||||
|
|
@ -1500,6 +1510,13 @@ class AppLocalizationsDe extends AppLocalizations {
|
|||
@override
|
||||
String get map_showSharedMarkers => 'Zeige gemeinsam genutzte Marker';
|
||||
|
||||
@override
|
||||
String get map_showGuessedLocations =>
|
||||
'Zeige die vermuteten Knotenpositionen';
|
||||
|
||||
@override
|
||||
String get map_guessedLocation => 'Geschätzter Ort';
|
||||
|
||||
@override
|
||||
String get map_lastSeenTime => 'Letzte Sichtung';
|
||||
|
||||
|
|
@ -3196,4 +3213,84 @@ class AppLocalizationsDe extends AppLocalizations {
|
|||
|
||||
@override
|
||||
String get snrIndicator_lastSeen => 'Zuletzt gesehen';
|
||||
|
||||
@override
|
||||
String get contactsSettings_title => 'Kontakteinstellungen';
|
||||
|
||||
@override
|
||||
String get contactsSettings_autoAddTitle => 'Automatische Erkennung';
|
||||
|
||||
@override
|
||||
String get contactsSettings_otherTitle =>
|
||||
'Weitere Einstellungen zu Kontakten';
|
||||
|
||||
@override
|
||||
String get contactsSettings_autoAddUsersTitle =>
|
||||
'Automatische Hinzufügung von Benutzern';
|
||||
|
||||
@override
|
||||
String get contactsSettings_autoAddUsersSubtitle =>
|
||||
'Ermöglichen Sie dem Begleiter, automatisch entdeckte Benutzer hinzuzufügen';
|
||||
|
||||
@override
|
||||
String get contactsSettings_autoAddRepeatersTitle =>
|
||||
'Automatisch Repeater hinzufügen';
|
||||
|
||||
@override
|
||||
String get contactsSettings_autoAddRepeatersSubtitle =>
|
||||
'Ermöglichen Sie dem Begleiter, automatisch entdeckte Repeater hinzuzufügen.';
|
||||
|
||||
@override
|
||||
String get contactsSettings_autoAddRoomServersTitle =>
|
||||
'Automatisch Raumservers hinzufügen';
|
||||
|
||||
@override
|
||||
String get contactsSettings_autoAddRoomServersSubtitle =>
|
||||
'Ermöglichen Sie dem Begleiter, entdeckte Raumserver automatisch hinzuzufügen';
|
||||
|
||||
@override
|
||||
String get contactsSettings_autoAddSensorsTitle =>
|
||||
'Automatisch Sensoren hinzufügen';
|
||||
|
||||
@override
|
||||
String get contactsSettings_autoAddSensorsSubtitle =>
|
||||
'Ermöglichen Sie dem Begleiter, automatisch entdeckte Sensoren hinzuzufügen';
|
||||
|
||||
@override
|
||||
String get contactsSettings_overwriteOldestTitle =>
|
||||
'Überschreiben des Ältesten';
|
||||
|
||||
@override
|
||||
String get contactsSettings_overwriteOldestSubtitle =>
|
||||
'Wenn die Kontaktliste voll ist, wird der älteste nicht favorisierte Kontakt ersetzt.';
|
||||
|
||||
@override
|
||||
String get discoveredContacts_Title => 'Entdeckte Kontakte';
|
||||
|
||||
@override
|
||||
String get discoveredContacts_noMatching => 'Keine passenden Kontakte';
|
||||
|
||||
@override
|
||||
String get discoveredContacts_searchHint => 'Entdeckte Kontakte suchen';
|
||||
|
||||
@override
|
||||
String get discoveredContacts_contactAdded => 'Kontakt hinzugefügt';
|
||||
|
||||
@override
|
||||
String get discoveredContacts_addContact => 'Kontakt hinzufügen';
|
||||
|
||||
@override
|
||||
String get discoveredContacts_copyContact =>
|
||||
'Kontakt in die Zwischenablage kopieren';
|
||||
|
||||
@override
|
||||
String get discoveredContacts_deleteContact => 'Kontakt löschen';
|
||||
|
||||
@override
|
||||
String get discoveredContacts_deleteContactAll =>
|
||||
'Alle entdeckten Kontakte löschen';
|
||||
|
||||
@override
|
||||
String get discoveredContacts_deleteContactAllContent =>
|
||||
'Sind Sie sicher, dass Sie alle gefundenen Kontakte löschen möchten?';
|
||||
}
|
||||
|
|
|
|||
|
|
@ -38,6 +38,9 @@ class AppLocalizationsEn extends AppLocalizations {
|
|||
@override
|
||||
String get common_delete => 'Delete';
|
||||
|
||||
@override
|
||||
String get common_deleteAll => 'Delete All';
|
||||
|
||||
@override
|
||||
String get common_close => 'Close';
|
||||
|
||||
|
|
@ -304,6 +307,13 @@ class AppLocalizationsEn extends AppLocalizations {
|
|||
@override
|
||||
String get settings_longitude => 'Longitude';
|
||||
|
||||
@override
|
||||
String get settings_contactSettings => 'Contact Settings';
|
||||
|
||||
@override
|
||||
String get settings_contactSettingsSubtitle =>
|
||||
'Settings for how contacts are added.';
|
||||
|
||||
@override
|
||||
String get settings_privacyMode => 'Privacy Mode';
|
||||
|
||||
|
|
@ -1476,6 +1486,12 @@ class AppLocalizationsEn extends AppLocalizations {
|
|||
@override
|
||||
String get map_showSharedMarkers => 'Show shared markers';
|
||||
|
||||
@override
|
||||
String get map_showGuessedLocations => 'Show guessed node locations';
|
||||
|
||||
@override
|
||||
String get map_guessedLocation => 'Guessed location';
|
||||
|
||||
@override
|
||||
String get map_lastSeenTime => 'Last Seen Time';
|
||||
|
||||
|
|
@ -3137,4 +3153,78 @@ class AppLocalizationsEn extends AppLocalizations {
|
|||
|
||||
@override
|
||||
String get snrIndicator_lastSeen => 'Last seen';
|
||||
|
||||
@override
|
||||
String get contactsSettings_title => 'Contacts settings';
|
||||
|
||||
@override
|
||||
String get contactsSettings_autoAddTitle => 'Automatic Discovery';
|
||||
|
||||
@override
|
||||
String get contactsSettings_otherTitle => 'Other contact related settings';
|
||||
|
||||
@override
|
||||
String get contactsSettings_autoAddUsersTitle => 'Auto-add users';
|
||||
|
||||
@override
|
||||
String get contactsSettings_autoAddUsersSubtitle =>
|
||||
'Allow the companion to automatically add discovered users.';
|
||||
|
||||
@override
|
||||
String get contactsSettings_autoAddRepeatersTitle => 'Auto-add repeaters';
|
||||
|
||||
@override
|
||||
String get contactsSettings_autoAddRepeatersSubtitle =>
|
||||
'Allow the companion to automatically add discovered repeaters.';
|
||||
|
||||
@override
|
||||
String get contactsSettings_autoAddRoomServersTitle =>
|
||||
'Auto-add room servers';
|
||||
|
||||
@override
|
||||
String get contactsSettings_autoAddRoomServersSubtitle =>
|
||||
'Allow the companion to automatically add discovered room servers.';
|
||||
|
||||
@override
|
||||
String get contactsSettings_autoAddSensorsTitle => 'Auto-add sensors';
|
||||
|
||||
@override
|
||||
String get contactsSettings_autoAddSensorsSubtitle =>
|
||||
'Allow the companion to automatically add discovered sensors.';
|
||||
|
||||
@override
|
||||
String get contactsSettings_overwriteOldestTitle => 'Overwrite Oldest';
|
||||
|
||||
@override
|
||||
String get contactsSettings_overwriteOldestSubtitle =>
|
||||
'When the contact list is full, the oldest non-favorited contact will be replaced.';
|
||||
|
||||
@override
|
||||
String get discoveredContacts_Title => 'Discovered Contacts';
|
||||
|
||||
@override
|
||||
String get discoveredContacts_noMatching => 'No matching contacts';
|
||||
|
||||
@override
|
||||
String get discoveredContacts_searchHint => 'Search discovered contacts';
|
||||
|
||||
@override
|
||||
String get discoveredContacts_contactAdded => 'Contact added';
|
||||
|
||||
@override
|
||||
String get discoveredContacts_addContact => 'Add Contact';
|
||||
|
||||
@override
|
||||
String get discoveredContacts_copyContact => 'Copy Contact to clipboard';
|
||||
|
||||
@override
|
||||
String get discoveredContacts_deleteContact => 'Delete Discovered Contact';
|
||||
|
||||
@override
|
||||
String get discoveredContacts_deleteContactAll =>
|
||||
'Delete All Discovered Contacts';
|
||||
|
||||
@override
|
||||
String get discoveredContacts_deleteContactAllContent =>
|
||||
'Are you sure you want to delete all discovered contacts?';
|
||||
}
|
||||
|
|
|
|||
|
|
@ -38,6 +38,9 @@ class AppLocalizationsEs extends AppLocalizations {
|
|||
@override
|
||||
String get common_delete => 'Eliminar';
|
||||
|
||||
@override
|
||||
String get common_deleteAll => 'Eliminar todo';
|
||||
|
||||
@override
|
||||
String get common_close => 'Cerrar';
|
||||
|
||||
|
|
@ -308,6 +311,13 @@ class AppLocalizationsEs extends AppLocalizations {
|
|||
@override
|
||||
String get settings_longitude => 'Longitud';
|
||||
|
||||
@override
|
||||
String get settings_contactSettings => 'Configuración de contacto';
|
||||
|
||||
@override
|
||||
String get settings_contactSettingsSubtitle =>
|
||||
'Configuración de cómo se agregan los contactos.';
|
||||
|
||||
@override
|
||||
String get settings_privacyMode => 'Modo Privacidad';
|
||||
|
||||
|
|
@ -1498,6 +1508,13 @@ class AppLocalizationsEs extends AppLocalizations {
|
|||
@override
|
||||
String get map_showSharedMarkers => 'Mostrar marcadores compartidos';
|
||||
|
||||
@override
|
||||
String get map_showGuessedLocations =>
|
||||
'Mostrar las ubicaciones estimadas de los nodos.';
|
||||
|
||||
@override
|
||||
String get map_guessedLocation => 'Ubicación estimada';
|
||||
|
||||
@override
|
||||
String get map_lastSeenTime => 'Última vez que se vio';
|
||||
|
||||
|
|
@ -3188,4 +3205,85 @@ class AppLocalizationsEs extends AppLocalizations {
|
|||
|
||||
@override
|
||||
String get snrIndicator_lastSeen => 'Visto por última vez';
|
||||
|
||||
@override
|
||||
String get contactsSettings_title => 'Configuración de contactos';
|
||||
|
||||
@override
|
||||
String get contactsSettings_autoAddTitle => 'Detección automática';
|
||||
|
||||
@override
|
||||
String get contactsSettings_otherTitle =>
|
||||
'Otras configuraciones relacionadas con el contacto';
|
||||
|
||||
@override
|
||||
String get contactsSettings_autoAddUsersTitle =>
|
||||
'Agregar usuarios automáticamente';
|
||||
|
||||
@override
|
||||
String get contactsSettings_autoAddUsersSubtitle =>
|
||||
'Permitir que el compañero agregue automáticamente a los usuarios descubiertos.';
|
||||
|
||||
@override
|
||||
String get contactsSettings_autoAddRepeatersTitle =>
|
||||
'Agregar repetidores automáticamente';
|
||||
|
||||
@override
|
||||
String get contactsSettings_autoAddRepeatersSubtitle =>
|
||||
'Permitir que el compañero agregue automáticamente los repetidores descubiertos.';
|
||||
|
||||
@override
|
||||
String get contactsSettings_autoAddRoomServersTitle =>
|
||||
'Agregar automáticamente servidores de sala';
|
||||
|
||||
@override
|
||||
String get contactsSettings_autoAddRoomServersSubtitle =>
|
||||
'Permitir que el compañero agregue automáticamente los servidores de salas descubiertos.';
|
||||
|
||||
@override
|
||||
String get contactsSettings_autoAddSensorsTitle =>
|
||||
'Agregar sensores automáticamente';
|
||||
|
||||
@override
|
||||
String get contactsSettings_autoAddSensorsSubtitle =>
|
||||
'Permitir que el compañero agregue automáticamente los sensores descubiertos.';
|
||||
|
||||
@override
|
||||
String get contactsSettings_overwriteOldestTitle =>
|
||||
'Sobreescribir el más antiguo';
|
||||
|
||||
@override
|
||||
String get contactsSettings_overwriteOldestSubtitle =>
|
||||
'Cuando la lista de contactos esté llena, se reemplazará el contacto no favorito más antiguo.';
|
||||
|
||||
@override
|
||||
String get discoveredContacts_Title => 'Contactos descubiertos';
|
||||
|
||||
@override
|
||||
String get discoveredContacts_noMatching =>
|
||||
'No se encontraron contactos coincidentes';
|
||||
|
||||
@override
|
||||
String get discoveredContacts_searchHint => 'Buscar contactos descubiertos';
|
||||
|
||||
@override
|
||||
String get discoveredContacts_contactAdded => 'Contacto agregado';
|
||||
|
||||
@override
|
||||
String get discoveredContacts_addContact => 'Agregar contacto';
|
||||
|
||||
@override
|
||||
String get discoveredContacts_copyContact =>
|
||||
'Copiar contacto al portapapeles';
|
||||
|
||||
@override
|
||||
String get discoveredContacts_deleteContact => 'Eliminar contacto';
|
||||
|
||||
@override
|
||||
String get discoveredContacts_deleteContactAll =>
|
||||
'Eliminar Todos los Contactos Descubiertos';
|
||||
|
||||
@override
|
||||
String get discoveredContacts_deleteContactAllContent =>
|
||||
'¿Está seguro de que desea eliminar todos los contactos descubiertos!';
|
||||
}
|
||||
|
|
|
|||
|
|
@ -38,6 +38,9 @@ class AppLocalizationsFr extends AppLocalizations {
|
|||
@override
|
||||
String get common_delete => 'Supprimer';
|
||||
|
||||
@override
|
||||
String get common_deleteAll => 'Supprimer tout';
|
||||
|
||||
@override
|
||||
String get common_close => 'Fermer';
|
||||
|
||||
|
|
@ -308,6 +311,13 @@ class AppLocalizationsFr extends AppLocalizations {
|
|||
@override
|
||||
String get settings_longitude => 'Longitude';
|
||||
|
||||
@override
|
||||
String get settings_contactSettings => 'Paramètres de contact';
|
||||
|
||||
@override
|
||||
String get settings_contactSettingsSubtitle =>
|
||||
'Paramètres pour l\'ajout de contacts';
|
||||
|
||||
@override
|
||||
String get settings_privacyMode => 'Mode de confidentialité';
|
||||
|
||||
|
|
@ -1504,6 +1514,13 @@ class AppLocalizationsFr extends AppLocalizations {
|
|||
@override
|
||||
String get map_showSharedMarkers => 'Afficher les marqueurs partagés';
|
||||
|
||||
@override
|
||||
String get map_showGuessedLocations =>
|
||||
'Afficher les emplacements des nœuds estimés';
|
||||
|
||||
@override
|
||||
String get map_guessedLocation => 'Lieu deviné';
|
||||
|
||||
@override
|
||||
String get map_lastSeenTime => 'Dernière fois vu';
|
||||
|
||||
|
|
@ -3209,4 +3226,84 @@ class AppLocalizationsFr extends AppLocalizations {
|
|||
|
||||
@override
|
||||
String get snrIndicator_lastSeen => 'Dernière fois vu';
|
||||
|
||||
@override
|
||||
String get contactsSettings_title => 'Paramètres des contacts';
|
||||
|
||||
@override
|
||||
String get contactsSettings_autoAddTitle => 'Découverte automatique';
|
||||
|
||||
@override
|
||||
String get contactsSettings_otherTitle =>
|
||||
'Autres paramètres liés aux contacts';
|
||||
|
||||
@override
|
||||
String get contactsSettings_autoAddUsersTitle =>
|
||||
'Ajouter automatiquement les utilisateurs';
|
||||
|
||||
@override
|
||||
String get contactsSettings_autoAddUsersSubtitle =>
|
||||
'Autoriser le compagnon à ajouter automatiquement les utilisateurs découverts';
|
||||
|
||||
@override
|
||||
String get contactsSettings_autoAddRepeatersTitle =>
|
||||
'Ajouter automatiquement les répéteurs';
|
||||
|
||||
@override
|
||||
String get contactsSettings_autoAddRepeatersSubtitle =>
|
||||
'Autoriser le compagnon à ajouter automatiquement les répéteurs découverts';
|
||||
|
||||
@override
|
||||
String get contactsSettings_autoAddRoomServersTitle =>
|
||||
'Ajouter automatiquement les serveurs de salle';
|
||||
|
||||
@override
|
||||
String get contactsSettings_autoAddRoomServersSubtitle =>
|
||||
'Autoriser le compagnon à ajouter automatiquement les serveurs de salles découverts';
|
||||
|
||||
@override
|
||||
String get contactsSettings_autoAddSensorsTitle =>
|
||||
'Ajouter automatiquement les capteurs';
|
||||
|
||||
@override
|
||||
String get contactsSettings_autoAddSensorsSubtitle =>
|
||||
'Autoriser le compagnon à ajouter automatiquement les capteurs découverts.';
|
||||
|
||||
@override
|
||||
String get contactsSettings_overwriteOldestTitle => 'Écraser le plus ancien';
|
||||
|
||||
@override
|
||||
String get contactsSettings_overwriteOldestSubtitle =>
|
||||
'Lorsque la liste de contacts est pleine, le contact le plus ancien non favori sera remplacé.';
|
||||
|
||||
@override
|
||||
String get discoveredContacts_Title => 'Contacts découverts';
|
||||
|
||||
@override
|
||||
String get discoveredContacts_noMatching => 'Aucun contact correspondant';
|
||||
|
||||
@override
|
||||
String get discoveredContacts_searchHint =>
|
||||
'Rechercher des contacts découverts';
|
||||
|
||||
@override
|
||||
String get discoveredContacts_contactAdded => 'Contact ajouté';
|
||||
|
||||
@override
|
||||
String get discoveredContacts_addContact => 'Ajouter un contact';
|
||||
|
||||
@override
|
||||
String get discoveredContacts_copyContact =>
|
||||
'Copier le contact dans le presse-papiers';
|
||||
|
||||
@override
|
||||
String get discoveredContacts_deleteContact => 'Supprimer le contact';
|
||||
|
||||
@override
|
||||
String get discoveredContacts_deleteContactAll =>
|
||||
'Supprimer tous les contacts découverts';
|
||||
|
||||
@override
|
||||
String get discoveredContacts_deleteContactAllContent =>
|
||||
'Êtes-vous sûr de vouloir supprimer tous les contacts découverts ?';
|
||||
}
|
||||
|
|
|
|||
|
|
@ -38,6 +38,9 @@ class AppLocalizationsIt extends AppLocalizations {
|
|||
@override
|
||||
String get common_delete => 'Elimina';
|
||||
|
||||
@override
|
||||
String get common_deleteAll => 'Elimina tutto';
|
||||
|
||||
@override
|
||||
String get common_close => 'Chiudi';
|
||||
|
||||
|
|
@ -308,6 +311,13 @@ class AppLocalizationsIt extends AppLocalizations {
|
|||
@override
|
||||
String get settings_longitude => 'Longitudine';
|
||||
|
||||
@override
|
||||
String get settings_contactSettings => 'Impostazioni di contatto';
|
||||
|
||||
@override
|
||||
String get settings_contactSettingsSubtitle =>
|
||||
'Impostazioni per l\'aggiunta dei contatti';
|
||||
|
||||
@override
|
||||
String get settings_privacyMode => 'Modalità Privacy';
|
||||
|
||||
|
|
@ -1497,6 +1507,12 @@ class AppLocalizationsIt extends AppLocalizations {
|
|||
@override
|
||||
String get map_showSharedMarkers => 'Mostra i segnaposto condivisi';
|
||||
|
||||
@override
|
||||
String get map_showGuessedLocations => 'Mostra le posizioni stimate dei nodi';
|
||||
|
||||
@override
|
||||
String get map_guessedLocation => 'Località indovinata';
|
||||
|
||||
@override
|
||||
String get map_lastSeenTime => 'Ultimo Tempo di Visualizzazione';
|
||||
|
||||
|
|
@ -3191,4 +3207,83 @@ class AppLocalizationsIt extends AppLocalizations {
|
|||
|
||||
@override
|
||||
String get snrIndicator_lastSeen => 'Ultimo accesso';
|
||||
|
||||
@override
|
||||
String get contactsSettings_title => 'Impostazioni dei contatti';
|
||||
|
||||
@override
|
||||
String get contactsSettings_autoAddTitle => 'Scoperta automatica';
|
||||
|
||||
@override
|
||||
String get contactsSettings_otherTitle =>
|
||||
'Altre impostazioni relative ai contatti';
|
||||
|
||||
@override
|
||||
String get contactsSettings_autoAddUsersTitle =>
|
||||
'Aggiungere utenti automaticamente';
|
||||
|
||||
@override
|
||||
String get contactsSettings_autoAddUsersSubtitle =>
|
||||
'Consenti al compagno di aggiungere automaticamente gli utenti scoperti.';
|
||||
|
||||
@override
|
||||
String get contactsSettings_autoAddRepeatersTitle =>
|
||||
'Aggiungere ripetitori automaticamente';
|
||||
|
||||
@override
|
||||
String get contactsSettings_autoAddRepeatersSubtitle =>
|
||||
'Consenti al compagno di aggiungere automaticamente i ripetitori scoperti.';
|
||||
|
||||
@override
|
||||
String get contactsSettings_autoAddRoomServersTitle =>
|
||||
'Aggiungere automaticamente i server delle stanze';
|
||||
|
||||
@override
|
||||
String get contactsSettings_autoAddRoomServersSubtitle =>
|
||||
'Consenti al compagno di aggiungere automaticamente i server delle stanze scoperte.';
|
||||
|
||||
@override
|
||||
String get contactsSettings_autoAddSensorsTitle =>
|
||||
'Aggiungere automaticamente i sensori';
|
||||
|
||||
@override
|
||||
String get contactsSettings_autoAddSensorsSubtitle =>
|
||||
'Consenti al compagno di aggiungere automaticamente i sensori scoperti';
|
||||
|
||||
@override
|
||||
String get contactsSettings_overwriteOldestTitle =>
|
||||
'Sostituisci il più vecchio';
|
||||
|
||||
@override
|
||||
String get contactsSettings_overwriteOldestSubtitle =>
|
||||
'Quando l\'elenco dei contatti è pieno, il contatto più vecchio non tra i preferiti verrà sostituito.';
|
||||
|
||||
@override
|
||||
String get discoveredContacts_Title => 'Contatti scoperti';
|
||||
|
||||
@override
|
||||
String get discoveredContacts_noMatching => 'Nessun contatto corrispondente';
|
||||
|
||||
@override
|
||||
String get discoveredContacts_searchHint => 'Cerca contatti scoperti';
|
||||
|
||||
@override
|
||||
String get discoveredContacts_contactAdded => 'Contatto aggiunto';
|
||||
|
||||
@override
|
||||
String get discoveredContacts_addContact => 'Aggiungi contatto';
|
||||
|
||||
@override
|
||||
String get discoveredContacts_copyContact => 'Copia contatto negli appunti';
|
||||
|
||||
@override
|
||||
String get discoveredContacts_deleteContact => 'Elimina Contatto';
|
||||
|
||||
@override
|
||||
String get discoveredContacts_deleteContactAll =>
|
||||
'Eliminare tutti i contatti scoperti';
|
||||
|
||||
@override
|
||||
String get discoveredContacts_deleteContactAllContent =>
|
||||
'Sei sicuro di voler eliminare tutti i contatti scoperti?';
|
||||
}
|
||||
|
|
|
|||
|
|
@ -38,6 +38,9 @@ class AppLocalizationsNl extends AppLocalizations {
|
|||
@override
|
||||
String get common_delete => 'Verwijderen';
|
||||
|
||||
@override
|
||||
String get common_deleteAll => 'Alles verwijderen';
|
||||
|
||||
@override
|
||||
String get common_close => 'Sluiten';
|
||||
|
||||
|
|
@ -306,6 +309,13 @@ class AppLocalizationsNl extends AppLocalizations {
|
|||
@override
|
||||
String get settings_longitude => 'Lengtegraad';
|
||||
|
||||
@override
|
||||
String get settings_contactSettings => 'Contactinstellingen';
|
||||
|
||||
@override
|
||||
String get settings_contactSettingsSubtitle =>
|
||||
'Instellingen voor het toevoegen van contacten';
|
||||
|
||||
@override
|
||||
String get settings_privacyMode => 'Privacy Mode';
|
||||
|
||||
|
|
@ -1490,6 +1500,13 @@ class AppLocalizationsNl extends AppLocalizations {
|
|||
@override
|
||||
String get map_showSharedMarkers => 'Toon gedeelde markeringen';
|
||||
|
||||
@override
|
||||
String get map_showGuessedLocations =>
|
||||
'Toon de voorspelde locaties van de knopen';
|
||||
|
||||
@override
|
||||
String get map_guessedLocation => 'Geroerde locatie';
|
||||
|
||||
@override
|
||||
String get map_lastSeenTime => 'Laatste Bekeken Tijd';
|
||||
|
||||
|
|
@ -3176,4 +3193,82 @@ class AppLocalizationsNl extends AppLocalizations {
|
|||
|
||||
@override
|
||||
String get snrIndicator_lastSeen => 'Laatst gezien';
|
||||
|
||||
@override
|
||||
String get contactsSettings_title => 'Instellingen voor contacten';
|
||||
|
||||
@override
|
||||
String get contactsSettings_autoAddTitle => 'Automatische detectie';
|
||||
|
||||
@override
|
||||
String get contactsSettings_otherTitle =>
|
||||
'Andere instellingen voor contactgerelateerde zaken';
|
||||
|
||||
@override
|
||||
String get contactsSettings_autoAddUsersTitle =>
|
||||
'Gebruikers automatisch toevoegen';
|
||||
|
||||
@override
|
||||
String get contactsSettings_autoAddUsersSubtitle =>
|
||||
'Sta toe dat de companion automatisch ontdekte gebruikers toevoegt';
|
||||
|
||||
@override
|
||||
String get contactsSettings_autoAddRepeatersTitle =>
|
||||
'Automatisch herhalingstoestellen toevoegen';
|
||||
|
||||
@override
|
||||
String get contactsSettings_autoAddRepeatersSubtitle =>
|
||||
'Sta toe dat de companion automatisch ontdekte repeaters toevoegt';
|
||||
|
||||
@override
|
||||
String get contactsSettings_autoAddRoomServersTitle =>
|
||||
'Automatisch kamerservers toevoegen';
|
||||
|
||||
@override
|
||||
String get contactsSettings_autoAddRoomServersSubtitle =>
|
||||
'Sta toe dat de companion automatisch ontdekte kamer servers toevoegt.';
|
||||
|
||||
@override
|
||||
String get contactsSettings_autoAddSensorsTitle =>
|
||||
'Automatisch sensoren toevoegen';
|
||||
|
||||
@override
|
||||
String get contactsSettings_autoAddSensorsSubtitle =>
|
||||
'Sta toe dat de companion automatisch ontdekte sensoren toevoegt';
|
||||
|
||||
@override
|
||||
String get contactsSettings_overwriteOldestTitle => 'Overschrijf Oudste';
|
||||
|
||||
@override
|
||||
String get contactsSettings_overwriteOldestSubtitle =>
|
||||
'Wanneer de contactenlijst vol is, wordt de oudste niet-favoriete contactpersoon vervangen.';
|
||||
|
||||
@override
|
||||
String get discoveredContacts_Title => 'Ontdekte contacten';
|
||||
|
||||
@override
|
||||
String get discoveredContacts_noMatching => 'Geen overeenkomende contacten';
|
||||
|
||||
@override
|
||||
String get discoveredContacts_searchHint => 'Ontdekte contacten zoeken';
|
||||
|
||||
@override
|
||||
String get discoveredContacts_contactAdded => 'Contact toegevoegd';
|
||||
|
||||
@override
|
||||
String get discoveredContacts_addContact => 'Contact toevoegen';
|
||||
|
||||
@override
|
||||
String get discoveredContacts_copyContact => 'Kopieer contact naar klembord';
|
||||
|
||||
@override
|
||||
String get discoveredContacts_deleteContact => 'Contact verwijderen';
|
||||
|
||||
@override
|
||||
String get discoveredContacts_deleteContactAll =>
|
||||
'Verwijder alle ontdekte contacten';
|
||||
|
||||
@override
|
||||
String get discoveredContacts_deleteContactAllContent =>
|
||||
'Weet u zeker dat u alle ontdekte contacten wilt verwijderen?';
|
||||
}
|
||||
|
|
|
|||
|
|
@ -38,6 +38,9 @@ class AppLocalizationsPl extends AppLocalizations {
|
|||
@override
|
||||
String get common_delete => 'Usuń';
|
||||
|
||||
@override
|
||||
String get common_deleteAll => 'Usuń wszystko';
|
||||
|
||||
@override
|
||||
String get common_close => 'Zamknąć';
|
||||
|
||||
|
|
@ -310,6 +313,13 @@ class AppLocalizationsPl extends AppLocalizations {
|
|||
@override
|
||||
String get settings_longitude => 'Długość';
|
||||
|
||||
@override
|
||||
String get settings_contactSettings => 'Ustawienia kontaktowe';
|
||||
|
||||
@override
|
||||
String get settings_contactSettingsSubtitle =>
|
||||
'Ustawienia dotyczące sposobu dodawania kontaktów';
|
||||
|
||||
@override
|
||||
String get settings_privacyMode => 'Tryb Prywatny';
|
||||
|
||||
|
|
@ -1498,6 +1508,13 @@ class AppLocalizationsPl extends AppLocalizations {
|
|||
@override
|
||||
String get map_showSharedMarkers => 'Pokaż współdzielone znaki.';
|
||||
|
||||
@override
|
||||
String get map_showGuessedLocations =>
|
||||
'Wyświetl lokalizacje zgadanych węzłów';
|
||||
|
||||
@override
|
||||
String get map_guessedLocation => 'Wydana lokalizacja';
|
||||
|
||||
@override
|
||||
String get map_lastSeenTime => 'Ostatni raz widiany';
|
||||
|
||||
|
|
@ -3191,4 +3208,82 @@ class AppLocalizationsPl extends AppLocalizations {
|
|||
|
||||
@override
|
||||
String get snrIndicator_lastSeen => 'Ostatnio widziany';
|
||||
|
||||
@override
|
||||
String get contactsSettings_title => 'Ustawienia kontaktów';
|
||||
|
||||
@override
|
||||
String get contactsSettings_autoAddTitle => 'Automatyczne odnajdywanie';
|
||||
|
||||
@override
|
||||
String get contactsSettings_otherTitle =>
|
||||
'Inne ustawienia związane z kontaktami';
|
||||
|
||||
@override
|
||||
String get contactsSettings_autoAddUsersTitle =>
|
||||
'Automatycznie dodaj użytkowników';
|
||||
|
||||
@override
|
||||
String get contactsSettings_autoAddUsersSubtitle =>
|
||||
'Pozwól towarzyszowi automatycznie dodawać znalezione użytkowników.';
|
||||
|
||||
@override
|
||||
String get contactsSettings_autoAddRepeatersTitle =>
|
||||
'Automatyczne dodawanie powtarzalników';
|
||||
|
||||
@override
|
||||
String get contactsSettings_autoAddRepeatersSubtitle =>
|
||||
'Zezwól na automatyczne dodawanie odkrytych repeaterów przez towarzysza.';
|
||||
|
||||
@override
|
||||
String get contactsSettings_autoAddRoomServersTitle =>
|
||||
'Automatycznie dodaj serwery pokojowe';
|
||||
|
||||
@override
|
||||
String get contactsSettings_autoAddRoomServersSubtitle =>
|
||||
'Zezwól towarzyszowi na automatyczne dodawanie znalezionych serwerów pokojowych.';
|
||||
|
||||
@override
|
||||
String get contactsSettings_autoAddSensorsTitle =>
|
||||
'Automatycznie dodaj czujniki';
|
||||
|
||||
@override
|
||||
String get contactsSettings_autoAddSensorsSubtitle =>
|
||||
'Zezwól towarzyszowi na automatyczne dodawanie wykrytych czujników.';
|
||||
|
||||
@override
|
||||
String get contactsSettings_overwriteOldestTitle => 'Nadpisz najstarszy';
|
||||
|
||||
@override
|
||||
String get contactsSettings_overwriteOldestSubtitle =>
|
||||
'Gdy lista kontaktów jest pełna, najstarszy nieulubiony kontakt zostanie zastąpiony.';
|
||||
|
||||
@override
|
||||
String get discoveredContacts_Title => 'Odkryte Kontakty';
|
||||
|
||||
@override
|
||||
String get discoveredContacts_noMatching => 'Brak pasujących kontaktów';
|
||||
|
||||
@override
|
||||
String get discoveredContacts_searchHint => 'Wyszukaj odkryte kontakty';
|
||||
|
||||
@override
|
||||
String get discoveredContacts_contactAdded => 'Kontakt dodany';
|
||||
|
||||
@override
|
||||
String get discoveredContacts_addContact => 'Dodaj kontakt';
|
||||
|
||||
@override
|
||||
String get discoveredContacts_copyContact => 'Kopiuj kontakt do schowka';
|
||||
|
||||
@override
|
||||
String get discoveredContacts_deleteContact => 'Usuń kontakt';
|
||||
|
||||
@override
|
||||
String get discoveredContacts_deleteContactAll =>
|
||||
'Usuń wszystkie odkryte kontakty';
|
||||
|
||||
@override
|
||||
String get discoveredContacts_deleteContactAllContent =>
|
||||
'Czy na pewno chcesz usunąć wszystkie znalezione kontakty?';
|
||||
}
|
||||
|
|
|
|||
|
|
@ -38,6 +38,9 @@ class AppLocalizationsPt extends AppLocalizations {
|
|||
@override
|
||||
String get common_delete => 'Excluir';
|
||||
|
||||
@override
|
||||
String get common_deleteAll => 'Excluir Tudo';
|
||||
|
||||
@override
|
||||
String get common_close => 'Fechar';
|
||||
|
||||
|
|
@ -309,6 +312,13 @@ class AppLocalizationsPt extends AppLocalizations {
|
|||
@override
|
||||
String get settings_longitude => 'Longitude';
|
||||
|
||||
@override
|
||||
String get settings_contactSettings => 'Configurações de Contato';
|
||||
|
||||
@override
|
||||
String get settings_contactSettingsSubtitle =>
|
||||
'Configurações para como os contatos são adicionados';
|
||||
|
||||
@override
|
||||
String get settings_privacyMode => 'Modo de Privacidade';
|
||||
|
||||
|
|
@ -1499,6 +1509,13 @@ class AppLocalizationsPt extends AppLocalizations {
|
|||
@override
|
||||
String get map_showSharedMarkers => 'Mostrar marcadores compartilhados';
|
||||
|
||||
@override
|
||||
String get map_showGuessedLocations =>
|
||||
'Mostrar as localizações dos nós estimados';
|
||||
|
||||
@override
|
||||
String get map_guessedLocation => 'Localização estimada';
|
||||
|
||||
@override
|
||||
String get map_lastSeenTime => 'Último Tempo de Visualização';
|
||||
|
||||
|
|
@ -3186,4 +3203,84 @@ class AppLocalizationsPt extends AppLocalizations {
|
|||
|
||||
@override
|
||||
String get snrIndicator_lastSeen => 'Visto pela última vez';
|
||||
|
||||
@override
|
||||
String get contactsSettings_title => 'Configurações de contatos';
|
||||
|
||||
@override
|
||||
String get contactsSettings_autoAddTitle => 'Descoberta Automática';
|
||||
|
||||
@override
|
||||
String get contactsSettings_otherTitle =>
|
||||
'Outras configurações relacionadas a contatos';
|
||||
|
||||
@override
|
||||
String get contactsSettings_autoAddUsersTitle =>
|
||||
'Adicionar usuários automaticamente';
|
||||
|
||||
@override
|
||||
String get contactsSettings_autoAddUsersSubtitle =>
|
||||
'Permitir que o companheiro adicione automaticamente os usuários descobertos.';
|
||||
|
||||
@override
|
||||
String get contactsSettings_autoAddRepeatersTitle =>
|
||||
'Adicionar repetidores automaticamente';
|
||||
|
||||
@override
|
||||
String get contactsSettings_autoAddRepeatersSubtitle =>
|
||||
'Permitir que o companheiro adicione automaticamente os repetidores descobertos.';
|
||||
|
||||
@override
|
||||
String get contactsSettings_autoAddRoomServersTitle =>
|
||||
'Adicionar automaticamente servidores de sala';
|
||||
|
||||
@override
|
||||
String get contactsSettings_autoAddRoomServersSubtitle =>
|
||||
'Permitir que o companheiro adicione automaticamente os servidores de salas descobertos.';
|
||||
|
||||
@override
|
||||
String get contactsSettings_autoAddSensorsTitle =>
|
||||
'Adicionar sensores automaticamente';
|
||||
|
||||
@override
|
||||
String get contactsSettings_autoAddSensorsSubtitle =>
|
||||
'Permitir que o companheiro adicione automaticamente sensores descobertos.';
|
||||
|
||||
@override
|
||||
String get contactsSettings_overwriteOldestTitle =>
|
||||
'Sobrescrever o Mais Antigo';
|
||||
|
||||
@override
|
||||
String get contactsSettings_overwriteOldestSubtitle =>
|
||||
'Quando a lista de contatos estiver cheia, o contato mais antigo não favoritado será substituído.';
|
||||
|
||||
@override
|
||||
String get discoveredContacts_Title => 'Contatos Descobertos';
|
||||
|
||||
@override
|
||||
String get discoveredContacts_noMatching => 'Nenhum contato correspondente';
|
||||
|
||||
@override
|
||||
String get discoveredContacts_searchHint => 'Pesquisar contatos descobertos';
|
||||
|
||||
@override
|
||||
String get discoveredContacts_contactAdded => 'Contato adicionado';
|
||||
|
||||
@override
|
||||
String get discoveredContacts_addContact => 'Adicionar Contato';
|
||||
|
||||
@override
|
||||
String get discoveredContacts_copyContact =>
|
||||
'Copiar Contato para a área de transferência';
|
||||
|
||||
@override
|
||||
String get discoveredContacts_deleteContact => 'Excluir Contato';
|
||||
|
||||
@override
|
||||
String get discoveredContacts_deleteContactAll =>
|
||||
'Excluir Todos os Contatos Descobertos';
|
||||
|
||||
@override
|
||||
String get discoveredContacts_deleteContactAllContent =>
|
||||
'Tem certeza de que deseja excluir todos os contatos descobertos?';
|
||||
}
|
||||
|
|
|
|||
|
|
@ -38,6 +38,9 @@ class AppLocalizationsRu extends AppLocalizations {
|
|||
@override
|
||||
String get common_delete => 'Удалить';
|
||||
|
||||
@override
|
||||
String get common_deleteAll => 'Удалить все';
|
||||
|
||||
@override
|
||||
String get common_close => 'Закрыть';
|
||||
|
||||
|
|
@ -308,6 +311,13 @@ class AppLocalizationsRu extends AppLocalizations {
|
|||
@override
|
||||
String get settings_longitude => 'Долгота';
|
||||
|
||||
@override
|
||||
String get settings_contactSettings => 'Настройки контактов';
|
||||
|
||||
@override
|
||||
String get settings_contactSettingsSubtitle =>
|
||||
'Настройки добавления контактов';
|
||||
|
||||
@override
|
||||
String get settings_privacyMode => 'Режим конфиденциальности';
|
||||
|
||||
|
|
@ -1501,6 +1511,13 @@ class AppLocalizationsRu extends AppLocalizations {
|
|||
@override
|
||||
String get map_showSharedMarkers => 'Показывать общие метки';
|
||||
|
||||
@override
|
||||
String get map_showGuessedLocations =>
|
||||
'Отобразить предполагаемые места расположения узлов';
|
||||
|
||||
@override
|
||||
String get map_guessedLocation => 'Угаданное место';
|
||||
|
||||
@override
|
||||
String get map_lastSeenTime => 'Время последнего появления';
|
||||
|
||||
|
|
@ -3199,4 +3216,84 @@ class AppLocalizationsRu extends AppLocalizations {
|
|||
|
||||
@override
|
||||
String get snrIndicator_lastSeen => 'Последний раз видели';
|
||||
|
||||
@override
|
||||
String get contactsSettings_title => 'Настройки контактов';
|
||||
|
||||
@override
|
||||
String get contactsSettings_autoAddTitle => 'Автоматическое обнаружение';
|
||||
|
||||
@override
|
||||
String get contactsSettings_otherTitle =>
|
||||
'Другие настройки, связанные с контактами';
|
||||
|
||||
@override
|
||||
String get contactsSettings_autoAddUsersTitle =>
|
||||
'Автоматически добавлять пользователей';
|
||||
|
||||
@override
|
||||
String get contactsSettings_autoAddUsersSubtitle =>
|
||||
'Разрешить компаньону автоматически добавлять обнаруженных пользователей';
|
||||
|
||||
@override
|
||||
String get contactsSettings_autoAddRepeatersTitle =>
|
||||
'Автоматически добавлять ретрансляторы';
|
||||
|
||||
@override
|
||||
String get contactsSettings_autoAddRepeatersSubtitle =>
|
||||
'Разрешить спутнику автоматически добавлять обнаруженные ретрансляторы';
|
||||
|
||||
@override
|
||||
String get contactsSettings_autoAddRoomServersTitle =>
|
||||
'Автоматически добавлять серверы комнат';
|
||||
|
||||
@override
|
||||
String get contactsSettings_autoAddRoomServersSubtitle =>
|
||||
'Разрешить компаньону автоматически добавлять обнаруженные сервера комнат.';
|
||||
|
||||
@override
|
||||
String get contactsSettings_autoAddSensorsTitle =>
|
||||
'Автоматически добавлять датчики';
|
||||
|
||||
@override
|
||||
String get contactsSettings_autoAddSensorsSubtitle =>
|
||||
'Разрешить компаньону автоматически добавлять обнаруженные датчики';
|
||||
|
||||
@override
|
||||
String get contactsSettings_overwriteOldestTitle =>
|
||||
'Перезаписать самое старое';
|
||||
|
||||
@override
|
||||
String get contactsSettings_overwriteOldestSubtitle =>
|
||||
'Когда список контактов заполнен, будет заменен самый старый контакт, который не находится в избранном.';
|
||||
|
||||
@override
|
||||
String get discoveredContacts_Title => 'Обнаруженные контакты';
|
||||
|
||||
@override
|
||||
String get discoveredContacts_noMatching => 'Нет совпадающих контактов';
|
||||
|
||||
@override
|
||||
String get discoveredContacts_searchHint => 'Найденные контакты поиска';
|
||||
|
||||
@override
|
||||
String get discoveredContacts_contactAdded => 'Контакт добавлен';
|
||||
|
||||
@override
|
||||
String get discoveredContacts_addContact => 'Добавить контакт';
|
||||
|
||||
@override
|
||||
String get discoveredContacts_copyContact =>
|
||||
'Копировать контакт в буфер обмена';
|
||||
|
||||
@override
|
||||
String get discoveredContacts_deleteContact => 'Удалить контакт';
|
||||
|
||||
@override
|
||||
String get discoveredContacts_deleteContactAll =>
|
||||
'Удалить Все Обнаруженные Контакты';
|
||||
|
||||
@override
|
||||
String get discoveredContacts_deleteContactAllContent =>
|
||||
'Вы уверены, что хотите удалить все обнаруженные контакты?';
|
||||
}
|
||||
|
|
|
|||
|
|
@ -38,6 +38,9 @@ class AppLocalizationsSk extends AppLocalizations {
|
|||
@override
|
||||
String get common_delete => 'Odstrániť';
|
||||
|
||||
@override
|
||||
String get common_deleteAll => 'Zmazať všetko';
|
||||
|
||||
@override
|
||||
String get common_close => 'Zavrieť';
|
||||
|
||||
|
|
@ -308,6 +311,13 @@ class AppLocalizationsSk extends AppLocalizations {
|
|||
@override
|
||||
String get settings_longitude => 'Dĺžka';
|
||||
|
||||
@override
|
||||
String get settings_contactSettings => 'Nastavenia kontaktov';
|
||||
|
||||
@override
|
||||
String get settings_contactSettingsSubtitle =>
|
||||
'Nastavenia pre pridávanie kontaktov.';
|
||||
|
||||
@override
|
||||
String get settings_privacyMode => 'Režim ochrany súkromia';
|
||||
|
||||
|
|
@ -1493,6 +1503,13 @@ class AppLocalizationsSk extends AppLocalizations {
|
|||
@override
|
||||
String get map_showSharedMarkers => 'Zobraziť zdieľané značky';
|
||||
|
||||
@override
|
||||
String get map_showGuessedLocations =>
|
||||
'Zobraziť umiestnenia odhadnutých uzlov';
|
||||
|
||||
@override
|
||||
String get map_guessedLocation => 'Odhadnutá lokalita';
|
||||
|
||||
@override
|
||||
String get map_lastSeenTime => 'Posledný čas sledovania';
|
||||
|
||||
|
|
@ -3173,4 +3190,82 @@ class AppLocalizationsSk extends AppLocalizations {
|
|||
|
||||
@override
|
||||
String get snrIndicator_lastSeen => 'Naposledy videný';
|
||||
|
||||
@override
|
||||
String get contactsSettings_title => 'Nastavenia kontaktov';
|
||||
|
||||
@override
|
||||
String get contactsSettings_autoAddTitle => 'Automatické zisťovanie';
|
||||
|
||||
@override
|
||||
String get contactsSettings_otherTitle =>
|
||||
'Ďalšie nastavenia súvisiace s kontaktami';
|
||||
|
||||
@override
|
||||
String get contactsSettings_autoAddUsersTitle =>
|
||||
'Automaticky pridávať užívateľov';
|
||||
|
||||
@override
|
||||
String get contactsSettings_autoAddUsersSubtitle =>
|
||||
'Povoliť spoločníkovi automaticky pridávať objavených užívateľov.';
|
||||
|
||||
@override
|
||||
String get contactsSettings_autoAddRepeatersTitle =>
|
||||
'Automaticky pridávať opakovače';
|
||||
|
||||
@override
|
||||
String get contactsSettings_autoAddRepeatersSubtitle =>
|
||||
'Povoliť spoločníkovi automaticky pridávať objavené repeater.';
|
||||
|
||||
@override
|
||||
String get contactsSettings_autoAddRoomServersTitle =>
|
||||
'Automaticky pridávať server miestnosti';
|
||||
|
||||
@override
|
||||
String get contactsSettings_autoAddRoomServersSubtitle =>
|
||||
'Povoliť spoločníkovi automaticky pridať objavené serverové miestnosti.';
|
||||
|
||||
@override
|
||||
String get contactsSettings_autoAddSensorsTitle =>
|
||||
'Automaticky pridávať senzory';
|
||||
|
||||
@override
|
||||
String get contactsSettings_autoAddSensorsSubtitle =>
|
||||
'Povoliť spoločníkovi automaticky pridávať objavené senzory.';
|
||||
|
||||
@override
|
||||
String get contactsSettings_overwriteOldestTitle => 'Prepísať najstaršie';
|
||||
|
||||
@override
|
||||
String get contactsSettings_overwriteOldestSubtitle =>
|
||||
'Keď je zoznam kontaktov plný, bude nahradený najstarší neoznačený kontakt.';
|
||||
|
||||
@override
|
||||
String get discoveredContacts_Title => 'Objavené kontakty';
|
||||
|
||||
@override
|
||||
String get discoveredContacts_noMatching => 'Žiadne zhodné kontakty';
|
||||
|
||||
@override
|
||||
String get discoveredContacts_searchHint => 'Vyhľadať objavené kontakty';
|
||||
|
||||
@override
|
||||
String get discoveredContacts_contactAdded => 'Kontakt bol pridaný';
|
||||
|
||||
@override
|
||||
String get discoveredContacts_addContact => 'Pridať kontakt';
|
||||
|
||||
@override
|
||||
String get discoveredContacts_copyContact => 'Kopírovať kontakt do schránky';
|
||||
|
||||
@override
|
||||
String get discoveredContacts_deleteContact => 'Zmazať kontakt';
|
||||
|
||||
@override
|
||||
String get discoveredContacts_deleteContactAll =>
|
||||
'Zmazať všetky objavené kontakty';
|
||||
|
||||
@override
|
||||
String get discoveredContacts_deleteContactAllContent =>
|
||||
'Ste si istí, že chcete zmazať všetky objavené kontakty?';
|
||||
}
|
||||
|
|
|
|||
|
|
@ -38,6 +38,9 @@ class AppLocalizationsSl extends AppLocalizations {
|
|||
@override
|
||||
String get common_delete => 'Izbrisati';
|
||||
|
||||
@override
|
||||
String get common_deleteAll => 'Izbriši vse';
|
||||
|
||||
@override
|
||||
String get common_close => 'Zapri';
|
||||
|
||||
|
|
@ -304,6 +307,13 @@ class AppLocalizationsSl extends AppLocalizations {
|
|||
@override
|
||||
String get settings_longitude => 'Dolžina';
|
||||
|
||||
@override
|
||||
String get settings_contactSettings => 'Nastavitve stika';
|
||||
|
||||
@override
|
||||
String get settings_contactSettingsSubtitle =>
|
||||
'Nastavitve za dodajanje stikov.';
|
||||
|
||||
@override
|
||||
String get settings_privacyMode => 'Zasebnost';
|
||||
|
||||
|
|
@ -1485,6 +1495,12 @@ class AppLocalizationsSl extends AppLocalizations {
|
|||
@override
|
||||
String get map_showSharedMarkers => 'Pokaži skupno označenja';
|
||||
|
||||
@override
|
||||
String get map_showGuessedLocations => 'Pokaži lokacije domnevnih not.';
|
||||
|
||||
@override
|
||||
String get map_guessedLocation => 'Predpostavljena lokacija';
|
||||
|
||||
@override
|
||||
String get map_lastSeenTime => 'Datum zadnjega vpogleda';
|
||||
|
||||
|
|
@ -3174,4 +3190,81 @@ class AppLocalizationsSl extends AppLocalizations {
|
|||
|
||||
@override
|
||||
String get snrIndicator_lastSeen => 'Zadnjič videno';
|
||||
|
||||
@override
|
||||
String get contactsSettings_title => 'Nastavitve stikov';
|
||||
|
||||
@override
|
||||
String get contactsSettings_autoAddTitle => 'Avtomatsko odkrivanje';
|
||||
|
||||
@override
|
||||
String get contactsSettings_otherTitle => 'Druge nastavitve v zvezi s stiki';
|
||||
|
||||
@override
|
||||
String get contactsSettings_autoAddUsersTitle =>
|
||||
'Avtomatsko dodaj uporabnike';
|
||||
|
||||
@override
|
||||
String get contactsSettings_autoAddUsersSubtitle =>
|
||||
'Dovoli spremljevalcu, da samodejno doda odkrite uporabnike.';
|
||||
|
||||
@override
|
||||
String get contactsSettings_autoAddRepeatersTitle =>
|
||||
'Avtomatsko dodaj ponovitelje';
|
||||
|
||||
@override
|
||||
String get contactsSettings_autoAddRepeatersSubtitle =>
|
||||
'Dovoli spremljevalcu, da samodejno doda odkrite ponovitelje.';
|
||||
|
||||
@override
|
||||
String get contactsSettings_autoAddRoomServersTitle =>
|
||||
'Avtomatsko dodaj strežnike sob';
|
||||
|
||||
@override
|
||||
String get contactsSettings_autoAddRoomServersSubtitle =>
|
||||
'Dovoli spremljevalcu, da samodejno doda odkrite strežnike sob.';
|
||||
|
||||
@override
|
||||
String get contactsSettings_autoAddSensorsTitle =>
|
||||
'Avtomatsko dodaj senzorje';
|
||||
|
||||
@override
|
||||
String get contactsSettings_autoAddSensorsSubtitle =>
|
||||
'Dovoli spremljevalcu, da samodejno doda odkrite senzorje.';
|
||||
|
||||
@override
|
||||
String get contactsSettings_overwriteOldestTitle => 'Prepiši najstarejše';
|
||||
|
||||
@override
|
||||
String get contactsSettings_overwriteOldestSubtitle =>
|
||||
'Ko je seznam stikov poln, bo najstarejši nestarševski stik zamenjan.';
|
||||
|
||||
@override
|
||||
String get discoveredContacts_Title => 'Odkriti stiki';
|
||||
|
||||
@override
|
||||
String get discoveredContacts_noMatching => 'Ni ujemajočih stikov';
|
||||
|
||||
@override
|
||||
String get discoveredContacts_searchHint => 'Najdeni stiki po iskanju';
|
||||
|
||||
@override
|
||||
String get discoveredContacts_contactAdded => 'Kontakt dodan';
|
||||
|
||||
@override
|
||||
String get discoveredContacts_addContact => 'Dodaj stik';
|
||||
|
||||
@override
|
||||
String get discoveredContacts_copyContact => 'Kopiraj stik v odložišče';
|
||||
|
||||
@override
|
||||
String get discoveredContacts_deleteContact => 'Izbriši stik';
|
||||
|
||||
@override
|
||||
String get discoveredContacts_deleteContactAll =>
|
||||
'Izbriši vse odkrite kontakte';
|
||||
|
||||
@override
|
||||
String get discoveredContacts_deleteContactAllContent =>
|
||||
'Ste prepričani, da želite izbrisati vse odkrite kontakte?';
|
||||
}
|
||||
|
|
|
|||
|
|
@ -38,6 +38,9 @@ class AppLocalizationsSv extends AppLocalizations {
|
|||
@override
|
||||
String get common_delete => 'Radera';
|
||||
|
||||
@override
|
||||
String get common_deleteAll => 'Ta bort alla';
|
||||
|
||||
@override
|
||||
String get common_close => 'Stänga';
|
||||
|
||||
|
|
@ -304,6 +307,13 @@ class AppLocalizationsSv extends AppLocalizations {
|
|||
@override
|
||||
String get settings_longitude => 'Längdgrad';
|
||||
|
||||
@override
|
||||
String get settings_contactSettings => 'Kontaktinställningar';
|
||||
|
||||
@override
|
||||
String get settings_contactSettingsSubtitle =>
|
||||
'Inställningar för hur kontakter läggs till.';
|
||||
|
||||
@override
|
||||
String get settings_privacyMode => 'Privatläge';
|
||||
|
||||
|
|
@ -1482,6 +1492,13 @@ class AppLocalizationsSv extends AppLocalizations {
|
|||
@override
|
||||
String get map_showSharedMarkers => 'Visa delade markörer';
|
||||
|
||||
@override
|
||||
String get map_showGuessedLocations =>
|
||||
'Visa upp de antagna nodernas placeringar';
|
||||
|
||||
@override
|
||||
String get map_guessedLocation => 'Gissad plats';
|
||||
|
||||
@override
|
||||
String get map_lastSeenTime => 'Senaste Visats Tid';
|
||||
|
||||
|
|
@ -3153,4 +3170,82 @@ class AppLocalizationsSv extends AppLocalizations {
|
|||
|
||||
@override
|
||||
String get snrIndicator_lastSeen => 'Senast sedd';
|
||||
|
||||
@override
|
||||
String get contactsSettings_title => 'Kontaktinställningar';
|
||||
|
||||
@override
|
||||
String get contactsSettings_autoAddTitle => 'Automatisk upptäckt';
|
||||
|
||||
@override
|
||||
String get contactsSettings_otherTitle =>
|
||||
'Andra inställningar relaterade till kontakt';
|
||||
|
||||
@override
|
||||
String get contactsSettings_autoAddUsersTitle =>
|
||||
'Lägg till användare automatiskt';
|
||||
|
||||
@override
|
||||
String get contactsSettings_autoAddUsersSubtitle =>
|
||||
'Tillåt kompanjonen att automatiskt lägga till upptäckta användare';
|
||||
|
||||
@override
|
||||
String get contactsSettings_autoAddRepeatersTitle =>
|
||||
'Lägg till upprepande enheter automatiskt';
|
||||
|
||||
@override
|
||||
String get contactsSettings_autoAddRepeatersSubtitle =>
|
||||
'Tillåt kompanjonen att automatiskt lägga till upptäckta repeater.';
|
||||
|
||||
@override
|
||||
String get contactsSettings_autoAddRoomServersTitle =>
|
||||
'Lägg automatiskt till rumsservrar';
|
||||
|
||||
@override
|
||||
String get contactsSettings_autoAddRoomServersSubtitle =>
|
||||
'Tillåt kompanjonen att automatiskt lägga till upptäckta rumsservrar.';
|
||||
|
||||
@override
|
||||
String get contactsSettings_autoAddSensorsTitle =>
|
||||
'Lägg till sensorer automatiskt';
|
||||
|
||||
@override
|
||||
String get contactsSettings_autoAddSensorsSubtitle =>
|
||||
'Tillåt kompanjonen att automatiskt lägga till upptäckta sensorer.';
|
||||
|
||||
@override
|
||||
String get contactsSettings_overwriteOldestTitle => 'Skriv över äldst';
|
||||
|
||||
@override
|
||||
String get contactsSettings_overwriteOldestSubtitle =>
|
||||
'När kontaktlistan är full ersätts den äldsta icke-favoriterade kontakten.';
|
||||
|
||||
@override
|
||||
String get discoveredContacts_Title => 'Upptäckta kontakter';
|
||||
|
||||
@override
|
||||
String get discoveredContacts_noMatching => 'Inga matchande kontakter';
|
||||
|
||||
@override
|
||||
String get discoveredContacts_searchHint => 'Sök uppfunna kontakter';
|
||||
|
||||
@override
|
||||
String get discoveredContacts_contactAdded => 'Kontakt tillagd';
|
||||
|
||||
@override
|
||||
String get discoveredContacts_addContact => 'Lägg till kontakt';
|
||||
|
||||
@override
|
||||
String get discoveredContacts_copyContact => 'Kopiera kontakt till urklipp';
|
||||
|
||||
@override
|
||||
String get discoveredContacts_deleteContact => 'Ta bort kontakt';
|
||||
|
||||
@override
|
||||
String get discoveredContacts_deleteContactAll =>
|
||||
'Ta bort alla upptäckta kontakter';
|
||||
|
||||
@override
|
||||
String get discoveredContacts_deleteContactAllContent =>
|
||||
'Är du säker på att du vill ta bort alla upptäckta kontakter?';
|
||||
}
|
||||
|
|
|
|||
|
|
@ -38,6 +38,9 @@ class AppLocalizationsUk extends AppLocalizations {
|
|||
@override
|
||||
String get common_delete => 'Видалити';
|
||||
|
||||
@override
|
||||
String get common_deleteAll => 'Видалити все';
|
||||
|
||||
@override
|
||||
String get common_close => 'Закрити';
|
||||
|
||||
|
|
@ -305,6 +308,13 @@ class AppLocalizationsUk extends AppLocalizations {
|
|||
@override
|
||||
String get settings_longitude => 'Довгота';
|
||||
|
||||
@override
|
||||
String get settings_contactSettings => 'Налаштування контактів';
|
||||
|
||||
@override
|
||||
String get settings_contactSettingsSubtitle =>
|
||||
'Налаштування для додавання контактів';
|
||||
|
||||
@override
|
||||
String get settings_privacyMode => 'Режим приватності';
|
||||
|
||||
|
|
@ -1497,6 +1507,13 @@ class AppLocalizationsUk extends AppLocalizations {
|
|||
@override
|
||||
String get map_showSharedMarkers => 'Показувати спільні маркери';
|
||||
|
||||
@override
|
||||
String get map_showGuessedLocations =>
|
||||
'Показати місцезнаходження передбачених вузлів';
|
||||
|
||||
@override
|
||||
String get map_guessedLocation => 'Визначено місцезнаходження';
|
||||
|
||||
@override
|
||||
String get map_lastSeenTime => 'Час останньої активності';
|
||||
|
||||
|
|
@ -3203,4 +3220,84 @@ class AppLocalizationsUk extends AppLocalizations {
|
|||
|
||||
@override
|
||||
String get snrIndicator_lastSeen => 'Останній раз бачили';
|
||||
|
||||
@override
|
||||
String get contactsSettings_title => 'Налаштування контактів';
|
||||
|
||||
@override
|
||||
String get contactsSettings_autoAddTitle => 'Автоматичне виявлення';
|
||||
|
||||
@override
|
||||
String get contactsSettings_otherTitle =>
|
||||
'Інші налаштування, пов\'язані з контактами';
|
||||
|
||||
@override
|
||||
String get contactsSettings_autoAddUsersTitle =>
|
||||
'Автоматично додавати користувачів';
|
||||
|
||||
@override
|
||||
String get contactsSettings_autoAddUsersSubtitle =>
|
||||
'Дозволити супутникові автоматично додавати виявлених користувачів';
|
||||
|
||||
@override
|
||||
String get contactsSettings_autoAddRepeatersTitle =>
|
||||
'Автоматично додавати повторювачі';
|
||||
|
||||
@override
|
||||
String get contactsSettings_autoAddRepeatersSubtitle =>
|
||||
'Дозволити супутнику автоматично додавати виявлені ретранслятори';
|
||||
|
||||
@override
|
||||
String get contactsSettings_autoAddRoomServersTitle =>
|
||||
'Автоматично додавати сервери кімнат';
|
||||
|
||||
@override
|
||||
String get contactsSettings_autoAddRoomServersSubtitle =>
|
||||
'Дозволити супровіднику автоматично додавати виявлені сервери кімнат.';
|
||||
|
||||
@override
|
||||
String get contactsSettings_autoAddSensorsTitle =>
|
||||
'Автоматично додавати датчики';
|
||||
|
||||
@override
|
||||
String get contactsSettings_autoAddSensorsSubtitle =>
|
||||
'Дозволити супровіднику автоматично додавати виявлені сенсори';
|
||||
|
||||
@override
|
||||
String get contactsSettings_overwriteOldestTitle => 'Перезаписати найстаріше';
|
||||
|
||||
@override
|
||||
String get contactsSettings_overwriteOldestSubtitle =>
|
||||
'Коли список контактів заповнений, найстарший контакт без позначки улюбленого буде замінений.';
|
||||
|
||||
@override
|
||||
String get discoveredContacts_Title => 'Виявлені контакти';
|
||||
|
||||
@override
|
||||
String get discoveredContacts_noMatching =>
|
||||
'Відповідних контактів не знайдено';
|
||||
|
||||
@override
|
||||
String get discoveredContacts_searchHint => 'Знайти виявлені контакти';
|
||||
|
||||
@override
|
||||
String get discoveredContacts_contactAdded => 'Контакт додано';
|
||||
|
||||
@override
|
||||
String get discoveredContacts_addContact => 'Додати контакт';
|
||||
|
||||
@override
|
||||
String get discoveredContacts_copyContact =>
|
||||
'Копіювати контакт у буфер обміну';
|
||||
|
||||
@override
|
||||
String get discoveredContacts_deleteContact => 'Видалити контакт';
|
||||
|
||||
@override
|
||||
String get discoveredContacts_deleteContactAll =>
|
||||
'Видалити всі виявлені контакти';
|
||||
|
||||
@override
|
||||
String get discoveredContacts_deleteContactAllContent =>
|
||||
'Ви впевнені, що хочете видалити всі виявлені контакти?';
|
||||
}
|
||||
|
|
|
|||
|
|
@ -38,6 +38,9 @@ class AppLocalizationsZh extends AppLocalizations {
|
|||
@override
|
||||
String get common_delete => '删除';
|
||||
|
||||
@override
|
||||
String get common_deleteAll => '删除全部';
|
||||
|
||||
@override
|
||||
String get common_close => '关闭';
|
||||
|
||||
|
|
@ -290,6 +293,12 @@ class AppLocalizationsZh extends AppLocalizations {
|
|||
@override
|
||||
String get settings_longitude => '经度';
|
||||
|
||||
@override
|
||||
String get settings_contactSettings => '联系人设置';
|
||||
|
||||
@override
|
||||
String get settings_contactSettingsSubtitle => '添加联系人的设置';
|
||||
|
||||
@override
|
||||
String get settings_privacyMode => '隐私模式';
|
||||
|
||||
|
|
@ -1411,6 +1420,12 @@ class AppLocalizationsZh extends AppLocalizations {
|
|||
@override
|
||||
String get map_showSharedMarkers => '显示共享标记';
|
||||
|
||||
@override
|
||||
String get map_showGuessedLocations => '显示猜测的节点位置';
|
||||
|
||||
@override
|
||||
String get map_guessedLocation => '猜测的位置';
|
||||
|
||||
@override
|
||||
String get map_lastSeenTime => '最后在线时间';
|
||||
|
||||
|
|
@ -2954,4 +2969,71 @@ class AppLocalizationsZh extends AppLocalizations {
|
|||
|
||||
@override
|
||||
String get snrIndicator_lastSeen => '最近访问';
|
||||
|
||||
@override
|
||||
String get contactsSettings_title => '联系人设置';
|
||||
|
||||
@override
|
||||
String get contactsSettings_autoAddTitle => '自动发现';
|
||||
|
||||
@override
|
||||
String get contactsSettings_otherTitle => '其他联系人相关设置';
|
||||
|
||||
@override
|
||||
String get contactsSettings_autoAddUsersTitle => '自动添加用户';
|
||||
|
||||
@override
|
||||
String get contactsSettings_autoAddUsersSubtitle => '允许伴侣自动添加发现的用户';
|
||||
|
||||
@override
|
||||
String get contactsSettings_autoAddRepeatersTitle => '自动添加重复器';
|
||||
|
||||
@override
|
||||
String get contactsSettings_autoAddRepeatersSubtitle => '允许伴侣自动添加发现的重复器';
|
||||
|
||||
@override
|
||||
String get contactsSettings_autoAddRoomServersTitle => '自动添加房间服务器';
|
||||
|
||||
@override
|
||||
String get contactsSettings_autoAddRoomServersSubtitle => '允许伴侣自动添加发现的房间服务器';
|
||||
|
||||
@override
|
||||
String get contactsSettings_autoAddSensorsTitle => '自动添加传感器';
|
||||
|
||||
@override
|
||||
String get contactsSettings_autoAddSensorsSubtitle => '允许伴侣自动添加发现的传感器';
|
||||
|
||||
@override
|
||||
String get contactsSettings_overwriteOldestTitle => '覆盖最旧的';
|
||||
|
||||
@override
|
||||
String get contactsSettings_overwriteOldestSubtitle =>
|
||||
'当联系人列表已满时,将替换最老的非收藏联系人。';
|
||||
|
||||
@override
|
||||
String get discoveredContacts_Title => '已发现的联系人';
|
||||
|
||||
@override
|
||||
String get discoveredContacts_noMatching => '没有匹配的联系人';
|
||||
|
||||
@override
|
||||
String get discoveredContacts_searchHint => '搜索已发现的联系人';
|
||||
|
||||
@override
|
||||
String get discoveredContacts_contactAdded => '联系人已添加';
|
||||
|
||||
@override
|
||||
String get discoveredContacts_addContact => '添加联系人';
|
||||
|
||||
@override
|
||||
String get discoveredContacts_copyContact => '复制联系人到剪贴板';
|
||||
|
||||
@override
|
||||
String get discoveredContacts_deleteContact => '删除联系人';
|
||||
|
||||
@override
|
||||
String get discoveredContacts_deleteContactAll => '删除所有发现的联系人';
|
||||
|
||||
@override
|
||||
String get discoveredContacts_deleteContactAllContent => '您确定要删除所有发现的联系人吗?';
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1802,23 +1802,31 @@
|
|||
"contacts_searchUsers": "Zoek {number}{str} gebruikers...",
|
||||
"contacts_searchFavorites": "Zoek {number}{str} favorieten...",
|
||||
"contacts_searchRoomServers": "Zoek {number}{str} Room servers...",
|
||||
"usbScreenTitle": "Verbind via USB",
|
||||
"usbScreenStatus": "Selecteer een USB-apparaat",
|
||||
"usbScreenNote": "USB-serieel is actief op ondersteunde Android-apparaten en desktop-platforms.",
|
||||
"usbScreenSubtitle": "Kies een gedetecteerd seriële apparaat en verbind deze direct met uw MeshCore-node.",
|
||||
"usbScreenEmptyState": "Geen USB-apparaten gevonden. Sluit er een aan en herlaad.",
|
||||
"usbErrorPermissionDenied": "Toegang via USB is geweigerd.",
|
||||
"usbErrorDeviceMissing": "Het geselecteerde USB-apparaat is niet meer beschikbaar.",
|
||||
"usbErrorInvalidPort": "Selecteer een geldig USB-apparaat.",
|
||||
"usbErrorBusy": "Een andere verzoek om een USB-verbinding is al in behandeling.",
|
||||
"usbErrorNotConnected": "Er is geen USB-apparaat aangesloten.",
|
||||
"usbErrorOpenFailed": "Kon het geselecteerde USB-apparaat niet openen.",
|
||||
"usbErrorConnectFailed": "Kon niet verbinding maken met het geselecteerde USB-apparaat.",
|
||||
"usbErrorUnsupported": "USB-serieel is niet ondersteund op deze platform.",
|
||||
"usbErrorAlreadyActive": "Een USB-verbinding is al actief.",
|
||||
"usbErrorNoDeviceSelected": "Geen USB-apparaat is geselecteerd.",
|
||||
"usbErrorPortClosed": "De USB-verbinding is niet actief.",
|
||||
"usbErrorConnectTimedOut": "Wachtperiode is verlopen, er is geen reactie ontvangen van het apparaat.",
|
||||
"connectionChoiceUsbLabel": "USB",
|
||||
"connectionChoiceBluetoothLabel": "Bluetooth"
|
||||
"contactsSettings_autoAddUsersTitle": "Gebruikers automatisch toevoegen",
|
||||
"contactsSettings_title": "Instellingen voor contacten",
|
||||
"settings_contactSettings": "Contactinstellingen",
|
||||
"contactsSettings_otherTitle": "Andere instellingen voor contactgerelateerde zaken",
|
||||
"contactsSettings_autoAddRepeatersSubtitle": "Sta toe dat de companion automatisch ontdekte repeaters toevoegt",
|
||||
"contactsSettings_autoAddRoomServersTitle": "Automatisch kamerservers toevoegen",
|
||||
"contactsSettings_autoAddRoomServersSubtitle": "Sta toe dat de companion automatisch ontdekte kamer servers toevoegt.",
|
||||
"contactsSettings_autoAddSensorsTitle": "Automatisch sensoren toevoegen",
|
||||
"settings_contactSettingsSubtitle": "Instellingen voor het toevoegen van contacten",
|
||||
"contactsSettings_autoAddTitle": "Automatische detectie",
|
||||
"contactsSettings_autoAddSensorsSubtitle": "Sta toe dat de companion automatisch ontdekte sensoren toevoegt",
|
||||
"contactsSettings_autoAddUsersSubtitle": "Sta toe dat de companion automatisch ontdekte gebruikers toevoegt",
|
||||
"contactsSettings_autoAddRepeatersTitle": "Automatisch herhalingstoestellen toevoegen",
|
||||
"contactsSettings_overwriteOldestTitle": "Overschrijf Oudste",
|
||||
"discoveredContacts_noMatching": "Geen overeenkomende contacten",
|
||||
"discoveredContacts_addContact": "Contact toevoegen",
|
||||
"discoveredContacts_copyContact": "Kopieer contact naar klembord",
|
||||
"discoveredContacts_deleteContact": "Contact verwijderen",
|
||||
"discoveredContacts_Title": "Ontdekte contacten",
|
||||
"discoveredContacts_contactAdded": "Contact toegevoegd",
|
||||
"discoveredContacts_searchHint": "Ontdekte contacten zoeken",
|
||||
"contactsSettings_overwriteOldestSubtitle": "Wanneer de contactenlijst vol is, wordt de oudste niet-favoriete contactpersoon vervangen.",
|
||||
"common_deleteAll": "Alles verwijderen",
|
||||
"discoveredContacts_deleteContactAll": "Verwijder alle ontdekte contacten",
|
||||
"discoveredContacts_deleteContactAllContent": "Weet u zeker dat u alle ontdekte contacten wilt verwijderen?",
|
||||
"map_guessedLocation": "Geroerde locatie",
|
||||
"map_showGuessedLocations": "Toon de voorspelde locaties van de knopen"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1802,23 +1802,31 @@
|
|||
"contacts_searchRoomServers": "Wyszukaj {number}{str} serwerów Room...",
|
||||
"contacts_searchUsers": "Wyszukaj {number}{str} Użytkowników...",
|
||||
"contacts_searchRepeaters": "Wyszukaj {number}{str} powtórników...",
|
||||
"usbScreenStatus": "Wybierz urządzenie USB",
|
||||
"usbScreenTitle": "Połącz przez USB",
|
||||
"usbScreenNote": "Port szeregowy USB jest aktywny na urządzeniach z Androidem i platformach stacjonarnych, które obsługują tę funkcję.",
|
||||
"usbScreenSubtitle": "Wybierz wykryty urządzenie szeregowe i podłącz je bezpośrednio do swojego węzła MeshCore.",
|
||||
"usbScreenEmptyState": "Nie znaleziono żadnych urządzeń USB. Podłącz jedno i zaktualizuj.",
|
||||
"usbErrorPermissionDenied": "Zostało odrzucone żądanie dostępu przez USB.",
|
||||
"usbErrorDeviceMissing": "Wybór urządzenia USB już nie jest dostępny.",
|
||||
"usbErrorInvalidPort": "Wybierz prawidłowe urządzenie USB.",
|
||||
"usbErrorBusy": "Kolejne żądanie połączenia przez USB jest już w trakcie realizacji.",
|
||||
"usbErrorNotConnected": "Brak podłączonego urządzenia USB.",
|
||||
"usbErrorOpenFailed": "Nie udało się otworzyć wybranego urządzenia USB.",
|
||||
"usbErrorConnectFailed": "Nie udało się nawiązać połączenia z wybranym urządzeniem USB.",
|
||||
"usbErrorUnsupported": "Port szeregowy USB nie jest obsługiwany na tym urządzeniu.",
|
||||
"usbErrorAlreadyActive": "Połączenie USB jest już aktywne.",
|
||||
"usbErrorNoDeviceSelected": "Nie został wybrany żaden urządzenie USB.",
|
||||
"usbErrorPortClosed": "Połączenie USB nie jest aktywne.",
|
||||
"usbErrorConnectTimedOut": "Czekanie na odpowiedź urządzenia zakończyło się z powodu braku reakcji.",
|
||||
"connectionChoiceUsbLabel": "USB",
|
||||
"connectionChoiceBluetoothLabel": "Bluetooth"
|
||||
"contactsSettings_title": "Ustawienia kontaktów",
|
||||
"settings_contactSettingsSubtitle": "Ustawienia dotyczące sposobu dodawania kontaktów",
|
||||
"contactsSettings_autoAddUsersSubtitle": "Pozwól towarzyszowi automatycznie dodawać znalezione użytkowników.",
|
||||
"contactsSettings_autoAddRepeatersTitle": "Automatyczne dodawanie powtarzalników",
|
||||
"contactsSettings_autoAddRepeatersSubtitle": "Zezwól na automatyczne dodawanie odkrytych repeaterów przez towarzysza.",
|
||||
"contactsSettings_autoAddRoomServersTitle": "Automatycznie dodaj serwery pokojowe",
|
||||
"contactsSettings_autoAddUsersTitle": "Automatycznie dodaj użytkowników",
|
||||
"settings_contactSettings": "Ustawienia kontaktowe",
|
||||
"contactsSettings_otherTitle": "Inne ustawienia związane z kontaktami",
|
||||
"contactsSettings_autoAddTitle": "Automatyczne odnajdywanie",
|
||||
"contactsSettings_autoAddRoomServersSubtitle": "Zezwól towarzyszowi na automatyczne dodawanie znalezionych serwerów pokojowych.",
|
||||
"contactsSettings_autoAddSensorsTitle": "Automatycznie dodaj czujniki",
|
||||
"discoveredContacts_searchHint": "Wyszukaj odkryte kontakty",
|
||||
"discoveredContacts_contactAdded": "Kontakt dodany",
|
||||
"discoveredContacts_addContact": "Dodaj kontakt",
|
||||
"discoveredContacts_copyContact": "Kopiuj kontakt do schowka",
|
||||
"contactsSettings_overwriteOldestTitle": "Nadpisz najstarszy",
|
||||
"discoveredContacts_Title": "Odkryte Kontakty",
|
||||
"contactsSettings_autoAddSensorsSubtitle": "Zezwól towarzyszowi na automatyczne dodawanie wykrytych czujników.",
|
||||
"discoveredContacts_noMatching": "Brak pasujących kontaktów",
|
||||
"discoveredContacts_deleteContact": "Usuń kontakt",
|
||||
"contactsSettings_overwriteOldestSubtitle": "Gdy lista kontaktów jest pełna, najstarszy nieulubiony kontakt zostanie zastąpiony.",
|
||||
"common_deleteAll": "Usuń wszystko",
|
||||
"discoveredContacts_deleteContactAllContent": "Czy na pewno chcesz usunąć wszystkie znalezione kontakty?",
|
||||
"discoveredContacts_deleteContactAll": "Usuń wszystkie odkryte kontakty",
|
||||
"map_guessedLocation": "Wydana lokalizacja",
|
||||
"map_showGuessedLocations": "Wyświetl lokalizacje zgadanych węzłów"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1802,23 +1802,31 @@
|
|||
"contacts_searchContactsNoNumber": "Pesquisar Contatos...",
|
||||
"contacts_unread": "Não lido",
|
||||
"contacts_searchRoomServers": "Pesquisar {number}{str} servidores de sala...",
|
||||
"usbScreenTitle": "Conecte via USB",
|
||||
"usbScreenSubtitle": "Selecione o dispositivo serial detectado e conecte-o diretamente ao seu nó MeshCore.",
|
||||
"usbScreenStatus": "Selecione um dispositivo USB",
|
||||
"usbScreenNote": "A comunicação serial via USB está ativa em dispositivos Android e plataformas de desktop compatíveis.",
|
||||
"usbScreenEmptyState": "Nenhum dispositivo USB encontrado. Conecte um e atualize.",
|
||||
"usbErrorPermissionDenied": "A permissão para acesso via USB foi negada.",
|
||||
"usbErrorDeviceMissing": "O dispositivo USB selecionado não está mais disponível.",
|
||||
"usbErrorInvalidPort": "Selecione um dispositivo USB válido.",
|
||||
"usbErrorBusy": "Já existe uma solicitação de conexão USB em andamento.",
|
||||
"usbErrorNotConnected": "Não há nenhum dispositivo USB conectado.",
|
||||
"usbErrorOpenFailed": "Não foi possível abrir o dispositivo USB selecionado.",
|
||||
"usbErrorConnectFailed": "Não foi possível conectar ao dispositivo USB selecionado.",
|
||||
"usbErrorUnsupported": "A comunicação serial via USB não é suportada nesta plataforma.",
|
||||
"usbErrorAlreadyActive": "A conexão USB já está ativa.",
|
||||
"usbErrorNoDeviceSelected": "Nenhum dispositivo USB foi selecionado.",
|
||||
"usbErrorPortClosed": "A conexão USB não está ativa.",
|
||||
"usbErrorConnectTimedOut": "Tempo limite aguardando a resposta do dispositivo.",
|
||||
"connectionChoiceUsbLabel": "USB",
|
||||
"connectionChoiceBluetoothLabel": "Bluetooth"
|
||||
"settings_contactSettings": "Configurações de Contato",
|
||||
"contactsSettings_otherTitle": "Outras configurações relacionadas a contatos",
|
||||
"contactsSettings_title": "Configurações de contatos",
|
||||
"contactsSettings_autoAddTitle": "Descoberta Automática",
|
||||
"settings_contactSettingsSubtitle": "Configurações para como os contatos são adicionados",
|
||||
"contactsSettings_autoAddUsersTitle": "Adicionar usuários automaticamente",
|
||||
"contactsSettings_autoAddRepeatersSubtitle": "Permitir que o companheiro adicione automaticamente os repetidores descobertos.",
|
||||
"contactsSettings_autoAddRoomServersTitle": "Adicionar automaticamente servidores de sala",
|
||||
"contactsSettings_overwriteOldestTitle": "Sobrescrever o Mais Antigo",
|
||||
"contactsSettings_autoAddSensorsTitle": "Adicionar sensores automaticamente",
|
||||
"discoveredContacts_Title": "Contatos Descobertos",
|
||||
"contactsSettings_autoAddUsersSubtitle": "Permitir que o companheiro adicione automaticamente os usuários descobertos.",
|
||||
"contactsSettings_autoAddRepeatersTitle": "Adicionar repetidores automaticamente",
|
||||
"discoveredContacts_noMatching": "Nenhum contato correspondente",
|
||||
"contactsSettings_autoAddRoomServersSubtitle": "Permitir que o companheiro adicione automaticamente os servidores de salas descobertos.",
|
||||
"discoveredContacts_searchHint": "Pesquisar contatos descobertos",
|
||||
"contactsSettings_autoAddSensorsSubtitle": "Permitir que o companheiro adicione automaticamente sensores descobertos.",
|
||||
"discoveredContacts_copyContact": "Copiar Contato para a área de transferência",
|
||||
"discoveredContacts_deleteContact": "Excluir Contato",
|
||||
"discoveredContacts_contactAdded": "Contato adicionado",
|
||||
"discoveredContacts_addContact": "Adicionar Contato",
|
||||
"contactsSettings_overwriteOldestSubtitle": "Quando a lista de contatos estiver cheia, o contato mais antigo não favoritado será substituído.",
|
||||
"common_deleteAll": "Excluir Tudo",
|
||||
"discoveredContacts_deleteContactAll": "Excluir Todos os Contatos Descobertos",
|
||||
"discoveredContacts_deleteContactAllContent": "Tem certeza de que deseja excluir todos os contatos descobertos?",
|
||||
"map_guessedLocation": "Localização estimada",
|
||||
"map_showGuessedLocations": "Mostrar as localizações dos nós estimados"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1042,23 +1042,31 @@
|
|||
"contacts_searchRoomServers": "Поиск {number}{str} серверов комнат...",
|
||||
"contacts_searchFavorites": "Поиск {number}{str} избранного...",
|
||||
"contacts_searchUsers": "Поиск {number}{str} пользователей...",
|
||||
"usbScreenSubtitle": "Выберите обнаруженное устройство с последовательным интерфейсом и подключите его напрямую к вашему узлу MeshCore.",
|
||||
"usbScreenTitle": "Подключение через USB",
|
||||
"usbScreenStatus": "Выберите USB-устройство",
|
||||
"usbScreenNote": "USB-серийный порт активен на поддерживаемых устройствах Android и на настольных платформах.",
|
||||
"usbScreenEmptyState": "Не обнаружено устройств USB. Подключите одно из них и обновите список.",
|
||||
"usbErrorPermissionDenied": "Запрос на доступ через USB был отклонен.",
|
||||
"usbErrorDeviceMissing": "Выбранное USB-устройство больше недоступно.",
|
||||
"usbErrorInvalidPort": "Выберите действительное USB-устройство.",
|
||||
"usbErrorBusy": "Еще одно запрошенное соединение через USB уже находится в процессе.",
|
||||
"usbErrorNotConnected": "Ни одно устройство USB не подключено.",
|
||||
"usbErrorOpenFailed": "Не удалось открыть выбранное USB-устройство.",
|
||||
"usbErrorConnectFailed": "Не удалось установить соединение с выбранным USB-устройством.",
|
||||
"usbErrorUnsupported": "Поддержка последовательного USB отсутствует на данной платформе.",
|
||||
"usbErrorAlreadyActive": "USB-соединение уже установлено и работает.",
|
||||
"usbErrorNoDeviceSelected": "Не было выбрано ни одно устройство USB.",
|
||||
"usbErrorPortClosed": "USB-соединение не установлено.",
|
||||
"usbErrorConnectTimedOut": "Ожидание ответа от устройства превысило установленное время.",
|
||||
"connectionChoiceBluetoothLabel": "Bluetooth",
|
||||
"connectionChoiceUsbLabel": "USB"
|
||||
"settings_contactSettings": "Настройки контактов",
|
||||
"settings_contactSettingsSubtitle": "Настройки добавления контактов",
|
||||
"contactsSettings_autoAddTitle": "Автоматическое обнаружение",
|
||||
"contactsSettings_title": "Настройки контактов",
|
||||
"contactsSettings_otherTitle": "Другие настройки, связанные с контактами",
|
||||
"contactsSettings_autoAddUsersSubtitle": "Разрешить компаньону автоматически добавлять обнаруженных пользователей",
|
||||
"contactsSettings_autoAddRoomServersTitle": "Автоматически добавлять серверы комнат",
|
||||
"contactsSettings_autoAddSensorsTitle": "Автоматически добавлять датчики",
|
||||
"contactsSettings_autoAddSensorsSubtitle": "Разрешить компаньону автоматически добавлять обнаруженные датчики",
|
||||
"contactsSettings_autoAddUsersTitle": "Автоматически добавлять пользователей",
|
||||
"contactsSettings_overwriteOldestTitle": "Перезаписать самое старое",
|
||||
"contactsSettings_autoAddRepeatersTitle": "Автоматически добавлять ретрансляторы",
|
||||
"contactsSettings_autoAddRepeatersSubtitle": "Разрешить спутнику автоматически добавлять обнаруженные ретрансляторы",
|
||||
"contactsSettings_autoAddRoomServersSubtitle": "Разрешить компаньону автоматически добавлять обнаруженные сервера комнат.",
|
||||
"discoveredContacts_noMatching": "Нет совпадающих контактов",
|
||||
"discoveredContacts_searchHint": "Найденные контакты поиска",
|
||||
"discoveredContacts_contactAdded": "Контакт добавлен",
|
||||
"discoveredContacts_copyContact": "Копировать контакт в буфер обмена",
|
||||
"discoveredContacts_addContact": "Добавить контакт",
|
||||
"discoveredContacts_Title": "Обнаруженные контакты",
|
||||
"discoveredContacts_deleteContact": "Удалить контакт",
|
||||
"contactsSettings_overwriteOldestSubtitle": "Когда список контактов заполнен, будет заменен самый старый контакт, который не находится в избранном.",
|
||||
"common_deleteAll": "Удалить все",
|
||||
"discoveredContacts_deleteContactAllContent": "Вы уверены, что хотите удалить все обнаруженные контакты?",
|
||||
"discoveredContacts_deleteContactAll": "Удалить Все Обнаруженные Контакты",
|
||||
"map_guessedLocation": "Угаданное место",
|
||||
"map_showGuessedLocations": "Отобразить предполагаемые места расположения узлов"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1802,23 +1802,31 @@
|
|||
"contacts_searchUsers": "Hľadať {number}{str} používateľov...",
|
||||
"contacts_searchContactsNoNumber": "Hľadať kontakty...",
|
||||
"contacts_unread": "Neprečítané",
|
||||
"usbScreenStatus": "Vyberte USB zariadenie",
|
||||
"usbScreenTitle": "Pripojte cez USB",
|
||||
"usbScreenSubtitle": "Vyberte detekovaný sériový zariadenie a pripojte ho priamo k vašej MeshCore uzlu.",
|
||||
"usbScreenNote": "USB sériová komunikácia je aktívna na podporovaných zariadeniach s Androidom a na desktopových platformách.",
|
||||
"usbScreenEmptyState": "Nenašli sa žiadne USB zariadenia. Pripojte jedno a obnovte.",
|
||||
"usbErrorPermissionDenied": "Žiadosť o prístup cez USB bola zamietnutá.",
|
||||
"usbErrorDeviceMissing": "Vybrané USB zariadenie už nie je dostupné.",
|
||||
"usbErrorInvalidPort": "Vyberte platné USB zariadenie.",
|
||||
"usbErrorBusy": "Ďalšia požiadavka na pripojenie cez USB je aktuálne v prebiehajúcom procese.",
|
||||
"usbErrorNotConnected": "Nie je pripojené žiadne USB zariadenie.",
|
||||
"usbErrorOpenFailed": "Nepodarilo sa otvoriť vybrané USB zariadenie.",
|
||||
"usbErrorConnectFailed": "Nezvládlo sa pripojenie k vybranému USB zariadeniu.",
|
||||
"usbErrorUnsupported": "Podpora USB sériového rozhrania nie je na tejto platforme dostupná.",
|
||||
"usbErrorAlreadyActive": "Pripojenie cez USB je už aktivované.",
|
||||
"usbErrorNoDeviceSelected": "Nebolo vybrané žiadne USB zariadenie.",
|
||||
"usbErrorPortClosed": "Pripojenie cez USB nie je aktivované.",
|
||||
"usbErrorConnectTimedOut": "Čakal som, kým sa zariadenie neozvými, ale časový limit sa dosiahol.",
|
||||
"connectionChoiceUsbLabel": "USB",
|
||||
"connectionChoiceBluetoothLabel": "Bluetooth"
|
||||
"settings_contactSettingsSubtitle": "Nastavenia pre pridávanie kontaktov.",
|
||||
"contactsSettings_autoAddUsersTitle": "Automaticky pridávať užívateľov",
|
||||
"contactsSettings_autoAddUsersSubtitle": "Povoliť spoločníkovi automaticky pridávať objavených užívateľov.",
|
||||
"contactsSettings_autoAddRepeatersTitle": "Automaticky pridávať opakovače",
|
||||
"contactsSettings_autoAddRoomServersTitle": "Automaticky pridávať server miestnosti",
|
||||
"contactsSettings_autoAddRoomServersSubtitle": "Povoliť spoločníkovi automaticky pridať objavené serverové miestnosti.",
|
||||
"contactsSettings_autoAddTitle": "Automatické zisťovanie",
|
||||
"contactsSettings_title": "Nastavenia kontaktov",
|
||||
"contactsSettings_otherTitle": "Ďalšie nastavenia súvisiace s kontaktami",
|
||||
"settings_contactSettings": "Nastavenia kontaktov",
|
||||
"contactsSettings_autoAddSensorsTitle": "Automaticky pridávať senzory",
|
||||
"discoveredContacts_noMatching": "Žiadne zhodné kontakty",
|
||||
"discoveredContacts_searchHint": "Vyhľadať objavené kontakty",
|
||||
"contactsSettings_autoAddRepeatersSubtitle": "Povoliť spoločníkovi automaticky pridávať objavené repeater.",
|
||||
"discoveredContacts_contactAdded": "Kontakt bol pridaný",
|
||||
"discoveredContacts_copyContact": "Kopírovať kontakt do schránky",
|
||||
"discoveredContacts_deleteContact": "Zmazať kontakt",
|
||||
"contactsSettings_autoAddSensorsSubtitle": "Povoliť spoločníkovi automaticky pridávať objavené senzory.",
|
||||
"discoveredContacts_Title": "Objavené kontakty",
|
||||
"contactsSettings_overwriteOldestTitle": "Prepísať najstaršie",
|
||||
"discoveredContacts_addContact": "Pridať kontakt",
|
||||
"contactsSettings_overwriteOldestSubtitle": "Keď je zoznam kontaktov plný, bude nahradený najstarší neoznačený kontakt.",
|
||||
"discoveredContacts_deleteContactAll": "Zmazať všetky objavené kontakty",
|
||||
"common_deleteAll": "Zmazať všetko",
|
||||
"discoveredContacts_deleteContactAllContent": "Ste si istí, že chcete zmazať všetky objavené kontakty?",
|
||||
"map_showGuessedLocations": "Zobraziť umiestnenia odhadnutých uzlov",
|
||||
"map_guessedLocation": "Odhadnutá lokalita"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1802,23 +1802,31 @@
|
|||
"contacts_searchContactsNoNumber": "Iskanje stikov...",
|
||||
"contacts_searchRepeaters": "Išči {number}{str} ponavljalnike...",
|
||||
"contacts_searchUsers": "Išči {number}{str} uporabnikov...",
|
||||
"usbScreenStatus": "Izberite USB naprave.",
|
||||
"usbScreenSubtitle": "Izberite zaznano serijsko napravo in se neposredno povežite z vašo MeshCore napravo.",
|
||||
"usbScreenTitle": "Povežite preko USB",
|
||||
"usbScreenNote": "USB serijska povezava je aktivna na podprtih napravah Android in na desktop platformah.",
|
||||
"usbScreenEmptyState": "Niti en USB naprave niso najdeni. Povežite eno in posodobite.",
|
||||
"usbErrorPermissionDenied": "Dovoljenje za dostop preko USB-ja je bilo zavrnjeno.",
|
||||
"usbErrorDeviceMissing": "Izbrani USB napravega več ni na voljo.",
|
||||
"usbErrorInvalidPort": "Izberite veljavno USB naprave.",
|
||||
"usbErrorBusy": "Že je v teku zahteva za povezavo preko USB.",
|
||||
"usbErrorNotConnected": "Ni priklopljenih USB naprav.",
|
||||
"usbErrorOpenFailed": "Niso uspeli odkriti izbrane USB naprave.",
|
||||
"usbErrorConnectFailed": "Niso bilo mogoče uskladiti povezave z izbranim USB napom.",
|
||||
"usbErrorUnsupported": "USB serijska komunikacija ni podprta na tej platformi.",
|
||||
"usbErrorAlreadyActive": "USB povezava je že aktivirana.",
|
||||
"usbErrorNoDeviceSelected": "Ni bilo izbranega USB naprave.",
|
||||
"usbErrorPortClosed": "USB povezava ni aktivirana.",
|
||||
"usbErrorConnectTimedOut": "Čakanje je preseglo določeno časovno obdobo, ker se naprave ni odzval.",
|
||||
"connectionChoiceBluetoothLabel": "Bluetooth",
|
||||
"connectionChoiceUsbLabel": "USB"
|
||||
"settings_contactSettings": "Nastavitve stika",
|
||||
"contactsSettings_autoAddTitle": "Avtomatsko odkrivanje",
|
||||
"contactsSettings_autoAddUsersTitle": "Avtomatsko dodaj uporabnike",
|
||||
"contactsSettings_autoAddRepeatersTitle": "Avtomatsko dodaj ponovitelje",
|
||||
"contactsSettings_autoAddRepeatersSubtitle": "Dovoli spremljevalcu, da samodejno doda odkrite ponovitelje.",
|
||||
"contactsSettings_autoAddRoomServersTitle": "Avtomatsko dodaj strežnike sob",
|
||||
"contactsSettings_autoAddRoomServersSubtitle": "Dovoli spremljevalcu, da samodejno doda odkrite strežnike sob.",
|
||||
"contactsSettings_otherTitle": "Druge nastavitve v zvezi s stiki",
|
||||
"settings_contactSettingsSubtitle": "Nastavitve za dodajanje stikov.",
|
||||
"contactsSettings_title": "Nastavitve stikov",
|
||||
"contactsSettings_autoAddSensorsTitle": "Avtomatsko dodaj senzorje",
|
||||
"contactsSettings_autoAddUsersSubtitle": "Dovoli spremljevalcu, da samodejno doda odkrite uporabnike.",
|
||||
"discoveredContacts_noMatching": "Ni ujemajočih stikov",
|
||||
"contactsSettings_autoAddSensorsSubtitle": "Dovoli spremljevalcu, da samodejno doda odkrite senzorje.",
|
||||
"discoveredContacts_addContact": "Dodaj stik",
|
||||
"discoveredContacts_contactAdded": "Kontakt dodan",
|
||||
"discoveredContacts_copyContact": "Kopiraj stik v odložišče",
|
||||
"contactsSettings_overwriteOldestTitle": "Prepiši najstarejše",
|
||||
"discoveredContacts_Title": "Odkriti stiki",
|
||||
"discoveredContacts_searchHint": "Najdeni stiki po iskanju",
|
||||
"discoveredContacts_deleteContact": "Izbriši stik",
|
||||
"contactsSettings_overwriteOldestSubtitle": "Ko je seznam stikov poln, bo najstarejši nestarševski stik zamenjan.",
|
||||
"common_deleteAll": "Izbriši vse",
|
||||
"discoveredContacts_deleteContactAllContent": "Ste prepričani, da želite izbrisati vse odkrite kontakte?",
|
||||
"discoveredContacts_deleteContactAll": "Izbriši vse odkrite kontakte",
|
||||
"map_guessedLocation": "Predpostavljena lokacija",
|
||||
"map_showGuessedLocations": "Pokaži lokacije domnevnih not."
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1802,23 +1802,31 @@
|
|||
"contacts_searchFavorites": "Sök {number}{str} Favoriter...",
|
||||
"contacts_searchUsers": "Sök {number}{str} användare...",
|
||||
"contacts_searchRoomServers": "Sök {number}{str} Room-servrar...",
|
||||
"usbScreenNote": "USB-seriell kommunikation är aktiv på stödda Android-enheter och skrivbordsplattformar.",
|
||||
"usbScreenStatus": "Välj en USB-enhet",
|
||||
"usbScreenSubtitle": "Välj en detekterad seriell enhet och anslut direkt till din MeshCore-nod.",
|
||||
"usbScreenTitle": "Anslut via USB",
|
||||
"usbScreenEmptyState": "Inga USB-enheter hittades. Anslut en och uppdatera.",
|
||||
"usbErrorPermissionDenied": "Tillgången via USB nekas.",
|
||||
"usbErrorDeviceMissing": "Den valda USB-enheten är inte längre tillgänglig.",
|
||||
"usbErrorInvalidPort": "Välj en giltig USB-enhet.",
|
||||
"usbErrorBusy": "En annan förfrågan om USB-anslutning är redan pågående.",
|
||||
"usbErrorNotConnected": "Ingen USB-enhet är ansluten.",
|
||||
"usbErrorOpenFailed": "Kunde inte öppna det valda USB-enheten.",
|
||||
"usbErrorConnectFailed": "Kunde inte ansluta till det valda USB-enheten.",
|
||||
"usbErrorUnsupported": "USB-seriell kommunikation stöds inte på denna plattform.",
|
||||
"usbErrorAlreadyActive": "En USB-anslutning är redan aktiv.",
|
||||
"usbErrorNoDeviceSelected": "Ingen USB-enhet valdes.",
|
||||
"usbErrorPortClosed": "USB-anslutningen är inte aktiv.",
|
||||
"usbErrorConnectTimedOut": "Tiden har löpt ut medan vi väntade på att enheten skulle svara.",
|
||||
"connectionChoiceBluetoothLabel": "Bluetooth",
|
||||
"connectionChoiceUsbLabel": "USB"
|
||||
"settings_contactSettingsSubtitle": "Inställningar för hur kontakter läggs till.",
|
||||
"settings_contactSettings": "Kontaktinställningar",
|
||||
"contactsSettings_autoAddTitle": "Automatisk upptäckt",
|
||||
"contactsSettings_otherTitle": "Andra inställningar relaterade till kontakt",
|
||||
"contactsSettings_autoAddUsersSubtitle": "Tillåt kompanjonen att automatiskt lägga till upptäckta användare",
|
||||
"contactsSettings_autoAddRepeatersTitle": "Lägg till upprepande enheter automatiskt",
|
||||
"contactsSettings_autoAddRoomServersSubtitle": "Tillåt kompanjonen att automatiskt lägga till upptäckta rumsservrar.",
|
||||
"contactsSettings_autoAddSensorsTitle": "Lägg till sensorer automatiskt",
|
||||
"contactsSettings_autoAddUsersTitle": "Lägg till användare automatiskt",
|
||||
"contactsSettings_title": "Kontaktinställningar",
|
||||
"contactsSettings_autoAddSensorsSubtitle": "Tillåt kompanjonen att automatiskt lägga till upptäckta sensorer.",
|
||||
"contactsSettings_overwriteOldestTitle": "Skriv över äldst",
|
||||
"contactsSettings_autoAddRepeatersSubtitle": "Tillåt kompanjonen att automatiskt lägga till upptäckta repeater.",
|
||||
"contactsSettings_autoAddRoomServersTitle": "Lägg automatiskt till rumsservrar",
|
||||
"discoveredContacts_noMatching": "Inga matchande kontakter",
|
||||
"discoveredContacts_searchHint": "Sök uppfunna kontakter",
|
||||
"discoveredContacts_deleteContact": "Ta bort kontakt",
|
||||
"discoveredContacts_Title": "Upptäckta kontakter",
|
||||
"discoveredContacts_contactAdded": "Kontakt tillagd",
|
||||
"discoveredContacts_addContact": "Lägg till kontakt",
|
||||
"discoveredContacts_copyContact": "Kopiera kontakt till urklipp",
|
||||
"contactsSettings_overwriteOldestSubtitle": "När kontaktlistan är full ersätts den äldsta icke-favoriterade kontakten.",
|
||||
"common_deleteAll": "Ta bort alla",
|
||||
"discoveredContacts_deleteContactAllContent": "Är du säker på att du vill ta bort alla upptäckta kontakter?",
|
||||
"discoveredContacts_deleteContactAll": "Ta bort alla upptäckta kontakter",
|
||||
"map_guessedLocation": "Gissad plats",
|
||||
"map_showGuessedLocations": "Visa upp de antagna nodernas placeringar"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1802,23 +1802,31 @@
|
|||
"contacts_searchContactsNoNumber": "Пошук контактів...",
|
||||
"contacts_searchRepeaters": "Пошук {number}{str} ретрансляторів...",
|
||||
"contacts_unread": "Непрочитане",
|
||||
"usbScreenNote": "USB-серіальний порт активний на підтримуваних пристроях на базі Android та на десктопних платформах.",
|
||||
"usbScreenSubtitle": "Виберіть виявлене серійне пристрій і підключіть його безпосередньо до вашого вузла MeshCore.",
|
||||
"usbScreenStatus": "Виберіть пристрій USB",
|
||||
"usbScreenTitle": "Підключити через USB",
|
||||
"usbScreenEmptyState": "Не знайдено жодних пристроїв USB. Підключіть один і перезавантажте.",
|
||||
"usbErrorPermissionDenied": "Було відмовлено у наданні дозволу на використання USB.",
|
||||
"usbErrorDeviceMissing": "Вибране USB-пристрій більше недоступне.",
|
||||
"usbErrorInvalidPort": "Виберіть дійсний USB-пристрій.",
|
||||
"usbErrorBusy": "Ще один запит на підключення через USB вже обробляється.",
|
||||
"usbErrorNotConnected": "Немає підключених пристроїв USB.",
|
||||
"usbErrorOpenFailed": "Не вдалося відкрити вибране USB-пристрій.",
|
||||
"usbErrorConnectFailed": "Не вдалося підключитися до вибраного USB-пристрою.",
|
||||
"usbErrorUnsupported": "Підтримка USB-серіального інтерфейсу не реалізована на цій платформі.",
|
||||
"usbErrorAlreadyActive": "USB-з'єднання вже встановлено.",
|
||||
"usbErrorNoDeviceSelected": "Не було вибрано жодного пристрою USB.",
|
||||
"usbErrorPortClosed": "З'єднання USB не встановлено.",
|
||||
"usbErrorConnectTimedOut": "Час очікування закінчився, оскільки пристрій не відповів.",
|
||||
"connectionChoiceBluetoothLabel": "Bluetooth",
|
||||
"connectionChoiceUsbLabel": "USB"
|
||||
"settings_contactSettingsSubtitle": "Налаштування для додавання контактів",
|
||||
"settings_contactSettings": "Налаштування контактів",
|
||||
"contactsSettings_autoAddUsersSubtitle": "Дозволити супутникові автоматично додавати виявлених користувачів",
|
||||
"contactsSettings_autoAddRepeatersTitle": "Автоматично додавати повторювачі",
|
||||
"contactsSettings_autoAddRepeatersSubtitle": "Дозволити супутнику автоматично додавати виявлені ретранслятори",
|
||||
"contactsSettings_autoAddRoomServersTitle": "Автоматично додавати сервери кімнат",
|
||||
"contactsSettings_otherTitle": "Інші налаштування, пов'язані з контактами",
|
||||
"contactsSettings_autoAddTitle": "Автоматичне виявлення",
|
||||
"contactsSettings_autoAddUsersTitle": "Автоматично додавати користувачів",
|
||||
"contactsSettings_title": "Налаштування контактів",
|
||||
"contactsSettings_autoAddRoomServersSubtitle": "Дозволити супровіднику автоматично додавати виявлені сервери кімнат.",
|
||||
"contactsSettings_autoAddSensorsTitle": "Автоматично додавати датчики",
|
||||
"discoveredContacts_searchHint": "Знайти виявлені контакти",
|
||||
"discoveredContacts_contactAdded": "Контакт додано",
|
||||
"contactsSettings_autoAddSensorsSubtitle": "Дозволити супровіднику автоматично додавати виявлені сенсори",
|
||||
"contactsSettings_overwriteOldestTitle": "Перезаписати найстаріше",
|
||||
"discoveredContacts_Title": "Виявлені контакти",
|
||||
"discoveredContacts_noMatching": "Відповідних контактів не знайдено",
|
||||
"discoveredContacts_deleteContact": "Видалити контакт",
|
||||
"discoveredContacts_copyContact": "Копіювати контакт у буфер обміну",
|
||||
"discoveredContacts_addContact": "Додати контакт",
|
||||
"contactsSettings_overwriteOldestSubtitle": "Коли список контактів заповнений, найстарший контакт без позначки улюбленого буде замінений.",
|
||||
"common_deleteAll": "Видалити все",
|
||||
"discoveredContacts_deleteContactAll": "Видалити всі виявлені контакти",
|
||||
"discoveredContacts_deleteContactAllContent": "Ви впевнені, що хочете видалити всі виявлені контакти?",
|
||||
"map_showGuessedLocations": "Показати місцезнаходження передбачених вузлів",
|
||||
"map_guessedLocation": "Визначено місцезнаходження"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1807,23 +1807,31 @@
|
|||
"contacts_searchContactsNoNumber": "搜索联系人...",
|
||||
"contacts_searchRoomServers": "搜索 {number}{str} 房间服务器...",
|
||||
"contacts_searchFavorites": "搜索 {number}{str} 收藏...",
|
||||
"usbScreenTitle": "通过USB连接",
|
||||
"usbScreenSubtitle": "选择已检测到的串行设备,并直接连接到您的 MeshCore 节点。",
|
||||
"usbScreenNote": "在支持的 Android 设备和桌面平台上,USB 串行通信功能已启用。",
|
||||
"usbScreenStatus": "选择一个 USB 设备",
|
||||
"usbScreenEmptyState": "未找到任何 USB 设备。请插入一个,然后刷新。",
|
||||
"usbErrorPermissionDenied": "拒绝了USB权限。",
|
||||
"usbErrorDeviceMissing": "所选的USB设备已不再可用。",
|
||||
"usbErrorInvalidPort": "选择一个有效的USB设备。",
|
||||
"usbErrorBusy": "还有一个 USB 连接请求正在进行中。",
|
||||
"usbErrorNotConnected": "没有连接任何USB设备。",
|
||||
"usbErrorOpenFailed": "未能打开所选的USB设备。",
|
||||
"usbErrorConnectFailed": "未能连接到所选的USB设备。",
|
||||
"usbErrorUnsupported": "此平台不支持USB串行通信。",
|
||||
"usbErrorAlreadyActive": "USB 连接已建立。",
|
||||
"usbErrorNoDeviceSelected": "未选择任何 USB 设备。",
|
||||
"usbErrorPortClosed": "USB 连接未建立。",
|
||||
"usbErrorConnectTimedOut": "等待设备响应超时。",
|
||||
"connectionChoiceBluetoothLabel": "蓝牙",
|
||||
"connectionChoiceUsbLabel": "USB"
|
||||
"settings_contactSettings": "联系人设置",
|
||||
"contactsSettings_title": "联系人设置",
|
||||
"contactsSettings_autoAddUsersTitle": "自动添加用户",
|
||||
"contactsSettings_otherTitle": "其他联系人相关设置",
|
||||
"contactsSettings_autoAddUsersSubtitle": "允许伴侣自动添加发现的用户",
|
||||
"contactsSettings_autoAddRepeatersSubtitle": "允许伴侣自动添加发现的重复器",
|
||||
"contactsSettings_autoAddSensorsTitle": "自动添加传感器",
|
||||
"contactsSettings_autoAddRoomServersSubtitle": "允许伴侣自动添加发现的房间服务器",
|
||||
"contactsSettings_autoAddRepeatersTitle": "自动添加重复器",
|
||||
"contactsSettings_autoAddTitle": "自动发现",
|
||||
"settings_contactSettingsSubtitle": "添加联系人的设置",
|
||||
"contactsSettings_overwriteOldestTitle": "覆盖最旧的",
|
||||
"contactsSettings_autoAddSensorsSubtitle": "允许伴侣自动添加发现的传感器",
|
||||
"discoveredContacts_searchHint": "搜索已发现的联系人",
|
||||
"contactsSettings_autoAddRoomServersTitle": "自动添加房间服务器",
|
||||
"discoveredContacts_contactAdded": "联系人已添加",
|
||||
"discoveredContacts_deleteContact": "删除联系人",
|
||||
"discoveredContacts_addContact": "添加联系人",
|
||||
"discoveredContacts_noMatching": "没有匹配的联系人",
|
||||
"discoveredContacts_Title": "已发现的联系人",
|
||||
"discoveredContacts_copyContact": "复制联系人到剪贴板",
|
||||
"contactsSettings_overwriteOldestSubtitle": "当联系人列表已满时,将替换最老的非收藏联系人。",
|
||||
"common_deleteAll": "删除全部",
|
||||
"discoveredContacts_deleteContactAllContent": "您确定要删除所有发现的联系人吗?",
|
||||
"discoveredContacts_deleteContactAll": "删除所有发现的联系人",
|
||||
"map_showGuessedLocations": "显示猜测的节点位置",
|
||||
"map_guessedLocation": "猜测的位置"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ class AppSettings {
|
|||
final bool mapKeyPrefixEnabled;
|
||||
final String mapKeyPrefix;
|
||||
final bool mapShowMarkers;
|
||||
final bool mapShowGuessedLocations;
|
||||
final bool enableMessageTracing;
|
||||
final Map<String, double>? mapCacheBounds;
|
||||
final int mapCacheMinZoom;
|
||||
|
|
@ -48,6 +49,7 @@ class AppSettings {
|
|||
this.mapKeyPrefixEnabled = false,
|
||||
this.mapKeyPrefix = '',
|
||||
this.mapShowMarkers = true,
|
||||
this.mapShowGuessedLocations = true,
|
||||
this.enableMessageTracing = false,
|
||||
this.mapCacheBounds,
|
||||
this.mapCacheMinZoom = 10,
|
||||
|
|
@ -78,6 +80,7 @@ class AppSettings {
|
|||
'map_key_prefix_enabled': mapKeyPrefixEnabled,
|
||||
'map_key_prefix': mapKeyPrefix,
|
||||
'map_show_markers': mapShowMarkers,
|
||||
'map_show_guessed_locations': mapShowGuessedLocations,
|
||||
'enable_message_tracing': enableMessageTracing,
|
||||
'map_cache_bounds': mapCacheBounds,
|
||||
'map_cache_min_zoom': mapCacheMinZoom,
|
||||
|
|
@ -115,6 +118,8 @@ class AppSettings {
|
|||
mapKeyPrefixEnabled: json['map_key_prefix_enabled'] as bool? ?? false,
|
||||
mapKeyPrefix: json['map_key_prefix'] as String? ?? '',
|
||||
mapShowMarkers: json['map_show_markers'] as bool? ?? true,
|
||||
mapShowGuessedLocations:
|
||||
json['map_show_guessed_locations'] as bool? ?? true,
|
||||
enableMessageTracing: json['enable_message_tracing'] as bool? ?? false,
|
||||
mapCacheBounds: (json['map_cache_bounds'] as Map?)?.map(
|
||||
(key, value) => MapEntry(key.toString(), (value as num).toDouble()),
|
||||
|
|
@ -159,6 +164,7 @@ class AppSettings {
|
|||
bool? mapKeyPrefixEnabled,
|
||||
String? mapKeyPrefix,
|
||||
bool? mapShowMarkers,
|
||||
bool? mapShowGuessedLocations,
|
||||
bool? enableMessageTracing,
|
||||
Object? mapCacheBounds = _unset,
|
||||
int? mapCacheMinZoom,
|
||||
|
|
@ -185,6 +191,8 @@ class AppSettings {
|
|||
mapKeyPrefixEnabled: mapKeyPrefixEnabled ?? this.mapKeyPrefixEnabled,
|
||||
mapKeyPrefix: mapKeyPrefix ?? this.mapKeyPrefix,
|
||||
mapShowMarkers: mapShowMarkers ?? this.mapShowMarkers,
|
||||
mapShowGuessedLocations:
|
||||
mapShowGuessedLocations ?? this.mapShowGuessedLocations,
|
||||
enableMessageTracing: enableMessageTracing ?? this.enableMessageTracing,
|
||||
mapCacheBounds: mapCacheBounds == _unset
|
||||
? this.mapCacheBounds
|
||||
|
|
|
|||
|
|
@ -183,7 +183,7 @@ class Contact {
|
|||
)
|
||||
: Uint8List(0);
|
||||
final name = readCString(data, contactNameOffset, maxNameSize);
|
||||
final lastmod = readUint32LE(data, contactLastmodOffset);
|
||||
final lastmod = readUint32LE(data, contactLastModOffset);
|
||||
|
||||
double? lat, lon;
|
||||
final latRaw = readInt32LE(data, contactLatOffset);
|
||||
|
|
|
|||
105
lib/models/discovery_contact.dart
Normal file
105
lib/models/discovery_contact.dart
Normal file
|
|
@ -0,0 +1,105 @@
|
|||
import 'dart:typed_data';
|
||||
import '../connector/meshcore_protocol.dart';
|
||||
|
||||
class DiscoveryContact {
|
||||
final Uint8List rawPacket;
|
||||
final Uint8List publicKey;
|
||||
final String name;
|
||||
final int type;
|
||||
final int pathLength; // -1 = flood, 0+ = direct hops (from device)
|
||||
final Uint8List path; // Path bytes from device
|
||||
final double? latitude;
|
||||
final double? longitude;
|
||||
final DateTime lastSeen;
|
||||
|
||||
DiscoveryContact({
|
||||
required this.rawPacket,
|
||||
required this.publicKey,
|
||||
required this.name,
|
||||
required this.type,
|
||||
required this.pathLength,
|
||||
required this.path,
|
||||
this.latitude,
|
||||
this.longitude,
|
||||
required this.lastSeen,
|
||||
});
|
||||
|
||||
String get publicKeyHex => pubKeyToHex(publicKey);
|
||||
|
||||
String get typeLabel {
|
||||
switch (type) {
|
||||
case advTypeChat:
|
||||
return 'Chat';
|
||||
case advTypeRepeater:
|
||||
return 'Repeater';
|
||||
case advTypeRoom:
|
||||
return 'Room';
|
||||
case advTypeSensor:
|
||||
return 'Sensor';
|
||||
default:
|
||||
return 'Unknown';
|
||||
}
|
||||
}
|
||||
|
||||
String get pathLabel {
|
||||
if (pathLength < 0) return 'Flood';
|
||||
if (pathLength == 0) return 'Direct';
|
||||
return '$pathLength hops';
|
||||
}
|
||||
|
||||
bool get hasLocation => latitude != null && longitude != null;
|
||||
|
||||
DiscoveryContact copyWith({
|
||||
Uint8List? rawPacket,
|
||||
Uint8List? publicKey,
|
||||
String? name,
|
||||
int? type,
|
||||
int? pathLength,
|
||||
Uint8List? path,
|
||||
double? latitude,
|
||||
double? longitude,
|
||||
DateTime? lastSeen,
|
||||
}) {
|
||||
return DiscoveryContact(
|
||||
rawPacket: rawPacket ?? this.rawPacket,
|
||||
publicKey: publicKey ?? this.publicKey,
|
||||
name: name ?? this.name,
|
||||
type: type ?? this.type,
|
||||
pathLength: pathLength ?? this.pathLength,
|
||||
path: path ?? this.path,
|
||||
latitude: latitude ?? this.latitude,
|
||||
longitude: longitude ?? this.longitude,
|
||||
lastSeen: lastSeen ?? this.lastSeen,
|
||||
);
|
||||
}
|
||||
|
||||
String get pathIdList {
|
||||
final pathBytes = path;
|
||||
if (pathBytes.isEmpty) return '';
|
||||
final parts = <String>[];
|
||||
final groupSize = pathHashSize;
|
||||
for (int i = 0; i < pathBytes.length; i += groupSize) {
|
||||
final end = (i + groupSize) <= pathBytes.length
|
||||
? (i + groupSize)
|
||||
: pathBytes.length;
|
||||
final chunk = pathBytes.sublist(i, end);
|
||||
parts.add(
|
||||
chunk
|
||||
.map((b) => b.toRadixString(16).padLeft(2, '0').toUpperCase())
|
||||
.join(),
|
||||
);
|
||||
}
|
||||
return parts.join(',');
|
||||
}
|
||||
|
||||
String get shortPubKeyHex {
|
||||
return "<${publicKeyHex.substring(0, 8)}...${publicKeyHex.substring(publicKeyHex.length - 8)}>";
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) =>
|
||||
other is DiscoveryContact && publicKeyHex == other.publicKeyHex;
|
||||
|
||||
@override
|
||||
int get hashCode => publicKeyHex.hashCode;
|
||||
}
|
||||
|
|
@ -818,6 +818,7 @@ class _ChatScreenState extends State<ChatScreen> {
|
|||
title: context.l10n.contacts_repeaterPathTrace,
|
||||
path: Uint8List.fromList(pathBytes),
|
||||
flipPathRound: true,
|
||||
targetContact: widget.contact,
|
||||
),
|
||||
),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ import '../widgets/room_login_dialog.dart';
|
|||
import '../widgets/unread_badge.dart';
|
||||
import 'channels_screen.dart';
|
||||
import 'chat_screen.dart';
|
||||
import 'discovery_screen.dart';
|
||||
import 'map_screen.dart';
|
||||
import 'repeater_hub_screen.dart';
|
||||
import 'settings_screen.dart';
|
||||
|
|
@ -218,9 +219,10 @@ class _ContactsScreenState extends State<ContactsScreen>
|
|||
}
|
||||
final hexString = text.substring('meshcore://'.length);
|
||||
try {
|
||||
final importContactFrame = buildImportContactFrame(hexString);
|
||||
final bytes = hex2Uint8List(hexString);
|
||||
final importContactFrame = buildImportContactFrame(bytes);
|
||||
_pendingOperations.add(ContactOperationType.import);
|
||||
await connector.sendFrame(importContactFrame, expectsGenericAck: true);
|
||||
connector.importContact(importContactFrame);
|
||||
} catch (e) {
|
||||
if (mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
|
|
@ -318,6 +320,21 @@ class _ContactsScreenState extends State<ContactsScreen>
|
|||
),
|
||||
onTap: () => _disconnect(context, connector),
|
||||
),
|
||||
PopupMenuItem(
|
||||
child: Row(
|
||||
children: [
|
||||
const Icon(Icons.person_add_rounded),
|
||||
const SizedBox(width: 8),
|
||||
Text("Discovered Contacts"),
|
||||
],
|
||||
),
|
||||
onTap: () => Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => const DiscoveryScreen(),
|
||||
),
|
||||
),
|
||||
),
|
||||
PopupMenuItem(
|
||||
child: Row(
|
||||
children: [
|
||||
|
|
@ -1121,6 +1138,7 @@ class _ContactsScreenState extends State<ContactsScreen>
|
|||
contact.name,
|
||||
),
|
||||
path: contact.traceRouteBytes ?? Uint8List(0),
|
||||
targetContact: contact,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
|
|
|||
419
lib/screens/discovery_screen.dart
Normal file
419
lib/screens/discovery_screen.dart
Normal file
|
|
@ -0,0 +1,419 @@
|
|||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
import '../connector/meshcore_connector.dart';
|
||||
import '../connector/meshcore_protocol.dart';
|
||||
import '../l10n/l10n.dart';
|
||||
import '../models/discovery_contact.dart';
|
||||
import '../utils/contact_search.dart';
|
||||
import '../widgets/app_bar.dart';
|
||||
import '../widgets/list_filter_widget.dart';
|
||||
|
||||
enum DiscoverySortOption { lastSeen, name, type }
|
||||
|
||||
class DiscoveryScreen extends StatefulWidget {
|
||||
const DiscoveryScreen({super.key});
|
||||
|
||||
@override
|
||||
State<DiscoveryScreen> createState() => _DiscoveryScreenState();
|
||||
}
|
||||
|
||||
class _DiscoveryScreenState extends State<DiscoveryScreen> {
|
||||
final TextEditingController _searchController = TextEditingController();
|
||||
String searchQuery = '';
|
||||
ContactSortOption sortOption = ContactSortOption.lastSeen;
|
||||
bool showUnreadOnly = false;
|
||||
ContactTypeFilter typeFilter = ContactTypeFilter.all;
|
||||
DiscoverySortOption discoverySortOption = DiscoverySortOption.lastSeen;
|
||||
Timer? _searchDebounce;
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_searchController.dispose();
|
||||
_searchDebounce?.cancel();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final l10n = context.l10n;
|
||||
final connector = context.watch<MeshCoreConnector>();
|
||||
|
||||
final discoveredContacts = connector.discoveredContacts;
|
||||
final filteredAndSorted = _filterAndSortContacts(
|
||||
discoveredContacts,
|
||||
connector,
|
||||
);
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: AppBarTitle(
|
||||
l10n.discoveredContacts_Title,
|
||||
indicators: false,
|
||||
subtitle: false,
|
||||
),
|
||||
centerTitle: true,
|
||||
actions: [
|
||||
PopupMenuButton(
|
||||
itemBuilder: (context) => [
|
||||
PopupMenuItem(
|
||||
child: Row(
|
||||
children: [
|
||||
const Icon(Icons.delete, color: Colors.red),
|
||||
const SizedBox(width: 8),
|
||||
Text(context.l10n.discoveredContacts_deleteContactAll),
|
||||
],
|
||||
),
|
||||
onTap: () {
|
||||
_deleteContacts(context, connector);
|
||||
},
|
||||
),
|
||||
],
|
||||
icon: const Icon(Icons.more_vert),
|
||||
),
|
||||
],
|
||||
),
|
||||
body: Column(
|
||||
children: [
|
||||
_buildFilters(filteredAndSorted, connector),
|
||||
Expanded(
|
||||
child: discoveredContacts.isEmpty
|
||||
? Center(child: Text(l10n.contacts_noContacts))
|
||||
: filteredAndSorted.isEmpty
|
||||
? Center(child: Text(l10n.discoveredContacts_noMatching))
|
||||
: ListView.builder(
|
||||
itemCount: filteredAndSorted.length,
|
||||
itemBuilder: (context, index) {
|
||||
final contact = filteredAndSorted[index];
|
||||
return ListTile(
|
||||
leading: CircleAvatar(
|
||||
backgroundColor: _getTypeColor(contact.type),
|
||||
child: Icon(
|
||||
_getTypeIcon(contact.type),
|
||||
color: Colors.white,
|
||||
size: 20,
|
||||
),
|
||||
),
|
||||
title: Text(
|
||||
contact.name,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
subtitle: Text(
|
||||
contact.shortPubKeyHex,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
trailing: Text(
|
||||
_formatLastSeen(context, contact.lastSeen),
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
color: Colors.grey[600],
|
||||
),
|
||||
),
|
||||
onTap: () {
|
||||
connector.importDiscoveredContact(contact);
|
||||
},
|
||||
onLongPress: () =>
|
||||
_showContactContextMenu(contact, connector),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _showContactContextMenu(
|
||||
DiscoveryContact contact,
|
||||
MeshCoreConnector connector,
|
||||
) async {
|
||||
final action = await showModalBottomSheet<String>(
|
||||
context: context,
|
||||
showDragHandle: true,
|
||||
builder: (sheetContext) {
|
||||
final l10n = context.l10n;
|
||||
return SafeArea(
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
ListTile(
|
||||
leading: const Icon(Icons.add_reaction_sharp),
|
||||
title: Text(l10n.discoveredContacts_addContact),
|
||||
onTap: () => Navigator.of(sheetContext).pop('import_contact'),
|
||||
),
|
||||
ListTile(
|
||||
leading: const Icon(Icons.copy),
|
||||
title: Text(l10n.discoveredContacts_copyContact),
|
||||
onTap: () => Navigator.of(sheetContext).pop('copy_contact'),
|
||||
),
|
||||
ListTile(
|
||||
leading: const Icon(Icons.delete),
|
||||
title: Text(l10n.discoveredContacts_deleteContact),
|
||||
onTap: () => Navigator.of(sheetContext).pop('delete_contact'),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
if (!mounted || action == null) return;
|
||||
|
||||
switch (action) {
|
||||
case 'import_contact':
|
||||
connector.importDiscoveredContact(contact);
|
||||
break;
|
||||
case 'copy_contact':
|
||||
final hexString = pubKeyToHex(contact.rawPacket);
|
||||
Clipboard.setData(ClipboardData(text: "meshcore://$hexString"));
|
||||
if (!mounted) return;
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text(context.l10n.contacts_contactAdvertCopied)),
|
||||
);
|
||||
break;
|
||||
case 'delete_contact':
|
||||
connector.removeDiscoveredContact(contact);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void _deleteContacts(BuildContext context, MeshCoreConnector connector) {
|
||||
final l10n = context.l10n;
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) => AlertDialog(
|
||||
title: Text(l10n.common_deleteAll),
|
||||
content: Text(l10n.discoveredContacts_deleteContactAllContent),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(context),
|
||||
child: Text(l10n.common_cancel),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () async {
|
||||
Navigator.pop(context);
|
||||
connector.removeAllDiscoveredContacts();
|
||||
},
|
||||
child: Text(l10n.common_deleteAll),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildFilters(
|
||||
List<DiscoveryContact> filteredAndSorted,
|
||||
MeshCoreConnector connector,
|
||||
) {
|
||||
String hintText = "";
|
||||
switch (typeFilter) {
|
||||
case ContactTypeFilter.all:
|
||||
hintText = context.l10n.contacts_searchContacts(
|
||||
filteredAndSorted.length,
|
||||
showUnreadOnly ? " ${context.l10n.contacts_unread}" : "",
|
||||
);
|
||||
break;
|
||||
case ContactTypeFilter.users:
|
||||
hintText = context.l10n.contacts_searchUsers(
|
||||
filteredAndSorted.length,
|
||||
showUnreadOnly ? " ${context.l10n.contacts_unread}" : "",
|
||||
);
|
||||
break;
|
||||
case ContactTypeFilter.repeaters:
|
||||
hintText = context.l10n.contacts_searchRepeaters(
|
||||
filteredAndSorted.length,
|
||||
showUnreadOnly ? " ${context.l10n.contacts_unread}" : "",
|
||||
);
|
||||
break;
|
||||
case ContactTypeFilter.rooms:
|
||||
hintText = context.l10n.contacts_searchRoomServers(
|
||||
filteredAndSorted.length,
|
||||
showUnreadOnly ? " ${context.l10n.contacts_unread}" : "",
|
||||
);
|
||||
break;
|
||||
case ContactTypeFilter.favorites:
|
||||
hintText = context.l10n.contacts_searchFavorites(
|
||||
filteredAndSorted.length,
|
||||
showUnreadOnly ? " ${context.l10n.contacts_unread}" : "",
|
||||
);
|
||||
break;
|
||||
}
|
||||
|
||||
return Column(
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: TextField(
|
||||
controller: _searchController,
|
||||
decoration: InputDecoration(
|
||||
hintText: hintText,
|
||||
prefixIcon: const Icon(Icons.search),
|
||||
suffixIcon: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
if (searchQuery.isNotEmpty)
|
||||
IconButton(
|
||||
icon: const Icon(Icons.clear),
|
||||
onPressed: () {
|
||||
_searchController.clear();
|
||||
setState(() {
|
||||
searchQuery = '';
|
||||
});
|
||||
},
|
||||
),
|
||||
_buildFilterButton(context, connector),
|
||||
],
|
||||
),
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
contentPadding: const EdgeInsets.symmetric(
|
||||
horizontal: 16,
|
||||
vertical: 12,
|
||||
),
|
||||
),
|
||||
onChanged: (value) {
|
||||
_searchDebounce?.cancel();
|
||||
_searchDebounce = Timer(const Duration(milliseconds: 300), () {
|
||||
if (!mounted) return;
|
||||
setState(() {
|
||||
searchQuery = value.toLowerCase();
|
||||
});
|
||||
});
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildFilterButton(BuildContext context, MeshCoreConnector connector) {
|
||||
return DiscoveryContactsFilterMenu(
|
||||
sortOption: sortOption,
|
||||
typeFilter: typeFilter,
|
||||
onSortChanged: (value) {
|
||||
setState(() {
|
||||
sortOption = value;
|
||||
});
|
||||
},
|
||||
onTypeFilterChanged: (value) {
|
||||
setState(() {
|
||||
typeFilter = value;
|
||||
});
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
List<DiscoveryContact> _filterAndSortContacts(
|
||||
List<DiscoveryContact> contacts,
|
||||
MeshCoreConnector connector,
|
||||
) {
|
||||
var filtered = contacts.where((contact) {
|
||||
if (searchQuery.isEmpty) return true;
|
||||
return matchesDiscoveryContactQuery(contact, searchQuery);
|
||||
}).toList();
|
||||
|
||||
filtered = filtered.where((contact) {
|
||||
return !connector.knownContactKeys.contains(contact.publicKeyHex);
|
||||
}).toList();
|
||||
|
||||
// Filter out own node from the list
|
||||
if (connector.selfPublicKey != null) {
|
||||
final selfPubKeyHex = pubKeyToHex(connector.selfPublicKey!);
|
||||
filtered = filtered.where((contact) {
|
||||
return contact.publicKeyHex != selfPubKeyHex;
|
||||
}).toList();
|
||||
}
|
||||
|
||||
if (typeFilter != ContactTypeFilter.all) {
|
||||
filtered = filtered.where(_matchesTypeFilter).toList();
|
||||
}
|
||||
|
||||
switch (sortOption) {
|
||||
case ContactSortOption.lastSeen:
|
||||
filtered.sort((a, b) => b.lastSeen.compareTo(a.lastSeen));
|
||||
break;
|
||||
case ContactSortOption.name:
|
||||
filtered.sort(
|
||||
(a, b) => a.name.toLowerCase().compareTo(b.name.toLowerCase()),
|
||||
);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return filtered;
|
||||
}
|
||||
|
||||
bool _matchesTypeFilter(DiscoveryContact contact) {
|
||||
switch (typeFilter) {
|
||||
case ContactTypeFilter.all:
|
||||
return true;
|
||||
case ContactTypeFilter.users:
|
||||
return contact.type == advTypeChat;
|
||||
case ContactTypeFilter.repeaters:
|
||||
return contact.type == advTypeRepeater;
|
||||
case ContactTypeFilter.rooms:
|
||||
return contact.type == advTypeRoom;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
IconData _getTypeIcon(int type) {
|
||||
switch (type) {
|
||||
case advTypeChat:
|
||||
return Icons.chat;
|
||||
case advTypeRepeater:
|
||||
return Icons.cell_tower;
|
||||
case advTypeRoom:
|
||||
return Icons.group;
|
||||
case advTypeSensor:
|
||||
return Icons.sensors;
|
||||
default:
|
||||
return Icons.device_unknown;
|
||||
}
|
||||
}
|
||||
|
||||
Color _getTypeColor(int type) {
|
||||
switch (type) {
|
||||
case advTypeChat:
|
||||
return Colors.blue;
|
||||
case advTypeRepeater:
|
||||
return Colors.orange;
|
||||
case advTypeRoom:
|
||||
return Colors.purple;
|
||||
case advTypeSensor:
|
||||
return Colors.green;
|
||||
default:
|
||||
return Colors.grey;
|
||||
}
|
||||
}
|
||||
|
||||
String _formatLastSeen(BuildContext context, DateTime lastSeen) {
|
||||
final now = DateTime.now();
|
||||
final diff = now.difference(lastSeen);
|
||||
|
||||
if (diff.isNegative || diff.inMinutes < 5) {
|
||||
return context.l10n.contacts_lastSeenNow;
|
||||
}
|
||||
if (diff.inMinutes < 60) {
|
||||
return context.l10n.contacts_lastSeenMinsAgo(diff.inMinutes);
|
||||
}
|
||||
if (diff.inHours < 24) {
|
||||
final hours = diff.inHours;
|
||||
return hours == 1
|
||||
? context.l10n.contacts_lastSeenHourAgo
|
||||
: context.l10n.contacts_lastSeenHoursAgo(hours);
|
||||
}
|
||||
final days = diff.inDays;
|
||||
return days == 1
|
||||
? context.l10n.contacts_lastSeenDayAgo
|
||||
: context.l10n.contacts_lastSeenDaysAgo(days);
|
||||
}
|
||||
}
|
||||
|
|
@ -15,6 +15,7 @@ import '../models/app_settings.dart';
|
|||
import '../models/channel.dart';
|
||||
import '../models/contact.dart';
|
||||
import '../services/app_settings_service.dart';
|
||||
import '../services/path_history_service.dart';
|
||||
import '../services/map_marker_service.dart';
|
||||
import '../services/map_tile_cache_service.dart';
|
||||
import '../utils/contact_search.dart';
|
||||
|
|
@ -64,6 +65,8 @@ class _MapScreenState extends State<MapScreen> {
|
|||
final List<Polyline> _polylines = [];
|
||||
bool _legendExpanded = false;
|
||||
bool _showNodeLabels = true;
|
||||
List<_GuessedLocation> _cachedGuessedLocations = [];
|
||||
String _guessedLocationsCacheKey = '';
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
|
|
@ -119,8 +122,8 @@ class _MapScreenState extends State<MapScreen> {
|
|||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Consumer2<MeshCoreConnector, AppSettingsService>(
|
||||
builder: (context, connector, settingsService, child) {
|
||||
return Consumer3<MeshCoreConnector, AppSettingsService, PathHistoryService>(
|
||||
builder: (context, connector, settingsService, pathHistory, child) {
|
||||
final tileCache = context.read<MapTileCacheService>();
|
||||
final settings = settingsService.settings;
|
||||
final contacts = connector.contacts;
|
||||
|
|
@ -160,6 +163,40 @@ class _MapScreenState extends State<MapScreen> {
|
|||
.where((c) => c.hasLocation)
|
||||
.toList();
|
||||
|
||||
// All contacts with a known location — used as anchors regardless of
|
||||
// time/key-prefix filters so that repeaters are always available.
|
||||
final allContactsWithLocation = contacts
|
||||
.where((c) => c.hasLocation)
|
||||
.toList();
|
||||
|
||||
// Compute guessed locations with caching
|
||||
final maxRangeKm = _estimateLoRaRangeKm(connector);
|
||||
final filteredKeys = filteredByKeyPrefix
|
||||
.map((c) => '${c.publicKeyHex}:${c.path.join("-")}')
|
||||
.join(',');
|
||||
final anchorKeys = allContactsWithLocation
|
||||
.map(
|
||||
(c) =>
|
||||
'${c.publicKeyHex}:${c.latitude}:${c.longitude}:${c.path.isNotEmpty ? c.path.last : ""}',
|
||||
)
|
||||
.join(',');
|
||||
final cacheKey =
|
||||
'$filteredKeys|$anchorKeys|${pathHistory.version}:${connector.currentSf}:${connector.currentBwHz}:${connector.currentTxPower}:${settings.mapShowGuessedLocations}';
|
||||
if (cacheKey != _guessedLocationsCacheKey) {
|
||||
_guessedLocationsCacheKey = cacheKey;
|
||||
_cachedGuessedLocations = settings.mapShowGuessedLocations
|
||||
? _computeGuessedLocations(
|
||||
filteredByKeyPrefix,
|
||||
allContactsWithLocation,
|
||||
pathHistory,
|
||||
maxRangeKm,
|
||||
)
|
||||
: [];
|
||||
}
|
||||
final guessedLocations = settings.mapShowGuessedLocations
|
||||
? _cachedGuessedLocations
|
||||
: <_GuessedLocation>[];
|
||||
|
||||
_polylines.clear();
|
||||
_polylines.addAll(
|
||||
_points.length > 1
|
||||
|
|
@ -430,6 +467,8 @@ class _MapScreenState extends State<MapScreen> {
|
|||
size: 34,
|
||||
),
|
||||
),
|
||||
if (!_isBuildingPathTrace)
|
||||
...guessedLocations.map(_buildGuessedMarker),
|
||||
..._buildMarkers(
|
||||
contactsWithLocation,
|
||||
settings,
|
||||
|
|
@ -489,6 +528,7 @@ class _MapScreenState extends State<MapScreen> {
|
|||
contactsWithLocation,
|
||||
settings,
|
||||
sharedMarkers.length,
|
||||
guessedLocations.length,
|
||||
),
|
||||
if (_isBuildingPathTrace) _buildPathTraceOverlay(),
|
||||
],
|
||||
|
|
@ -512,6 +552,200 @@ class _MapScreenState extends State<MapScreen> {
|
|||
);
|
||||
}
|
||||
|
||||
List<_GuessedLocation> _computeGuessedLocations(
|
||||
List<Contact> allContacts,
|
||||
List<Contact> withLocation,
|
||||
PathHistoryService pathHistory,
|
||||
double? maxRangeKm,
|
||||
) {
|
||||
// Index known-location repeaters by their 1-byte hash.
|
||||
// null value = two repeaters share the same hash byte (ambiguous collision).
|
||||
final repeaterByHash = <int, Contact?>{};
|
||||
for (final c in withLocation) {
|
||||
if (c.type == advTypeRepeater) {
|
||||
if (repeaterByHash.containsKey(c.publicKey[0])) {
|
||||
repeaterByHash[c.publicKey[0]] =
|
||||
null; // collision: can't disambiguate
|
||||
} else {
|
||||
repeaterByHash[c.publicKey[0]] = c;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
final result = <_GuessedLocation>[];
|
||||
|
||||
for (final contact in allContacts) {
|
||||
if (contact.hasLocation) continue;
|
||||
|
||||
final anchorSet = <LatLng>{};
|
||||
|
||||
// Collect the contact-side (last-hop) repeater from every known path.
|
||||
// path = [device-side hop, ..., contact-side hop]
|
||||
// Only path.last is actually within radio range of the contact — using
|
||||
// earlier bytes would anchor against our own side of the network.
|
||||
final pathSets = <List<int>>[
|
||||
contact.path.toList(),
|
||||
...pathHistory
|
||||
.getRecentPaths(contact.publicKeyHex)
|
||||
.map((r) => r.pathBytes),
|
||||
];
|
||||
final lastHopBytes = <int>{};
|
||||
for (final pathBytes in pathSets) {
|
||||
if (pathBytes.isEmpty) continue;
|
||||
final lastHop = pathBytes.last;
|
||||
lastHopBytes.add(lastHop);
|
||||
final r = repeaterByHash[lastHop];
|
||||
if (r != null) anchorSet.add(LatLng(r.latitude!, r.longitude!));
|
||||
}
|
||||
|
||||
// Fallback: for any last-hop byte with no GPS repeater, average the
|
||||
// positions of contacts with known GPS that share the same last hop.
|
||||
// Those contacts are all adjacent to the same unknown repeater, so their
|
||||
// centroid is a reasonable proxy for its location.
|
||||
for (final byte in lastHopBytes) {
|
||||
if (repeaterByHash.containsKey(byte)) continue;
|
||||
for (final c in withLocation) {
|
||||
if (c.path.isNotEmpty && c.path.last == byte) {
|
||||
anchorSet.add(LatLng(c.latitude!, c.longitude!));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Filter anchors that are geometrically inconsistent with radio range.
|
||||
// Two anchors more than 2 * maxRange apart cannot both be in direct radio
|
||||
// range of the same node, so isolated outliers are removed.
|
||||
final anchors = maxRangeKm != null && anchorSet.length > 1
|
||||
? _filterConsistentAnchors(anchorSet.toList(), maxRangeKm)
|
||||
: anchorSet.toList();
|
||||
|
||||
if (anchors.isEmpty) continue;
|
||||
|
||||
final LatLng position;
|
||||
if (anchors.length == 1) {
|
||||
// Offset single-anchor guesses so they don't overlap the repeater marker.
|
||||
// Use the contact's public key byte as a deterministic angle seed.
|
||||
const offsetDeg = 0.003; // ~330 m at the equator
|
||||
final angle = (contact.publicKey[1] / 255.0) * 2 * pi;
|
||||
position = LatLng(
|
||||
anchors[0].latitude + offsetDeg * cos(angle),
|
||||
anchors[0].longitude + offsetDeg * sin(angle),
|
||||
);
|
||||
} else {
|
||||
double lat = 0, lon = 0;
|
||||
for (final a in anchors) {
|
||||
lat += a.latitude;
|
||||
lon += a.longitude;
|
||||
}
|
||||
position = LatLng(lat / anchors.length, lon / anchors.length);
|
||||
}
|
||||
result.add(
|
||||
_GuessedLocation(
|
||||
contact: contact,
|
||||
position: position,
|
||||
highConfidence: anchors.length >= 2,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// Estimates the free-space maximum LoRa range in km from the connected
|
||||
/// device's current radio parameters. Returns null if parameters are unknown.
|
||||
double? _estimateLoRaRangeKm(MeshCoreConnector connector) {
|
||||
final freqHz = connector.currentFreqHz;
|
||||
final bwHz = connector.currentBwHz;
|
||||
final sf = connector.currentSf;
|
||||
final txPower = connector.currentTxPower;
|
||||
if (freqHz == null || bwHz == null || sf == null || txPower == null) {
|
||||
return null;
|
||||
}
|
||||
// LoRa receiver sensitivity = thermal noise + NF + required demod SNR
|
||||
const noiseFigureDb = 6.0;
|
||||
final thermalNoiseDbm = -174.0 + 10 * log(bwHz.toDouble()) / ln10;
|
||||
final sensitivityDbm =
|
||||
thermalNoiseDbm + noiseFigureDb + _sfToRequiredSnrDb(sf);
|
||||
// FSPL at max range equals link budget:
|
||||
// FSPL = 20*log10(d_m) + 20*log10(f_hz) - 147.55
|
||||
final linkBudgetDb = txPower.toDouble() - sensitivityDbm;
|
||||
final exponent =
|
||||
(linkBudgetDb + 147.55 - 20 * log(freqHz.toDouble()) / ln10) / 20;
|
||||
return pow(10, exponent) / 1000;
|
||||
}
|
||||
|
||||
double _sfToRequiredSnrDb(int sf) {
|
||||
switch (sf) {
|
||||
case 5:
|
||||
return -2.5;
|
||||
case 6:
|
||||
return -5.0;
|
||||
case 7:
|
||||
return -7.5;
|
||||
case 8:
|
||||
return -10.0;
|
||||
case 9:
|
||||
return -12.5;
|
||||
case 10:
|
||||
return -15.0;
|
||||
case 11:
|
||||
return -17.5;
|
||||
case 12:
|
||||
return -20.0;
|
||||
default:
|
||||
return -10.0;
|
||||
}
|
||||
}
|
||||
|
||||
/// Removes anchors that have no neighbour within 2 * maxRangeKm.
|
||||
/// A node cannot be simultaneously in radio range of two points farther apart
|
||||
/// than twice the expected maximum range.
|
||||
List<LatLng> _filterConsistentAnchors(
|
||||
List<LatLng> anchors,
|
||||
double maxRangeKm,
|
||||
) {
|
||||
const distance = Distance();
|
||||
final maxDistM = maxRangeKm * 2000;
|
||||
return anchors
|
||||
.where((a) => anchors.any((b) => b != a && distance(a, b) <= maxDistM))
|
||||
.toList();
|
||||
}
|
||||
|
||||
Marker _buildGuessedMarker(_GuessedLocation guess) {
|
||||
final color = _getNodeColor(guess.contact.type);
|
||||
return Marker(
|
||||
point: guess.position,
|
||||
width: 35,
|
||||
height: 35,
|
||||
child: GestureDetector(
|
||||
onTap: () => _showNodeInfo(
|
||||
context,
|
||||
guess.contact,
|
||||
guessedPosition: guess.position,
|
||||
),
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(4),
|
||||
decoration: BoxDecoration(
|
||||
color: color.withValues(alpha: guess.highConfidence ? 0.55 : 0.30),
|
||||
shape: BoxShape.circle,
|
||||
border: Border.all(color: Colors.white, width: 2),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.black.withValues(alpha: 0.3),
|
||||
blurRadius: 4,
|
||||
offset: const Offset(0, 2),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: const Icon(
|
||||
Icons.not_listed_location,
|
||||
color: Colors.white,
|
||||
size: 20,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
List<Marker> _buildMarkers(
|
||||
List<Contact> contacts,
|
||||
settings, {
|
||||
|
|
@ -657,6 +891,7 @@ class _MapScreenState extends State<MapScreen> {
|
|||
List<Contact> contactsWithLocation,
|
||||
settings,
|
||||
int markerCount,
|
||||
int guessedCount,
|
||||
) {
|
||||
int nodeCount = 0;
|
||||
for (final contact in contactsWithLocation) {
|
||||
|
|
@ -696,7 +931,12 @@ class _MapScreenState extends State<MapScreen> {
|
|||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
context.l10n.map_nodesCount(nodeCount),
|
||||
context.l10n.map_nodesCount(
|
||||
nodeCount +
|
||||
(settings.mapShowGuessedLocations
|
||||
? guessedCount
|
||||
: 0),
|
||||
),
|
||||
style: const TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 14,
|
||||
|
|
@ -764,6 +1004,12 @@ class _MapScreenState extends State<MapScreen> {
|
|||
context.l10n.map_pinPublic,
|
||||
Colors.orange,
|
||||
),
|
||||
if (settings.mapShowGuessedLocations && guessedCount > 0)
|
||||
_buildLegendItem(
|
||||
Icons.not_listed_location,
|
||||
context.l10n.map_guessedLocation,
|
||||
Colors.grey,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
|
@ -952,7 +1198,11 @@ class _MapScreenState extends State<MapScreen> {
|
|||
);
|
||||
}
|
||||
|
||||
void _showNodeInfo(BuildContext context, Contact contact) {
|
||||
void _showNodeInfo(
|
||||
BuildContext context,
|
||||
Contact contact, {
|
||||
LatLng? guessedPosition,
|
||||
}) {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (dialogContext) => AlertDialog(
|
||||
|
|
@ -972,10 +1222,16 @@ class _MapScreenState extends State<MapScreen> {
|
|||
children: [
|
||||
_buildInfoRow('Type', contact.typeLabel),
|
||||
_buildInfoRow('Path', contact.pathLabel),
|
||||
_buildInfoRow(
|
||||
'Location',
|
||||
'${contact.latitude!.toStringAsFixed(6)}, ${contact.longitude!.toStringAsFixed(6)}',
|
||||
),
|
||||
if (contact.hasLocation)
|
||||
_buildInfoRow(
|
||||
'Location',
|
||||
'${contact.latitude!.toStringAsFixed(6)}, ${contact.longitude!.toStringAsFixed(6)}',
|
||||
)
|
||||
else if (guessedPosition != null)
|
||||
_buildInfoRow(
|
||||
'Est. Location',
|
||||
'~${guessedPosition.latitude.toStringAsFixed(6)}, ${guessedPosition.longitude.toStringAsFixed(6)}',
|
||||
),
|
||||
_buildInfoRow(
|
||||
context.l10n.map_lastSeen,
|
||||
_formatLastSeen(contact.lastSeen),
|
||||
|
|
@ -1481,6 +1737,14 @@ class _MapScreenState extends State<MapScreen> {
|
|||
},
|
||||
contentPadding: EdgeInsets.zero,
|
||||
),
|
||||
CheckboxListTile(
|
||||
title: Text(context.l10n.map_showGuessedLocations),
|
||||
value: settings.mapShowGuessedLocations,
|
||||
onChanged: (value) {
|
||||
service.setMapShowGuessedLocations(value ?? true);
|
||||
},
|
||||
contentPadding: EdgeInsets.zero,
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
context.l10n.map_keyPrefix,
|
||||
|
|
@ -1744,6 +2008,18 @@ class _MapScreenState extends State<MapScreen> {
|
|||
}
|
||||
}
|
||||
|
||||
class _GuessedLocation {
|
||||
final Contact contact;
|
||||
final LatLng position;
|
||||
final bool highConfidence;
|
||||
|
||||
_GuessedLocation({
|
||||
required this.contact,
|
||||
required this.position,
|
||||
required this.highConfidence,
|
||||
});
|
||||
}
|
||||
|
||||
class _MarkerPayload {
|
||||
final LatLng position;
|
||||
final String label;
|
||||
|
|
|
|||
|
|
@ -54,6 +54,7 @@ class PathTraceMapScreen extends StatefulWidget {
|
|||
final int? repeaterId;
|
||||
final bool flipPathRound;
|
||||
final bool reversePathRound;
|
||||
final Contact? targetContact;
|
||||
|
||||
const PathTraceMapScreen({
|
||||
super.key,
|
||||
|
|
@ -62,6 +63,7 @@ class PathTraceMapScreen extends StatefulWidget {
|
|||
this.repeaterId,
|
||||
this.flipPathRound = false,
|
||||
this.reversePathRound = false,
|
||||
this.targetContact,
|
||||
});
|
||||
|
||||
@override
|
||||
|
|
@ -78,6 +80,11 @@ class _PathTraceMapScreenState extends State<PathTraceMapScreen> {
|
|||
bool _failed2Loaded = false;
|
||||
bool _hasData = false;
|
||||
PathTraceData? _traceData;
|
||||
// Inferred positions for hops that have no GPS location, keyed by hop byte.
|
||||
Map<int, LatLng> _inferredHopPositions = {};
|
||||
// Endpoint position for the target contact (GPS or guessed).
|
||||
LatLng? _targetContactPosition;
|
||||
bool _targetContactIsGuessed = false;
|
||||
List<LatLng> _points = <LatLng>[];
|
||||
List<Polyline> _polylines = [];
|
||||
LatLng? _initialCenter = LatLng(0, 0);
|
||||
|
|
@ -242,25 +249,91 @@ class _PathTraceMapScreenState extends State<PathTraceMapScreen> {
|
|||
}
|
||||
});
|
||||
|
||||
// For hops with no GPS contact, infer position from other contacts
|
||||
// with known GPS that share the same last-hop byte.
|
||||
final Map<int, LatLng> inferredPositions = {};
|
||||
for (final hop in pathData) {
|
||||
final contact = pathContacts[hop];
|
||||
if (contact != null && contact.hasLocation) continue;
|
||||
final peers = connector.contacts
|
||||
.where(
|
||||
(c) => c.hasLocation && c.path.isNotEmpty && c.path.last == hop,
|
||||
)
|
||||
.toList();
|
||||
if (peers.isNotEmpty) {
|
||||
final lat =
|
||||
peers.map((c) => c.latitude!).reduce((a, b) => a + b) /
|
||||
peers.length;
|
||||
final lon =
|
||||
peers.map((c) => c.longitude!).reduce((a, b) => a + b) /
|
||||
peers.length;
|
||||
inferredPositions[hop] = LatLng(lat, lon);
|
||||
}
|
||||
}
|
||||
|
||||
setState(() {
|
||||
_isLoading = false;
|
||||
_hasData = true;
|
||||
_inferredHopPositions = inferredPositions;
|
||||
_traceData = PathTraceData(
|
||||
pathData: pathData,
|
||||
snrData: snrData,
|
||||
pathContacts: pathContacts,
|
||||
);
|
||||
// Compute endpoint position for the target contact.
|
||||
LatLng? targetPos;
|
||||
bool targetGuessed = false;
|
||||
final target = widget.targetContact;
|
||||
if (target != null) {
|
||||
if (target.hasLocation) {
|
||||
targetPos = LatLng(target.latitude!, target.longitude!);
|
||||
} else if (pathData.isNotEmpty) {
|
||||
// Infer from the last hop: average GPS contacts sharing that hop.
|
||||
// For a round-trip path (flipPathRound), the target-side hop sits
|
||||
// in the middle of the symmetric sequence; .last is the local side.
|
||||
final lastHop = (widget.flipPathRound && pathData.length > 1)
|
||||
? pathData[(pathData.length - 1) ~/ 2]
|
||||
: pathData.last;
|
||||
final peers = connector.contacts
|
||||
.where(
|
||||
(c) =>
|
||||
c.hasLocation &&
|
||||
c.path.isNotEmpty &&
|
||||
c.path.last == lastHop,
|
||||
)
|
||||
.toList();
|
||||
if (peers.isNotEmpty) {
|
||||
final lat =
|
||||
peers.map((c) => c.latitude!).reduce((a, b) => a + b) /
|
||||
peers.length;
|
||||
final lon =
|
||||
peers.map((c) => c.longitude!).reduce((a, b) => a + b) /
|
||||
peers.length;
|
||||
const offsetDeg = 0.003;
|
||||
final angle = (target.publicKey[1] / 255.0) * 2 * pi;
|
||||
targetPos = LatLng(
|
||||
lat + offsetDeg * cos(angle),
|
||||
lon + offsetDeg * sin(angle),
|
||||
);
|
||||
targetGuessed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
_targetContactPosition = targetPos;
|
||||
_targetContactIsGuessed = targetGuessed;
|
||||
|
||||
_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) {
|
||||
if (contact != null && contact.hasLocation) {
|
||||
_points.add(LatLng(contact.latitude!, contact.longitude!));
|
||||
} else {
|
||||
final inferred = inferredPositions[hop];
|
||||
if (inferred != null) _points.add(inferred);
|
||||
}
|
||||
}
|
||||
if (targetPos != null) _points.add(targetPos);
|
||||
_polylines = _points.length > 1
|
||||
? [
|
||||
Polyline(
|
||||
|
|
@ -382,8 +455,13 @@ class _PathTraceMapScreenState extends State<PathTraceMapScreen> {
|
|||
final markers = <Marker>[];
|
||||
for (final hop in pathData) {
|
||||
final contact = _traceData!.pathContacts[hop];
|
||||
if (contact == null || !contact.hasLocation) continue;
|
||||
final point = LatLng(contact.latitude!, contact.longitude!);
|
||||
final inferred = _inferredHopPositions[hop];
|
||||
final hasGps = contact != null && contact.hasLocation;
|
||||
if (!hasGps && inferred == null) continue;
|
||||
final point = hasGps
|
||||
? LatLng(contact.latitude!, contact.longitude!)
|
||||
: inferred!;
|
||||
final label = hop.toRadixString(16).padLeft(2, '0').toUpperCase();
|
||||
markers.add(
|
||||
Marker(
|
||||
point: point,
|
||||
|
|
@ -392,7 +470,9 @@ class _PathTraceMapScreenState extends State<PathTraceMapScreen> {
|
|||
child: Container(
|
||||
padding: const EdgeInsets.all(4),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.green,
|
||||
color: hasGps
|
||||
? Colors.green
|
||||
: Colors.orange.withValues(alpha: 0.75),
|
||||
shape: BoxShape.circle,
|
||||
border: Border.all(color: Colors.white, width: 2),
|
||||
boxShadow: [
|
||||
|
|
@ -405,10 +485,7 @@ class _PathTraceMapScreenState extends State<PathTraceMapScreen> {
|
|||
),
|
||||
alignment: Alignment.center,
|
||||
child: Text(
|
||||
contact.publicKey
|
||||
.sublist(0, 1)
|
||||
.map((b) => b.toRadixString(16).padLeft(2, '0').toUpperCase())
|
||||
.join(),
|
||||
hasGps ? label : '~$label',
|
||||
style: const TextStyle(
|
||||
color: Colors.white,
|
||||
fontWeight: FontWeight.bold,
|
||||
|
|
@ -419,7 +496,12 @@ class _PathTraceMapScreenState extends State<PathTraceMapScreen> {
|
|||
),
|
||||
);
|
||||
if (showLabels) {
|
||||
markers.add(_buildNodeLabelMarker(point: point, label: contact.name));
|
||||
markers.add(
|
||||
_buildNodeLabelMarker(
|
||||
point: point,
|
||||
label: contact?.name ?? '~$label',
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -468,6 +550,47 @@ class _PathTraceMapScreenState extends State<PathTraceMapScreen> {
|
|||
}
|
||||
}
|
||||
|
||||
// Add target contact endpoint marker.
|
||||
final targetPos = _targetContactPosition;
|
||||
if (targetPos != null) {
|
||||
final isGuessed = _targetContactIsGuessed;
|
||||
final targetName = widget.targetContact?.name ?? '?';
|
||||
markers.add(
|
||||
Marker(
|
||||
point: targetPos,
|
||||
width: 35,
|
||||
height: 35,
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(4),
|
||||
decoration: BoxDecoration(
|
||||
color: isGuessed
|
||||
? Colors.purple.withValues(alpha: 0.55)
|
||||
: Colors.red,
|
||||
shape: BoxShape.circle,
|
||||
border: Border.all(color: Colors.white, width: 2),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.black.withValues(alpha: 0.3),
|
||||
blurRadius: 4,
|
||||
offset: const Offset(0, 2),
|
||||
),
|
||||
],
|
||||
),
|
||||
alignment: Alignment.center,
|
||||
child: const Icon(Icons.person, color: Colors.white, size: 18),
|
||||
),
|
||||
),
|
||||
);
|
||||
if (showLabels) {
|
||||
markers.add(
|
||||
_buildNodeLabelMarker(
|
||||
point: targetPos,
|
||||
label: isGuessed ? '~$targetName' : targetName,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return markers;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ import '../connector/meshcore_connector.dart';
|
|||
import '../connector/meshcore_protocol.dart';
|
||||
import '../l10n/l10n.dart';
|
||||
import '../models/radio_settings.dart';
|
||||
import '../widgets/adaptive_app_bar_title.dart';
|
||||
import '../widgets/app_bar.dart';
|
||||
import 'app_settings_screen.dart';
|
||||
import 'app_debug_log_screen.dart';
|
||||
import 'ble_debug_log_screen.dart';
|
||||
|
|
@ -43,8 +43,11 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
|||
final l10n = context.l10n;
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: AdaptiveAppBarTitle(l10n.settings_title),
|
||||
centerTitle: true,
|
||||
title: AppBarTitle(
|
||||
l10n.settings_title,
|
||||
indicators: false,
|
||||
subtitle: false,
|
||||
),
|
||||
),
|
||||
body: SafeArea(
|
||||
top: false,
|
||||
|
|
@ -274,6 +277,14 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
|||
onTap: () => _editLocation(context, connector),
|
||||
),
|
||||
const Divider(height: 1),
|
||||
ListTile(
|
||||
leading: const Icon(Icons.group_add_outlined),
|
||||
title: Text(l10n.settings_contactSettings),
|
||||
subtitle: Text(l10n.settings_contactSettingsSubtitle),
|
||||
trailing: const Icon(Icons.chevron_right),
|
||||
onTap: () => _editAutoAddConfig(context, connector),
|
||||
),
|
||||
const Divider(height: 1),
|
||||
ListTile(
|
||||
leading: const Icon(Icons.visibility_off_outlined),
|
||||
title: Text(l10n.settings_privacyMode),
|
||||
|
|
@ -849,6 +860,121 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
|||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _editAutoAddConfig(BuildContext context, MeshCoreConnector connector) {
|
||||
final l10n = context.l10n;
|
||||
bool autoAddChat = false;
|
||||
bool autoAddRepeater = false;
|
||||
bool autoAddRoomServer = false;
|
||||
bool autoAddSensor = false;
|
||||
bool overwriteOldest = false;
|
||||
|
||||
final connector = context.read<MeshCoreConnector>();
|
||||
autoAddChat = connector.autoAddUsers ?? false;
|
||||
autoAddRepeater = connector.autoAddRepeaters ?? false;
|
||||
autoAddRoomServer = connector.autoAddRoomServers ?? false;
|
||||
autoAddSensor = connector.autoAddSensors ?? false;
|
||||
overwriteOldest = connector.autoAddOverwriteOldest ?? false;
|
||||
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (dialogContext) => StatefulBuilder(
|
||||
builder: (context, setDialogState) => AlertDialog(
|
||||
title: Text(l10n.contactsSettings_autoAddTitle),
|
||||
content: SingleChildScrollView(
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
FeatureToggleRow(
|
||||
title: l10n.contactsSettings_autoAddUsersTitle,
|
||||
subtitle: l10n.contactsSettings_autoAddUsersSubtitle,
|
||||
value: autoAddChat,
|
||||
onChanged: (value) {
|
||||
setDialogState(() => autoAddChat = value);
|
||||
},
|
||||
),
|
||||
SizedBox(height: 8),
|
||||
FeatureToggleRow(
|
||||
title: l10n.contactsSettings_autoAddRepeatersTitle,
|
||||
subtitle: l10n.contactsSettings_autoAddRepeatersSubtitle,
|
||||
value: autoAddRepeater,
|
||||
onChanged: (value) {
|
||||
setDialogState(() => autoAddRepeater = value);
|
||||
},
|
||||
),
|
||||
SizedBox(height: 8),
|
||||
FeatureToggleRow(
|
||||
title: l10n.contactsSettings_autoAddRoomServersTitle,
|
||||
subtitle: l10n.contactsSettings_autoAddRoomServersSubtitle,
|
||||
value: autoAddRoomServer,
|
||||
onChanged: (value) {
|
||||
setDialogState(() => autoAddRoomServer = value);
|
||||
},
|
||||
),
|
||||
SizedBox(height: 8),
|
||||
FeatureToggleRow(
|
||||
title: l10n.contactsSettings_autoAddSensorsTitle,
|
||||
subtitle: l10n.contactsSettings_autoAddSensorsSubtitle,
|
||||
value: autoAddSensor,
|
||||
onChanged: (value) {
|
||||
setDialogState(() => autoAddSensor = value);
|
||||
},
|
||||
),
|
||||
Divider(height: 4),
|
||||
FeatureToggleRow(
|
||||
title: l10n.contactsSettings_overwriteOldestTitle,
|
||||
subtitle: l10n.contactsSettings_overwriteOldestSubtitle,
|
||||
value: overwriteOldest,
|
||||
onChanged: (value) {
|
||||
setDialogState(() => overwriteOldest = value);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(context),
|
||||
child: Text(l10n.common_cancel),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
_sendSettings(
|
||||
connector,
|
||||
autoAddChat,
|
||||
autoAddRepeater,
|
||||
autoAddRoomServer,
|
||||
autoAddSensor,
|
||||
overwriteOldest,
|
||||
);
|
||||
Navigator.pop(context);
|
||||
},
|
||||
child: Text(l10n.common_save),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _sendSettings(
|
||||
MeshCoreConnector connector,
|
||||
bool autoAddChat,
|
||||
bool autoAddRepeater,
|
||||
bool autoAddRoomServer,
|
||||
bool autoAddSensor,
|
||||
bool overwriteOldest,
|
||||
) async {
|
||||
final frame = buildSetAutoAddConfigFrame(
|
||||
autoAddChat: autoAddChat,
|
||||
autoAddRepeater: autoAddRepeater,
|
||||
autoAddRoomServer: autoAddRoomServer,
|
||||
autoAddSensor: autoAddSensor,
|
||||
overwriteOldest: overwriteOldest,
|
||||
);
|
||||
await connector.sendFrame(frame);
|
||||
await connector.sendFrame(buildGetAutoAddFlagsFrame());
|
||||
}
|
||||
}
|
||||
|
||||
class _RadioSettingsDialog extends StatefulWidget {
|
||||
|
|
|
|||
|
|
@ -80,6 +80,10 @@ class AppSettingsService extends ChangeNotifier {
|
|||
await updateSettings(_settings.copyWith(mapShowMarkers: value));
|
||||
}
|
||||
|
||||
Future<void> setMapShowGuessedLocations(bool value) async {
|
||||
await updateSettings(_settings.copyWith(mapShowGuessedLocations: value));
|
||||
}
|
||||
|
||||
Future<void> setEnableMessageTracing(bool value) async {
|
||||
await updateSettings(_settings.copyWith(enableMessageTracing: value));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -172,8 +172,6 @@ class BleDebugLogService extends ChangeNotifier {
|
|||
return 'CMD_GET_CHANNEL';
|
||||
case cmdSetChannel:
|
||||
return 'CMD_SET_CHANNEL';
|
||||
case cmdGetRadioSettings:
|
||||
return 'CMD_GET_RADIO_SETTINGS';
|
||||
case cmdSetCustomVar:
|
||||
return 'CMD_SET_CUSTOM_VAR';
|
||||
case cmdSendTracePath:
|
||||
|
|
@ -215,8 +213,8 @@ class BleDebugLogService extends ChangeNotifier {
|
|||
return 'RESP_CODE_CHANNEL_MSG_RECV_V3';
|
||||
case respCodeChannelInfo:
|
||||
return 'RESP_CODE_CHANNEL_INFO';
|
||||
case respCodeRadioSettings:
|
||||
return 'RESP_CODE_RADIO_SETTINGS';
|
||||
case respCodeAutoAddConfig:
|
||||
return 'RESP_CODE_AUTO_ADD_CONFIG';
|
||||
case pushCodeTraceData:
|
||||
return 'PUSH_CODE_TRACE_DATA';
|
||||
default:
|
||||
|
|
|
|||
|
|
@ -15,6 +15,9 @@ class PathHistoryService extends ChangeNotifier {
|
|||
final List<String> _cacheAccessOrder = [];
|
||||
|
||||
static const int _maxHistoryEntries = 100;
|
||||
|
||||
int _version = 0;
|
||||
int get version => _version;
|
||||
static const int _autoRotationTopCount = 3;
|
||||
|
||||
PathHistoryService(this._storage);
|
||||
|
|
@ -185,6 +188,7 @@ class PathHistoryService extends ChangeNotifier {
|
|||
) {
|
||||
var history = _cache[contactPubKeyHex];
|
||||
if (history == null) return;
|
||||
_version++;
|
||||
|
||||
final existing = _findPathRecord(contactPubKeyHex, pathBytes);
|
||||
if (existing != null) {
|
||||
|
|
@ -241,6 +245,7 @@ class PathHistoryService extends ChangeNotifier {
|
|||
_cache[contactPubKeyHex] = loaded;
|
||||
_trackAccess(contactPubKeyHex);
|
||||
_evictIfNeeded();
|
||||
_version++;
|
||||
notifyListeners();
|
||||
}
|
||||
});
|
||||
|
|
@ -276,6 +281,7 @@ class PathHistoryService extends ChangeNotifier {
|
|||
_autoRotationIndex.remove(contactPubKeyHex);
|
||||
_floodStats.remove(contactPubKeyHex);
|
||||
await _storage.clearPathHistory(contactPubKeyHex);
|
||||
_version++;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
|
|
@ -295,6 +301,7 @@ class PathHistoryService extends ChangeNotifier {
|
|||
);
|
||||
|
||||
await _storage.savePathHistory(contactPubKeyHex, _cache[contactPubKeyHex]!);
|
||||
_version++;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
|
|
|
|||
61
lib/storage/contact_discovery_store.dart
Normal file
61
lib/storage/contact_discovery_store.dart
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
import 'dart:convert';
|
||||
import 'dart:typed_data';
|
||||
|
||||
import '../models/discovery_contact.dart';
|
||||
import 'prefs_manager.dart';
|
||||
|
||||
class ContactDiscoveryStore {
|
||||
static const String _key = 'discovered_contacts';
|
||||
|
||||
Future<List<DiscoveryContact>> loadContacts() async {
|
||||
final prefs = PrefsManager.instance;
|
||||
final jsonStr = prefs.getString(_key);
|
||||
if (jsonStr == null) return [];
|
||||
|
||||
try {
|
||||
final jsonList = jsonDecode(jsonStr) as List<dynamic>;
|
||||
return jsonList
|
||||
.map((entry) => _fromJson(entry as Map<String, dynamic>))
|
||||
.toList();
|
||||
} catch (_) {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> saveContacts(List<DiscoveryContact> contacts) async {
|
||||
final prefs = PrefsManager.instance;
|
||||
final jsonList = contacts.map(_toJson).toList();
|
||||
await prefs.setString(_key, jsonEncode(jsonList));
|
||||
}
|
||||
|
||||
Map<String, dynamic> _toJson(DiscoveryContact contact) {
|
||||
return {
|
||||
'rawPacket': base64Encode(contact.rawPacket),
|
||||
'publicKey': base64Encode(contact.publicKey),
|
||||
'name': contact.name,
|
||||
'type': contact.type,
|
||||
'pathLength': contact.pathLength,
|
||||
'path': base64Encode(contact.path),
|
||||
'latitude': contact.latitude,
|
||||
'longitude': contact.longitude,
|
||||
'lastSeen': contact.lastSeen.millisecondsSinceEpoch,
|
||||
};
|
||||
}
|
||||
|
||||
DiscoveryContact _fromJson(Map<String, dynamic> json) {
|
||||
final lastSeenMs = json['lastSeen'] as int? ?? 0;
|
||||
return DiscoveryContact(
|
||||
rawPacket: Uint8List.fromList(base64Decode(json['rawPacket'] as String)),
|
||||
publicKey: Uint8List.fromList(base64Decode(json['publicKey'] as String)),
|
||||
name: json['name'] as String? ?? 'Unknown',
|
||||
type: json['type'] as int? ?? 0,
|
||||
pathLength: json['pathLength'] as int? ?? -1,
|
||||
path: json['path'] != null
|
||||
? Uint8List.fromList(base64Decode(json['path'] as String))
|
||||
: Uint8List(0),
|
||||
latitude: (json['latitude'] as num?)?.toDouble(),
|
||||
longitude: (json['longitude'] as num?)?.toDouble(),
|
||||
lastSeen: DateTime.fromMillisecondsSinceEpoch(lastSeenMs),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,3 +1,5 @@
|
|||
import 'package:meshcore_open/models/discovery_contact.dart';
|
||||
|
||||
import '../models/contact.dart';
|
||||
|
||||
bool matchesContactQuery(Contact contact, String query) {
|
||||
|
|
@ -14,6 +16,20 @@ bool matchesContactQuery(Contact contact, String query) {
|
|||
return contact.publicKeyHex.toLowerCase().startsWith(hexPrefix);
|
||||
}
|
||||
|
||||
bool matchesDiscoveryContactQuery(DiscoveryContact contact, String query) {
|
||||
final normalizedQuery = query.trim().toLowerCase();
|
||||
if (normalizedQuery.isEmpty) return true;
|
||||
|
||||
if (contact.name.toLowerCase().contains(normalizedQuery)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
final hexPrefix = _extractHexPrefix(normalizedQuery);
|
||||
if (hexPrefix == null) return false;
|
||||
|
||||
return contact.publicKeyHex.toLowerCase().startsWith(hexPrefix);
|
||||
}
|
||||
|
||||
String? _extractHexPrefix(String query) {
|
||||
var cleaned = query;
|
||||
if (cleaned.startsWith('<')) {
|
||||
|
|
|
|||
|
|
@ -9,7 +9,16 @@ class AppBarTitle extends StatelessWidget {
|
|||
final String title;
|
||||
final Widget? leading;
|
||||
final Widget? trailing;
|
||||
const AppBarTitle(this.title, {this.leading, this.trailing, super.key});
|
||||
final bool indicators;
|
||||
final bool subtitle;
|
||||
const AppBarTitle(
|
||||
this.title, {
|
||||
this.leading,
|
||||
this.trailing,
|
||||
this.indicators = true,
|
||||
this.subtitle = true,
|
||||
super.key,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
|
|
@ -21,12 +30,12 @@ class AppBarTitle extends StatelessWidget {
|
|||
final availableWidth = constraints.hasBoundedWidth
|
||||
? constraints.maxWidth
|
||||
: MediaQuery.sizeOf(context).width;
|
||||
final compact = availableWidth < 240;
|
||||
final compact = availableWidth < 170;
|
||||
final showSubtitle =
|
||||
!compact && connector.isConnected && selfName != null;
|
||||
!compact && connector.isConnected && selfName != null && subtitle;
|
||||
final showBattery = availableWidth >= 60;
|
||||
final showSnr = availableWidth >= 110;
|
||||
final showIndicators = showBattery || showSnr;
|
||||
final showIndicators = (showBattery || showSnr) && indicators;
|
||||
|
||||
return Row(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
|
|
|
|||
|
|
@ -224,3 +224,93 @@ class ContactsFilterMenu extends StatelessWidget {
|
|||
);
|
||||
}
|
||||
}
|
||||
|
||||
class DiscoveryContactsFilterMenu extends StatelessWidget {
|
||||
final ContactSortOption sortOption;
|
||||
final ContactTypeFilter typeFilter;
|
||||
final ValueChanged<ContactSortOption> onSortChanged;
|
||||
final ValueChanged<ContactTypeFilter> onTypeFilterChanged;
|
||||
|
||||
const DiscoveryContactsFilterMenu({
|
||||
super.key,
|
||||
required this.sortOption,
|
||||
required this.typeFilter,
|
||||
required this.onSortChanged,
|
||||
required this.onTypeFilterChanged,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final l10n = context.l10n;
|
||||
return SortFilterMenu(
|
||||
tooltip: l10n.listFilter_tooltip,
|
||||
sections: [
|
||||
SortFilterMenuSection(
|
||||
title: l10n.listFilter_sortBy,
|
||||
options: [
|
||||
SortFilterMenuOption(
|
||||
value: _actionSortLastSeen,
|
||||
label: l10n.listFilter_heardRecently,
|
||||
checked: sortOption == ContactSortOption.lastSeen,
|
||||
),
|
||||
SortFilterMenuOption(
|
||||
value: _actionSortName,
|
||||
label: l10n.listFilter_az,
|
||||
checked: sortOption == ContactSortOption.name,
|
||||
),
|
||||
],
|
||||
),
|
||||
SortFilterMenuSection(
|
||||
title: l10n.listFilter_filters,
|
||||
options: [
|
||||
SortFilterMenuOption(
|
||||
value: _actionFilterAll,
|
||||
label: l10n.listFilter_all,
|
||||
checked: typeFilter == ContactTypeFilter.all,
|
||||
),
|
||||
SortFilterMenuOption(
|
||||
value: _actionFilterUsers,
|
||||
label: l10n.listFilter_users,
|
||||
checked: typeFilter == ContactTypeFilter.users,
|
||||
),
|
||||
SortFilterMenuOption(
|
||||
value: _actionFilterRepeaters,
|
||||
label: l10n.listFilter_repeaters,
|
||||
checked: typeFilter == ContactTypeFilter.repeaters,
|
||||
),
|
||||
SortFilterMenuOption(
|
||||
value: _actionFilterRooms,
|
||||
label: l10n.listFilter_roomServers,
|
||||
checked: typeFilter == ContactTypeFilter.rooms,
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
onSelected: (action) {
|
||||
switch (action) {
|
||||
case _actionSortName:
|
||||
onSortChanged(ContactSortOption.name);
|
||||
break;
|
||||
case _actionSortLastSeen:
|
||||
onSortChanged(ContactSortOption.lastSeen);
|
||||
break;
|
||||
case _actionFilterAll:
|
||||
onTypeFilterChanged(ContactTypeFilter.all);
|
||||
break;
|
||||
case _actionFilterUsers:
|
||||
onTypeFilterChanged(ContactTypeFilter.users);
|
||||
break;
|
||||
case _actionFilterFavorites:
|
||||
onTypeFilterChanged(ContactTypeFilter.favorites);
|
||||
break;
|
||||
case _actionFilterRepeaters:
|
||||
onTypeFilterChanged(ContactTypeFilter.repeaters);
|
||||
break;
|
||||
case _actionFilterRooms:
|
||||
onTypeFilterChanged(ContactTypeFilter.rooms);
|
||||
break;
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -79,6 +79,7 @@ class _PathManagementDialogState extends State<_PathManagementDialog> {
|
|||
title: context.l10n.contacts_repeaterPathTrace,
|
||||
path: Uint8List.fromList(pathBytes),
|
||||
flipPathRound: true,
|
||||
targetContact: widget.contact,
|
||||
),
|
||||
),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -130,7 +130,7 @@ void main() {
|
|||
connector.fakeActiveUsbPortDisplayLabel =
|
||||
'COM6 - KD3CGK mesh-utility.org';
|
||||
connector.notifyListeners();
|
||||
await tester.pump();
|
||||
await tester.pump(const Duration(milliseconds: 60));
|
||||
|
||||
await tester.tap(find.widgetWithText(FilledButton, 'Connect'));
|
||||
await tester.pump();
|
||||
|
|
@ -155,6 +155,11 @@ void main() {
|
|||
} else {
|
||||
expect(find.widgetWithText(FloatingActionButton, 'USB'), findsNothing);
|
||||
}
|
||||
|
||||
// ScannerScreen.dispose() schedules disconnect work that debounces notify.
|
||||
// Drain that debounce timer before test teardown.
|
||||
await tester.pumpWidget(const SizedBox.shrink());
|
||||
await tester.pump(const Duration(milliseconds: 60));
|
||||
});
|
||||
|
||||
group('Error Handling', () {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue