Compare commits

..

3 Commits

Author SHA1 Message Date
Winston Lowe 49665fd563 Enhance location handling and UI updates
- Refactor location permission handling in SparseLocationLogger
- Add updateMyLocation method to SparseLocationLogger
- Introduce new contact handling in MeshCoreConnector
- Update map screen with a new option to refresh location
- Add localization for "Update Location" in app_en.arb
- Adjust payload types in meshcore_protocol.dart
2026-02-13 16:53:10 -08:00
Winston Lowe 1603adf5dd Add snapToGridCenter method to align position coordinates to grid 2026-02-12 14:18:30 -08:00
Winston Lowe cedbe1dd6c Implement sparse location logging feature and update related services 2026-02-12 14:18:30 -08:00
63 changed files with 1350 additions and 2308 deletions
+1 -1
View File
@@ -83,5 +83,5 @@ flutter {
} }
dependencies { dependencies {
coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:2.1.4") coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:2.0.4")
} }
+60 -134
View File
@@ -2,6 +2,8 @@ import 'dart:async';
import 'dart:convert'; import 'dart:convert';
import 'package:crypto/crypto.dart' as crypto; import 'package:crypto/crypto.dart' as crypto;
import 'package:geolocator/geolocator.dart';
import 'package:meshcore_open/services/sparse_location_logger.dart';
import 'package:pointycastle/export.dart'; import 'package:pointycastle/export.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter_blue_plus/flutter_blue_plus.dart'; import 'package:flutter_blue_plus/flutter_blue_plus.dart';
@@ -37,42 +39,6 @@ class MeshCoreUuids {
static const String txCharacteristic = "6e400003-b5a3-f393-e0a9-e50e24dcca9e"; static const String txCharacteristic = "6e400003-b5a3-f393-e0a9-e50e24dcca9e";
} }
class DirectRepeater {
static const int maxAgeMinutes = 30; // Max age for direct repeater info
final int pubkeyFirstByte;
double snr;
DateTime lastUpdated;
DirectRepeater({
required this.pubkeyFirstByte,
required this.snr,
DateTime? lastUpdated,
}) : lastUpdated = lastUpdated ?? DateTime.now();
void update(double newSNR) {
snr = newSNR;
lastUpdated = DateTime.now();
}
int get ranking {
if (isStale()) {
return -1; // Stale repeaters get lowest rank
}
// Higher SNR gets higher rank and recency within maxAgeMinutes breaks ties.
final ageMs =
DateTime.now().millisecondsSinceEpoch -
lastUpdated.millisecondsSinceEpoch;
final maxAgeMs = maxAgeMinutes * 60 * 1000;
final recencyScore = (maxAgeMs - ageMs).clamp(0, maxAgeMs);
return ((snr - 31.75) * 1000).round() + recencyScore;
}
bool isStale() {
return DateTime.now().difference(lastUpdated) >
const Duration(minutes: maxAgeMinutes);
}
}
enum MeshCoreConnectionState { enum MeshCoreConnectionState {
disconnected, disconnected,
scanning, scanning,
@@ -129,7 +95,6 @@ class MeshCoreConnector extends ChangeNotifier {
int? _batteryMillivolts; int? _batteryMillivolts;
double? _selfLatitude; double? _selfLatitude;
double? _selfLongitude; double? _selfLongitude;
final List<DirectRepeater> _directRepeaters = List.empty(growable: true);
bool _isLoadingContacts = false; bool _isLoadingContacts = false;
bool _isLoadingChannels = false; bool _isLoadingChannels = false;
bool _hasLoadedChannels = false; bool _hasLoadedChannels = false;
@@ -167,6 +132,7 @@ class MeshCoreConnector extends ChangeNotifier {
PathHistoryService? _pathHistoryService; PathHistoryService? _pathHistoryService;
AppSettingsService? _appSettingsService; AppSettingsService? _appSettingsService;
BackgroundService? _backgroundService; BackgroundService? _backgroundService;
SparseLocationLogger? _sparseLocationLogger;
final NotificationService _notificationService = NotificationService(); final NotificationService _notificationService = NotificationService();
BleDebugLogService? _bleDebugLogService; BleDebugLogService? _bleDebugLogService;
AppDebugLogService? _appDebugLogService; AppDebugLogService? _appDebugLogService;
@@ -231,7 +197,6 @@ class MeshCoreConnector extends ChangeNotifier {
String? get selfName => _selfName; String? get selfName => _selfName;
double? get selfLatitude => _selfLatitude; double? get selfLatitude => _selfLatitude;
double? get selfLongitude => _selfLongitude; double? get selfLongitude => _selfLongitude;
List<DirectRepeater> get directRepeaters => _directRepeaters;
int? get currentTxPower => _currentTxPower; int? get currentTxPower => _currentTxPower;
int? get maxTxPower => _maxTxPower; int? get maxTxPower => _maxTxPower;
int? get currentFreqHz => _currentFreqHz; int? get currentFreqHz => _currentFreqHz;
@@ -540,6 +505,7 @@ class MeshCoreConnector extends ChangeNotifier {
BleDebugLogService? bleDebugLogService, BleDebugLogService? bleDebugLogService,
AppDebugLogService? appDebugLogService, AppDebugLogService? appDebugLogService,
BackgroundService? backgroundService, BackgroundService? backgroundService,
SparseLocationLogger? sparseLocationLogger,
}) { }) {
_retryService = retryService; _retryService = retryService;
_pathHistoryService = pathHistoryService; _pathHistoryService = pathHistoryService;
@@ -547,11 +513,14 @@ class MeshCoreConnector extends ChangeNotifier {
_bleDebugLogService = bleDebugLogService; _bleDebugLogService = bleDebugLogService;
_appDebugLogService = appDebugLogService; _appDebugLogService = appDebugLogService;
_backgroundService = backgroundService; _backgroundService = backgroundService;
_sparseLocationLogger = sparseLocationLogger;
// Initialize notification service // Initialize notification service
_notificationService.initialize(); _notificationService.initialize();
_loadChannelOrder(); _loadChannelOrder();
_sparseLocationLogger?.initialize(_updateLocationandAdvert);
// Initialize retry service callbacks // Initialize retry service callbacks
_retryService?.initialize( _retryService?.initialize(
sendMessageCallback: _sendMessageDirect, sendMessageCallback: _sendMessageDirect,
@@ -866,6 +835,8 @@ class MeshCoreConnector extends ChangeNotifier {
return result; return result;
} }
SparseLocationLogger? get sparseLocationLogger => _sparseLocationLogger;
bool get _shouldAutoReconnect => !_manualDisconnect && _lastDeviceId != null; bool get _shouldAutoReconnect => !_manualDisconnect && _lastDeviceId != null;
void _cancelReconnectTimer() { void _cancelReconnectTimer() {
@@ -1730,7 +1701,7 @@ class MeshCoreConnector extends ChangeNotifier {
break; break;
case pushCodeNewAdvert: case pushCodeNewAdvert:
debugPrint('Got New CONTACT'); debugPrint('Got New CONTACT');
// It's the same format as respCodeContact, so we can reuse the handler // It the same format as respCodeContact, so we can reuse the handler
_handleContact(frame); _handleContact(frame);
break; break;
case respCodeContact: case respCodeContact:
@@ -2048,10 +2019,6 @@ class MeshCoreConnector extends ChangeNotifier {
} }
void _handleContactAdvert(Contact contact) { void _handleContactAdvert(Contact contact) {
if (listEquals(contact.publicKey, _selfPublicKey)) {
return;
}
if (contact.type == advTypeRepeater) { if (contact.type == advTypeRepeater) {
_contactUnreadCount.remove(contact.publicKeyHex); _contactUnreadCount.remove(contact.publicKeyHex);
_unreadStore.saveContactUnreadCount( _unreadStore.saveContactUnreadCount(
@@ -3380,11 +3347,7 @@ class MeshCoreConnector extends ChangeNotifier {
void _handleCustomVars(Uint8List frame) { void _handleCustomVars(Uint8List frame) {
final buf = BufferReader(frame.sublist(1)); final buf = BufferReader(frame.sublist(1));
try {
_currentCustomVars = _parseKeyValueString(buf.readString()); _currentCustomVars = _parseKeyValueString(buf.readString());
} catch (e) {
appLogger.warn('Malformed custom vars frame: $e', tag: 'Connector');
}
} }
void _setState(MeshCoreConnectionState newState) { void _setState(MeshCoreConnectionState newState) {
@@ -3409,98 +3372,97 @@ class MeshCoreConnector extends ChangeNotifier {
super.dispose(); super.dispose();
} }
_updateLocationandAdvert(Position position) async {
final snapToGridCenter = _sparseLocationLogger?.snapToGridCenter(
position: position,
cellSizeMeters: 0.001,
);
double lat = snapToGridCenter?.latitude ?? 0.0;
double lon = snapToGridCenter?.longitude ?? 0.0;
if (lat == 0.0 && lon == 0.0) {
debugPrint('Invalid location (0,0), skipping advert');
return;
}
await sendFrame(buildSetOtherParamsFrame(true, 1, 1, 0));
await setNodeLocation(lat: lat, lon: lon);
await sendSelfAdvert(flood: true);
_selfLatitude = lat;
_selfLongitude = lon;
notifyListeners();
}
void _handleRxData(Uint8List frame) { void _handleRxData(Uint8List frame) {
final packet = BufferReader(frame); final packet = BufferReader(frame);
try { packet.skipBytes(3); // Skip frame type byte
packet.skipBytes(1); // Skip frame type byte //final snr = packet.readByte() / 4.0;
final snr = packet.readInt8() / 4.0;
packet.skipBytes(1); // Skip RSSI byte
//final rssi = packet.readByte(); //final rssi = packet.readByte();
final header = packet.readByte(); final header = packet.readByte();
final routeType = header & 0x03; //final routeType = header & 0x03;
final payloadType = (header >> 2) & 0x0F; final payloadType = (header >> 2) & 0x0F;
//final payloadVer = (header >> 6) & 0x03; //final payloadVer = (header >> 6) & 0x03;
if (packet.remaining <= 0) return;
final pathLen = packet.readByte(); final pathLen = packet.readByte();
if (packet.remaining < pathLen) return;
final pathBytes = packet.readBytes(pathLen); final pathBytes = packet.readBytes(pathLen);
if (packet.remaining <= 0) return;
final payload = packet.readBytes(packet.remaining); final payload = packet.readBytes(packet.remaining);
switch (payloadType) { switch (payloadType) {
case payloadTypeADVERT: case payloadTypeADVERT:
_handlePayloadAdvertReceived(payload, pathBytes, routeType, snr); _handlePayloadAdvertReceived(payload, pathBytes);
break; break;
default: default:
} }
} catch (e) {
appLogger.warn('Malformed RX frame: $e', tag: 'Connector');
}
} }
void _handlePayloadAdvertReceived( void _handlePayloadAdvertReceived(Uint8List frame, Uint8List path) {
Uint8List frame,
Uint8List path,
int routeType,
double snr,
) {
final advert = BufferReader(frame); final advert = BufferReader(frame);
double latitude = 0.0; if (advert.remaining <= 32) return;
double longitude = 0.0; final publicKey = advert.readBytes(32);
String name = ''; final contactKeyHex = publicKey
String contactKeyHex = '';
Uint8List publicKey = Uint8List(0);
int type = 0;
int timestamp = 0;
bool hasLocation = false;
bool hasName = false;
try {
publicKey = advert.readBytes(32);
contactKeyHex = publicKey
.map((b) => b.toRadixString(16).padLeft(2, '0')) .map((b) => b.toRadixString(16).padLeft(2, '0'))
.join(); .join();
if (advert.remaining <= 4) return;
timestamp = advert.readInt32LE(); final timestamp = advert.readInt32LE();
//TODO add signature verification if (advert.remaining <= 64) return;
advert.skipBytes(64); // Skip signature for now advert.skipBytes(64); // Skip signature for now
if (advert.remaining <= 1) return;
final flags = advert.readByte(); final flags = advert.readByte();
type = flags & 0x0F; final type = flags & 0x0F;
hasLocation = (flags & 0x10) != 0; final hasLocation = (flags & 0x10) != 0;
// For future use:
//final hasFeature1 = (flags & 0x20) != 0; //final hasFeature1 = (flags & 0x20) != 0;
//final hasFeature2 = (flags & 0x40) != 0; //final hasFeature2 = (flags & 0x40) != 0;
hasName = (flags & 0x80) != 0; final hasName = (flags & 0x80) != 0;
double latitude = 0.0;
double longitude = 0.0;
if (hasLocation && advert.remaining >= 8) { if (hasLocation && advert.remaining >= 8) {
latitude = advert.readInt32LE() / 1e6; latitude = advert.readInt32LE() / 1e6;
longitude = advert.readInt32LE() / 1e6; longitude = advert.readInt32LE() / 1e6;
} }
String name = '';
if (hasName && advert.remaining > 0) { if (hasName && advert.remaining > 0) {
name = advert.readString(); name = advert.readString();
} }
} catch (e) {
appLogger.warn('Malformed advert frame: $e', tag: 'Connector');
return;
}
if (listEquals(publicKey, _selfPublicKey)) {
return;
}
// Check if this is a new contact // Check if this is a new contact
final isNewContact = !_knownContactKeys.contains(contactKeyHex); final isNewContact = !_knownContactKeys.contains(contactKeyHex);
if (isNewContact) { if (isNewContact) {
final newContact = Contact( _handleContactAdvert(
Contact(
publicKey: publicKey, publicKey: publicKey,
name: name, name: name,
type: type, type: type,
pathLength: path.length, pathLength: path.length,
path: Uint8List.fromList( path: path,
path.reversed.toList(),
), // Store path in reverse for easier use in outgoing messages
latitude: latitude, latitude: latitude,
longitude: longitude, longitude: longitude,
lastSeen: DateTime.fromMillisecondsSinceEpoch(timestamp * 1000), lastSeen: DateTime.fromMillisecondsSinceEpoch(timestamp * 1000),
),
); );
_handleContactAdvert(newContact);
_updateDirectRepeater(newContact, snr, path);
return; return;
} }
@@ -3524,7 +3486,7 @@ class MeshCoreConnector extends ChangeNotifier {
latitude: hasLocation ? latitude : existing.latitude, latitude: hasLocation ? latitude : existing.latitude,
longitude: hasLocation ? longitude : existing.longitude, longitude: hasLocation ? longitude : existing.longitude,
name: hasName ? name : existing.name, name: hasName ? name : existing.name,
path: Uint8List.fromList(path.reversed.toList()), path: path,
pathLength: path.length, pathLength: path.length,
lastMessageAt: mergedLastMessageAt, lastMessageAt: mergedLastMessageAt,
lastSeen: DateTime.fromMillisecondsSinceEpoch(timestamp * 1000), lastSeen: DateTime.fromMillisecondsSinceEpoch(timestamp * 1000),
@@ -3532,48 +3494,12 @@ class MeshCoreConnector extends ChangeNotifier {
pathOverrideBytes: existing.pathOverrideBytes, pathOverrideBytes: existing.pathOverrideBytes,
); );
// Add path to history if we have a valid path
if (_pathHistoryService != null &&
_contacts[existingIndex].pathLength >= 0) {
_pathHistoryService!.handlePathUpdated(_contacts[existingIndex]);
}
_updateDirectRepeater(_contacts[existingIndex], snr, path);
appLogger.info( appLogger.info(
'After merge: pathOverride=${_contacts[existingIndex].pathOverride}, devicePath=${_contacts[existingIndex].pathLength}', 'After merge: pathOverride=${_contacts[existingIndex].pathOverride}, devicePath=${_contacts[existingIndex].pathLength}',
tag: 'Connector', tag: 'Connector',
); );
}
}
void _updateDirectRepeater(Contact contact, double snr, Uint8List path) {
final pubkeyFirstByte = path.isNotEmpty
? path.last
: contact.publicKey.first;
_directRepeaters.removeWhere((r) => r.isStale());
//We can use adverts from chat and sensor nodes, but only if the advert has a path to get the last hop.
if ((contact.type == advTypeChat || contact.type == advTypeSensor) &&
path.isEmpty) {
notifyListeners(); notifyListeners();
return;
} }
final isTracked = _directRepeaters.where(
(r) => r.pubkeyFirstByte == pubkeyFirstByte,
);
if (isTracked.isNotEmpty) {
final repeater = isTracked.first;
repeater.update(snr);
} else if (_directRepeaters.length < 5) {
_directRepeaters.add(
DirectRepeater(pubkeyFirstByte: pubkeyFirstByte, snr: snr),
);
}
notifyListeners();
} }
} }
+1 -11
View File
@@ -13,22 +13,12 @@ class BufferReader {
int readByte() => readBytes(1)[0]; int readByte() => readBytes(1)[0];
Uint8List readBytes(int count) { Uint8List readBytes(int count) {
if (_pointer + count > _buffer.length) {
throw RangeError(
'Attempted to read $count bytes at offset $_pointer, but only $remaining bytes remaining in buffer of length ${_buffer.length}',
);
}
final data = _buffer.sublist(_pointer, _pointer + count); final data = _buffer.sublist(_pointer, _pointer + count);
_pointer += count; _pointer += count;
return data; return data;
} }
void skipBytes(int count) { void skipBytes(int count) {
if (_pointer + count > _buffer.length) {
throw RangeError(
'Attempted to skip $count bytes at offset $_pointer, but only $remaining bytes remaining in buffer of length ${_buffer.length}',
);
}
_pointer += count; _pointer += count;
} }
@@ -177,7 +167,7 @@ const int reqTypeGetStatus = 0x01;
const int reqTypeKeepAlive = 0x02; const int reqTypeKeepAlive = 0x02;
const int reqTypeGetTelemetry = 0x03; const int reqTypeGetTelemetry = 0x03;
const int reqTypeGetAccessList = 0x05; const int reqTypeGetAccessList = 0x05;
const int reqTypeGetNeighbors = 0x06; const int reqTypeGetNeighbours = 0x06;
// Repeater response codes // Repeater response codes
const int respServerLoginOk = 0; const int respServerLoginOk = 0;
+5 -19
View File
@@ -1,6 +1,4 @@
import 'dart:typed_data'; import 'dart:typed_data';
import 'package:meshcore_open/utils/app_logger.dart';
import '../connector/meshcore_protocol.dart'; import '../connector/meshcore_protocol.dart';
class CayenneLpp { class CayenneLpp {
@@ -86,7 +84,7 @@ class CayenneLpp {
static List<Map<String, dynamic>> parse(Uint8List bytes) { static List<Map<String, dynamic>> parse(Uint8List bytes) {
final buffer = BufferReader(bytes); final buffer = BufferReader(bytes);
final telemetry = <Map<String, dynamic>>[]; final telemetry = <Map<String, dynamic>>[];
try {
while (buffer.remaining >= 2) { while (buffer.remaining >= 2) {
final channel = buffer.readUInt8(); final channel = buffer.readUInt8();
final type = buffer.readUInt8(); final type = buffer.readUInt8();
@@ -188,19 +186,14 @@ class CayenneLpp {
return telemetry; return telemetry;
} }
} }
return telemetry; return telemetry;
} catch (e) {
// Handle parsing errors, possibly due to malformed data
appLogger.error('Error parsing Cayenne LPP data: $e');
// Return any telemetry parsed so far to preserve partial data
return telemetry;
}
} }
static List<Map<String, dynamic>> parseByChannel(Uint8List bytes) { static List<Map<String, dynamic>> parseByChannel(Uint8List bytes) {
final buffer = BufferReader(bytes); final buffer = BufferReader(bytes);
final Map<int, Map<String, dynamic>> channels = {}; final Map<int, Map<String, dynamic>> channels = {};
try {
while (buffer.remaining >= 2) { while (buffer.remaining >= 2) {
final channel = buffer.readUInt8(); final channel = buffer.readUInt8();
final type = buffer.readUInt8(); final type = buffer.readUInt8();
@@ -258,20 +251,13 @@ class CayenneLpp {
break; break;
// Add more types as needed... // Add more types as needed...
default: default:
//Stopped parsing to avoid misalignment // Unknown type: skip or handle error?
return channels.values.toList(); continue;
} }
} }
final List<Map<String, dynamic>> channelsOut = channels.values.toList(); final List<Map<String, dynamic>> channelsOut = channels.values.toList();
channelsOut.sort((a, b) => a['channel'].compareTo(b['channel'])); channelsOut.sort((a, b) => a['channel'].compareTo(b['channel']));
return channelsOut; return channelsOut;
} catch (e) {
// Handle parsing errors, possibly due to malformed data
appLogger.error('Error parsing Cayenne LPP data: $e');
return <
Map<String, dynamic>
>[]; // Return an empty list on error to avoid crashing the app
}
} }
} }
+6 -14
View File
@@ -1356,12 +1356,12 @@
} }
} }
}, },
"repeater_neighborsSubtitle": "Преглед на съседни възли с нулев скок.", "repeater_neighboursSubtitle": "Преглед на съседни възли с нулев скок.",
"repeater_neighbors": "Съседи", "repeater_neighbours": "Съседи",
"neighbors_receivedData": "Получени данни за съседи", "neighbors_receivedData": "Получени данни за съседи",
"neighbors_requestTimedOut": "Съседите поискат изтичане на време.", "neighbors_requestTimedOut": "Съседите поискат изтичане на време.",
"neighbors_errorLoading": "Грешка при зареждане на съседи: {error}", "neighbors_errorLoading": "Грешка при зареждане на съседи: {error}",
"neighbors_repeatersNeighbors": "Повторители Съседи", "neighbors_repeatersNeighbours": "Повторители Съседи",
"neighbors_noData": "Няма налични данни за съседи.", "neighbors_noData": "Няма налични данни за съседи.",
"channels_createPrivateChannel": "Създай Частен Канал", "channels_createPrivateChannel": "Създай Частен Канал",
"channels_joinPrivateChannel": "Присъедини се към Частен Канал", "channels_joinPrivateChannel": "Присъедини се към Частен Канал",
@@ -1575,6 +1575,7 @@
"notification_newNodesCount": "{count} {count, plural, =1{нов възел} other{нови възли}}", "notification_newNodesCount": "{count} {count, plural, =1{нов възел} other{нови възли}}",
"notification_newTypeDiscovered": "Открит нов {contactType}", "notification_newTypeDiscovered": "Открит нов {contactType}",
"notification_receivedNewMessage": "Получено ново съобщение", "notification_receivedNewMessage": "Получено ново съобщение",
"contacts_contactAdvertCopyFailed": "Копирането на обявата в клипборда не успя.",
"settings_gpxExportContactsSubtitle": "Експортира спътници с местоположение в GPX файл.", "settings_gpxExportContactsSubtitle": "Експортира спътници с местоположение в GPX файл.",
"settings_gpxExportRepeatersSubtitle": "Изпраща повторители / roomserver с местоположение в GPX файл.", "settings_gpxExportRepeatersSubtitle": "Изпраща повторители / roomserver с местоположение в GPX файл.",
"settings_gpxExportAll": "Експортирай всички контакти в GPX", "settings_gpxExportAll": "Експортирай всички контакти в GPX",
@@ -1590,15 +1591,6 @@
"settings_gpxExportAllContacts": "Местоположения на всички контакти", "settings_gpxExportAllContacts": "Местоположения на всички контакти",
"settings_gpxExportShareText": "Картинни данни изнесени от meshcore-open", "settings_gpxExportShareText": "Картинни данни изнесени от meshcore-open",
"settings_gpxExportShareSubject": "meshcore-open износ на данни за карта в формат GPX", "settings_gpxExportShareSubject": "meshcore-open износ на данни за карта в формат GPX",
"pathTrace_someHopsNoLocation": "Един или повече от хмелите липсва местоположение!", "pathTrace_someHopsNoLocation": "Един или повече от хмелите липсва местоположение!"
"map_pathTraceCancelled": "Отменен е следването на пътя.",
"pathTrace_clearTooltip": "Изчисти пътя",
"map_removeLast": "Премахни Последно",
"map_runTrace": "Изпълни Път на Следване",
"map_tapToAdd": "Натиснете върху възлите, за да ги добавите към пътя.",
"scanner_bluetoothOff": "Bluetooth е изключен.",
"scanner_enableBluetooth": "Активирайте Bluetooth",
"scanner_bluetoothOffMessage": "Моля, активирайте Bluetooth, за да сканирате за устройства.",
"snrIndicator_lastSeen": "Последно видян",
"snrIndicator_nearByRepeaters": "Близки повтарящи се устройства"
} }
+11 -26
View File
@@ -1356,12 +1356,12 @@
} }
} }
}, },
"repeater_neighbors": "Nachbarn", "repeater_neighbours": "Nachbarn",
"repeater_neighborsSubtitle": "Anzahl der Hop-Nachbarn anzeigen.", "repeater_neighboursSubtitle": "Anzahl der Hop-Nachbarn anzeigen.",
"neighbors_receivedData": "Empfangene Nachbarsdaten", "neighbors_receivedData": "Empfangene Nachbarsdaten",
"neighbors_requestTimedOut": "Anfrage durch Timeout fehlgeschlagen.", "neighbors_requestTimedOut": "Anfrage durch Timeout fehlgeschlagen.",
"neighbors_errorLoading": "Fehler beim Laden der Nachbarn: {error}", "neighbors_errorLoading": "Fehler beim Laden der Nachbarn: {error}",
"neighbors_repeatersNeighbors": "Nachbarn", "neighbors_repeatersNeighbours": "Nachbarn",
"neighbors_noData": "Keine Nachbarsdaten verfügbar.", "neighbors_noData": "Keine Nachbarsdaten verfügbar.",
"channels_joinPrivateChannel": "Treten Sie einem privaten Kanal bei", "channels_joinPrivateChannel": "Treten Sie einem privaten Kanal bei",
"channels_joinPrivateChannelDesc": "Manuelle Eingabe eines geheimen Schlüssels.", "channels_joinPrivateChannelDesc": "Manuelle Eingabe eines geheimen Schlüssels.",
@@ -1569,40 +1569,34 @@
"contacts_zeroHopContactAdvertSent": "Kontakt über Anzeige gesendet", "contacts_zeroHopContactAdvertSent": "Kontakt über Anzeige gesendet",
"contacts_contactAdvertCopied": "Anzeige in die Zwischenablage kopiert.", "contacts_contactAdvertCopied": "Anzeige in die Zwischenablage kopiert.",
"contacts_contactAdvertCopyFailed": "Kopieren der Ankündigung in die Zwischenablage fehlgeschlagen.", "contacts_contactAdvertCopyFailed": "Kopieren der Ankündigung in die Zwischenablage fehlgeschlagen.",
"notification_activityTitle": "MeshCore Aktivität", "notification_activityTitle": "MeshCore Aktivität",
"notification_messagesCount": "{count} {count, plural, =1{Nachricht} other{Nachrichten}}", "notification_messagesCount": "{count} {count, plural, =1{Nachricht} other{Nachrichten}}",
"@notification_messagesCount": { "@notification_messagesCount": {
"placeholders": { "placeholders": {
"count": { "count": {"type": "int"}
"type": "int"
}
} }
}, },
"notification_channelMessagesCount": "{count} {count, plural, =1{Kanalnachricht} other{Kanalnachrichten}}", "notification_channelMessagesCount": "{count} {count, plural, =1{Kanalnachricht} other{Kanalnachrichten}}",
"@notification_channelMessagesCount": { "@notification_channelMessagesCount": {
"placeholders": { "placeholders": {
"count": { "count": {"type": "int"}
"type": "int"
}
} }
}, },
"notification_newNodesCount": "{count} {count, plural, =1{neuer Knoten} other{neue Knoten}}", "notification_newNodesCount": "{count} {count, plural, =1{neuer Knoten} other{neue Knoten}}",
"@notification_newNodesCount": { "@notification_newNodesCount": {
"placeholders": { "placeholders": {
"count": { "count": {"type": "int"}
"type": "int"
}
} }
}, },
"notification_newTypeDiscovered": "Neuer {contactType} entdeckt", "notification_newTypeDiscovered": "Neuer {contactType} entdeckt",
"@notification_newTypeDiscovered": { "@notification_newTypeDiscovered": {
"placeholders": { "placeholders": {
"contactType": { "contactType": {"type": "String"}
"type": "String"
}
} }
}, },
"notification_receivedNewMessage": "Neue Nachricht empfangen", "notification_receivedNewMessage": "Neue Nachricht empfangen",
"contacts_contactAdvertCopyFailed": "Kopieren der Ankündigung in die Zwischenablage fehlgeschlagen.",
"settings_gpxExportAll": "Alle Knoten als GPX exportieren", "settings_gpxExportAll": "Alle Knoten als GPX exportieren",
"settings_gpxExportAllSubtitle": "Exportiert alle Knoten mit einem Standort in eine GPX-Datei.", "settings_gpxExportAllSubtitle": "Exportiert alle Knoten mit einem Standort in eine GPX-Datei.",
"settings_gpxExportRepeaters": "Repeater und Raumserver als GPX exportieren", "settings_gpxExportRepeaters": "Repeater und Raumserver als GPX exportieren",
@@ -1618,15 +1612,6 @@
"settings_gpxExportAllContacts": "Alle Kontaktstandorte", "settings_gpxExportAllContacts": "Alle Kontaktstandorte",
"settings_gpxExportShareSubject": "GPX-Kartendaten aus meshcore-open exportieren", "settings_gpxExportShareSubject": "GPX-Kartendaten aus meshcore-open exportieren",
"settings_gpxExportShareText": "GPX-Kartendaten aus meshcore-open exportiert", "settings_gpxExportShareText": "GPX-Kartendaten aus meshcore-open exportiert",
"pathTrace_someHopsNoLocation": "Bei einer oder mehreren Knoten fehlt der Standort!", "pathTrace_someHopsNoLocation": "Bei einer oder mehreren Knoten fehlt der Standort!"
"map_removeLast": "Letztes Entfernen",
"map_tapToAdd": "Tippen Sie auf Knoten, um sie zum Pfad hinzuzufügen.",
"map_runTrace": "Pfadverlauf ausführen",
"pathTrace_clearTooltip": "Pfad löschen",
"map_pathTraceCancelled": "Pfadverfolgung abgebrochen.",
"scanner_bluetoothOffMessage": "Bitte aktivieren Sie Bluetooth, um nach Geräten zu suchen.",
"scanner_bluetoothOff": "Bluetooth ist deaktiviert.",
"scanner_enableBluetooth": "Bluetooth aktivieren",
"snrIndicator_lastSeen": "Zuletzt gesehen",
"snrIndicator_nearByRepeaters": "In der Nähe befindliche Repeater"
} }
+8 -17
View File
@@ -66,9 +66,6 @@
}, },
"scanner_stop": "Stop", "scanner_stop": "Stop",
"scanner_scan": "Scan", "scanner_scan": "Scan",
"scanner_bluetoothOff": "Bluetooth is off",
"scanner_bluetoothOffMessage": "Please turn on Bluetooth to scan for devices",
"scanner_enableBluetooth": "Enable Bluetooth",
"device_quickSwitch": "Quick switch", "device_quickSwitch": "Quick switch",
"device_meshcore": "MeshCore", "device_meshcore": "MeshCore",
@@ -622,10 +619,7 @@
"map_sharedPin": "Shared pin", "map_sharedPin": "Shared pin",
"map_joinRoom": "Join Room", "map_joinRoom": "Join Room",
"map_manageRepeater": "Manage Repeater", "map_manageRepeater": "Manage Repeater",
"map_tapToAdd": "Tap on nodes to add them to the path.", "map_updateMyLocation": "Update Location",
"map_runTrace": "Run Path Trace",
"map_removeLast": "Remove Last",
"map_pathTraceCancelled": "Path trace cancelled.",
"mapCache_title": "Offline Map Cache", "mapCache_title": "Offline Map Cache",
"mapCache_selectAreaFirst": "Select an area to cache first", "mapCache_selectAreaFirst": "Select an area to cache first",
"mapCache_noTilesToDownload": "No tiles to download for this area", "mapCache_noTilesToDownload": "No tiles to download for this area",
@@ -793,8 +787,8 @@
"repeater_telemetrySubtitle": "View telemetry of sensors and system stats", "repeater_telemetrySubtitle": "View telemetry of sensors and system stats",
"repeater_cli": "CLI", "repeater_cli": "CLI",
"repeater_cliSubtitle": "Send commands to the repeater", "repeater_cliSubtitle": "Send commands to the repeater",
"repeater_neighbors": "Neighbors", "repeater_neighbours": "Neighbors",
"repeater_neighborsSubtitle": "View zero hop neighbors.", "repeater_neighboursSubtitle": "View zero hop neighbors.",
"repeater_settings": "Settings", "repeater_settings": "Settings",
"repeater_settingsSubtitle": "Configure repeater parameters", "repeater_settingsSubtitle": "Configure repeater parameters",
@@ -1098,16 +1092,16 @@
} }
}, },
"neighbors_receivedData": "Received Neighbors Data", "neighbors_receivedData": "Received Neighbours Data",
"neighbors_requestTimedOut": "Neighbors request timed out.", "neighbors_requestTimedOut": "Neighbours request timed out.",
"neighbors_errorLoading": "Error loading neighbors: {error}", "neighbors_errorLoading": "Error loading neighbors: {error}",
"@neighbors_errorLoading": { "@neighbors_errorLoading": {
"placeholders": { "placeholders": {
"error": {"type": "String"} "error": {"type": "String"}
} }
}, },
"neighbors_repeatersNeighbors": "Repeaters Neighbors", "neighbors_repeatersNeighbours": "Repeaters Neighbours",
"neighbors_noData": "No neighbors data available.", "neighbors_noData": "No neighbours data available.",
"neighbors_unknownContact": "Unknown {pubkey}", "neighbors_unknownContact": "Unknown {pubkey}",
"@neighbors_unknownContact": { "@neighbors_unknownContact": {
"placeholders": { "placeholders": {
@@ -1324,7 +1318,6 @@
"pathTrace_notAvailable": "Path trace not available.", "pathTrace_notAvailable": "Path trace not available.",
"pathTrace_refreshTooltip": "Refresh Path Trace.", "pathTrace_refreshTooltip": "Refresh Path Trace.",
"pathTrace_someHopsNoLocation": "One or more of the hops is missing a location!", "pathTrace_someHopsNoLocation": "One or more of the hops is missing a location!",
"pathTrace_clearTooltip": "Clear path.",
"contacts_pathTrace": "Path Trace", "contacts_pathTrace": "Path Trace",
"contacts_ping": "Ping", "contacts_ping": "Ping",
"contacts_repeaterPathTrace": "Path trace to repeater", "contacts_repeaterPathTrace": "Path trace to repeater",
@@ -1395,7 +1388,5 @@
"settings_gpxExportChat": "Companion locations", "settings_gpxExportChat": "Companion locations",
"settings_gpxExportAllContacts": "All contacts locations", "settings_gpxExportAllContacts": "All contacts locations",
"settings_gpxExportShareText": "Map data exported from meshcore-open", "settings_gpxExportShareText": "Map data exported from meshcore-open",
"settings_gpxExportShareSubject": "meshcore-open GPX map data export", "settings_gpxExportShareSubject": "meshcore-open GPX map data export"
"snrIndicator_nearByRepeaters": "Nearby Repeaters",
"snrIndicator_lastSeen": "Last seen"
} }
+11 -26
View File
@@ -1356,12 +1356,12 @@
} }
} }
}, },
"repeater_neighbors": "Vecinos", "repeater_neighbours": "Vecinos",
"repeater_neighborsSubtitle": "Ver vecinos de salto cero.", "repeater_neighboursSubtitle": "Ver vecinos de salto cero.",
"neighbors_receivedData": "Recibidas Datos de Vecinos", "neighbors_receivedData": "Recibidas Datos de Vecinos",
"neighbors_requestTimedOut": "Los vecinos solicitan que se desconecte.", "neighbors_requestTimedOut": "Los vecinos solicitan que se desconecte.",
"neighbors_errorLoading": "Error al cargar vecinos: {error}", "neighbors_errorLoading": "Error al cargar vecinos: {error}",
"neighbors_repeatersNeighbors": "Repetidores Vecinos", "neighbors_repeatersNeighbours": "Repetidores Vecinos",
"neighbors_noData": "No hay datos de vecinos disponibles.", "neighbors_noData": "No hay datos de vecinos disponibles.",
"channels_joinPrivateChannel": "Únete a un Canal Privado", "channels_joinPrivateChannel": "Únete a un Canal Privado",
"channels_createPrivateChannel": "Crear un Canal Privado", "channels_createPrivateChannel": "Crear un Canal Privado",
@@ -1569,40 +1569,34 @@
"contacts_zeroHopContactAdvertSent": "Envió contacto por anuncio.", "contacts_zeroHopContactAdvertSent": "Envió contacto por anuncio.",
"contacts_contactAdvertCopied": "Anuncio copiado al Portapapeles.", "contacts_contactAdvertCopied": "Anuncio copiado al Portapapeles.",
"contacts_contactAdvertCopyFailed": "Copiar anuncio al Portapapeles ha fallado.", "contacts_contactAdvertCopyFailed": "Copiar anuncio al Portapapeles ha fallado.",
"notification_activityTitle": "Actividad de MeshCore", "notification_activityTitle": "Actividad de MeshCore",
"notification_messagesCount": "{count} {count, plural, =1{mensaje} other{mensajes}}", "notification_messagesCount": "{count} {count, plural, =1{mensaje} other{mensajes}}",
"@notification_messagesCount": { "@notification_messagesCount": {
"placeholders": { "placeholders": {
"count": { "count": {"type": "int"}
"type": "int"
}
} }
}, },
"notification_channelMessagesCount": "{count} {count, plural, =1{mensaje de canal} other{mensajes de canal}}", "notification_channelMessagesCount": "{count} {count, plural, =1{mensaje de canal} other{mensajes de canal}}",
"@notification_channelMessagesCount": { "@notification_channelMessagesCount": {
"placeholders": { "placeholders": {
"count": { "count": {"type": "int"}
"type": "int"
}
} }
}, },
"notification_newNodesCount": "{count} {count, plural, =1{nuevo nodo} other{nuevos nodos}}", "notification_newNodesCount": "{count} {count, plural, =1{nuevo nodo} other{nuevos nodos}}",
"@notification_newNodesCount": { "@notification_newNodesCount": {
"placeholders": { "placeholders": {
"count": { "count": {"type": "int"}
"type": "int"
}
} }
}, },
"notification_newTypeDiscovered": "Nuevo {contactType} descubierto", "notification_newTypeDiscovered": "Nuevo {contactType} descubierto",
"@notification_newTypeDiscovered": { "@notification_newTypeDiscovered": {
"placeholders": { "placeholders": {
"contactType": { "contactType": {"type": "String"}
"type": "String"
}
} }
}, },
"notification_receivedNewMessage": "Nuevo mensaje recibido", "notification_receivedNewMessage": "Nuevo mensaje recibido",
"contacts_contactAdvertCopyFailed": "Copiar anuncio al Portapapeles ha fallado.",
"settings_gpxExportContactsSubtitle": "Exporta compañeros con una ubicación a archivo GPX.", "settings_gpxExportContactsSubtitle": "Exporta compañeros con una ubicación a archivo GPX.",
"settings_gpxExportRepeaters": "Exportar repetidores / servidor de sala a GPX", "settings_gpxExportRepeaters": "Exportar repetidores / servidor de sala a GPX",
"settings_gpxExportSuccess": "Archivo GPX exportado con éxito.", "settings_gpxExportSuccess": "Archivo GPX exportado con éxito.",
@@ -1618,15 +1612,6 @@
"settings_gpxExportAllContacts": "Todas las ubicaciones de contactos", "settings_gpxExportAllContacts": "Todas las ubicaciones de contactos",
"settings_gpxExportShareText": "Datos del mapa exportados desde meshcore-open", "settings_gpxExportShareText": "Datos del mapa exportados desde meshcore-open",
"settings_gpxExportShareSubject": "meshcore-open exportación de datos de mapa GPX", "settings_gpxExportShareSubject": "meshcore-open exportación de datos de mapa GPX",
"pathTrace_someHopsNoLocation": "Uno o más de los lúpulos carecen de una ubicación", "pathTrace_someHopsNoLocation": "Uno o más de los lúpulos carecen de una ubicación"
"pathTrace_clearTooltip": "Borrar ruta",
"map_runTrace": "Ejecutar Rastreo de Ruta",
"map_tapToAdd": "Pulse en los nodos para agregarlos al camino.",
"map_removeLast": "Eliminar último",
"map_pathTraceCancelled": "Rastreo de ruta cancelado.",
"scanner_bluetoothOffMessage": "Por favor, active el Bluetooth para escanear dispositivos.",
"scanner_bluetoothOff": "Bluetooth está desactivado.",
"scanner_enableBluetooth": "Habilitar Bluetooth",
"snrIndicator_nearByRepeaters": "Repetidores cercanos",
"snrIndicator_lastSeen": "Visto por última vez"
} }
+4 -14
View File
@@ -1356,12 +1356,12 @@
} }
} }
}, },
"repeater_neighbors": "Voisins", "repeater_neighbours": "Voisins",
"repeater_neighborsSubtitle": "Afficher les voisins de saut nuls.", "repeater_neighboursSubtitle": "Afficher les voisins de saut nuls.",
"neighbors_receivedData": "Données des voisins reçues", "neighbors_receivedData": "Données des voisins reçues",
"neighbors_requestTimedOut": "Les voisins demandent un délai.", "neighbors_requestTimedOut": "Les voisins demandent un délai.",
"neighbors_errorLoading": "Erreur lors du chargement des voisins : {error}", "neighbors_errorLoading": "Erreur lors du chargement des voisins : {error}",
"neighbors_repeatersNeighbors": "Répéteurs Voisins", "neighbors_repeatersNeighbours": "Répéteurs Voisins",
"neighbors_noData": "Aucune donnée concernant les voisins disponible.", "neighbors_noData": "Aucune donnée concernant les voisins disponible.",
"channels_createPrivateChannelDesc": "Sécurisé avec une clé secrète.", "channels_createPrivateChannelDesc": "Sécurisé avec une clé secrète.",
"channels_joinPrivateChannel": "Rejoindre un Canal Privé", "channels_joinPrivateChannel": "Rejoindre un Canal Privé",
@@ -1590,15 +1590,5 @@
"settings_gpxExportAllContacts": "Tous les emplacements des contacts", "settings_gpxExportAllContacts": "Tous les emplacements des contacts",
"settings_gpxExportShareText": "Données de carte exportées à partir de meshcore-open", "settings_gpxExportShareText": "Données de carte exportées à partir de meshcore-open",
"settings_gpxExportShareSubject": "meshcore-open exporter les données de carte GPX", "settings_gpxExportShareSubject": "meshcore-open exporter les données de carte GPX",
"pathTrace_someHopsNoLocation": "Un ou plusieurs des sauts manquent d'une localisation !", "pathTrace_someHopsNoLocation": "Un ou plusieurs des sauts manquent d'une localisation !"
"map_tapToAdd": "Appuyez sur les nœuds pour les ajouter au chemin.",
"pathTrace_clearTooltip": "Effacer le chemin",
"map_pathTraceCancelled": "Traçage de chemin annulé",
"map_removeLast": "Supprimer le dernier",
"map_runTrace": "Exécuter la traçage de chemin",
"scanner_bluetoothOffMessage": "Veuillez activer le Bluetooth pour rechercher des appareils.",
"scanner_bluetoothOff": "Le Bluetooth est désactivé.",
"scanner_enableBluetooth": "Activer le Bluetooth",
"snrIndicator_lastSeen": "Dernière fois vu",
"snrIndicator_nearByRepeaters": "Répéteurs à proximité"
} }
+6 -14
View File
@@ -1356,12 +1356,12 @@
} }
} }
}, },
"repeater_neighbors": "Vicini", "repeater_neighbours": "Vicini",
"repeater_neighborsSubtitle": "Visualizza vicini di salto pari a zero.", "repeater_neighboursSubtitle": "Visualizza vicini di salto pari a zero.",
"neighbors_receivedData": "Ricevute dati vicini", "neighbors_receivedData": "Ricevute dati vicini",
"neighbors_requestTimedOut": "I vicini richiedono un timeout.", "neighbors_requestTimedOut": "I vicini richiedono un timeout.",
"neighbors_errorLoading": "Errore nel caricamento dei vicini: {error}", "neighbors_errorLoading": "Errore nel caricamento dei vicini: {error}",
"neighbors_repeatersNeighbors": "Ripetitori Vicini", "neighbors_repeatersNeighbours": "Ripetitori Vicini",
"neighbors_noData": "Nessun dato sugli vicini disponibile.", "neighbors_noData": "Nessun dato sugli vicini disponibile.",
"channels_createPrivateChannel": "Crea un Canale Privato", "channels_createPrivateChannel": "Crea un Canale Privato",
"channels_createPrivateChannelDesc": "Protetta con una chiave segreta.", "channels_createPrivateChannelDesc": "Protetta con una chiave segreta.",
@@ -1575,6 +1575,7 @@
"notification_newNodesCount": "{count} {count, plural, =1{nuovo nodo} other{nuovi nodi}}", "notification_newNodesCount": "{count} {count, plural, =1{nuovo nodo} other{nuovi nodi}}",
"notification_newTypeDiscovered": "Nuovo {contactType} scoperto", "notification_newTypeDiscovered": "Nuovo {contactType} scoperto",
"notification_receivedNewMessage": "Nuovo messaggio ricevuto", "notification_receivedNewMessage": "Nuovo messaggio ricevuto",
"contacts_contactAdvertCopied": "Annuncio copiato negli Appunti.",
"settings_gpxExportRepeaters": "Esporta ripetitori / server di stanza in GPX", "settings_gpxExportRepeaters": "Esporta ripetitori / server di stanza in GPX",
"settings_gpxExportContacts": "Esporta compagni in GPX", "settings_gpxExportContacts": "Esporta compagni in GPX",
"settings_gpxExportSuccess": "Esportazione del file GPX completata con successo.", "settings_gpxExportSuccess": "Esportazione del file GPX completata con successo.",
@@ -1590,15 +1591,6 @@
"settings_gpxExportAllContacts": "Tutte le posizioni dei contatti", "settings_gpxExportAllContacts": "Tutte le posizioni dei contatti",
"settings_gpxExportShareText": "Dati mappa esportati da meshcore-open", "settings_gpxExportShareText": "Dati mappa esportati da meshcore-open",
"settings_gpxExportShareSubject": "meshcore-open esportazione dati mappa GPX", "settings_gpxExportShareSubject": "meshcore-open esportazione dati mappa GPX",
"pathTrace_someHopsNoLocation": "Uno o più dei luppoli mancano di una posizione!", "pathTrace_someHopsNoLocation": "Uno o più dei luppoli mancano di una posizione!"
"map_removeLast": "Rimuovi ultimo",
"map_pathTraceCancelled": "Tracciamento del percorso annullato.",
"pathTrace_clearTooltip": "Pulisci percorso",
"map_runTrace": "Esegui Path Trace",
"map_tapToAdd": "Tocca i nodi per aggiungerli al percorso.",
"scanner_bluetoothOff": "Il Bluetooth è disattivato.",
"scanner_bluetoothOffMessage": "Si prega di attivare il Bluetooth per effettuare la scansione dei dispositivi.",
"scanner_enableBluetooth": "Abilita il Bluetooth",
"snrIndicator_nearByRepeaters": "Ripetitori vicini",
"snrIndicator_lastSeen": "Ultimo accesso"
} }
+10 -70
View File
@@ -376,24 +376,6 @@ abstract class AppLocalizations {
/// **'Scan'** /// **'Scan'**
String get scanner_scan; String get scanner_scan;
/// No description provided for @scanner_bluetoothOff.
///
/// In en, this message translates to:
/// **'Bluetooth is off'**
String get scanner_bluetoothOff;
/// No description provided for @scanner_bluetoothOffMessage.
///
/// In en, this message translates to:
/// **'Please turn on Bluetooth to scan for devices'**
String get scanner_bluetoothOffMessage;
/// No description provided for @scanner_enableBluetooth.
///
/// In en, this message translates to:
/// **'Enable Bluetooth'**
String get scanner_enableBluetooth;
/// No description provided for @device_quickSwitch. /// No description provided for @device_quickSwitch.
/// ///
/// In en, this message translates to: /// In en, this message translates to:
@@ -2536,30 +2518,6 @@ abstract class AppLocalizations {
/// **'Manage Repeater'** /// **'Manage Repeater'**
String get map_manageRepeater; String get map_manageRepeater;
/// No description provided for @map_tapToAdd.
///
/// In en, this message translates to:
/// **'Tap on nodes to add them to the path.'**
String get map_tapToAdd;
/// No description provided for @map_runTrace.
///
/// In en, this message translates to:
/// **'Run Path Trace'**
String get map_runTrace;
/// No description provided for @map_removeLast.
///
/// In en, this message translates to:
/// **'Remove Last'**
String get map_removeLast;
/// No description provided for @map_pathTraceCancelled.
///
/// In en, this message translates to:
/// **'Path trace cancelled.'**
String get map_pathTraceCancelled;
/// No description provided for @mapCache_title. /// No description provided for @mapCache_title.
/// ///
/// In en, this message translates to: /// In en, this message translates to:
@@ -3039,17 +2997,17 @@ abstract class AppLocalizations {
/// **'Send commands to the repeater'** /// **'Send commands to the repeater'**
String get repeater_cliSubtitle; String get repeater_cliSubtitle;
/// No description provided for @repeater_neighbors. /// No description provided for @repeater_neighbours.
/// ///
/// In en, this message translates to: /// In en, this message translates to:
/// **'Neighbors'** /// **'Neighbors'**
String get repeater_neighbors; String get repeater_neighbours;
/// No description provided for @repeater_neighborsSubtitle. /// No description provided for @repeater_neighboursSubtitle.
/// ///
/// In en, this message translates to: /// In en, this message translates to:
/// **'View zero hop neighbors.'** /// **'View zero hop neighbors.'**
String get repeater_neighborsSubtitle; String get repeater_neighboursSubtitle;
/// No description provided for @repeater_settings. /// No description provided for @repeater_settings.
/// ///
@@ -4193,13 +4151,13 @@ abstract class AppLocalizations {
/// No description provided for @neighbors_receivedData. /// No description provided for @neighbors_receivedData.
/// ///
/// In en, this message translates to: /// In en, this message translates to:
/// **'Received Neighbors Data'** /// **'Received Neighbours Data'**
String get neighbors_receivedData; String get neighbors_receivedData;
/// No description provided for @neighbors_requestTimedOut. /// No description provided for @neighbors_requestTimedOut.
/// ///
/// In en, this message translates to: /// In en, this message translates to:
/// **'Neighbors request timed out.'** /// **'Neighbours request timed out.'**
String get neighbors_requestTimedOut; String get neighbors_requestTimedOut;
/// No description provided for @neighbors_errorLoading. /// No description provided for @neighbors_errorLoading.
@@ -4208,16 +4166,16 @@ abstract class AppLocalizations {
/// **'Error loading neighbors: {error}'** /// **'Error loading neighbors: {error}'**
String neighbors_errorLoading(String error); String neighbors_errorLoading(String error);
/// No description provided for @neighbors_repeatersNeighbors. /// No description provided for @neighbors_repeatersNeighbours.
/// ///
/// In en, this message translates to: /// In en, this message translates to:
/// **'Repeaters Neighbors'** /// **'Repeaters Neighbours'**
String get neighbors_repeatersNeighbors; String get neighbors_repeatersNeighbours;
/// No description provided for @neighbors_noData. /// No description provided for @neighbors_noData.
/// ///
/// In en, this message translates to: /// In en, this message translates to:
/// **'No neighbors data available.'** /// **'No neighbours data available.'**
String get neighbors_noData; String get neighbors_noData;
/// No description provided for @neighbors_unknownContact. /// No description provided for @neighbors_unknownContact.
@@ -4772,12 +4730,6 @@ abstract class AppLocalizations {
/// **'One or more of the hops is missing a location!'** /// **'One or more of the hops is missing a location!'**
String get pathTrace_someHopsNoLocation; String get pathTrace_someHopsNoLocation;
/// No description provided for @pathTrace_clearTooltip.
///
/// In en, this message translates to:
/// **'Clear path.'**
String get pathTrace_clearTooltip;
/// No description provided for @contacts_pathTrace. /// No description provided for @contacts_pathTrace.
/// ///
/// In en, this message translates to: /// In en, this message translates to:
@@ -5035,18 +4987,6 @@ abstract class AppLocalizations {
/// In en, this message translates to: /// In en, this message translates to:
/// **'meshcore-open GPX map data export'** /// **'meshcore-open GPX map data export'**
String get settings_gpxExportShareSubject; String get settings_gpxExportShareSubject;
/// No description provided for @snrIndicator_nearByRepeaters.
///
/// In en, this message translates to:
/// **'Nearby Repeaters'**
String get snrIndicator_nearByRepeaters;
/// No description provided for @snrIndicator_lastSeen.
///
/// In en, this message translates to:
/// **'Last seen'**
String get snrIndicator_lastSeen;
} }
class _AppLocalizationsDelegate class _AppLocalizationsDelegate
+3 -35
View File
@@ -143,16 +143,6 @@ class AppLocalizationsBg extends AppLocalizations {
@override @override
String get scanner_scan => 'Сканирай'; String get scanner_scan => 'Сканирай';
@override
String get scanner_bluetoothOff => 'Bluetooth е изключен.';
@override
String get scanner_bluetoothOffMessage =>
'Моля, активирайте Bluetooth, за да сканирате за устройства.';
@override
String get scanner_enableBluetooth => 'Активирайте Bluetooth';
@override @override
String get device_quickSwitch => 'Бързо превключване'; String get device_quickSwitch => 'Бързо превключване';
@@ -1373,19 +1363,6 @@ class AppLocalizationsBg extends AppLocalizations {
@override @override
String get map_manageRepeater => 'Управление на Повтарящ се Елемент'; String get map_manageRepeater => 'Управление на Повтарящ се Елемент';
@override
String get map_tapToAdd =>
'Натиснете върху възлите, за да ги добавите към пътя.';
@override
String get map_runTrace => 'Изпълни Път на Следване';
@override
String get map_removeLast => 'Премахни Последно';
@override
String get map_pathTraceCancelled => 'Отменен е следването на пътя.';
@override @override
String get mapCache_title => 'Кеш на офлайн карти'; String get mapCache_title => 'Кеш на офлайн карти';
@@ -1681,10 +1658,10 @@ class AppLocalizationsBg extends AppLocalizations {
String get repeater_cliSubtitle => 'Изпрати команди към ретранслатора'; String get repeater_cliSubtitle => 'Изпрати команди към ретранслатора';
@override @override
String get repeater_neighbors => 'Съседи'; String get repeater_neighbours => 'Съседи';
@override @override
String get repeater_neighborsSubtitle => String get repeater_neighboursSubtitle =>
'Преглед на съседни възли с нулев скок.'; 'Преглед на съседни възли с нулев скок.';
@override @override
@@ -2384,7 +2361,7 @@ class AppLocalizationsBg extends AppLocalizations {
} }
@override @override
String get neighbors_repeatersNeighbors => 'Повторители Съседи'; String get neighbors_repeatersNeighbours => 'Повторители Съседи';
@override @override
String get neighbors_noData => 'Няма налични данни за съседи.'; String get neighbors_noData => 'Няма налични данни за съседи.';
@@ -2722,9 +2699,6 @@ class AppLocalizationsBg extends AppLocalizations {
String get pathTrace_someHopsNoLocation => String get pathTrace_someHopsNoLocation =>
'Един или повече от хмелите липсва местоположение!'; 'Един или повече от хмелите липсва местоположение!';
@override
String get pathTrace_clearTooltip => 'Изчисти пътя';
@override @override
String get contacts_pathTrace => 'Пътен проследяване'; String get contacts_pathTrace => 'Пътен проследяване';
@@ -2894,10 +2868,4 @@ class AppLocalizationsBg extends AppLocalizations {
@override @override
String get settings_gpxExportShareSubject => String get settings_gpxExportShareSubject =>
'meshcore-open износ на данни за карта в формат GPX'; 'meshcore-open износ на данни за карта в формат GPX';
@override
String get snrIndicator_nearByRepeaters => 'Близки повтарящи се устройства';
@override
String get snrIndicator_lastSeen => 'Последно видян';
} }
+3 -35
View File
@@ -143,16 +143,6 @@ class AppLocalizationsDe extends AppLocalizations {
@override @override
String get scanner_scan => 'Scannen'; String get scanner_scan => 'Scannen';
@override
String get scanner_bluetoothOff => 'Bluetooth ist deaktiviert.';
@override
String get scanner_bluetoothOffMessage =>
'Bitte aktivieren Sie Bluetooth, um nach Geräten zu suchen.';
@override
String get scanner_enableBluetooth => 'Bluetooth aktivieren';
@override @override
String get device_quickSwitch => 'Schnelles Umschalten'; String get device_quickSwitch => 'Schnelles Umschalten';
@@ -1372,19 +1362,6 @@ class AppLocalizationsDe extends AppLocalizations {
@override @override
String get map_manageRepeater => 'Repeater verwalten'; String get map_manageRepeater => 'Repeater verwalten';
@override
String get map_tapToAdd =>
'Tippen Sie auf Knoten, um sie zum Pfad hinzuzufügen.';
@override
String get map_runTrace => 'Pfadverlauf ausführen';
@override
String get map_removeLast => 'Letztes Entfernen';
@override
String get map_pathTraceCancelled => 'Pfadverfolgung abgebrochen.';
@override @override
String get mapCache_title => 'Offline-Karten-Cache'; String get mapCache_title => 'Offline-Karten-Cache';
@@ -1680,10 +1657,10 @@ class AppLocalizationsDe extends AppLocalizations {
String get repeater_cliSubtitle => 'Sende Befehle an den Repeater'; String get repeater_cliSubtitle => 'Sende Befehle an den Repeater';
@override @override
String get repeater_neighbors => 'Nachbarn'; String get repeater_neighbours => 'Nachbarn';
@override @override
String get repeater_neighborsSubtitle => 'Anzahl der Hop-Nachbarn anzeigen.'; String get repeater_neighboursSubtitle => 'Anzahl der Hop-Nachbarn anzeigen.';
@override @override
String get repeater_settings => 'Einstellungen'; String get repeater_settings => 'Einstellungen';
@@ -2386,7 +2363,7 @@ class AppLocalizationsDe extends AppLocalizations {
} }
@override @override
String get neighbors_repeatersNeighbors => 'Nachbarn'; String get neighbors_repeatersNeighbours => 'Nachbarn';
@override @override
String get neighbors_noData => 'Keine Nachbarsdaten verfügbar.'; String get neighbors_noData => 'Keine Nachbarsdaten verfügbar.';
@@ -2727,9 +2704,6 @@ class AppLocalizationsDe extends AppLocalizations {
String get pathTrace_someHopsNoLocation => String get pathTrace_someHopsNoLocation =>
'Bei einer oder mehreren Knoten fehlt der Standort!'; 'Bei einer oder mehreren Knoten fehlt der Standort!';
@override
String get pathTrace_clearTooltip => 'Pfad löschen';
@override @override
String get contacts_pathTrace => 'Pfadverfolgung'; String get contacts_pathTrace => 'Pfadverfolgung';
@@ -2902,10 +2876,4 @@ class AppLocalizationsDe extends AppLocalizations {
@override @override
String get settings_gpxExportShareSubject => String get settings_gpxExportShareSubject =>
'GPX-Kartendaten aus meshcore-open exportieren'; 'GPX-Kartendaten aus meshcore-open exportieren';
@override
String get snrIndicator_nearByRepeaters => 'In der Nähe befindliche Repeater';
@override
String get snrIndicator_lastSeen => 'Zuletzt gesehen';
} }
+6 -37
View File
@@ -142,16 +142,6 @@ class AppLocalizationsEn extends AppLocalizations {
@override @override
String get scanner_scan => 'Scan'; String get scanner_scan => 'Scan';
@override
String get scanner_bluetoothOff => 'Bluetooth is off';
@override
String get scanner_bluetoothOffMessage =>
'Please turn on Bluetooth to scan for devices';
@override
String get scanner_enableBluetooth => 'Enable Bluetooth';
@override @override
String get device_quickSwitch => 'Quick switch'; String get device_quickSwitch => 'Quick switch';
@@ -1352,18 +1342,6 @@ class AppLocalizationsEn extends AppLocalizations {
@override @override
String get map_manageRepeater => 'Manage Repeater'; String get map_manageRepeater => 'Manage Repeater';
@override
String get map_tapToAdd => 'Tap on nodes to add them to the path.';
@override
String get map_runTrace => 'Run Path Trace';
@override
String get map_removeLast => 'Remove Last';
@override
String get map_pathTraceCancelled => 'Path trace cancelled.';
@override @override
String get mapCache_title => 'Offline Map Cache'; String get mapCache_title => 'Offline Map Cache';
@@ -1654,10 +1632,10 @@ class AppLocalizationsEn extends AppLocalizations {
String get repeater_cliSubtitle => 'Send commands to the repeater'; String get repeater_cliSubtitle => 'Send commands to the repeater';
@override @override
String get repeater_neighbors => 'Neighbors'; String get repeater_neighbours => 'Neighbors';
@override @override
String get repeater_neighborsSubtitle => 'View zero hop neighbors.'; String get repeater_neighboursSubtitle => 'View zero hop neighbors.';
@override @override
String get repeater_settings => 'Settings'; String get repeater_settings => 'Settings';
@@ -2333,10 +2311,10 @@ class AppLocalizationsEn extends AppLocalizations {
} }
@override @override
String get neighbors_receivedData => 'Received Neighbors Data'; String get neighbors_receivedData => 'Received Neighbours Data';
@override @override
String get neighbors_requestTimedOut => 'Neighbors request timed out.'; String get neighbors_requestTimedOut => 'Neighbours request timed out.';
@override @override
String neighbors_errorLoading(String error) { String neighbors_errorLoading(String error) {
@@ -2344,10 +2322,10 @@ class AppLocalizationsEn extends AppLocalizations {
} }
@override @override
String get neighbors_repeatersNeighbors => 'Repeaters Neighbors'; String get neighbors_repeatersNeighbours => 'Repeaters Neighbours';
@override @override
String get neighbors_noData => 'No neighbors data available.'; String get neighbors_noData => 'No neighbours data available.';
@override @override
String neighbors_unknownContact(String pubkey) { String neighbors_unknownContact(String pubkey) {
@@ -2681,9 +2659,6 @@ class AppLocalizationsEn extends AppLocalizations {
String get pathTrace_someHopsNoLocation => String get pathTrace_someHopsNoLocation =>
'One or more of the hops is missing a location!'; 'One or more of the hops is missing a location!';
@override
String get pathTrace_clearTooltip => 'Clear path.';
@override @override
String get contacts_pathTrace => 'Path Trace'; String get contacts_pathTrace => 'Path Trace';
@@ -2849,10 +2824,4 @@ class AppLocalizationsEn extends AppLocalizations {
@override @override
String get settings_gpxExportShareSubject => String get settings_gpxExportShareSubject =>
'meshcore-open GPX map data export'; 'meshcore-open GPX map data export';
@override
String get snrIndicator_nearByRepeaters => 'Nearby Repeaters';
@override
String get snrIndicator_lastSeen => 'Last seen';
} }
+3 -34
View File
@@ -143,16 +143,6 @@ class AppLocalizationsEs extends AppLocalizations {
@override @override
String get scanner_scan => 'Escanea'; String get scanner_scan => 'Escanea';
@override
String get scanner_bluetoothOff => 'Bluetooth está desactivado.';
@override
String get scanner_bluetoothOffMessage =>
'Por favor, active el Bluetooth para escanear dispositivos.';
@override
String get scanner_enableBluetooth => 'Habilitar Bluetooth';
@override @override
String get device_quickSwitch => 'Cambiar rápidamente'; String get device_quickSwitch => 'Cambiar rápidamente';
@@ -1370,18 +1360,6 @@ class AppLocalizationsEs extends AppLocalizations {
@override @override
String get map_manageRepeater => 'Gestionar Repetidor'; String get map_manageRepeater => 'Gestionar Repetidor';
@override
String get map_tapToAdd => 'Pulse en los nodos para agregarlos al camino.';
@override
String get map_runTrace => 'Ejecutar Rastreo de Ruta';
@override
String get map_removeLast => 'Eliminar último';
@override
String get map_pathTraceCancelled => 'Rastreo de ruta cancelado.';
@override @override
String get mapCache_title => 'Caché de Mapa Offline'; String get mapCache_title => 'Caché de Mapa Offline';
@@ -1678,10 +1656,10 @@ class AppLocalizationsEs extends AppLocalizations {
String get repeater_cliSubtitle => 'Enviar comandos al repetidor'; String get repeater_cliSubtitle => 'Enviar comandos al repetidor';
@override @override
String get repeater_neighbors => 'Vecinos'; String get repeater_neighbours => 'Vecinos';
@override @override
String get repeater_neighborsSubtitle => 'Ver vecinos de salto cero.'; String get repeater_neighboursSubtitle => 'Ver vecinos de salto cero.';
@override @override
String get repeater_settings => 'Configuración'; String get repeater_settings => 'Configuración';
@@ -2380,7 +2358,7 @@ class AppLocalizationsEs extends AppLocalizations {
} }
@override @override
String get neighbors_repeatersNeighbors => 'Repetidores Vecinos'; String get neighbors_repeatersNeighbours => 'Repetidores Vecinos';
@override @override
String get neighbors_noData => 'No hay datos de vecinos disponibles.'; String get neighbors_noData => 'No hay datos de vecinos disponibles.';
@@ -2720,9 +2698,6 @@ class AppLocalizationsEs extends AppLocalizations {
String get pathTrace_someHopsNoLocation => String get pathTrace_someHopsNoLocation =>
'Uno o más de los lúpulos carecen de una ubicación'; 'Uno o más de los lúpulos carecen de una ubicación';
@override
String get pathTrace_clearTooltip => 'Borrar ruta';
@override @override
String get contacts_pathTrace => 'Rastreo de caminos'; String get contacts_pathTrace => 'Rastreo de caminos';
@@ -2893,10 +2868,4 @@ class AppLocalizationsEs extends AppLocalizations {
@override @override
String get settings_gpxExportShareSubject => String get settings_gpxExportShareSubject =>
'meshcore-open exportación de datos de mapa GPX'; 'meshcore-open exportación de datos de mapa GPX';
@override
String get snrIndicator_nearByRepeaters => 'Repetidores cercanos';
@override
String get snrIndicator_lastSeen => 'Visto por última vez';
} }
+4 -35
View File
@@ -143,16 +143,6 @@ class AppLocalizationsFr extends AppLocalizations {
@override @override
String get scanner_scan => 'Scanner'; String get scanner_scan => 'Scanner';
@override
String get scanner_bluetoothOff => 'Le Bluetooth est désactivé.';
@override
String get scanner_bluetoothOffMessage =>
'Veuillez activer le Bluetooth pour rechercher des appareils.';
@override
String get scanner_enableBluetooth => 'Activer le Bluetooth';
@override @override
String get device_quickSwitch => 'Basculement rapide'; String get device_quickSwitch => 'Basculement rapide';
@@ -1377,19 +1367,6 @@ class AppLocalizationsFr extends AppLocalizations {
@override @override
String get map_manageRepeater => 'Gérer le répéteur'; String get map_manageRepeater => 'Gérer le répéteur';
@override
String get map_tapToAdd =>
'Appuyez sur les nœuds pour les ajouter au chemin.';
@override
String get map_runTrace => 'Exécuter la traçage de chemin';
@override
String get map_removeLast => 'Supprimer le dernier';
@override
String get map_pathTraceCancelled => 'Traçage de chemin annulé';
@override @override
String get mapCache_title => 'Cache de Carte Hors Ligne'; String get mapCache_title => 'Cache de Carte Hors Ligne';
@@ -1686,10 +1663,11 @@ class AppLocalizationsFr extends AppLocalizations {
String get repeater_cliSubtitle => 'Envoyer des commandes au répéteur'; String get repeater_cliSubtitle => 'Envoyer des commandes au répéteur';
@override @override
String get repeater_neighbors => 'Voisins'; String get repeater_neighbours => 'Voisins';
@override @override
String get repeater_neighborsSubtitle => 'Afficher les voisins de saut nuls.'; String get repeater_neighboursSubtitle =>
'Afficher les voisins de saut nuls.';
@override @override
String get repeater_settings => 'Paramètres'; String get repeater_settings => 'Paramètres';
@@ -2394,7 +2372,7 @@ class AppLocalizationsFr extends AppLocalizations {
} }
@override @override
String get neighbors_repeatersNeighbors => 'Répéteurs Voisins'; String get neighbors_repeatersNeighbours => 'Répéteurs Voisins';
@override @override
String get neighbors_noData => String get neighbors_noData =>
@@ -2736,9 +2714,6 @@ class AppLocalizationsFr extends AppLocalizations {
String get pathTrace_someHopsNoLocation => String get pathTrace_someHopsNoLocation =>
'Un ou plusieurs des sauts manquent d\'une localisation !'; 'Un ou plusieurs des sauts manquent d\'une localisation !';
@override
String get pathTrace_clearTooltip => 'Effacer le chemin';
@override @override
String get contacts_pathTrace => 'Traçage de chemin'; String get contacts_pathTrace => 'Traçage de chemin';
@@ -2916,10 +2891,4 @@ class AppLocalizationsFr extends AppLocalizations {
@override @override
String get settings_gpxExportShareSubject => String get settings_gpxExportShareSubject =>
'meshcore-open exporter les données de carte GPX'; 'meshcore-open exporter les données de carte GPX';
@override
String get snrIndicator_nearByRepeaters => 'Répéteurs à proximité';
@override
String get snrIndicator_lastSeen => 'Dernière fois vu';
} }
+3 -34
View File
@@ -143,16 +143,6 @@ class AppLocalizationsIt extends AppLocalizations {
@override @override
String get scanner_scan => 'Scansiona'; String get scanner_scan => 'Scansiona';
@override
String get scanner_bluetoothOff => 'Il Bluetooth è disattivato.';
@override
String get scanner_bluetoothOffMessage =>
'Si prega di attivare il Bluetooth per effettuare la scansione dei dispositivi.';
@override
String get scanner_enableBluetooth => 'Abilita il Bluetooth';
@override @override
String get device_quickSwitch => 'Passa velocemente'; String get device_quickSwitch => 'Passa velocemente';
@@ -1369,18 +1359,6 @@ class AppLocalizationsIt extends AppLocalizations {
@override @override
String get map_manageRepeater => 'Gestisci Ripetitore'; String get map_manageRepeater => 'Gestisci Ripetitore';
@override
String get map_tapToAdd => 'Tocca i nodi per aggiungerli al percorso.';
@override
String get map_runTrace => 'Esegui Path Trace';
@override
String get map_removeLast => 'Rimuovi ultimo';
@override
String get map_pathTraceCancelled => 'Tracciamento del percorso annullato.';
@override @override
String get mapCache_title => 'Cache Mappa Offline'; String get mapCache_title => 'Cache Mappa Offline';
@@ -1676,10 +1654,10 @@ class AppLocalizationsIt extends AppLocalizations {
String get repeater_cliSubtitle => 'Invia comandi al ripetitore'; String get repeater_cliSubtitle => 'Invia comandi al ripetitore';
@override @override
String get repeater_neighbors => 'Vicini'; String get repeater_neighbours => 'Vicini';
@override @override
String get repeater_neighborsSubtitle => String get repeater_neighboursSubtitle =>
'Visualizza vicini di salto pari a zero.'; 'Visualizza vicini di salto pari a zero.';
@override @override
@@ -2380,7 +2358,7 @@ class AppLocalizationsIt extends AppLocalizations {
} }
@override @override
String get neighbors_repeatersNeighbors => 'Ripetitori Vicini'; String get neighbors_repeatersNeighbours => 'Ripetitori Vicini';
@override @override
String get neighbors_noData => 'Nessun dato sugli vicini disponibile.'; String get neighbors_noData => 'Nessun dato sugli vicini disponibile.';
@@ -2721,9 +2699,6 @@ class AppLocalizationsIt extends AppLocalizations {
String get pathTrace_someHopsNoLocation => String get pathTrace_someHopsNoLocation =>
'Uno o più dei luppoli mancano di una posizione!'; 'Uno o più dei luppoli mancano di una posizione!';
@override
String get pathTrace_clearTooltip => 'Pulisci percorso';
@override @override
String get contacts_pathTrace => 'Traccia Percorso'; String get contacts_pathTrace => 'Traccia Percorso';
@@ -2897,10 +2872,4 @@ class AppLocalizationsIt extends AppLocalizations {
@override @override
String get settings_gpxExportShareSubject => String get settings_gpxExportShareSubject =>
'meshcore-open esportazione dati mappa GPX'; 'meshcore-open esportazione dati mappa GPX';
@override
String get snrIndicator_nearByRepeaters => 'Ripetitori vicini';
@override
String get snrIndicator_lastSeen => 'Ultimo accesso';
} }
+3 -35
View File
@@ -142,16 +142,6 @@ class AppLocalizationsNl extends AppLocalizations {
@override @override
String get scanner_scan => 'Scan'; String get scanner_scan => 'Scan';
@override
String get scanner_bluetoothOff => 'Bluetooth is uitgeschakeld';
@override
String get scanner_bluetoothOffMessage =>
'Zorg ervoor dat Bluetooth is ingeschakeld om naar apparaten te zoeken.';
@override
String get scanner_enableBluetooth => 'Activeer Bluetooth';
@override @override
String get device_quickSwitch => 'Snelle overschakeling'; String get device_quickSwitch => 'Snelle overschakeling';
@@ -1365,19 +1355,6 @@ class AppLocalizationsNl extends AppLocalizations {
@override @override
String get map_manageRepeater => 'Beheer Repeater'; String get map_manageRepeater => 'Beheer Repeater';
@override
String get map_tapToAdd =>
'Tik op knooppunten om ze toe te voegen aan het pad';
@override
String get map_runTrace => 'Padeshulp traceren';
@override
String get map_removeLast => 'Verwijder Laatste';
@override
String get map_pathTraceCancelled => 'Pad traceren geannuleerd';
@override @override
String get mapCache_title => 'Offline Kaarten Cache'; String get mapCache_title => 'Offline Kaarten Cache';
@@ -1672,10 +1649,10 @@ class AppLocalizationsNl extends AppLocalizations {
String get repeater_cliSubtitle => 'Verzend commando\'s naar de repeater'; String get repeater_cliSubtitle => 'Verzend commando\'s naar de repeater';
@override @override
String get repeater_neighbors => 'Buren'; String get repeater_neighbours => 'Buren';
@override @override
String get repeater_neighborsSubtitle => 'Bekijk nul hops buren.'; String get repeater_neighboursSubtitle => 'Bekijk nul hops buren.';
@override @override
String get repeater_settings => 'Instellingen'; String get repeater_settings => 'Instellingen';
@@ -2371,7 +2348,7 @@ class AppLocalizationsNl extends AppLocalizations {
} }
@override @override
String get neighbors_repeatersNeighbors => 'Herhalingen Buren'; String get neighbors_repeatersNeighbours => 'Herhalingen Buren';
@override @override
String get neighbors_noData => 'Geen gegevens van buren beschikbaar.'; String get neighbors_noData => 'Geen gegevens van buren beschikbaar.';
@@ -2712,9 +2689,6 @@ class AppLocalizationsNl extends AppLocalizations {
String get pathTrace_someHopsNoLocation => String get pathTrace_someHopsNoLocation =>
'Een of meer van de hops ontbreken een locatie!'; 'Een of meer van de hops ontbreken een locatie!';
@override
String get pathTrace_clearTooltip => 'Weg wissen';
@override @override
String get contacts_pathTrace => 'Pad Traceren'; String get contacts_pathTrace => 'Pad Traceren';
@@ -2885,10 +2859,4 @@ class AppLocalizationsNl extends AppLocalizations {
@override @override
String get settings_gpxExportShareSubject => String get settings_gpxExportShareSubject =>
'meshcore-open GPX kaartgegevens exporteren'; 'meshcore-open GPX kaartgegevens exporteren';
@override
String get snrIndicator_nearByRepeaters => 'Nabije herhalingseenheden';
@override
String get snrIndicator_lastSeen => 'Laatst gezien';
} }
+3 -34
View File
@@ -143,16 +143,6 @@ class AppLocalizationsPl extends AppLocalizations {
@override @override
String get scanner_scan => 'Przeskanuj'; String get scanner_scan => 'Przeskanuj';
@override
String get scanner_bluetoothOff => 'Bluetooth jest wyłączony';
@override
String get scanner_bluetoothOffMessage =>
'Prosimy włączyć Bluetooth, aby przeskanować urządzenia.';
@override
String get scanner_enableBluetooth => 'Włącz Bluetooth';
@override @override
String get device_quickSwitch => 'Szybka zmiana'; String get device_quickSwitch => 'Szybka zmiana';
@@ -1371,18 +1361,6 @@ class AppLocalizationsPl extends AppLocalizations {
@override @override
String get map_manageRepeater => 'Zarządzaj Powtórzami'; String get map_manageRepeater => 'Zarządzaj Powtórzami';
@override
String get map_tapToAdd => 'Kliknij na węzły, aby dodać je do ścieżki.';
@override
String get map_runTrace => 'Uruchom ślad ścieżki';
@override
String get map_removeLast => 'Usuń ostatni';
@override
String get map_pathTraceCancelled => 'Śledzenie ścieżki anulowano.';
@override @override
String get mapCache_title => 'Bufor Map Offline'; String get mapCache_title => 'Bufor Map Offline';
@@ -1680,10 +1658,10 @@ class AppLocalizationsPl extends AppLocalizations {
String get repeater_cliSubtitle => 'Wyślij polecenia do powielacza'; String get repeater_cliSubtitle => 'Wyślij polecenia do powielacza';
@override @override
String get repeater_neighbors => 'Sąsiedzi'; String get repeater_neighbours => 'Sąsiedzi';
@override @override
String get repeater_neighborsSubtitle => String get repeater_neighboursSubtitle =>
'Wyświetl sąsiedztwo zerowych hopów.'; 'Wyświetl sąsiedztwo zerowych hopów.';
@override @override
@@ -2379,7 +2357,7 @@ class AppLocalizationsPl extends AppLocalizations {
} }
@override @override
String get neighbors_repeatersNeighbors => 'Powtarzacze Sąsiedzi'; String get neighbors_repeatersNeighbours => 'Powtarzacze Sąsiedzi';
@override @override
String get neighbors_noData => 'Brak danych dotyczących sąsiadów.'; String get neighbors_noData => 'Brak danych dotyczących sąsiadów.';
@@ -2719,9 +2697,6 @@ class AppLocalizationsPl extends AppLocalizations {
String get pathTrace_someHopsNoLocation => String get pathTrace_someHopsNoLocation =>
'Jeden lub więcej z chmieli nie ma określonej lokalizacji!'; 'Jeden lub więcej z chmieli nie ma określonej lokalizacji!';
@override
String get pathTrace_clearTooltip => 'Wyczyść ścieżkę';
@override @override
String get contacts_pathTrace => 'Śledzenie Ścieżek'; String get contacts_pathTrace => 'Śledzenie Ścieżek';
@@ -2899,10 +2874,4 @@ class AppLocalizationsPl extends AppLocalizations {
@override @override
String get settings_gpxExportShareSubject => String get settings_gpxExportShareSubject =>
'Eksport danych mapy GPX meshcore-open'; 'Eksport danych mapy GPX meshcore-open';
@override
String get snrIndicator_nearByRepeaters => 'Nadajniki w pobliżu';
@override
String get snrIndicator_lastSeen => 'Ostatnio widziany';
} }
+4 -34
View File
@@ -143,16 +143,6 @@ class AppLocalizationsPt extends AppLocalizations {
@override @override
String get scanner_scan => 'Digitalizar'; String get scanner_scan => 'Digitalizar';
@override
String get scanner_bluetoothOff => 'Bluetooth está desativado';
@override
String get scanner_bluetoothOffMessage =>
'Por favor, ative o Bluetooth para escanear por dispositivos.';
@override
String get scanner_enableBluetooth => 'Ative o Bluetooth';
@override @override
String get device_quickSwitch => 'Mudar rapidamente'; String get device_quickSwitch => 'Mudar rapidamente';
@@ -1371,18 +1361,6 @@ class AppLocalizationsPt extends AppLocalizations {
@override @override
String get map_manageRepeater => 'Gerenciar Repetidor'; String get map_manageRepeater => 'Gerenciar Repetidor';
@override
String get map_tapToAdd => 'Toque nos nós para adicioná-los ao caminho.';
@override
String get map_runTrace => 'Executar Traçado de Caminho';
@override
String get map_removeLast => 'Remover Último';
@override
String get map_pathTraceCancelled => 'Rastreamento de caminho cancelado.';
@override @override
String get mapCache_title => 'Cache de Mapa Offline'; String get mapCache_title => 'Cache de Mapa Offline';
@@ -1678,10 +1656,11 @@ class AppLocalizationsPt extends AppLocalizations {
String get repeater_cliSubtitle => 'Enviar comandos ao repetidor'; String get repeater_cliSubtitle => 'Enviar comandos ao repetidor';
@override @override
String get repeater_neighbors => 'Vizinhos'; String get repeater_neighbours => 'Vizinhos';
@override @override
String get repeater_neighborsSubtitle => 'Visualizar vizinhos de salto zero.'; String get repeater_neighboursSubtitle =>
'Visualizar vizinhos de salto zero.';
@override @override
String get repeater_settings => 'Configurações'; String get repeater_settings => 'Configurações';
@@ -2380,7 +2359,7 @@ class AppLocalizationsPt extends AppLocalizations {
} }
@override @override
String get neighbors_repeatersNeighbors => 'Repetidores Vizinhos'; String get neighbors_repeatersNeighbours => 'Repetidores Vizinhos';
@override @override
String get neighbors_noData => 'Não estão disponíveis dados de vizinhos.'; String get neighbors_noData => 'Não estão disponíveis dados de vizinhos.';
@@ -2721,9 +2700,6 @@ class AppLocalizationsPt extends AppLocalizations {
String get pathTrace_someHopsNoLocation => String get pathTrace_someHopsNoLocation =>
'Um ou mais dos lúpulos estão sem localização!'; 'Um ou mais dos lúpulos estão sem localização!';
@override
String get pathTrace_clearTooltip => 'Limpar caminho';
@override @override
String get contacts_pathTrace => 'Traçado de Caminho'; String get contacts_pathTrace => 'Traçado de Caminho';
@@ -2893,10 +2869,4 @@ class AppLocalizationsPt extends AppLocalizations {
@override @override
String get settings_gpxExportShareSubject => String get settings_gpxExportShareSubject =>
'meshcore-open exportação de dados de mapa GPX'; 'meshcore-open exportação de dados de mapa GPX';
@override
String get snrIndicator_nearByRepeaters => 'Repetidores Próximos';
@override
String get snrIndicator_lastSeen => 'Visto pela última vez';
} }
+3 -34
View File
@@ -142,16 +142,6 @@ class AppLocalizationsRu extends AppLocalizations {
@override @override
String get scanner_scan => 'Сканирование'; String get scanner_scan => 'Сканирование';
@override
String get scanner_bluetoothOff => 'Bluetooth выключен';
@override
String get scanner_bluetoothOffMessage =>
'Пожалуйста, включите Bluetooth, чтобы найти устройства.';
@override
String get scanner_enableBluetooth => 'Включите Bluetooth';
@override @override
String get device_quickSwitch => 'Быстрое переключение'; String get device_quickSwitch => 'Быстрое переключение';
@@ -1372,18 +1362,6 @@ class AppLocalizationsRu extends AppLocalizations {
@override @override
String get map_manageRepeater => 'Управление репитером'; String get map_manageRepeater => 'Управление репитером';
@override
String get map_tapToAdd => 'Нажимайте на узлы, чтобы добавить их в путь.';
@override
String get map_runTrace => 'Запустить трассировку пути';
@override
String get map_removeLast => 'Удалить последний';
@override
String get map_pathTraceCancelled => 'Отмена трассировки пути';
@override @override
String get mapCache_title => 'Кэш офлайн-карты'; String get mapCache_title => 'Кэш офлайн-карты';
@@ -1680,10 +1658,10 @@ class AppLocalizationsRu extends AppLocalizations {
String get repeater_cliSubtitle => 'Отправка команд репитеру'; String get repeater_cliSubtitle => 'Отправка команд репитеру';
@override @override
String get repeater_neighbors => 'Соседи'; String get repeater_neighbours => 'Соседи';
@override @override
String get repeater_neighborsSubtitle => 'Просмотр соседей на нулевом хопе.'; String get repeater_neighboursSubtitle => 'Просмотр соседей на нулевом хопе.';
@override @override
String get repeater_settings => 'Настройки'; String get repeater_settings => 'Настройки';
@@ -2383,7 +2361,7 @@ class AppLocalizationsRu extends AppLocalizations {
} }
@override @override
String get neighbors_repeatersNeighbors => 'Соседи репитеров'; String get neighbors_repeatersNeighbours => 'Соседи репитеров';
@override @override
String get neighbors_noData => 'Данные о соседях недоступны.'; String get neighbors_noData => 'Данные о соседях недоступны.';
@@ -2724,9 +2702,6 @@ class AppLocalizationsRu extends AppLocalizations {
String get pathTrace_someHopsNoLocation => String get pathTrace_someHopsNoLocation =>
'Одному или нескольким хмелям не указано местоположение!'; 'Одному или нескольким хмелям не указано местоположение!';
@override
String get pathTrace_clearTooltip => 'Очистить путь';
@override @override
String get contacts_pathTrace => 'Трассировка пути'; String get contacts_pathTrace => 'Трассировка пути';
@@ -2905,10 +2880,4 @@ class AppLocalizationsRu extends AppLocalizations {
@override @override
String get settings_gpxExportShareSubject => String get settings_gpxExportShareSubject =>
'meshcore-open экспорт данных карты GPX'; 'meshcore-open экспорт данных карты GPX';
@override
String get snrIndicator_nearByRepeaters => 'Ближайшие ретрансляторы';
@override
String get snrIndicator_lastSeen => 'Последний раз видели';
} }
+3 -34
View File
@@ -143,16 +143,6 @@ class AppLocalizationsSk extends AppLocalizations {
@override @override
String get scanner_scan => 'Skončiť'; String get scanner_scan => 'Skončiť';
@override
String get scanner_bluetoothOff => 'Bluetooth je vypnutý';
@override
String get scanner_bluetoothOffMessage =>
'Prosím, zapnite Bluetooth, aby ste mohli skenovať pre zariadenia.';
@override
String get scanner_enableBluetooth => 'Povolte Bluetooth';
@override @override
String get device_quickSwitch => 'Rýchle prepínač'; String get device_quickSwitch => 'Rýchle prepínač';
@@ -1366,18 +1356,6 @@ class AppLocalizationsSk extends AppLocalizations {
@override @override
String get map_manageRepeater => 'Spravovať Opakovanie'; String get map_manageRepeater => 'Spravovať Opakovanie';
@override
String get map_tapToAdd => 'Kliknite na uzly, aby ste ich pridali k ceste.';
@override
String get map_runTrace => 'Spustiť trasovaním cesty';
@override
String get map_removeLast => 'Odstrániť posledný';
@override
String get map_pathTraceCancelled => 'Zrušenie stopáže cesty bolo zrušené.';
@override @override
String get mapCache_title => 'Offline Mapa Pamäť'; String get mapCache_title => 'Offline Mapa Pamäť';
@@ -1673,10 +1651,10 @@ class AppLocalizationsSk extends AppLocalizations {
String get repeater_cliSubtitle => 'Pošlite príkazy opakovaču'; String get repeater_cliSubtitle => 'Pošlite príkazy opakovaču';
@override @override
String get repeater_neighbors => 'Súsezný'; String get repeater_neighbours => 'Súsezný';
@override @override
String get repeater_neighborsSubtitle => 'Zobraziť susedné body bez skokov.'; String get repeater_neighboursSubtitle => 'Zobraziť susedné body bez skokov.';
@override @override
String get repeater_settings => 'Nastavenia'; String get repeater_settings => 'Nastavenia';
@@ -2367,7 +2345,7 @@ class AppLocalizationsSk extends AppLocalizations {
} }
@override @override
String get neighbors_repeatersNeighbors => 'Opakovadlá Súsezná'; String get neighbors_repeatersNeighbours => 'Opakovadlá Súsezná';
@override @override
String get neighbors_noData => String get neighbors_noData =>
@@ -2707,9 +2685,6 @@ class AppLocalizationsSk extends AppLocalizations {
String get pathTrace_someHopsNoLocation => String get pathTrace_someHopsNoLocation =>
'Jedna alebo viac chmeľov chýba lokalita!'; 'Jedna alebo viac chmeľov chýba lokalita!';
@override
String get pathTrace_clearTooltip => 'Zmazať cestu';
@override @override
String get contacts_pathTrace => 'Sledovanie lúčov'; String get contacts_pathTrace => 'Sledovanie lúčov';
@@ -2881,10 +2856,4 @@ class AppLocalizationsSk extends AppLocalizations {
@override @override
String get settings_gpxExportShareSubject => String get settings_gpxExportShareSubject =>
'meshcore-open export dát GPX mapových údajov'; 'meshcore-open export dát GPX mapových údajov';
@override
String get snrIndicator_nearByRepeaters => 'Miestne opakovače';
@override
String get snrIndicator_lastSeen => 'Naposledy videný';
} }
+3 -34
View File
@@ -143,16 +143,6 @@ class AppLocalizationsSl extends AppLocalizations {
@override @override
String get scanner_scan => 'Skeniraj'; String get scanner_scan => 'Skeniraj';
@override
String get scanner_bluetoothOff => 'Bluetooth je izklopljen';
@override
String get scanner_bluetoothOffMessage =>
'Prosimo, vklopite Bluetooth, da lahko poiščete naprave.';
@override
String get scanner_enableBluetooth => 'Omogočite Bluetooth';
@override @override
String get device_quickSwitch => 'Hitro preklop'; String get device_quickSwitch => 'Hitro preklop';
@@ -1362,18 +1352,6 @@ class AppLocalizationsSl extends AppLocalizations {
@override @override
String get map_manageRepeater => 'Upravljajte Ponovitve'; String get map_manageRepeater => 'Upravljajte Ponovitve';
@override
String get map_tapToAdd => 'Pritisnite na vozlišča, da jih dodate poti.';
@override
String get map_runTrace => 'Zaženi sledenje poti';
@override
String get map_removeLast => 'Odstrani Zadnji';
@override
String get map_pathTraceCancelled => 'Spremljanje poti je prekinjeno.';
@override @override
String get mapCache_title => String get mapCache_title =>
'Omrezni predpomnilnik zemljeških zemljejevskih slik'; 'Omrezni predpomnilnik zemljeških zemljejevskih slik';
@@ -1672,10 +1650,10 @@ class AppLocalizationsSl extends AppLocalizations {
'Pošlji ukazne povelje na ponovitveno enoto.'; 'Pošlji ukazne povelje na ponovitveno enoto.';
@override @override
String get repeater_neighbors => 'Sosedi'; String get repeater_neighbours => 'Sosedi';
@override @override
String get repeater_neighborsSubtitle => 'Pogledati nič sosednjih hopjev.'; String get repeater_neighboursSubtitle => 'Pogledati nič sosednjih hopjev.';
@override @override
String get repeater_settings => 'Nastavitve'; String get repeater_settings => 'Nastavitve';
@@ -2371,7 +2349,7 @@ class AppLocalizationsSl extends AppLocalizations {
} }
@override @override
String get neighbors_repeatersNeighbors => 'Ponovitve Sosedi'; String get neighbors_repeatersNeighbours => 'Ponovitve Sosedi';
@override @override
String get neighbors_noData => 'Niso na voljo podatki o sosedih.'; String get neighbors_noData => 'Niso na voljo podatki o sosedih.';
@@ -2710,9 +2688,6 @@ class AppLocalizationsSl extends AppLocalizations {
String get pathTrace_someHopsNoLocation => String get pathTrace_someHopsNoLocation =>
'Ena ali več hmelju manjka lokacija!'; 'Ena ali več hmelju manjka lokacija!';
@override
String get pathTrace_clearTooltip => 'Počisti pot';
@override @override
String get contacts_pathTrace => 'Sledenje poti'; String get contacts_pathTrace => 'Sledenje poti';
@@ -2886,10 +2861,4 @@ class AppLocalizationsSl extends AppLocalizations {
@override @override
String get settings_gpxExportShareSubject => String get settings_gpxExportShareSubject =>
'meshcore-open izvoz podatkov GPX karte'; 'meshcore-open izvoz podatkov GPX karte';
@override
String get snrIndicator_nearByRepeaters => 'Bližnji ponovitelji';
@override
String get snrIndicator_lastSeen => 'Zadnjič videno';
} }
+3 -34
View File
@@ -142,16 +142,6 @@ class AppLocalizationsSv extends AppLocalizations {
@override @override
String get scanner_scan => 'Skanna'; String get scanner_scan => 'Skanna';
@override
String get scanner_bluetoothOff => 'Bluetooth är avstängt';
@override
String get scanner_bluetoothOffMessage =>
'Vänligen aktivera Bluetooth för att söka efter enheter.';
@override
String get scanner_enableBluetooth => 'Aktivera Bluetooth';
@override @override
String get device_quickSwitch => 'Snabb växling'; String get device_quickSwitch => 'Snabb växling';
@@ -1358,18 +1348,6 @@ class AppLocalizationsSv extends AppLocalizations {
@override @override
String get map_manageRepeater => 'Hantera Upprepare'; String get map_manageRepeater => 'Hantera Upprepare';
@override
String get map_tapToAdd => 'Tryck på noder för att lägga till dem i banan.';
@override
String get map_runTrace => 'Kör spårsökning';
@override
String get map_removeLast => 'Ta bort sista';
@override
String get map_pathTraceCancelled => 'Sökvägsspårning avbruten.';
@override @override
String get mapCache_title => 'Offline Kartcache'; String get mapCache_title => 'Offline Kartcache';
@@ -1662,10 +1640,10 @@ class AppLocalizationsSv extends AppLocalizations {
String get repeater_cliSubtitle => 'Skicka kommandon till repetitorn'; String get repeater_cliSubtitle => 'Skicka kommandon till repetitorn';
@override @override
String get repeater_neighbors => 'Grannar'; String get repeater_neighbours => 'Grannar';
@override @override
String get repeater_neighborsSubtitle => 'Visa noll hoppgrannar.'; String get repeater_neighboursSubtitle => 'Visa noll hoppgrannar.';
@override @override
String get repeater_settings => 'Inställningar'; String get repeater_settings => 'Inställningar';
@@ -2356,7 +2334,7 @@ class AppLocalizationsSv extends AppLocalizations {
} }
@override @override
String get neighbors_repeatersNeighbors => 'Upprepar grannar'; String get neighbors_repeatersNeighbours => 'Upprepar grannar';
@override @override
String get neighbors_noData => 'Inga grannuppgifter finns tillgängliga.'; String get neighbors_noData => 'Inga grannuppgifter finns tillgängliga.';
@@ -2695,9 +2673,6 @@ class AppLocalizationsSv extends AppLocalizations {
String get pathTrace_someHopsNoLocation => String get pathTrace_someHopsNoLocation =>
'En eller flera av humlen saknar en plats!'; 'En eller flera av humlen saknar en plats!';
@override
String get pathTrace_clearTooltip => 'Rensa väg';
@override @override
String get contacts_pathTrace => 'Path Trace'; String get contacts_pathTrace => 'Path Trace';
@@ -2866,10 +2841,4 @@ class AppLocalizationsSv extends AppLocalizations {
@override @override
String get settings_gpxExportShareSubject => String get settings_gpxExportShareSubject =>
'meshcore-open export av GPX-kartdata'; 'meshcore-open export av GPX-kartdata';
@override
String get snrIndicator_nearByRepeaters => 'Närliggande uppreparstationer';
@override
String get snrIndicator_lastSeen => 'Senast sedd';
} }
+3 -34
View File
@@ -143,16 +143,6 @@ class AppLocalizationsUk extends AppLocalizations {
@override @override
String get scanner_scan => 'Сканувати'; String get scanner_scan => 'Сканувати';
@override
String get scanner_bluetoothOff => 'Bluetooth вимкнено';
@override
String get scanner_bluetoothOffMessage =>
'Будь ласка, увімкніть Bluetooth, щоб сканувати пристрої.';
@override
String get scanner_enableBluetooth => 'Увімкніть Bluetooth';
@override @override
String get device_quickSwitch => 'Швидке перемикання'; String get device_quickSwitch => 'Швидке перемикання';
@@ -1371,18 +1361,6 @@ class AppLocalizationsUk extends AppLocalizations {
@override @override
String get map_manageRepeater => 'Керувати ретранслятором'; String get map_manageRepeater => 'Керувати ретранслятором';
@override
String get map_tapToAdd => 'Натисніть на вузли, щоб додати їх до шляху';
@override
String get map_runTrace => 'Виконати трасування шляху';
@override
String get map_removeLast => 'Видалити останній';
@override
String get map_pathTraceCancelled => 'Відмінується трасування шляху';
@override @override
String get mapCache_title => 'Офлайн-кеш карти'; String get mapCache_title => 'Офлайн-кеш карти';
@@ -1679,10 +1657,10 @@ class AppLocalizationsUk extends AppLocalizations {
String get repeater_cliSubtitle => 'Надіслати команди ретранслятору'; String get repeater_cliSubtitle => 'Надіслати команди ретранслятору';
@override @override
String get repeater_neighbors => 'Сусіди'; String get repeater_neighbours => 'Сусіди';
@override @override
String get repeater_neighborsSubtitle => String get repeater_neighboursSubtitle =>
'Показати сусідів нульового стрибка.'; 'Показати сусідів нульового стрибка.';
@override @override
@@ -2384,7 +2362,7 @@ class AppLocalizationsUk extends AppLocalizations {
} }
@override @override
String get neighbors_repeatersNeighbors => 'Ретранслятори-сусіди'; String get neighbors_repeatersNeighbours => 'Ретранслятори-сусіди';
@override @override
String get neighbors_noData => 'Дані про сусідів недоступні.'; String get neighbors_noData => 'Дані про сусідів недоступні.';
@@ -2731,9 +2709,6 @@ class AppLocalizationsUk extends AppLocalizations {
String get pathTrace_someHopsNoLocation => String get pathTrace_someHopsNoLocation =>
'Одне або більше хмелів відсутнє місце розташування!'; 'Одне або більше хмелів відсутнє місце розташування!';
@override
String get pathTrace_clearTooltip => 'Очистити шлях';
@override @override
String get contacts_pathTrace => 'Трасування шляхів'; String get contacts_pathTrace => 'Трасування шляхів';
@@ -2911,10 +2886,4 @@ class AppLocalizationsUk extends AppLocalizations {
@override @override
String get settings_gpxExportShareSubject => String get settings_gpxExportShareSubject =>
'експорт даних карти meshcore-open у форматі GPX'; 'експорт даних карти meshcore-open у форматі GPX';
@override
String get snrIndicator_nearByRepeaters => 'Ближні ретранслятори';
@override
String get snrIndicator_lastSeen => 'Останній раз бачили';
} }
+3 -33
View File
@@ -142,15 +142,6 @@ class AppLocalizationsZh extends AppLocalizations {
@override @override
String get scanner_scan => '扫描'; String get scanner_scan => '扫描';
@override
String get scanner_bluetoothOff => '蓝牙已关闭';
@override
String get scanner_bluetoothOffMessage => '请打开蓝牙功能,以便搜索设备。';
@override
String get scanner_enableBluetooth => '启用蓝牙';
@override @override
String get device_quickSwitch => '快速切换'; String get device_quickSwitch => '快速切换';
@@ -1310,18 +1301,6 @@ class AppLocalizationsZh extends AppLocalizations {
@override @override
String get map_manageRepeater => '管理重复器'; String get map_manageRepeater => '管理重复器';
@override
String get map_tapToAdd => '点击节点将其添加到路径中';
@override
String get map_runTrace => '运行路径跟踪';
@override
String get map_removeLast => '删除最后一个';
@override
String get map_pathTraceCancelled => '路径跟踪已取消';
@override @override
String get mapCache_title => '离线地图缓存'; String get mapCache_title => '离线地图缓存';
@@ -1601,10 +1580,10 @@ class AppLocalizationsZh extends AppLocalizations {
String get repeater_cliSubtitle => '向复用器发送指令'; String get repeater_cliSubtitle => '向复用器发送指令';
@override @override
String get repeater_neighbors => '邻居'; String get repeater_neighbours => '邻居';
@override @override
String get repeater_neighborsSubtitle => '查看邻居节点(无需中间节点)。'; String get repeater_neighboursSubtitle => '查看邻居节点(无需中间节点)。';
@override @override
String get repeater_settings => '设置'; String get repeater_settings => '设置';
@@ -2251,7 +2230,7 @@ class AppLocalizationsZh extends AppLocalizations {
} }
@override @override
String get neighbors_repeatersNeighbors => '重复使用的邻居'; String get neighbors_repeatersNeighbours => '重复使用的邻居';
@override @override
String get neighbors_noData => '没有可用的邻居信息。'; String get neighbors_noData => '没有可用的邻居信息。';
@@ -2578,9 +2557,6 @@ class AppLocalizationsZh extends AppLocalizations {
@override @override
String get pathTrace_someHopsNoLocation => '其中一个或多个啤酒花缺少位置!'; String get pathTrace_someHopsNoLocation => '其中一个或多个啤酒花缺少位置!';
@override
String get pathTrace_clearTooltip => '清除路径';
@override @override
String get contacts_pathTrace => '路径追踪'; String get contacts_pathTrace => '路径追踪';
@@ -2719,10 +2695,4 @@ class AppLocalizationsZh extends AppLocalizations {
@override @override
String get settings_gpxExportShareSubject => 'meshcore-open GPX 地图数据导出'; String get settings_gpxExportShareSubject => 'meshcore-open GPX 地图数据导出';
@override
String get snrIndicator_nearByRepeaters => '附近的重复器';
@override
String get snrIndicator_lastSeen => '最近访问';
} }
+6 -14
View File
@@ -1356,12 +1356,12 @@
} }
} }
}, },
"repeater_neighbors": "Buren", "repeater_neighbours": "Buren",
"repeater_neighborsSubtitle": "Bekijk nul hops buren.", "repeater_neighboursSubtitle": "Bekijk nul hops buren.",
"neighbors_receivedData": "Ontvangen Buurdata", "neighbors_receivedData": "Ontvangen Buurdata",
"neighbors_requestTimedOut": "Buren vragen om tijdelijk uitgeschakeld.", "neighbors_requestTimedOut": "Buren vragen om tijdelijk uitgeschakeld.",
"neighbors_errorLoading": "Fout bij het laden van buren: {error}", "neighbors_errorLoading": "Fout bij het laden van buren: {error}",
"neighbors_repeatersNeighbors": "Herhalingen Buren", "neighbors_repeatersNeighbours": "Herhalingen Buren",
"neighbors_noData": "Geen gegevens van buren beschikbaar.", "neighbors_noData": "Geen gegevens van buren beschikbaar.",
"channels_createPrivateChannelDesc": "Beveiligd met een geheime sleutel.", "channels_createPrivateChannelDesc": "Beveiligd met een geheime sleutel.",
"channels_createPrivateChannel": "Maak een Privé Kanaal", "channels_createPrivateChannel": "Maak een Privé Kanaal",
@@ -1575,6 +1575,7 @@
"notification_newNodesCount": "{count} {count, plural, =1{nieuw knooppunt} other{nieuwe knooppunten}}", "notification_newNodesCount": "{count} {count, plural, =1{nieuw knooppunt} other{nieuwe knooppunten}}",
"notification_newTypeDiscovered": "Nieuw {contactType} ontdekt", "notification_newTypeDiscovered": "Nieuw {contactType} ontdekt",
"notification_receivedNewMessage": "Nieuw bericht ontvangen", "notification_receivedNewMessage": "Nieuw bericht ontvangen",
"contacts_zeroHopContactAdvertFailed": "Mislukt om contact te verzenden",
"settings_gpxExportRepeatersSubtitle": "Exporteert repeaters / roomserver met een locatie naar GPX-bestand.", "settings_gpxExportRepeatersSubtitle": "Exporteert repeaters / roomserver met een locatie naar GPX-bestand.",
"settings_gpxExportRepeaters": "Exporteer repeaters / roomserver naar GPX", "settings_gpxExportRepeaters": "Exporteer repeaters / roomserver naar GPX",
"settings_gpxExportSuccess": "Succesvol GPX-bestand geëxporteerd.", "settings_gpxExportSuccess": "Succesvol GPX-bestand geëxporteerd.",
@@ -1590,15 +1591,6 @@
"settings_gpxExportAllContacts": "Alle contactlocaties", "settings_gpxExportAllContacts": "Alle contactlocaties",
"settings_gpxExportShareText": "Kaartgegevens geëxporteerd uit meshcore-open", "settings_gpxExportShareText": "Kaartgegevens geëxporteerd uit meshcore-open",
"settings_gpxExportShareSubject": "meshcore-open GPX kaartgegevens exporteren", "settings_gpxExportShareSubject": "meshcore-open GPX kaartgegevens exporteren",
"pathTrace_someHopsNoLocation": "Een of meer van de hops ontbreken een locatie!", "pathTrace_someHopsNoLocation": "Een of meer van de hops ontbreken een locatie!"
"map_removeLast": "Verwijder Laatste",
"pathTrace_clearTooltip": "Weg wissen",
"map_pathTraceCancelled": "Pad traceren geannuleerd",
"map_tapToAdd": "Tik op knooppunten om ze toe te voegen aan het pad",
"map_runTrace": "Padeshulp traceren",
"scanner_enableBluetooth": "Activeer Bluetooth",
"scanner_bluetoothOffMessage": "Zorg ervoor dat Bluetooth is ingeschakeld om naar apparaten te zoeken.",
"scanner_bluetoothOff": "Bluetooth is uitgeschakeld",
"snrIndicator_lastSeen": "Laatst gezien",
"snrIndicator_nearByRepeaters": "Nabije herhalingseenheden"
} }
+6 -14
View File
@@ -1356,12 +1356,12 @@
} }
} }
}, },
"repeater_neighbors": "Sąsiedzi", "repeater_neighbours": "Sąsiedzi",
"repeater_neighborsSubtitle": "Wyświetl sąsiedztwo zerowych hopów.", "repeater_neighboursSubtitle": "Wyświetl sąsiedztwo zerowych hopów.",
"neighbors_receivedData": "Otrzymano dane sąsiedztwa", "neighbors_receivedData": "Otrzymano dane sąsiedztwa",
"neighbors_requestTimedOut": "Sąsiedzi proszą o wyłączenie timingu.", "neighbors_requestTimedOut": "Sąsiedzi proszą o wyłączenie timingu.",
"neighbors_errorLoading": "Błąd podczas ładowania sąsiadów: {error}", "neighbors_errorLoading": "Błąd podczas ładowania sąsiadów: {error}",
"neighbors_repeatersNeighbors": "Powtarzacze Sąsiedzi", "neighbors_repeatersNeighbours": "Powtarzacze Sąsiedzi",
"neighbors_noData": "Brak danych dotyczących sąsiadów.", "neighbors_noData": "Brak danych dotyczących sąsiadów.",
"channels_joinPrivateChannelDesc": "Ręcznie wprowadź klucz tajny.", "channels_joinPrivateChannelDesc": "Ręcznie wprowadź klucz tajny.",
"channels_createPrivateChannel": "Utwórz Prywatny Kanał", "channels_createPrivateChannel": "Utwórz Prywatny Kanał",
@@ -1575,6 +1575,7 @@
"notification_newNodesCount": "{count} {count, plural, =1{nowy węzeł} few{nowe węzły} many{nowych węzłów} other{nowych węzłów}}", "notification_newNodesCount": "{count} {count, plural, =1{nowy węzeł} few{nowe węzły} many{nowych węzłów} other{nowych węzłów}}",
"notification_newTypeDiscovered": "Nowy {contactType} wykryty", "notification_newTypeDiscovered": "Nowy {contactType} wykryty",
"notification_receivedNewMessage": "Otrzymano nową wiadomość", "notification_receivedNewMessage": "Otrzymano nową wiadomość",
"contacts_zeroHopContactAdvertFailed": "Nie udało się wysłać kontaktu.",
"settings_gpxExportContacts": "Eksportuj towarzyszy do GPX", "settings_gpxExportContacts": "Eksportuj towarzyszy do GPX",
"settings_gpxExportRepeaters": "Eksportuj powtórki / serwer pokojowy do GPX", "settings_gpxExportRepeaters": "Eksportuj powtórki / serwer pokojowy do GPX",
"settings_gpxExportRepeatersSubtitle": "Eksportuje powtarzacze / roomserver z lokalizacją do pliku GPX.", "settings_gpxExportRepeatersSubtitle": "Eksportuje powtarzacze / roomserver z lokalizacją do pliku GPX.",
@@ -1590,15 +1591,6 @@
"settings_gpxExportChat": "Lokalizacje towarzyszy", "settings_gpxExportChat": "Lokalizacje towarzyszy",
"settings_gpxExportShareText": "Dane mapy wyeksportowane z meshcore-open", "settings_gpxExportShareText": "Dane mapy wyeksportowane z meshcore-open",
"settings_gpxExportShareSubject": "Eksport danych mapy GPX meshcore-open", "settings_gpxExportShareSubject": "Eksport danych mapy GPX meshcore-open",
"pathTrace_someHopsNoLocation": "Jeden lub więcej z chmieli nie ma określonej lokalizacji!", "pathTrace_someHopsNoLocation": "Jeden lub więcej z chmieli nie ma określonej lokalizacji!"
"map_pathTraceCancelled": "Śledzenie ścieżki anulowano.",
"map_runTrace": "Uruchom ślad ścieżki",
"pathTrace_clearTooltip": "Wyczyść ścieżkę",
"map_removeLast": "Usuń ostatni",
"map_tapToAdd": "Kliknij na węzły, aby dodać je do ścieżki.",
"scanner_bluetoothOffMessage": "Prosimy włączyć Bluetooth, aby przeskanować urządzenia.",
"scanner_bluetoothOff": "Bluetooth jest wyłączony",
"scanner_enableBluetooth": "Włącz Bluetooth",
"snrIndicator_lastSeen": "Ostatnio widziany",
"snrIndicator_nearByRepeaters": "Nadajniki w pobliżu"
} }
+6 -14
View File
@@ -1356,12 +1356,12 @@
} }
} }
}, },
"repeater_neighbors": "Vizinhos", "repeater_neighbours": "Vizinhos",
"neighbors_receivedData": "Dados dos Vizinhos Recebidos", "neighbors_receivedData": "Dados dos Vizinhos Recebidos",
"repeater_neighborsSubtitle": "Visualizar vizinhos de salto zero.", "repeater_neighboursSubtitle": "Visualizar vizinhos de salto zero.",
"neighbors_requestTimedOut": "Vizinhos solicitam tempo limite esgotado.", "neighbors_requestTimedOut": "Vizinhos solicitam tempo limite esgotado.",
"neighbors_errorLoading": "Erro ao carregar vizinhos: {error}", "neighbors_errorLoading": "Erro ao carregar vizinhos: {error}",
"neighbors_repeatersNeighbors": "Repetidores Vizinhos", "neighbors_repeatersNeighbours": "Repetidores Vizinhos",
"neighbors_noData": "Não estão disponíveis dados de vizinhos.", "neighbors_noData": "Não estão disponíveis dados de vizinhos.",
"channels_createPrivateChannelDesc": "Protegido com uma chave secreta.", "channels_createPrivateChannelDesc": "Protegido com uma chave secreta.",
"channels_joinPrivateChannelDesc": "Inserir uma chave secreta manualmente.", "channels_joinPrivateChannelDesc": "Inserir uma chave secreta manualmente.",
@@ -1575,6 +1575,7 @@
"notification_newNodesCount": "{count} {count, plural, =1{novo nó} other{novos nós}}", "notification_newNodesCount": "{count} {count, plural, =1{novo nó} other{novos nós}}",
"notification_newTypeDiscovered": "Novo {contactType} descoberto", "notification_newTypeDiscovered": "Novo {contactType} descoberto",
"notification_receivedNewMessage": "Nova mensagem recebida", "notification_receivedNewMessage": "Nova mensagem recebida",
"contacts_zeroHopContactAdvertFailed": "Falha ao enviar contato.",
"settings_gpxExportRepeaters": "Exportar repetidores / servidor de sala para GPX", "settings_gpxExportRepeaters": "Exportar repetidores / servidor de sala para GPX",
"settings_gpxExportRepeatersSubtitle": "Exporta repetidores / roomserver com localização para arquivo GPX.", "settings_gpxExportRepeatersSubtitle": "Exporta repetidores / roomserver com localização para arquivo GPX.",
"settings_gpxExportSuccess": "Arquivo GPX exportado com sucesso.", "settings_gpxExportSuccess": "Arquivo GPX exportado com sucesso.",
@@ -1590,15 +1591,6 @@
"settings_gpxExportAllContacts": "Todos os locais de contatos", "settings_gpxExportAllContacts": "Todos os locais de contatos",
"settings_gpxExportShareText": "Dados do mapa exportados do meshcore-open", "settings_gpxExportShareText": "Dados do mapa exportados do meshcore-open",
"settings_gpxExportShareSubject": "meshcore-open exportação de dados de mapa GPX", "settings_gpxExportShareSubject": "meshcore-open exportação de dados de mapa GPX",
"pathTrace_someHopsNoLocation": "Um ou mais dos lúpulos estão sem localização!", "pathTrace_someHopsNoLocation": "Um ou mais dos lúpulos estão sem localização!"
"map_runTrace": "Executar Traçado de Caminho",
"map_pathTraceCancelled": "Rastreamento de caminho cancelado.",
"pathTrace_clearTooltip": "Limpar caminho",
"map_removeLast": "Remover Último",
"map_tapToAdd": "Toque nos nós para adicioná-los ao caminho.",
"scanner_enableBluetooth": "Ative o Bluetooth",
"scanner_bluetoothOff": "Bluetooth está desativado",
"scanner_bluetoothOffMessage": "Por favor, ative o Bluetooth para escanear por dispositivos.",
"snrIndicator_nearByRepeaters": "Repetidores Próximos",
"snrIndicator_lastSeen": "Visto pela última vez"
} }
+6 -14
View File
@@ -472,8 +472,8 @@
"repeater_telemetrySubtitle": "Просмотр телеметрии датчиков и системной статистики", "repeater_telemetrySubtitle": "Просмотр телеметрии датчиков и системной статистики",
"repeater_cli": "CLI", "repeater_cli": "CLI",
"repeater_cliSubtitle": "Отправка команд репитеру", "repeater_cliSubtitle": "Отправка команд репитеру",
"repeater_neighbors": "Соседи", "repeater_neighbours": "Соседи",
"repeater_neighborsSubtitle": "Просмотр соседей на нулевом хопе.", "repeater_neighboursSubtitle": "Просмотр соседей на нулевом хопе.",
"repeater_settings": "Настройки", "repeater_settings": "Настройки",
"repeater_settingsSubtitle": "Настройка параметров репитера", "repeater_settingsSubtitle": "Настройка параметров репитера",
"repeater_statusTitle": "Статус репитера", "repeater_statusTitle": "Статус репитера",
@@ -666,7 +666,7 @@
"neighbors_receivedData": "Полученные данные о соседях", "neighbors_receivedData": "Полученные данные о соседях",
"neighbors_requestTimedOut": "Время ожидания данных о соседях истекло.", "neighbors_requestTimedOut": "Время ожидания данных о соседях истекло.",
"neighbors_errorLoading": "Ошибка загрузки соседей: {error}", "neighbors_errorLoading": "Ошибка загрузки соседей: {error}",
"neighbors_repeatersNeighbors": "Соседи репитеров", "neighbors_repeatersNeighbours": "Соседи репитеров",
"neighbors_noData": "Данные о соседях недоступны.", "neighbors_noData": "Данные о соседях недоступны.",
"neighbors_unknownContact": "Неизвестный {pubkey}", "neighbors_unknownContact": "Неизвестный {pubkey}",
"neighbors_heardA ago": "Слышали: {time} назад", "neighbors_heardA ago": "Слышали: {time} назад",
@@ -815,6 +815,7 @@
"notification_newNodesCount": "{count} {count, plural, =1{новый узел} few{новых узла} many{новых узлов} other{новых узлов}}", "notification_newNodesCount": "{count} {count, plural, =1{новый узел} few{новых узла} many{новых узлов} other{новых узлов}}",
"notification_newTypeDiscovered": "Обнаружен новый {contactType}", "notification_newTypeDiscovered": "Обнаружен новый {contactType}",
"notification_receivedNewMessage": "Получено новое сообщение", "notification_receivedNewMessage": "Получено новое сообщение",
"contacts_zeroHopContactAdvertSent": "Отправлено сообщение по объявлению.",
"settings_gpxExportRepeaters": "Экспортировать рипитеры / сервер комнаты в GPX", "settings_gpxExportRepeaters": "Экспортировать рипитеры / сервер комнаты в GPX",
"settings_gpxExportRepeatersSubtitle": "Экспортирует ретрансляторы / сервер комнат с местоположением в файл GPX.", "settings_gpxExportRepeatersSubtitle": "Экспортирует ретрансляторы / сервер комнат с местоположением в файл GPX.",
"settings_gpxExportContacts": "Экспортировать спутников в GPX", "settings_gpxExportContacts": "Экспортировать спутников в GPX",
@@ -830,15 +831,6 @@
"settings_gpxExportNoContacts": "Нет контактов для экспорта.", "settings_gpxExportNoContacts": "Нет контактов для экспорта.",
"settings_gpxExportShareText": "Данные карты экспортированы из meshcore-open", "settings_gpxExportShareText": "Данные карты экспортированы из meshcore-open",
"settings_gpxExportShareSubject": "meshcore-open экспорт данных карты GPX", "settings_gpxExportShareSubject": "meshcore-open экспорт данных карты GPX",
"pathTrace_someHopsNoLocation": "Одному или нескольким хмелям не указано местоположение!", "pathTrace_someHopsNoLocation": "Одному или нескольким хмелям не указано местоположение!"
"map_tapToAdd": "Нажимайте на узлы, чтобы добавить их в путь.",
"map_removeLast": "Удалить последний",
"map_pathTraceCancelled": "Отмена трассировки пути",
"pathTrace_clearTooltip": "Очистить путь",
"map_runTrace": "Запустить трассировку пути",
"scanner_enableBluetooth": "Включите Bluetooth",
"scanner_bluetoothOff": "Bluetooth выключен",
"scanner_bluetoothOffMessage": "Пожалуйста, включите Bluetooth, чтобы найти устройства.",
"snrIndicator_nearByRepeaters": "Ближайшие ретрансляторы",
"snrIndicator_lastSeen": "Последний раз видели"
} }
+6 -14
View File
@@ -1356,12 +1356,12 @@
} }
} }
}, },
"repeater_neighborsSubtitle": "Zobraziť susedné body bez skokov.", "repeater_neighboursSubtitle": "Zobraziť susedné body bez skokov.",
"neighbors_requestTimedOut": "Súďia žiadajú o časové ukončenie.", "neighbors_requestTimedOut": "Súďia žiadajú o časové ukončenie.",
"neighbors_receivedData": "Obdielo dáta suseda", "neighbors_receivedData": "Obdielo dáta suseda",
"repeater_neighbors": "Súsezný", "repeater_neighbours": "Súsezný",
"neighbors_errorLoading": "Chyba pri načítaní susedov: {error}", "neighbors_errorLoading": "Chyba pri načítaní susedov: {error}",
"neighbors_repeatersNeighbors": "Opakovadlá Súsezná", "neighbors_repeatersNeighbours": "Opakovadlá Súsezná",
"neighbors_noData": "Nie je dostupná žiadna informácia o susedoch.", "neighbors_noData": "Nie je dostupná žiadna informácia o susedoch.",
"channels_createPrivateChannel": "Vytvorte súkromný kanál", "channels_createPrivateChannel": "Vytvorte súkromný kanál",
"channels_joinPrivateChannel": "Pripojiť sa k súkromnému kanálu", "channels_joinPrivateChannel": "Pripojiť sa k súkromnému kanálu",
@@ -1575,6 +1575,7 @@
"notification_newNodesCount": "{count} {count, plural, =1{nový uzol} few{nové uzly} other{nových uzlov}}", "notification_newNodesCount": "{count} {count, plural, =1{nový uzol} few{nové uzly} other{nových uzlov}}",
"notification_newTypeDiscovered": "Nový {contactType} objavený", "notification_newTypeDiscovered": "Nový {contactType} objavený",
"notification_receivedNewMessage": "Prijatá nová správa", "notification_receivedNewMessage": "Prijatá nová správa",
"contacts_ShareContact": "Kopírovať kontakt do schránky",
"settings_gpxExportRepeatersSubtitle": "Exportuje repeater / roomserver s lokalitou do súboru GPX.", "settings_gpxExportRepeatersSubtitle": "Exportuje repeater / roomserver s lokalitou do súboru GPX.",
"settings_gpxExportContacts": "Export sprievodcov do GPX", "settings_gpxExportContacts": "Export sprievodcov do GPX",
"settings_gpxExportSuccess": "Úspešne exportovaný súbor GPX.", "settings_gpxExportSuccess": "Úspešne exportovaný súbor GPX.",
@@ -1590,15 +1591,6 @@
"settings_gpxExportChat": "Lokácie sprievodcov", "settings_gpxExportChat": "Lokácie sprievodcov",
"settings_gpxExportShareText": "Mapové údaje exportované z meshcore-open", "settings_gpxExportShareText": "Mapové údaje exportované z meshcore-open",
"settings_gpxExportShareSubject": "meshcore-open export dát GPX mapových údajov", "settings_gpxExportShareSubject": "meshcore-open export dát GPX mapových údajov",
"pathTrace_someHopsNoLocation": "Jedna alebo viac chmeľov chýba lokalita!", "pathTrace_someHopsNoLocation": "Jedna alebo viac chmeľov chýba lokalita!"
"pathTrace_clearTooltip": "Zmazať cestu",
"map_tapToAdd": "Kliknite na uzly, aby ste ich pridali k ceste.",
"map_removeLast": "Odstrániť posledný",
"map_runTrace": "Spustiť trasovaním cesty",
"map_pathTraceCancelled": "Zrušenie stopáže cesty bolo zrušené.",
"scanner_bluetoothOffMessage": "Prosím, zapnite Bluetooth, aby ste mohli skenovať pre zariadenia.",
"scanner_bluetoothOff": "Bluetooth je vypnutý",
"scanner_enableBluetooth": "Povolte Bluetooth",
"snrIndicator_lastSeen": "Naposledy videný",
"snrIndicator_nearByRepeaters": "Miestne opakovače"
} }
+6 -14
View File
@@ -1356,12 +1356,12 @@
} }
} }
}, },
"repeater_neighborsSubtitle": "Pogledati nič sosednjih hopjev.", "repeater_neighboursSubtitle": "Pogledati nič sosednjih hopjev.",
"repeater_neighbors": "Sosedi", "repeater_neighbours": "Sosedi",
"neighbors_receivedData": "Prejeto podatke o sosedih", "neighbors_receivedData": "Prejeto podatke o sosedih",
"neighbors_requestTimedOut": "Sosedi zahtevajo izklop po dogovoru.", "neighbors_requestTimedOut": "Sosedi zahtevajo izklop po dogovoru.",
"neighbors_errorLoading": "Napaka pri obnašanju sosedov: {error}", "neighbors_errorLoading": "Napaka pri obnašanju sosedov: {error}",
"neighbors_repeatersNeighbors": "Ponovitve Sosedi", "neighbors_repeatersNeighbours": "Ponovitve Sosedi",
"neighbors_noData": "Niso na voljo podatki o sosedih.", "neighbors_noData": "Niso na voljo podatki o sosedih.",
"channels_joinPrivateChannel": "Pridružite se zasebni skupini", "channels_joinPrivateChannel": "Pridružite se zasebni skupini",
"channels_createPrivateChannelDesc": "Varno zaklenjeno s skrivnim ključem.", "channels_createPrivateChannelDesc": "Varno zaklenjeno s skrivnim ključem.",
@@ -1575,6 +1575,7 @@
"notification_newNodesCount": "{count} {count, plural, =1{novo vozlišče} =2{novi vozlišči} few{nova vozlišča} other{novih vozlišč}}", "notification_newNodesCount": "{count} {count, plural, =1{novo vozlišče} =2{novi vozlišči} few{nova vozlišča} other{novih vozlišč}}",
"notification_newTypeDiscovered": "Odkrito novo {contactType}", "notification_newTypeDiscovered": "Odkrito novo {contactType}",
"notification_receivedNewMessage": "Prejeto novo sporočilo", "notification_receivedNewMessage": "Prejeto novo sporočilo",
"contacts_ShareContact": "Kopiraj stik v Odložišče",
"settings_gpxExportAll": "Izvozi vse kontakte v GPX", "settings_gpxExportAll": "Izvozi vse kontakte v GPX",
"settings_gpxExportContacts": "Izvoz spremljevalcev v GPX", "settings_gpxExportContacts": "Izvoz spremljevalcev v GPX",
"settings_gpxExportRepeatersSubtitle": "Izvozi ponovljene oddajnike / strežnik sobe z lokacijo v datoteko GPX.", "settings_gpxExportRepeatersSubtitle": "Izvozi ponovljene oddajnike / strežnik sobe z lokacijo v datoteko GPX.",
@@ -1590,15 +1591,6 @@
"settings_gpxExportNoContacts": "Ni stikov za izvoz.", "settings_gpxExportNoContacts": "Ni stikov za izvoz.",
"settings_gpxExportNotAvailable": "Ni podprto na vašem napravi/operacijskem sistemu", "settings_gpxExportNotAvailable": "Ni podprto na vašem napravi/operacijskem sistemu",
"settings_gpxExportShareSubject": "meshcore-open izvoz podatkov GPX karte", "settings_gpxExportShareSubject": "meshcore-open izvoz podatkov GPX karte",
"pathTrace_someHopsNoLocation": "Ena ali več hmelju manjka lokacija!", "pathTrace_someHopsNoLocation": "Ena ali več hmelju manjka lokacija!"
"map_tapToAdd": "Pritisnite na vozlišča, da jih dodate poti.",
"map_removeLast": "Odstrani Zadnji",
"map_runTrace": "Zaženi sledenje poti",
"pathTrace_clearTooltip": "Počisti pot",
"map_pathTraceCancelled": "Spremljanje poti je prekinjeno.",
"scanner_enableBluetooth": "Omogočite Bluetooth",
"scanner_bluetoothOffMessage": "Prosimo, vklopite Bluetooth, da lahko poiščete naprave.",
"scanner_bluetoothOff": "Bluetooth je izklopljen",
"snrIndicator_lastSeen": "Zadnjič videno",
"snrIndicator_nearByRepeaters": "Bližnji ponovitelji"
} }
+6 -14
View File
@@ -1356,12 +1356,12 @@
} }
} }
}, },
"repeater_neighbors": "Grannar", "repeater_neighbours": "Grannar",
"repeater_neighborsSubtitle": "Visa noll hoppgrannar.", "repeater_neighboursSubtitle": "Visa noll hoppgrannar.",
"neighbors_receivedData": "Mottagna grannars data", "neighbors_receivedData": "Mottagna grannars data",
"neighbors_requestTimedOut": "Grannar begär tidsinställd utskick.", "neighbors_requestTimedOut": "Grannar begär tidsinställd utskick.",
"neighbors_errorLoading": "Fel vid inläsning av grannar: {error}", "neighbors_errorLoading": "Fel vid inläsning av grannar: {error}",
"neighbors_repeatersNeighbors": "Upprepar grannar", "neighbors_repeatersNeighbours": "Upprepar grannar",
"neighbors_noData": "Inga grannuppgifter finns tillgängliga.", "neighbors_noData": "Inga grannuppgifter finns tillgängliga.",
"channels_createPrivateChannel": "Skapa en privat kanal", "channels_createPrivateChannel": "Skapa en privat kanal",
"channels_joinPrivateChannel": "Gå med i en Privat Kanal", "channels_joinPrivateChannel": "Gå med i en Privat Kanal",
@@ -1575,6 +1575,7 @@
"notification_newNodesCount": "{count} {count, plural, =1{ny nod} other{nya noder}}", "notification_newNodesCount": "{count} {count, plural, =1{ny nod} other{nya noder}}",
"notification_newTypeDiscovered": "Ny {contactType} upptäckt", "notification_newTypeDiscovered": "Ny {contactType} upptäckt",
"notification_receivedNewMessage": "Nytt meddelande mottaget", "notification_receivedNewMessage": "Nytt meddelande mottaget",
"contacts_ShareContactZeroHop": "Dela kontakt via annons",
"settings_gpxExportAll": "Exportera alla kontakter till GPX", "settings_gpxExportAll": "Exportera alla kontakter till GPX",
"settings_gpxExportRepeatersSubtitle": "Exporterar repeater / roomserver med plats till GPX-fil.", "settings_gpxExportRepeatersSubtitle": "Exporterar repeater / roomserver med plats till GPX-fil.",
"settings_gpxExportSuccess": "Har exporterat GPX-fil med framgång", "settings_gpxExportSuccess": "Har exporterat GPX-fil med framgång",
@@ -1590,15 +1591,6 @@
"settings_gpxExportAllContacts": "Alla kontakters platser", "settings_gpxExportAllContacts": "Alla kontakters platser",
"settings_gpxExportShareSubject": "meshcore-open export av GPX-kartdata", "settings_gpxExportShareSubject": "meshcore-open export av GPX-kartdata",
"settings_gpxExportShareText": "Kartdata exporterad från meshcore-open", "settings_gpxExportShareText": "Kartdata exporterad från meshcore-open",
"pathTrace_someHopsNoLocation": "En eller flera av humlen saknar en plats!", "pathTrace_someHopsNoLocation": "En eller flera av humlen saknar en plats!"
"pathTrace_clearTooltip": "Rensa väg",
"map_pathTraceCancelled": "Sökvägsspårning avbruten.",
"map_runTrace": "Kör spårsökning",
"map_tapToAdd": "Tryck på noder för att lägga till dem i banan.",
"map_removeLast": "Ta bort sista",
"scanner_enableBluetooth": "Aktivera Bluetooth",
"scanner_bluetoothOffMessage": "Vänligen aktivera Bluetooth för att söka efter enheter.",
"scanner_bluetoothOff": "Bluetooth är avstängt",
"snrIndicator_lastSeen": "Senast sedd",
"snrIndicator_nearByRepeaters": "Närliggande uppreparstationer"
} }
+6 -14
View File
@@ -1357,12 +1357,12 @@
} }
} }
}, },
"repeater_neighbors": "Сусіди", "repeater_neighbours": "Сусіди",
"repeater_neighborsSubtitle": "Показати сусідів нульового стрибка.", "repeater_neighboursSubtitle": "Показати сусідів нульового стрибка.",
"neighbors_receivedData": "Дані сусідів отримано", "neighbors_receivedData": "Дані сусідів отримано",
"neighbors_requestTimedOut": "Час запиту сусідів вичерпано.", "neighbors_requestTimedOut": "Час запиту сусідів вичерпано.",
"neighbors_errorLoading": "Помилка завантаження сусідів: {error}", "neighbors_errorLoading": "Помилка завантаження сусідів: {error}",
"neighbors_repeatersNeighbors": "Ретранслятори-сусіди", "neighbors_repeatersNeighbours": "Ретранслятори-сусіди",
"neighbors_noData": "Дані про сусідів недоступні.", "neighbors_noData": "Дані про сусідів недоступні.",
"channels_createPrivateChannelDesc": "Захищено секретним ключем.", "channels_createPrivateChannelDesc": "Захищено секретним ключем.",
"channels_joinPrivateChannel": "Приєднатися до приватного каналу", "channels_joinPrivateChannel": "Приєднатися до приватного каналу",
@@ -1575,6 +1575,7 @@
"notification_newNodesCount": "{count} {count, plural, =1{новий вузол} few{нових вузли} many{нових вузлів} other{нових вузлів}}", "notification_newNodesCount": "{count} {count, plural, =1{новий вузол} few{нових вузли} many{нових вузлів} other{нових вузлів}}",
"notification_newTypeDiscovered": "Виявлено новий {contactType}", "notification_newTypeDiscovered": "Виявлено новий {contactType}",
"notification_receivedNewMessage": "Отримано нове повідомлення", "notification_receivedNewMessage": "Отримано нове повідомлення",
"contacts_ShareContactZeroHop": "Поділитися контактом за оголошенням",
"settings_gpxExportRepeaters": "Експортувати ретранслятори / сервер кімнати до GPX", "settings_gpxExportRepeaters": "Експортувати ретранслятори / сервер кімнати до GPX",
"settings_gpxExportRepeatersSubtitle": "Експортує ретранслятори / сервер кімнати з місцезнаходженням у файл GPX.", "settings_gpxExportRepeatersSubtitle": "Експортує ретранслятори / сервер кімнати з місцезнаходженням у файл GPX.",
"settings_gpxExportSuccess": "Успішно експортовано файл GPX.", "settings_gpxExportSuccess": "Успішно експортовано файл GPX.",
@@ -1590,15 +1591,6 @@
"settings_gpxExportShareText": "Дані карти експортовані з meshcore-open", "settings_gpxExportShareText": "Дані карти експортовані з meshcore-open",
"settings_gpxExportAllContacts": "Усі місця контактів", "settings_gpxExportAllContacts": "Усі місця контактів",
"settings_gpxExportShareSubject": "експорт даних карти meshcore-open у форматі GPX", "settings_gpxExportShareSubject": "експорт даних карти meshcore-open у форматі GPX",
"pathTrace_someHopsNoLocation": "Одне або більше хмелів відсутнє місце розташування!", "pathTrace_someHopsNoLocation": "Одне або більше хмелів відсутнє місце розташування!"
"map_tapToAdd": "Натисніть на вузли, щоб додати їх до шляху",
"map_runTrace": "Виконати трасування шляху",
"pathTrace_clearTooltip": "Очистити шлях",
"map_removeLast": "Видалити останній",
"map_pathTraceCancelled": "Відмінується трасування шляху",
"scanner_enableBluetooth": "Увімкніть Bluetooth",
"scanner_bluetoothOffMessage": "Будь ласка, увімкніть Bluetooth, щоб сканувати пристрої.",
"scanner_bluetoothOff": "Bluetooth вимкнено",
"snrIndicator_lastSeen": "Останній раз бачили",
"snrIndicator_nearByRepeaters": "Ближні ретранслятори"
} }
+6 -14
View File
@@ -900,8 +900,8 @@
"repeater_telemetrySubtitle": "查看传感器和系统状态的数据。", "repeater_telemetrySubtitle": "查看传感器和系统状态的数据。",
"repeater_cli": "命令行界面", "repeater_cli": "命令行界面",
"repeater_cliSubtitle": "向复用器发送指令", "repeater_cliSubtitle": "向复用器发送指令",
"repeater_neighbors": "邻居", "repeater_neighbours": "邻居",
"repeater_neighborsSubtitle": "查看邻居节点(无需中间节点)。", "repeater_neighboursSubtitle": "查看邻居节点(无需中间节点)。",
"repeater_settings": "设置", "repeater_settings": "设置",
"repeater_settingsSubtitle": "配置重复器参数", "repeater_settingsSubtitle": "配置重复器参数",
"repeater_statusTitle": "重复器状态", "repeater_statusTitle": "重复器状态",
@@ -1271,7 +1271,7 @@
} }
} }
}, },
"neighbors_repeatersNeighbors": "重复使用的邻居", "neighbors_repeatersNeighbours": "重复使用的邻居",
"neighbors_noData": "没有可用的邻居信息。", "neighbors_noData": "没有可用的邻居信息。",
"neighbors_unknownContact": "Unknown {pubkey}", "neighbors_unknownContact": "Unknown {pubkey}",
"@neighbors_unknownContact": { "@neighbors_unknownContact": {
@@ -1575,6 +1575,7 @@
"notification_newNodesCount": "{count} 个新节点", "notification_newNodesCount": "{count} 个新节点",
"notification_newTypeDiscovered": "发现新 {contactType}", "notification_newTypeDiscovered": "发现新 {contactType}",
"notification_receivedNewMessage": "收到新消息", "notification_receivedNewMessage": "收到新消息",
"contacts_contactAdvertCopyFailed": "将广告复制到剪贴板操作失败。",
"settings_gpxExportRepeaters": "导出重复器/房间服务器到GPX", "settings_gpxExportRepeaters": "导出重复器/房间服务器到GPX",
"settings_gpxExportRepeatersSubtitle": "导出带有位置的重复器/房间服务器到GPX文件。", "settings_gpxExportRepeatersSubtitle": "导出带有位置的重复器/房间服务器到GPX文件。",
"settings_gpxExportContactsSubtitle": "导出带有位置的伙伴到GPX文件。", "settings_gpxExportContactsSubtitle": "导出带有位置的伙伴到GPX文件。",
@@ -1590,15 +1591,6 @@
"settings_gpxExportNoContacts": "没有联系人可导出", "settings_gpxExportNoContacts": "没有联系人可导出",
"settings_gpxExportShareText": "来自meshcore-open的导出地图数据", "settings_gpxExportShareText": "来自meshcore-open的导出地图数据",
"settings_gpxExportShareSubject": "meshcore-open GPX 地图数据导出", "settings_gpxExportShareSubject": "meshcore-open GPX 地图数据导出",
"pathTrace_someHopsNoLocation": "其中一个或多个啤酒花缺少位置!", "pathTrace_someHopsNoLocation": "其中一个或多个啤酒花缺少位置!"
"map_tapToAdd": "点击节点将其添加到路径中",
"pathTrace_clearTooltip": "清除路径",
"map_pathTraceCancelled": "路径跟踪已取消",
"map_removeLast": "删除最后一个",
"map_runTrace": "运行路径跟踪",
"scanner_bluetoothOffMessage": "请打开蓝牙功能,以便搜索设备。",
"scanner_bluetoothOff": "蓝牙已关闭",
"scanner_enableBluetooth": "启用蓝牙",
"snrIndicator_lastSeen": "最近访问",
"snrIndicator_nearByRepeaters": "附近的重复器"
} }
+6 -1
View File
@@ -1,5 +1,6 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_localizations/flutter_localizations.dart'; import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:meshcore_open/services/sparse_location_logger.dart';
import 'l10n/app_localizations.dart'; import 'l10n/app_localizations.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
@@ -33,7 +34,7 @@ void main() async {
final appDebugLogService = AppDebugLogService(); final appDebugLogService = AppDebugLogService();
final backgroundService = BackgroundService(); final backgroundService = BackgroundService();
final mapTileCacheService = MapTileCacheService(); final mapTileCacheService = MapTileCacheService();
final sparseLocationLogger = SparseLocationLogger();
// Load settings // Load settings
await appSettingsService.loadSettings(); await appSettingsService.loadSettings();
@@ -56,6 +57,7 @@ void main() async {
bleDebugLogService: bleDebugLogService, bleDebugLogService: bleDebugLogService,
appDebugLogService: appDebugLogService, appDebugLogService: appDebugLogService,
backgroundService: backgroundService, backgroundService: backgroundService,
sparseLocationLogger: sparseLocationLogger,
); );
await connector.loadContactCache(); await connector.loadContactCache();
@@ -76,6 +78,7 @@ void main() async {
bleDebugLogService: bleDebugLogService, bleDebugLogService: bleDebugLogService,
appDebugLogService: appDebugLogService, appDebugLogService: appDebugLogService,
mapTileCacheService: mapTileCacheService, mapTileCacheService: mapTileCacheService,
sparseLocationLogger: sparseLocationLogger,
), ),
); );
} }
@@ -89,6 +92,7 @@ class MeshCoreApp extends StatelessWidget {
final BleDebugLogService bleDebugLogService; final BleDebugLogService bleDebugLogService;
final AppDebugLogService appDebugLogService; final AppDebugLogService appDebugLogService;
final MapTileCacheService mapTileCacheService; final MapTileCacheService mapTileCacheService;
final SparseLocationLogger sparseLocationLogger;
const MeshCoreApp({ const MeshCoreApp({
super.key, super.key,
@@ -100,6 +104,7 @@ class MeshCoreApp extends StatelessWidget {
required this.bleDebugLogService, required this.bleDebugLogService,
required this.appDebugLogService, required this.appDebugLogService,
required this.mapTileCacheService, required this.mapTileCacheService,
required this.sparseLocationLogger,
}); });
@override @override
+3 -7
View File
@@ -119,7 +119,7 @@ class Contact {
final pathBytes = _pathBytesForDisplay; final pathBytes = _pathBytesForDisplay;
Uint8List? traceBytes; Uint8List? traceBytes;
if (pathBytes.isEmpty) { if (pathLength <= 0) {
traceBytes = Uint8List(1); traceBytes = Uint8List(1);
traceBytes[0] = publicKey[0]; traceBytes[0] = publicKey[0];
return traceBytes; return traceBytes;
@@ -160,9 +160,9 @@ class Contact {
} }
static Contact? fromFrame(Uint8List data) { static Contact? fromFrame(Uint8List data) {
if (data.isEmpty) return null; if (data.length < contactFrameSize) return null;
if (data[0] != respCodeContact) return null; if (data[0] != respCodeContact) return null;
try {
final pubKey = Uint8List.fromList( final pubKey = Uint8List.fromList(
data.sublist(contactPubKeyOffset, contactPubKeyOffset + pubKeySize), data.sublist(contactPubKeyOffset, contactPubKeyOffset + pubKeySize),
); );
@@ -197,10 +197,6 @@ class Contact {
longitude: lon, longitude: lon,
lastSeen: DateTime.fromMillisecondsSinceEpoch(lastmod * 1000), lastSeen: DateTime.fromMillisecondsSinceEpoch(lastmod * 1000),
); );
} catch (e) {
// If parsing fails, return null
return null;
}
} }
@override @override
+1 -1
View File
@@ -55,7 +55,7 @@ class AppDebugLogScreen extends StatelessWidget {
child: hasEntries child: hasEntries
? ListView.separated( ? ListView.separated(
itemCount: entries.length, itemCount: entries.length,
separatorBuilder: (_, _) => const Divider(height: 1), separatorBuilder: (_, __) => const Divider(height: 1),
itemBuilder: (context, index) { itemBuilder: (context, index) {
final entry = entries[index]; final entry = entries[index];
return ListTile( return ListTile(
+1 -1
View File
@@ -100,7 +100,7 @@ class _BleDebugLogScreenState extends State<BleDebugLogScreen> {
itemCount: showingFrames itemCount: showingFrames
? entries.length ? entries.length
: rawEntries.length, : rawEntries.length,
separatorBuilder: (_, _) => const Divider(height: 1), separatorBuilder: (_, __) => const Divider(height: 1),
itemBuilder: (context, index) { itemBuilder: (context, index) {
if (showingFrames) { if (showingFrames) {
final entry = entries[index]; final entry = entries[index];
+1 -2
View File
@@ -901,8 +901,7 @@ class _ChannelChatScreenState extends State<ChannelChatScreen> {
Navigator.push( Navigator.push(
context, context,
MaterialPageRoute( MaterialPageRoute(
builder: (context) => builder: (context) => ChannelMessagePathScreen(message: message),
ChannelMessagePathScreen(message: message, channelMessage: true),
), ),
); );
} }
+11 -49
View File
@@ -17,27 +17,18 @@ import '../models/contact.dart';
class ChannelMessagePathScreen extends StatelessWidget { class ChannelMessagePathScreen extends StatelessWidget {
final ChannelMessage message; final ChannelMessage message;
final bool channelMessage;
const ChannelMessagePathScreen({ const ChannelMessagePathScreen({super.key, required this.message});
super.key,
required this.message,
this.channelMessage = false,
});
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Consumer<MeshCoreConnector>( return Consumer<MeshCoreConnector>(
builder: (context, connector, _) { builder: (context, connector, _) {
final l10n = context.l10n; final l10n = context.l10n;
final primaryPathTmp = _selectPrimaryPath( final primaryPath = _selectPrimaryPath(
message.pathBytes, message.pathBytes,
message.pathVariants, message.pathVariants,
); );
final primaryPath = !channelMessage && !message.isOutgoing
? Uint8List.fromList(primaryPathTmp.reversed.toList())
: primaryPathTmp;
final hops = _buildPathHops(primaryPath, connector.contacts, l10n); final hops = _buildPathHops(primaryPath, connector.contacts, l10n);
final hasHopDetails = primaryPath.isNotEmpty; final hasHopDetails = primaryPath.isNotEmpty;
final observedLabel = _formatObservedHops( final observedLabel = _formatObservedHops(
@@ -46,6 +37,7 @@ class ChannelMessagePathScreen extends StatelessWidget {
l10n, l10n,
); );
final extraPaths = _otherPaths(primaryPath, message.pathVariants); final extraPaths = _otherPaths(primaryPath, message.pathVariants);
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
title: Text(l10n.channelPath_title), title: Text(l10n.channelPath_title),
@@ -58,9 +50,9 @@ class ChannelMessagePathScreen extends StatelessWidget {
MaterialPageRoute( MaterialPageRoute(
builder: (context) => PathTraceMapScreen( builder: (context) => PathTraceMapScreen(
title: context.l10n.contacts_repeaterPathTrace, title: context.l10n.contacts_repeaterPathTrace,
path: primaryPath, path: Uint8List.fromList(primaryPath),
flipPathRound: true, flipPathRound: true,
reversePathRound: !message.isOutgoing && !channelMessage, reversePathRound: true,
), ),
), ),
), ),
@@ -70,7 +62,7 @@ class ChannelMessagePathScreen extends StatelessWidget {
tooltip: l10n.channelPath_viewMap, tooltip: l10n.channelPath_viewMap,
onPressed: hasHopDetails onPressed: hasHopDetails
? () { ? () {
_openPathMap(context, channelMessage: channelMessage); _openPathMap(context);
} }
: null, : null,
), ),
@@ -165,11 +157,7 @@ class ChannelMessagePathScreen extends StatelessWidget {
), ),
subtitle: Text(_formatPathPrefixes(variants[i])), subtitle: Text(_formatPathPrefixes(variants[i])),
trailing: const Icon(Icons.map_outlined, size: 20), trailing: const Icon(Icons.map_outlined, size: 20),
onTap: () => _openPathMap( onTap: () => _openPathMap(context, initialPath: variants[i]),
context,
initialPath: variants[i],
channelMessage: channelMessage,
),
), ),
), ),
], ],
@@ -260,18 +248,13 @@ class ChannelMessagePathScreen extends StatelessWidget {
); );
} }
void _openPathMap( void _openPathMap(BuildContext context, {Uint8List? initialPath}) {
BuildContext context, {
Uint8List? initialPath,
bool channelMessage = false,
}) {
Navigator.push( Navigator.push(
context, context,
MaterialPageRoute( MaterialPageRoute(
builder: (context) => ChannelMessagePathMapScreen( builder: (context) => ChannelMessagePathMapScreen(
message: message, message: message,
initialPath: initialPath, initialPath: initialPath,
channelMessage: channelMessage,
), ),
), ),
); );
@@ -281,13 +264,11 @@ class ChannelMessagePathScreen extends StatelessWidget {
class ChannelMessagePathMapScreen extends StatefulWidget { class ChannelMessagePathMapScreen extends StatefulWidget {
final ChannelMessage message; final ChannelMessage message;
final Uint8List? initialPath; final Uint8List? initialPath;
final bool channelMessage;
const ChannelMessagePathMapScreen({ const ChannelMessagePathMapScreen({
super.key, super.key,
required this.message, required this.message,
this.initialPath, this.initialPath,
this.channelMessage = false,
}); });
@override @override
@@ -342,18 +323,11 @@ class _ChannelMessagePathMapScreenState
primaryPath, primaryPath,
widget.message.pathVariants, widget.message.pathVariants,
); );
final selectedPathTmp = _resolveSelectedPath( final selectedPath = _resolveSelectedPath(
_selectedPath, _selectedPath,
observedPaths, observedPaths,
primaryPath, primaryPath,
); );
final selectedPath =
((!widget.message.isOutgoing && !widget.channelMessage) ||
(widget.message.isOutgoing && widget.channelMessage))
? Uint8List.fromList(selectedPathTmp.reversed.toList())
: selectedPathTmp;
final selectedIndex = _indexForPath(selectedPath, observedPaths); final selectedIndex = _indexForPath(selectedPath, observedPaths);
final hops = _buildPathHops( final hops = _buildPathHops(
selectedPath, selectedPath,
@@ -362,24 +336,12 @@ class _ChannelMessagePathMapScreenState
); );
final points = <LatLng>[]; final points = <LatLng>[];
print(
'outgoing: ${widget.message.isOutgoing}, channelMsg: ${widget.channelMessage}',
);
if ((widget.message.isOutgoing && !widget.channelMessage) ||
(widget.message.isOutgoing && widget.channelMessage)) {
points.add(LatLng(connector.selfLatitude!, connector.selfLongitude!));
}
for (final hop in hops) { for (final hop in hops) {
if (hop.hasLocation) { if (hop.hasLocation) {
points.add(hop.position!); points.add(hop.position!);
} }
} }
if ((!widget.message.isOutgoing && !widget.channelMessage) ||
(!widget.message.isOutgoing && widget.channelMessage)) {
points.add(LatLng(connector.selfLatitude!, connector.selfLongitude!)); points.add(LatLng(connector.selfLatitude!, connector.selfLongitude!));
}
final polylines = points.length > 1 final polylines = points.length > 1
? [ ? [
@@ -632,7 +594,7 @@ class _ChannelMessagePathMapScreenState
: ListView.separated( : ListView.separated(
padding: const EdgeInsets.symmetric(vertical: 4), padding: const EdgeInsets.symmetric(vertical: 4),
itemCount: hops.length, itemCount: hops.length,
separatorBuilder: (_, _) => const Divider(height: 1), separatorBuilder: (_, __) => const Divider(height: 1),
itemBuilder: (context, index) { itemBuilder: (context, index) {
final hop = hops[index]; final hop = hops[index];
return ListTile( return ListTile(
+3 -2
View File
@@ -3,7 +3,6 @@ import 'dart:math';
import 'dart:typed_data'; import 'dart:typed_data';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:meshcore_open/widgets/app_bar.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:uuid/uuid.dart'; import 'package:uuid/uuid.dart';
@@ -15,6 +14,7 @@ import '../storage/community_store.dart';
import '../utils/dialog_utils.dart'; import '../utils/dialog_utils.dart';
import '../utils/disconnect_navigation_mixin.dart'; import '../utils/disconnect_navigation_mixin.dart';
import '../utils/route_transitions.dart'; import '../utils/route_transitions.dart';
import '../widgets/battery_indicator.dart';
import '../widgets/list_filter_widget.dart'; import '../widgets/list_filter_widget.dart';
import '../widgets/empty_state.dart'; import '../widgets/empty_state.dart';
import '../widgets/qr_code_display.dart'; import '../widgets/qr_code_display.dart';
@@ -116,7 +116,8 @@ class _ChannelsScreenState extends State<ChannelsScreen>
canPop: allowBack, canPop: allowBack,
child: Scaffold( child: Scaffold(
appBar: AppBar( appBar: AppBar(
title: AppBarTitle(context.l10n.channels_title), leading: BatteryIndicator(connector: connector),
title: Text(context.l10n.channels_title),
centerTitle: true, centerTitle: true,
automaticallyImplyLeading: false, automaticallyImplyLeading: false,
actions: [ actions: [
+3 -40
View File
@@ -437,20 +437,6 @@ class _ChatScreenState extends State<ChatScreen> {
builder: (context) => Consumer<PathHistoryService>( builder: (context) => Consumer<PathHistoryService>(
builder: (context, pathService, _) { builder: (context, pathService, _) {
final paths = pathService.getRecentPaths(widget.contact.publicKeyHex); final paths = pathService.getRecentPaths(widget.contact.publicKeyHex);
final repeatersList = List.of(connector.directRepeaters)
..sort((a, b) => b.ranking.compareTo(a.ranking));
final directRepeater = repeatersList.isEmpty
? null
: repeatersList.first;
final secondDirectRepeater = repeatersList.length < 2
? null
: repeatersList.elementAt(1);
final thirdDirectRepeater = repeatersList.length < 3
? null
: repeatersList.elementAt(2);
return AlertDialog( return AlertDialog(
title: Row( title: Row(
children: [ children: [
@@ -492,38 +478,15 @@ class _ChatScreenState extends State<ChatScreen> {
], ],
const SizedBox(height: 8), const SizedBox(height: 8),
...paths.map((path) { ...paths.map((path) {
final isDirectRepeater =
directRepeater != null &&
path.pathBytes.isNotEmpty &&
directRepeater.pubkeyFirstByte ==
path.pathBytes.first;
final isSecondDirectRepeater =
secondDirectRepeater != null &&
path.pathBytes.isNotEmpty &&
secondDirectRepeater.pubkeyFirstByte ==
path.pathBytes.first;
final isThirdDirectRepeater =
thirdDirectRepeater != null &&
path.pathBytes.isNotEmpty &&
thirdDirectRepeater.pubkeyFirstByte ==
path.pathBytes.first;
Color color = Colors.grey;
if (isDirectRepeater) {
color = Colors.green;
} else if (isSecondDirectRepeater) {
color = Colors.yellow;
} else if (isThirdDirectRepeater) {
color = Colors.red;
} else if (path.wasFloodDiscovery) {
color = Colors.blue;
}
return Card( return Card(
margin: const EdgeInsets.symmetric(vertical: 4), margin: const EdgeInsets.symmetric(vertical: 4),
child: ListTile( child: ListTile(
dense: true, dense: true,
leading: CircleAvatar( leading: CircleAvatar(
radius: 16, radius: 16,
backgroundColor: color, backgroundColor: path.wasFloodDiscovery
? Colors.blue
: Colors.green,
child: Text( child: Text(
'${path.hopCount}', '${path.hopCount}',
style: const TextStyle(fontSize: 12), style: const TextStyle(fontSize: 12),
+6 -16
View File
@@ -3,8 +3,6 @@ import 'dart:async';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:meshcore_open/screens/path_trace_map.dart'; import 'package:meshcore_open/screens/path_trace_map.dart';
import 'package:meshcore_open/utils/app_logger.dart';
import 'package:meshcore_open/widgets/app_bar.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import '../connector/meshcore_connector.dart'; import '../connector/meshcore_connector.dart';
@@ -18,6 +16,7 @@ import '../utils/dialog_utils.dart';
import '../utils/disconnect_navigation_mixin.dart'; import '../utils/disconnect_navigation_mixin.dart';
import '../utils/emoji_utils.dart'; import '../utils/emoji_utils.dart';
import '../utils/route_transitions.dart'; import '../utils/route_transitions.dart';
import '../widgets/battery_indicator.dart';
import '../widgets/list_filter_widget.dart'; import '../widgets/list_filter_widget.dart';
import '../widgets/empty_state.dart'; import '../widgets/empty_state.dart';
import '../widgets/quick_switch_bar.dart'; import '../widgets/quick_switch_bar.dart';
@@ -91,7 +90,6 @@ class _ContactsScreenState extends State<ContactsScreen>
_frameSubscription = connector.receivedFrames.listen((frame) { _frameSubscription = connector.receivedFrames.listen((frame) {
if (frame.isEmpty) return; if (frame.isEmpty) return;
final frameBuffer = BufferReader(frame); final frameBuffer = BufferReader(frame);
try {
final code = frameBuffer.readUInt8(); final code = frameBuffer.readUInt8();
if (code == respCodeExportContact) { if (code == respCodeExportContact) {
@@ -132,9 +130,7 @@ class _ContactsScreenState extends State<ContactsScreen>
if (_pendingOperations.contains(ContactOperationType.export)) { if (_pendingOperations.contains(ContactOperationType.export)) {
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(context).showSnackBar(
SnackBar( SnackBar(content: Text(context.l10n.contacts_contactAdvertCopied)),
content: Text(context.l10n.contacts_contactAdvertCopied),
),
); );
} }
@@ -147,9 +143,7 @@ class _ContactsScreenState extends State<ContactsScreen>
if (_pendingOperations.contains(ContactOperationType.import)) { if (_pendingOperations.contains(ContactOperationType.import)) {
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(context).showSnackBar(
SnackBar( SnackBar(content: Text(context.l10n.contacts_contactImportFailed)),
content: Text(context.l10n.contacts_contactImportFailed),
),
); );
} }
@@ -170,12 +164,6 @@ class _ContactsScreenState extends State<ContactsScreen>
_pendingOperations.clear(); _pendingOperations.clear();
} }
} catch (e) {
appLogger.error(
'Error processing received frame: $e',
tag: 'ContactsScreen',
);
}
}); });
} }
@@ -241,7 +229,9 @@ class _ContactsScreenState extends State<ContactsScreen>
canPop: allowBack, canPop: allowBack,
child: Scaffold( child: Scaffold(
appBar: AppBar( appBar: AppBar(
title: AppBarTitle(context.l10n.contacts_title), leading: BatteryIndicator(connector: connector),
title: Text(context.l10n.contacts_title),
centerTitle: true,
automaticallyImplyLeading: false, automaticallyImplyLeading: false,
actions: [ actions: [
PopupMenuButton( PopupMenuButton(
+20 -152
View File
@@ -1,11 +1,8 @@
import 'dart:math'; import 'dart:math';
import 'dart:typed_data';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_map/flutter_map.dart'; import 'package:flutter_map/flutter_map.dart';
import 'package:latlong2/latlong.dart'; import 'package:latlong2/latlong.dart';
import 'package:meshcore_open/screens/path_trace_map.dart';
import 'package:meshcore_open/widgets/app_bar.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import '../connector/meshcore_connector.dart'; import '../connector/meshcore_connector.dart';
@@ -18,6 +15,7 @@ import '../services/map_marker_service.dart';
import '../services/map_tile_cache_service.dart'; import '../services/map_tile_cache_service.dart';
import '../utils/contact_search.dart'; import '../utils/contact_search.dart';
import '../utils/route_transitions.dart'; import '../utils/route_transitions.dart';
import '../widgets/battery_indicator.dart';
import '../widgets/quick_switch_bar.dart'; import '../widgets/quick_switch_bar.dart';
import 'channels_screen.dart'; import 'channels_screen.dart';
import 'chat_screen.dart'; import 'chat_screen.dart';
@@ -50,13 +48,9 @@ class _MapScreenState extends State<MapScreen> {
final MapMarkerService _markerService = MapMarkerService(); final MapMarkerService _markerService = MapMarkerService();
final Set<String> _hiddenMarkerIds = {}; final Set<String> _hiddenMarkerIds = {};
Set<String> _removedMarkerIds = {}; Set<String> _removedMarkerIds = {};
bool _isBuildingPathTrace = false;
bool _isSelectingPoi = false; bool _isSelectingPoi = false;
bool _hasInitializedMap = false; bool _hasInitializedMap = false;
bool _removedMarkersLoaded = false; bool _removedMarkersLoaded = false;
final List<int> _pathTrace = [];
final List<LatLng> _points = [];
final List<Polyline> _polylines = [];
bool _legendExpanded = false; bool _legendExpanded = false;
@override @override
@@ -105,7 +99,7 @@ class _MapScreenState extends State<MapScreen> {
double _zoomFromStdDev(double latStdDev, double lonStdDev) { double _zoomFromStdDev(double latStdDev, double lonStdDev) {
final maxSpread = max(latStdDev, lonStdDev); final maxSpread = max(latStdDev, lonStdDev);
if (maxSpread <= 0) return 13.0; if (maxSpread <= 0) return 13.0;
// Approximate: each zoom level halves the visible area // Approzimate: each zoom level halves the visible area
// ~0.01 degrees spread -> zoom 13, ~0.1 -> zoom 10, ~1.0 -> zoom 7 // ~0.01 degrees spread -> zoom 13, ~0.1 -> zoom 10, ~1.0 -> zoom 7
final zoom = 10.0 - log(maxSpread * 10 + 1) / ln10 * 3; final zoom = 10.0 - log(maxSpread * 10 + 1) / ln10 * 3;
return zoom.clamp(4.0, 15.0); return zoom.clamp(4.0, 15.0);
@@ -154,19 +148,6 @@ class _MapScreenState extends State<MapScreen> {
.where((c) => c.hasLocation) .where((c) => c.hasLocation)
.toList(); .toList();
_polylines.clear();
_polylines.addAll(
_points.length > 1
? [
Polyline(
points: _points,
strokeWidth: 4,
color: Colors.blueAccent,
),
]
: <Polyline>[],
);
// Calculate center and zoom of all nodes, or default to (0, 0) // Calculate center and zoom of all nodes, or default to (0, 0)
LatLng center = const LatLng(0, 0); LatLng center = const LatLng(0, 0);
double initialZoom = 10.0; double initialZoom = 10.0;
@@ -262,16 +243,11 @@ class _MapScreenState extends State<MapScreen> {
canPop: allowBack, canPop: allowBack,
child: Scaffold( child: Scaffold(
appBar: AppBar( appBar: AppBar(
title: AppBarTitle(context.l10n.map_title), leading: BatteryIndicator(connector: connector),
title: Text(context.l10n.map_title),
centerTitle: true, centerTitle: true,
automaticallyImplyLeading: false, automaticallyImplyLeading: false,
actions: [ actions: [
if (!_isBuildingPathTrace)
IconButton(
icon: const Icon(Icons.radar),
onPressed: () => _startPath(),
tooltip: context.l10n.contacts_pathTrace,
),
PopupMenuButton( PopupMenuButton(
itemBuilder: (context) => [ itemBuilder: (context) => [
PopupMenuItem( PopupMenuItem(
@@ -299,6 +275,17 @@ class _MapScreenState extends State<MapScreen> {
), ),
), ),
), ),
PopupMenuItem(
child: Row(
children: [
const Icon(Icons.refresh),
const SizedBox(width: 8),
Text(context.l10n.map_updateMyLocation),
],
),
onTap: () =>
connector.sparseLocationLogger?.updateMyLocation(),
),
], ],
icon: const Icon(Icons.more_vert), icon: const Icon(Icons.more_vert),
), ),
@@ -359,8 +346,6 @@ class _MapScreenState extends State<MapScreen> {
MapTileCacheService.userAgentPackageName, MapTileCacheService.userAgentPackageName,
maxZoom: 19, maxZoom: 19,
), ),
if (_polylines.isNotEmpty && _isBuildingPathTrace)
PolylineLayer(polylines: _polylines),
MarkerLayer( MarkerLayer(
markers: [ markers: [
if (highlightPosition != null) if (highlightPosition != null)
@@ -417,12 +402,7 @@ class _MapScreenState extends State<MapScreen> {
), ),
], ],
), ),
if (!_isBuildingPathTrace) _buildLegend(contactsWithLocation.length, sharedMarkers.length),
_buildLegend(
contactsWithLocation.length,
sharedMarkers.length,
),
if (_isBuildingPathTrace) _buildPathTraceOverlay(),
], ],
), ),
bottomNavigationBar: SafeArea( bottomNavigationBar: SafeArea(
@@ -466,11 +446,7 @@ class _MapScreenState extends State<MapScreen> {
width: 35, width: 35,
height: 35, height: 35,
child: GestureDetector( child: GestureDetector(
onLongPress: () => onTap: () => _showNodeInfo(context, contact),
_isBuildingPathTrace ? _showNodeInfo(context, contact) : null,
onTap: () => _isBuildingPathTrace
? _addToPath(context, contact)
: _showNodeInfo(context, contact),
child: Column( child: Column(
children: [ children: [
Container( Container(
@@ -642,7 +618,7 @@ class _MapScreenState extends State<MapScreen> {
Widget _buildLegendItem(IconData icon, String label, Color color) { Widget _buildLegendItem(IconData icon, String label, Color color) {
return Padding( return Padding(
padding: const EdgeInsets.symmetric(vertical: 1.0), padding: const EdgeInsets.symmetric(vertical: 4.0),
child: Row( child: Row(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
@@ -825,7 +801,7 @@ class _MapScreenState extends State<MapScreen> {
color: _getNodeColor(contact.type), color: _getNodeColor(contact.type),
), ),
const SizedBox(width: 8), const SizedBox(width: 8),
Expanded(child: SelectableText(contact.name)), Expanded(child: Text(contact.name)),
], ],
), ),
content: Column( content: Column(
@@ -996,7 +972,7 @@ class _MapScreenState extends State<MapScreen> {
), ),
), ),
const SizedBox(height: 2), const SizedBox(height: 2),
SelectableText(value, style: const TextStyle(fontSize: 14)), Text(value, style: const TextStyle(fontSize: 14)),
], ],
), ),
); );
@@ -1490,114 +1466,6 @@ class _MapScreenState extends State<MapScreen> {
return context.l10n.time_allTime; return context.l10n.time_allTime;
} }
} }
void _addToPath(BuildContext context, Contact contact) {
setState(() {
_pathTrace.add(
contact.publicKey[0],
); // Add first 16 bytes of public key to path trace
_points.add(LatLng(contact.latitude!, contact.longitude!));
});
}
void _startPath() {
setState(() {
_isBuildingPathTrace = true;
_pathTrace.clear();
_points.clear();
_polylines.clear();
});
}
void _removePath() {
setState(() {
_pathTrace.removeLast(); // Remove last node from path trace
_points.removeLast(); // Remove last point from points list
_polylines.clear(); // Clear polylines
});
}
Widget _buildPathTraceOverlay() {
final l10n = context.l10n;
return Positioned(
top: 16,
left: 16,
right: 16,
child: Card(
child: Padding(
padding: const EdgeInsets.all(12.0),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Text(
l10n.contacts_pathTrace,
style: TextStyle(fontWeight: FontWeight.bold),
),
if (_pathTrace.isEmpty) const SizedBox(height: 8),
if (_pathTrace.isEmpty)
Text(l10n.map_tapToAdd, style: TextStyle(fontSize: 12)),
const SizedBox(height: 6),
if (_pathTrace.isNotEmpty)
Text(
"${l10n.path_currentPathLabel} ${formatDistance(getPathDistanceMeters(_points))}",
style: TextStyle(fontSize: 12, color: Colors.grey[700]),
),
SelectableText(
_pathTrace
.map((b) => b.toRadixString(16).padLeft(2, '0'))
.join(','),
style: TextStyle(fontSize: 18),
),
const SizedBox(height: 6),
Row(
mainAxisSize: MainAxisSize.min,
children: [
if (_pathTrace.isNotEmpty)
ElevatedButton(
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => PathTraceMapScreen(
title: l10n.contacts_pathTrace,
path: Uint8List.fromList(_pathTrace),
),
),
);
setState(() {
_isBuildingPathTrace = false;
});
},
child: Text(l10n.map_runTrace),
),
if (_pathTrace.isNotEmpty)
ElevatedButton(
onPressed: _removePath,
child: Text(l10n.map_removeLast),
),
if (_pathTrace.isEmpty)
ElevatedButton(
onPressed: () {
setState(() {
_isBuildingPathTrace = false;
_pathTrace.clear();
_points.clear();
_polylines.clear();
});
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(l10n.map_pathTraceCancelled)),
);
},
child: Text(l10n.common_cancel),
),
],
),
],
),
),
),
);
}
} }
class _MarkerPayload { class _MarkerPayload {
@@ -1,7 +1,6 @@
import 'dart:async'; import 'dart:async';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:meshcore_open/utils/app_logger.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import '../l10n/l10n.dart'; import '../l10n/l10n.dart';
import '../models/contact.dart'; import '../models/contact.dart';
@@ -12,28 +11,28 @@ import '../services/repeater_command_service.dart';
import '../widgets/path_management_dialog.dart'; import '../widgets/path_management_dialog.dart';
import '../widgets/snr_indicator.dart'; import '../widgets/snr_indicator.dart';
class NeighborsScreen extends StatefulWidget { class NeighboursScreen extends StatefulWidget {
final Contact repeater; final Contact repeater;
final String password; final String password;
const NeighborsScreen({ const NeighboursScreen({
super.key, super.key,
required this.repeater, required this.repeater,
required this.password, required this.password,
}); });
@override @override
State<NeighborsScreen> createState() => _NeighborsScreenState(); State<NeighboursScreen> createState() => _NeighboursScreenState();
} }
class _NeighborsScreenState extends State<NeighborsScreen> { class _NeighboursScreenState extends State<NeighboursScreen> {
static const int _reqNeighborsKeyLen = 4; static const int _reqNeighboursKeyLen = 4;
static const int _statusPayloadOffset = 8; static const int _statusPayloadOffset = 8;
static const int _statusStatsSize = 52; static const int _statusStatsSize = 52;
static const int _statusResponseBytes = static const int _statusResponseBytes =
_statusPayloadOffset + _statusStatsSize; _statusPayloadOffset + _statusStatsSize;
Uint8List _tagData = Uint8List(4); Uint8List _tagData = Uint8List(4);
int _neighborCount = 0; int _neighbourCount = 0;
bool _isLoading = false; bool _isLoading = false;
bool _isLoaded = false; bool _isLoaded = false;
@@ -42,7 +41,7 @@ class _NeighborsScreenState extends State<NeighborsScreen> {
StreamSubscription<Uint8List>? _frameSubscription; StreamSubscription<Uint8List>? _frameSubscription;
RepeaterCommandService? _commandService; RepeaterCommandService? _commandService;
PathSelection? _pendingStatusSelection; PathSelection? _pendingStatusSelection;
List<Map<String, dynamic>>? _parsedNeighbors; List<Map<String, dynamic>>? _parsedNeighbours;
@override @override
void initState() { void initState() {
@@ -50,7 +49,7 @@ class _NeighborsScreenState extends State<NeighborsScreen> {
final connector = Provider.of<MeshCoreConnector>(context, listen: false); final connector = Provider.of<MeshCoreConnector>(context, listen: false);
_commandService = RepeaterCommandService(connector); _commandService = RepeaterCommandService(connector);
_setupMessageListener(); _setupMessageListener();
_loadNeighbors(); _loadNeighbours();
_hasData = false; _hasData = false;
} }
@@ -63,12 +62,13 @@ class _NeighborsScreenState extends State<NeighborsScreen> {
if (frame[0] == respCodeSent) { if (frame[0] == respCodeSent) {
_tagData = frame.sublist(2, 6); _tagData = frame.sublist(2, 6);
//_timeEstment = frame.buffer.asByteData().getUint32(6, Endian.little);
} }
// Check if it's a binary response // Check if it's a binary response
if (frame[0] == pushCodeBinaryResponse && if (frame[0] == pushCodeBinaryResponse &&
listEquals(frame.sublist(2, 6), _tagData)) { listEquals(frame.sublist(2, 6), _tagData)) {
_handleNeighborsResponse(connector, frame.sublist(6)); _handleNeighboursResponse(connector, frame.sublist(6));
} }
}); });
} }
@@ -91,14 +91,13 @@ class _NeighborsScreenState extends State<NeighborsScreen> {
return '${h}h ${m2}m'; return '${h}h ${m2}m';
} }
static List<Map<String, dynamic>> parseNeighborsData( static List<Map<String, dynamic>> parseNeighboursData(
BufferReader buffer, BufferReader buffer,
int resultsCount, int resultsCount,
) { ) {
final Map<int, Map<String, dynamic>> neighbors = {}; final Map<int, Map<String, dynamic>> neighbours = {};
try {
for (var i = 0; i < resultsCount; i++) { for (var i = 0; i < resultsCount; i++) {
final neighborData = neighbors.putIfAbsent( final neighbourData = neighbours.putIfAbsent(
i, i,
() => { () => {
'contact': null, 'contact': null,
@@ -107,43 +106,35 @@ class _NeighborsScreenState extends State<NeighborsScreen> {
'snr': <double>{}, 'snr': <double>{},
}, },
); );
neighborData['publicKey'] = buffer.readBytes(_reqNeighborsKeyLen); neighbourData['publicKey'] = buffer.readBytes(_reqNeighboursKeyLen);
neighborData['lastHeard'] = buffer.readUInt32LE(); neighbourData['lastHeard'] = buffer.readUInt32LE();
neighborData['snr'] = buffer.readInt8() / 4.0; neighbourData['snr'] = buffer.readInt8() / 4.0;
} }
return neighbors.values.toList(); return neighbours.values.toList();
} catch (e) {
appLogger.error(
'Error parsing neighbors data: $e',
tag: 'NeighborsScreen',
);
return [];
}
} }
void _handleNeighborsResponse(MeshCoreConnector connector, Uint8List frame) { void _handleNeighboursResponse(MeshCoreConnector connector, Uint8List frame) {
final buffer = BufferReader(frame); final buffer = BufferReader(frame);
try { final neighbourCount = buffer.readUInt16LE();
final neighborCount = buffer.readUInt16LE(); final parsedNeighbours = parseNeighboursData(buffer, buffer.readUInt16LE());
final parsedNeighbors = parseNeighborsData(buffer, buffer.readUInt16LE());
connector.contacts.where((c) => c.type == advTypeRepeater).forEach(( connector.contacts.where((c) => c.type == advTypeRepeater).forEach((
repeater, repeater,
) { ) {
for (var neighborData in parsedNeighbors) { for (var neighbourData in parsedNeighbours) {
final publicKey = neighborData['publicKey']; final publicKey = neighbourData['publicKey'];
if (listEquals( if (listEquals(
repeater.publicKey.sublist(0, _reqNeighborsKeyLen), repeater.publicKey.sublist(0, _reqNeighboursKeyLen),
publicKey, publicKey,
)) { )) {
neighborData['contact'] = repeater; neighbourData['contact'] = repeater;
} }
} }
}); });
setState(() { setState(() {
_parsedNeighbors = parsedNeighbors; _parsedNeighbours = parsedNeighbours;
_neighborCount = neighborCount; _neighbourCount = neighbourCount;
}); });
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(context).showSnackBar(
@@ -159,9 +150,6 @@ class _NeighborsScreenState extends State<NeighborsScreen> {
_isLoaded = true; _isLoaded = true;
_hasData = true; _hasData = true;
}); });
} catch (e) {
appLogger.error('Error handling neighbors response: $e');
}
} }
Contact _resolveRepeater(MeshCoreConnector connector) { Contact _resolveRepeater(MeshCoreConnector connector) {
@@ -171,7 +159,7 @@ class _NeighborsScreenState extends State<NeighborsScreen> {
); );
} }
Future<void> _loadNeighbors() async { Future<void> _loadNeighbours() async {
if (_commandService == null) return; if (_commandService == null) return;
setState(() { setState(() {
@@ -184,17 +172,17 @@ class _NeighborsScreenState extends State<NeighborsScreen> {
final selection = await connector.preparePathForContactSend(repeater); final selection = await connector.preparePathForContactSend(repeater);
_pendingStatusSelection = selection; _pendingStatusSelection = selection;
//[version][number of requested neighbors][offset_16bit][order by][len of public key] //[version][number of requested neighbours][offset_16bit][order by][len of public key]
final frame = buildSendBinaryReq( final frame = buildSendBinaryReq(
repeater.publicKey, repeater.publicKey,
payload: Uint8List.fromList([ payload: Uint8List.fromList([
reqTypeGetNeighbors, reqTypeGetNeighbours,
0x00, 0x00,
0x0F, 0x0F,
0x00, 0x00,
0x00, 0x00,
0x00, 0x00,
_reqNeighborsKeyLen, _reqNeighboursKeyLen,
]), ]),
); );
await connector.sendFrame(frame); await connector.sendFrame(frame);
@@ -270,7 +258,7 @@ class _NeighborsScreenState extends State<NeighborsScreen> {
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
Text( Text(
l10n.neighbors_repeatersNeighbors, l10n.neighbors_repeatersNeighbours,
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold), style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
), ),
Text( Text(
@@ -357,7 +345,7 @@ class _NeighborsScreenState extends State<NeighborsScreen> {
child: CircularProgressIndicator(strokeWidth: 2), child: CircularProgressIndicator(strokeWidth: 2),
) )
: const Icon(Icons.refresh), : const Icon(Icons.refresh),
onPressed: _isLoading ? null : _loadNeighbors, onPressed: _isLoading ? null : _loadNeighbours,
tooltip: l10n.repeater_refresh, tooltip: l10n.repeater_refresh,
), ),
], ],
@@ -365,13 +353,13 @@ class _NeighborsScreenState extends State<NeighborsScreen> {
body: SafeArea( body: SafeArea(
top: false, top: false,
child: RefreshIndicator( child: RefreshIndicator(
onRefresh: _loadNeighbors, onRefresh: _loadNeighbours,
child: ListView( child: ListView(
padding: const EdgeInsets.all(16), padding: const EdgeInsets.all(16),
children: [ children: [
if (!_isLoaded && if (!_isLoaded &&
!_hasData && !_hasData &&
(_parsedNeighbors == null || _parsedNeighbors!.isEmpty)) (_parsedNeighbours == null || _parsedNeighbours!.isEmpty))
Center( Center(
child: Text( child: Text(
l10n.neighbors_noData, l10n.neighbors_noData,
@@ -380,9 +368,10 @@ class _NeighborsScreenState extends State<NeighborsScreen> {
), ),
if (_isLoaded || if (_isLoaded ||
_hasData && _hasData &&
!(_parsedNeighbors == null || _parsedNeighbors!.isEmpty)) !(_parsedNeighbours == null ||
_buildNeighborsInfoCard( _parsedNeighbours!.isEmpty))
"${l10n.repeater_neighbors} - $_neighborCount", _buildNeighboursInfoCard(
"${l10n.repeater_neighbours} - $_neighbourCount",
), ),
], ],
), ),
@@ -391,7 +380,7 @@ class _NeighborsScreenState extends State<NeighborsScreen> {
); );
} }
Widget _buildNeighborsInfoCard(String title) { Widget _buildNeighboursInfoCard(String title) {
final connector = Provider.of<MeshCoreConnector>(context, listen: false); final connector = Provider.of<MeshCoreConnector>(context, listen: false);
return Card( return Card(
child: Padding( child: Padding(
@@ -416,7 +405,7 @@ class _NeighborsScreenState extends State<NeighborsScreen> {
], ],
), ),
const Divider(), const Divider(),
for (final entry in _parsedNeighbors!.asMap().entries) for (final entry in _parsedNeighbours!.asMap().entries)
_buildInfoRow( _buildInfoRow(
entry.value['contact'] != null entry.value['contact'] != null
? entry.value['contact'].name ? entry.value['contact'].name
@@ -441,7 +430,6 @@ class _NeighborsScreenState extends State<NeighborsScreen> {
double snr, double snr,
int spreadingFactor, int spreadingFactor,
) { ) {
final snrUi = snrUiFromSNR(snr, spreadingFactor);
return Padding( return Padding(
padding: const EdgeInsets.symmetric(vertical: 3), padding: const EdgeInsets.symmetric(vertical: 3),
child: Row( child: Row(
@@ -455,15 +443,9 @@ class _NeighborsScreenState extends State<NeighborsScreen> {
style: const TextStyle(fontWeight: FontWeight.w500), style: const TextStyle(fontWeight: FontWeight.w500),
), ),
subtitle: Text(value), subtitle: Text(value),
trailing: Column( trailing: SNRIcon(
mainAxisAlignment: MainAxisAlignment.center, snr: snr,
children: [ snrLevels: getSNRfromSF(spreadingFactor),
Icon(snrUi.icon, color: snrUi.color, size: 18.0),
Text(
snrUi.text,
style: TextStyle(fontSize: 10, color: snrUi.color),
),
],
), ),
), ),
), ),
+86 -143
View File
@@ -10,30 +10,12 @@ import 'package:meshcore_open/connector/meshcore_protocol.dart';
import 'package:meshcore_open/l10n/l10n.dart'; import 'package:meshcore_open/l10n/l10n.dart';
import 'package:meshcore_open/models/contact.dart'; import 'package:meshcore_open/models/contact.dart';
import 'package:meshcore_open/services/map_tile_cache_service.dart'; import 'package:meshcore_open/services/map_tile_cache_service.dart';
import 'package:meshcore_open/utils/app_logger.dart';
import 'package:meshcore_open/widgets/snr_indicator.dart'; import 'package:meshcore_open/widgets/snr_indicator.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
double getPathDistanceMeters(List<LatLng> points) {
if (points.length <= 1) return 0.0;
double distanceMeters = 0.0;
final distanceCalculator = Distance();
for (int i = 0; i < points.length - 1; i++) {
distanceMeters += distanceCalculator(points[i], points[i + 1]);
}
return distanceMeters;
}
String formatDistance(double distanceMeters) {
return '(${(distanceMeters / 1609.34).toStringAsFixed(2)} Miles / ${(distanceMeters / 1000).toStringAsFixed(2)} Km)';
}
class PathTraceData { class PathTraceData {
final Uint8List pathData; final Uint8List pathData;
final List<double> snrData; final Uint8List snrData;
final Map<int, Contact> pathContacts; final Map<int, Contact> pathContacts;
PathTraceData({ PathTraceData({
@@ -46,7 +28,6 @@ class PathTraceData {
class PathTraceMapScreen extends StatefulWidget { class PathTraceMapScreen extends StatefulWidget {
final String title; final String title;
final Uint8List path; final Uint8List path;
final int? repeaterId;
final bool flipPathRound; final bool flipPathRound;
final bool reversePathRound; final bool reversePathRound;
@@ -54,7 +35,6 @@ class PathTraceMapScreen extends StatefulWidget {
super.key, super.key,
required this.title, required this.title,
required this.path, required this.path,
this.repeaterId,
this.flipPathRound = false, this.flipPathRound = false,
this.reversePathRound = false, this.reversePathRound = false,
}); });
@@ -70,6 +50,7 @@ class _PathTraceMapScreenState extends State<PathTraceMapScreen> {
bool _isLoading = false; bool _isLoading = false;
bool _failed2Loaded = false; bool _failed2Loaded = false;
bool _hasData = false; bool _hasData = false;
bool _noLocationErr = false;
PathTraceData? _traceData; PathTraceData? _traceData;
List<LatLng> _points = <LatLng>[]; List<LatLng> _points = <LatLng>[];
List<Polyline> _polylines = []; List<Polyline> _polylines = [];
@@ -77,7 +58,7 @@ class _PathTraceMapScreenState extends State<PathTraceMapScreen> {
double _initialZoom = 2.0; double _initialZoom = 2.0;
LatLngBounds? _bounds; LatLngBounds? _bounds;
ValueKey<String> _mapKey = const ValueKey('initial'); ValueKey<String> _mapKey = const ValueKey('initial');
double _pathDistanceMeters = 0.0; double _pathDistance = 0.0;
String _formatPathPrefixes(Uint8List pathBytes) { String _formatPathPrefixes(Uint8List pathBytes) {
return pathBytes return pathBytes
@@ -99,7 +80,7 @@ class _PathTraceMapScreenState extends State<PathTraceMapScreen> {
super.dispose(); super.dispose();
} }
Uint8List addReturnPath(Uint8List pathBytes) { Uint8List addReturnpath(Uint8List pathBytes) {
Uint8List? traceBytes; Uint8List? traceBytes;
final len = (pathBytes.length + pathBytes.length - 1); final len = (pathBytes.length + pathBytes.length - 1);
traceBytes = Uint8List(len); traceBytes = Uint8List(len);
@@ -112,11 +93,23 @@ class _PathTraceMapScreenState extends State<PathTraceMapScreen> {
return traceBytes; return traceBytes;
} }
double getPathDistance() {
double totalDistance = 0.0;
final distanceCalculator = Distance();
for (int i = 0; i < _points.length - 1; i++) {
totalDistance += distanceCalculator(_points[i], _points[i + 1]);
}
return totalDistance;
}
Future<void> _doPathTrace() async { Future<void> _doPathTrace() async {
if (mounted) { if (mounted) {
setState(() { setState(() {
_isLoading = true; _isLoading = true;
_failed2Loaded = false; _failed2Loaded = false;
_noLocationErr = false;
}); });
} }
@@ -127,13 +120,11 @@ class _PathTraceMapScreenState extends State<PathTraceMapScreen> {
: widget.path; : widget.path;
if (widget.flipPathRound) { if (widget.flipPathRound) {
path = addReturnPath(pathTmp); path = addReturnpath(pathTmp);
} else { } else {
path = pathTmp; path = pathTmp;
} }
print('Initiating path trace with path: ${_formatPathPrefixes(path)}');
final connector = Provider.of<MeshCoreConnector>(context, listen: false); final connector = Provider.of<MeshCoreConnector>(context, listen: false);
final frame = buildTraceReq( final frame = buildTraceReq(
DateTime.now().millisecondsSinceEpoch ~/ 1000, DateTime.now().millisecondsSinceEpoch ~/ 1000,
@@ -151,7 +142,6 @@ class _PathTraceMapScreenState extends State<PathTraceMapScreen> {
_frameSubscription = connector.receivedFrames.listen((frame) { _frameSubscription = connector.receivedFrames.listen((frame) {
if (frame.isEmpty) return; if (frame.isEmpty) return;
final frameBuffer = BufferReader(frame); final frameBuffer = BufferReader(frame);
try {
final code = frameBuffer.readUInt8(); final code = frameBuffer.readUInt8();
if (code == respCodeSent) { if (code == respCodeSent) {
@@ -170,15 +160,6 @@ class _PathTraceMapScreenState extends State<PathTraceMapScreen> {
}); });
} }
if (code == respCodeErr) {
_timeoutTimer?.cancel();
if (!mounted) return;
setState(() {
_isLoading = false;
_failed2Loaded = true;
});
}
// Check if it's a binary response // Check if it's a binary response
if (frame.length > 8 && if (frame.length > 8 &&
code == pushCodeTraceData && code == pushCodeTraceData &&
@@ -190,16 +171,6 @@ class _PathTraceMapScreenState extends State<PathTraceMapScreen> {
_handleTraceResponse(frame); _handleTraceResponse(frame);
} }
} }
} catch (e) {
_timeoutTimer?.cancel();
if (!mounted) return;
setState(() {
_isLoading = false;
_failed2Loaded = true;
});
// Handle any parsing errors gracefully
appLogger.error('Error parsing frame: $e', tag: 'PathTraceMapScreen');
}
}); });
} }
@@ -207,7 +178,6 @@ class _PathTraceMapScreenState extends State<PathTraceMapScreen> {
final connector = Provider.of<MeshCoreConnector>(context, listen: false); final connector = Provider.of<MeshCoreConnector>(context, listen: false);
final buffer = BufferReader(frame); final buffer = BufferReader(frame);
try {
buffer.skipBytes(2); // Skip push code and reserved byte buffer.skipBytes(2); // Skip push code and reserved byte
int pathLength = buffer.readUInt8(); int pathLength = buffer.readUInt8();
buffer.skipBytes(5); // Skip Flag byte and tag data buffer.skipBytes(5); // Skip Flag byte and tag data
@@ -217,9 +187,7 @@ class _PathTraceMapScreenState extends State<PathTraceMapScreen> {
Map<int, Contact> pathContacts = {}; Map<int, Contact> pathContacts = {};
connector.contacts.where((c) => c.type != advTypeChat).forEach(( connector.contacts.where((c) => c.type != advTypeChat).forEach((repeater) {
repeater,
) {
for (var repeaterData in pathData) { for (var repeaterData in pathData) {
if (listEquals( if (listEquals(
repeater.publicKey.sublist(0, 1), repeater.publicKey.sublist(0, 1),
@@ -235,7 +203,7 @@ class _PathTraceMapScreenState extends State<PathTraceMapScreen> {
_hasData = true; _hasData = true;
_traceData = PathTraceData( _traceData = PathTraceData(
pathData: pathData, pathData: pathData,
snrData: snrData.map((e) => e.toSigned(8).toDouble() / 4).toList(), snrData: snrData,
pathContacts: pathContacts, pathContacts: pathContacts,
); );
_points = <LatLng>[]; _points = <LatLng>[];
@@ -247,6 +215,8 @@ class _PathTraceMapScreenState extends State<PathTraceMapScreen> {
contact.latitude != null && contact.latitude != null &&
contact.longitude != null) { contact.longitude != null) {
_points.add(LatLng(contact.latitude!, contact.longitude!)); _points.add(LatLng(contact.latitude!, contact.longitude!));
} else {
_noLocationErr = true;
} }
} }
_polylines = _points.length > 1 _polylines = _points.length > 1
@@ -259,28 +229,14 @@ class _PathTraceMapScreenState extends State<PathTraceMapScreen> {
] ]
: <Polyline>[]; : <Polyline>[];
_initialCenter = _points.isNotEmpty _initialCenter = _points.isNotEmpty ? _points.first : const LatLng(0, 0);
? _points.first
: const LatLng(0, 0);
_initialZoom = _points.isNotEmpty ? 13.0 : 2.0; _initialZoom = _points.isNotEmpty ? 13.0 : 2.0;
_bounds = _points.length > 1 ? LatLngBounds.fromPoints(_points) : null; _bounds = _points.length > 1 ? LatLngBounds.fromPoints(_points) : null;
_mapKey = ValueKey( _mapKey = ValueKey(
'${context.l10n.pathTrace_you},${_formatPathPrefixes(_traceData!.pathData)}', '${context.l10n.pathTrace_you},${_formatPathPrefixes(_traceData!.pathData)}',
); );
_pathDistanceMeters = getPathDistanceMeters(_points); _pathDistance = getPathDistance();
}); });
} catch (e) {
appLogger.error(
'Error handling trace response: $e',
tag: 'PathTraceMapScreen',
);
if (mounted) {
setState(() {
_isLoading = false;
_failed2Loaded = true;
});
}
}
} }
@override @override
@@ -323,7 +279,20 @@ class _PathTraceMapScreenState extends State<PathTraceMapScreen> {
top: false, top: false,
child: Stack( child: Stack(
children: [ children: [
if (!_hasData) if (_noLocationErr)
Center(
child: Card(
color: Colors.red,
child: Padding(
padding: EdgeInsets.all(12),
child: Text(
context.l10n.pathTrace_someHopsNoLocation,
style: TextStyle(color: Colors.white),
),
),
),
),
if (!_hasData && !_noLocationErr)
Center( Center(
child: Column( child: Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
@@ -335,11 +304,43 @@ class _PathTraceMapScreenState extends State<PathTraceMapScreen> {
], ],
), ),
), ),
if (_hasData) _buildMapPathTrace(context, tileCache), if (_hasData && !_noLocationErr)
FlutterMap(
key: _mapKey,
options: MapOptions(
initialCenter: _initialCenter!,
initialZoom: _initialZoom,
initialCameraFit: _bounds == null
? null
: CameraFit.bounds(
bounds: _bounds!,
padding: const EdgeInsets.all(64),
maxZoom: 16,
),
minZoom: 2.0,
maxZoom: 18.0,
),
children: [
TileLayer(
urlTemplate: kMapTileUrlTemplate,
tileProvider: tileCache.tileProvider,
userAgentPackageName:
MapTileCacheService.userAgentPackageName,
maxZoom: 19,
),
if (_polylines.isNotEmpty)
PolylineLayer(polylines: _polylines),
if (_traceData!.pathData.isNotEmpty)
MarkerLayer(
markers: _buildHopMarkers(_traceData!.pathData),
),
],
),
if (_points.isEmpty && if (_points.isEmpty &&
!_hasData && !_hasData &&
!_isLoading && !_isLoading &&
!_failed2Loaded) !_failed2Loaded &&
!_noLocationErr)
Center( Center(
child: Card( child: Card(
color: Colors.white.withValues(alpha: 0.9), color: Colors.white.withValues(alpha: 0.9),
@@ -351,7 +352,8 @@ class _PathTraceMapScreenState extends State<PathTraceMapScreen> {
), ),
), ),
), ),
if (_hasData) _buildLegendCard(context, _traceData!), if (_hasData && !_noLocationErr)
_buildLegendCard(context, _traceData!),
], ],
), ),
), ),
@@ -363,8 +365,7 @@ class _PathTraceMapScreenState extends State<PathTraceMapScreen> {
List<Marker> _buildHopMarkers(List<int> pathData) { List<Marker> _buildHopMarkers(List<int> pathData) {
return [ return [
for (final hop in pathData) for (final hop in pathData)
if (_traceData!.pathContacts[hop] != null && if (_traceData!.pathContacts[hop]!.hasLocation)
_traceData!.pathContacts[hop]!.hasLocation)
Marker( Marker(
point: LatLng( point: LatLng(
_traceData!.pathContacts[hop]!.latitude!, _traceData!.pathContacts[hop]!.latitude!,
@@ -452,9 +453,7 @@ class _PathTraceMapScreenState extends State<PathTraceMapScreen> {
.toRadixString(16) .toRadixString(16)
.padLeft(2, '0') .padLeft(2, '0')
.toUpperCase(); .toUpperCase();
return contactName != null return contactName != null ? "$hex: $contactName" : hex;
? "$hex: $contactName"
: "$hex: ${context.l10n.channelPath_unknownRepeater}";
} }
} else { } else {
final contactName = final contactName =
@@ -463,9 +462,7 @@ class _PathTraceMapScreenState extends State<PathTraceMapScreen> {
.toRadixString(16) .toRadixString(16)
.padLeft(2, '0') .padLeft(2, '0')
.toUpperCase(); .toUpperCase();
return contactName != null return contactName != null ? "$hex: $contactName" : hex;
? "$hex: $contactName"
: "$hex: ${context.l10n.channelPath_unknownRepeater}";
} }
} }
@@ -478,9 +475,7 @@ class _PathTraceMapScreenState extends State<PathTraceMapScreen> {
.toRadixString(16) .toRadixString(16)
.padLeft(2, '0') .padLeft(2, '0')
.toUpperCase(); .toUpperCase();
return contactName != null return contactName != null ? "$hex: $contactName" : hex;
? "$hex: $contactName"
: "$hex: ${context.l10n.channelPath_unknownRepeater}";
} else { } else {
return context.l10n.pathTrace_you; return context.l10n.pathTrace_you;
} }
@@ -491,46 +486,10 @@ class _PathTraceMapScreenState extends State<PathTraceMapScreen> {
.toRadixString(16) .toRadixString(16)
.padLeft(2, '0') .padLeft(2, '0')
.toUpperCase(); .toUpperCase();
return contactName != null return contactName != null ? "$hex: $contactName" : hex;
? "$hex: $contactName"
: "$hex: ${context.l10n.channelPath_unknownRepeater}";
} }
} }
Widget _buildMapPathTrace(
BuildContext context,
MapTileCacheService tileCache,
) {
return FlutterMap(
key: _mapKey,
options: MapOptions(
interactionOptions: InteractionOptions(flags: ~InteractiveFlag.rotate),
initialCenter: _initialCenter!,
initialZoom: _initialZoom,
initialCameraFit: _bounds == null
? null
: CameraFit.bounds(
bounds: _bounds!,
padding: const EdgeInsets.all(64),
maxZoom: 16,
),
minZoom: 2.0,
maxZoom: 18.0,
),
children: [
TileLayer(
urlTemplate: kMapTileUrlTemplate,
tileProvider: tileCache.tileProvider,
userAgentPackageName: MapTileCacheService.userAgentPackageName,
maxZoom: 19,
),
if (_polylines.isNotEmpty) PolylineLayer(polylines: _polylines),
if (_traceData!.pathData.isNotEmpty)
MarkerLayer(markers: _buildHopMarkers(_traceData!.pathData)),
],
);
}
Widget _buildLegendCard(BuildContext context, PathTraceData pathTraceData) { Widget _buildLegendCard(BuildContext context, PathTraceData pathTraceData) {
final l10n = context.l10n; final l10n = context.l10n;
final maxHeight = MediaQuery.of(context).size.height * 0.35; final maxHeight = MediaQuery.of(context).size.height * 0.35;
@@ -550,7 +509,7 @@ class _PathTraceMapScreenState extends State<PathTraceMapScreen> {
Padding( Padding(
padding: const EdgeInsets.all(12), padding: const EdgeInsets.all(12),
child: Text( child: Text(
'${l10n.channelPath_repeaterHops} ${formatDistance(_pathDistanceMeters)}', '${l10n.channelPath_repeaterHops} (${(_pathDistance / 1609.34).toStringAsFixed(2)} Miles / ${(_pathDistance / 1000).toStringAsFixed(2)} Km)',
style: const TextStyle(fontWeight: FontWeight.w600), style: const TextStyle(fontWeight: FontWeight.w600),
), ),
), ),
@@ -564,14 +523,8 @@ class _PathTraceMapScreenState extends State<PathTraceMapScreen> {
child: ListView.separated( child: ListView.separated(
padding: const EdgeInsets.symmetric(vertical: 4), padding: const EdgeInsets.symmetric(vertical: 4),
itemCount: pathTraceData.pathData.length + 1, itemCount: pathTraceData.pathData.length + 1,
separatorBuilder: (_, _) => const Divider(height: 1), separatorBuilder: (_, __) => const Divider(height: 1),
itemBuilder: (context, index) { itemBuilder: (context, index) {
final snrUi = snrUiFromSNR(
index < pathTraceData.snrData.length
? pathTraceData.snrData[index]
: null,
context.read<MeshCoreConnector>().currentSf,
);
return Column( return Column(
children: [ children: [
ListTile( ListTile(
@@ -590,22 +543,12 @@ class _PathTraceMapScreenState extends State<PathTraceMapScreen> {
), ),
style: const TextStyle(fontSize: 14), style: const TextStyle(fontSize: 14),
), ),
trailing: Column( trailing: SNRIcon(
mainAxisAlignment: MainAxisAlignment.center, snr:
children: [ pathTraceData.snrData[index].toSigned(
Icon( 8,
snrUi.icon, ) /
color: snrUi.color, 4.0,
size: 18.0,
),
Text(
snrUi.text,
style: TextStyle(
fontSize: 10,
color: snrUi.color,
),
),
],
), ),
onTap: () { onTap: () {
// Handle item tap // Handle item tap
+7 -5
View File
@@ -6,7 +6,7 @@ import 'repeater_status_screen.dart';
import 'repeater_cli_screen.dart'; import 'repeater_cli_screen.dart';
import 'repeater_settings_screen.dart'; import 'repeater_settings_screen.dart';
import 'telemetry_screen.dart'; import 'telemetry_screen.dart';
import 'neighbors_screen.dart'; import 'neighbours_screen.dart';
class RepeaterHubScreen extends StatelessWidget { class RepeaterHubScreen extends StatelessWidget {
final Contact repeater; final Contact repeater;
@@ -174,15 +174,17 @@ class RepeaterHubScreen extends StatelessWidget {
_buildManagementCard( _buildManagementCard(
context, context,
icon: Icons.group, icon: Icons.group,
title: l10n.repeater_neighbors, title: l10n.repeater_neighbours,
subtitle: l10n.repeater_neighborsSubtitle, subtitle: l10n.repeater_neighboursSubtitle,
color: Colors.orange, color: Colors.orange,
onTap: () { onTap: () {
Navigator.push( Navigator.push(
context, context,
MaterialPageRoute( MaterialPageRoute(
builder: (context) => builder: (context) => NeighboursScreen(
NeighborsScreen(repeater: repeater, password: password), repeater: repeater,
password: password,
),
), ),
); );
}, },
+1 -69
View File
@@ -1,6 +1,3 @@
import 'dart:async';
import 'dart:io' show Platform;
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_blue_plus/flutter_blue_plus.dart'; import 'package:flutter_blue_plus/flutter_blue_plus.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
@@ -21,8 +18,6 @@ class ScannerScreen extends StatefulWidget {
class _ScannerScreenState extends State<ScannerScreen> { class _ScannerScreenState extends State<ScannerScreen> {
bool _changedNavigation = false; bool _changedNavigation = false;
late final VoidCallback _connectionListener; late final VoidCallback _connectionListener;
BluetoothAdapterState _bluetoothState = BluetoothAdapterState.unknown;
late StreamSubscription<BluetoothAdapterState> _bluetoothStateSubscription;
@override @override
void initState() { void initState() {
@@ -44,25 +39,12 @@ class _ScannerScreenState extends State<ScannerScreen> {
}; };
connector.addListener(_connectionListener); connector.addListener(_connectionListener);
_bluetoothStateSubscription = FlutterBluePlus.adapterState.listen((state) {
if (mounted) {
setState(() {
_bluetoothState = state;
});
// Cancel scan if Bluetooth turns off while scanning
if (state != BluetoothAdapterState.on) {
unawaited(connector.stopScan());
}
}
});
} }
@override @override
void dispose() { void dispose() {
final connector = Provider.of<MeshCoreConnector>(context, listen: false); final connector = Provider.of<MeshCoreConnector>(context, listen: false);
connector.removeListener(_connectionListener); connector.removeListener(_connectionListener);
unawaited(_bluetoothStateSubscription.cancel());
super.dispose(); super.dispose();
} }
@@ -80,10 +62,6 @@ class _ScannerScreenState extends State<ScannerScreen> {
builder: (context, connector, child) { builder: (context, connector, child) {
return Column( return Column(
children: [ children: [
// Bluetooth off warning
if (_bluetoothState == BluetoothAdapterState.off)
_bluetoothOffWarning(context),
// Status bar // Status bar
_buildStatusBar(context, connector), _buildStatusBar(context, connector),
@@ -98,12 +76,9 @@ class _ScannerScreenState extends State<ScannerScreen> {
builder: (context, connector, child) { builder: (context, connector, child) {
final isScanning = final isScanning =
connector.state == MeshCoreConnectionState.scanning; connector.state == MeshCoreConnectionState.scanning;
final isBluetoothOff = _bluetoothState == BluetoothAdapterState.off;
return FloatingActionButton.extended( return FloatingActionButton.extended(
onPressed: isBluetoothOff onPressed: () {
? null
: () {
if (isScanning) { if (isScanning) {
connector.stopScan(); connector.stopScan();
} else { } else {
@@ -230,47 +205,4 @@ class _ScannerScreenState extends State<ScannerScreen> {
} }
} }
} }
Widget _bluetoothOffWarning(BuildContext context) {
final errorColor = Theme.of(context).colorScheme.error;
return Container(
width: double.infinity,
padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 16),
color: errorColor.withValues(alpha: 0.15),
child: Row(
children: [
Icon(Icons.bluetooth_disabled, size: 24, color: errorColor),
const SizedBox(width: 12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
context.l10n.scanner_bluetoothOff,
style: TextStyle(
color: errorColor,
fontWeight: FontWeight.w600,
fontSize: 14,
),
),
const SizedBox(height: 4),
Text(
context.l10n.scanner_bluetoothOffMessage,
style: TextStyle(
color: errorColor.withValues(alpha: 0.85),
fontSize: 12,
),
),
],
),
),
if (Platform.isAndroid)
TextButton(
onPressed: () => FlutterBluePlus.turnOn(),
child: Text(context.l10n.scanner_enableBluetooth),
),
],
),
);
}
} }
+18 -2
View File
@@ -490,6 +490,8 @@ class _SettingsScreenState extends State<SettingsScreen> {
final latController = TextEditingController(); final latController = TextEditingController();
final lonController = TextEditingController(); final lonController = TextEditingController();
final intervalController = TextEditingController(); final intervalController = TextEditingController();
bool isLogging = connector.sparseLocationLogger?.isLogging() ?? false;
latController.text = connector.selfLatitude?.toStringAsFixed(6) ?? ''; latController.text = connector.selfLatitude?.toStringAsFixed(6) ?? '';
lonController.text = connector.selfLongitude?.toStringAsFixed(6) ?? ''; lonController.text = connector.selfLongitude?.toStringAsFixed(6) ?? '';
@@ -534,6 +536,20 @@ class _SettingsScreenState extends State<SettingsScreen> {
signed: true, signed: true,
), ),
), ),
const SizedBox(height: 16),
FeatureToggleRow(
title: "GPS Logging",
subtitle: "Enable GPS logging on the device",
value: isLogging,
onChanged: (value) async {
setDialogState(() => isLogging = value);
if (value) {
await connector.sparseLocationLogger?.startLogging();
} else {
await connector.sparseLocationLogger?.stopLogging();
}
},
),
if (hasGPS) ...[ if (hasGPS) ...[
const SizedBox(height: 16), const SizedBox(height: 16),
TextField( TextField(
@@ -742,7 +758,7 @@ class _SettingsScreenState extends State<SettingsScreen> {
); );
} }
Future<void> _gpxExport( _gpxExport(
GpxExport exporter, GpxExport exporter,
String name, String name,
String description, String description,
@@ -782,7 +798,7 @@ class _SettingsScreenState extends State<SettingsScreen> {
} }
} }
Widget _buildExportCard(MeshCoreConnector connector) { _buildExportCard(MeshCoreConnector connector) {
final l10n = context.l10n; final l10n = context.l10n;
return Card( return Card(
child: Column( child: Column(
+11 -5
View File
@@ -1,3 +1,4 @@
import 'dart:isolate';
import 'dart:io'; import 'dart:io';
import 'package:flutter_foreground_task/flutter_foreground_task.dart'; import 'package:flutter_foreground_task/flutter_foreground_task.dart';
@@ -14,13 +15,18 @@ class BackgroundService {
channelDescription: 'Keeps MeshCore running in the background.', channelDescription: 'Keeps MeshCore running in the background.',
channelImportance: NotificationChannelImportance.LOW, channelImportance: NotificationChannelImportance.LOW,
priority: NotificationPriority.LOW, priority: NotificationPriority.LOW,
iconData: const NotificationIconData(
resType: ResourceType.mipmap,
resPrefix: ResourcePrefix.ic,
name: 'launcher',
),
), ),
iosNotificationOptions: const IOSNotificationOptions( iosNotificationOptions: const IOSNotificationOptions(
showNotification: false, showNotification: false,
playSound: false, playSound: false,
), ),
foregroundTaskOptions: ForegroundTaskOptions( foregroundTaskOptions: const ForegroundTaskOptions(
eventAction: ForegroundTaskEventAction.repeat(5000), interval: 5000,
autoRunOnBoot: false, autoRunOnBoot: false,
allowWifiLock: false, allowWifiLock: false,
), ),
@@ -57,13 +63,13 @@ void startCallback() {
class _MeshCoreTaskHandler extends TaskHandler { class _MeshCoreTaskHandler extends TaskHandler {
@override @override
Future<void> onStart(DateTime timestamp, TaskStarter starter) async {} void onStart(DateTime timestamp, SendPort? sendPort) {}
@override @override
void onRepeatEvent(DateTime timestamp) {} void onRepeatEvent(DateTime timestamp, SendPort? sendPort) {}
@override @override
Future<void> onDestroy(DateTime timestamp, bool isTimeout) async {} void onDestroy(DateTime timestamp, SendPort? sendPort) {}
@override @override
void onNotificationButtonPressed(String id) {} void onNotificationButtonPressed(String id) {}
+18 -18
View File
@@ -67,7 +67,7 @@ class NotificationService {
try { try {
await _notifications.initialize( await _notifications.initialize(
settings: initSettings, initSettings,
onDidReceiveNotificationResponse: _onNotificationTapped, onDidReceiveNotificationResponse: _onNotificationTapped,
); );
_isInitialized = true; _isInitialized = true;
@@ -149,10 +149,10 @@ class NotificationService {
); );
await _notifications.show( await _notifications.show(
id: contactId?.hashCode ?? 0, contactId?.hashCode ?? 0,
title: contactName, contactName,
body: message, message,
notificationDetails: notificationDetails, notificationDetails,
payload: 'message:$contactId', payload: 'message:$contactId',
); );
} }
@@ -194,10 +194,10 @@ class NotificationService {
); );
await _notifications.show( await _notifications.show(
id: contactId?.hashCode ?? DateTime.now().millisecondsSinceEpoch, contactId?.hashCode ?? DateTime.now().millisecondsSinceEpoch,
title: _l10n.notification_newTypeDiscovered(contactType), _l10n.notification_newTypeDiscovered(contactType),
body: contactName, contactName,
notificationDetails: notificationDetails, notificationDetails,
payload: 'advert:$contactId', payload: 'advert:$contactId',
); );
} }
@@ -248,10 +248,10 @@ class NotificationService {
: preview; : preview;
await _notifications.show( await _notifications.show(
id: channelIndex?.hashCode ?? DateTime.now().millisecondsSinceEpoch, channelIndex?.hashCode ?? DateTime.now().millisecondsSinceEpoch,
title: channelName, channelName,
body: body, body,
notificationDetails: notificationDetails, notificationDetails,
payload: 'channel:$channelIndex', payload: 'channel:$channelIndex',
); );
} }
@@ -285,7 +285,7 @@ class NotificationService {
} }
Future<void> cancel(int id) async { Future<void> cancel(int id) async {
await _notifications.cancel(id: id); await _notifications.cancel(id);
} }
// //
@@ -469,10 +469,10 @@ class NotificationService {
const notificationDetails = NotificationDetails(android: androidDetails); const notificationDetails = NotificationDetails(android: androidDetails);
await _notifications.show( await _notifications.show(
id: 'batch_summary'.hashCode, 'batch_summary'.hashCode,
title: _l10n.notification_activityTitle, _l10n.notification_activityTitle,
body: parts.join(', '), parts.join(', '),
notificationDetails: notificationDetails, notificationDetails,
payload: 'batch', payload: 'batch',
); );
} }
+244
View File
@@ -0,0 +1,244 @@
import 'dart:async';
import 'dart:io';
import 'dart:math' as math;
import 'package:flutter/widgets.dart';
import 'package:geolocator/geolocator.dart';
import 'package:gpx/gpx.dart';
import 'package:path_provider/path_provider.dart';
import 'package:permission_handler/permission_handler.dart';
import 'package:share_plus/share_plus.dart';
class SparseLocationLogger {
static const double distanceThresholdMiles = 0.25;
static const double distanceThresholdMeters =
distanceThresholdMiles * 1609.34;
static const double headingChangeThresholdDeg = 35.0;
static const double minSpeedForTurnKmh = 8.0;
static const double minTime = 120.0; // seconds
Position? _lastLoggedPosition;
double? _lastHeading;
DateTime? _lastLoggedTime;
StreamSubscription<Position>? _positionStream;
Timer? _timer;
Function(Position position)? _onNewLogPoint;
// GPX structures
final Gpx _gpx = Gpx();
Trkseg _currentSegment = Trkseg(); // one segment for the whole session
File? _gpxFile;
bool _isInitialized = false;
void initialize(Function(Position position) onNewLogPoint) {
_onNewLogPoint = onNewLogPoint;
}
Future<bool> getPremissons() async {
// Permissions & service check (same as before)
var status = await Permission.location.request();
if (!status.isGranted) {
debugPrint('Location permission denied');
return false;
}
bool serviceEnabled = await Geolocator.isLocationServiceEnabled();
if (!serviceEnabled) {
debugPrint('Location services disabled');
return false;
}
return true;
}
Future<void> startLogging() async {
if (!await getPremissons()) {
return;
}
// Prepare files
final directory = await getApplicationDocumentsDirectory();
final timestamp = DateTime.now()
.toIso8601String()
.replaceAll(':', '-')
.split('.')
.first;
_gpxFile = File('${directory.path}/track_$timestamp.gpx');
// Init GPX metadata
_gpx.metadata = Metadata(
name: 'Sparse Track ${DateTime.now().toString().split(' ').first}',
desc: 'Sparse GPS log: ~every 1.5 mi or significant turns',
time: DateTime.now(),
);
// Add one track with one segment
final track = Trk(name: 'Main Track');
_currentSegment = Trkseg();
track.trksegs.add(_currentSegment);
_gpx.trks.add(track);
_isInitialized = true;
// Start location stream
_positionStream = Geolocator.getPositionStream(
locationSettings: const LocationSettings(
accuracy: LocationAccuracy.high,
distanceFilter: 152, // meters (~0.16 mi) - helps battery
),
).listen(_onPositionReceived);
// Also poll via timer as fallback
_timer = Timer.periodic(Duration(seconds: (minTime / 2).toInt()), (
_,
) async {
final position = await Geolocator.getCurrentPosition();
await _onPositionReceived(position);
});
_lastLoggedPosition = null;
_lastHeading = null;
_lastLoggedTime = null;
debugPrint('Sparse GPX logging started → ${_gpxFile?.path}');
}
Future<void> stopLogging() async {
await _positionStream?.cancel();
_positionStream = null;
_timer?.cancel();
_timer = null;
if (_isInitialized && _currentSegment.trkpts.isNotEmpty) {
// Write GPX file on stop
final xmlString = GpxWriter().asString(_gpx, pretty: true);
await _gpxFile?.writeAsString(xmlString);
await SharePlus.instance.share(
ShareParams(
text: 'Sparse GPS track',
subject: 'Sparse GPS track',
files: [XFile(_gpxFile?.path ?? '')],
),
);
await _gpxFile?.delete();
}
debugPrint('Logging stopped');
}
Future<void> updateMyLocation() async {
if (!await getPremissons()) {
return;
}
try {
final position = await Geolocator.getCurrentPosition();
_onNewLogPoint?.call(position);
} catch (e) {
debugPrint('Error updating location: $e');
}
}
Future<void> _onPositionReceived(Position position) async {
final now = DateTime.now();
final speedKmh = position.speed * 3.6;
final heading = position.heading;
bool shouldLog = false;
String reason = '';
if (_lastLoggedPosition == null) {
shouldLog = true;
reason = 'start';
} else {
final distanceMeters = Geolocator.distanceBetween(
_lastLoggedPosition!.latitude,
_lastLoggedPosition!.longitude,
position.latitude,
position.longitude,
);
if (distanceMeters >= distanceThresholdMeters) {
shouldLog = true;
reason =
'distance (${(distanceMeters / 1609.34).toStringAsFixed(2)} mi)';
} else if (speedKmh > minSpeedForTurnKmh && _lastHeading != null) {
double delta = (heading - _lastHeading!).abs();
delta = math.min(delta, 360 - delta);
if (delta > headingChangeThresholdDeg) {
shouldLog = true;
reason = 'turn (${delta.toStringAsFixed(1)}°)';
}
} else if (_lastLoggedTime != null) {
final elapsed = now.difference(_lastLoggedTime!).inSeconds;
if (elapsed >= minTime && distanceMeters >= distanceThresholdMeters) {
shouldLog = true;
reason = 'time (${elapsed}s)';
}
}
}
if (shouldLog) {
// Create GPX Waypoint (trkpt)
final pt = Wpt(
lat: position.latitude,
lon: position.longitude,
ele: position.altitude, // if available
time: now,
extensions: {
"course": ?heading.isFinite ? heading : null,
"speed": ?speedKmh > 0 ? speedKmh / 3.6 : null, // GPX speed in m/s
},
// You can add hdop, vdop, etc. from position if desired
);
_currentSegment.trkpts.add(pt);
_onNewLogPoint?.call(position);
debugPrint('Logged point: ${pt.lat}, ${pt.lon} ($reason)');
_lastLoggedPosition = position;
_lastHeading = heading;
_lastLoggedTime = now;
} else {
debugPrint('Skipped point: ${position.latitude}, ${position.longitude}');
}
}
Position snapToGridCenter({
required Position position,
required double cellSizeMeters,
}) {
Position snappedPosition = position;
// Snap latitude
final latFloor =
(position.latitude / cellSizeMeters).floor() * cellSizeMeters;
final snappedLat = latFloor + (cellSizeMeters / 2);
// Snap longitude
final lonFloor =
(position.longitude / cellSizeMeters).floor() * cellSizeMeters;
final snappedLon = lonFloor + (cellSizeMeters / 2);
snappedPosition = Position(
latitude: snappedLat,
longitude: snappedLon,
altitude: position.altitude,
accuracy: position.accuracy,
heading: position.heading,
speed: position.speed,
speedAccuracy: position.speedAccuracy,
altitudeAccuracy: position.altitudeAccuracy,
headingAccuracy: position.headingAccuracy,
timestamp: position.timestamp,
);
return snappedPosition;
}
Future<String> getGpxFilePath() async => _gpxFile?.path ?? 'Not started';
bool isLogging() => _positionStream != null;
int getPointCount() => _currentSegment.trkpts.length;
}
-54
View File
@@ -1,54 +0,0 @@
import 'package:flutter/material.dart';
import 'package:meshcore_open/connector/meshcore_connector.dart';
import 'package:meshcore_open/widgets/battery_indicator.dart';
import 'package:provider/provider.dart';
import 'snr_indicator.dart';
class AppBarTitle extends StatelessWidget {
final String title;
final Widget? leading;
final Widget? trailing;
const AppBarTitle(this.title, {this.leading, this.trailing, super.key});
@override
Widget build(BuildContext context) {
final connector = context.watch<MeshCoreConnector>();
return Row(
mainAxisAlignment: MainAxisAlignment.start,
children: [
leading ?? const SizedBox.shrink(),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
leading ?? const SizedBox.shrink(),
Text(
title,
overflow: TextOverflow.ellipsis,
),
if (connector.isConnected && connector.selfName != null)
Center(
child: Text(
'(${connector.selfName})',
style: TextStyle(fontSize: 14, color: Colors.grey[600]),
overflow: TextOverflow.ellipsis,
),
),
],
),
),
const SizedBox(width: 8),
Row(
mainAxisSize: MainAxisSize.min,
children: [
BatteryIndicator(connector: connector),
SNRIndicator(connector: connector),
],
),
trailing ?? const SizedBox.shrink(),
],
);
}
}
+3 -7
View File
@@ -66,13 +66,10 @@ class _BatteryIndicatorState extends State<BatteryIndicator> {
child: Padding( child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 4, vertical: 8), padding: const EdgeInsets.symmetric(horizontal: 4, vertical: 8),
child: Row( child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
Icon(batteryUi.icon, size: 18, color: batteryUi.color), Icon(batteryUi.icon, size: 18, color: batteryUi.color),
const SizedBox(height: 2), const SizedBox(width: 2),
Flexible( Flexible(
child: Text( child: Text(
displayText, displayText,
@@ -81,14 +78,13 @@ class _BatteryIndicatorState extends State<BatteryIndicator> {
fontWeight: FontWeight.w500, fontWeight: FontWeight.w500,
color: batteryUi.color, color: batteryUi.color,
), ),
overflow: TextOverflow.visible,
maxLines: 1, maxLines: 1,
overflow: TextOverflow.ellipsis, softWrap: false,
), ),
), ),
], ],
), ),
],
),
), ),
); );
} }
+3 -39
View File
@@ -134,19 +134,6 @@ class _PathManagementDialog extends StatelessWidget {
final currentContact = _resolveContact(connector); final currentContact = _resolveContact(connector);
final paths = pathService.getRecentPaths(currentContact.publicKeyHex); final paths = pathService.getRecentPaths(currentContact.publicKeyHex);
final repeatersList = List.of(connector.directRepeaters)
..sort((a, b) => b.ranking.compareTo(a.ranking));
final directRepeater = repeatersList.isEmpty
? null
: repeatersList.first;
final secondDirectRepeater = repeatersList.length < 2
? null
: repeatersList.elementAt(1);
final thirdDirectRepeater = repeatersList.length < 3
? null
: repeatersList.elementAt(2);
return AlertDialog( return AlertDialog(
title: Text(l10n.chat_pathManagement), title: Text(l10n.chat_pathManagement),
content: SingleChildScrollView( content: SingleChildScrollView(
@@ -187,38 +174,15 @@ class _PathManagementDialog extends StatelessWidget {
], ],
const SizedBox(height: 8), const SizedBox(height: 8),
...paths.map((path) { ...paths.map((path) {
final isDirectRepeater =
directRepeater != null &&
path.pathBytes.isNotEmpty &&
directRepeater.pubkeyFirstByte == path.pathBytes.first;
final isSecondDirectRepeater =
secondDirectRepeater != null &&
path.pathBytes.isNotEmpty &&
secondDirectRepeater.pubkeyFirstByte ==
path.pathBytes.first;
final isThirdDirectRepeater =
thirdDirectRepeater != null &&
path.pathBytes.isNotEmpty &&
thirdDirectRepeater.pubkeyFirstByte ==
path.pathBytes.first;
Color color = Colors.grey;
if (isDirectRepeater) {
color = Colors.green;
} else if (isSecondDirectRepeater) {
color = Colors.yellow;
} else if (isThirdDirectRepeater) {
color = Colors.red;
} else if (path.wasFloodDiscovery) {
color = Colors.blue;
}
return Card( return Card(
margin: const EdgeInsets.symmetric(vertical: 4), margin: const EdgeInsets.symmetric(vertical: 4),
child: ListTile( child: ListTile(
dense: true, dense: true,
leading: CircleAvatar( leading: CircleAvatar(
radius: 16, radius: 16,
backgroundColor: color, backgroundColor: path.wasFloodDiscovery
? Colors.blue
: Colors.green,
child: Text( child: Text(
'${path.hopCount}', '${path.hopCount}',
style: const TextStyle(fontSize: 12), style: const TextStyle(fontSize: 12),
+1 -1
View File
@@ -156,7 +156,7 @@ class _QrScannerWidgetState extends State<QrScannerWidget>
MobileScanner( MobileScanner(
controller: _controller, controller: _controller,
onDetect: _handleDetection, onDetect: _handleDetection,
errorBuilder: (context, error) { errorBuilder: (context, error, child) {
return _buildErrorWidget(context, error); return _buildErrorWidget(context, error);
}, },
), ),
+13 -156
View File
@@ -1,13 +1,4 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import '../connector/meshcore_connector.dart';
import '../l10n/l10n.dart';
class SNRUi {
final IconData icon;
final Color color;
final String text;
const SNRUi(this.icon, this.color, this.text);
}
List<double> getSNRfromSF(int spreadingFactor) { List<double> getSNRfromSF(int spreadingFactor) {
switch (spreadingFactor) { switch (spreadingFactor) {
@@ -28,19 +19,20 @@ List<double> getSNRfromSF(int spreadingFactor) {
} }
} }
SNRUi snrUiFromSNR(double? snr, int? spreadingFactor) { class SNRIcon extends StatelessWidget {
if (snr == null || final double snr;
spreadingFactor == null || final List<double> snrLevels;
spreadingFactor < 7 ||
spreadingFactor > 12) {
return const SNRUi(Icons.signal_cellular_off, Colors.grey, '');
}
final snrLevels = getSNRfromSF(spreadingFactor); const SNRIcon({
super.key,
required this.snr,
this.snrLevels = const [4.0, -2.0, -4.0, -6.0],
});
@override
Widget build(BuildContext context) {
IconData icon; IconData icon;
Color color; Color color;
String text = '${snr.toStringAsFixed(1)} dB';
if (snr >= snrLevels[0]) { if (snr >= snrLevels[0]) {
icon = Icons.signal_cellular_alt; icon = Icons.signal_cellular_alt;
@@ -59,147 +51,12 @@ SNRUi snrUiFromSNR(double? snr, int? spreadingFactor) {
color = Colors.red; color = Colors.red;
} }
return SNRUi(icon, color, text);
}
class SNRIndicator extends StatefulWidget {
final MeshCoreConnector connector;
const SNRIndicator({super.key, required this.connector});
@override
State<SNRIndicator> createState() => _SNRIndicatorState();
}
class _SNRIndicatorState extends State<SNRIndicator> {
@override
Widget build(BuildContext context) {
final directRepeaters = widget.connector.directRepeaters;
final directBestRepeaters = List.of(directRepeaters)
..sort((a, b) => (b.ranking).compareTo(a.ranking));
final directRepeater = directBestRepeaters.isEmpty
? null
: directBestRepeaters.first;
final snrUi = snrUiFromSNR(
directBestRepeaters.isNotEmpty ? directRepeater!.snr : null,
widget.connector.currentSf,
);
return InkWell(
onTap: () {
if (directRepeater != null) {
_showFullPathDialog(context, directBestRepeaters);
}
},
borderRadius: BorderRadius.circular(8),
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 4, vertical: 8),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Column(
mainAxisSize: MainAxisSize.min,
children: [
Icon(snrUi.icon, size: 18, color: snrUi.color),
Text(
snrUi.text,
style: TextStyle(fontSize: 12, color: snrUi.color),
),
],
),
if (directRepeater != null)
Text(
'${directRepeaters.length}: ${directRepeater.pubkeyFirstByte.toRadixString(16).padLeft(2, '0')}: ${_formatLastUpdated(directRepeater.lastUpdated)}',
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.w500,
color: Colors.grey,
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
],
),
),
);
}
String _formatLastUpdated(DateTime lastSeen) {
final now = DateTime.now();
final diff = now.difference(lastSeen);
if (diff.isNegative) {
return "0s";
}
if (diff.inMinutes < 1) {
return "${diff.inSeconds}s";
}
if (diff.inMinutes < 60) {
return "${diff.inMinutes}m";
}
if (diff.inHours < 24) {
final hours = diff.inHours;
return hours == 1 ? "1h" : "${hours}hs";
}
final days = diff.inDays;
return days == 1 ? "1d" : "${days}ds";
}
void _showFullPathDialog(
BuildContext context,
List<DirectRepeater> directBestRepeaters,
) {
final l10n = context.l10n;
showDialog(
context: context,
builder: (context) => AlertDialog(
title: Text(l10n.snrIndicator_nearByRepeaters),
content: SizedBox(
child: Scrollbar(
child: ListView.separated(
padding: const EdgeInsets.symmetric(vertical: 4),
itemCount: directBestRepeaters.length,
separatorBuilder: (_, _) => const Divider(height: 1),
itemBuilder: (context, index) {
final repeater = directBestRepeaters[index];
final snrUi = snrUiFromSNR(
repeater.snr,
widget.connector.currentSf,
);
final name = widget.connector.contacts
.where((c) => c.publicKey.first == repeater.pubkeyFirstByte)
.map((c) => c.name)
.firstOrNull;
return Column( return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
ListTile( Icon(icon, color: color),
leading: Icon(snrUi.icon, color: snrUi.color), Text('$snr dB', style: TextStyle(fontSize: 10, color: color)),
title: Text(
name ??
repeater.pubkeyFirstByte
.toRadixString(16)
.padLeft(2, '0'),
),
subtitle: Text(
'SNR: ${repeater.snr.toStringAsFixed(1)} dB\n${l10n.snrIndicator_lastSeen}: ${_formatLastUpdated(repeater.lastUpdated)}',
),
),
], ],
); );
},
),
),
),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: Text(l10n.common_close),
),
],
),
);
} }
} }
@@ -7,6 +7,7 @@ import Foundation
import flutter_blue_plus_darwin import flutter_blue_plus_darwin
import flutter_local_notifications import flutter_local_notifications
import geolocator_apple
import mobile_scanner import mobile_scanner
import package_info_plus import package_info_plus
import share_plus import share_plus
@@ -18,6 +19,7 @@ import wakelock_plus
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
FlutterBluePlusPlugin.register(with: registry.registrar(forPlugin: "FlutterBluePlusPlugin")) FlutterBluePlusPlugin.register(with: registry.registrar(forPlugin: "FlutterBluePlusPlugin"))
FlutterLocalNotificationsPlugin.register(with: registry.registrar(forPlugin: "FlutterLocalNotificationsPlugin")) FlutterLocalNotificationsPlugin.register(with: registry.registrar(forPlugin: "FlutterLocalNotificationsPlugin"))
GeolocatorPlugin.register(with: registry.registrar(forPlugin: "GeolocatorPlugin"))
MobileScannerPlugin.register(with: registry.registrar(forPlugin: "MobileScannerPlugin")) MobileScannerPlugin.register(with: registry.registrar(forPlugin: "MobileScannerPlugin"))
FPPPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FPPPackageInfoPlusPlugin")) FPPPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FPPPackageInfoPlusPlugin"))
SharePlusMacosPlugin.register(with: registry.registrar(forPlugin: "SharePlusMacosPlugin")) SharePlusMacosPlugin.register(with: registry.registrar(forPlugin: "SharePlusMacosPlugin"))
+186 -66
View File
@@ -69,10 +69,10 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: characters name: characters
sha256: faf38497bda5ead2a8c7615f4f7939df04333478bf32e4173fcb06d428b5716b sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.4.1" version: "1.4.0"
checked_yaml: checked_yaml:
dependency: transitive dependency: transitive
description: description:
@@ -153,22 +153,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.2.0" version: "1.2.0"
dart_polylabel2:
dependency: transitive
description:
name: dart_polylabel2
sha256: "7eeab15ce72894e4bdba6a8765712231fc81be0bd95247de4ad9966abc57adc6"
url: "https://pub.dev"
source: hosted
version: "1.0.0"
dbus: dbus:
dependency: transitive dependency: transitive
description: description:
name: dbus name: dbus
sha256: d0c98dcd4f5169878b6cf8f6e0a52403a9dff371a3e2f019697accbf6f44a270 sha256: "79e0c23480ff85dc68de79e2cd6334add97e48f7f4865d17686dd6ea81a47e8c"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.7.12" version: "0.7.11"
fake_async: fake_async:
dependency: transitive dependency: transitive
description: description:
@@ -181,10 +173,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: ffi name: ffi
sha256: "6d7fd89431262d8f3125e81b50d3847a091d846eafcd4fdb88dd06f36d705a45" sha256: d07d37192dbf97461359c1518788f203b0c9102cfd2c35a716b823741219542c
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.2.0" version: "2.1.5"
file: file:
dependency: transitive dependency: transitive
description: description:
@@ -210,10 +202,10 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: flutter_blue_plus name: flutter_blue_plus
sha256: "88a65ead7dea67ddcc03e6ca846163c6b6cc09a2dcebdb8bb601fcd654ea9382" sha256: "399b3dbc15562ef59749f04e43a99ccbb91540022380d5f269aff3c2787534e4"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.1.1" version: "2.1.0"
flutter_blue_plus_android: flutter_blue_plus_android:
dependency: transitive dependency: transitive
description: description:
@@ -226,10 +218,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: flutter_blue_plus_darwin name: flutter_blue_plus_darwin
sha256: "52b155d868e17c1c8ad85520a0912d447d92aedccb5a5e234d3edc98ebd1307a" sha256: d160a8128e3a016fa58dd65ab6dac05cbc73e0fa799a1f24211d041641ed63ba
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "8.1.1" version: "8.1.0"
flutter_blue_plus_linux: flutter_blue_plus_linux:
dependency: transitive dependency: transitive
description: description:
@@ -258,10 +250,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: flutter_blue_plus_winrt name: flutter_blue_plus_winrt
sha256: ed894f0ab341f4cece8fa33edc381d46424a7c5bfd0e841d933d0f8c34c86521 sha256: "34be2d8e23d5881b46accebb0e71025f7d52869d72ea98b5082c20764e06aa80"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.0.18" version: "0.0.16"
flutter_cache_manager: flutter_cache_manager:
dependency: "direct main" dependency: "direct main"
description: description:
@@ -274,18 +266,18 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: flutter_foreground_task name: flutter_foreground_task
sha256: "48ea45056155a99fb30b15f14f4039a044d925bc85f381ed0b2d3b00a60b99de" sha256: "6cf10a27f5e344cd2ecad0752d3a5f4ec32846d82fda8753b3fe2480ebb832a3"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "9.2.0" version: "6.5.0"
flutter_launcher_icons: flutter_launcher_icons:
dependency: "direct dev" dependency: "direct dev"
description: description:
name: flutter_launcher_icons name: flutter_launcher_icons
sha256: "10f13781741a2e3972126fae08393d3c4e01fa4cd7473326b94b72cf594195e7" sha256: "526faf84284b86a4cb36d20a5e45147747b7563d921373d4ee0559c54fcdbcea"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.14.4" version: "0.13.1"
flutter_linkify: flutter_linkify:
dependency: "direct main" dependency: "direct main"
description: description:
@@ -298,42 +290,34 @@ packages:
dependency: "direct dev" dependency: "direct dev"
description: description:
name: flutter_lints name: flutter_lints
sha256: "3105dc8492f6183fb076ccf1f351ac3d60564bff92e20bfc4af9cc1651f4e7e1" sha256: "5398f14efa795ffb7a33e9b6a08798b26a180edac4ad7db3f231e40f82ce11e1"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "6.0.0" version: "5.0.0"
flutter_local_notifications: flutter_local_notifications:
dependency: "direct main" dependency: "direct main"
description: description:
name: flutter_local_notifications name: flutter_local_notifications
sha256: "2b50e938a275e1ad77352d6a25e25770f4130baa61eaf02de7a9a884680954ad" sha256: ef41ae901e7529e52934feba19ed82827b11baa67336829564aeab3129460610
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "20.1.0" version: "18.0.1"
flutter_local_notifications_linux: flutter_local_notifications_linux:
dependency: transitive dependency: transitive
description: description:
name: flutter_local_notifications_linux name: flutter_local_notifications_linux
sha256: dce0116868cedd2cdf768af0365fc37ff1cbef7c02c4f51d0587482e625868d0 sha256: "8f685642876742c941b29c32030f6f4f6dacd0e4eaecb3efbb187d6a3812ca01"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "7.0.0" version: "5.0.0"
flutter_local_notifications_platform_interface: flutter_local_notifications_platform_interface:
dependency: transitive dependency: transitive
description: description:
name: flutter_local_notifications_platform_interface name: flutter_local_notifications_platform_interface
sha256: "23de31678a48c084169d7ae95866df9de5c9d2a44be3e5915a2ff067aeeba899" sha256: "6c5b83c86bf819cdb177a9247a3722067dd8cc6313827ce7c77a4b238a26fd52"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "10.0.0" version: "8.0.0"
flutter_local_notifications_windows:
dependency: transitive
description:
name: flutter_local_notifications_windows
sha256: e97a1a3016512437d9c0b12fae7d1491c3c7b9aa7f03a69b974308840656b02a
url: "https://pub.dev"
source: hosted
version: "2.0.1"
flutter_localizations: flutter_localizations:
dependency: "direct main" dependency: "direct main"
description: flutter description: flutter
@@ -343,10 +327,10 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: flutter_map name: flutter_map
sha256: "391e7dc95cc3f5190748210a69d4cfeb5d8f84dcdfa9c3235d0a9d7742ccb3f8" sha256: "2ecb34619a4be19df6f40c2f8dce1591675b4eff7a6857bd8f533706977385da"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "8.2.2" version: "7.0.2"
flutter_test: flutter_test:
dependency: "direct dev" dependency: "direct dev"
description: flutter description: flutter
@@ -357,6 +341,70 @@ packages:
description: flutter description: flutter
source: sdk source: sdk
version: "0.0.0" version: "0.0.0"
geoclue:
dependency: transitive
description:
name: geoclue
sha256: c2a998c77474fc57aa00c6baa2928e58f4b267649057a1c76738656e9dbd2a7f
url: "https://pub.dev"
source: hosted
version: "0.1.1"
geolocator:
dependency: "direct main"
description:
name: geolocator
sha256: "79939537046c9025be47ec645f35c8090ecadb6fe98eba146a0d25e8c1357516"
url: "https://pub.dev"
source: hosted
version: "14.0.2"
geolocator_android:
dependency: transitive
description:
name: geolocator_android
sha256: "179c3cb66dfa674fc9ccbf2be872a02658724d1c067634e2c427cf6df7df901a"
url: "https://pub.dev"
source: hosted
version: "5.0.2"
geolocator_apple:
dependency: transitive
description:
name: geolocator_apple
sha256: dbdd8789d5aaf14cf69f74d4925ad1336b4433a6efdf2fce91e8955dc921bf22
url: "https://pub.dev"
source: hosted
version: "2.3.13"
geolocator_linux:
dependency: transitive
description:
name: geolocator_linux
sha256: c4e966f0a7a87e70049eac7a2617f9e16fd4c585a26e4330bdfc3a71e6a721f3
url: "https://pub.dev"
source: hosted
version: "0.2.3"
geolocator_platform_interface:
dependency: transitive
description:
name: geolocator_platform_interface
sha256: "30cb64f0b9adcc0fb36f628b4ebf4f731a2961a0ebd849f4b56200205056fe67"
url: "https://pub.dev"
source: hosted
version: "4.2.6"
geolocator_web:
dependency: transitive
description:
name: geolocator_web
sha256: b1ae9bdfd90f861fde8fd4f209c37b953d65e92823cb73c7dee1fa021b06f172
url: "https://pub.dev"
source: hosted
version: "4.1.3"
geolocator_windows:
dependency: transitive
description:
name: geolocator_windows
sha256: "175435404d20278ffd220de83c2ca293b73db95eafbdc8131fe8609be1421eb6"
url: "https://pub.dev"
source: hosted
version: "0.2.5"
glob: glob:
dependency: transitive dependency: transitive
description: description:
@@ -373,14 +421,22 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.3.0" version: "2.3.0"
gsettings:
dependency: transitive
description:
name: gsettings
sha256: "1b0ce661f5436d2db1e51f3c4295a49849f03d304003a7ba177d01e3a858249c"
url: "https://pub.dev"
source: hosted
version: "0.2.8"
hooks: hooks:
dependency: transitive dependency: transitive
description: description:
name: hooks name: hooks
sha256: "7a08a0d684cb3b8fb604b78455d5d352f502b68079f7b80b831c62220ab0a4f6" sha256: "5d309c86e7ce34cd8e37aa71cb30cb652d3829b900ab145e4d9da564b31d59f7"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.0.1" version: "1.0.0"
http: http:
dependency: "direct main" dependency: "direct main"
description: description:
@@ -413,6 +469,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.20.2" version: "0.20.2"
js:
dependency: transitive
description:
name: js
sha256: "53385261521cc4a0c4658fd0ad07a7d14591cf8fc33abbceae306ddb974888dc"
url: "https://pub.dev"
source: hosted
version: "0.7.2"
json_annotation: json_annotation:
dependency: transitive dependency: transitive
description: description:
@@ -465,10 +529,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: lints name: lints
sha256: "12f842a479589fea194fe5c5a3095abc7be0c1f2ddfa9a0e76aed1dbd26a87df" sha256: c35bb79562d980e9a453fc715854e1ed39e24e7d0297a880ef54e17f9874a9d7
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "6.1.0" version: "5.1.1"
lists: lists:
dependency: transitive dependency: transitive
description: description:
@@ -497,18 +561,18 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: matcher name: matcher
sha256: "12956d0ad8390bbcc63ca2e1469c0619946ccb52809807067a7020d57e647aa6" sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.12.18" version: "0.12.17"
material_color_utilities: material_color_utilities:
dependency: transitive dependency: transitive
description: description:
name: material_color_utilities name: material_color_utilities
sha256: "9c337007e82b1889149c82ed242ed1cb24a66044e30979c44912381e9be4c48b" sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.13.0" version: "0.11.1"
meta: meta:
dependency: transitive dependency: transitive
description: description:
@@ -537,10 +601,10 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: mobile_scanner name: mobile_scanner
sha256: c6184bf2913dd66be244108c9c27ca04b01caf726321c44b0e7a7a1e32d41044 sha256: "0b466a0a8a211b366c2e87f3345715faef9b6011c7147556ad22f37de6ba3173"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "7.1.4" version: "6.0.11"
native_toolchain_c: native_toolchain_c:
dependency: transitive dependency: transitive
description: description:
@@ -561,10 +625,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: objective_c name: objective_c
sha256: "100a1c87616ab6ed41ec263b083c0ef3261ee6cd1dc3b0f35f8ddfa4f996fe52" sha256: "983c7fa1501f6dcc0cb7af4e42072e9993cb28d73604d25ebf4dab08165d997e"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "9.3.0" version: "9.2.5"
octo_image: octo_image:
dependency: transitive dependency: transitive
description: description:
@@ -577,10 +641,10 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: package_info_plus name: package_info_plus
sha256: f69da0d3189a4b4ceaeb1a3defb0f329b3b352517f52bed4290f83d4f06bc08d sha256: "16eee997588c60225bda0488b6dcfac69280a6b7a3cf02c741895dd370a02968"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "9.0.0" version: "8.3.1"
package_info_plus_platform_interface: package_info_plus_platform_interface:
dependency: transitive dependency: transitive
description: description:
@@ -645,6 +709,54 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.3.0" version: "2.3.0"
permission_handler:
dependency: "direct main"
description:
name: permission_handler
sha256: bc917da36261b00137bbc8896bf1482169cd76f866282368948f032c8c1caae1
url: "https://pub.dev"
source: hosted
version: "12.0.1"
permission_handler_android:
dependency: transitive
description:
name: permission_handler_android
sha256: "1e3bc410ca1bf84662104b100eb126e066cb55791b7451307f9708d4007350e6"
url: "https://pub.dev"
source: hosted
version: "13.0.1"
permission_handler_apple:
dependency: transitive
description:
name: permission_handler_apple
sha256: f000131e755c54cf4d84a5d8bd6e4149e262cc31c5a8b1d698de1ac85fa41023
url: "https://pub.dev"
source: hosted
version: "9.4.7"
permission_handler_html:
dependency: transitive
description:
name: permission_handler_html
sha256: "38f000e83355abb3392140f6bc3030660cfaef189e1f87824facb76300b4ff24"
url: "https://pub.dev"
source: hosted
version: "0.1.3+5"
permission_handler_platform_interface:
dependency: transitive
description:
name: permission_handler_platform_interface
sha256: eb99b295153abce5d683cac8c02e22faab63e50679b937fa1bf67d58bb282878
url: "https://pub.dev"
source: hosted
version: "4.3.0"
permission_handler_windows:
dependency: transitive
description:
name: permission_handler_windows
sha256: "1a790728016f79a41216d88672dbc5df30e686e811ad4e698bfc51f76ad91f1e"
url: "https://pub.dev"
source: hosted
version: "0.2.1"
petitparser: petitparser:
dependency: transitive dependency: transitive
description: description:
@@ -673,10 +785,18 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: pointycastle name: pointycastle
sha256: "92aa3841d083cc4b0f4709b5c74fd6409a3e6ba833ffc7dc6a8fee096366acf5" sha256: "4be0097fcf3fd3e8449e53730c631200ebc7b88016acecab2b0da2f0149222fe"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "4.0.0" version: "3.9.1"
polylabel:
dependency: transitive
description:
name: polylabel
sha256: "41b9099afb2aa6c1730bdd8a0fab1400d287694ec7615dd8516935fa3144214b"
url: "https://pub.dev"
source: hosted
version: "1.0.1"
posix: posix:
dependency: transitive dependency: transitive
description: description:
@@ -822,10 +942,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: source_span name: source_span
sha256: "56a02f1f4cd1a2d96303c0144c93bd6d909eea6bee6bf5a0e0b685edbd4c47ab" sha256: "254ee5351d6cb365c859e20ee823c3bb479bf4a293c22d17a9f1bf144ce86f7c"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.10.2" version: "1.10.1"
sqflite: sqflite:
dependency: transitive dependency: transitive
description: description:
@@ -910,10 +1030,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: test_api name: test_api
sha256: "93167629bfc610f71560ab9312acdda4959de4df6fac7492c89ff0d3886f6636" sha256: ab2726c1a94d3176a45960b6234466ec367179b87dd74f1611adb1f3b5fb9d55
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.7.9" version: "0.7.7"
timezone: timezone:
dependency: transitive dependency: transitive
description: description:
@@ -958,10 +1078,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: url_launcher_ios name: url_launcher_ios
sha256: "580fe5dfb51671ae38191d316e027f6b76272b026370708c2d898799750a02b0" sha256: cfde38aa257dae62ffe79c87fab20165dfdf6988c1d31b58ebf59b9106062aad
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "6.4.1" version: "6.3.6"
url_launcher_linux: url_launcher_linux:
dependency: transitive dependency: transitive
description: description:
@@ -1030,10 +1150,10 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: wakelock_plus name: wakelock_plus
sha256: "9296d40c9adbedaba95d1e704f4e0b434be446e2792948d0e4aa977048104228" sha256: "61713aa82b7f85c21c9f4cd0a148abd75f38a74ec645fcb1e446f882c82fd09b"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.4.0" version: "1.3.3"
wakelock_plus_platform_interface: wakelock_plus_platform_interface:
dependency: transitive dependency: transitive
description: description:
+11 -9
View File
@@ -16,7 +16,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
# In Windows, build-name is used as the major, minor, and patch parts # In Windows, build-name is used as the major, minor, and patch parts
# of the product and file versions while build-number is used as the build suffix. # of the product and file versions while build-number is used as the build suffix.
version: 6.0.0+7 version: 6.0.0+1
environment: environment:
sdk: ^3.9.2 sdk: ^3.9.2
@@ -41,25 +41,27 @@ dependencies:
provider: ^6.1.5+1 provider: ^6.1.5+1
shared_preferences: ^2.2.2 shared_preferences: ^2.2.2
uuid: ^4.3.3 uuid: ^4.3.3
flutter_map: ^8.2.2 flutter_map: ^7.0.2
latlong2: ^0.9.1 latlong2: ^0.9.1
flutter_local_notifications: ^20.1.0 flutter_local_notifications: ^18.0.1
crypto: ^3.0.3 crypto: ^3.0.3
pointycastle: ^4.0.0 pointycastle: ^3.7.4
http: ^1.2.0 http: ^1.2.0
cached_network_image: ^3.4.1 cached_network_image: ^3.4.1
flutter_cache_manager: ^3.4.1 flutter_cache_manager: ^3.4.1
flutter_foreground_task: ^9.2.0 flutter_foreground_task: ^6.1.2
wakelock_plus: ^1.2.8 wakelock_plus: ^1.2.8
characters: ^1.4.0 characters: ^1.4.0
package_info_plus: ^9.0.0 package_info_plus: ^8.0.0
mobile_scanner: ^7.1.4 # QR/barcode scanning mobile_scanner: ^6.0.0 # QR/barcode scanning
qr_flutter: ^4.1.0 # QR code generation qr_flutter: ^4.1.0 # QR code generation
url_launcher: ^6.3.0 # Launch URLs in system browser url_launcher: ^6.3.0 # Launch URLs in system browser
flutter_linkify: ^6.0.0 # Auto-detect and linkify URLs in text flutter_linkify: ^6.0.0 # Auto-detect and linkify URLs in text
gpx: ^2.3.0 gpx: ^2.3.0
path_provider: ^2.1.5 path_provider: ^2.1.5
share_plus: ^12.0.1 share_plus: ^12.0.1
geolocator: ^14.0.2
permission_handler: ^12.0.1
dev_dependencies: dev_dependencies:
flutter_test: flutter_test:
@@ -70,8 +72,8 @@ dev_dependencies:
# activated in the `analysis_options.yaml` file located at the root of your # activated in the `analysis_options.yaml` file located at the root of your
# package. See that file for information about deactivating specific lint # package. See that file for information about deactivating specific lint
# rules and activating additional ones. # rules and activating additional ones.
flutter_lints: ^6.0.0 flutter_lints: ^5.0.0
flutter_launcher_icons: ^0.14.4 flutter_launcher_icons: ^0.13.1
# For information on the generic Dart part of this file, see the # For information on the generic Dart part of this file, see the
# following page: https://dart.dev/tools/pub/pubspec # following page: https://dart.dev/tools/pub/pubspec
@@ -7,12 +7,18 @@
#include "generated_plugin_registrant.h" #include "generated_plugin_registrant.h"
#include <flutter_blue_plus_winrt/flutter_blue_plus_plugin.h> #include <flutter_blue_plus_winrt/flutter_blue_plus_plugin.h>
#include <geolocator_windows/geolocator_windows.h>
#include <permission_handler_windows/permission_handler_windows_plugin.h>
#include <share_plus/share_plus_windows_plugin_c_api.h> #include <share_plus/share_plus_windows_plugin_c_api.h>
#include <url_launcher_windows/url_launcher_windows.h> #include <url_launcher_windows/url_launcher_windows.h>
void RegisterPlugins(flutter::PluginRegistry* registry) { void RegisterPlugins(flutter::PluginRegistry* registry) {
FlutterBluePlusPluginRegisterWithRegistrar( FlutterBluePlusPluginRegisterWithRegistrar(
registry->GetRegistrarForPlugin("FlutterBluePlusPlugin")); registry->GetRegistrarForPlugin("FlutterBluePlusPlugin"));
GeolocatorWindowsRegisterWithRegistrar(
registry->GetRegistrarForPlugin("GeolocatorWindows"));
PermissionHandlerWindowsPluginRegisterWithRegistrar(
registry->GetRegistrarForPlugin("PermissionHandlerWindowsPlugin"));
SharePlusWindowsPluginCApiRegisterWithRegistrar( SharePlusWindowsPluginCApiRegisterWithRegistrar(
registry->GetRegistrarForPlugin("SharePlusWindowsPluginCApi")); registry->GetRegistrarForPlugin("SharePlusWindowsPluginCApi"));
UrlLauncherWindowsRegisterWithRegistrar( UrlLauncherWindowsRegisterWithRegistrar(
+2 -1
View File
@@ -4,12 +4,13 @@
list(APPEND FLUTTER_PLUGIN_LIST list(APPEND FLUTTER_PLUGIN_LIST
flutter_blue_plus_winrt flutter_blue_plus_winrt
geolocator_windows
permission_handler_windows
share_plus share_plus
url_launcher_windows url_launcher_windows
) )
list(APPEND FLUTTER_FFI_PLUGIN_LIST list(APPEND FLUTTER_FFI_PLUGIN_LIST
flutter_local_notifications_windows
) )
set(PLUGIN_BUNDLED_LIBRARIES) set(PLUGIN_BUNDLED_LIBRARIES)