Compare commits

..

25 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
just_stuff_tm fac062a100 Refine device info layout and add collapsible map legend (#164) 2026-02-12 13:46:28 -07:00
Ded c52b19b09f Merge pull request #162 from just-stuff-tm/fix/battery-layout-overflow
Fix battery chemistry dropdown layout overflow
2026-02-11 21:27:30 -08:00
just_stuff_tm 6a666839b6 Fix battery chemistry dropdown layout overflow 2026-02-12 00:05:00 -05:00
Ded 4995f5f380 Merge pull request #159 from ChaoticLeah/add-flake-nix
Add flake.nix for development environment
2026-02-11 09:10:46 -08:00
Leah 4e6e7b6061 fix smaller copilot issues 2026-02-11 17:45:15 +01:00
Leah aa350aa4ae fixing copilot issues 2026-02-11 17:33:31 +01:00
Leah dfd38b19e9 add flake.lock 2026-02-11 17:26:43 +01:00
Leah 4afab3f629 remove unnessisary bits and nix darwin stuff 2026-02-11 17:25:44 +01:00
Ded 67816130ac Merge pull request #152 from zjs81/remove-wakelock
remove wakelock
2026-02-11 08:18:57 -08:00
Ded d573f0c312 Merge pull request #158 from ChaoticLeah/update-gitignore
Update .gitignore to exclude .gradle/
2026-02-11 08:16:38 -08:00
Ded 5b699cd624 keep ignores organized 2026-02-11 08:16:07 -08:00
Leah a4d3d248a5 Add flake.nix for development environment 2026-02-11 17:15:49 +01:00
Ded 2a3f2b3a24 Merge pull request #150 from ericszimmermann/german
translations to german updated.
2026-02-11 08:13:28 -08:00
Leah 675083fa01 Update .gitignore to exclude .gradle/ 2026-02-11 17:10:49 +01:00
Ded 5fc4b80b16 Merge pull request #144 from zjs81/support-whisperos
add support for whipseros
2026-02-11 08:03:44 -08:00
446564 84a32c1e67 remove wakelock
was being used to keep ble active which is not what it does

in early testing the ble remains connected with display off and also
when switching apps
2026-02-10 19:38:46 -08:00
ericz 607583060a translations to german updated. 2026-02-10 22:55:39 +01:00
Ded 71cf556b61 Merge pull request #148 from spfmoby/more-better-french-translations
Still better french translations
2026-02-10 11:24:47 -08:00
Zach c26174ad18 Chore bump version 2026-02-10 09:01:56 -07:00
spfmoby 04021a39a1 Better french translations 2026-02-10 08:12:51 +01:00
446564 fe23e9f7a0 add support for whipseros
needed a new ble prefix
2026-02-09 05:36:25 -08:00
Ded d7ec1876af Merge pull request #143 from zjs81/alpha6
chore: update version to alpha 6
2026-02-08 19:07:29 -08:00
22 changed files with 1180 additions and 388 deletions
+1
View File
@@ -65,6 +65,7 @@ secrets.dart
**/ios/Flutter/Flutter.podspec
# Android
.gradle/
**/android/.gradle/
**/android/captures/
**/android/local.properties
Generated
+61
View File
@@ -0,0 +1,61 @@
{
"nodes": {
"flake-utils": {
"inputs": {
"systems": "systems"
},
"locked": {
"lastModified": 1731533236,
"narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
},
"nixpkgs": {
"locked": {
"lastModified": 1770562336,
"narHash": "sha256-ub1gpAONMFsT/GU2hV6ZWJjur8rJ6kKxdm9IlCT0j84=",
"owner": "nixos",
"repo": "nixpkgs",
"rev": "d6c71932130818840fc8fe9509cf50be8c64634f",
"type": "github"
},
"original": {
"owner": "nixos",
"ref": "nixos-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"root": {
"inputs": {
"flake-utils": "flake-utils",
"nixpkgs": "nixpkgs"
}
},
"systems": {
"locked": {
"lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "nix-systems",
"repo": "default",
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"type": "github"
},
"original": {
"owner": "nix-systems",
"repo": "default",
"type": "github"
}
}
},
"root": "root",
"version": 7
}
+86
View File
@@ -0,0 +1,86 @@
{
description = "MeshCore Flutter Application";
inputs = {
nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable";
flake-utils.url = "github:numtide/flake-utils";
};
outputs = { self, nixpkgs, flake-utils }:
flake-utils.lib.eachDefaultSystem (system:
let
pkgs = nixpkgs.legacyPackages.${system};
in
{
devShells.default = pkgs.mkShell {
buildInputs = with pkgs; [
# Flutter and Dart
flutter
dart
# Java (required for Android development)
jdk17
# Android development tools
android-tools
gradle
# For the shell hook to set up the environment for Flutter development
gtk3
glib
sysprof
libclang
cmake
ninja
pkg-config
libdatrie
# Additional tools for installing Android SDK if not present
curl
unzip
];
shellHook = ''
echo "MeshCore Flutter Development Environment"
export PKG_CONFIG_PATH="${pkgs.gtk3}/lib/pkgconfig:${pkgs.glib}/lib/pkgconfig:${pkgs.sysprof}/lib/pkgconfig:$PKG_CONFIG_PATH"
export LD_LIBRARY_PATH="${pkgs.lib.makeLibraryPath [pkgs.gtk3 pkgs.glib pkgs.sysprof pkgs.libdatrie]}:$LD_LIBRARY_PATH"
export CMAKE_INSTALL_PREFIX="$PWD/build/bundle"
# Setup Android SDK in home directory (standard location)
export ANDROID_HOME="$HOME/Android/Sdk"
export ANDROID_SDK_ROOT="$ANDROID_HOME"
export PATH="$ANDROID_HOME/cmdline-tools/latest/bin:$ANDROID_HOME/platform-tools:$ANDROID_HOME/tools/bin:$PATH"
echo "Android SDK: $ANDROID_HOME"
echo ""
# Check if Android SDK exists and offer to download if not
if [ ! -d "$ANDROID_HOME" ]; then
echo "WARNING: Android SDK not found at $ANDROID_HOME"
echo ""
echo "To download and set up the Android SDK, run this command:"
echo ""
cat << 'EOF'
mkdir -p ~/Android/Sdk && cd ~/Android/Sdk && \
curl -o cmdline-tools.zip ${if pkgs.stdenv.isDarwin then "https://dl.google.com/android/repository/commandlinetools-mac-10406996_latest.zip" else "https://dl.google.com/android/repository/commandlinetools-linux-10406996_latest.zip"} && \
unzip -q cmdline-tools.zip && \
mkdir -p cmdline-tools/latest && \
mv cmdline-tools/* cmdline-tools/latest/ 2>/dev/null || echo "Warning: failed to move Android cmdline-tools into 'latest' directory; please check your SDK layout." >&2 && \
rm cmdline-tools.zip && \
cd cmdline-tools/latest/bin && \
yes | ./sdkmanager --sdk_root=~/Android/Sdk 'platform-tools' && \
echo "Android SDK setup complete!"
EOF
echo ""
echo "Then run 'flutter doctor' again to verify."
echo ""
else
echo "Android SDK found at $ANDROID_HOME"
fi
echo "To check that everything is set up correctly, run 'flutter doctor' and ensure there are no issues."
'';
};
}
);
}
+218 -10
View File
@@ -2,10 +2,11 @@ import 'dart:async';
import 'dart:convert';
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:flutter/foundation.dart';
import 'package:flutter_blue_plus/flutter_blue_plus.dart';
import 'package:wakelock_plus/wakelock_plus.dart';
import '../models/channel.dart';
import '../models/channel_message.dart';
@@ -131,6 +132,7 @@ class MeshCoreConnector extends ChangeNotifier {
PathHistoryService? _pathHistoryService;
AppSettingsService? _appSettingsService;
BackgroundService? _backgroundService;
SparseLocationLogger? _sparseLocationLogger;
final NotificationService _notificationService = NotificationService();
BleDebugLogService? _bleDebugLogService;
AppDebugLogService? _appDebugLogService;
@@ -503,6 +505,7 @@ class MeshCoreConnector extends ChangeNotifier {
BleDebugLogService? bleDebugLogService,
AppDebugLogService? appDebugLogService,
BackgroundService? backgroundService,
SparseLocationLogger? sparseLocationLogger,
}) {
_retryService = retryService;
_pathHistoryService = pathHistoryService;
@@ -510,11 +513,14 @@ class MeshCoreConnector extends ChangeNotifier {
_bleDebugLogService = bleDebugLogService;
_appDebugLogService = appDebugLogService;
_backgroundService = backgroundService;
_sparseLocationLogger = sparseLocationLogger;
// Initialize notification service
_notificationService.initialize();
_loadChannelOrder();
_sparseLocationLogger?.initialize(_updateLocationandAdvert);
// Initialize retry service callbacks
_retryService?.initialize(
sendMessageCallback: _sendMessageDirect,
@@ -658,7 +664,8 @@ class MeshCoreConnector extends ChangeNotifier {
_scanResults.clear();
for (var result in results) {
if (result.device.platformName.startsWith("MeshCore-") ||
result.advertisementData.advName.startsWith("MeshCore-")) {
result.advertisementData.advName.startsWith("MeshCore-") ||
result.advertisementData.advName.startsWith("Whisper-")) {
_scanResults.add(result);
}
}
@@ -775,9 +782,6 @@ class MeshCoreConnector extends ChangeNotifier {
_setState(MeshCoreConnectionState.connected);
// Enable wake lock to prevent BLE disconnection when screen turns off
await WakelockPlus.enable();
await _requestDeviceInfo();
_startBatteryPolling();
final gotSelfInfo = await _waitForSelfInfo(
@@ -831,6 +835,8 @@ class MeshCoreConnector extends ChangeNotifier {
return result;
}
SparseLocationLogger? get sparseLocationLogger => _sparseLocationLogger;
bool get _shouldAutoReconnect => !_manualDisconnect && _lastDeviceId != null;
void _cancelReconnectTimer() {
@@ -886,9 +892,6 @@ class MeshCoreConnector extends ChangeNotifier {
_setState(MeshCoreConnectionState.disconnecting);
_stopBatteryPolling();
// Disable wake lock when disconnecting
await WakelockPlus.disable();
await _notifySubscription?.cancel();
_notifySubscription = null;
@@ -1696,6 +1699,11 @@ class MeshCoreConnector extends ChangeNotifier {
_isLoadingContacts = true;
notifyListeners();
break;
case pushCodeNewAdvert:
debugPrint('Got New CONTACT');
// It the same format as respCodeContact, so we can reuse the handler
_handleContact(frame);
break;
case respCodeContact:
debugPrint('Got CONTACT');
_handleContact(frame);
@@ -1740,6 +1748,7 @@ class MeshCoreConnector extends ChangeNotifier {
case pushCodeStatusResponse:
break;
case pushCodeLogRxData:
_handleRxData(frame);
_handleLogRxData(frame);
break;
case respCodeChannelInfo:
@@ -1753,6 +1762,7 @@ class MeshCoreConnector extends ChangeNotifier {
break;
case respCodeCustomVars:
_handleCustomVars(frame);
break;
default:
debugPrint('Unknown frame code: $code');
}
@@ -2008,6 +2018,76 @@ class MeshCoreConnector extends ChangeNotifier {
}
}
void _handleContactAdvert(Contact contact) {
if (contact.type == advTypeRepeater) {
_contactUnreadCount.remove(contact.publicKeyHex);
_unreadStore.saveContactUnreadCount(
Map<String, int>.from(_contactUnreadCount),
);
}
// Check if this is a new contact
final isNewContact = !_knownContactKeys.contains(contact.publicKeyHex);
final existingIndex = _contacts.indexWhere(
(c) => c.publicKeyHex == contact.publicKeyHex,
);
if (existingIndex >= 0) {
final existing = _contacts[existingIndex];
final mergedLastMessageAt =
existing.lastMessageAt.isAfter(contact.lastMessageAt)
? existing.lastMessageAt
: contact.lastMessageAt;
appLogger.info(
'Refreshing contact ${contact.name}: devicePath=${contact.pathLength}, existingOverride=${existing.pathOverride}',
tag: 'Connector',
);
// CRITICAL: Preserve user's path override when contact is refreshed from device
_contacts[existingIndex] = contact.copyWith(
lastMessageAt: mergedLastMessageAt,
pathOverride: existing.pathOverride, // Preserve user's path choice
pathOverrideBytes: existing.pathOverrideBytes,
);
appLogger.info(
'After merge: pathOverride=${_contacts[existingIndex].pathOverride}, devicePath=${_contacts[existingIndex].pathLength}',
tag: 'Connector',
);
} else {
_contacts.add(contact);
appLogger.info(
'Added new contact ${contact.name}: pathLen=${contact.pathLength}',
tag: 'Connector',
);
}
_knownContactKeys.add(contact.publicKeyHex);
_loadMessagesForContact(contact.publicKeyHex);
// Add path to history if we have a valid path
if (_pathHistoryService != null && contact.pathLength >= 0) {
_pathHistoryService!.handlePathUpdated(contact);
}
notifyListeners();
// Show notification for new contact (advertisement)
if (isNewContact && _appSettingsService != null) {
final settings = _appSettingsService!.settings;
if (settings.notificationsEnabled && settings.notifyOnNewAdvert) {
_notificationService.showAdvertNotification(
contactName: contact.name,
contactType: contact.typeLabel,
contactId: contact.publicKeyHex,
);
}
}
if (!_isLoadingContacts) {
unawaited(_persistContacts());
}
}
Future<void> _persistContacts() async {
await _contactStore.saveContacts(_contacts);
}
@@ -3214,8 +3294,6 @@ class MeshCoreConnector extends ChangeNotifier {
}
void _handleDisconnection() {
// Disable wake lock when connection is lost
WakelockPlus.disable();
_stopBatteryPolling();
for (final entry in _pendingRepeaterAcks.values) {
@@ -3293,6 +3371,136 @@ class MeshCoreConnector extends ChangeNotifier {
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) {
final packet = BufferReader(frame);
packet.skipBytes(3); // Skip frame type byte
//final snr = packet.readByte() / 4.0;
//final rssi = packet.readByte();
final header = packet.readByte();
//final routeType = header & 0x03;
final payloadType = (header >> 2) & 0x0F;
//final payloadVer = (header >> 6) & 0x03;
if (packet.remaining <= 0) return;
final pathLen = packet.readByte();
if (packet.remaining < pathLen) return;
final pathBytes = packet.readBytes(pathLen);
if (packet.remaining <= 0) return;
final payload = packet.readBytes(packet.remaining);
switch (payloadType) {
case payloadTypeADVERT:
_handlePayloadAdvertReceived(payload, pathBytes);
break;
default:
}
}
void _handlePayloadAdvertReceived(Uint8List frame, Uint8List path) {
final advert = BufferReader(frame);
if (advert.remaining <= 32) return;
final publicKey = advert.readBytes(32);
final contactKeyHex = publicKey
.map((b) => b.toRadixString(16).padLeft(2, '0'))
.join();
if (advert.remaining <= 4) return;
final timestamp = advert.readInt32LE();
if (advert.remaining <= 64) return;
advert.skipBytes(64); // Skip signature for now
if (advert.remaining <= 1) return;
final flags = advert.readByte();
final type = flags & 0x0F;
final hasLocation = (flags & 0x10) != 0;
//final hasFeature1 = (flags & 0x20) != 0;
//final hasFeature2 = (flags & 0x40) != 0;
final hasName = (flags & 0x80) != 0;
double latitude = 0.0;
double longitude = 0.0;
if (hasLocation && advert.remaining >= 8) {
latitude = advert.readInt32LE() / 1e6;
longitude = advert.readInt32LE() / 1e6;
}
String name = '';
if (hasName && advert.remaining > 0) {
name = advert.readString();
}
// Check if this is a new contact
final isNewContact = !_knownContactKeys.contains(contactKeyHex);
if (isNewContact) {
_handleContactAdvert(
Contact(
publicKey: publicKey,
name: name,
type: type,
pathLength: path.length,
path: path,
latitude: latitude,
longitude: longitude,
lastSeen: DateTime.fromMillisecondsSinceEpoch(timestamp * 1000),
),
);
return;
}
final existingIndex = _contacts.indexWhere(
(c) => c.publicKeyHex == contactKeyHex,
);
if (existingIndex >= 0) {
final existing = _contacts[existingIndex];
final mergedLastMessageAt = existing.lastMessageAt.isAfter(DateTime.now())
? DateTime.now()
: existing.lastMessageAt;
appLogger.info(
'Refreshing contact ${existing.name}: devicePath=${existing.pathLength}, existingOverride=${existing.pathOverride}',
tag: 'Connector',
);
// CRITICAL: Preserve user's path override when contact is refreshed from device
_contacts[existingIndex] = existing.copyWith(
latitude: hasLocation ? latitude : existing.latitude,
longitude: hasLocation ? longitude : existing.longitude,
name: hasName ? name : existing.name,
path: path,
pathLength: path.length,
lastMessageAt: mergedLastMessageAt,
lastSeen: DateTime.fromMillisecondsSinceEpoch(timestamp * 1000),
pathOverride: existing.pathOverride, // Preserve user's path choice
pathOverrideBytes: existing.pathOverrideBytes,
);
appLogger.info(
'After merge: pathOverride=${_contacts[existingIndex].pathOverride}, devicePath=${_contacts[existingIndex].pathLength}',
tag: 'Connector',
);
notifyListeners();
}
}
}
const int _phRouteMask = 0x03;
+44
View File
@@ -151,6 +151,7 @@ const int cmdGetContactByKey = 30;
const int cmdGetChannel = 31;
const int cmdSetChannel = 32;
const int cmdSendTracePath = 36;
const int cmdSetOtherParams = 38;
const int cmdGetRadioSettings = 57;
const int cmdGetTelemetryReq = 39;
const int cmdGetCustomVar = 40;
@@ -212,6 +213,30 @@ const int advTypeRepeater = 2;
const int advTypeRoom = 3;
const int advTypeSensor = 4;
// Payload Types
const int payloadTypeREQ =
0x00; // request (prefixed with dest/src hashes, MAC) (enc data: timestamp, blob)
const int payloadTypeRESPONSE =
0x01; // response to REQ or ANON_REQ (prefixed with dest/src hashes, MAC) (enc data: timestamp, blob)
const int payloadTypeTXTMSG =
0x02; // a plain text message (prefixed with dest/src hashes, MAC) (enc data: timestamp, text)
const int payloadTypeACK = 0x03; // a simple ack
const int payloadTypeADVERT = 0x04; // a node advertising its Identity
const int payloadTypeGRPTXT =
0x05; // an (unverified) group text message (prefixed with channel hash, MAC) (enc data: timestamp, "name: msg")
const int payloadTypeGRPDATA =
0x06; // an (unverified) group datagram (prefixed with channel hash, MAC) (enc data: timestamp, blob)
const int payloadTypeANONREQ =
0x07; // generic request (prefixed with dest_hash, ephemeral pub_key, MAC) (enc data: ...)
const int payloadTypePATH =
0x08; // returned path (prefixed with dest/src hashes, MAC) (enc data: path, extra)
const int payloadTypeTRACE = 0x09; // trace a path, collecting SNI for each hop
const int payloadTypeMULTIPART = 0x0A; // packet is one of a set of packets
const int payloadTypeCONTROL = 0x0B; // a control/discovery packet
//...
const int payloadTypeRawCustom =
0x0F; // custom packet as raw bytes, for applications with custom encryption, payloads, etc
// Sizes
const int pubKeySize = 32;
const int maxPathSize = 64;
@@ -777,3 +802,22 @@ Uint8List buildZeroHopContact(Uint8List pubKey) {
writer.writeBytes(pubKey);
return writer.toBytes();
}
// Build CMD_SET_OTHER_PARAMS frame
// Format: [cmd][allowAutoAddContacts][allowTelemetryFlags][advertLocationPolicy][multiAcks]
Uint8List buildSetOtherParamsFrame(
bool allowAutoAddContacts,
int allowTelemetryFlags,
int advertLocationPolicy,
int multiAcks,
) {
final writer = BufferWriter();
writer.writeByte(cmdSetOtherParams);
writer.writeByte(
allowAutoAddContacts ? 0x00 : 0x01,
); // Allow Auto Add Contacts
writer.writeByte(allowTelemetryFlags); // Allow Telemetry Flags
writer.writeByte(advertLocationPolicy); // Advertisement Location Policy
writer.writeByte(multiAcks); // Multi Acknowledgements
return writer.toBytes();
}
+39 -39
View File
@@ -96,14 +96,14 @@
"settings_privacyModeEnabled": "Datenschutzmodus aktiviert",
"settings_privacyModeDisabled": "Datenschutzmodus deaktiviert",
"settings_actions": "Aktionen",
"settings_sendAdvertisement": "Sende eine Ankündigung",
"settings_sendAdvertisementSubtitle": "Sende Ankündigung",
"settings_sendAdvertisement": "Sende Ankündigung",
"settings_sendAdvertisementSubtitle": "Sende eine Ankündigung",
"settings_advertisementSent": "Ankündigung gesendet",
"settings_syncTime": "Zeitsynchronisierung",
"settings_syncTimeSubtitle": "Stelle die Gerätezeit auf die Uhrzeit des Telefons ein",
"settings_timeSynchronized": "Zeit synchronisiert",
"settings_refreshContacts": "Kontakte aktualisieren",
"settings_refreshContactsSubtitle": "Kontakte-Liste vom Gerät neu laden",
"settings_refreshContactsSubtitle": "Kontakt-Liste vom Gerät neu laden",
"settings_rebootDevice": "Gerät neu starten",
"settings_rebootDeviceSubtitle": "MeshCore-Gerät neu starten",
"settings_rebootDeviceConfirm": "Sind Sie sicher, dass Sie das Gerät neu starten möchten? Sie werden getrennt.",
@@ -540,7 +540,7 @@
"chat_routingMode": "Routenmodus",
"chat_autoUseSavedPath": "Automatisch (gespeicherten Pfad verwenden)",
"chat_forceFloodMode": "Flut-Modus erzwingen",
"chat_recentAckPaths": "Aktuelle ACK-Pfade (tasten, um zu verwenden):",
"chat_recentAckPaths": "Aktuelle ACK-Pfade (antippen, um zu verwenden):",
"chat_pathHistoryFull": "Die Pfadhistorie ist voll. Entferne Einträge, um neue hinzuzufügen.",
"chat_hopSingular": "Sprung",
"chat_hopPlural": "Sprünge",
@@ -554,7 +554,7 @@
},
"chat_successes": "Erfolgreich",
"chat_removePath": "Pfad entfernen",
"chat_noPathHistoryYet": "Keine eine Pfadhistorie vorhanden.\nSende eine Nachricht, um Pfade zu entdecken.",
"chat_noPathHistoryYet": "Keine Pfadhistorie vorhanden.\nSende eine Nachricht, um Pfade zu entdecken.",
"chat_pathActions": "Pfadaktionen:",
"chat_setCustomPath": "Lege benutzerdefinierten Pfad fest",
"chat_setCustomPathSubtitle": "Manuellen Routenpfad festlegen",
@@ -717,7 +717,7 @@
"mapCache_cacheArea": "Zwischenspeicherbereich",
"mapCache_useCurrentView": "Aktuelle Ansicht verwenden",
"mapCache_zoomRange": "Zoom Bereich",
"mapCache_estimatedTiles": "Geschätzte Fliesen: {count}",
"mapCache_estimatedTiles": "Geschätzte Kacheln: {count}",
"@mapCache_estimatedTiles": {
"placeholders": {
"count": {
@@ -854,7 +854,7 @@
},
"path_enterCustomPath": "Gebe Pfad ein",
"path_currentPathLabel": "Aktueller Pfad",
"path_hexPrefixInstructions": "Gebe für jeden Hopfen 2-stellige Hex-Präfixe ein, getrennt durch Kommas.",
"path_hexPrefixInstructions": "Gebe für jeden Zwischen-Hop das 2-stellige Hex-Präfix ein, getrennt durch Kommas.",
"path_hexPrefixExample": "Beispiel: A1,F2,3C (jeder Knoten verwendet den ersten Byte seines öffentlichen Schlüssels)",
"path_labelHexPrefixes": "Pfad (Hex-Präfixe)",
"path_helperMaxHops": "Max 64 Sprünge. Jede Präfixe ist 2 Hexadezimalzeichen (1 Byte)",
@@ -887,7 +887,7 @@
"repeater_forceFloodMode": "Flut-Modus erzwingen",
"repeater_pathManagement": "Pfadverwaltung",
"repeater_refresh": "Aktualisieren",
"repeater_statusRequestTimeout": "Statusanfrage zeitweise fehlgeschlagen.",
"repeater_statusRequestTimeout": "Statusanfrage durch Timeout fehlgeschlagen.",
"repeater_errorLoadingStatus": "Fehler beim Laden des Status: {error}",
"@repeater_errorLoadingStatus": {
"placeholders": {
@@ -957,7 +957,7 @@
}
}
},
"repeater_duplicatesFloodDirect": "Überflut: {flood}, Direkt: {direct}",
"repeater_duplicatesFloodDirect": "Flut: {flood}, Direkt: {direct}",
"@repeater_duplicatesFloodDirect": {
"placeholders": {
"flood": {
@@ -983,7 +983,7 @@
"repeater_adminPassword": "Admin-Passwort",
"repeater_adminPasswordHelper": "Vollzugriffspasswort",
"repeater_guestPassword": "Gast-Passwort",
"repeater_guestPasswordHelper": "Schreibgeschützter Zugriffspasswort",
"repeater_guestPasswordHelper": "Schreibgeschütztes Zugriffspasswort",
"repeater_radioSettings": "Funk Einstellungen",
"repeater_frequencyMhz": "Frequenz (MHz)",
"repeater_frequencyHelper": "300-2500 MHz",
@@ -1086,11 +1086,11 @@
}
},
"repeater_cliTitle": "Repeater CLI",
"repeater_debugNextCommand": "Fehlersuche Nächster Befehl",
"repeater_debugNextCommand": "Fehlersuche des nächsten Befehls",
"repeater_commandHelp": "Hilfe",
"repeater_clearHistory": "Löschen der Historie",
"repeater_noCommandsSent": "Noch keine Befehle gesendet.",
"repeater_typeCommandOrUseQuick": "Geben Sie einen Befehl unten ein oder verwenden Sie Schnellbefehle",
"repeater_typeCommandOrUseQuick": "Geben Sie unten einen Befehl ein oder verwenden Sie die Schnellbefehle",
"repeater_enterCommandHint": "Geben Sie den Befehl ein...",
"repeater_previousCommand": "Vorhergehende Aktion",
"repeater_nextCommand": "Nächste Aktion",
@@ -1132,7 +1132,7 @@
"repeater_cliHelpSetLat": "Legt die Breitengrad der Ankündigung fest. (dezimale Grad)",
"repeater_cliHelpSetLon": "Legt die Längengrade der Ankündigung fest. (dezimale Grad)",
"repeater_cliHelpSetRadio": "Legt komplett neue Radio-Parameter fest und speichert diese als Präferenzen. Benötigt einen \"Reboot\"-Befehl, um sie anzuwenden.",
"repeater_cliHelpSetRxDelay": "Sets (experimentell) als Basis (muss > 1 sein für den Effekt) zur Anwendung einer leichten Verzögerung bei empfangenen Paketen, basierend auf Signalstärke/Punktzahl. Auf 0 setzen, um die Funktion zu deaktivieren.",
"repeater_cliHelpSetRxDelay": "Fügt eine leichte Verzögerung bei empfangenen Paketen hinzu, basierend auf Signalstärke/Punktzahl. Auf 0 setzen, um die Funktion zu deaktivieren.",
"repeater_cliHelpSetTxDelay": "Legt einen Faktor fest, der mit der Zeit bei voller Zuluft für ein Flood-Mode-Paket und mit einem zufälligen Slot-System multipliziert wird, um dessen Weiterleitung zu verzögern (um Kollisionen zu vermeiden).",
"repeater_cliHelpSetDirectTxDelay": "Ähnlich wie txdelay, aber zum Anwenden einer zufälligen Verzögerung bei der Weiterleitung von Direktmodus-Paketen.",
"repeater_cliHelpSetBridgeEnabled": "Brücke aktivieren/deaktivieren.",
@@ -1143,14 +1143,14 @@
"repeater_cliHelpSetAdcMultiplier": "Legt einen benutzerdefinierten Faktor zur Anpassung der gemeldeten Batteriewirkspannung fest (nur auf ausgewählten Boards unterstützt).",
"repeater_cliHelpTempRadio": "Legt vorübergehende Funkparameter für die angegebene Anzahl von Minuten fest und kehrt anschließend zu den ursprünglichen Funkparametern zurück (wird nicht in den Einstellungen gespeichert).",
"repeater_cliHelpSetPerm": "Ändert die ACL. Entfernt das passende Eintragen (durch Pubkey-Präfix), wenn \"permissions\" auf 0 steht. Fügt ein neues Eintragen hinzu, wenn die Pubkey-Hex-Länge vollständig ist und nicht bereits in der ACL vorhanden ist. Aktualisiert das Eintragen anhand des übereinstimmenden Pubkey-Präfix. Berechtigungsbits variieren je nach Firmware-Rolle, aber die unteren 2 Bits sind: 0 (Gast), 1 (Nur Lesen), 2 (Lesen/Schreiben), 3 (Admin)",
"repeater_cliHelpGetBridgeType": "Ruft Brückentyp none, rs232, espnow ab.",
"repeater_cliHelpGetBridgeType": "Ruft Brückentyp: none, rs232, espnow ab.",
"repeater_cliHelpLogStart": "Beginnt die Paketprotokollierung in das Dateisystem.",
"repeater_cliHelpLogStop": "Stoppt das Paketprotokollieren in das Dateisystem.",
"repeater_cliHelpLogErase": "Löscht die Paketprotokolle aus dem Dateisystem.",
"repeater_cliHelpNeighbors": "Zeigt eine Liste anderer Repeater-Knoten an, die über Zero-Hop-Ankündigung gehört wurden. Jede Zeile ist id-prefix-hex:timestamp:snr-times-4",
"repeater_cliHelpNeighborRemove": "Entfernt das erste übereinstimmende Element (über Pubkey-Präfix (hex)) aus der Liste der Nachbarn.",
"repeater_cliHelpRegion": "Listet alle definierten Regionen auf.",
"repeater_cliHelpRegionLoad": "Hinweis: Dies ist ein spezieller Mehrbefehl-Aufruf. Jeder nachfolgende Befehl ist ein Regionsname (eingedruckt mit Leerzeichen zur Angabe der übergeordneten Hierarchie, mit mindestens einem Leerzeichen). Beendet durch das Senden einer Leerzeile/des Befehls.",
"repeater_cliHelpRegionLoad": "Hinweis: Dies ist ein spezieller Mehrbefehl-Aufruf. Jeder nachfolgende Befehl ist ein Regionsname (eingeckt mit Leerzeichen zur Angabe der übergeordneten Hierarchie, mit mindestens einem Leerzeichen). Beendet durch das Senden einer Leerzeile.",
"repeater_cliHelpRegionGet": "Sucht die Region mit dem gegebenen Namenspräfix (oder \"\\\" für den globalen Scope) und antwortet mit \"-> region-name (parent-name) 'F'\".",
"repeater_cliHelpRegionPut": "Fügt eine Region-Definition mit dem angegebenen Namen hinzu oder aktualisiert diese.",
"repeater_cliHelpRegionRemove": "Löscht eine Regiondefinition mit dem angegebenen Namen. (muss genau übereinstimmen und keine Kindregionen haben)",
@@ -1243,7 +1243,7 @@
"channelPath_otherObservedPaths": "Sonstige beobachtete Pfade",
"channelPath_repeaterHops": "Repeater-Sprünge",
"channelPath_noHopDetails": "Die Detailangaben für dieses Paket sind nicht verfügbar.",
"channelPath_messageDetails": "Nachrichtsdetails",
"channelPath_messageDetails": "Nachrichtendetails",
"channelPath_senderLabel": "Sender",
"channelPath_timeLabel": "Zeit",
"channelPath_repeatsLabel": "Wiederholungen",
@@ -1347,7 +1347,7 @@
"listFilter_users": "Benutzer",
"listFilter_repeaters": "Repeater",
"listFilter_roomServers": "Raumserver",
"listFilter_unreadOnly": "Nur nicht gelesen",
"listFilter_unreadOnly": "Nicht gelesen",
"listFilter_newGroup": "Neue Gruppe",
"@neighbors_errorLoading": {
"placeholders": {
@@ -1358,11 +1358,11 @@
},
"repeater_neighbours": "Nachbarn",
"repeater_neighboursSubtitle": "Anzahl der Hop-Nachbarn anzeigen.",
"neighbors_receivedData": "Empfangene Nachbarendaten",
"neighbors_requestTimedOut": "Nachbarn melden zeitweise Ausfall.",
"neighbors_receivedData": "Empfangene Nachbarsdaten",
"neighbors_requestTimedOut": "Anfrage durch Timeout fehlgeschlagen.",
"neighbors_errorLoading": "Fehler beim Laden der Nachbarn: {error}",
"neighbors_repeatersNeighbours": "Nachbarn",
"neighbors_noData": "Keine Nachbardaten verfügbar.",
"neighbors_noData": "Keine Nachbarsdaten verfügbar.",
"channels_joinPrivateChannel": "Treten Sie einem privaten Kanal bei",
"channels_joinPrivateChannelDesc": "Manuelle Eingabe eines geheimen Schlüssels.",
"channels_createPrivateChannel": "Erstelle einen privaten Kanal",
@@ -1389,8 +1389,8 @@
}
}
},
"neighbors_heardAgo": "Hörte: {time} vor her.",
"neighbors_unknownContact": "Unbekannte {pubkey}",
"neighbors_heardAgo": "Gehört vor: {time}",
"neighbors_unknownContact": "Unbekannt {pubkey}",
"settings_locationGPSEnable": "GPS aktivieren",
"settings_locationGPSEnableSubtitle": "Aktiviert GPS zur automatischen Aktualisierung des Standorts.",
"settings_locationIntervalSec": "Intervall für GPS (Sekunden)",
@@ -1493,9 +1493,9 @@
"community_deleted": "Community \"{name}\" verlassen",
"community_addHashtagChannel": "Füge einen Community-Hashtag hinzu",
"community_addHashtagChannelDesc": "Füge einen Hashtag-Kanal für diese Community hinzu",
"community_selectCommunity": "Wählen Sie Community",
"community_selectCommunity": "Wählen Sie eine Community",
"community_regularHashtag": "Regulärer Hashtag",
"community_regularHashtagDesc": "Öffentliches Hashtag (jeder kann teilnehmen)",
"community_regularHashtagDesc": "Öffentlicher Hashtag (jeder kann teilnehmen)",
"community_communityHashtagDesc": "Nur für Mitglieder der Community",
"community_forCommunity": "Für {name}",
"community_communityHashtag": "Community Hashtag",
@@ -1559,16 +1559,16 @@
"appSettings_languageUk": "Ukrainisch",
"contacts_contactImported": "Kontakt wurde importiert.",
"contacts_contactImportFailed": "Kontakt konnte nicht importiert werden",
"contacts_zeroHopAdvert": "Zero-Hop-Anzeige",
"contacts_floodAdvert": "Überflutungsanzeige",
"contacts_zeroHopAdvert": "Zero-Hop-Ankündigung",
"contacts_floodAdvert": "Flut-Ankündigung",
"contacts_addContactFromClipboard": "Kontakt aus Zwischenablage hinzufügen",
"contacts_ShareContactZeroHop": "Kontakt über Anzeige teilen",
"contacts_copyAdvertToClipboard": "Werbung in die Zwischenablage kopieren",
"contacts_copyAdvertToClipboard": "Ankündigung in die Zwischenablage kopieren",
"contacts_ShareContact": "Kontakt in die Zwischenablage kopieren",
"contacts_zeroHopContactAdvertFailed": "Kontakt konnte nicht gesendet werden.",
"contacts_zeroHopContactAdvertSent": "Kontakt über Anzeige gesendet",
"contacts_contactAdvertCopied": "Anzeige in die Zwischenablage kopiert.",
"contacts_contactAdvertCopyFailed": "Kopieren des Werbeinhalts in die Zwischenablage fehlgeschlagen.",
"contacts_contactAdvertCopyFailed": "Kopieren der Ankündigung in die Zwischenablage fehlgeschlagen.",
"notification_activityTitle": "MeshCore Aktivität",
"notification_messagesCount": "{count} {count, plural, =1{Nachricht} other{Nachrichten}}",
@@ -1596,22 +1596,22 @@
}
},
"notification_receivedNewMessage": "Neue Nachricht empfangen",
"contacts_contactAdvertCopyFailed": "Kopieren des Werbeinhalts in die Zwischenablage fehlgeschlagen.",
"settings_gpxExportAll": "Alle Kontakte nach GPX exportieren",
"settings_gpxExportAllSubtitle": "Exportiert alle Kontakte mit einem Standort in eine GPX-Datei.",
"settings_gpxExportRepeaters": "Repeater und Raumserver nach GPX exportieren",
"settings_gpxExportContacts": "Begleiter nach GPX exportieren",
"contacts_contactAdvertCopyFailed": "Kopieren der Ankündigung in die Zwischenablage fehlgeschlagen.",
"settings_gpxExportAll": "Alle Knoten als GPX exportieren",
"settings_gpxExportAllSubtitle": "Exportiert alle Knoten mit einem Standort in eine GPX-Datei.",
"settings_gpxExportRepeaters": "Repeater und Raumserver als GPX exportieren",
"settings_gpxExportContacts": "Kontakte als GPX exportieren",
"settings_gpxExportRepeatersSubtitle": "Exportiert Repeater und Raumserver mit einem Standort in eine GPX-Datei.",
"settings_gpxExportContactsSubtitle": "Exportiert Begleiter mit einem Ort in eine GPX-Datei.",
"settings_gpxExportContactsSubtitle": "Exportiert Kontakte mit einem Ort in eine GPX-Datei.",
"settings_gpxExportRepeatersRoom": "Repeater- und Raumserver-Standorte",
"settings_gpxExportChat": "Begleiterstandorte",
"settings_gpxExportChat": "Kontaktstandorte",
"settings_gpxExportNoContacts": "Keine Kontakte zum Exportieren.",
"settings_gpxExportError": "Beim Export ist ein Fehler aufgetreten.",
"settings_gpxExportNotAvailable": "Nicht auf Ihrem Gerät/Betriebssystem unterstützt",
"settings_gpxExportSuccess": "Erfolgreich GPX-Datei exportiert.",
"settings_gpxExportSuccess": "GPX-Datei erfolgreich exportiert.",
"settings_gpxExportAllContacts": "Alle Kontaktstandorte",
"settings_gpxExportShareSubject": "meshcore-open GPX-Kartendaten exportieren",
"settings_gpxExportShareText": "Kartendaten aus meshcore-open exportiert",
"pathTrace_someHopsNoLocation": "Eine oder mehrere der Hopfen fehlen einen Standort!"
"settings_gpxExportShareSubject": "GPX-Kartendaten aus meshcore-open exportieren",
"settings_gpxExportShareText": "GPX-Kartendaten aus meshcore-open exportiert",
"pathTrace_someHopsNoLocation": "Bei einer oder mehreren Knoten fehlt der Standort!"
}
+1
View File
@@ -619,6 +619,7 @@
"map_sharedPin": "Shared pin",
"map_joinRoom": "Join Room",
"map_manageRepeater": "Manage Repeater",
"map_updateMyLocation": "Update Location",
"mapCache_title": "Offline Map Cache",
"mapCache_selectAreaFirst": "Select an area to cache first",
"mapCache_noTilesToDownload": "No tiles to download for this area",
+35 -37
View File
@@ -210,8 +210,8 @@
"appSettings_batteryLifepo4": "LiFePO4 (2,6-3,65V)",
"appSettings_batteryLipo": "LiPo (3,0-4,2V)",
"appSettings_mapDisplay": "Affichage de la carte",
"appSettings_showRepeaters": "Afficher les répétiteurs",
"appSettings_showRepeatersSubtitle": "Afficher les nœuds répétiteurs sur la carte",
"appSettings_showRepeaters": "Afficher les répéteurs",
"appSettings_showRepeatersSubtitle": "Afficher les nœuds répéteurs sur la carte",
"appSettings_showChatNodes": "Afficher les nœuds de discussion",
"appSettings_showChatNodesSubtitle": "Afficher les nœuds de chat sur la carte",
"appSettings_showOtherNodes": "Afficher d'autres nœuds",
@@ -266,7 +266,7 @@
}
}
},
"contacts_manageRepeater": "Gérer le répétiteur",
"contacts_manageRepeater": "Gérer le répéteur",
"contacts_roomLogin": "Connexion Salle",
"contacts_openChat": "Ouverture du Chat",
"contacts_editGroup": "Modifier le groupe",
@@ -542,9 +542,9 @@
"chat_forceFloodMode": "Mode tout le réseau forcé",
"chat_recentAckPaths": "Chemins ACK récents (touchez pour utiliser) :",
"chat_pathHistoryFull": "L'historique du chemin est plein. Supprimez les entrées pour en ajouter de nouvelles.",
"chat_hopSingular": "Sautez",
"chat_hopPlural": "sautez",
"chat_hopsCount": "{count} {count, plural, =1{hop} other{hops}}",
"chat_hopSingular": "saut",
"chat_hopPlural": "sauts",
"chat_hopsCount": "{count} {count, plural, =1{saut} other{sauts}}",
"@chat_hopsCount": {
"placeholders": {
"count": {
@@ -636,7 +636,7 @@
}
},
"map_chat": "Chat",
"map_repeater": "Répétiteur",
"map_repeater": "Répéteur",
"map_room": "Salle",
"map_sensor": "Capteur",
"map_pinDm": "Clé (DM)",
@@ -677,7 +677,7 @@
"map_lastSeenTime": "Dernière fois vu",
"map_sharedPin": "Clé partagée",
"map_joinRoom": "Rejoindre la salle",
"map_manageRepeater": "Gérer le répétiteur",
"map_manageRepeater": "Gérer le répéteur",
"mapCache_title": "Cache de Carte Hors Ligne",
"mapCache_selectAreaFirst": "Sélectionner une zone pour la mise en cache en premier",
"mapCache_noTilesToDownload": "Aucun tuilage à télécharger pour cette zone.",
@@ -800,13 +800,13 @@
"time_allTime": "Tout le temps",
"dialog_disconnect": "Déconnecter",
"dialog_disconnectConfirm": "Êtes-vous sûr de vouloir vous déconnecter de cet appareil ?",
"login_repeaterLogin": "Connexion au répétiteur",
"login_repeaterLogin": "Connexion au répéteur",
"login_roomLogin": "Connexion Salle",
"login_password": "Mot de passe",
"login_enterPassword": "Entrez votre mot de passe",
"login_savePassword": "Sauvegarder le mot de passe",
"login_savePasswordSubtitle": "Le mot de passe sera stocké en toute sécurité sur cet appareil.",
"login_repeaterDescription": "Entrez le mot de passe du répétiteur pour accéder aux paramètres et à l'état.",
"login_repeaterDescription": "Entrez le mot de passe du répéteur pour accéder aux paramètres et à l'état.",
"login_roomDescription": "Entrez le mot de passe de la pièce pour accéder aux paramètres et à l'état.",
"login_routing": "Redirection",
"login_routingMode": "Mode de routage",
@@ -871,17 +871,17 @@
},
"path_tooLong": "Le chemin est trop long. Maximum 64 sauts autorisés.",
"path_setPath": "Définir le chemin",
"repeater_management": "Gestion des répétiteurs",
"repeater_management": "Gestion des répéteurs",
"repeater_managementTools": "Outils de Gestion",
"repeater_status": "État",
"repeater_statusSubtitle": "Afficher l'état, les statistiques et les voisins du répétiteur",
"repeater_statusSubtitle": "Afficher l'état, les statistiques et les voisins du répéteur",
"repeater_telemetry": "Télémetrie",
"repeater_telemetrySubtitle": "Afficher la télémétrie des capteurs et les statistiques du système",
"repeater_cli": "CLI",
"repeater_cliSubtitle": "Envoyer des commandes au répétiteur",
"repeater_cliSubtitle": "Envoyer des commandes au répéteur",
"repeater_settings": "Paramètres",
"repeater_settingsSubtitle": "Configurer les paramètres du répétiteur",
"repeater_statusTitle": "État du répétiteur",
"repeater_settingsSubtitle": "Configurer les paramètres du répéteur",
"repeater_statusTitle": "État du répéteur",
"repeater_routingMode": "Mode de routage",
"repeater_autoUseSavedPath": "Auto (utiliser le chemin sauvegardé)",
"repeater_forceFloodMode": "Mode tout le réseau forcé",
@@ -976,10 +976,10 @@
}
}
},
"repeater_settingsTitle": "Paramètres du répétiteur",
"repeater_settingsTitle": "Paramètres du répéteur",
"repeater_basicSettings": "Paramètres de base",
"repeater_repeaterName": "Nom du répétiteur",
"repeater_repeaterNameHelper": "Afficher le nom de ce répétiteur",
"repeater_repeaterName": "Nom du répéteur",
"repeater_repeaterNameHelper": "Afficher le nom de ce répéteur",
"repeater_adminPassword": "Mot de passe Administrateur",
"repeater_adminPasswordHelper": "Mot de passe d'accès complet",
"repeater_guestPassword": "Mot de passe invité",
@@ -999,7 +999,7 @@
"repeater_longitudeHelper": "Degrés décimaux (par exemple, -122,4194)",
"repeater_features": "Fonctionnalités",
"repeater_packetForwarding": "Transfert de paquets",
"repeater_packetForwardingSubtitle": "Activer le répétiteur pour transmettre des paquets",
"repeater_packetForwardingSubtitle": "Activer le répéteur pour transmettre des paquets",
"repeater_guestAccess": "Accès Invité",
"repeater_guestAccessSubtitle": "Autoriser l'accès invité en lecture seule",
"repeater_privacyMode": "Mode de confidentialité",
@@ -1026,14 +1026,14 @@
"repeater_encryptedAdvertInterval": "Intervalle d'annonces cryptées",
"repeater_dangerZone": "Zone dangereuse",
"repeater_rebootRepeater": "Redémarrer Répéteur",
"repeater_rebootRepeaterSubtitle": "Réinitialiser l'appareil répétiteur",
"repeater_rebootRepeaterConfirm": "Êtes-vous sûr de vouloir redémarrer ce répétiteur ?",
"repeater_rebootRepeaterSubtitle": "Réinitialiser l'appareil répéteur",
"repeater_rebootRepeaterConfirm": "Êtes-vous sûr de vouloir redémarrer ce répéteur ?",
"repeater_regenerateIdentityKey": "Ré générer la clé d'identité",
"repeater_regenerateIdentityKeySubtitle": "Générer une nouvelle paire de clés publique/privée",
"repeater_regenerateIdentityKeyConfirm": "Cela générera une nouvelle identité pour le répétiteur. Continuer ?",
"repeater_regenerateIdentityKeyConfirm": "Cela générera une nouvelle identité pour le répéteur. Continuer ?",
"repeater_eraseFileSystem": "Supprimer le système de fichiers",
"repeater_eraseFileSystemSubtitle": "Formater le système de fichiers du répétiteur",
"repeater_eraseFileSystemConfirm": "AVERTISSEMENT : Cela effacera toutes les données du répétiteur. Cela ne peut pas être annulé !",
"repeater_eraseFileSystemSubtitle": "Formater le système de fichiers du répéteur",
"repeater_eraseFileSystemConfirm": "AVERTISSEMENT : Cela effacera toutes les données du répéteur. Cela ne peut pas être annulé !",
"repeater_eraseSerialOnly": "Erase n'est disponible que via la console série.",
"repeater_commandSent": "Commande envoyée : {command}",
"@repeater_commandSent": {
@@ -1085,7 +1085,7 @@
}
}
},
"repeater_cliTitle": "Répétiteur CLI",
"repeater_cliTitle": "Répéteur CLI",
"repeater_debugNextCommand": "Déboguer Prochaine Commande",
"repeater_commandHelp": "Aide",
"repeater_clearHistory": "Effacer l'historique",
@@ -1119,13 +1119,13 @@
"repeater_cliHelpClearStats": "Réinitialise divers compteurs de statistiques à zéro.",
"repeater_cliHelpSetAf": "Définit le facteur de temps d'air.",
"repeater_cliHelpSetTx": "Définit la puissance de transmission LoRa en dBm (réinitialisation requise pour appliquer).",
"repeater_cliHelpSetRepeat": "Active ou désactive le rôle du répétiteur pour ce nœud.",
"repeater_cliHelpSetRepeat": "Active ou désactive le rôle du répéteur pour ce nœud.",
"repeater_cliHelpSetAllowReadOnly": "(Room server) Si \"activé\", alors un mot de passe vide permettra la connexion, mais ne permettra pas de publier dans la pièce. (lecture seule uniquement)",
"repeater_cliHelpSetFloodMax": "Définit le nombre maximal de sauts pour les paquets de balayage entrants (si >= max, le paquet n'est pas acheminé).",
"repeater_cliHelpSetIntThresh": "Définit le seuil d'interférence (en dB). La valeur par défaut est de 14. Définir sur 0 désactive la détection des interférences de canal.",
"repeater_cliHelpSetAgcResetInterval": "Définit l'intervalle pour réinitialiser le contrôleur de gain automatique. Mettez à 0 pour désactiver.",
"repeater_cliHelpSetMultiAcks": "Active ou désactive la fonctionnalité « double ACKs ».",
"repeater_cliHelpSetAdvertInterval": "Définit l'intervalle du minuteur pour envoyer un paquet d'annonce local (sans relais). Définir sur 0 pour désactiver.",
"repeater_cliHelpSetAdvertInterval": "Définit l'intervalle entre chaque émission d'une annonce locale (sans relais). Définir sur 0 pour désactiver.",
"repeater_cliHelpSetFloodAdvertInterval": "Définit l'intervalle du minuteur en heures pour envoyer un paquet d'annonce massive. Définir sur 0 pour désactiver.",
"repeater_cliHelpSetGuestPassword": "Définit/met à jour le mot de passe de l'invité. (pour les répéteurs, les connexions d'invités peuvent envoyer la requête \"Get Stats\")",
"repeater_cliHelpSetName": "Définit le nom de l'annonce.",
@@ -1147,7 +1147,7 @@
"repeater_cliHelpLogStart": "Démarre l'enregistrement des paquets dans le système de fichiers.",
"repeater_cliHelpLogStop": "Arrêter de journaliser les paquets vers le système de fichiers.",
"repeater_cliHelpLogErase": "Supprime les journaux de paquets du système de fichiers.",
"repeater_cliHelpNeighbors": "Affiche une liste d'autres nœuds répétiteurs entendus via des annonces sans relais. Chaque ligne est id-préfixe-hexadécimal:timestamp:snr-fois-4",
"repeater_cliHelpNeighbors": "Affiche une liste d'autres nœuds répéteurs entendus via des annonces sans relais. Chaque ligne est id-préfixe-hexadécimal:timestamp:snr-fois-4",
"repeater_cliHelpNeighborRemove": "Supprime la première entrée correspondante (par préfixe de clé publique (hexadécimal)) de la liste des voisins.",
"repeater_cliHelpRegion": "(série uniquement) Liste toutes les régions définies et les autorisations actuelles d'annonces sur tout le réseau (flood).",
"repeater_cliHelpRegionLoad": "REMARQUE : il s'agit d'une invocation multi-commande spéciale. Chaque commande subséquente est un nom de région (indenté avec des espaces pour indiquer la hiérarchie parent, avec un minimum d'un espace). Terminé par l'envoi d'une ligne vide/commande.",
@@ -1171,8 +1171,8 @@
"repeater_settingsCategory": "Paramètres",
"repeater_bridge": "Pont",
"repeater_logging": "Journalisation",
"repeater_neighborsRepeaterOnly": "Voisins (Uniquement répétiteur)",
"repeater_regionManagementRepeaterOnly": "Gestion des régions (uniquement pour le répétiteur)",
"repeater_neighborsRepeaterOnly": "Voisins (Uniquement répéteur)",
"repeater_regionManagementRepeaterOnly": "Gestion des régions (uniquement pour le répéteur)",
"repeater_regionNote": "Les commandes de région ont été introduites pour gérer les définitions et les autorisations des régions.",
"repeater_gpsManagement": "Gestion GPS",
"repeater_gpsNote": "La commande GPS a été introduite pour gérer les sujets liés à la localisation.",
@@ -1241,7 +1241,7 @@
"channelPath_title": "Chemin de paquet",
"channelPath_viewMap": "Afficher la carte",
"channelPath_otherObservedPaths": "Autres chemins observés",
"channelPath_repeaterHops": "Sauts du répétiteur",
"channelPath_repeaterHops": "Sauts du répéteur",
"channelPath_noHopDetails": "Les détails de l'envoi ne sont pas fournis pour ce paquet.",
"channelPath_messageDetails": "Détails du message",
"channelPath_senderLabel": "Expéditeur",
@@ -1306,7 +1306,7 @@
}
},
"channelPath_mapTitle": "Carte du chemin",
"channelPath_noRepeaterLocations": "Aucune position de répétiteur disponible pour ce chemin.",
"channelPath_noRepeaterLocations": "Aucune position de répéteur disponible pour ce chemin.",
"channelPath_primaryPath": "Chemin {index} (Principal)",
"@channelPath_primaryPath": {
"placeholders": {
@@ -1558,9 +1558,9 @@
"appSettings_languageRu": "Russe",
"contacts_clipboardEmpty": "Le presse-papiers est vide.",
"contacts_contactImported": "Le contact a été importé.",
"contacts_floodAdvert": "Annonce de crue",
"contacts_floodAdvert": "Annonce à tout le réseau",
"contacts_contactImportFailed": "Échec de l'importation du contact.",
"contacts_zeroHopAdvert": "Annonce Zero Hop",
"contacts_zeroHopAdvert": "Annonce Zero saut",
"contacts_copyAdvertToClipboard": "Copier l'annonce dans le presse-papiers",
"contacts_addContactFromClipboard": "Ajouter un contact depuis le presse-papiers",
"contacts_ShareContact": "Copier le contact dans le presse-papiers",
@@ -1575,7 +1575,6 @@
"notification_newNodesCount": "{count} {count, plural, =1{nouveau nœud} other{nouveaux nœuds}}",
"notification_newTypeDiscovered": "Nouveau {contactType} découvert",
"notification_receivedNewMessage": "Nouveau message reçu",
"contacts_zeroHopContactAdvertFailed": "Échec de l'envoi du contact.",
"settings_gpxExportRepeaters": "Exporter les répéteurs / serveur de salle au format GPX",
"settings_gpxExportRepeatersSubtitle": "Exporte les répéteurs / roomserver avec une localisation vers un fichier GPX.",
"settings_gpxExportNoContacts": "Aucun contact à exporter.",
@@ -1591,6 +1590,5 @@
"settings_gpxExportAllContacts": "Tous les emplacements des contacts",
"settings_gpxExportShareText": "Données de carte exportées à partir de meshcore-open",
"settings_gpxExportShareSubject": "meshcore-open exporter les données de carte GPX",
"pathTrace_someHopsNoLocation": "Une ou plusieurs des houblons manquent d'une localisation !"
"pathTrace_someHopsNoLocation": "Un ou plusieurs des sauts manquent d'une localisation !"
}
+39 -38
View File
@@ -244,10 +244,10 @@ class AppLocalizationsDe extends AppLocalizations {
String get settings_actions => 'Aktionen';
@override
String get settings_sendAdvertisement => 'Sende eine Ankündigung';
String get settings_sendAdvertisement => 'Sende Ankündigung';
@override
String get settings_sendAdvertisementSubtitle => 'Sende Ankündigung';
String get settings_sendAdvertisementSubtitle => 'Sende eine Ankündigung';
@override
String get settings_advertisementSent => 'Ankündigung gesendet';
@@ -267,7 +267,7 @@ class AppLocalizationsDe extends AppLocalizations {
@override
String get settings_refreshContactsSubtitle =>
'Kontakte-Liste vom Gerät neu laden';
'Kontakt-Liste vom Gerät neu laden';
@override
String get settings_rebootDevice => 'Gerät neu starten';
@@ -1086,7 +1086,7 @@ class AppLocalizationsDe extends AppLocalizations {
@override
String get chat_recentAckPaths =>
'Aktuelle ACK-Pfade (tasten, um zu verwenden):';
'Aktuelle ACK-Pfade (antippen, um zu verwenden):';
@override
String get chat_pathHistoryFull =>
@@ -1117,7 +1117,7 @@ class AppLocalizationsDe extends AppLocalizations {
@override
String get chat_noPathHistoryYet =>
'Keine eine Pfadhistorie vorhanden.\nSende eine Nachricht, um Pfade zu entdecken.';
'Keine Pfadhistorie vorhanden.\nSende eine Nachricht, um Pfade zu entdecken.';
@override
String get chat_pathActions => 'Pfadaktionen:';
@@ -1418,7 +1418,7 @@ class AppLocalizationsDe extends AppLocalizations {
@override
String mapCache_estimatedTiles(int count) {
return 'Geschätzte Fliesen: $count';
return 'Geschätzte Kacheln: $count';
}
@override
@@ -1592,7 +1592,7 @@ class AppLocalizationsDe extends AppLocalizations {
@override
String get path_hexPrefixInstructions =>
'Gebe für jeden Hopfen 2-stellige Hex-Präfixe ein, getrennt durch Kommas.';
'Gebe für jeden Zwischen-Hop das 2-stellige Hex-Präfix ein, getrennt durch Kommas.';
@override
String get path_hexPrefixExample =>
@@ -1689,7 +1689,7 @@ class AppLocalizationsDe extends AppLocalizations {
@override
String get repeater_statusRequestTimeout =>
'Statusanfrage zeitweise fehlgeschlagen.';
'Statusanfrage durch Timeout fehlgeschlagen.';
@override
String repeater_errorLoadingStatus(String error) {
@@ -1766,7 +1766,7 @@ class AppLocalizationsDe extends AppLocalizations {
@override
String repeater_duplicatesFloodDirect(String flood, String direct) {
return 'Überflut: $flood, Direkt: $direct';
return 'Flut: $flood, Direkt: $direct';
}
@override
@@ -1797,7 +1797,7 @@ class AppLocalizationsDe extends AppLocalizations {
@override
String get repeater_guestPasswordHelper =>
'Schreibgeschützter Zugriffspasswort';
'Schreibgeschütztes Zugriffspasswort';
@override
String get repeater_radioSettings => 'Funk Einstellungen';
@@ -1992,7 +1992,7 @@ class AppLocalizationsDe extends AppLocalizations {
String get repeater_cliTitle => 'Repeater CLI';
@override
String get repeater_debugNextCommand => 'Fehlersuche Nächster Befehl';
String get repeater_debugNextCommand => 'Fehlersuche des nächsten Befehls';
@override
String get repeater_commandHelp => 'Hilfe';
@@ -2005,7 +2005,7 @@ class AppLocalizationsDe extends AppLocalizations {
@override
String get repeater_typeCommandOrUseQuick =>
'Geben Sie einen Befehl unten ein oder verwenden Sie Schnellbefehle';
'Geben Sie unten einen Befehl ein oder verwenden Sie die Schnellbefehle';
@override
String get repeater_enterCommandHint => 'Geben Sie den Befehl ein...';
@@ -2131,7 +2131,7 @@ class AppLocalizationsDe extends AppLocalizations {
@override
String get repeater_cliHelpSetRxDelay =>
'Sets (experimentell) als Basis (muss > 1 sein für den Effekt) zur Anwendung einer leichten Verzögerung bei empfangenen Paketen, basierend auf Signalstärke/Punktzahl. Auf 0 setzen, um die Funktion zu deaktivieren.';
'Fügt eine leichte Verzögerung bei empfangenen Paketen hinzu, basierend auf Signalstärke/Punktzahl. Auf 0 setzen, um die Funktion zu deaktivieren.';
@override
String get repeater_cliHelpSetTxDelay =>
@@ -2175,7 +2175,7 @@ class AppLocalizationsDe extends AppLocalizations {
@override
String get repeater_cliHelpGetBridgeType =>
'Ruft Brückentyp none, rs232, espnow ab.';
'Ruft Brückentyp: none, rs232, espnow ab.';
@override
String get repeater_cliHelpLogStart =>
@@ -2202,7 +2202,7 @@ class AppLocalizationsDe extends AppLocalizations {
@override
String get repeater_cliHelpRegionLoad =>
'Hinweis: Dies ist ein spezieller Mehrbefehl-Aufruf. Jeder nachfolgende Befehl ist ein Regionsname (eingedruckt mit Leerzeichen zur Angabe der übergeordneten Hierarchie, mit mindestens einem Leerzeichen). Beendet durch das Senden einer Leerzeile/des Befehls.';
'Hinweis: Dies ist ein spezieller Mehrbefehl-Aufruf. Jeder nachfolgende Befehl ist ein Regionsname (eingeckt mit Leerzeichen zur Angabe der übergeordneten Hierarchie, mit mindestens einem Leerzeichen). Beendet durch das Senden einer Leerzeile.';
@override
String get repeater_cliHelpRegionGet =>
@@ -2351,10 +2351,11 @@ class AppLocalizationsDe extends AppLocalizations {
}
@override
String get neighbors_receivedData => 'Empfangene Nachbarendaten';
String get neighbors_receivedData => 'Empfangene Nachbarsdaten';
@override
String get neighbors_requestTimedOut => 'Nachbarn melden zeitweise Ausfall.';
String get neighbors_requestTimedOut =>
'Anfrage durch Timeout fehlgeschlagen.';
@override
String neighbors_errorLoading(String error) {
@@ -2365,16 +2366,16 @@ class AppLocalizationsDe extends AppLocalizations {
String get neighbors_repeatersNeighbours => 'Nachbarn';
@override
String get neighbors_noData => 'Keine Nachbardaten verfügbar.';
String get neighbors_noData => 'Keine Nachbarsdaten verfügbar.';
@override
String neighbors_unknownContact(String pubkey) {
return 'Unbekannte $pubkey';
return 'Unbekannt $pubkey';
}
@override
String neighbors_heardAgo(String time) {
return 'Hörte: $time vor her.';
return 'Gehört vor: $time';
}
@override
@@ -2394,7 +2395,7 @@ class AppLocalizationsDe extends AppLocalizations {
'Die Detailangaben für dieses Paket sind nicht verfügbar.';
@override
String get channelPath_messageDetails => 'Nachrichtsdetails';
String get channelPath_messageDetails => 'Nachrichtendetails';
@override
String get channelPath_senderLabel => 'Sender';
@@ -2630,14 +2631,14 @@ class AppLocalizationsDe extends AppLocalizations {
'Füge einen Hashtag-Kanal für diese Community hinzu';
@override
String get community_selectCommunity => 'Wählen Sie Community';
String get community_selectCommunity => 'Wählen Sie eine Community';
@override
String get community_regularHashtag => 'Regulärer Hashtag';
@override
String get community_regularHashtagDesc =>
'Öffentliches Hashtag (jeder kann teilnehmen)';
'Öffentlicher Hashtag (jeder kann teilnehmen)';
@override
String get community_communityHashtag => 'Community Hashtag';
@@ -2682,7 +2683,7 @@ class AppLocalizationsDe extends AppLocalizations {
String get listFilter_roomServers => 'Raumserver';
@override
String get listFilter_unreadOnly => 'Nur nicht gelesen';
String get listFilter_unreadOnly => 'Nicht gelesen';
@override
String get listFilter_newGroup => 'Neue Gruppe';
@@ -2701,7 +2702,7 @@ class AppLocalizationsDe extends AppLocalizations {
@override
String get pathTrace_someHopsNoLocation =>
'Eine oder mehrere der Hopfen fehlen einen Standort!';
'Bei einer oder mehreren Knoten fehlt der Standort!';
@override
String get contacts_pathTrace => 'Pfadverfolgung';
@@ -2743,14 +2744,14 @@ class AppLocalizationsDe extends AppLocalizations {
'Kontakt konnte nicht importiert werden';
@override
String get contacts_zeroHopAdvert => 'Zero-Hop-Anzeige';
String get contacts_zeroHopAdvert => 'Zero-Hop-Ankündigung';
@override
String get contacts_floodAdvert => 'Überflutungsanzeige';
String get contacts_floodAdvert => 'Flut-Ankündigung';
@override
String get contacts_copyAdvertToClipboard =>
'Werbung in die Zwischenablage kopieren';
'Ankündigung in die Zwischenablage kopieren';
@override
String get contacts_addContactFromClipboard =>
@@ -2776,7 +2777,7 @@ class AppLocalizationsDe extends AppLocalizations {
@override
String get contacts_contactAdvertCopyFailed =>
'Kopieren des Werbeinhalts in die Zwischenablage fehlgeschlagen.';
'Kopieren der Ankündigung in die Zwischenablage fehlgeschlagen.';
@override
String get notification_activityTitle => 'MeshCore Aktivität';
@@ -2824,28 +2825,28 @@ class AppLocalizationsDe extends AppLocalizations {
@override
String get settings_gpxExportRepeaters =>
'Repeater und Raumserver nach GPX exportieren';
'Repeater und Raumserver als GPX exportieren';
@override
String get settings_gpxExportRepeatersSubtitle =>
'Exportiert Repeater und Raumserver mit einem Standort in eine GPX-Datei.';
@override
String get settings_gpxExportContacts => 'Begleiter nach GPX exportieren';
String get settings_gpxExportContacts => 'Kontakte als GPX exportieren';
@override
String get settings_gpxExportContactsSubtitle =>
'Exportiert Begleiter mit einem Ort in eine GPX-Datei.';
'Exportiert Kontakte mit einem Ort in eine GPX-Datei.';
@override
String get settings_gpxExportAll => 'Alle Kontakte nach GPX exportieren';
String get settings_gpxExportAll => 'Alle Knoten als GPX exportieren';
@override
String get settings_gpxExportAllSubtitle =>
'Exportiert alle Kontakte mit einem Standort in eine GPX-Datei.';
'Exportiert alle Knoten mit einem Standort in eine GPX-Datei.';
@override
String get settings_gpxExportSuccess => 'Erfolgreich GPX-Datei exportiert.';
String get settings_gpxExportSuccess => 'GPX-Datei erfolgreich exportiert.';
@override
String get settings_gpxExportNoContacts => 'Keine Kontakte zum Exportieren.';
@@ -2863,16 +2864,16 @@ class AppLocalizationsDe extends AppLocalizations {
'Repeater- und Raumserver-Standorte';
@override
String get settings_gpxExportChat => 'Begleiterstandorte';
String get settings_gpxExportChat => 'Kontaktstandorte';
@override
String get settings_gpxExportAllContacts => 'Alle Kontaktstandorte';
@override
String get settings_gpxExportShareText =>
'Kartendaten aus meshcore-open exportiert';
'GPX-Kartendaten aus meshcore-open exportiert';
@override
String get settings_gpxExportShareSubject =>
'meshcore-open GPX-Kartendaten exportieren';
'GPX-Kartendaten aus meshcore-open exportieren';
}
+36 -37
View File
@@ -560,11 +560,11 @@ class AppLocalizationsFr extends AppLocalizations {
String get appSettings_mapDisplay => 'Affichage de la carte';
@override
String get appSettings_showRepeaters => 'Afficher les répétiteurs';
String get appSettings_showRepeaters => 'Afficher les répéteurs';
@override
String get appSettings_showRepeatersSubtitle =>
'Afficher les nœuds répétiteurs sur la carte';
'Afficher les nœuds répéteurs sur la carte';
@override
String get appSettings_showChatNodes => 'Afficher les nœuds de discussion';
@@ -671,7 +671,7 @@ class AppLocalizationsFr extends AppLocalizations {
}
@override
String get contacts_manageRepeater => 'Gérer le répétiteur';
String get contacts_manageRepeater => 'Gérer le répéteur';
@override
String get contacts_manageRoom => 'Gérer le Room Server';
@@ -1094,18 +1094,18 @@ class AppLocalizationsFr extends AppLocalizations {
'L\'historique du chemin est plein. Supprimez les entrées pour en ajouter de nouvelles.';
@override
String get chat_hopSingular => 'Sautez';
String get chat_hopSingular => 'saut';
@override
String get chat_hopPlural => 'sautez';
String get chat_hopPlural => 'sauts';
@override
String chat_hopsCount(int count) {
String _temp0 = intl.Intl.pluralLogic(
count,
locale: localeName,
other: 'hops',
one: 'hop',
other: 'sauts',
one: 'saut',
);
return '$count $_temp0';
}
@@ -1259,7 +1259,7 @@ class AppLocalizationsFr extends AppLocalizations {
String get map_chat => 'Chat';
@override
String get map_repeater => 'Répétiteur';
String get map_repeater => 'Répéteur';
@override
String get map_room => 'Salle';
@@ -1365,7 +1365,7 @@ class AppLocalizationsFr extends AppLocalizations {
String get map_joinRoom => 'Rejoindre la salle';
@override
String get map_manageRepeater => 'Gérer le répétiteur';
String get map_manageRepeater => 'Gérer le répéteur';
@override
String get mapCache_title => 'Cache de Carte Hors Ligne';
@@ -1509,7 +1509,7 @@ class AppLocalizationsFr extends AppLocalizations {
'Êtes-vous sûr de vouloir vous déconnecter de cet appareil ?';
@override
String get login_repeaterLogin => 'Connexion au répétiteur';
String get login_repeaterLogin => 'Connexion au répéteur';
@override
String get login_roomLogin => 'Connexion Salle';
@@ -1529,7 +1529,7 @@ class AppLocalizationsFr extends AppLocalizations {
@override
String get login_repeaterDescription =>
'Entrez le mot de passe du répétiteur pour accéder aux paramètres et à l\'état.';
'Entrez le mot de passe du répéteur pour accéder aux paramètres et à l\'état.';
@override
String get login_roomDescription =>
@@ -1634,7 +1634,7 @@ class AppLocalizationsFr extends AppLocalizations {
String get path_setPath => 'Définir le chemin';
@override
String get repeater_management => 'Gestion des répétiteurs';
String get repeater_management => 'Gestion des répéteurs';
@override
String get room_management => 'Administración del Servidor de Habitación';
@@ -1647,7 +1647,7 @@ class AppLocalizationsFr extends AppLocalizations {
@override
String get repeater_statusSubtitle =>
'Afficher l\'état, les statistiques et les voisins du répétiteur';
'Afficher l\'état, les statistiques et les voisins du répéteur';
@override
String get repeater_telemetry => 'Télémetrie';
@@ -1660,7 +1660,7 @@ class AppLocalizationsFr extends AppLocalizations {
String get repeater_cli => 'CLI';
@override
String get repeater_cliSubtitle => 'Envoyer des commandes au répétiteur';
String get repeater_cliSubtitle => 'Envoyer des commandes au répéteur';
@override
String get repeater_neighbours => 'Voisins';
@@ -1674,10 +1674,10 @@ class AppLocalizationsFr extends AppLocalizations {
@override
String get repeater_settingsSubtitle =>
'Configurer les paramètres du répétiteur';
'Configurer les paramètres du répéteur';
@override
String get repeater_statusTitle => 'État du répétiteur';
String get repeater_statusTitle => 'État du répéteur';
@override
String get repeater_routingMode => 'Mode de routage';
@@ -1783,16 +1783,16 @@ class AppLocalizationsFr extends AppLocalizations {
}
@override
String get repeater_settingsTitle => 'Paramètres du répétiteur';
String get repeater_settingsTitle => 'Paramètres du répéteur';
@override
String get repeater_basicSettings => 'Paramètres de base';
@override
String get repeater_repeaterName => 'Nom du répétiteur';
String get repeater_repeaterName => 'Nom du répéteur';
@override
String get repeater_repeaterNameHelper => 'Afficher le nom de ce répétiteur';
String get repeater_repeaterNameHelper => 'Afficher le nom de ce répéteur';
@override
String get repeater_adminPassword => 'Mot de passe Administrateur';
@@ -1856,7 +1856,7 @@ class AppLocalizationsFr extends AppLocalizations {
@override
String get repeater_packetForwardingSubtitle =>
'Activer le répétiteur pour transmettre des paquets';
'Activer le répéteur pour transmettre des paquets';
@override
String get repeater_guestAccess => 'Accès Invité';
@@ -1905,11 +1905,11 @@ class AppLocalizationsFr extends AppLocalizations {
@override
String get repeater_rebootRepeaterSubtitle =>
'Réinitialiser l\'appareil répétiteur';
'Réinitialiser l\'appareil répéteur';
@override
String get repeater_rebootRepeaterConfirm =>
'Êtes-vous sûr de vouloir redémarrer ce répétiteur ?';
'Êtes-vous sûr de vouloir redémarrer ce répéteur ?';
@override
String get repeater_regenerateIdentityKey => 'Ré générer la clé d\'identité';
@@ -1920,18 +1920,18 @@ class AppLocalizationsFr extends AppLocalizations {
@override
String get repeater_regenerateIdentityKeyConfirm =>
'Cela générera une nouvelle identité pour le répétiteur. Continuer ?';
'Cela générera une nouvelle identité pour le répéteur. Continuer ?';
@override
String get repeater_eraseFileSystem => 'Supprimer le système de fichiers';
@override
String get repeater_eraseFileSystemSubtitle =>
'Formater le système de fichiers du répétiteur';
'Formater le système de fichiers du répéteur';
@override
String get repeater_eraseFileSystemConfirm =>
'AVERTISSEMENT : Cela effacera toutes les données du répétiteur. Cela ne peut pas être annulé !';
'AVERTISSEMENT : Cela effacera toutes les données du répéteur. Cela ne peut pas être annulé !';
@override
String get repeater_eraseSerialOnly =>
@@ -1999,7 +1999,7 @@ class AppLocalizationsFr extends AppLocalizations {
}
@override
String get repeater_cliTitle => 'Répétiteur CLI';
String get repeater_cliTitle => 'Répéteur CLI';
@override
String get repeater_debugNextCommand => 'Déboguer Prochaine Commande';
@@ -2091,7 +2091,7 @@ class AppLocalizationsFr extends AppLocalizations {
@override
String get repeater_cliHelpSetRepeat =>
'Active ou désactive le rôle du répétiteur pour ce nœud.';
'Active ou désactive le rôle du répéteur pour ce nœud.';
@override
String get repeater_cliHelpSetAllowReadOnly =>
@@ -2115,7 +2115,7 @@ class AppLocalizationsFr extends AppLocalizations {
@override
String get repeater_cliHelpSetAdvertInterval =>
'Définit l\'intervalle du minuteur pour envoyer un paquet d\'annonce local (sans relais). Définir sur 0 pour désactiver.';
'Définit l\'intervalle entre chaque émission d\'une annonce locale (sans relais). Définir sur 0 pour désactiver.';
@override
String get repeater_cliHelpSetFloodAdvertInterval =>
@@ -2201,7 +2201,7 @@ class AppLocalizationsFr extends AppLocalizations {
@override
String get repeater_cliHelpNeighbors =>
'Affiche une liste d\'autres nœuds répétiteurs entendus via des annonces sans relais. Chaque ligne est id-préfixe-hexadécimal:timestamp:snr-fois-4';
'Affiche une liste d\'autres nœuds répéteurs entendus via des annonces sans relais. Chaque ligne est id-préfixe-hexadécimal:timestamp:snr-fois-4';
@override
String get repeater_cliHelpNeighborRemove =>
@@ -2289,12 +2289,11 @@ class AppLocalizationsFr extends AppLocalizations {
String get repeater_logging => 'Journalisation';
@override
String get repeater_neighborsRepeaterOnly =>
'Voisins (Uniquement répétiteur)';
String get repeater_neighborsRepeaterOnly => 'Voisins (Uniquement répéteur)';
@override
String get repeater_regionManagementRepeaterOnly =>
'Gestion des régions (uniquement pour le répétiteur)';
'Gestion des régions (uniquement pour le répéteur)';
@override
String get repeater_regionNote =>
@@ -2399,7 +2398,7 @@ class AppLocalizationsFr extends AppLocalizations {
String get channelPath_otherObservedPaths => 'Autres chemins observés';
@override
String get channelPath_repeaterHops => 'Sauts du répétiteur';
String get channelPath_repeaterHops => 'Sauts du répéteur';
@override
String get channelPath_noHopDetails =>
@@ -2467,7 +2466,7 @@ class AppLocalizationsFr extends AppLocalizations {
@override
String get channelPath_noRepeaterLocations =>
'Aucune position de répétiteur disponible pour ce chemin.';
'Aucune position de répéteur disponible pour ce chemin.';
@override
String channelPath_primaryPath(int index) {
@@ -2713,7 +2712,7 @@ class AppLocalizationsFr extends AppLocalizations {
@override
String get pathTrace_someHopsNoLocation =>
'Une ou plusieurs des houblons manquent d\'une localisation !';
'Un ou plusieurs des sauts manquent d\'une localisation !';
@override
String get contacts_pathTrace => 'Traçage de chemin';
@@ -2756,10 +2755,10 @@ class AppLocalizationsFr extends AppLocalizations {
'Échec de l\'importation du contact.';
@override
String get contacts_zeroHopAdvert => 'Annonce Zero Hop';
String get contacts_zeroHopAdvert => 'Annonce Zero saut';
@override
String get contacts_floodAdvert => 'Annonce de crue';
String get contacts_floodAdvert => 'Annonce à tout le réseau';
@override
String get contacts_copyAdvertToClipboard =>
+6 -1
View File
@@ -1,5 +1,6 @@
import 'package:flutter/material.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:meshcore_open/services/sparse_location_logger.dart';
import 'l10n/app_localizations.dart';
import 'package:provider/provider.dart';
@@ -33,7 +34,7 @@ void main() async {
final appDebugLogService = AppDebugLogService();
final backgroundService = BackgroundService();
final mapTileCacheService = MapTileCacheService();
final sparseLocationLogger = SparseLocationLogger();
// Load settings
await appSettingsService.loadSettings();
@@ -56,6 +57,7 @@ void main() async {
bleDebugLogService: bleDebugLogService,
appDebugLogService: appDebugLogService,
backgroundService: backgroundService,
sparseLocationLogger: sparseLocationLogger,
);
await connector.loadContactCache();
@@ -76,6 +78,7 @@ void main() async {
bleDebugLogService: bleDebugLogService,
appDebugLogService: appDebugLogService,
mapTileCacheService: mapTileCacheService,
sparseLocationLogger: sparseLocationLogger,
),
);
}
@@ -89,6 +92,7 @@ class MeshCoreApp extends StatelessWidget {
final BleDebugLogService bleDebugLogService;
final AppDebugLogService appDebugLogService;
final MapTileCacheService mapTileCacheService;
final SparseLocationLogger sparseLocationLogger;
const MeshCoreApp({
super.key,
@@ -100,6 +104,7 @@ class MeshCoreApp extends StatelessWidget {
required this.bleDebugLogService,
required this.appDebugLogService,
required this.mapTileCacheService,
required this.sparseLocationLogger,
});
@override
+17 -2
View File
@@ -384,6 +384,7 @@ class AppSettingsScreen extends StatelessWidget {
);
}
// Fixed rendering issues
Widget _buildBatteryCard(
BuildContext context,
AppSettingsService settingsService,
@@ -399,6 +400,7 @@ class AppSettingsScreen extends StatelessWidget {
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const SizedBox(height: 4),
Padding(
padding: const EdgeInsets.fromLTRB(16, 16, 16, 8),
child: Text(
@@ -406,6 +408,8 @@ class AppSettingsScreen extends StatelessWidget {
style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
),
),
// Main tile (icon + text only)
ListTile(
leading: const Icon(Icons.battery_full),
title: Text(context.l10n.appSettings_batteryChemistry),
@@ -416,8 +420,19 @@ class AppSettingsScreen extends StatelessWidget {
)
: context.l10n.appSettings_batteryChemistryConnectFirst,
),
trailing: DropdownButton<String>(
value: selection,
contentPadding: const EdgeInsets.symmetric(horizontal: 16),
),
// Dropdown (separate full-width row)
Padding(
padding: const EdgeInsets.fromLTRB(16, 0, 16, 16),
child: DropdownButtonFormField<String>(
initialValue: selection,
isExpanded: true,
decoration: const InputDecoration(
border: UnderlineInputBorder(),
isDense: true,
),
onChanged: isConnected
? (value) {
if (value != null) {
+106 -52
View File
@@ -51,6 +51,7 @@ class _MapScreenState extends State<MapScreen> {
bool _isSelectingPoi = false;
bool _hasInitializedMap = false;
bool _removedMarkersLoaded = false;
bool _legendExpanded = false;
@override
void initState() {
@@ -274,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),
),
@@ -356,8 +368,8 @@ class _MapScreenState extends State<MapScreen> {
connector.selfLatitude!,
connector.selfLongitude!,
),
width: 35,
height: 35,
width: 40,
height: 40,
child: Container(
padding: const EdgeInsets.all(4),
decoration: BoxDecoration(
@@ -503,60 +515,102 @@ class _MapScreenState extends State<MapScreen> {
top: 16,
right: 16,
child: Card(
child: Padding(
padding: const EdgeInsets.all(12.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
Text(
context.l10n.map_nodesCount(nodeCount),
style: const TextStyle(
fontWeight: FontWeight.bold,
fontSize: 14,
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
InkWell(
borderRadius: BorderRadius.circular(12),
onTap: () {
setState(() {
_legendExpanded = !_legendExpanded;
});
},
child: Padding(
padding: const EdgeInsets.fromLTRB(12, 10, 12, 10),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
context.l10n.map_nodesCount(nodeCount),
style: const TextStyle(
fontWeight: FontWeight.bold,
fontSize: 14,
),
),
Text(
context.l10n.map_pinsCount(markerCount),
style: const TextStyle(
fontWeight: FontWeight.w600,
fontSize: 12,
),
),
],
),
const SizedBox(width: 8),
AnimatedRotation(
turns: _legendExpanded ? 0.5 : 0,
duration: const Duration(milliseconds: 200),
child: const Icon(Icons.expand_more, size: 20),
),
],
),
),
Text(
context.l10n.map_pinsCount(markerCount),
style: const TextStyle(
fontWeight: FontWeight.bold,
fontSize: 12,
),
AnimatedCrossFade(
firstChild: const SizedBox.shrink(),
secondChild: Padding(
padding: const EdgeInsets.fromLTRB(12, 0, 12, 12),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const SizedBox(height: 6),
_buildLegendItem(
Icons.person,
context.l10n.map_chat,
Colors.blue,
),
_buildLegendItem(
Icons.router,
context.l10n.map_repeater,
Colors.green,
),
_buildLegendItem(
Icons.meeting_room,
context.l10n.map_room,
Colors.purple,
),
_buildLegendItem(
Icons.sensors,
context.l10n.map_sensor,
Colors.orange,
),
_buildLegendItem(
Icons.flag,
context.l10n.map_pinDm,
Colors.blue,
),
_buildLegendItem(
Icons.flag,
context.l10n.map_pinPrivate,
Colors.purple,
),
_buildLegendItem(
Icons.flag,
context.l10n.map_pinPublic,
Colors.orange,
),
],
),
),
const SizedBox(height: 8),
_buildLegendItem(
Icons.person,
context.l10n.map_chat,
Colors.blue,
),
_buildLegendItem(
Icons.router,
context.l10n.map_repeater,
Colors.green,
),
_buildLegendItem(
Icons.meeting_room,
context.l10n.map_room,
Colors.purple,
),
_buildLegendItem(
Icons.sensors,
context.l10n.map_sensor,
Colors.orange,
),
_buildLegendItem(Icons.flag, context.l10n.map_pinDm, Colors.blue),
_buildLegendItem(
Icons.flag,
context.l10n.map_pinPrivate,
Colors.purple,
),
_buildLegendItem(
Icons.flag,
context.l10n.map_pinPublic,
Colors.orange,
),
],
),
crossFadeState: _legendExpanded
? CrossFadeState.showSecond
: CrossFadeState.showFirst,
duration: const Duration(milliseconds: 200),
),
],
),
),
);
+113 -43
View File
@@ -21,6 +21,7 @@ class SettingsScreen extends StatefulWidget {
class _SettingsScreenState extends State<SettingsScreen> {
bool _showBatteryVoltage = false;
bool _deviceInfoExpanded = false;
String _appVersion = '';
@override
@@ -74,43 +75,84 @@ class _SettingsScreenState extends State<SettingsScreen> {
MeshCoreConnector connector,
) {
final l10n = context.l10n;
return Card(
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
l10n.settings_deviceInfo,
style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
),
const SizedBox(height: 16),
_buildInfoRow(l10n.settings_infoName, connector.deviceDisplayName),
_buildInfoRow(l10n.settings_infoId, connector.deviceIdLabel),
_buildInfoRow(
l10n.settings_infoStatus,
connector.isConnected
? l10n.common_connected
: l10n.common_disconnected,
),
_buildBatteryInfoRow(context, connector),
if (connector.selfName != null)
_buildInfoRow(l10n.settings_nodeName, connector.selfName!),
if (connector.selfPublicKey != null)
_buildInfoRow(
l10n.settings_infoPublicKey,
'${pubKeyToHex(connector.selfPublicKey!).substring(0, 16)}...',
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
InkWell(
borderRadius: BorderRadius.circular(12),
onTap: () {
setState(() {
_deviceInfoExpanded = !_deviceInfoExpanded;
});
},
child: Padding(
padding: const EdgeInsets.fromLTRB(16, 16, 16, 16),
child: Row(
children: [
Expanded(
child: Text(
l10n.settings_deviceInfo,
style: const TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
),
),
),
AnimatedRotation(
turns: _deviceInfoExpanded ? 0.5 : 0,
duration: const Duration(milliseconds: 200),
child: const Icon(Icons.expand_more),
),
],
),
_buildInfoRow(
l10n.settings_infoContactsCount,
'${connector.contacts.length}',
),
_buildInfoRow(
l10n.settings_infoChannelCount,
'${connector.channels.length}',
),
AnimatedCrossFade(
firstChild: const SizedBox.shrink(),
secondChild: Padding(
padding: const EdgeInsets.fromLTRB(16, 0, 16, 16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_buildInfoRow(
l10n.settings_infoName,
connector.deviceDisplayName,
),
_buildInfoRow(l10n.settings_infoId, connector.deviceIdLabel),
_buildInfoRow(
l10n.settings_infoStatus,
connector.isConnected
? l10n.common_connected
: l10n.common_disconnected,
),
_buildBatteryInfoRow(context, connector),
if (connector.selfName != null)
_buildInfoRow(l10n.settings_nodeName, connector.selfName!),
if (connector.selfPublicKey != null)
_buildInfoRow(
l10n.settings_infoPublicKey,
'${pubKeyToHex(connector.selfPublicKey!).substring(0, 16)}...',
),
_buildInfoRow(
l10n.settings_infoContactsCount,
'${connector.contacts.length}',
),
_buildInfoRow(
l10n.settings_infoChannelCount,
'${connector.channels.length}',
),
],
),
),
],
),
crossFadeState: _deviceInfoExpanded
? CrossFadeState.showSecond
: CrossFadeState.showFirst,
duration: const Duration(milliseconds: 200),
),
],
),
);
}
@@ -355,22 +397,33 @@ class _SettingsScreenState extends State<SettingsScreen> {
Color? valueColor,
VoidCallback? onTap,
}) {
final theme = Theme.of(context);
final row = Padding(
padding: const EdgeInsets.symmetric(vertical: 4),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
padding: const EdgeInsets.symmetric(vertical: 10),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
if (leading != null) ...[leading, const SizedBox(width: 8)],
Text(label, style: TextStyle(color: Colors.grey[600])),
Expanded(
child: Text(
label,
style: theme.textTheme.bodySmall?.copyWith(
color: theme.colorScheme.onSurfaceVariant,
fontWeight: FontWeight.w500,
),
),
),
],
),
Flexible(
child: Text(
value,
style: TextStyle(fontWeight: FontWeight.w500, color: valueColor),
overflow: TextOverflow.ellipsis,
const SizedBox(height: 4),
Text(
value,
style: theme.textTheme.bodyLarge?.copyWith(
color: valueColor,
fontWeight: FontWeight.w500,
),
),
],
@@ -379,11 +432,12 @@ class _SettingsScreenState extends State<SettingsScreen> {
if (onTap != null) {
return InkWell(
borderRadius: BorderRadius.circular(6),
onTap: onTap,
borderRadius: BorderRadius.circular(4),
child: row,
);
}
return row;
}
@@ -436,6 +490,8 @@ class _SettingsScreenState extends State<SettingsScreen> {
final latController = TextEditingController();
final lonController = TextEditingController();
final intervalController = TextEditingController();
bool isLogging = connector.sparseLocationLogger?.isLogging() ?? false;
latController.text = connector.selfLatitude?.toStringAsFixed(6) ?? '';
lonController.text = connector.selfLongitude?.toStringAsFixed(6) ?? '';
@@ -480,6 +536,20 @@ class _SettingsScreenState extends State<SettingsScreen> {
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) ...[
const SizedBox(height: 16),
TextField(
-1
View File
@@ -28,7 +28,6 @@ class BackgroundService {
foregroundTaskOptions: const ForegroundTaskOptions(
interval: 5000,
autoRunOnBoot: false,
allowWakeLock: true,
allowWifiLock: false,
),
);
+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;
}
@@ -7,6 +7,7 @@ import Foundation
import flutter_blue_plus_darwin
import flutter_local_notifications
import geolocator_apple
import mobile_scanner
import package_info_plus
import share_plus
@@ -18,6 +19,7 @@ import wakelock_plus
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
FlutterBluePlusPlugin.register(with: registry.registrar(forPlugin: "FlutterBluePlusPlugin"))
FlutterLocalNotificationsPlugin.register(with: registry.registrar(forPlugin: "FlutterLocalNotificationsPlugin"))
GeolocatorPlugin.register(with: registry.registrar(forPlugin: "GeolocatorPlugin"))
MobileScannerPlugin.register(with: registry.registrar(forPlugin: "MobileScannerPlugin"))
FPPPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FPPPackageInfoPlusPlugin"))
SharePlusMacosPlugin.register(with: registry.registrar(forPlugin: "SharePlusMacosPlugin"))
+120
View File
@@ -341,6 +341,70 @@ packages:
description: flutter
source: sdk
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:
dependency: transitive
description:
@@ -357,6 +421,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.3.0"
gsettings:
dependency: transitive
description:
name: gsettings
sha256: "1b0ce661f5436d2db1e51f3c4295a49849f03d304003a7ba177d01e3a858249c"
url: "https://pub.dev"
source: hosted
version: "0.2.8"
hooks:
dependency: transitive
description:
@@ -637,6 +709,54 @@ packages:
url: "https://pub.dev"
source: hosted
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:
dependency: transitive
description:
+3 -1
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
# 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.
version: 5.0.0+6
version: 6.0.0+1
environment:
sdk: ^3.9.2
@@ -60,6 +60,8 @@ dependencies:
gpx: ^2.3.0
path_provider: ^2.1.5
share_plus: ^12.0.1
geolocator: ^14.0.2
permission_handler: ^12.0.1
dev_dependencies:
flutter_test:
+1 -127
View File
@@ -1,127 +1 @@
{
"bg": [
"notification_activityTitle",
"notification_messagesCount",
"notification_channelMessagesCount",
"notification_newNodesCount",
"notification_newTypeDiscovered",
"notification_receivedNewMessage"
],
"de": [
"notification_activityTitle",
"notification_messagesCount",
"notification_channelMessagesCount",
"notification_newNodesCount",
"notification_newTypeDiscovered",
"notification_receivedNewMessage"
],
"es": [
"notification_activityTitle",
"notification_messagesCount",
"notification_channelMessagesCount",
"notification_newNodesCount",
"notification_newTypeDiscovered",
"notification_receivedNewMessage"
],
"fr": [
"notification_activityTitle",
"notification_messagesCount",
"notification_channelMessagesCount",
"notification_newNodesCount",
"notification_newTypeDiscovered",
"notification_receivedNewMessage"
],
"it": [
"notification_activityTitle",
"notification_messagesCount",
"notification_channelMessagesCount",
"notification_newNodesCount",
"notification_newTypeDiscovered",
"notification_receivedNewMessage"
],
"nl": [
"notification_activityTitle",
"notification_messagesCount",
"notification_channelMessagesCount",
"notification_newNodesCount",
"notification_newTypeDiscovered",
"notification_receivedNewMessage"
],
"pl": [
"notification_activityTitle",
"notification_messagesCount",
"notification_channelMessagesCount",
"notification_newNodesCount",
"notification_newTypeDiscovered",
"notification_receivedNewMessage"
],
"pt": [
"notification_activityTitle",
"notification_messagesCount",
"notification_channelMessagesCount",
"notification_newNodesCount",
"notification_newTypeDiscovered",
"notification_receivedNewMessage"
],
"ru": [
"notification_activityTitle",
"notification_messagesCount",
"notification_channelMessagesCount",
"notification_newNodesCount",
"notification_newTypeDiscovered",
"notification_receivedNewMessage"
],
"sk": [
"notification_activityTitle",
"notification_messagesCount",
"notification_channelMessagesCount",
"notification_newNodesCount",
"notification_newTypeDiscovered",
"notification_receivedNewMessage"
],
"sl": [
"notification_activityTitle",
"notification_messagesCount",
"notification_channelMessagesCount",
"notification_newNodesCount",
"notification_newTypeDiscovered",
"notification_receivedNewMessage"
],
"sv": [
"notification_activityTitle",
"notification_messagesCount",
"notification_channelMessagesCount",
"notification_newNodesCount",
"notification_newTypeDiscovered",
"notification_receivedNewMessage"
],
"uk": [
"notification_activityTitle",
"notification_messagesCount",
"notification_channelMessagesCount",
"notification_newNodesCount",
"notification_newTypeDiscovered",
"notification_receivedNewMessage"
],
"zh": [
"notification_activityTitle",
"notification_messagesCount",
"notification_channelMessagesCount",
"notification_newNodesCount",
"notification_newTypeDiscovered",
"notification_receivedNewMessage"
]
}
{}
@@ -7,12 +7,18 @@
#include "generated_plugin_registrant.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 <url_launcher_windows/url_launcher_windows.h>
void RegisterPlugins(flutter::PluginRegistry* registry) {
FlutterBluePlusPluginRegisterWithRegistrar(
registry->GetRegistrarForPlugin("FlutterBluePlusPlugin"));
GeolocatorWindowsRegisterWithRegistrar(
registry->GetRegistrarForPlugin("GeolocatorWindows"));
PermissionHandlerWindowsPluginRegisterWithRegistrar(
registry->GetRegistrarForPlugin("PermissionHandlerWindowsPlugin"));
SharePlusWindowsPluginCApiRegisterWithRegistrar(
registry->GetRegistrarForPlugin("SharePlusWindowsPluginCApi"));
UrlLauncherWindowsRegisterWithRegistrar(
+2
View File
@@ -4,6 +4,8 @@
list(APPEND FLUTTER_PLUGIN_LIST
flutter_blue_plus_winrt
geolocator_windows
permission_handler_windows
share_plus
url_launcher_windows
)