mirror of
https://github.com/dz0ny/meshcore-sar.git
synced 2026-08-11 16:30:28 +00:00
Compare commits
164 Commits
v2026.0308
...
feat/updat
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c299349b6a | ||
|
|
ab05f13de9 | ||
|
|
0e61dc5bf1 | ||
|
|
d45924d7b4 | ||
|
|
a24e699e22 | ||
|
|
7486837ad0 | ||
|
|
059125c714 | ||
|
|
9279ff2158 | ||
|
|
dc297b0b9f | ||
|
|
2d24481aba | ||
|
|
20a1df94eb | ||
|
|
5cc81bef65 | ||
|
|
6145462555 | ||
|
|
721fb7e7a2 | ||
|
|
5a58e45e13 | ||
|
|
de4258c0a4 | ||
|
|
d8cb35c595 | ||
|
|
8da69705ee | ||
|
|
e474168ba8 | ||
|
|
03dba8b3ce | ||
|
|
bc9e37bd22 | ||
|
|
a32d5d2c85 | ||
|
|
e683ab933f | ||
|
|
f286b0afd1 | ||
|
|
68ec72534d | ||
|
|
6af8b62d9b | ||
|
|
01fbb7de6c | ||
|
|
9303d24b69 | ||
|
|
140088fe2f | ||
|
|
67d8e1c906 | ||
|
|
29b75a3155 | ||
|
|
87cb890877 | ||
|
|
095458ece4 | ||
|
|
5659242672 | ||
|
|
3da4ba8fb2 | ||
|
|
89dc1f2862 | ||
|
|
251932bb47 | ||
|
|
b48139964a | ||
|
|
d29b85feb8 | ||
|
|
c01ef7a059 | ||
|
|
fa2b586ab7 | ||
|
|
bbd5d108cb | ||
|
|
e17f24a6bc | ||
|
|
372224a62c | ||
|
|
e4c8709ee2 | ||
|
|
3c469b62d0 | ||
|
|
1e97ba9831 | ||
|
|
6187067017 | ||
|
|
cd165d2372 | ||
|
|
59f6df4260 | ||
|
|
464fc5e715 | ||
|
|
a76ee4f8f6 | ||
|
|
c884f81ca5 | ||
|
|
993563f059 | ||
|
|
cfd233d38e | ||
|
|
9b14ff4e1a | ||
|
|
84cd8eec00 | ||
|
|
434d3df53e | ||
|
|
1fe260b95e | ||
|
|
d3d53995a6 | ||
|
|
efbddc8831 | ||
|
|
0e3acfa4ca | ||
|
|
450b8382e8 | ||
|
|
420690d683 | ||
|
|
fedd7b9a2b | ||
|
|
c82e0c3452 | ||
|
|
a55cfffa59 | ||
|
|
2bb5b353bd | ||
|
|
991421d097 | ||
|
|
1dcd85f178 | ||
|
|
5d2f61749e | ||
|
|
69377d90a4 | ||
|
|
3e559a4f4e | ||
|
|
a124e3d9f2 | ||
|
|
3bf4971279 | ||
|
|
e355f28ca9 | ||
|
|
7a9be3eb76 | ||
|
|
650f3c66aa | ||
|
|
df993cd176 | ||
|
|
feb3145854 | ||
|
|
da40acf950 | ||
|
|
66eb32bf44 | ||
|
|
c2e0fa952e | ||
|
|
4557fb4b9e | ||
|
|
c05de2cf0a | ||
|
|
c5145c2fca | ||
|
|
e502bc23b7 | ||
|
|
62bf33c9d1 | ||
|
|
7be1460ef6 | ||
|
|
30c273c656 | ||
|
|
5712feba53 | ||
|
|
6de12225fc | ||
|
|
6e9e9e397d | ||
|
|
a3147e9d7c | ||
|
|
5e1912d9f2 | ||
|
|
b42328bd82 | ||
|
|
1b801a10ef | ||
|
|
06632ee32d | ||
|
|
e7a481f97b | ||
|
|
9f31f50e61 | ||
|
|
d4237b7569 | ||
|
|
b35c11c804 | ||
|
|
be2cf53374 | ||
|
|
13a729492f | ||
|
|
9d8594fe05 | ||
|
|
249268dd00 | ||
|
|
63aa9619e6 | ||
|
|
a7ad4bb8ec | ||
|
|
636e114428 | ||
|
|
d5684c51c7 | ||
|
|
ced39b6992 | ||
|
|
083b715039 | ||
|
|
36584be0ab | ||
|
|
dc19296804 | ||
|
|
40072175e3 | ||
|
|
11f3bf9f97 | ||
|
|
24e715d01e | ||
|
|
6e17de3866 | ||
|
|
90877a1891 | ||
|
|
77e43843bf | ||
|
|
1e70f52069 | ||
|
|
5e404944e9 | ||
|
|
53daf2c9a7 | ||
|
|
c36d63a0ad | ||
|
|
69ec2dfdb5 | ||
|
|
d2e6b692f3 | ||
|
|
baa1aa6edd | ||
|
|
38cb3dc031 | ||
|
|
abb73a4c65 | ||
|
|
2d1e6c0ca4 | ||
|
|
b38a8c55d6 | ||
|
|
ca872f2049 | ||
|
|
1b6ff59af3 | ||
|
|
e31bfac2ac | ||
|
|
99bb446ee9 | ||
|
|
088f3d37ef | ||
|
|
7f200e5334 | ||
|
|
ae16417886 | ||
|
|
467371946d | ||
|
|
2d23328ad0 | ||
|
|
0dfb84bf4e | ||
|
|
193a193e8a | ||
|
|
c1d1ec7d0c | ||
|
|
b415b63b4b | ||
|
|
818b9dbfb2 | ||
|
|
8085b89c07 | ||
|
|
86771a3c55 | ||
|
|
4b668ac2e9 | ||
|
|
9deb563c05 | ||
|
|
15ac3e2798 | ||
|
|
97addf7ba6 | ||
|
|
025bd737e7 | ||
|
|
07b675dec6 | ||
|
|
d675ce18dd | ||
|
|
a8e31bb2e8 | ||
|
|
3cd96ba961 | ||
|
|
a98fc6314e | ||
|
|
ae1ea71f58 | ||
|
|
4dff79cd7b | ||
|
|
cbcb329c70 | ||
|
|
7abf96abf2 | ||
|
|
13bf74178c | ||
|
|
66a00c9569 | ||
|
|
b60fa008eb |
23
.github/workflows/build-artifacts.yml
vendored
23
.github/workflows/build-artifacts.yml
vendored
@@ -40,6 +40,9 @@ jobs:
|
||||
flutter-version: ${{ env.FLUTTER_VERSION }}
|
||||
channel: stable
|
||||
cache: true
|
||||
pub-cache: true
|
||||
cache-key: flutter-${{ runner.os }}-stable-${{ env.FLUTTER_VERSION }}-${{ runner.arch }}
|
||||
pub-cache-key: flutter-pub-${{ runner.os }}-stable-${{ env.FLUTTER_VERSION }}-${{ runner.arch }}-${{ hashFiles('pubspec.lock') }}
|
||||
|
||||
- name: Cache Android NDK
|
||||
uses: actions/cache@v4
|
||||
@@ -95,6 +98,9 @@ jobs:
|
||||
flutter-version: ${{ env.FLUTTER_VERSION }}
|
||||
channel: stable
|
||||
cache: true
|
||||
pub-cache: true
|
||||
cache-key: flutter-${{ runner.os }}-stable-${{ env.FLUTTER_VERSION }}-${{ runner.arch }}
|
||||
pub-cache-key: flutter-pub-${{ runner.os }}-stable-${{ env.FLUTTER_VERSION }}-${{ runner.arch }}-${{ hashFiles('pubspec.lock') }}
|
||||
|
||||
- name: Enable Linux desktop
|
||||
run: flutter config --enable-linux-desktop
|
||||
@@ -132,6 +138,9 @@ jobs:
|
||||
flutter-version: ${{ env.FLUTTER_VERSION }}
|
||||
channel: stable
|
||||
cache: true
|
||||
pub-cache: true
|
||||
cache-key: flutter-${{ runner.os }}-stable-${{ env.FLUTTER_VERSION }}-${{ runner.arch }}
|
||||
pub-cache-key: flutter-pub-${{ runner.os }}-stable-${{ env.FLUTTER_VERSION }}-${{ runner.arch }}-${{ hashFiles('pubspec.lock') }}
|
||||
|
||||
- name: Enable Windows desktop
|
||||
shell: pwsh
|
||||
@@ -174,6 +183,9 @@ jobs:
|
||||
flutter-version: ${{ env.FLUTTER_VERSION }}
|
||||
channel: stable
|
||||
cache: true
|
||||
pub-cache: true
|
||||
cache-key: flutter-${{ runner.os }}-stable-${{ env.FLUTTER_VERSION }}-${{ runner.arch }}
|
||||
pub-cache-key: flutter-pub-${{ runner.os }}-stable-${{ env.FLUTTER_VERSION }}-${{ runner.arch }}-${{ hashFiles('pubspec.lock') }}
|
||||
|
||||
- name: Enable macOS desktop
|
||||
run: flutter config --enable-macos-desktop
|
||||
@@ -181,6 +193,9 @@ jobs:
|
||||
- name: Install dependencies
|
||||
run: flutter pub get
|
||||
|
||||
- name: Install macOS native build tools
|
||||
run: brew install automake libtool
|
||||
|
||||
- name: Build macOS release
|
||||
run: flutter build macos --release
|
||||
|
||||
@@ -216,6 +231,9 @@ jobs:
|
||||
flutter-version: ${{ env.FLUTTER_VERSION }}
|
||||
channel: stable
|
||||
cache: true
|
||||
pub-cache: true
|
||||
cache-key: flutter-${{ runner.os }}-stable-${{ env.FLUTTER_VERSION }}-${{ runner.arch }}
|
||||
pub-cache-key: flutter-pub-${{ runner.os }}-stable-${{ env.FLUTTER_VERSION }}-${{ runner.arch }}-${{ hashFiles('pubspec.lock') }}
|
||||
|
||||
- name: Install dependencies
|
||||
run: flutter pub get
|
||||
@@ -272,6 +290,9 @@ jobs:
|
||||
flutter-version: ${{ env.FLUTTER_VERSION }}
|
||||
channel: stable
|
||||
cache: true
|
||||
pub-cache: true
|
||||
cache-key: flutter-${{ runner.os }}-stable-${{ env.FLUTTER_VERSION }}-${{ runner.arch }}
|
||||
pub-cache-key: flutter-pub-${{ runner.os }}-stable-${{ env.FLUTTER_VERSION }}-${{ runner.arch }}-${{ hashFiles('pubspec.lock') }}
|
||||
|
||||
- name: Enable web
|
||||
run: flutter config --enable-web
|
||||
@@ -280,7 +301,7 @@ jobs:
|
||||
run: flutter pub get
|
||||
|
||||
- name: Build web release
|
||||
run: flutter build web --release --base-href /meshcore_sar_app/
|
||||
run: flutter build web --release --base-href /meshcore-sar/
|
||||
|
||||
- name: Upload pages artifact
|
||||
uses: actions/upload-pages-artifact@v3
|
||||
|
||||
3
.github/workflows/build-multiplatform.yml
vendored
3
.github/workflows/build-multiplatform.yml
vendored
@@ -27,6 +27,9 @@ jobs:
|
||||
flutter-version: ${{ env.FLUTTER_VERSION }}
|
||||
channel: 'stable'
|
||||
cache: true
|
||||
pub-cache: true
|
||||
cache-key: flutter-${{ runner.os }}-stable-${{ env.FLUTTER_VERSION }}-${{ runner.arch }}
|
||||
pub-cache-key: flutter-pub-${{ runner.os }}-stable-${{ env.FLUTTER_VERSION }}-${{ runner.arch }}-${{ hashFiles('pubspec.lock') }}
|
||||
|
||||
- name: Install dependencies
|
||||
run: flutter pub get
|
||||
|
||||
4
.gitignore
vendored
4
.gitignore
vendored
@@ -28,6 +28,7 @@ migrate_working_dir/
|
||||
**/ios/Flutter/.last_build_id
|
||||
.dart_tool/
|
||||
.flutter-plugins-dependencies
|
||||
pubspec_overrides.yaml
|
||||
.pub-cache/
|
||||
.pub/
|
||||
/build/
|
||||
@@ -80,10 +81,11 @@ create_feature_graphic.py
|
||||
# Tool caches and local state
|
||||
.cachebro/
|
||||
.osgrep/
|
||||
third_party/lpcnet_flutter/
|
||||
|
||||
# Claude Code local settings (permissions, personal config)
|
||||
.claude/settings.local.json
|
||||
|
||||
# Dart code coverage
|
||||
coverage/
|
||||
lcov.info
|
||||
lcov.info
|
||||
|
||||
25
CHANGELOG.md
Normal file
25
CHANGELOG.md
Normal file
@@ -0,0 +1,25 @@
|
||||
# Changelog
|
||||
|
||||
## 2026-03-15 to 2026-03-22
|
||||
|
||||
- Device connectivity and routing: added Android USB serial companion support, serial compass support, GPS module controls, per-contact path size selection, and favourites support via firmware flags.
|
||||
- Location and map intelligence: added RSSI-based repeater trilateration and contact location estimates, isolated MET history, matched the official advert import/discovery flow, and refreshed map presentation by removing contact trails.
|
||||
- Telemetry and device info: surfaced self telemetry and sensor defaults, opened device info from signal and battery taps, improved signal chip behavior, fixed duplicate telemetry delivery, and restored telemetry refresh behavior.
|
||||
- Contacts and messaging: added meshcore:// contact sharing and repeater neighbour views, supported MeshCore message links, fixed DM retry behavior, corrected room poster naming, and compacted the recipient selector.
|
||||
- UI polish and platform upkeep: improved device settings layout, tightened sensor and contact presentation, expanded localization coverage, and refreshed platform dependencies and build numbers.
|
||||
|
||||
## Key PRs
|
||||
|
||||
- [#26](https://github.com/dz0ny/meshcore-sar/pull/26) Merge pull request #26 from `dz0ny/feat/sensor-telemetry-preview`
|
||||
|
||||
## 2026-03-08 to 2026-03-14
|
||||
|
||||
- Discovery and contacts: finished the discovery flow, removed artificial UI delays, added profile storage and sync, and allowed contact name overrides.
|
||||
- Messaging and channels: fixed channel sync, deduplication, overwrite and deletion issues, cleared message input after send, and added delayed-message warning tracking.
|
||||
- Voice and transport: improved voice playback quality, improved scan and advert parsing, and kept route path data visible in logs.
|
||||
- Maps and field operations: enabled iOS background GPS updates, added GPX trail import/export, introduced a fullscreen repeater map, and added live mesh traffic views.
|
||||
- Localization and UI polish: added multilingual localization coverage, updated locale strings, fixed onboarding layout issues, and refined channel and contacts presentation.
|
||||
|
||||
## Key PRs
|
||||
|
||||
- [#16](https://github.com/dz0ny/meshcore-sar/pull/16) Merge pull request #16 from `MGJ520/main`
|
||||
@@ -16,7 +16,7 @@
|
||||
|
||||
MeshCore SAR helps teams coordinate in low-connectivity or no-connectivity environments with messaging, voice, images, maps, and live location context in one app.
|
||||
It uses the MeshCore protocol over LoRa for long-range, infrastructure-free communication.
|
||||
`iOS TestFlight:` https://testflight.apple.com/join/HhzerdHp
|
||||
`iOS TestFlight:` https://testflight.apple.com/join/nCZYMPPz
|
||||
|
||||
## Highlights
|
||||
|
||||
|
||||
@@ -84,6 +84,8 @@ PODS:
|
||||
- Flutter
|
||||
- vibration (3.0.0):
|
||||
- Flutter
|
||||
- wakelock_plus (0.0.1):
|
||||
- Flutter
|
||||
|
||||
DEPENDENCIES:
|
||||
- audioplayers_darwin (from `.symlinks/plugins/audioplayers_darwin/darwin`)
|
||||
@@ -108,6 +110,7 @@ DEPENDENCIES:
|
||||
- sqflite_darwin (from `.symlinks/plugins/sqflite_darwin/darwin`)
|
||||
- url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`)
|
||||
- vibration (from `.symlinks/plugins/vibration/ios`)
|
||||
- wakelock_plus (from `.symlinks/plugins/wakelock_plus/ios`)
|
||||
|
||||
SPEC REPOS:
|
||||
trunk:
|
||||
@@ -161,6 +164,8 @@ EXTERNAL SOURCES:
|
||||
:path: ".symlinks/plugins/url_launcher_ios/ios"
|
||||
vibration:
|
||||
:path: ".symlinks/plugins/vibration/ios"
|
||||
wakelock_plus:
|
||||
:path: ".symlinks/plugins/wakelock_plus/ios"
|
||||
|
||||
SPEC CHECKSUMS:
|
||||
audioplayers_darwin: 835ced6edd4c9fc8ebb0a7cc9e294a91d99917d5
|
||||
@@ -189,6 +194,7 @@ SPEC CHECKSUMS:
|
||||
SwiftyGif: 706c60cf65fa2bc5ee0313beece843c8eb8194d4
|
||||
url_launcher_ios: 7a95fa5b60cc718a708b8f2966718e93db0cef1b
|
||||
vibration: ca8104a8875b9c493e15b21b04e456befd0ff6eb
|
||||
wakelock_plus: e29112ab3ef0b318e58cfa5c32326458be66b556
|
||||
|
||||
PODFILE CHECKSUM: 2d56c9747241a29800bf539d978b210bda037878
|
||||
|
||||
|
||||
@@ -489,7 +489,7 @@
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CURRENT_PROJECT_VERSION = 106;
|
||||
CURRENT_PROJECT_VERSION = 124;
|
||||
DEVELOPMENT_TEAM = JND55328G8;
|
||||
ENABLE_BITCODE = NO;
|
||||
INFOPLIST_FILE = Runner/Info.plist;
|
||||
@@ -511,7 +511,7 @@
|
||||
buildSettings = {
|
||||
BUNDLE_LOADER = "$(TEST_HOST)";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 106;
|
||||
CURRENT_PROJECT_VERSION = 124;
|
||||
DEVELOPMENT_TEAM = JND55328G8;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
MARKETING_VERSION = 1.0;
|
||||
@@ -530,7 +530,7 @@
|
||||
buildSettings = {
|
||||
BUNDLE_LOADER = "$(TEST_HOST)";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 106;
|
||||
CURRENT_PROJECT_VERSION = 124;
|
||||
DEVELOPMENT_TEAM = JND55328G8;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
MARKETING_VERSION = 1.0;
|
||||
@@ -547,7 +547,7 @@
|
||||
buildSettings = {
|
||||
BUNDLE_LOADER = "$(TEST_HOST)";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 106;
|
||||
CURRENT_PROJECT_VERSION = 124;
|
||||
DEVELOPMENT_TEAM = JND55328G8;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
MARKETING_VERSION = 1.0;
|
||||
@@ -679,7 +679,7 @@
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CURRENT_PROJECT_VERSION = 106;
|
||||
CURRENT_PROJECT_VERSION = 124;
|
||||
DEVELOPMENT_TEAM = JND55328G8;
|
||||
ENABLE_BITCODE = NO;
|
||||
INFOPLIST_FILE = Runner/Info.plist;
|
||||
@@ -702,7 +702,7 @@
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CURRENT_PROJECT_VERSION = 106;
|
||||
CURRENT_PROJECT_VERSION = 124;
|
||||
DEVELOPMENT_TEAM = JND55328G8;
|
||||
ENABLE_BITCODE = NO;
|
||||
INFOPLIST_FILE = Runner/Info.plist;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import UIKit
|
||||
import Flutter
|
||||
import UserNotifications
|
||||
|
||||
@main
|
||||
@objc class AppDelegate: FlutterAppDelegate {
|
||||
@@ -8,6 +9,7 @@ import Flutter
|
||||
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
|
||||
) -> Bool {
|
||||
GeneratedPluginRegistrant.register(with: self)
|
||||
UNUserNotificationCenter.current().delegate = self as UNUserNotificationCenterDelegate
|
||||
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,7 +43,7 @@
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>106</string>
|
||||
<string>124</string>
|
||||
<key>LSRequiresIPhoneOS</key>
|
||||
<true/>
|
||||
<key>ITSAppUsesNonExemptEncryption</key>
|
||||
@@ -102,6 +102,7 @@
|
||||
<key>UIBackgroundModes</key>
|
||||
<array>
|
||||
<string>location</string>
|
||||
<string>bluetooth-central</string>
|
||||
<string>bluetooth-peripheral</string>
|
||||
</array>
|
||||
<key>UILaunchStoryboardName</key>
|
||||
|
||||
@@ -5,24 +5,22 @@
|
||||
|
||||
|
||||
|
||||
<testcase classname="fastlane.lanes" name="0: default_platform" time="0.000243">
|
||||
<testcase classname="fastlane.lanes" name="0: default_platform" time="0.00024">
|
||||
|
||||
</testcase>
|
||||
|
||||
|
||||
<testcase classname="fastlane.lanes" name="1: increment_build_number" time="0.511584">
|
||||
<testcase classname="fastlane.lanes" name="1: increment_build_number" time="1.144858">
|
||||
|
||||
</testcase>
|
||||
|
||||
|
||||
<testcase classname="fastlane.lanes" name="2: build_app" time="95.467725">
|
||||
<testcase classname="fastlane.lanes" name="2: build_app" time="98.142592">
|
||||
|
||||
</testcase>
|
||||
|
||||
|
||||
<testcase classname="fastlane.lanes" name="3: upload_to_app_store" time="240.60994">
|
||||
|
||||
<failure message="/opt/homebrew/Cellar/fastlane/2.231.1/libexec/gems/fastlane-2.231.1/fastlane/lib/fastlane/actions/actions_helper.rb:67:in 'Fastlane::Actions.execute_action' /opt/homebrew/Cellar/fastlane/2.231.1/libexec/gems/fastlane-2.231.1/fastlane/lib/fastlane/runner.rb:255:in 'block in Fastlane::Runner#execute_action' /opt/homebrew/Cellar/fastlane/2.231.1/libexec/gems/fastlane-2.231.1/fastlane/lib/fastlane/runner.rb:229:in 'Dir.chdir' /opt/homebrew/Cellar/fastlane/2.231.1/libexec/gems/fastlane-2.231.1/fastlane/lib/fastlane/runner.rb:229:in 'Fastlane::Runner#execute_action' /opt/homebrew/Cellar/fastlane/2.231.1/libexec/gems/fastlane-2.231.1/fastlane/lib/fastlane/runner.rb:157:in 'Fastlane::Runner#trigger_action_by_name' /opt/homebrew/Cellar/fastlane/2.231.1/libexec/gems/fastlane-2.231.1/fastlane/lib/fastlane/fast_file.rb:159:in 'Fastlane::FastFile#method_missing' Fastfile:23:in 'block (2 levels) in Fastlane::FastFile#parsing_binding' /opt/homebrew/Cellar/fastlane/2.231.1/libexec/gems/fastlane-2.231.1/fastlane/lib/fastlane/lane.rb:41:in 'Fastlane::Lane#call' /opt/homebrew/Cellar/fastlane/2.231.1/libexec/gems/fastlane-2.231.1/fastlane/lib/fastlane/runner.rb:49:in 'block in Fastlane::Runner#execute' /opt/homebrew/Cellar/fastlane/2.231.1/libexec/gems/fastlane-2.231.1/fastlane/lib/fastlane/runner.rb:45:in 'Dir.chdir' /opt/homebrew/Cellar/fastlane/2.231.1/libexec/gems/fastlane-2.231.1/fastlane/lib/fastlane/runner.rb:45:in 'Fastlane::Runner#execute' /opt/homebrew/Cellar/fastlane/2.231.1/libexec/gems/fastlane-2.231.1/fastlane/lib/fastlane/lane_manager.rb:46:in 'Fastlane::LaneManager.cruise_lane' /opt/homebrew/Cellar/fastlane/2.231.1/libexec/gems/fastlane-2.231.1/fastlane/lib/fastlane/command_line_handler.rb:34:in 'Fastlane::CommandLineHandler.handle' /opt/homebrew/Cellar/fastlane/2.231.1/libexec/gems/fastlane-2.231.1/fastlane/lib/fastlane/commands_generator.rb:110:in 'block (2 levels) in Fastlane::CommandsGenerator#run' /opt/homebrew/Cellar/fastlane/2.231.1/libexec/gems/commander-4.6.0/lib/commander/command.rb:187:in 'Commander::Command#call' /opt/homebrew/Cellar/fastlane/2.231.1/libexec/gems/commander-4.6.0/lib/commander/command.rb:157:in 'Commander::Command#run' /opt/homebrew/Cellar/fastlane/2.231.1/libexec/gems/commander-4.6.0/lib/commander/runner.rb:444:in 'Commander::Runner#run_active_command' /opt/homebrew/Cellar/fastlane/2.231.1/libexec/gems/fastlane-2.231.1/fastlane_core/lib/fastlane_core/ui/fastlane_runner.rb:124:in 'Commander::Runner#run!' /opt/homebrew/Cellar/fastlane/2.231.1/libexec/gems/commander-4.6.0/lib/commander/delegates.rb:18:in 'Commander::Delegates#run!' /opt/homebrew/Cellar/fastlane/2.231.1/libexec/gems/fastlane-2.231.1/fastlane/lib/fastlane/commands_generator.rb:363:in 'Fastlane::CommandsGenerator#run' /opt/homebrew/Cellar/fastlane/2.231.1/libexec/gems/fastlane-2.231.1/fastlane/lib/fastlane/commands_generator.rb:43:in 'Fastlane::CommandsGenerator.start' /opt/homebrew/Cellar/fastlane/2.231.1/libexec/gems/fastlane-2.231.1/fastlane/lib/fastlane/cli_tools_distributor.rb:132:in 'Fastlane::CLIToolsDistributor.take_off' /opt/homebrew/Cellar/fastlane/2.231.1/libexec/gems/fastlane-2.231.1/bin/fastlane:23:in '<top (required)>' /opt/homebrew/Cellar/ruby/4.0.1/lib/ruby/4.0.0/rubygems.rb:303:in 'Kernel#load' /opt/homebrew/Cellar/ruby/4.0.1/lib/ruby/4.0.0/rubygems.rb:303:in 'Gem.activate_and_load_bin_path' /opt/homebrew/Cellar/fastlane/2.231.1/libexec/bin/fastlane:25:in '<main>' Error uploading ipa file: [Application Loader Error Output]: [ContentDelivery.Uploader.104B12FA0]
|
||||
<testcase classname="fastlane.lanes" name="3: upload_to_app_store" time="157.12775">
|
||||
|
||||
</testcase>
|
||||
|
||||
|
||||
@@ -1586,5 +1586,304 @@
|
||||
"rawDump": "Rohdaten",
|
||||
"cannotRetryMissingRecipient": "Wiederholung nicht möglich: Empfängerinformationen fehlen",
|
||||
"voiceUnavailable": "Sprachnachricht momentan nicht verfügbar",
|
||||
"requestingVoice": "Sprachnachricht wird angefordert"
|
||||
"requestingVoice": "Sprachnachricht wird angefordert",
|
||||
"device": "Gerät",
|
||||
"change": "Ändern",
|
||||
"wizardOverviewDescription": "Diese App vereint MeshCore-Nachrichten, SAR-Feldupdates, Karten und Gerätetools an einem Ort.",
|
||||
"wizardOverviewFeature1": "Sende Direkt-, Raum- und Kanalnachrichten aus dem Haupttab \"Nachrichten\".",
|
||||
"wizardOverviewFeature2": "Teile SAR-Markierungen, Kartenzeichnungen, Sprachclips und Bilder über das Mesh.",
|
||||
"wizardOverviewFeature3": "Verbinde dich per BLE oder TCP und verwalte dann das Begleitfunkgerät direkt in der App.",
|
||||
"wizardMessagingTitle": "Nachrichten und Feldberichte",
|
||||
"wizardMessagingDescription": "Nachrichten sind hier mehr als nur Klartext. Die App unterstützt bereits mehrere operative Datentypen und Übertragungsabläufe.",
|
||||
"wizardMessagingFeature1": "Sende Direktnachrichten, Raumbeiträge und Kanalverkehr aus einem einzigen Editor.",
|
||||
"wizardMessagingFeature2": "Erstelle SAR-Updates und wiederverwendbare SAR-Vorlagen für häufige Feldberichte.",
|
||||
"wizardMessagingFeature3": "Übertrage Sprachsitzungen und Bilder mit Fortschritts- und Airtime-Anzeigen in der Oberfläche.",
|
||||
"wizardConnectDeviceTitle": "Gerät verbinden",
|
||||
"wizardConnectDeviceDescription": "Verbinde dein MeshCore-Funkgerät, wähle einen Namen und wende ein Funkprofil an, bevor du fortfährst.",
|
||||
"wizardSetupBadge": "Einrichtung",
|
||||
"wizardOverviewBadge": "Überblick",
|
||||
"wizardConnectedToDevice": "Verbunden mit {deviceName}",
|
||||
"wizardNoDeviceConnected": "Noch kein Gerät verbunden",
|
||||
"wizardSkipForNow": "Vorerst überspringen",
|
||||
"wizardDeviceNameLabel": "Gerätename",
|
||||
"wizardDeviceNameHelp": "Dieser Name wird an andere MeshCore-Nutzer ausgesendet.",
|
||||
"wizardConfigRegionLabel": "Konfigurationsregion",
|
||||
"wizardConfigRegionHelp": "Verwendet die vollständige offizielle Liste der MeshCore-Voreinstellungen. Standard ist EU/UK (Narrow).",
|
||||
"wizardPresetNote1": "Stelle sicher, dass die ausgewählte Voreinstellung zu deinen lokalen Funkvorschriften passt.",
|
||||
"wizardPresetNote2": "Die Liste entspricht dem offiziellen Preset-Feed des MeshCore-Konfigurationstools.",
|
||||
"wizardPresetNote3": "EU/UK (Narrow) bleibt standardmäßig für das Onboarding ausgewählt.",
|
||||
"wizardSaving": "Wird gespeichert...",
|
||||
"wizardSaveAndContinue": "Speichern und fortfahren",
|
||||
"wizardEnterDeviceName": "Gib vor dem Fortfahren einen Gerätenamen ein.",
|
||||
"wizardDeviceSetupSaved": "{deviceName} mit {presetName} gespeichert.",
|
||||
"wizardNetworkTitle": "Kontakte, Räume und Repeater",
|
||||
"wizardNetworkDescription": "Der Kontakte-Tab organisiert das Netzwerk, das du entdeckst, und die Routen, die im Laufe der Zeit gelernt werden.",
|
||||
"wizardNetworkFeature1": "Prüfe Teammitglieder, Repeater, Räume, Kanäle und ausstehende Adverts in einer Liste.",
|
||||
"wizardNetworkFeature2": "Nutze Smart Ping, Raum-Login, gelernte Pfade und Pfad-Reset-Tools, wenn die Verbindung unübersichtlich wird.",
|
||||
"wizardNetworkFeature3": "Erstelle Kanäle und verwalte Netzwerkziele, ohne die App zu verlassen.",
|
||||
"wizardMapOpsTitle": "Karte, Spuren und geteilte Geometrie",
|
||||
"wizardMapOpsDescription": "Die Karte der App ist direkt mit Nachrichten, Tracking und SAR-Overlays verbunden, statt nur ein separater Viewer zu sein.",
|
||||
"wizardMapOpsFeature1": "Verfolge deine eigene Position, Teamstandorte und Bewegungsspuren auf der Karte.",
|
||||
"wizardMapOpsFeature2": "Öffne Zeichnungen aus Nachrichten, prüfe sie inline und entferne sie bei Bedarf von der Karte.",
|
||||
"wizardMapOpsFeature3": "Nutze Repeater-Kartenansichten und geteilte Overlays, um die Netzabdeckung im Feld zu verstehen.",
|
||||
"wizardToolsTitle": "Werkzeuge jenseits von Nachrichten",
|
||||
"wizardToolsDescription": "Es gibt hier mehr als nur die vier Haupttabs. Die App enthält außerdem Konfiguration, Diagnose und optionale Sensor-Workflows.",
|
||||
"wizardToolsFeature1": "Öffne die Gerätekonfiguration, um Funkparameter, Telemetrie, Sendeleistung und Begleitdetails zu ändern.",
|
||||
"wizardToolsFeature2": "Aktiviere den Sensoren-Tab, wenn du überwachte Sensor-Dashboards und schnelle Aktualisierungen möchtest.",
|
||||
"wizardToolsFeature3": "Nutze Paketprotokolle, Spektrumsuche und Entwicklerdiagnosen zur Fehlersuche im Mesh.",
|
||||
"contactInSensors": "In Sensoren",
|
||||
"contactAddToSensors": "Zu Sensoren hinzufügen",
|
||||
"contactSetPath": "Pfad festlegen",
|
||||
"contactAddedToSensors": "{contactName} zu Sensoren hinzugefügt",
|
||||
"contactFailedToClearRoute": "Pfad konnte nicht gelöscht werden: {error}",
|
||||
"contactRouteCleared": "Pfad gelöscht",
|
||||
"contactRouteSet": "Pfad gesetzt: {route}",
|
||||
"contactFailedToSetRoute": "Pfad konnte nicht gesetzt werden: {error}",
|
||||
"rssi": "RSSI",
|
||||
"snr": "SNR",
|
||||
"ackTimeout": "ACK-Zeitüberschreitung",
|
||||
"opcode": "Opcode",
|
||||
"payload": "Nutzlast",
|
||||
"hops": "Hops",
|
||||
"hashSize": "Hash-Größe",
|
||||
"pathBytes": "Pfad-Bytes",
|
||||
"selectedPath": "Ausgewählter Pfad",
|
||||
"estimatedTx": "Geschätzte Sendung",
|
||||
"senderToReceipt": "Sender bis Empfang",
|
||||
"receivedCopies": "Empfangene Kopien",
|
||||
"retryCause": "Wiederholungsursache",
|
||||
"retryMode": "Wiederholungsmodus",
|
||||
"retryResult": "Wiederholungsergebnis",
|
||||
"lastRetry": "Letzter Versuch",
|
||||
"rxPackets": "RX-Pakete",
|
||||
"mesh": "Mesh",
|
||||
"rate": "Rate",
|
||||
"window": "Fenster",
|
||||
"posttxDelay": "Sendeverzögerung",
|
||||
"bandpass": "Bandpass",
|
||||
"bandpassFilterVoice": "Bandpassfilter Sprache",
|
||||
"frequency": "Frequenz",
|
||||
"australia": "Australien",
|
||||
"australiaNarrow": "Australien (Schmal)",
|
||||
"australiaQld": "Australien: QLD",
|
||||
"australiaSaWa": "Australien: SA, WA",
|
||||
"newZealand": "Neuseeland",
|
||||
"newZealandNarrow": "Neuseeland (Schmal)",
|
||||
"switzerland": "Schweiz",
|
||||
"portugal433": "Portugal 433",
|
||||
"portugal868": "Portugal 868",
|
||||
"czechRepublicNarrow": "Tschechien (Schmal)",
|
||||
"eu433mhzLongRange": "EU 433MHz (Langstrecke)",
|
||||
"euukDeprecated": "EU/UK (Veraltet)",
|
||||
"euukNarrow": "EU/UK (Schmal)",
|
||||
"usacanadaRecommended": "USA/Kanada (Empfohlen)",
|
||||
"vietnamDeprecated": "Vietnam (Veraltet)",
|
||||
"vietnamNarrow": "Vietnam (Schmal)",
|
||||
"active": "Aktiv",
|
||||
"addContact": "Kontakt hinzufügen",
|
||||
"all": "Alle",
|
||||
"autoResolve": "Automatisch auflösen",
|
||||
"clearAllLabel": "Alle löschen",
|
||||
"clearRelays": "Relais löschen",
|
||||
"clearFilters": "Filter löschen",
|
||||
"clearRoute": "Route löschen",
|
||||
"clearMessages": "Nachrichten löschen",
|
||||
"clearScale": "Maßstab löschen",
|
||||
"clearDiscoveries": "Entdeckungen löschen",
|
||||
"clearOnlineTraceDatabase": "Online-Trace-Datenbank löschen",
|
||||
"clearAllChannels": "Alle Kanäle löschen",
|
||||
"clearAllContacts": "Alle Kontakte löschen",
|
||||
"clearChannels": "Kanäle löschen",
|
||||
"clearContacts": "Kontakte löschen",
|
||||
"clearPathOnMaxRetry": "Pfad bei max. Wiederholung löschen",
|
||||
"create": "Erstellen",
|
||||
"custom": "Benutzerdefiniert",
|
||||
"defaultValue": "Standard",
|
||||
"duplicate": "Duplizieren",
|
||||
"editName": "Name bearbeiten",
|
||||
"open": "Öffnen",
|
||||
"paste": "Einfügen",
|
||||
"preview": "Vorschau",
|
||||
"remove": "Entfernen",
|
||||
"rename": "Umbenennen",
|
||||
"resolveAll": "Alle auflösen",
|
||||
"send": "Senden",
|
||||
"sendAnyway": "Trotzdem senden",
|
||||
"share": "Teilen",
|
||||
"shareContact": "Kontakt teilen",
|
||||
"trace": "Trace",
|
||||
"use": "Verwenden",
|
||||
"useSelectedFrequency": "Ausgewählte Frequenz verwenden",
|
||||
"discovery": "Erkennung",
|
||||
"discoverRepeaters": "Repeater entdecken",
|
||||
"discoverSensors": "Sensoren entdecken",
|
||||
"repeaterDiscoverySent": "Repeater-Erkennung gesendet",
|
||||
"sensorDiscoverySent": "Sensor-Erkennung gesendet",
|
||||
"clearedPendingDiscoveries": "Ausstehende Entdeckungen gelöscht.",
|
||||
"autoDiscovery": "Automatische Erkennung",
|
||||
"enableAutomaticAdding": "Automatisches Hinzufügen aktivieren",
|
||||
"autoaddRepeaters": "Repeater automatisch hinzufügen",
|
||||
"autoaddRoomServers": "Raumserver automatisch hinzufügen",
|
||||
"autoaddSensors": "Sensoren automatisch hinzufügen",
|
||||
"autoaddUsers": "Benutzer automatisch hinzufügen",
|
||||
"overwriteOldestWhenFull": "Älteste bei vollem Speicher überschreiben",
|
||||
"storage": "Speicher",
|
||||
"dangerZone": "Gefahrenbereich",
|
||||
"profiles": "Profile",
|
||||
"favourites": "Favoriten",
|
||||
"sensors": "Sensoren",
|
||||
"others": "Andere",
|
||||
"gpsModule": "GPS-Modul",
|
||||
"liveTraffic": "Live-Verkehr",
|
||||
"repeatersMap": "Repeater-Karte",
|
||||
"spectrumScan": "Spektrum-Scan",
|
||||
"blePacketLogs": "BLE-Paketprotokolle",
|
||||
"onlineTraceDatabase": "Online-Trace-Datenbank",
|
||||
"routePathByteSize": "Pfad-Bytegröße",
|
||||
"messageNotifications": "Nachrichtenbenachrichtigungen",
|
||||
"sarAlerts": "SAR-Alarme",
|
||||
"discoveryNotifications": "Erkennungsbenachrichtigungen",
|
||||
"updateNotifications": "Updatebenachrichtigungen",
|
||||
"muteWhileAppIsOpen": "Stumm bei geöffneter App",
|
||||
"disableContacts": "Kontakte deaktivieren",
|
||||
"enableSensorsTab": "Sensoren-Tab aktivieren",
|
||||
"enableProfiles": "Profile aktivieren",
|
||||
"autoRouteRotation": "Automatische Routenrotation",
|
||||
"nearestRepeaterFallback": "Nächster Repeater als Fallback",
|
||||
"deleteAllStoredMessageHistory": "Gesamten Nachrichtenverlauf löschen",
|
||||
"messageFontSize": "Nachrichtenschriftgröße",
|
||||
"rotateMapWithHeading": "Karte mit Richtung drehen",
|
||||
"showMapDebugInfo": "Karten-Debug-Info anzeigen",
|
||||
"openMapInFullscreen": "Karte im Vollbild öffnen",
|
||||
"showSarMarkersLabel": "SAR-Markierungen anzeigen",
|
||||
"displaySarMarkersOnTheMainMap": "SAR-Markierungen auf der Hauptkarte anzeigen",
|
||||
"showAllContactTrailsLabel": "Alle Kontaktpfade anzeigen",
|
||||
"hideRepeatersOnMap": "Repeater auf Karte ausblenden",
|
||||
"setMapScale": "Kartenmaßstab einstellen",
|
||||
"customMapScaleSaved": "Benutzerdefinierter Kartenmaßstab gespeichert",
|
||||
"voiceBitrate": "Sprach-Bitrate",
|
||||
"voiceCompressor": "Sprachkompressor",
|
||||
"balancesQuietAndLoudSpeechLevels": "Gleicht leise und laute Sprache aus",
|
||||
"voiceLimiter": "Sprach-Limiter",
|
||||
"preventsClippingPeaksBeforeEncoding": "Verhindert Clipping-Spitzen vor der Kodierung",
|
||||
"micAutoGain": "Mikrofon-Automatik",
|
||||
"letsTheRecorderAdjustInputLevel": "Lässt den Rekorder den Eingangspegel anpassen",
|
||||
"echoCancellation": "Echounterdrückung",
|
||||
"noiseSuppression": "Rauschunterdrückung",
|
||||
"trimSilenceInVoiceMessages": "Stille in Sprachnachrichten trimmen",
|
||||
"compressor": "Kompressor",
|
||||
"limiter": "Limiter",
|
||||
"autoGain": "Automatische Verstärkung",
|
||||
"echoCancel": "Echo",
|
||||
"noiseSuppress": "Rauschen",
|
||||
"silenceTrim": "Stille",
|
||||
"maxImageSize": "Maximale Bildgröße",
|
||||
"imageCompression": "Bildkomprimierung",
|
||||
"grayscale": "Graustufen",
|
||||
"ultraMode": "Ultra-Modus",
|
||||
"fastPrivateGpsUpdates": "Schnelle private GPS-Updates",
|
||||
"movementThreshold": "Bewegungsschwelle",
|
||||
"fastGpsMovementThreshold": "Schneller GPS-Bewegungsschwellenwert",
|
||||
"fastGpsActiveuseInterval": "Schnelles GPS aktive Nutzung Intervall",
|
||||
"activeuseUpdateInterval": "Aktive Nutzung Updateintervall",
|
||||
"repeatNearbyTraffic": "Nahverkehr wiederholen",
|
||||
"relayThroughRepeatersAcrossTheMesh": "Über Repeater im Mesh weiterleiten",
|
||||
"nearbyOnlyWithoutRepeaterFlooding": "Nur in der Nähe, ohne Repeater-Flooding",
|
||||
"multihop": "Multi-Hop",
|
||||
"createProfile": "Profil erstellen",
|
||||
"renameProfile": "Profil umbenennen",
|
||||
"newProfile": "Neues Profil",
|
||||
"manageProfiles": "Profile verwalten",
|
||||
"enableProfilesToStartManagingThem": "Aktivieren Sie Profile, um sie zu verwalten.",
|
||||
"openMessage": "Nachricht öffnen",
|
||||
"jumpToTheRelatedSarMessage": "Zur zugehörigen SAR-Nachricht springen",
|
||||
"removeSarMarker": "SAR-Markierung entfernen",
|
||||
"pleaseSelectADestinationToSendSarMarker": "Bitte wählen Sie ein Ziel zum Senden der SAR-Markierung",
|
||||
"sarMarkerBroadcastToPublicChannel": "SAR-Markierung an öffentlichen Kanal gesendet",
|
||||
"sarMarkerSentToRoom": "SAR-Markierung an Raum gesendet",
|
||||
"loadFromGallery": "Aus Galerie laden",
|
||||
"replaceImage": "Bild ersetzen",
|
||||
"selectFromGallery": "Aus Galerie auswählen",
|
||||
"team": "Team",
|
||||
"found": "Gefunden",
|
||||
"staging": "Sammelpunkt",
|
||||
"object": "Objekt",
|
||||
"quiet": "Ruhig",
|
||||
"moderate": "Mäßig",
|
||||
"busy": "Beschäftigt",
|
||||
"spectrumScanReturnedNoCandidateFrequencies": "Spektrum-Scan hat keine Kandidatenfrequenzen gefunden",
|
||||
"searchMessages": "Nachrichten suchen",
|
||||
"sendImageFromGallery": "Bild aus Galerie senden",
|
||||
"takePhoto": "Foto aufnehmen",
|
||||
"dmOnly": "Nur Direktnachricht",
|
||||
"allMessages": "Alle Nachrichten",
|
||||
"sendToPublicChannel": "An öffentlichen Kanal senden?",
|
||||
"selectMarkerTypeAndDestination": "Markierungstyp und Ziel auswählen",
|
||||
"noDestinationsAvailableLabel": "Keine Ziele verfügbar",
|
||||
"image": "Bild",
|
||||
"format": "Format",
|
||||
"dimensions": "Abmessungen",
|
||||
"segments": "Segmente",
|
||||
"transfers": "Übertragungen",
|
||||
"downloadedBy": "Heruntergeladen von",
|
||||
"saveDiscoverySettings": "Erkennungseinstellungen speichern",
|
||||
"savePublicInfo": "Öffentliche Info speichern",
|
||||
"saveRadioSettings": "Funkeinstellungen speichern",
|
||||
"savePath": "Pfad speichern",
|
||||
"wipeDeviceData": "Gerätedaten löschen",
|
||||
"wipeDevice": "Gerät löschen",
|
||||
"destructiveDeviceActions": "Destruktive Geräteaktionen.",
|
||||
"chooseAPresetOrFinetuneCustomRadioSettings": "Wählen Sie eine Voreinstellung oder passen Sie die Funkeinstellungen an.",
|
||||
"chooseTheNameAndLocationThisDeviceShares": "Wählen Sie den Namen und Standort, den dieses Gerät teilt.",
|
||||
"availableSpaceOnThisDevice": "Verfügbarer Speicherplatz auf diesem Gerät.",
|
||||
"used": "Belegt",
|
||||
"total": "Gesamt",
|
||||
"renameValue": "Wert umbenennen",
|
||||
"customizeFields": "Felder anpassen",
|
||||
"livePreview": "Live-Vorschau",
|
||||
"refreshSchedule": "Aktualisierungsplan",
|
||||
"noResponse": "Keine Antwort",
|
||||
"refreshing": "Wird aktualisiert",
|
||||
"unavailable": "Nicht verfügbar",
|
||||
"pickARelayOrNodeToWatchInSensors": "Wählen Sie einen Relay oder Knoten zur Beobachtung.",
|
||||
"publicKeyLabel": "Öffentlicher Schlüssel",
|
||||
"alreadyInContacts": "Bereits in Kontakten",
|
||||
"connectToADeviceBeforeAddingContacts": "Verbinden Sie sich mit einem Gerät, bevor Sie Kontakte hinzufügen",
|
||||
"fromContacts": "Aus Kontakten",
|
||||
"onlineOnly": "Nur online",
|
||||
"inBoth": "In beiden",
|
||||
"source": "Quelle",
|
||||
"manualRouteEdit": "Manuelle Routenbearbeitung",
|
||||
"observedMeshRoute": "Beobachtete Mesh-Route",
|
||||
"allMessagesCleared": "Alle Nachrichten gelöscht",
|
||||
"onlineTraceDatabaseCleared": "Online-Trace-Datenbank gelöscht",
|
||||
"packetLogsCleared": "Paketprotokolle gelöscht",
|
||||
"hexDataCopiedToClipboard": "Hex-Daten in die Zwischenablage kopiert",
|
||||
"developerModeEnabled": "Entwicklermodus aktiviert",
|
||||
"developerModeDisabled": "Entwicklermodus deaktiviert",
|
||||
"clipboardIsEmpty": "Zwischenablage ist leer",
|
||||
"contactImported": "Kontakt importiert",
|
||||
"contactLinkCopiedToClipboard": "Kontaktlink in Zwischenablage kopiert",
|
||||
"failedToExportContact": "Kontaktexport fehlgeschlagen",
|
||||
"noLogsToExport": "Keine Protokolle zum Exportieren",
|
||||
"exportAsCsv": "Als CSV exportieren",
|
||||
"exportAsText": "Als Text exportieren",
|
||||
"receivedRfc3339": "Empfangen (RFC3339)",
|
||||
"buildTime": "Build-Zeit",
|
||||
"downloadUrlNotAvailable": "Download-URL nicht verfügbar",
|
||||
"cannotOpenDownloadUrl": "Download-URL kann nicht geöffnet werden",
|
||||
"updateCheckIsOnlyAvailableOnAndroid": "Update-Prüfung nur auf Android verfügbar",
|
||||
"youAreRunningTheLatestVersion": "Sie verwenden die neueste Version",
|
||||
"updateAvailableButDownloadUrlNotFound": "Update verfügbar, aber Download-URL nicht gefunden",
|
||||
"startTictactoe": "Tic-Tac-Toe starten",
|
||||
"tictactoeUnavailable": "Tic-Tac-Toe nicht verfügbar",
|
||||
"tictactoeOpponentUnknown": "Tic-Tac-Toe: Gegner unbekannt",
|
||||
"tictactoeWaitingForStart": "Tic-Tac-Toe: Warten auf Start",
|
||||
"acceptsShareLinks": "Akzeptiert freigegebene Links",
|
||||
"supportsRawHex": "Unterstützt rohes Hex",
|
||||
"clipboardfriendly": "Zwischenablage-freundlich",
|
||||
"captured": "Erfasst",
|
||||
"size": "Größe",
|
||||
"noCustomChannelsToClear": "Keine benutzerdefinierten Kanäle zum Löschen.",
|
||||
"noDeviceContactsToClear": "Keine Gerätekontakte zum Löschen."
|
||||
}
|
||||
|
||||
@@ -2251,5 +2251,304 @@
|
||||
"rawDump": "Ακατέργαστα δεδομένα",
|
||||
"cannotRetryMissingRecipient": "Δεν είναι δυνατή η επανάληψη: λείπουν πληροφορίες παραλήπτη",
|
||||
"voiceUnavailable": "Η φωνή δεν είναι διαθέσιμη αυτή τη στιγμή",
|
||||
"requestingVoice": "Αίτηση φωνής"
|
||||
"requestingVoice": "Αίτηση φωνής",
|
||||
"device": "συσκευή",
|
||||
"change": "Αλλαγή",
|
||||
"wizardOverviewDescription": "Αυτή η εφαρμογή συνδυάζει μηνύματα MeshCore, ενημερώσεις πεδίου SAR, χαρτογράφηση και εργαλεία συσκευής σε ένα σημείο.",
|
||||
"wizardOverviewFeature1": "Στείλτε άμεσα μηνύματα, δημοσιεύσεις δωματίου και μηνύματα καναλιού από την κύρια καρτέλα Μηνύματα.",
|
||||
"wizardOverviewFeature2": "Μοιραστείτε δείκτες SAR, σχεδιάσεις χάρτη, ηχητικά αποσπάσματα και εικόνες μέσω του mesh.",
|
||||
"wizardOverviewFeature3": "Συνδεθείτε μέσω BLE ή TCP και στη συνέχεια διαχειριστείτε το συνοδευτικό ραδιόφωνο μέσα από την εφαρμογή.",
|
||||
"wizardMessagingTitle": "Μηνύματα και αναφορές πεδίου",
|
||||
"wizardMessagingDescription": "Τα μηνύματα εδώ είναι κάτι περισσότερο από απλό κείμενο. Η εφαρμογή υποστηρίζει ήδη πολλαπλά επιχειρησιακά φορτία και ροές μεταφοράς.",
|
||||
"wizardMessagingFeature1": "Στείλτε άμεσα μηνύματα, δημοσιεύσεις δωματίου και κίνηση καναλιού από έναν μόνο συντάκτη.",
|
||||
"wizardMessagingFeature2": "Δημιουργήστε ενημερώσεις SAR και επαναχρησιμοποιήσιμα πρότυπα SAR για συνηθισμένες αναφορές πεδίου.",
|
||||
"wizardMessagingFeature3": "Μεταφέρετε φωνητικές συνεδρίες και εικόνες, με ένδειξη προόδου και εκτιμήσεις χρόνου εκπομπής στο περιβάλλον.",
|
||||
"wizardConnectDeviceTitle": "Σύνδεση συσκευής",
|
||||
"wizardConnectDeviceDescription": "Συνδέστε το ραδιόφωνο MeshCore, επιλέξτε όνομα και εφαρμόστε μια προρύθμιση ραδιοφώνου πριν συνεχίσετε.",
|
||||
"wizardSetupBadge": "Ρύθμιση",
|
||||
"wizardOverviewBadge": "Επισκόπηση",
|
||||
"wizardConnectedToDevice": "Συνδέθηκε με το {deviceName}",
|
||||
"wizardNoDeviceConnected": "Δεν έχει συνδεθεί ακόμη συσκευή",
|
||||
"wizardSkipForNow": "Παράλειψη προς το παρόν",
|
||||
"wizardDeviceNameLabel": "Όνομα συσκευής",
|
||||
"wizardDeviceNameHelp": "Αυτό το όνομα ανακοινώνεται σε άλλους χρήστες του MeshCore.",
|
||||
"wizardConfigRegionLabel": "Περιοχή ρύθμισης",
|
||||
"wizardConfigRegionHelp": "Χρησιμοποιεί την πλήρη επίσημη λίστα προρυθμίσεων MeshCore. Η προεπιλογή είναι EU/UK (Narrow).",
|
||||
"wizardPresetNote1": "Βεβαιωθείτε ότι η επιλεγμένη προρύθμιση ταιριάζει με τους τοπικούς ραδιοφωνικούς κανονισμούς.",
|
||||
"wizardPresetNote2": "Η λίστα αντιστοιχεί στην επίσημη ροή προρυθμίσεων του εργαλείου ρύθμισης MeshCore.",
|
||||
"wizardPresetNote3": "Το EU/UK (Narrow) παραμένει επιλεγμένο από προεπιλογή κατά την αρχική ρύθμιση.",
|
||||
"wizardSaving": "Αποθήκευση...",
|
||||
"wizardSaveAndContinue": "Αποθήκευση και συνέχεια",
|
||||
"wizardEnterDeviceName": "Εισαγάγετε όνομα συσκευής πριν συνεχίσετε.",
|
||||
"wizardDeviceSetupSaved": "Αποθηκεύτηκε το {deviceName} με {presetName}.",
|
||||
"wizardNetworkTitle": "Επαφές, δωμάτια και αναμεταδότες",
|
||||
"wizardNetworkDescription": "Η καρτέλα Επαφές οργανώνει το δίκτυο που ανακαλύπτετε και τις διαδρομές που μαθαίνετε με τον χρόνο.",
|
||||
"wizardNetworkFeature1": "Ελέγξτε μέλη ομάδας, αναμεταδότες, δωμάτια, κανάλια και εκκρεμείς ανακοινώσεις σε μία λίστα.",
|
||||
"wizardNetworkFeature2": "Χρησιμοποιήστε smart ping, σύνδεση σε δωμάτια, μαθημένες διαδρομές και εργαλεία επαναφοράς διαδρομών όταν η συνδεσιμότητα γίνεται χαοτική.",
|
||||
"wizardNetworkFeature3": "Δημιουργήστε κανάλια και διαχειριστείτε προορισμούς δικτύου χωρίς να φύγετε από την εφαρμογή.",
|
||||
"wizardMapOpsTitle": "Χάρτης, ίχνη και κοινόχρηστη γεωμετρία",
|
||||
"wizardMapOpsDescription": "Ο χάρτης της εφαρμογής συνδέεται άμεσα με τα μηνύματα, την παρακολούθηση και τα επικαλύμματα SAR αντί να είναι ξεχωριστός προβολέας.",
|
||||
"wizardMapOpsFeature1": "Παρακολουθήστε τη θέση σας, τις τοποθεσίες της ομάδας και τα ίχνη κίνησης στον χάρτη.",
|
||||
"wizardMapOpsFeature2": "Ανοίξτε σχεδιάσεις από μηνύματα, προεπισκοπήστε τες επιτόπου και αφαιρέστε τες από τον χάρτη όταν χρειάζεται.",
|
||||
"wizardMapOpsFeature3": "Χρησιμοποιήστε προβολές χάρτη αναμεταδοτών και κοινόχρηστα επικαλύμματα για να κατανοήσετε την εμβέλεια του δικτύου στο πεδίο.",
|
||||
"wizardToolsTitle": "Εργαλεία πέρα από τα μηνύματα",
|
||||
"wizardToolsDescription": "Υπάρχουν περισσότερα εδώ από τις τέσσερις κύριες καρτέλες. Η εφαρμογή περιλαμβάνει επίσης ρυθμίσεις, διαγνωστικά και προαιρετικές ροές αισθητήρων.",
|
||||
"wizardToolsFeature1": "Ανοίξτε τη ρύθμιση συσκευής για να αλλάξετε παραμέτρους ραδιοφώνου, τηλεμετρία, ισχύ TX και στοιχεία συνοδευτικής συσκευής.",
|
||||
"wizardToolsFeature2": "Ενεργοποιήστε την καρτέλα Αισθητήρες όταν θέλετε πίνακες παρακολούθησης και γρήγορες ενέργειες ανανέωσης.",
|
||||
"wizardToolsFeature3": "Χρησιμοποιήστε καταγραφές πακέτων, σάρωση φάσματος και διαγνωστικά προγραμματιστή κατά την αντιμετώπιση προβλημάτων του mesh.",
|
||||
"contactInSensors": "Στους αισθητήρες",
|
||||
"contactAddToSensors": "Προσθήκη στους αισθητήρες",
|
||||
"contactSetPath": "Ορισμός διαδρομής",
|
||||
"contactAddedToSensors": "Το {contactName} προστέθηκε στους Αισθητήρες",
|
||||
"contactFailedToClearRoute": "Αποτυχία εκκαθάρισης διαδρομής: {error}",
|
||||
"contactRouteCleared": "Η διαδρομή εκκαθαρίστηκε",
|
||||
"contactRouteSet": "Ορίστηκε διαδρομή: {route}",
|
||||
"contactFailedToSetRoute": "Αποτυχία ορισμού διαδρομής: {error}",
|
||||
"rssi": "RSSI",
|
||||
"snr": "SNR",
|
||||
"ackTimeout": "Λήξη χρόνου ACK",
|
||||
"opcode": "Opcode",
|
||||
"payload": "Ωφέλιμο φορτίο",
|
||||
"hops": "Αναπηδήσεις",
|
||||
"hashSize": "Μέγεθος hash",
|
||||
"pathBytes": "Bytes διαδρομής",
|
||||
"selectedPath": "Επιλεγμένη διαδρομή",
|
||||
"estimatedTx": "Εκτιμώμενη εκπομπή",
|
||||
"senderToReceipt": "Αποστολέας σε απόδειξη",
|
||||
"receivedCopies": "Ληφθέντα αντίγραφα",
|
||||
"retryCause": "Αιτία επανάληψης",
|
||||
"retryMode": "Λειτουργία επανάληψης",
|
||||
"retryResult": "Αποτέλεσμα επανάληψης",
|
||||
"lastRetry": "Τελευταία επανάληψη",
|
||||
"rxPackets": "Πακέτα RX",
|
||||
"mesh": "Mesh",
|
||||
"rate": "Ρυθμός",
|
||||
"window": "Παράθυρο",
|
||||
"posttxDelay": "Καθυστέρηση μετά-tx",
|
||||
"bandpass": "Ζωνοπερατό",
|
||||
"bandpassFilterVoice": "Ζωνοπερατό φίλτρο φωνής",
|
||||
"frequency": "Συχνότητα",
|
||||
"australia": "Αυστραλία",
|
||||
"australiaNarrow": "Αυστραλία (Στενό)",
|
||||
"australiaQld": "Αυστραλία: QLD",
|
||||
"australiaSaWa": "Αυστραλία: SA, WA",
|
||||
"newZealand": "Νέα Ζηλανδία",
|
||||
"newZealandNarrow": "Νέα Ζηλανδία (Στενό)",
|
||||
"switzerland": "Ελβετία",
|
||||
"portugal433": "Πορτογαλία 433",
|
||||
"portugal868": "Πορτογαλία 868",
|
||||
"czechRepublicNarrow": "Τσεχία (Στενό)",
|
||||
"eu433mhzLongRange": "ΕΕ 433MHz (Μεγάλη Εμβέλεια)",
|
||||
"euukDeprecated": "ΕΕ/ΗΒ (Παρωχημένο)",
|
||||
"euukNarrow": "ΕΕ/ΗΒ (Στενό)",
|
||||
"usacanadaRecommended": "ΗΠΑ/Καναδάς (Προτεινόμενο)",
|
||||
"vietnamDeprecated": "Βιετνάμ (Παρωχημένο)",
|
||||
"vietnamNarrow": "Βιετνάμ (Στενό)",
|
||||
"active": "Ενεργό",
|
||||
"addContact": "Προσθήκη επαφής",
|
||||
"all": "Όλα",
|
||||
"autoResolve": "Αυτόματη επίλυση",
|
||||
"clearAllLabel": "Εκκαθάριση όλων",
|
||||
"clearRelays": "Εκκαθάριση αναμεταδοτών",
|
||||
"clearFilters": "Εκκαθάριση φίλτρων",
|
||||
"clearRoute": "Εκκαθάριση διαδρομής",
|
||||
"clearMessages": "Εκκαθάριση μηνυμάτων",
|
||||
"clearScale": "Εκκαθάριση κλίμακας",
|
||||
"clearDiscoveries": "Εκκαθάριση ανακαλύψεων",
|
||||
"clearOnlineTraceDatabase": "Εκκαθάριση βάσης ιχνών",
|
||||
"clearAllChannels": "Εκκαθάριση όλων των καναλιών",
|
||||
"clearAllContacts": "Εκκαθάριση όλων των επαφών",
|
||||
"clearChannels": "Εκκαθάριση καναλιών",
|
||||
"clearContacts": "Εκκαθάριση επαφών",
|
||||
"clearPathOnMaxRetry": "Εκκαθάριση μονοπατιού στο μέγιστο",
|
||||
"create": "Δημιουργία",
|
||||
"custom": "Προσαρμοσμένο",
|
||||
"defaultValue": "Προεπιλογή",
|
||||
"duplicate": "Αντιγραφή",
|
||||
"editName": "Επεξεργασία ονόματος",
|
||||
"open": "Άνοιγμα",
|
||||
"paste": "Επικόλληση",
|
||||
"preview": "Προεπισκόπηση",
|
||||
"remove": "Αφαίρεση",
|
||||
"rename": "Μετονομασία",
|
||||
"resolveAll": "Επίλυση όλων",
|
||||
"send": "Αποστολή",
|
||||
"sendAnyway": "Αποστολή ούτως ή άλλως",
|
||||
"share": "Κοινοποίηση",
|
||||
"shareContact": "Κοινοποίηση επαφής",
|
||||
"trace": "Ίχνος",
|
||||
"use": "Χρήση",
|
||||
"useSelectedFrequency": "Χρήση επιλεγμένης συχνότητας",
|
||||
"discovery": "Ανακάλυψη",
|
||||
"discoverRepeaters": "Ανακάλυψη αναμεταδοτών",
|
||||
"discoverSensors": "Ανακάλυψη αισθητήρων",
|
||||
"repeaterDiscoverySent": "Αποστολή ανακάλυψης αναμεταδοτών",
|
||||
"sensorDiscoverySent": "Αποστολή ανακάλυψης αισθητήρων",
|
||||
"clearedPendingDiscoveries": "Εκκαθάριση εκκρεμών ανακαλύψεων.",
|
||||
"autoDiscovery": "Αυτόματη ανακάλυψη",
|
||||
"enableAutomaticAdding": "Ενεργοποίηση αυτόματης προσθήκης",
|
||||
"autoaddRepeaters": "Αυτόματη προσθήκη αναμεταδοτών",
|
||||
"autoaddRoomServers": "Αυτόματη προσθήκη διακομιστών δωματίου",
|
||||
"autoaddSensors": "Αυτόματη προσθήκη αισθητήρων",
|
||||
"autoaddUsers": "Αυτόματη προσθήκη χρηστών",
|
||||
"overwriteOldestWhenFull": "Αντικατάσταση παλαιότερων όταν γεμίσει",
|
||||
"storage": "Αποθήκευση",
|
||||
"dangerZone": "Ζώνη κινδύνου",
|
||||
"profiles": "Προφίλ",
|
||||
"favourites": "Αγαπημένα",
|
||||
"sensors": "Αισθητήρες",
|
||||
"others": "Άλλοι",
|
||||
"gpsModule": "Μονάδα GPS",
|
||||
"liveTraffic": "Ζωντανή κίνηση",
|
||||
"repeatersMap": "Χάρτης αναμεταδοτών",
|
||||
"spectrumScan": "Σάρωση φάσματος",
|
||||
"blePacketLogs": "Αρχεία πακέτων BLE",
|
||||
"onlineTraceDatabase": "Βάση δεδομένων ιχνών",
|
||||
"routePathByteSize": "Μέγεθος διαδρομής σε bytes",
|
||||
"messageNotifications": "Ειδοποιήσεις μηνυμάτων",
|
||||
"sarAlerts": "Ειδοποιήσεις SAR",
|
||||
"discoveryNotifications": "Ειδοποιήσεις ανακάλυψης",
|
||||
"updateNotifications": "Ειδοποιήσεις ενημερώσεων",
|
||||
"muteWhileAppIsOpen": "Σίγαση με ανοιχτή εφαρμογή",
|
||||
"disableContacts": "Απενεργοποίηση επαφών",
|
||||
"enableSensorsTab": "Ενεργοποίηση καρτέλας Αισθητήρων",
|
||||
"enableProfiles": "Ενεργοποίηση προφίλ",
|
||||
"autoRouteRotation": "Αυτόματη εναλλαγή διαδρομής",
|
||||
"nearestRepeaterFallback": "Πλησιέστερος αναμεταδότης ως εφεδρεία",
|
||||
"deleteAllStoredMessageHistory": "Διαγραφή όλου του ιστορικού",
|
||||
"messageFontSize": "Μέγεθος γραμματοσειράς μηνυμάτων",
|
||||
"rotateMapWithHeading": "Περιστροφή χάρτη με κατεύθυνση",
|
||||
"showMapDebugInfo": "Εμφάνιση πληροφοριών αποσφαλμάτωσης",
|
||||
"openMapInFullscreen": "Άνοιγμα χάρτη σε πλήρη οθόνη",
|
||||
"showSarMarkersLabel": "Εμφάνιση δεικτών SAR",
|
||||
"displaySarMarkersOnTheMainMap": "Εμφάνιση δεικτών SAR στον κύριο χάρτη",
|
||||
"showAllContactTrailsLabel": "Εμφάνιση όλων των ιχνών επαφών",
|
||||
"hideRepeatersOnMap": "Απόκρυψη αναμεταδοτών στον χάρτη",
|
||||
"setMapScale": "Ρύθμιση κλίμακας χάρτη",
|
||||
"customMapScaleSaved": "Προσαρμοσμένη κλίμακα χάρτη αποθηκεύτηκε",
|
||||
"voiceBitrate": "Ρυθμός bit φωνής",
|
||||
"voiceCompressor": "Συμπιεστής φωνής",
|
||||
"balancesQuietAndLoudSpeechLevels": "Εξισορρόπηση χαμηλών και δυνατών επιπέδων",
|
||||
"voiceLimiter": "Περιοριστής φωνής",
|
||||
"preventsClippingPeaksBeforeEncoding": "Αποτρέπει κοπή κορυφών πριν την κωδικοποίηση",
|
||||
"micAutoGain": "Αυτόματη ενίσχυση μικροφώνου",
|
||||
"letsTheRecorderAdjustInputLevel": "Αφήνει τη συσκευή να ρυθμίσει την ένταση",
|
||||
"echoCancellation": "Ακύρωση ηχούς",
|
||||
"noiseSuppression": "Καταστολή θορύβου",
|
||||
"trimSilenceInVoiceMessages": "Περικοπή σιωπής σε φωνητικά μηνύματα",
|
||||
"compressor": "Συμπιεστής",
|
||||
"limiter": "Περιοριστής",
|
||||
"autoGain": "Αυτόματη ενίσχυση",
|
||||
"echoCancel": "Ηχώ",
|
||||
"noiseSuppress": "Θόρυβος",
|
||||
"silenceTrim": "Σιωπή",
|
||||
"maxImageSize": "Μέγιστο μέγεθος εικόνας",
|
||||
"imageCompression": "Συμπίεση εικόνας",
|
||||
"grayscale": "Κλίμακα του γκρι",
|
||||
"ultraMode": "Λειτουργία ultra",
|
||||
"fastPrivateGpsUpdates": "Γρήγορες ιδιωτικές ενημερώσεις GPS",
|
||||
"movementThreshold": "Κατώφλι κίνησης",
|
||||
"fastGpsMovementThreshold": "Κατώφλι κίνησης γρήγορου GPS",
|
||||
"fastGpsActiveuseInterval": "Διάστημα ενεργής χρήσης γρήγορου GPS",
|
||||
"activeuseUpdateInterval": "Διάστημα ενημέρωσης ενεργής χρήσης",
|
||||
"repeatNearbyTraffic": "Επανάληψη κοντινής κίνησης",
|
||||
"relayThroughRepeatersAcrossTheMesh": "Αναμετάδοση μέσω αναμεταδοτών στο δίκτυο",
|
||||
"nearbyOnlyWithoutRepeaterFlooding": "Μόνο κοντά, χωρίς πλημμυρισμό",
|
||||
"multihop": "Πολλαπλών αναπηδήσεων",
|
||||
"createProfile": "Δημιουργία προφίλ",
|
||||
"renameProfile": "Μετονομασία προφίλ",
|
||||
"newProfile": "Νέο προφίλ",
|
||||
"manageProfiles": "Διαχείριση προφίλ",
|
||||
"enableProfilesToStartManagingThem": "Ενεργοποιήστε τα προφίλ για να τα διαχειριστείτε.",
|
||||
"openMessage": "Άνοιγμα μηνύματος",
|
||||
"jumpToTheRelatedSarMessage": "Μετάβαση στο σχετικό μήνυμα SAR",
|
||||
"removeSarMarker": "Αφαίρεση δείκτη SAR",
|
||||
"pleaseSelectADestinationToSendSarMarker": "Επιλέξτε προορισμό για αποστολή δείκτη SAR",
|
||||
"sarMarkerBroadcastToPublicChannel": "Δείκτης SAR εκπεμπόμενος στο δημόσιο κανάλι",
|
||||
"sarMarkerSentToRoom": "Δείκτης SAR εστάλη στο δωμάτιο",
|
||||
"loadFromGallery": "Φόρτωση από τη γκαλερί",
|
||||
"replaceImage": "Αντικατάσταση εικόνας",
|
||||
"selectFromGallery": "Επιλογή από τη γκαλερί",
|
||||
"team": "Ομάδα",
|
||||
"found": "Βρέθηκε",
|
||||
"staging": "Χώρος συγκέντρωσης",
|
||||
"object": "Αντικείμενο",
|
||||
"quiet": "Ήσυχο",
|
||||
"moderate": "Μέτριο",
|
||||
"busy": "Απασχολημένο",
|
||||
"spectrumScanReturnedNoCandidateFrequencies": "Η σάρωση φάσματος δεν βρήκε υποψήφιες συχνότητες",
|
||||
"searchMessages": "Αναζήτηση μηνυμάτων",
|
||||
"sendImageFromGallery": "Αποστολή εικόνας από τη γκαλερί",
|
||||
"takePhoto": "Λήψη φωτογραφίας",
|
||||
"dmOnly": "Μόνο απευθείας",
|
||||
"allMessages": "Όλα τα μηνύματα",
|
||||
"sendToPublicChannel": "Αποστολή στο δημόσιο κανάλι;",
|
||||
"selectMarkerTypeAndDestination": "Επιλέξτε τύπο δείκτη και προορισμό",
|
||||
"noDestinationsAvailableLabel": "Δεν υπάρχουν διαθέσιμοι προορισμοί",
|
||||
"image": "Εικόνα",
|
||||
"format": "Μορφή",
|
||||
"dimensions": "Διαστάσεις",
|
||||
"segments": "Τμήματα",
|
||||
"transfers": "Μεταφορές",
|
||||
"downloadedBy": "Λήφθηκε από",
|
||||
"saveDiscoverySettings": "Αποθήκευση ρυθμίσεων ανακάλυψης",
|
||||
"savePublicInfo": "Αποθήκευση δημόσιων πληροφοριών",
|
||||
"saveRadioSettings": "Αποθήκευση ρυθμίσεων ραδιοφώνου",
|
||||
"savePath": "Αποθήκευση διαδρομής",
|
||||
"wipeDeviceData": "Διαγραφή δεδομένων συσκευής",
|
||||
"wipeDevice": "Διαγραφή συσκευής",
|
||||
"destructiveDeviceActions": "Καταστροφικές ενέργειες συσκευής.",
|
||||
"chooseAPresetOrFinetuneCustomRadioSettings": "Επιλέξτε μια προεπιλογή ή ρυθμίστε τις ρυθμίσεις ραδιοφώνου.",
|
||||
"chooseTheNameAndLocationThisDeviceShares": "Επιλέξτε το όνομα και την τοποθεσία που μοιράζεται αυτή η συσκευή.",
|
||||
"availableSpaceOnThisDevice": "Διαθέσιμος χώρος σε αυτή τη συσκευή.",
|
||||
"used": "Χρησιμοποιήθηκε",
|
||||
"total": "Σύνολο",
|
||||
"renameValue": "Μετονομασία τιμής",
|
||||
"customizeFields": "Προσαρμογή πεδίων",
|
||||
"livePreview": "Ζωντανή προεπισκόπηση",
|
||||
"refreshSchedule": "Χρονοδιάγραμμα ανανέωσης",
|
||||
"noResponse": "Χωρίς απάντηση",
|
||||
"refreshing": "Ανανέωση",
|
||||
"unavailable": "Μη διαθέσιμο",
|
||||
"pickARelayOrNodeToWatchInSensors": "Επιλέξτε ένα relay ή κόμβο για παρακολούθηση.",
|
||||
"publicKeyLabel": "Δημόσιο κλειδί",
|
||||
"alreadyInContacts": "Ήδη στις επαφές",
|
||||
"connectToADeviceBeforeAddingContacts": "Συνδεθείτε σε μια συσκευή πριν προσθέσετε επαφές",
|
||||
"fromContacts": "Από τις επαφές",
|
||||
"onlineOnly": "Μόνο συνδεδεμένοι",
|
||||
"inBoth": "Σε αμφότερα",
|
||||
"source": "Πηγή",
|
||||
"manualRouteEdit": "Χειροκίνητη επεξεργασία διαδρομής",
|
||||
"observedMeshRoute": "Παρατηρούμενη διαδρομή δικτύου",
|
||||
"allMessagesCleared": "Όλα τα μηνύματα διαγράφηκαν",
|
||||
"onlineTraceDatabaseCleared": "Βάση δεδομένων ιχνών εκκαθαρίστηκε",
|
||||
"packetLogsCleared": "Τα αρχεία πακέτων εκκαθαρίστηκαν",
|
||||
"hexDataCopiedToClipboard": "Τα δεδομένα hex αντιγράφηκαν",
|
||||
"developerModeEnabled": "Λειτουργία προγραμματιστή ενεργοποιήθηκε",
|
||||
"developerModeDisabled": "Λειτουργία προγραμματιστή απενεργοποιήθηκε",
|
||||
"clipboardIsEmpty": "Το πρόχειρο είναι κενό",
|
||||
"contactImported": "Επαφή εισήχθη",
|
||||
"contactLinkCopiedToClipboard": "Ο σύνδεσμος επαφής αντιγράφηκε",
|
||||
"failedToExportContact": "Αποτυχία εξαγωγής επαφής",
|
||||
"noLogsToExport": "Δεν υπάρχουν αρχεία για εξαγωγή",
|
||||
"exportAsCsv": "Εξαγωγή ως CSV",
|
||||
"exportAsText": "Εξαγωγή ως κείμενο",
|
||||
"receivedRfc3339": "Ελήφθη (RFC3339)",
|
||||
"buildTime": "Ώρα κατασκευής",
|
||||
"downloadUrlNotAvailable": "Η URL λήψης δεν είναι διαθέσιμη",
|
||||
"cannotOpenDownloadUrl": "Αδυναμία ανοίγματος URL λήψης",
|
||||
"updateCheckIsOnlyAvailableOnAndroid": "Ο έλεγχος ενημερώσεων είναι διαθέσιμος μόνο σε Android",
|
||||
"youAreRunningTheLatestVersion": "Χρησιμοποιείτε την πιο πρόσφατη έκδοση",
|
||||
"updateAvailableButDownloadUrlNotFound": "Διαθέσιμη ενημέρωση αλλά η URL λήψης δεν βρέθηκε",
|
||||
"startTictactoe": "Εκκίνηση Tic-Tac-Toe",
|
||||
"tictactoeUnavailable": "Tic-Tac-Toe μη διαθέσιμο",
|
||||
"tictactoeOpponentUnknown": "Tic-Tac-Toe: άγνωστος αντίπαλος",
|
||||
"tictactoeWaitingForStart": "Tic-Tac-Toe: αναμονή για εκκίνηση",
|
||||
"acceptsShareLinks": "Αποδέχεται κοινόχρηστους συνδέσμους",
|
||||
"supportsRawHex": "Υποστηρίζει ακατέργαστο hex",
|
||||
"clipboardfriendly": "Φιλικό προς πρόχειρο",
|
||||
"captured": "Καταγράφηκε",
|
||||
"size": "Μέγεθος",
|
||||
"noCustomChannelsToClear": "Δεν υπάρχουν προσαρμοσμένα κανάλια.",
|
||||
"noDeviceContactsToClear": "Δεν υπάρχουν επαφές συσκευής."
|
||||
}
|
||||
|
||||
@@ -2228,78 +2228,547 @@
|
||||
"description": "Message when there are no chat contacts to send to"
|
||||
},
|
||||
"reply": "Reply",
|
||||
"@reply": {
|
||||
},
|
||||
"@reply": {},
|
||||
"technicalDetails": "Technical details",
|
||||
"@technicalDetails": {
|
||||
},
|
||||
"@technicalDetails": {},
|
||||
"messageTechnicalDetails": "Message technical details",
|
||||
"@messageTechnicalDetails": {
|
||||
},
|
||||
"@messageTechnicalDetails": {},
|
||||
"linkQuality": "Link quality",
|
||||
"@linkQuality": {
|
||||
},
|
||||
"@linkQuality": {},
|
||||
"delivery": "Delivery",
|
||||
"@delivery": {
|
||||
},
|
||||
"@delivery": {},
|
||||
"status": "Status",
|
||||
"@status": {
|
||||
},
|
||||
"@status": {},
|
||||
"expectedAckTag": "Expected ACK tag",
|
||||
"@expectedAckTag": {
|
||||
},
|
||||
"@expectedAckTag": {},
|
||||
"roundTrip": "Round-trip",
|
||||
"@roundTrip": {
|
||||
},
|
||||
"@roundTrip": {},
|
||||
"retryAttempt": "Retry attempt",
|
||||
"@retryAttempt": {
|
||||
},
|
||||
"@retryAttempt": {},
|
||||
"floodFallback": "Flood fallback",
|
||||
"@floodFallback": {
|
||||
},
|
||||
"@floodFallback": {},
|
||||
"identity": "Identity",
|
||||
"@identity": {
|
||||
},
|
||||
"@identity": {},
|
||||
"messageId": "Message ID",
|
||||
"@messageId": {
|
||||
},
|
||||
"@messageId": {},
|
||||
"sender": "Sender",
|
||||
"@sender": {
|
||||
},
|
||||
"@sender": {},
|
||||
"senderKey": "Sender key",
|
||||
"@senderKey": {
|
||||
},
|
||||
"@senderKey": {},
|
||||
"recipient": "Recipient",
|
||||
"@recipient": {
|
||||
},
|
||||
"@recipient": {},
|
||||
"recipientKey": "Recipient key",
|
||||
"@recipientKey": {
|
||||
},
|
||||
"@recipientKey": {},
|
||||
"voice": "Voice",
|
||||
"@voice": {
|
||||
},
|
||||
"@voice": {},
|
||||
"voiceId": "Voice ID",
|
||||
"@voiceId": {
|
||||
},
|
||||
"@voiceId": {},
|
||||
"envelope": "Envelope",
|
||||
"@envelope": {
|
||||
},
|
||||
"@envelope": {},
|
||||
"sessionProgress": "Session progress",
|
||||
"@sessionProgress": {
|
||||
},
|
||||
"@sessionProgress": {},
|
||||
"complete": "Complete",
|
||||
"@complete": {
|
||||
},
|
||||
"@complete": {},
|
||||
"rawDump": "Raw dump",
|
||||
"@rawDump": {
|
||||
},
|
||||
"@rawDump": {},
|
||||
"cannotRetryMissingRecipient": "Cannot retry: recipient information missing",
|
||||
"@cannotRetryMissingRecipient": {
|
||||
},
|
||||
"@cannotRetryMissingRecipient": {},
|
||||
"voiceUnavailable": "Voice unavailable right now",
|
||||
"@voiceUnavailable": {
|
||||
},
|
||||
"@voiceUnavailable": {},
|
||||
"requestingVoice": "Requesting voice",
|
||||
"@requestingVoice": {
|
||||
}
|
||||
"@requestingVoice": {},
|
||||
"device": "device",
|
||||
"@device": {
|
||||
"description": "Generic device label"
|
||||
},
|
||||
"change": "Change",
|
||||
"@change": {
|
||||
"description": "Generic change action"
|
||||
},
|
||||
"wizardOverviewDescription": "This app combines MeshCore messaging, SAR field updates, mapping, and device tools in one place.",
|
||||
"@wizardOverviewDescription": {
|
||||
"description": "Updated onboarding overview description"
|
||||
},
|
||||
"wizardOverviewFeature1": "Send direct, room, and channel messages from the main Messages tab.",
|
||||
"@wizardOverviewFeature1": {
|
||||
"description": "Updated onboarding overview feature 1"
|
||||
},
|
||||
"wizardOverviewFeature2": "Share SAR markers, map drawings, voice clips, and images over the mesh.",
|
||||
"@wizardOverviewFeature2": {
|
||||
"description": "Updated onboarding overview feature 2"
|
||||
},
|
||||
"wizardOverviewFeature3": "Connect over BLE or TCP, then manage the companion radio from inside the app.",
|
||||
"@wizardOverviewFeature3": {
|
||||
"description": "Updated onboarding overview feature 3"
|
||||
},
|
||||
"wizardMessagingTitle": "Messaging and Field Reports",
|
||||
"@wizardMessagingTitle": {
|
||||
"description": "Updated onboarding messaging page title"
|
||||
},
|
||||
"wizardMessagingDescription": "Messages are more than plain text here. The app already supports several operational payloads and transfer workflows.",
|
||||
"@wizardMessagingDescription": {
|
||||
"description": "Updated onboarding messaging page description"
|
||||
},
|
||||
"wizardMessagingFeature1": "Send direct messages, room posts, and channel traffic from one composer.",
|
||||
"@wizardMessagingFeature1": {
|
||||
"description": "Updated onboarding messaging feature 1"
|
||||
},
|
||||
"wizardMessagingFeature2": "Create SAR updates and reusable SAR templates for common field reports.",
|
||||
"@wizardMessagingFeature2": {
|
||||
"description": "Updated onboarding messaging feature 2"
|
||||
},
|
||||
"wizardMessagingFeature3": "Transfer voice sessions and images, with progress and airtime estimates in the UI.",
|
||||
"@wizardMessagingFeature3": {
|
||||
"description": "Updated onboarding messaging feature 3"
|
||||
},
|
||||
"wizardConnectDeviceTitle": "Connect device",
|
||||
"@wizardConnectDeviceTitle": {
|
||||
"description": "Onboarding connect device page title"
|
||||
},
|
||||
"wizardConnectDeviceDescription": "Connect your MeshCore radio, choose a name, and apply a radio preset before continuing.",
|
||||
"@wizardConnectDeviceDescription": {
|
||||
"description": "Onboarding connect device page description"
|
||||
},
|
||||
"wizardSetupBadge": "Setup",
|
||||
"@wizardSetupBadge": {
|
||||
"description": "Onboarding setup badge label"
|
||||
},
|
||||
"wizardOverviewBadge": "Overview",
|
||||
"@wizardOverviewBadge": {
|
||||
"description": "Onboarding overview badge label"
|
||||
},
|
||||
"wizardConnectedToDevice": "Connected to {deviceName}",
|
||||
"@wizardConnectedToDevice": {
|
||||
"description": "Onboarding connection status when connected",
|
||||
"placeholders": {
|
||||
"deviceName": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"wizardNoDeviceConnected": "No device connected yet",
|
||||
"@wizardNoDeviceConnected": {
|
||||
"description": "Onboarding connection status when disconnected"
|
||||
},
|
||||
"wizardSkipForNow": "Skip for now",
|
||||
"@wizardSkipForNow": {
|
||||
"description": "Onboarding skip current setup step action"
|
||||
},
|
||||
"wizardDeviceNameLabel": "Device name",
|
||||
"@wizardDeviceNameLabel": {
|
||||
"description": "Onboarding device name field label"
|
||||
},
|
||||
"wizardDeviceNameHelp": "This name is advertised to other MeshCore users.",
|
||||
"@wizardDeviceNameHelp": {
|
||||
"description": "Onboarding device name field helper text"
|
||||
},
|
||||
"wizardConfigRegionLabel": "Config region",
|
||||
"@wizardConfigRegionLabel": {
|
||||
"description": "Onboarding config region field label"
|
||||
},
|
||||
"wizardConfigRegionHelp": "Uses the full official MeshCore preset list. Default is EU/UK (Narrow).",
|
||||
"@wizardConfigRegionHelp": {
|
||||
"description": "Onboarding config region helper text"
|
||||
},
|
||||
"wizardPresetNote1": "Make sure the selected preset matches your local radio regulations.",
|
||||
"@wizardPresetNote1": {
|
||||
"description": "Onboarding preset note 1"
|
||||
},
|
||||
"wizardPresetNote2": "The list matches the official MeshCore config tool preset feed.",
|
||||
"@wizardPresetNote2": {
|
||||
"description": "Onboarding preset note 2"
|
||||
},
|
||||
"wizardPresetNote3": "EU/UK (Narrow) stays selected by default for onboarding.",
|
||||
"@wizardPresetNote3": {
|
||||
"description": "Onboarding preset note 3"
|
||||
},
|
||||
"wizardSaving": "Saving...",
|
||||
"@wizardSaving": {
|
||||
"description": "Onboarding saving button label"
|
||||
},
|
||||
"wizardSaveAndContinue": "Save and continue",
|
||||
"@wizardSaveAndContinue": {
|
||||
"description": "Onboarding save and continue button label"
|
||||
},
|
||||
"wizardEnterDeviceName": "Enter a device name before continuing.",
|
||||
"@wizardEnterDeviceName": {
|
||||
"description": "Onboarding validation when device name is missing"
|
||||
},
|
||||
"wizardDeviceSetupSaved": "Saved {deviceName} with {presetName}.",
|
||||
"@wizardDeviceSetupSaved": {
|
||||
"description": "Onboarding success message after saving device setup",
|
||||
"placeholders": {
|
||||
"deviceName": {
|
||||
"type": "String"
|
||||
},
|
||||
"presetName": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"wizardNetworkTitle": "Contacts, Rooms, and Repeaters",
|
||||
"@wizardNetworkTitle": {
|
||||
"description": "Updated onboarding network page title"
|
||||
},
|
||||
"wizardNetworkDescription": "The Contacts tab organizes the network you discover and the routes you learn over time.",
|
||||
"@wizardNetworkDescription": {
|
||||
"description": "Updated onboarding network page description"
|
||||
},
|
||||
"wizardNetworkFeature1": "Review team members, repeaters, rooms, channels, and pending adverts in one list.",
|
||||
"@wizardNetworkFeature1": {
|
||||
"description": "Updated onboarding network feature 1"
|
||||
},
|
||||
"wizardNetworkFeature2": "Use smart ping, room login, learned paths, and route reset tools when connectivity gets messy.",
|
||||
"@wizardNetworkFeature2": {
|
||||
"description": "Updated onboarding network feature 2"
|
||||
},
|
||||
"wizardNetworkFeature3": "Create channels and manage network destinations without leaving the app.",
|
||||
"@wizardNetworkFeature3": {
|
||||
"description": "Updated onboarding network feature 3"
|
||||
},
|
||||
"wizardMapOpsTitle": "Map, Trails, and Shared Geometry",
|
||||
"@wizardMapOpsTitle": {
|
||||
"description": "Updated onboarding map page title"
|
||||
},
|
||||
"wizardMapOpsDescription": "The app map is tied directly into messaging, tracking, and SAR overlays instead of being a separate viewer.",
|
||||
"@wizardMapOpsDescription": {
|
||||
"description": "Updated onboarding map page description"
|
||||
},
|
||||
"wizardMapOpsFeature1": "Track your own position, teammate locations, and movement trails on the map.",
|
||||
"@wizardMapOpsFeature1": {
|
||||
"description": "Updated onboarding map feature 1"
|
||||
},
|
||||
"wizardMapOpsFeature2": "Open drawings from messages, preview them inline, and remove them from the map when needed.",
|
||||
"@wizardMapOpsFeature2": {
|
||||
"description": "Updated onboarding map feature 2"
|
||||
},
|
||||
"wizardMapOpsFeature3": "Use repeater map views and shared overlays to understand network reach in the field.",
|
||||
"@wizardMapOpsFeature3": {
|
||||
"description": "Updated onboarding map feature 3"
|
||||
},
|
||||
"wizardToolsTitle": "Tools Beyond Messaging",
|
||||
"@wizardToolsTitle": {
|
||||
"description": "Updated onboarding tools page title"
|
||||
},
|
||||
"wizardToolsDescription": "There is more here than the four main tabs. The app also includes configuration, diagnostics, and optional sensor workflows.",
|
||||
"@wizardToolsDescription": {
|
||||
"description": "Updated onboarding tools page description"
|
||||
},
|
||||
"wizardToolsFeature1": "Open device config to change radio settings, telemetry, TX power, and companion details.",
|
||||
"@wizardToolsFeature1": {
|
||||
"description": "Updated onboarding tools feature 1"
|
||||
},
|
||||
"wizardToolsFeature2": "Enable the Sensors tab when you want watched sensor dashboards and quick refresh actions.",
|
||||
"@wizardToolsFeature2": {
|
||||
"description": "Updated onboarding tools feature 2"
|
||||
},
|
||||
"wizardToolsFeature3": "Use packet logs, spectrum scan, and developer diagnostics when troubleshooting the mesh.",
|
||||
"@wizardToolsFeature3": {
|
||||
"description": "Updated onboarding tools feature 3"
|
||||
},
|
||||
"contactInSensors": "In Sensors",
|
||||
"@contactInSensors": {
|
||||
"description": "Contact actions label when already in sensors"
|
||||
},
|
||||
"contactAddToSensors": "Add to Sensors",
|
||||
"@contactAddToSensors": {
|
||||
"description": "Contact actions label to add to sensors"
|
||||
},
|
||||
"contactSetPath": "Set path",
|
||||
"@contactSetPath": {
|
||||
"description": "Contact actions label to set route path"
|
||||
},
|
||||
"contactAddedToSensors": "{contactName} added to Sensors",
|
||||
"@contactAddedToSensors": {
|
||||
"description": "Snackbar after adding contact to sensors",
|
||||
"placeholders": {
|
||||
"contactName": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"contactFailedToClearRoute": "Failed to clear route: {error}",
|
||||
"@contactFailedToClearRoute": {
|
||||
"description": "Error when clearing a contact route fails",
|
||||
"placeholders": {
|
||||
"error": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"contactRouteCleared": "Route cleared",
|
||||
"@contactRouteCleared": {
|
||||
"description": "Snackbar after route is cleared"
|
||||
},
|
||||
"contactRouteSet": "Route set: {route}",
|
||||
"@contactRouteSet": {
|
||||
"description": "Snackbar after route is set",
|
||||
"placeholders": {
|
||||
"route": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"contactFailedToSetRoute": "Failed to set route: {error}",
|
||||
"@contactFailedToSetRoute": {
|
||||
"description": "Error when setting a contact route fails",
|
||||
"placeholders": {
|
||||
"error": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"rssi": "RSSI",
|
||||
"@rssi": {
|
||||
"description": "Received signal strength indicator"
|
||||
},
|
||||
"snr": "SNR",
|
||||
"@snr": {
|
||||
"description": "Signal-to-noise ratio"
|
||||
},
|
||||
"ackTimeout": "ACK timeout",
|
||||
"opcode": "Opcode",
|
||||
"payload": "Payload",
|
||||
"hops": "Hops",
|
||||
"hashSize": "Hash size",
|
||||
"pathBytes": "Path bytes",
|
||||
"selectedPath": "Selected path",
|
||||
"estimatedTx": "Estimated tx",
|
||||
"senderToReceipt": "Sender to receipt",
|
||||
"receivedCopies": "Received copies",
|
||||
"retryCause": "Retry cause",
|
||||
"retryMode": "Retry mode",
|
||||
"retryResult": "Retry result",
|
||||
"lastRetry": "Last retry",
|
||||
"rxPackets": "RX packets",
|
||||
"mesh": "Mesh",
|
||||
"rate": "Rate",
|
||||
"window": "Window",
|
||||
"posttxDelay": "Post-tx delay",
|
||||
"bandpass": "Band-pass",
|
||||
"bandpassFilterVoice": "Band-pass filter voice",
|
||||
"frequency": "Frequency",
|
||||
"australia": "Australia",
|
||||
"australiaNarrow": "Australia (Narrow)",
|
||||
"australiaQld": "Australia: QLD",
|
||||
"australiaSaWa": "Australia: SA, WA",
|
||||
"newZealand": "New Zealand",
|
||||
"newZealandNarrow": "New Zealand (Narrow)",
|
||||
"switzerland": "Switzerland",
|
||||
"portugal433": "Portugal 433",
|
||||
"portugal868": "Portugal 868",
|
||||
"czechRepublicNarrow": "Czech Republic (Narrow)",
|
||||
"eu433mhzLongRange": "EU 433MHz (Long Range)",
|
||||
"euukDeprecated": "EU/UK (Deprecated)",
|
||||
"euukNarrow": "EU/UK (Narrow)",
|
||||
"usacanadaRecommended": "USA/Canada (Recommended)",
|
||||
"vietnamDeprecated": "Vietnam (Deprecated)",
|
||||
"vietnamNarrow": "Vietnam (Narrow)",
|
||||
"active": "Active",
|
||||
"addContact": "Add Contact",
|
||||
"all": "All",
|
||||
"autoResolve": "Auto resolve",
|
||||
"clearAllLabel": "Clear all",
|
||||
"clearRelays": "Clear relays",
|
||||
"clearFilters": "Clear filters",
|
||||
"clearRoute": "Clear Route",
|
||||
"clearMessages": "Clear Messages",
|
||||
"clearScale": "Clear scale",
|
||||
"clearDiscoveries": "Clear discoveries",
|
||||
"clearOnlineTraceDatabase": "Clear online trace database",
|
||||
"clearAllChannels": "Clear all channels",
|
||||
"clearAllContacts": "Clear all contacts",
|
||||
"clearChannels": "Clear channels",
|
||||
"clearContacts": "Clear contacts",
|
||||
"clearPathOnMaxRetry": "Clear path on max retry",
|
||||
"create": "Create",
|
||||
"custom": "Custom",
|
||||
"defaultValue": "Default",
|
||||
"duplicate": "Duplicate",
|
||||
"editName": "Edit name",
|
||||
"open": "Open",
|
||||
"paste": "Paste",
|
||||
"preview": "Preview",
|
||||
"remove": "Remove",
|
||||
"rename": "Rename",
|
||||
"resolveAll": "Resolve all",
|
||||
"send": "Send",
|
||||
"sendAnyway": "Send anyway",
|
||||
"share": "Share",
|
||||
"shareContact": "Share Contact",
|
||||
"trace": "Trace",
|
||||
"use": "Use",
|
||||
"useSelectedFrequency": "Use selected frequency",
|
||||
"discovery": "Discovery",
|
||||
"discoverRepeaters": "Discover repeaters",
|
||||
"discoverSensors": "Discover sensors",
|
||||
"repeaterDiscoverySent": "Repeater discovery sent",
|
||||
"sensorDiscoverySent": "Sensor discovery sent",
|
||||
"clearedPendingDiscoveries": "Cleared pending discoveries.",
|
||||
"autoDiscovery": "Auto discovery",
|
||||
"enableAutomaticAdding": "Enable automatic adding",
|
||||
"autoaddRepeaters": "Auto-add repeaters",
|
||||
"autoaddRoomServers": "Auto-add room servers",
|
||||
"autoaddSensors": "Auto-add sensors",
|
||||
"autoaddUsers": "Auto-add users",
|
||||
"overwriteOldestWhenFull": "Overwrite oldest when full",
|
||||
"storage": "Storage",
|
||||
"dangerZone": "Danger zone",
|
||||
"profiles": "Profiles",
|
||||
"favourites": "Favourites",
|
||||
"sensors": "Sensors",
|
||||
"others": "Others",
|
||||
"gpsModule": "GPS Module",
|
||||
"liveTraffic": "Live Traffic",
|
||||
"repeatersMap": "Repeaters Map",
|
||||
"spectrumScan": "Spectrum Scan",
|
||||
"blePacketLogs": "BLE Packet Logs",
|
||||
"onlineTraceDatabase": "Online trace database",
|
||||
"routePathByteSize": "Route path byte size",
|
||||
"messageNotifications": "Message notifications",
|
||||
"sarAlerts": "SAR alerts",
|
||||
"discoveryNotifications": "Discovery notifications",
|
||||
"updateNotifications": "Update notifications",
|
||||
"muteWhileAppIsOpen": "Mute while app is open",
|
||||
"disableContacts": "Disable Contacts",
|
||||
"enableSensorsTab": "Enable Sensors tab",
|
||||
"enableProfiles": "Enable Profiles",
|
||||
"autoRouteRotation": "Auto route rotation",
|
||||
"nearestRepeaterFallback": "Nearest repeater fallback",
|
||||
"deleteAllStoredMessageHistory": "Delete all stored message history",
|
||||
"messageFontSize": "Message font size",
|
||||
"rotateMapWithHeading": "Rotate map with heading",
|
||||
"showMapDebugInfo": "Show map debug info",
|
||||
"openMapInFullscreen": "Open map in fullscreen",
|
||||
"showSarMarkersLabel": "Show SAR markers",
|
||||
"displaySarMarkersOnTheMainMap": "Display SAR markers on the main map",
|
||||
"showAllContactTrailsLabel": "Show all contact trails",
|
||||
"hideRepeatersOnMap": "Hide repeaters on map",
|
||||
"setMapScale": "Set map scale",
|
||||
"customMapScaleSaved": "Custom map scale saved",
|
||||
"voiceBitrate": "Voice bitrate",
|
||||
"voiceCompressor": "Voice compressor",
|
||||
"balancesQuietAndLoudSpeechLevels": "Balances quiet and loud speech levels",
|
||||
"voiceLimiter": "Voice limiter",
|
||||
"preventsClippingPeaksBeforeEncoding": "Prevents clipping peaks before encoding",
|
||||
"micAutoGain": "Mic auto gain",
|
||||
"letsTheRecorderAdjustInputLevel": "Lets the recorder adjust input level",
|
||||
"echoCancellation": "Echo cancellation",
|
||||
"noiseSuppression": "Noise suppression",
|
||||
"trimSilenceInVoiceMessages": "Trim silence in voice messages",
|
||||
"compressor": "Compressor",
|
||||
"limiter": "Limiter",
|
||||
"autoGain": "Auto gain",
|
||||
"echoCancel": "Echo cancel",
|
||||
"noiseSuppress": "Noise suppress",
|
||||
"silenceTrim": "Silence trim",
|
||||
"maxImageSize": "Max image size",
|
||||
"imageCompression": "Image compression",
|
||||
"grayscale": "Grayscale",
|
||||
"ultraMode": "Ultra mode",
|
||||
"fastPrivateGpsUpdates": "Fast private GPS updates",
|
||||
"movementThreshold": "Movement threshold",
|
||||
"fastGpsMovementThreshold": "Fast GPS movement threshold",
|
||||
"fastGpsActiveuseInterval": "Fast GPS active-use interval",
|
||||
"activeuseUpdateInterval": "Active-use update interval",
|
||||
"repeatNearbyTraffic": "Repeat nearby traffic",
|
||||
"relayThroughRepeatersAcrossTheMesh": "Relay through repeaters across the mesh",
|
||||
"nearbyOnlyWithoutRepeaterFlooding": "Nearby only, without repeater flooding",
|
||||
"multihop": "Multi-hop",
|
||||
"createProfile": "Create Profile",
|
||||
"renameProfile": "Rename Profile",
|
||||
"newProfile": "New Profile",
|
||||
"manageProfiles": "Manage profiles",
|
||||
"enableProfilesToStartManagingThem": "Enable profiles to start managing them.",
|
||||
"openMessage": "Open message",
|
||||
"jumpToTheRelatedSarMessage": "Jump to the related SAR message",
|
||||
"removeSarMarker": "Remove SAR marker",
|
||||
"pleaseSelectADestinationToSendSarMarker": "Please select a destination to send SAR marker",
|
||||
"sarMarkerBroadcastToPublicChannel": "SAR marker broadcast to public channel",
|
||||
"sarMarkerSentToRoom": "SAR marker sent to room",
|
||||
"loadFromGallery": "Load from gallery",
|
||||
"replaceImage": "Replace image",
|
||||
"selectFromGallery": "Select from gallery",
|
||||
"team": "Team",
|
||||
"found": "Found",
|
||||
"staging": "Staging",
|
||||
"object": "Object",
|
||||
"quiet": "Quiet",
|
||||
"moderate": "Moderate",
|
||||
"busy": "Busy",
|
||||
"spectrumScanReturnedNoCandidateFrequencies": "Spectrum scan returned no candidate frequencies",
|
||||
"searchMessages": "Search messages",
|
||||
"sendImageFromGallery": "Send image from gallery",
|
||||
"takePhoto": "Take photo",
|
||||
"dmOnly": "DM only",
|
||||
"allMessages": "All messages",
|
||||
"sendToPublicChannel": "Send to Public Channel?",
|
||||
"selectMarkerTypeAndDestination": "Select marker type and destination",
|
||||
"noDestinationsAvailableLabel": "No destinations available",
|
||||
"image": "Image",
|
||||
"format": "Format",
|
||||
"dimensions": "Dimensions",
|
||||
"segments": "Segments",
|
||||
"transfers": "Transfers",
|
||||
"downloadedBy": "Downloaded by",
|
||||
"saveDiscoverySettings": "Save discovery settings",
|
||||
"savePublicInfo": "Save public info",
|
||||
"saveRadioSettings": "Save radio settings",
|
||||
"savePath": "Save Path",
|
||||
"wipeDeviceData": "Wipe device data",
|
||||
"wipeDevice": "Wipe device",
|
||||
"destructiveDeviceActions": "Destructive device actions.",
|
||||
"chooseAPresetOrFinetuneCustomRadioSettings": "Choose a preset or fine-tune custom radio settings.",
|
||||
"chooseTheNameAndLocationThisDeviceShares": "Choose the name and location this device shares.",
|
||||
"availableSpaceOnThisDevice": "Available space on this device.",
|
||||
"used": "Used",
|
||||
"total": "Total",
|
||||
"renameValue": "Rename value",
|
||||
"customizeFields": "Customize fields",
|
||||
"livePreview": "Live preview",
|
||||
"refreshSchedule": "Refresh schedule",
|
||||
"noResponse": "No response",
|
||||
"refreshing": "Refreshing",
|
||||
"unavailable": "Unavailable",
|
||||
"pickARelayOrNodeToWatchInSensors": "Pick a relay or node to watch in Sensors.",
|
||||
"publicKeyLabel": "Public key",
|
||||
"alreadyInContacts": "Already in contacts",
|
||||
"connectToADeviceBeforeAddingContacts": "Connect to a device before adding contacts",
|
||||
"fromContacts": "From contacts",
|
||||
"onlineOnly": "Online only",
|
||||
"inBoth": "In both",
|
||||
"source": "Source",
|
||||
"manualRouteEdit": "Manual route edit",
|
||||
"observedMeshRoute": "Observed mesh route",
|
||||
"allMessagesCleared": "All messages cleared",
|
||||
"onlineTraceDatabaseCleared": "Online trace database cleared",
|
||||
"packetLogsCleared": "Packet logs cleared",
|
||||
"hexDataCopiedToClipboard": "Hex data copied to clipboard",
|
||||
"developerModeEnabled": "Developer mode enabled",
|
||||
"developerModeDisabled": "Developer mode disabled",
|
||||
"clipboardIsEmpty": "Clipboard is empty",
|
||||
"contactImported": "Contact imported",
|
||||
"contactLinkCopiedToClipboard": "Contact link copied to clipboard",
|
||||
"failedToExportContact": "Failed to export contact",
|
||||
"noLogsToExport": "No logs to export",
|
||||
"exportAsCsv": "Export as CSV",
|
||||
"exportAsText": "Export as Text",
|
||||
"receivedRfc3339": "Received (RFC3339)",
|
||||
"buildTime": "Build Time",
|
||||
"downloadUrlNotAvailable": "Download URL not available",
|
||||
"cannotOpenDownloadUrl": "Cannot open download URL",
|
||||
"updateCheckIsOnlyAvailableOnAndroid": "Update check is only available on Android",
|
||||
"youAreRunningTheLatestVersion": "You are running the latest version",
|
||||
"updateAvailableButDownloadUrlNotFound": "Update available but download URL not found",
|
||||
"startTictactoe": "Start Tic-Tac-Toe",
|
||||
"tictactoeUnavailable": "Tic-Tac-Toe unavailable",
|
||||
"tictactoeOpponentUnknown": "Tic-Tac-Toe: opponent unknown",
|
||||
"tictactoeWaitingForStart": "Tic-Tac-Toe: waiting for start",
|
||||
"acceptsShareLinks": "Accepts share links",
|
||||
"supportsRawHex": "Supports raw hex",
|
||||
"clipboardfriendly": "Clipboard-friendly",
|
||||
"captured": "Captured",
|
||||
"size": "Size",
|
||||
"noCustomChannelsToClear": "No custom channels to clear.",
|
||||
"noDeviceContactsToClear": "No device contacts to clear."
|
||||
}
|
||||
|
||||
@@ -1586,5 +1586,304 @@
|
||||
"rawDump": "Volcado sin procesar",
|
||||
"cannotRetryMissingRecipient": "No se puede reintentar: falta información del destinatario",
|
||||
"voiceUnavailable": "Voz no disponible en este momento",
|
||||
"requestingVoice": "Solicitando voz"
|
||||
"requestingVoice": "Solicitando voz",
|
||||
"device": "dispositivo",
|
||||
"change": "Cambiar",
|
||||
"wizardOverviewDescription": "Esta aplicación reúne mensajería MeshCore, actualizaciones SAR de campo, mapas y herramientas del dispositivo en un solo lugar.",
|
||||
"wizardOverviewFeature1": "Envía mensajes directos, publicaciones en salas y mensajes de canal desde la pestaña principal Mensajes.",
|
||||
"wizardOverviewFeature2": "Comparte marcadores SAR, dibujos del mapa, clips de voz e imágenes a través de la malla.",
|
||||
"wizardOverviewFeature3": "Conéctate por BLE o TCP y luego gestiona la radio complementaria desde la aplicación.",
|
||||
"wizardMessagingTitle": "Mensajería e informes de campo",
|
||||
"wizardMessagingDescription": "Aquí los mensajes son más que texto plano. La aplicación ya admite varias cargas operativas y flujos de transferencia.",
|
||||
"wizardMessagingFeature1": "Envía mensajes directos, publicaciones en salas y tráfico de canal desde un solo editor.",
|
||||
"wizardMessagingFeature2": "Crea actualizaciones SAR y plantillas SAR reutilizables para informes de campo habituales.",
|
||||
"wizardMessagingFeature3": "Transfiere sesiones de voz e imágenes con progreso y estimaciones de tiempo de aire en la interfaz.",
|
||||
"wizardConnectDeviceTitle": "Conectar dispositivo",
|
||||
"wizardConnectDeviceDescription": "Conecta tu radio MeshCore, elige un nombre y aplica un ajuste predefinido antes de continuar.",
|
||||
"wizardSetupBadge": "Configuración",
|
||||
"wizardOverviewBadge": "Resumen",
|
||||
"wizardConnectedToDevice": "Conectado a {deviceName}",
|
||||
"wizardNoDeviceConnected": "Aún no hay ningún dispositivo conectado",
|
||||
"wizardSkipForNow": "Omitir por ahora",
|
||||
"wizardDeviceNameLabel": "Nombre del dispositivo",
|
||||
"wizardDeviceNameHelp": "Este nombre se anuncia a otros usuarios de MeshCore.",
|
||||
"wizardConfigRegionLabel": "Región de configuración",
|
||||
"wizardConfigRegionHelp": "Usa la lista oficial completa de ajustes predefinidos de MeshCore. El valor predeterminado es EU/UK (Narrow).",
|
||||
"wizardPresetNote1": "Asegúrate de que el ajuste seleccionado coincida con la normativa local de radio.",
|
||||
"wizardPresetNote2": "La lista coincide con la fuente oficial de ajustes predefinidos de la herramienta de configuración de MeshCore.",
|
||||
"wizardPresetNote3": "EU/UK (Narrow) sigue seleccionado por defecto durante la incorporación.",
|
||||
"wizardSaving": "Guardando...",
|
||||
"wizardSaveAndContinue": "Guardar y continuar",
|
||||
"wizardEnterDeviceName": "Introduce un nombre de dispositivo antes de continuar.",
|
||||
"wizardDeviceSetupSaved": "Se guardó {deviceName} con {presetName}.",
|
||||
"wizardNetworkTitle": "Contactos, salas y repetidores",
|
||||
"wizardNetworkDescription": "La pestaña Contactos organiza la red que descubres y las rutas que aprendes con el tiempo.",
|
||||
"wizardNetworkFeature1": "Revisa miembros del equipo, repetidores, salas, canales y anuncios pendientes en una sola lista.",
|
||||
"wizardNetworkFeature2": "Usa ping inteligente, acceso a salas, rutas aprendidas y herramientas de reinicio de ruta cuando la conectividad se complique.",
|
||||
"wizardNetworkFeature3": "Crea canales y gestiona destinos de red sin salir de la aplicación.",
|
||||
"wizardMapOpsTitle": "Mapa, rastros y geometría compartida",
|
||||
"wizardMapOpsDescription": "El mapa de la aplicación está vinculado directamente a la mensajería, el seguimiento y las superposiciones SAR en lugar de ser un visor aparte.",
|
||||
"wizardMapOpsFeature1": "Sigue tu propia posición, las ubicaciones de tus compañeros y los rastros de movimiento en el mapa.",
|
||||
"wizardMapOpsFeature2": "Abre dibujos desde mensajes, prévisualos en línea y elimínalos del mapa cuando sea necesario.",
|
||||
"wizardMapOpsFeature3": "Usa vistas de mapa de repetidores y superposiciones compartidas para comprender el alcance de la red sobre el terreno.",
|
||||
"wizardToolsTitle": "Herramientas más allá de la mensajería",
|
||||
"wizardToolsDescription": "Aquí hay más que las cuatro pestañas principales. La aplicación también incluye configuración, diagnóstico y flujos opcionales de sensores.",
|
||||
"wizardToolsFeature1": "Abre la configuración del dispositivo para cambiar ajustes de radio, telemetría, potencia TX y detalles del equipo complementario.",
|
||||
"wizardToolsFeature2": "Activa la pestaña Sensores cuando quieras paneles de sensores vigilados y acciones de actualización rápida.",
|
||||
"wizardToolsFeature3": "Usa registros de paquetes, escaneo de espectro y diagnósticos de desarrollador al solucionar problemas de la malla.",
|
||||
"contactInSensors": "En Sensores",
|
||||
"contactAddToSensors": "Añadir a Sensores",
|
||||
"contactSetPath": "Definir ruta",
|
||||
"contactAddedToSensors": "{contactName} se añadió a Sensores",
|
||||
"contactFailedToClearRoute": "No se pudo borrar la ruta: {error}",
|
||||
"contactRouteCleared": "Ruta borrada",
|
||||
"contactRouteSet": "Ruta establecida: {route}",
|
||||
"contactFailedToSetRoute": "No se pudo establecer la ruta: {error}",
|
||||
"rssi": "RSSI",
|
||||
"snr": "SNR",
|
||||
"ackTimeout": "Tiempo de espera ACK",
|
||||
"opcode": "Código de operación",
|
||||
"payload": "Carga útil",
|
||||
"hops": "Saltos",
|
||||
"hashSize": "Tamaño del hash",
|
||||
"pathBytes": "Bytes de ruta",
|
||||
"selectedPath": "Ruta seleccionada",
|
||||
"estimatedTx": "Transmisión estimada",
|
||||
"senderToReceipt": "Del remitente al recibo",
|
||||
"receivedCopies": "Copias recibidas",
|
||||
"retryCause": "Causa del reintento",
|
||||
"retryMode": "Modo de reintento",
|
||||
"retryResult": "Resultado del reintento",
|
||||
"lastRetry": "Último reintento",
|
||||
"rxPackets": "Paquetes RX",
|
||||
"mesh": "Mesh",
|
||||
"rate": "Velocidad",
|
||||
"window": "Ventana",
|
||||
"posttxDelay": "Retardo post-tx",
|
||||
"bandpass": "Paso de banda",
|
||||
"bandpassFilterVoice": "Filtro de paso de banda de voz",
|
||||
"frequency": "Frecuencia",
|
||||
"australia": "Australia",
|
||||
"australiaNarrow": "Australia (Estrecho)",
|
||||
"australiaQld": "Australia: QLD",
|
||||
"australiaSaWa": "Australia: SA, WA",
|
||||
"newZealand": "Nueva Zelanda",
|
||||
"newZealandNarrow": "Nueva Zelanda (Estrecho)",
|
||||
"switzerland": "Suiza",
|
||||
"portugal433": "Portugal 433",
|
||||
"portugal868": "Portugal 868",
|
||||
"czechRepublicNarrow": "República Checa (Estrecho)",
|
||||
"eu433mhzLongRange": "EU 433MHz (Largo Alcance)",
|
||||
"euukDeprecated": "UE/RU (Obsoleto)",
|
||||
"euukNarrow": "UE/RU (Estrecho)",
|
||||
"usacanadaRecommended": "EE.UU./Canadá (Recomendado)",
|
||||
"vietnamDeprecated": "Vietnam (Obsoleto)",
|
||||
"vietnamNarrow": "Vietnam (Estrecho)",
|
||||
"active": "Activo",
|
||||
"addContact": "Añadir contacto",
|
||||
"all": "Todo",
|
||||
"autoResolve": "Resolver automáticamente",
|
||||
"clearAllLabel": "Borrar todo",
|
||||
"clearRelays": "Borrar repetidores",
|
||||
"clearFilters": "Borrar filtros",
|
||||
"clearRoute": "Borrar ruta",
|
||||
"clearMessages": "Borrar mensajes",
|
||||
"clearScale": "Borrar escala",
|
||||
"clearDiscoveries": "Borrar descubrimientos",
|
||||
"clearOnlineTraceDatabase": "Borrar base de datos de trazas",
|
||||
"clearAllChannels": "Borrar todos los canales",
|
||||
"clearAllContacts": "Borrar todos los contactos",
|
||||
"clearChannels": "Borrar canales",
|
||||
"clearContacts": "Borrar contactos",
|
||||
"clearPathOnMaxRetry": "Borrar ruta en máximo reintento",
|
||||
"create": "Crear",
|
||||
"custom": "Personalizado",
|
||||
"defaultValue": "Predeterminado",
|
||||
"duplicate": "Duplicar",
|
||||
"editName": "Editar nombre",
|
||||
"open": "Abrir",
|
||||
"paste": "Pegar",
|
||||
"preview": "Vista previa",
|
||||
"remove": "Eliminar",
|
||||
"rename": "Renombrar",
|
||||
"resolveAll": "Resolver todo",
|
||||
"send": "Enviar",
|
||||
"sendAnyway": "Enviar de todos modos",
|
||||
"share": "Compartir",
|
||||
"shareContact": "Compartir contacto",
|
||||
"trace": "Traza",
|
||||
"use": "Usar",
|
||||
"useSelectedFrequency": "Usar frecuencia seleccionada",
|
||||
"discovery": "Descubrimiento",
|
||||
"discoverRepeaters": "Descubrir repetidores",
|
||||
"discoverSensors": "Descubrir sensores",
|
||||
"repeaterDiscoverySent": "Descubrimiento de repetidores enviado",
|
||||
"sensorDiscoverySent": "Descubrimiento de sensores enviado",
|
||||
"clearedPendingDiscoveries": "Descubrimientos pendientes borrados.",
|
||||
"autoDiscovery": "Descubrimiento automático",
|
||||
"enableAutomaticAdding": "Habilitar adición automática",
|
||||
"autoaddRepeaters": "Añadir repetidores automáticamente",
|
||||
"autoaddRoomServers": "Añadir servidores de sala automáticamente",
|
||||
"autoaddSensors": "Añadir sensores automáticamente",
|
||||
"autoaddUsers": "Añadir usuarios automáticamente",
|
||||
"overwriteOldestWhenFull": "Sobrescribir más antiguos cuando esté lleno",
|
||||
"storage": "Almacenamiento",
|
||||
"dangerZone": "Zona de peligro",
|
||||
"profiles": "Perfiles",
|
||||
"favourites": "Favoritos",
|
||||
"sensors": "Sensores",
|
||||
"others": "Otros",
|
||||
"gpsModule": "Módulo GPS",
|
||||
"liveTraffic": "Tráfico en vivo",
|
||||
"repeatersMap": "Mapa de repetidores",
|
||||
"spectrumScan": "Escaneo de espectro",
|
||||
"blePacketLogs": "Registros de paquetes BLE",
|
||||
"onlineTraceDatabase": "Base de datos de trazas",
|
||||
"routePathByteSize": "Tamaño en bytes de la ruta",
|
||||
"messageNotifications": "Notificaciones de mensajes",
|
||||
"sarAlerts": "Alertas SAR",
|
||||
"discoveryNotifications": "Notificaciones de descubrimiento",
|
||||
"updateNotifications": "Notificaciones de actualización",
|
||||
"muteWhileAppIsOpen": "Silenciar con la app abierta",
|
||||
"disableContacts": "Desactivar contactos",
|
||||
"enableSensorsTab": "Habilitar pestaña Sensores",
|
||||
"enableProfiles": "Habilitar perfiles",
|
||||
"autoRouteRotation": "Rotación automática de ruta",
|
||||
"nearestRepeaterFallback": "Repetidor más cercano como reserva",
|
||||
"deleteAllStoredMessageHistory": "Eliminar todo el historial de mensajes",
|
||||
"messageFontSize": "Tamaño de fuente de mensajes",
|
||||
"rotateMapWithHeading": "Rotar mapa con rumbo",
|
||||
"showMapDebugInfo": "Mostrar info de depuración del mapa",
|
||||
"openMapInFullscreen": "Abrir mapa en pantalla completa",
|
||||
"showSarMarkersLabel": "Mostrar marcadores SAR",
|
||||
"displaySarMarkersOnTheMainMap": "Mostrar marcadores SAR en el mapa principal",
|
||||
"showAllContactTrailsLabel": "Mostrar todos los rastros de contactos",
|
||||
"hideRepeatersOnMap": "Ocultar repetidores en el mapa",
|
||||
"setMapScale": "Establecer escala del mapa",
|
||||
"customMapScaleSaved": "Escala de mapa personalizada guardada",
|
||||
"voiceBitrate": "Tasa de bits de voz",
|
||||
"voiceCompressor": "Compresor de voz",
|
||||
"balancesQuietAndLoudSpeechLevels": "Equilibra niveles de habla suaves y altos",
|
||||
"voiceLimiter": "Limitador de voz",
|
||||
"preventsClippingPeaksBeforeEncoding": "Previene recorte de picos antes de codificar",
|
||||
"micAutoGain": "Ganancia automática del micrófono",
|
||||
"letsTheRecorderAdjustInputLevel": "Permite al grabador ajustar el nivel de entrada",
|
||||
"echoCancellation": "Cancelación de eco",
|
||||
"noiseSuppression": "Supresión de ruido",
|
||||
"trimSilenceInVoiceMessages": "Recortar silencio en mensajes de voz",
|
||||
"compressor": "Compresor",
|
||||
"limiter": "Limitador",
|
||||
"autoGain": "Ganancia automática",
|
||||
"echoCancel": "Eco",
|
||||
"noiseSuppress": "Ruido",
|
||||
"silenceTrim": "Silencio",
|
||||
"maxImageSize": "Tamaño máximo de imagen",
|
||||
"imageCompression": "Compresión de imagen",
|
||||
"grayscale": "Escala de grises",
|
||||
"ultraMode": "Modo ultra",
|
||||
"fastPrivateGpsUpdates": "Actualizaciones GPS privadas rápidas",
|
||||
"movementThreshold": "Umbral de movimiento",
|
||||
"fastGpsMovementThreshold": "Umbral de movimiento GPS rápido",
|
||||
"fastGpsActiveuseInterval": "Intervalo de uso activo GPS rápido",
|
||||
"activeuseUpdateInterval": "Intervalo de actualización de uso activo",
|
||||
"repeatNearbyTraffic": "Repetir tráfico cercano",
|
||||
"relayThroughRepeatersAcrossTheMesh": "Retransmitir a través de repetidores en la red",
|
||||
"nearbyOnlyWithoutRepeaterFlooding": "Solo cercanos, sin inundación de repetidores",
|
||||
"multihop": "Multi-salto",
|
||||
"createProfile": "Crear perfil",
|
||||
"renameProfile": "Renombrar perfil",
|
||||
"newProfile": "Nuevo perfil",
|
||||
"manageProfiles": "Gestionar perfiles",
|
||||
"enableProfilesToStartManagingThem": "Habilite los perfiles para comenzar a gestionarlos.",
|
||||
"openMessage": "Abrir mensaje",
|
||||
"jumpToTheRelatedSarMessage": "Ir al mensaje SAR relacionado",
|
||||
"removeSarMarker": "Eliminar marcador SAR",
|
||||
"pleaseSelectADestinationToSendSarMarker": "Seleccione un destino para enviar el marcador SAR",
|
||||
"sarMarkerBroadcastToPublicChannel": "Marcador SAR transmitido al canal público",
|
||||
"sarMarkerSentToRoom": "Marcador SAR enviado a la sala",
|
||||
"loadFromGallery": "Cargar desde galería",
|
||||
"replaceImage": "Reemplazar imagen",
|
||||
"selectFromGallery": "Seleccionar de galería",
|
||||
"team": "Equipo",
|
||||
"found": "Encontrado",
|
||||
"staging": "Área de reunión",
|
||||
"object": "Objeto",
|
||||
"quiet": "Silencioso",
|
||||
"moderate": "Moderado",
|
||||
"busy": "Ocupado",
|
||||
"spectrumScanReturnedNoCandidateFrequencies": "El escaneo de espectro no encontró frecuencias candidatas",
|
||||
"searchMessages": "Buscar mensajes",
|
||||
"sendImageFromGallery": "Enviar imagen desde galería",
|
||||
"takePhoto": "Tomar foto",
|
||||
"dmOnly": "Solo mensaje directo",
|
||||
"allMessages": "Todos los mensajes",
|
||||
"sendToPublicChannel": "¿Enviar al canal público?",
|
||||
"selectMarkerTypeAndDestination": "Seleccione tipo de marcador y destino",
|
||||
"noDestinationsAvailableLabel": "No hay destinos disponibles",
|
||||
"image": "Imagen",
|
||||
"format": "Formato",
|
||||
"dimensions": "Dimensiones",
|
||||
"segments": "Segmentos",
|
||||
"transfers": "Transferencias",
|
||||
"downloadedBy": "Descargado por",
|
||||
"saveDiscoverySettings": "Guardar ajustes de descubrimiento",
|
||||
"savePublicInfo": "Guardar información pública",
|
||||
"saveRadioSettings": "Guardar ajustes de radio",
|
||||
"savePath": "Guardar ruta",
|
||||
"wipeDeviceData": "Borrar datos del dispositivo",
|
||||
"wipeDevice": "Borrar dispositivo",
|
||||
"destructiveDeviceActions": "Acciones destructivas del dispositivo.",
|
||||
"chooseAPresetOrFinetuneCustomRadioSettings": "Elija un preajuste o ajuste la configuración de radio.",
|
||||
"chooseTheNameAndLocationThisDeviceShares": "Elija el nombre y la ubicación que comparte este dispositivo.",
|
||||
"availableSpaceOnThisDevice": "Espacio disponible en este dispositivo.",
|
||||
"used": "Usado",
|
||||
"total": "Total",
|
||||
"renameValue": "Renombrar valor",
|
||||
"customizeFields": "Personalizar campos",
|
||||
"livePreview": "Vista previa en vivo",
|
||||
"refreshSchedule": "Programación de actualización",
|
||||
"noResponse": "Sin respuesta",
|
||||
"refreshing": "Actualizando",
|
||||
"unavailable": "No disponible",
|
||||
"pickARelayOrNodeToWatchInSensors": "Elija un relay o nodo para observar.",
|
||||
"publicKeyLabel": "Clave pública",
|
||||
"alreadyInContacts": "Ya en contactos",
|
||||
"connectToADeviceBeforeAddingContacts": "Conéctese a un dispositivo antes de añadir contactos",
|
||||
"fromContacts": "De contactos",
|
||||
"onlineOnly": "Solo en línea",
|
||||
"inBoth": "En ambos",
|
||||
"source": "Fuente",
|
||||
"manualRouteEdit": "Edición manual de ruta",
|
||||
"observedMeshRoute": "Ruta mesh observada",
|
||||
"allMessagesCleared": "Todos los mensajes borrados",
|
||||
"onlineTraceDatabaseCleared": "Base de datos de trazas borrada",
|
||||
"packetLogsCleared": "Registros de paquetes borrados",
|
||||
"hexDataCopiedToClipboard": "Datos hex copiados al portapapeles",
|
||||
"developerModeEnabled": "Modo desarrollador activado",
|
||||
"developerModeDisabled": "Modo desarrollador desactivado",
|
||||
"clipboardIsEmpty": "El portapapeles está vacío",
|
||||
"contactImported": "Contacto importado",
|
||||
"contactLinkCopiedToClipboard": "Enlace de contacto copiado al portapapeles",
|
||||
"failedToExportContact": "Error al exportar el contacto",
|
||||
"noLogsToExport": "No hay registros para exportar",
|
||||
"exportAsCsv": "Exportar como CSV",
|
||||
"exportAsText": "Exportar como texto",
|
||||
"receivedRfc3339": "Recibido (RFC3339)",
|
||||
"buildTime": "Hora de compilación",
|
||||
"downloadUrlNotAvailable": "URL de descarga no disponible",
|
||||
"cannotOpenDownloadUrl": "No se puede abrir la URL de descarga",
|
||||
"updateCheckIsOnlyAvailableOnAndroid": "La comprobación de actualizaciones solo está disponible en Android",
|
||||
"youAreRunningTheLatestVersion": "Está ejecutando la última versión",
|
||||
"updateAvailableButDownloadUrlNotFound": "Actualización disponible pero URL de descarga no encontrada",
|
||||
"startTictactoe": "Iniciar Tic-Tac-Toe",
|
||||
"tictactoeUnavailable": "Tic-Tac-Toe no disponible",
|
||||
"tictactoeOpponentUnknown": "Tic-Tac-Toe: oponente desconocido",
|
||||
"tictactoeWaitingForStart": "Tic-Tac-Toe: esperando inicio",
|
||||
"acceptsShareLinks": "Acepta enlaces compartidos",
|
||||
"supportsRawHex": "Soporta hex sin procesar",
|
||||
"clipboardfriendly": "Compatible con portapapeles",
|
||||
"captured": "Capturado",
|
||||
"size": "Tamaño",
|
||||
"noCustomChannelsToClear": "No hay canales personalizados que borrar.",
|
||||
"noDeviceContactsToClear": "No hay contactos del dispositivo que borrar."
|
||||
}
|
||||
|
||||
@@ -1589,5 +1589,304 @@
|
||||
"rawDump": "Vidage brut",
|
||||
"cannotRetryMissingRecipient": "Impossible de réessayer : informations du destinataire manquantes",
|
||||
"voiceUnavailable": "Voix indisponible pour le moment",
|
||||
"requestingVoice": "Demande de voix"
|
||||
"requestingVoice": "Demande de voix",
|
||||
"device": "appareil",
|
||||
"change": "Modifier",
|
||||
"wizardOverviewDescription": "Cette application réunit la messagerie MeshCore, les mises à jour SAR sur le terrain, la cartographie et les outils de l'appareil au même endroit.",
|
||||
"wizardOverviewFeature1": "Envoyez des messages directs, des publications de salon et des messages de canal depuis l'onglet principal Messages.",
|
||||
"wizardOverviewFeature2": "Partagez des marqueurs SAR, des dessins de carte, des extraits vocaux et des images sur le réseau maillé.",
|
||||
"wizardOverviewFeature3": "Connectez-vous en BLE ou en TCP, puis gérez la radio compagnon directement dans l'application.",
|
||||
"wizardMessagingTitle": "Messagerie et rapports de terrain",
|
||||
"wizardMessagingDescription": "Ici, les messages sont plus que du texte brut. L'application prend déjà en charge plusieurs charges opérationnelles et flux de transfert.",
|
||||
"wizardMessagingFeature1": "Envoyez des messages directs, des publications de salon et du trafic de canal depuis un seul compositeur.",
|
||||
"wizardMessagingFeature2": "Créez des mises à jour SAR et des modèles SAR réutilisables pour les rapports de terrain courants.",
|
||||
"wizardMessagingFeature3": "Transférez des sessions vocales et des images avec la progression et les estimations de temps d'antenne dans l'interface.",
|
||||
"wizardConnectDeviceTitle": "Connecter l'appareil",
|
||||
"wizardConnectDeviceDescription": "Connectez votre radio MeshCore, choisissez un nom et appliquez un préréglage radio avant de continuer.",
|
||||
"wizardSetupBadge": "Configuration",
|
||||
"wizardOverviewBadge": "Aperçu",
|
||||
"wizardConnectedToDevice": "Connecté à {deviceName}",
|
||||
"wizardNoDeviceConnected": "Aucun appareil connecté pour le moment",
|
||||
"wizardSkipForNow": "Ignorer pour l'instant",
|
||||
"wizardDeviceNameLabel": "Nom de l'appareil",
|
||||
"wizardDeviceNameHelp": "Ce nom est annoncé aux autres utilisateurs de MeshCore.",
|
||||
"wizardConfigRegionLabel": "Région de configuration",
|
||||
"wizardConfigRegionHelp": "Utilise la liste officielle complète des préréglages MeshCore. La valeur par défaut est EU/UK (Narrow).",
|
||||
"wizardPresetNote1": "Assurez-vous que le préréglage sélectionné correspond à votre réglementation radio locale.",
|
||||
"wizardPresetNote2": "La liste correspond au flux officiel des préréglages de l'outil de configuration MeshCore.",
|
||||
"wizardPresetNote3": "EU/UK (Narrow) reste sélectionné par défaut pendant l'onboarding.",
|
||||
"wizardSaving": "Enregistrement...",
|
||||
"wizardSaveAndContinue": "Enregistrer et continuer",
|
||||
"wizardEnterDeviceName": "Saisissez un nom d'appareil avant de continuer.",
|
||||
"wizardDeviceSetupSaved": "{deviceName} enregistré avec {presetName}.",
|
||||
"wizardNetworkTitle": "Contacts, salons et répéteurs",
|
||||
"wizardNetworkDescription": "L'onglet Contacts organise le réseau que vous découvrez et les routes apprises au fil du temps.",
|
||||
"wizardNetworkFeature1": "Consultez les membres de l'équipe, les répéteurs, les salons, les canaux et les annonces en attente dans une seule liste.",
|
||||
"wizardNetworkFeature2": "Utilisez le smart ping, la connexion aux salons, les chemins appris et les outils de réinitialisation des routes lorsque la connectivité devient chaotique.",
|
||||
"wizardNetworkFeature3": "Créez des canaux et gérez les destinations réseau sans quitter l'application.",
|
||||
"wizardMapOpsTitle": "Carte, traces et géométrie partagée",
|
||||
"wizardMapOpsDescription": "La carte de l'application est directement liée à la messagerie, au suivi et aux couches SAR au lieu d'être un simple visualiseur séparé.",
|
||||
"wizardMapOpsFeature1": "Suivez votre position, celles de vos coéquipiers et les traces de déplacement sur la carte.",
|
||||
"wizardMapOpsFeature2": "Ouvrez les dessins depuis les messages, prévisualisez-les en ligne et supprimez-les de la carte si nécessaire.",
|
||||
"wizardMapOpsFeature3": "Utilisez les vues de carte des répéteurs et les couches partagées pour comprendre la portée du réseau sur le terrain.",
|
||||
"wizardToolsTitle": "Outils au-delà de la messagerie",
|
||||
"wizardToolsDescription": "Il y a plus ici que les quatre onglets principaux. L'application inclut aussi la configuration, le diagnostic et des workflows de capteurs optionnels.",
|
||||
"wizardToolsFeature1": "Ouvrez la configuration de l'appareil pour modifier les réglages radio, la télémétrie, la puissance TX et les détails du compagnon.",
|
||||
"wizardToolsFeature2": "Activez l'onglet Capteurs lorsque vous voulez des tableaux de bord surveillés et des actions d'actualisation rapide.",
|
||||
"wizardToolsFeature3": "Utilisez les journaux de paquets, le balayage du spectre et les diagnostics développeur pour dépanner le réseau maillé.",
|
||||
"contactInSensors": "Dans Capteurs",
|
||||
"contactAddToSensors": "Ajouter aux capteurs",
|
||||
"contactSetPath": "Définir le chemin",
|
||||
"contactAddedToSensors": "{contactName} ajouté aux capteurs",
|
||||
"contactFailedToClearRoute": "Échec de l'effacement de la route : {error}",
|
||||
"contactRouteCleared": "Route effacée",
|
||||
"contactRouteSet": "Route définie : {route}",
|
||||
"contactFailedToSetRoute": "Échec du réglage de la route : {error}",
|
||||
"rssi": "RSSI",
|
||||
"snr": "SNR",
|
||||
"ackTimeout": "Délai ACK expiré",
|
||||
"opcode": "Opcode",
|
||||
"payload": "Charge utile",
|
||||
"hops": "Sauts",
|
||||
"hashSize": "Taille du hash",
|
||||
"pathBytes": "Octets du chemin",
|
||||
"selectedPath": "Chemin sélectionné",
|
||||
"estimatedTx": "Émission estimée",
|
||||
"senderToReceipt": "Émetteur au reçu",
|
||||
"receivedCopies": "Copies reçues",
|
||||
"retryCause": "Cause de la relance",
|
||||
"retryMode": "Mode de relance",
|
||||
"retryResult": "Résultat de la relance",
|
||||
"lastRetry": "Dernière relance",
|
||||
"rxPackets": "Paquets RX",
|
||||
"mesh": "Mesh",
|
||||
"rate": "Débit",
|
||||
"window": "Fenêtre",
|
||||
"posttxDelay": "Délai post-émission",
|
||||
"bandpass": "Passe-bande",
|
||||
"bandpassFilterVoice": "Filtre passe-bande voix",
|
||||
"frequency": "Fréquence",
|
||||
"australia": "Australie",
|
||||
"australiaNarrow": "Australie (Étroit)",
|
||||
"australiaQld": "Australie: QLD",
|
||||
"australiaSaWa": "Australie: SA, WA",
|
||||
"newZealand": "Nouvelle-Zélande",
|
||||
"newZealandNarrow": "Nouvelle-Zélande (Étroit)",
|
||||
"switzerland": "Suisse",
|
||||
"portugal433": "Portugal 433",
|
||||
"portugal868": "Portugal 868",
|
||||
"czechRepublicNarrow": "République tchèque (Étroit)",
|
||||
"eu433mhzLongRange": "UE 433MHz (Longue Portée)",
|
||||
"euukDeprecated": "UE/RU (Obsolète)",
|
||||
"euukNarrow": "UE/RU (Étroit)",
|
||||
"usacanadaRecommended": "USA/Canada (Recommandé)",
|
||||
"vietnamDeprecated": "Vietnam (Obsolète)",
|
||||
"vietnamNarrow": "Vietnam (Étroit)",
|
||||
"active": "Actif",
|
||||
"addContact": "Ajouter un contact",
|
||||
"all": "Tout",
|
||||
"autoResolve": "Résolution automatique",
|
||||
"clearAllLabel": "Tout effacer",
|
||||
"clearRelays": "Effacer les relais",
|
||||
"clearFilters": "Effacer les filtres",
|
||||
"clearRoute": "Effacer la route",
|
||||
"clearMessages": "Effacer les messages",
|
||||
"clearScale": "Effacer l'échelle",
|
||||
"clearDiscoveries": "Effacer les découvertes",
|
||||
"clearOnlineTraceDatabase": "Effacer la base de traces",
|
||||
"clearAllChannels": "Effacer tous les canaux",
|
||||
"clearAllContacts": "Effacer tous les contacts",
|
||||
"clearChannels": "Effacer les canaux",
|
||||
"clearContacts": "Effacer les contacts",
|
||||
"clearPathOnMaxRetry": "Effacer le chemin au max de relances",
|
||||
"create": "Créer",
|
||||
"custom": "Personnalisé",
|
||||
"defaultValue": "Par défaut",
|
||||
"duplicate": "Dupliquer",
|
||||
"editName": "Modifier le nom",
|
||||
"open": "Ouvrir",
|
||||
"paste": "Coller",
|
||||
"preview": "Aperçu",
|
||||
"remove": "Supprimer",
|
||||
"rename": "Renommer",
|
||||
"resolveAll": "Résoudre tout",
|
||||
"send": "Envoyer",
|
||||
"sendAnyway": "Envoyer quand même",
|
||||
"share": "Partager",
|
||||
"shareContact": "Partager le contact",
|
||||
"trace": "Trace",
|
||||
"use": "Utiliser",
|
||||
"useSelectedFrequency": "Utiliser la fréquence sélectionnée",
|
||||
"discovery": "Découverte",
|
||||
"discoverRepeaters": "Découvrir les répéteurs",
|
||||
"discoverSensors": "Découvrir les capteurs",
|
||||
"repeaterDiscoverySent": "Découverte de répéteurs envoyée",
|
||||
"sensorDiscoverySent": "Découverte de capteurs envoyée",
|
||||
"clearedPendingDiscoveries": "Découvertes en attente effacées.",
|
||||
"autoDiscovery": "Découverte automatique",
|
||||
"enableAutomaticAdding": "Activer l'ajout automatique",
|
||||
"autoaddRepeaters": "Ajout auto des répéteurs",
|
||||
"autoaddRoomServers": "Ajout auto des serveurs de salle",
|
||||
"autoaddSensors": "Ajout auto des capteurs",
|
||||
"autoaddUsers": "Ajout auto des utilisateurs",
|
||||
"overwriteOldestWhenFull": "Écraser les plus anciens si plein",
|
||||
"storage": "Stockage",
|
||||
"dangerZone": "Zone de danger",
|
||||
"profiles": "Profils",
|
||||
"favourites": "Favoris",
|
||||
"sensors": "Capteurs",
|
||||
"others": "Autres",
|
||||
"gpsModule": "Module GPS",
|
||||
"liveTraffic": "Trafic en direct",
|
||||
"repeatersMap": "Carte des répéteurs",
|
||||
"spectrumScan": "Scan spectral",
|
||||
"blePacketLogs": "Journaux de paquets BLE",
|
||||
"onlineTraceDatabase": "Base de données de traces",
|
||||
"routePathByteSize": "Taille en octets du chemin",
|
||||
"messageNotifications": "Notifications de messages",
|
||||
"sarAlerts": "Alertes SAR",
|
||||
"discoveryNotifications": "Notifications de découverte",
|
||||
"updateNotifications": "Notifications de mise à jour",
|
||||
"muteWhileAppIsOpen": "Muet quand l'appli est ouverte",
|
||||
"disableContacts": "Désactiver les contacts",
|
||||
"enableSensorsTab": "Activer l'onglet Capteurs",
|
||||
"enableProfiles": "Activer les profils",
|
||||
"autoRouteRotation": "Rotation automatique de route",
|
||||
"nearestRepeaterFallback": "Répéteur le plus proche en repli",
|
||||
"deleteAllStoredMessageHistory": "Supprimer tout l'historique",
|
||||
"messageFontSize": "Taille de police des messages",
|
||||
"rotateMapWithHeading": "Tourner la carte avec le cap",
|
||||
"showMapDebugInfo": "Afficher les infos de débogage",
|
||||
"openMapInFullscreen": "Ouvrir la carte en plein écran",
|
||||
"showSarMarkersLabel": "Afficher les marqueurs SAR",
|
||||
"displaySarMarkersOnTheMainMap": "Afficher les marqueurs SAR sur la carte",
|
||||
"showAllContactTrailsLabel": "Afficher toutes les traces de contacts",
|
||||
"hideRepeatersOnMap": "Masquer les répéteurs sur la carte",
|
||||
"setMapScale": "Définir l'échelle de la carte",
|
||||
"customMapScaleSaved": "Échelle de carte personnalisée enregistrée",
|
||||
"voiceBitrate": "Débit vocal",
|
||||
"voiceCompressor": "Compresseur vocal",
|
||||
"balancesQuietAndLoudSpeechLevels": "Équilibre les niveaux faibles et forts",
|
||||
"voiceLimiter": "Limiteur vocal",
|
||||
"preventsClippingPeaksBeforeEncoding": "Empêche l'écrêtage avant l'encodage",
|
||||
"micAutoGain": "Gain auto du micro",
|
||||
"letsTheRecorderAdjustInputLevel": "Laisse l'enregistreur ajuster le niveau d'entrée",
|
||||
"echoCancellation": "Annulation d'écho",
|
||||
"noiseSuppression": "Suppression du bruit",
|
||||
"trimSilenceInVoiceMessages": "Couper le silence dans les messages vocaux",
|
||||
"compressor": "Compresseur",
|
||||
"limiter": "Limiteur",
|
||||
"autoGain": "Gain automatique",
|
||||
"echoCancel": "Écho",
|
||||
"noiseSuppress": "Bruit",
|
||||
"silenceTrim": "Silence",
|
||||
"maxImageSize": "Taille d'image maximale",
|
||||
"imageCompression": "Compression d'image",
|
||||
"grayscale": "Niveaux de gris",
|
||||
"ultraMode": "Mode ultra",
|
||||
"fastPrivateGpsUpdates": "Mises à jour GPS privées rapides",
|
||||
"movementThreshold": "Seuil de mouvement",
|
||||
"fastGpsMovementThreshold": "Seuil de mouvement GPS rapide",
|
||||
"fastGpsActiveuseInterval": "Intervalle d'utilisation active GPS rapide",
|
||||
"activeuseUpdateInterval": "Intervalle de mise à jour en utilisation active",
|
||||
"repeatNearbyTraffic": "Répéter le trafic proche",
|
||||
"relayThroughRepeatersAcrossTheMesh": "Relayer via les répéteurs du réseau",
|
||||
"nearbyOnlyWithoutRepeaterFlooding": "Proximité seulement, sans inondation",
|
||||
"multihop": "Multi-saut",
|
||||
"createProfile": "Créer un profil",
|
||||
"renameProfile": "Renommer le profil",
|
||||
"newProfile": "Nouveau profil",
|
||||
"manageProfiles": "Gérer les profils",
|
||||
"enableProfilesToStartManagingThem": "Activez les profils pour commencer à les gérer.",
|
||||
"openMessage": "Ouvrir le message",
|
||||
"jumpToTheRelatedSarMessage": "Aller au message SAR associé",
|
||||
"removeSarMarker": "Supprimer le marqueur SAR",
|
||||
"pleaseSelectADestinationToSendSarMarker": "Sélectionnez une destination pour envoyer le marqueur",
|
||||
"sarMarkerBroadcastToPublicChannel": "Marqueur SAR diffusé sur le canal public",
|
||||
"sarMarkerSentToRoom": "Marqueur SAR envoyé à la salle",
|
||||
"loadFromGallery": "Charger depuis la galerie",
|
||||
"replaceImage": "Remplacer l'image",
|
||||
"selectFromGallery": "Sélectionner depuis la galerie",
|
||||
"team": "Équipe",
|
||||
"found": "Trouvé",
|
||||
"staging": "Zone de regroupement",
|
||||
"object": "Objet",
|
||||
"quiet": "Calme",
|
||||
"moderate": "Modéré",
|
||||
"busy": "Occupé",
|
||||
"spectrumScanReturnedNoCandidateFrequencies": "Le scan spectral n'a trouvé aucune fréquence candidate",
|
||||
"searchMessages": "Rechercher des messages",
|
||||
"sendImageFromGallery": "Envoyer une image de la galerie",
|
||||
"takePhoto": "Prendre une photo",
|
||||
"dmOnly": "Message direct uniquement",
|
||||
"allMessages": "Tous les messages",
|
||||
"sendToPublicChannel": "Envoyer au canal public ?",
|
||||
"selectMarkerTypeAndDestination": "Sélectionnez le type de marqueur et la destination",
|
||||
"noDestinationsAvailableLabel": "Aucune destination disponible",
|
||||
"image": "Image",
|
||||
"format": "Format",
|
||||
"dimensions": "Dimensions",
|
||||
"segments": "Segments",
|
||||
"transfers": "Transferts",
|
||||
"downloadedBy": "Téléchargé par",
|
||||
"saveDiscoverySettings": "Enregistrer les paramètres de découverte",
|
||||
"savePublicInfo": "Enregistrer les infos publiques",
|
||||
"saveRadioSettings": "Enregistrer les paramètres radio",
|
||||
"savePath": "Enregistrer le chemin",
|
||||
"wipeDeviceData": "Effacer les données de l'appareil",
|
||||
"wipeDevice": "Effacer l'appareil",
|
||||
"destructiveDeviceActions": "Actions destructives de l'appareil.",
|
||||
"chooseAPresetOrFinetuneCustomRadioSettings": "Choisissez un préréglage ou ajustez les paramètres radio.",
|
||||
"chooseTheNameAndLocationThisDeviceShares": "Choisissez le nom et l'emplacement partagés par cet appareil.",
|
||||
"availableSpaceOnThisDevice": "Espace disponible sur cet appareil.",
|
||||
"used": "Utilisé",
|
||||
"total": "Total",
|
||||
"renameValue": "Renommer la valeur",
|
||||
"customizeFields": "Personnaliser les champs",
|
||||
"livePreview": "Aperçu en direct",
|
||||
"refreshSchedule": "Planification d'actualisation",
|
||||
"noResponse": "Pas de réponse",
|
||||
"refreshing": "Actualisation",
|
||||
"unavailable": "Indisponible",
|
||||
"pickARelayOrNodeToWatchInSensors": "Choisissez un relais ou nœud à surveiller.",
|
||||
"publicKeyLabel": "Clé publique",
|
||||
"alreadyInContacts": "Déjà dans les contacts",
|
||||
"connectToADeviceBeforeAddingContacts": "Connectez-vous à un appareil avant d'ajouter des contacts",
|
||||
"fromContacts": "Des contacts",
|
||||
"onlineOnly": "En ligne uniquement",
|
||||
"inBoth": "Dans les deux",
|
||||
"source": "Source",
|
||||
"manualRouteEdit": "Édition manuelle de route",
|
||||
"observedMeshRoute": "Route mesh observée",
|
||||
"allMessagesCleared": "Tous les messages effacés",
|
||||
"onlineTraceDatabaseCleared": "Base de données de traces effacée",
|
||||
"packetLogsCleared": "Journaux de paquets effacés",
|
||||
"hexDataCopiedToClipboard": "Données hex copiées dans le presse-papiers",
|
||||
"developerModeEnabled": "Mode développeur activé",
|
||||
"developerModeDisabled": "Mode développeur désactivé",
|
||||
"clipboardIsEmpty": "Le presse-papiers est vide",
|
||||
"contactImported": "Contact importé",
|
||||
"contactLinkCopiedToClipboard": "Lien du contact copié dans le presse-papiers",
|
||||
"failedToExportContact": "Échec de l'exportation du contact",
|
||||
"noLogsToExport": "Aucun journal à exporter",
|
||||
"exportAsCsv": "Exporter en CSV",
|
||||
"exportAsText": "Exporter en texte",
|
||||
"receivedRfc3339": "Reçu (RFC3339)",
|
||||
"buildTime": "Heure de build",
|
||||
"downloadUrlNotAvailable": "URL de téléchargement non disponible",
|
||||
"cannotOpenDownloadUrl": "Impossible d'ouvrir l'URL de téléchargement",
|
||||
"updateCheckIsOnlyAvailableOnAndroid": "La vérification des mises à jour n'est disponible que sur Android",
|
||||
"youAreRunningTheLatestVersion": "Vous utilisez la dernière version",
|
||||
"updateAvailableButDownloadUrlNotFound": "Mise à jour disponible mais URL de téléchargement introuvable",
|
||||
"startTictactoe": "Démarrer Tic-Tac-Toe",
|
||||
"tictactoeUnavailable": "Tic-Tac-Toe indisponible",
|
||||
"tictactoeOpponentUnknown": "Tic-Tac-Toe: adversaire inconnu",
|
||||
"tictactoeWaitingForStart": "Tic-Tac-Toe: en attente du début",
|
||||
"acceptsShareLinks": "Accepte les liens partagés",
|
||||
"supportsRawHex": "Supporte le hex brut",
|
||||
"clipboardfriendly": "Compatible avec le presse-papiers",
|
||||
"captured": "Capturé",
|
||||
"size": "Taille",
|
||||
"noCustomChannelsToClear": "Aucun canal personnalisé à effacer.",
|
||||
"noDeviceContactsToClear": "Aucun contact de l'appareil à effacer."
|
||||
}
|
||||
|
||||
@@ -511,5 +511,304 @@
|
||||
"rawDump": "Sirovi ispis",
|
||||
"cannotRetryMissingRecipient": "Nije moguće ponoviti: nedostaju informacije o primatelju",
|
||||
"voiceUnavailable": "Glas trenutno nije dostupan",
|
||||
"requestingVoice": "Zahtjev za glasom"
|
||||
"requestingVoice": "Zahtjev za glasom",
|
||||
"device": "uređaj",
|
||||
"change": "Promijeni",
|
||||
"wizardOverviewDescription": "Ova aplikacija objedinjuje MeshCore poruke, SAR ažuriranja s terena, karte i alate za uređaj na jednom mjestu.",
|
||||
"wizardOverviewFeature1": "Šaljite izravne poruke, objave u sobama i poruke kanala iz glavne kartice Poruke.",
|
||||
"wizardOverviewFeature2": "Dijelite SAR oznake, crteže karte, glasovne isječke i slike preko mesh mreže.",
|
||||
"wizardOverviewFeature3": "Povežite se putem BLE-a ili TCP-a, a zatim upravljajte pratećim radijem izravno iz aplikacije.",
|
||||
"wizardMessagingTitle": "Poruke i terenska izvješća",
|
||||
"wizardMessagingDescription": "Poruke ovdje nisu samo običan tekst. Aplikacija već podržava više operativnih sadržaja i tijekova prijenosa.",
|
||||
"wizardMessagingFeature1": "Šaljite izravne poruke, objave u sobama i promet kanala iz jednog uređivača.",
|
||||
"wizardMessagingFeature2": "Stvarajte SAR ažuriranja i višekratne SAR predloške za uobičajena terenska izvješća.",
|
||||
"wizardMessagingFeature3": "Prenosite glasovne sesije i slike uz prikaz napretka i procjene vremena prijenosa u sučelju.",
|
||||
"wizardConnectDeviceTitle": "Poveži uređaj",
|
||||
"wizardConnectDeviceDescription": "Povežite svoj MeshCore radio, odaberite naziv i primijenite radijski predložak prije nastavka.",
|
||||
"wizardSetupBadge": "Postavljanje",
|
||||
"wizardOverviewBadge": "Pregled",
|
||||
"wizardConnectedToDevice": "Povezano s uređajem {deviceName}",
|
||||
"wizardNoDeviceConnected": "Još nema povezanog uređaja",
|
||||
"wizardSkipForNow": "Preskoči za sada",
|
||||
"wizardDeviceNameLabel": "Naziv uređaja",
|
||||
"wizardDeviceNameHelp": "Taj se naziv oglašava drugim korisnicima MeshCorea.",
|
||||
"wizardConfigRegionLabel": "Regija konfiguracije",
|
||||
"wizardConfigRegionHelp": "Koristi puni službeni popis MeshCore predložaka. Zadana je opcija EU/UK (Narrow).",
|
||||
"wizardPresetNote1": "Provjerite odgovara li odabrani predložak lokalnim radijskim propisima.",
|
||||
"wizardPresetNote2": "Popis odgovara službenom izvoru predložaka alata MeshCore config.",
|
||||
"wizardPresetNote3": "EU/UK (Narrow) ostaje zadano odabran tijekom uvodnog postavljanja.",
|
||||
"wizardSaving": "Spremanje...",
|
||||
"wizardSaveAndContinue": "Spremi i nastavi",
|
||||
"wizardEnterDeviceName": "Unesite naziv uređaja prije nastavka.",
|
||||
"wizardDeviceSetupSaved": "Spremljeno {deviceName} s postavkom {presetName}.",
|
||||
"wizardNetworkTitle": "Kontakti, sobe i repetitori",
|
||||
"wizardNetworkDescription": "Kartica Kontakti organizira mrežu koju otkrijete i rute koje s vremenom naučite.",
|
||||
"wizardNetworkFeature1": "Pregledajte članove tima, repetitore, sobe, kanale i oglase na čekanju na jednom popisu.",
|
||||
"wizardNetworkFeature2": "Koristite pametni ping, prijavu u sobe, naučene putanje i alate za resetiranje ruta kada povezanost postane neuredna.",
|
||||
"wizardNetworkFeature3": "Stvarajte kanale i upravljajte mrežnim odredištima bez napuštanja aplikacije.",
|
||||
"wizardMapOpsTitle": "Karta, tragovi i dijeljena geometrija",
|
||||
"wizardMapOpsDescription": "Karta u aplikaciji izravno je povezana s porukama, praćenjem i SAR slojevima umjesto da bude zaseban preglednik.",
|
||||
"wizardMapOpsFeature1": "Pratite vlastitu poziciju, lokacije suigrača i tragove kretanja na karti.",
|
||||
"wizardMapOpsFeature2": "Otvorite crteže iz poruka, pregledajte ih unutar sučelja i po potrebi ih uklonite s karte.",
|
||||
"wizardMapOpsFeature3": "Koristite karte repetitora i dijeljene slojeve kako biste razumjeli doseg mreže na terenu.",
|
||||
"wizardToolsTitle": "Alati izvan poruka",
|
||||
"wizardToolsDescription": "Ovdje postoji više od četiri glavne kartice. Aplikacija uključuje i konfiguraciju, dijagnostiku te opcionalne radne tokove senzora.",
|
||||
"wizardToolsFeature1": "Otvorite konfiguraciju uređaja kako biste promijenili radijske postavke, telemetriju, TX snagu i detalje pratećeg uređaja.",
|
||||
"wizardToolsFeature2": "Omogućite karticu Senzori kada želite nadzorne ploče praćenih senzora i brza osvježavanja.",
|
||||
"wizardToolsFeature3": "Koristite zapisnike paketa, skeniranje spektra i razvojnu dijagnostiku pri rješavanju problema s mesh mrežom.",
|
||||
"contactInSensors": "U Senzorima",
|
||||
"contactAddToSensors": "Dodaj u Senzore",
|
||||
"contactSetPath": "Postavi putanju",
|
||||
"contactAddedToSensors": "{contactName} dodan u Senzore",
|
||||
"contactFailedToClearRoute": "Brisanje rute nije uspjelo: {error}",
|
||||
"contactRouteCleared": "Ruta obrisana",
|
||||
"contactRouteSet": "Ruta postavljena: {route}",
|
||||
"contactFailedToSetRoute": "Postavljanje rute nije uspjelo: {error}",
|
||||
"rssi": "RSSI",
|
||||
"snr": "SNR",
|
||||
"ackTimeout": "ACK istek vremena",
|
||||
"opcode": "Opkod",
|
||||
"payload": "Sadržaj",
|
||||
"hops": "Skokovi",
|
||||
"hashSize": "Veličina hash-a",
|
||||
"pathBytes": "Bajtovi putanje",
|
||||
"selectedPath": "Odabrana putanja",
|
||||
"estimatedTx": "Procijenjeno slanje",
|
||||
"senderToReceipt": "Od pošiljatelja do primitka",
|
||||
"receivedCopies": "Primljene kopije",
|
||||
"retryCause": "Razlog ponovnog pokušaja",
|
||||
"retryMode": "Način ponovnog pokušaja",
|
||||
"retryResult": "Rezultat ponovnog pokušaja",
|
||||
"lastRetry": "Zadnji pokušaj",
|
||||
"rxPackets": "RX paketi",
|
||||
"mesh": "Mesh",
|
||||
"rate": "Brzina",
|
||||
"window": "Prozor",
|
||||
"posttxDelay": "Kašnjenje nakon slanja",
|
||||
"bandpass": "Pojasni filtar",
|
||||
"bandpassFilterVoice": "Pojasni filtar glasa",
|
||||
"frequency": "Frekvencija",
|
||||
"australia": "Australija",
|
||||
"australiaNarrow": "Australija (usko)",
|
||||
"australiaQld": "Australija: QLD",
|
||||
"australiaSaWa": "Australija: SA, WA",
|
||||
"newZealand": "Novi Zeland",
|
||||
"newZealandNarrow": "Novi Zeland (usko)",
|
||||
"switzerland": "Švicarska",
|
||||
"portugal433": "Portugal 433",
|
||||
"portugal868": "Portugal 868",
|
||||
"czechRepublicNarrow": "Češka (usko)",
|
||||
"eu433mhzLongRange": "EU 433MHz (veliki domet)",
|
||||
"euukDeprecated": "EU/UK (zastarjelo)",
|
||||
"euukNarrow": "EU/UK (usko)",
|
||||
"usacanadaRecommended": "SAD/Kanada (preporučeno)",
|
||||
"vietnamDeprecated": "Vijetnam (zastarjelo)",
|
||||
"vietnamNarrow": "Vijetnam (usko)",
|
||||
"active": "Aktivno",
|
||||
"addContact": "Dodaj kontakt",
|
||||
"all": "Sve",
|
||||
"autoResolve": "Automatski razriješi",
|
||||
"clearAllLabel": "Očisti sve",
|
||||
"clearRelays": "Očisti posrednike",
|
||||
"clearFilters": "Očisti filtre",
|
||||
"clearRoute": "Očisti putanju",
|
||||
"clearMessages": "Očisti poruke",
|
||||
"clearScale": "Očisti mjerilo",
|
||||
"clearDiscoveries": "Očisti otkrića",
|
||||
"clearOnlineTraceDatabase": "Očisti bazu mrežnih tragova",
|
||||
"clearAllChannels": "Očisti sve kanale",
|
||||
"clearAllContacts": "Očisti sve kontakte",
|
||||
"clearChannels": "Očisti kanale",
|
||||
"clearContacts": "Očisti kontakte",
|
||||
"clearPathOnMaxRetry": "Očisti putanju kod maks. pokušaja",
|
||||
"create": "Stvori",
|
||||
"custom": "Prilagođeno",
|
||||
"defaultValue": "Zadano",
|
||||
"duplicate": "Dupliciraj",
|
||||
"editName": "Uredi ime",
|
||||
"open": "Otvori",
|
||||
"paste": "Zalijepi",
|
||||
"preview": "Pregled",
|
||||
"remove": "Ukloni",
|
||||
"rename": "Preimenuj",
|
||||
"resolveAll": "Razriješi sve",
|
||||
"send": "Pošalji",
|
||||
"sendAnyway": "Svejedno pošalji",
|
||||
"share": "Dijeli",
|
||||
"shareContact": "Dijeli kontakt",
|
||||
"trace": "Praćenje",
|
||||
"use": "Koristi",
|
||||
"useSelectedFrequency": "Koristi odabranu frekvenciju",
|
||||
"discovery": "Otkrivanje",
|
||||
"discoverRepeaters": "Otkrij posrednike",
|
||||
"discoverSensors": "Otkrij senzore",
|
||||
"repeaterDiscoverySent": "Otkrivanje posrednika poslano",
|
||||
"sensorDiscoverySent": "Otkrivanje senzora poslano",
|
||||
"clearedPendingDiscoveries": "Očišćena čekajuća otkrića.",
|
||||
"autoDiscovery": "Automatsko otkrivanje",
|
||||
"enableAutomaticAdding": "Omogući automatsko dodavanje",
|
||||
"autoaddRepeaters": "Automatski dodaj posrednike",
|
||||
"autoaddRoomServers": "Automatski dodaj poslužitelje soba",
|
||||
"autoaddSensors": "Automatski dodaj senzore",
|
||||
"autoaddUsers": "Automatski dodaj korisnike",
|
||||
"overwriteOldestWhenFull": "Prepiši najstarije kad je puno",
|
||||
"storage": "Pohrana",
|
||||
"dangerZone": "Opasna zona",
|
||||
"profiles": "Profili",
|
||||
"favourites": "Favoriti",
|
||||
"sensors": "Senzori",
|
||||
"others": "Ostali",
|
||||
"gpsModule": "GPS modul",
|
||||
"liveTraffic": "Promet uživo",
|
||||
"repeatersMap": "Karta posrednika",
|
||||
"spectrumScan": "Spektralno skeniranje",
|
||||
"blePacketLogs": "BLE zapisi paketa",
|
||||
"onlineTraceDatabase": "Baza mrežnih tragova",
|
||||
"routePathByteSize": "Veličina putanje u bajtima",
|
||||
"messageNotifications": "Obavijesti o porukama",
|
||||
"sarAlerts": "SAR upozorenja",
|
||||
"discoveryNotifications": "Obavijesti o otkrivanju",
|
||||
"updateNotifications": "Obavijesti o ažuriranju",
|
||||
"muteWhileAppIsOpen": "Utišaj dok je aplikacija otvorena",
|
||||
"disableContacts": "Onemogući kontakte",
|
||||
"enableSensorsTab": "Omogući karticu Senzori",
|
||||
"enableProfiles": "Omogući profile",
|
||||
"autoRouteRotation": "Automatsko rotiranje putanje",
|
||||
"nearestRepeaterFallback": "Najbliži posrednik kao rezerva",
|
||||
"deleteAllStoredMessageHistory": "Izbriši svu pohranjenu povijest poruka",
|
||||
"messageFontSize": "Veličina fonta poruka",
|
||||
"rotateMapWithHeading": "Rotiraj kartu prema smjeru",
|
||||
"showMapDebugInfo": "Prikaži debug info karte",
|
||||
"openMapInFullscreen": "Otvori kartu na cijelom zaslonu",
|
||||
"showSarMarkersLabel": "Prikaži SAR oznake",
|
||||
"displaySarMarkersOnTheMainMap": "Prikaži SAR oznake na glavnoj karti",
|
||||
"showAllContactTrailsLabel": "Prikaži sve tragove kontakata",
|
||||
"hideRepeatersOnMap": "Sakrij posrednike na karti",
|
||||
"setMapScale": "Postavi mjerilo karte",
|
||||
"customMapScaleSaved": "Prilagođeno mjerilo karte spremljeno",
|
||||
"voiceBitrate": "Bitna brzina glasa",
|
||||
"voiceCompressor": "Kompresor glasa",
|
||||
"balancesQuietAndLoudSpeechLevels": "Izravnava tihe i glasne razine govora",
|
||||
"voiceLimiter": "Ograničivač glasa",
|
||||
"preventsClippingPeaksBeforeEncoding": "Sprječava rezanje vrhova prije kodiranja",
|
||||
"micAutoGain": "Automatsko pojačanje mikrofona",
|
||||
"letsTheRecorderAdjustInputLevel": "Dopušta snimaču prilagodbu razine ulaza",
|
||||
"echoCancellation": "Poništavanje odjeka",
|
||||
"noiseSuppression": "Potiskivanje šuma",
|
||||
"trimSilenceInVoiceMessages": "Izreži tišinu u glasovnim porukama",
|
||||
"compressor": "Kompresor",
|
||||
"limiter": "Ograničivač",
|
||||
"autoGain": "Automatsko pojačanje",
|
||||
"echoCancel": "Odjek",
|
||||
"noiseSuppress": "Šum",
|
||||
"silenceTrim": "Tišina",
|
||||
"maxImageSize": "Maksimalna veličina slike",
|
||||
"imageCompression": "Kompresija slike",
|
||||
"grayscale": "Sivine",
|
||||
"ultraMode": "Ultra način",
|
||||
"fastPrivateGpsUpdates": "Brza privatna GPS ažuriranja",
|
||||
"movementThreshold": "Prag kretanja",
|
||||
"fastGpsMovementThreshold": "Prag kretanja za brzi GPS",
|
||||
"fastGpsActiveuseInterval": "Interval aktivnog korištenja brzog GPS-a",
|
||||
"activeuseUpdateInterval": "Interval ažuriranja pri aktivnom korištenju",
|
||||
"repeatNearbyTraffic": "Ponovi obližnji promet",
|
||||
"relayThroughRepeatersAcrossTheMesh": "Proslijedi kroz posrednike kroz mrežu",
|
||||
"nearbyOnlyWithoutRepeaterFlooding": "Samo obližnji, bez preplavljivanja posrednika",
|
||||
"multihop": "Višeskočno",
|
||||
"createProfile": "Stvori profil",
|
||||
"renameProfile": "Preimenuj profil",
|
||||
"newProfile": "Novi profil",
|
||||
"manageProfiles": "Upravljaj profilima",
|
||||
"enableProfilesToStartManagingThem": "Omogućite profile da biste ih počeli upravljati.",
|
||||
"openMessage": "Otvori poruku",
|
||||
"jumpToTheRelatedSarMessage": "Skoči na povezanu SAR poruku",
|
||||
"removeSarMarker": "Ukloni SAR oznaku",
|
||||
"pleaseSelectADestinationToSendSarMarker": "Odaberite odredište za slanje SAR oznake",
|
||||
"sarMarkerBroadcastToPublicChannel": "SAR oznaka emitirana na javni kanal",
|
||||
"sarMarkerSentToRoom": "SAR oznaka poslana u sobu",
|
||||
"loadFromGallery": "Učitaj iz galerije",
|
||||
"replaceImage": "Zamijeni sliku",
|
||||
"selectFromGallery": "Odaberi iz galerije",
|
||||
"team": "Tim",
|
||||
"found": "Pronađeno",
|
||||
"staging": "Sabirno mjesto",
|
||||
"object": "Objekt",
|
||||
"quiet": "Tiho",
|
||||
"moderate": "Umjereno",
|
||||
"busy": "Zauzeto",
|
||||
"spectrumScanReturnedNoCandidateFrequencies": "Spektralno skeniranje nije pronašlo prikladne frekvencije",
|
||||
"searchMessages": "Pretraži poruke",
|
||||
"sendImageFromGallery": "Pošalji sliku iz galerije",
|
||||
"takePhoto": "Snimi fotografiju",
|
||||
"dmOnly": "Samo izravno",
|
||||
"allMessages": "Sve poruke",
|
||||
"sendToPublicChannel": "Pošalji na javni kanal?",
|
||||
"selectMarkerTypeAndDestination": "Odaberite vrstu oznake i odredište",
|
||||
"noDestinationsAvailableLabel": "Nema dostupnih odredišta",
|
||||
"image": "Slika",
|
||||
"format": "Format",
|
||||
"dimensions": "Dimenzije",
|
||||
"segments": "Segmenti",
|
||||
"transfers": "Prijenosi",
|
||||
"downloadedBy": "Preuzeo",
|
||||
"saveDiscoverySettings": "Spremi postavke otkrivanja",
|
||||
"savePublicInfo": "Spremi javne podatke",
|
||||
"saveRadioSettings": "Spremi radijske postavke",
|
||||
"savePath": "Spremi putanju",
|
||||
"wipeDeviceData": "Obriši podatke uređaja",
|
||||
"wipeDevice": "Obriši uređaj",
|
||||
"destructiveDeviceActions": "Destruktivne radnje na uređaju.",
|
||||
"chooseAPresetOrFinetuneCustomRadioSettings": "Odaberite predefiniranu postavku ili fino podesite radijske postavke.",
|
||||
"chooseTheNameAndLocationThisDeviceShares": "Odaberite ime i lokaciju koju ovaj uređaj dijeli.",
|
||||
"availableSpaceOnThisDevice": "Raspoloživi prostor na ovom uređaju.",
|
||||
"used": "Korišteno",
|
||||
"total": "Ukupno",
|
||||
"renameValue": "Preimenuj vrijednost",
|
||||
"customizeFields": "Prilagodi polja",
|
||||
"livePreview": "Pregled uživo",
|
||||
"refreshSchedule": "Raspored osvježavanja",
|
||||
"noResponse": "Nema odgovora",
|
||||
"refreshing": "Osvježavam",
|
||||
"unavailable": "Nedostupno",
|
||||
"pickARelayOrNodeToWatchInSensors": "Odaberite posrednika ili čvor za praćenje.",
|
||||
"publicKeyLabel": "Javni ključ",
|
||||
"alreadyInContacts": "Već u kontaktima",
|
||||
"connectToADeviceBeforeAddingContacts": "Povežite se s uređajem prije dodavanja kontakata",
|
||||
"fromContacts": "Iz kontakata",
|
||||
"onlineOnly": "Samo povezani",
|
||||
"inBoth": "U oba",
|
||||
"source": "Izvor",
|
||||
"manualRouteEdit": "Ručno uređivanje putanje",
|
||||
"observedMeshRoute": "Opažena mrežna putanja",
|
||||
"allMessagesCleared": "Sve poruke očišćene",
|
||||
"onlineTraceDatabaseCleared": "Baza mrežnih tragova očišćena",
|
||||
"packetLogsCleared": "Zapisi paketa očišćeni",
|
||||
"hexDataCopiedToClipboard": "Hex podaci kopirani u međuspremnik",
|
||||
"developerModeEnabled": "Razvojni način omogućen",
|
||||
"developerModeDisabled": "Razvojni način onemogućen",
|
||||
"clipboardIsEmpty": "Međuspremnik je prazan",
|
||||
"contactImported": "Kontakt uvezen",
|
||||
"contactLinkCopiedToClipboard": "Poveznica na kontakt kopirana u međuspremnik",
|
||||
"failedToExportContact": "Izvoz kontakta nije uspio",
|
||||
"noLogsToExport": "Nema zapisa za izvoz",
|
||||
"exportAsCsv": "Izvezi kao CSV",
|
||||
"exportAsText": "Izvezi kao tekst",
|
||||
"receivedRfc3339": "Primljeno (RFC3339)",
|
||||
"buildTime": "Vrijeme gradnje",
|
||||
"downloadUrlNotAvailable": "URL za preuzimanje nije dostupan",
|
||||
"cannotOpenDownloadUrl": "Ne mogu otvoriti URL za preuzimanje",
|
||||
"updateCheckIsOnlyAvailableOnAndroid": "Provjera ažuriranja dostupna je samo na Androidu",
|
||||
"youAreRunningTheLatestVersion": "Koristite najnoviju verziju",
|
||||
"updateAvailableButDownloadUrlNotFound": "Ažuriranje dostupno, ali URL za preuzimanje nije pronađen",
|
||||
"startTictactoe": "Pokreni Tic-Tac-Toe",
|
||||
"tictactoeUnavailable": "Tic-Tac-Toe nije dostupan",
|
||||
"tictactoeOpponentUnknown": "Tic-Tac-Toe: protivnik nepoznat",
|
||||
"tictactoeWaitingForStart": "Tic-Tac-Toe: čekanje na početak",
|
||||
"acceptsShareLinks": "Prihvaća dijeljene veze",
|
||||
"supportsRawHex": "Podržava sirovi hex",
|
||||
"clipboardfriendly": "Pogodno za međuspremnik",
|
||||
"captured": "Zabilježeno",
|
||||
"size": "Veličina",
|
||||
"noCustomChannelsToClear": "Nema prilagođenih kanala za brisanje.",
|
||||
"noDeviceContactsToClear": "Nema kontakata uređaja za brisanje."
|
||||
}
|
||||
|
||||
@@ -1589,5 +1589,304 @@
|
||||
"rawDump": "Dump grezzo",
|
||||
"cannotRetryMissingRecipient": "Impossibile riprovare: informazioni destinatario mancanti",
|
||||
"voiceUnavailable": "Voce non disponibile al momento",
|
||||
"requestingVoice": "Richiesta voce"
|
||||
"requestingVoice": "Richiesta voce",
|
||||
"device": "dispositivo",
|
||||
"change": "Modifica",
|
||||
"wizardOverviewDescription": "Questa app riunisce messaggistica MeshCore, aggiornamenti SAR sul campo, mappe e strumenti del dispositivo in un unico posto.",
|
||||
"wizardOverviewFeature1": "Invia messaggi diretti, post nelle stanze e messaggi di canale dalla scheda principale Messaggi.",
|
||||
"wizardOverviewFeature2": "Condividi marcatori SAR, disegni sulla mappa, clip vocali e immagini sulla mesh.",
|
||||
"wizardOverviewFeature3": "Connettiti tramite BLE o TCP e poi gestisci la radio companion direttamente dall'app.",
|
||||
"wizardMessagingTitle": "Messaggistica e rapporti sul campo",
|
||||
"wizardMessagingDescription": "Qui i messaggi sono più del semplice testo. L'app supporta già diversi payload operativi e flussi di trasferimento.",
|
||||
"wizardMessagingFeature1": "Invia messaggi diretti, post nelle stanze e traffico di canale da un unico compositore.",
|
||||
"wizardMessagingFeature2": "Crea aggiornamenti SAR e modelli SAR riutilizzabili per i rapporti sul campo più comuni.",
|
||||
"wizardMessagingFeature3": "Trasferisci sessioni vocali e immagini, con avanzamento e stime del tempo d'aria nell'interfaccia.",
|
||||
"wizardConnectDeviceTitle": "Connetti dispositivo",
|
||||
"wizardConnectDeviceDescription": "Connetti la tua radio MeshCore, scegli un nome e applica un preset radio prima di continuare.",
|
||||
"wizardSetupBadge": "Configurazione",
|
||||
"wizardOverviewBadge": "Panoramica",
|
||||
"wizardConnectedToDevice": "Connesso a {deviceName}",
|
||||
"wizardNoDeviceConnected": "Nessun dispositivo ancora connesso",
|
||||
"wizardSkipForNow": "Salta per ora",
|
||||
"wizardDeviceNameLabel": "Nome del dispositivo",
|
||||
"wizardDeviceNameHelp": "Questo nome viene annunciato agli altri utenti MeshCore.",
|
||||
"wizardConfigRegionLabel": "Regione di configurazione",
|
||||
"wizardConfigRegionHelp": "Usa l'elenco ufficiale completo dei preset MeshCore. Il valore predefinito è EU/UK (Narrow).",
|
||||
"wizardPresetNote1": "Assicurati che il preset selezionato corrisponda alle normative radio locali.",
|
||||
"wizardPresetNote2": "L'elenco corrisponde al feed ufficiale dei preset dello strumento di configurazione MeshCore.",
|
||||
"wizardPresetNote3": "EU/UK (Narrow) resta selezionato per impostazione predefinita durante l'onboarding.",
|
||||
"wizardSaving": "Salvataggio...",
|
||||
"wizardSaveAndContinue": "Salva e continua",
|
||||
"wizardEnterDeviceName": "Inserisci un nome dispositivo prima di continuare.",
|
||||
"wizardDeviceSetupSaved": "{deviceName} salvato con {presetName}.",
|
||||
"wizardNetworkTitle": "Contatti, stanze e ripetitori",
|
||||
"wizardNetworkDescription": "La scheda Contatti organizza la rete che scopri e i percorsi che apprendi nel tempo.",
|
||||
"wizardNetworkFeature1": "Controlla membri del team, ripetitori, stanze, canali e annunci in sospeso in un unico elenco.",
|
||||
"wizardNetworkFeature2": "Usa smart ping, accesso alle stanze, percorsi appresi e strumenti di reset delle route quando la connettività diventa complicata.",
|
||||
"wizardNetworkFeature3": "Crea canali e gestisci le destinazioni di rete senza uscire dall'app.",
|
||||
"wizardMapOpsTitle": "Mappa, tracce e geometria condivisa",
|
||||
"wizardMapOpsDescription": "La mappa dell'app è collegata direttamente a messaggistica, tracciamento e overlay SAR invece di essere un visualizzatore separato.",
|
||||
"wizardMapOpsFeature1": "Monitora la tua posizione, le posizioni dei compagni e le tracce di movimento sulla mappa.",
|
||||
"wizardMapOpsFeature2": "Apri i disegni dai messaggi, visualizzali in anteprima in linea e rimuovili dalla mappa quando serve.",
|
||||
"wizardMapOpsFeature3": "Usa le viste mappa dei ripetitori e gli overlay condivisi per capire la copertura della rete sul campo.",
|
||||
"wizardToolsTitle": "Strumenti oltre la messaggistica",
|
||||
"wizardToolsDescription": "Qui c'è più delle quattro schede principali. L'app include anche configurazione, diagnostica e flussi sensori opzionali.",
|
||||
"wizardToolsFeature1": "Apri la configurazione del dispositivo per cambiare impostazioni radio, telemetria, potenza TX e dettagli del companion.",
|
||||
"wizardToolsFeature2": "Abilita la scheda Sensori quando vuoi dashboard monitorate e azioni di aggiornamento rapido.",
|
||||
"wizardToolsFeature3": "Usa log dei pacchetti, scansione dello spettro e diagnostica sviluppatore per risolvere problemi della mesh.",
|
||||
"contactInSensors": "Nei Sensori",
|
||||
"contactAddToSensors": "Aggiungi ai Sensori",
|
||||
"contactSetPath": "Imposta percorso",
|
||||
"contactAddedToSensors": "{contactName} aggiunto a Sensori",
|
||||
"contactFailedToClearRoute": "Impossibile cancellare il percorso: {error}",
|
||||
"contactRouteCleared": "Percorso cancellato",
|
||||
"contactRouteSet": "Percorso impostato: {route}",
|
||||
"contactFailedToSetRoute": "Impossibile impostare il percorso: {error}",
|
||||
"rssi": "RSSI",
|
||||
"snr": "SNR",
|
||||
"ackTimeout": "Timeout ACK",
|
||||
"opcode": "Opcode",
|
||||
"payload": "Payload",
|
||||
"hops": "Hop",
|
||||
"hashSize": "Dimensione hash",
|
||||
"pathBytes": "Byte percorso",
|
||||
"selectedPath": "Percorso selezionato",
|
||||
"estimatedTx": "Trasmissione stimata",
|
||||
"senderToReceipt": "Mittente a ricevuta",
|
||||
"receivedCopies": "Copie ricevute",
|
||||
"retryCause": "Causa del ritentativo",
|
||||
"retryMode": "Modalità ritentativo",
|
||||
"retryResult": "Risultato ritentativo",
|
||||
"lastRetry": "Ultimo ritentativo",
|
||||
"rxPackets": "Pacchetti RX",
|
||||
"mesh": "Mesh",
|
||||
"rate": "Velocità",
|
||||
"window": "Finestra",
|
||||
"posttxDelay": "Ritardo post-tx",
|
||||
"bandpass": "Passa-banda",
|
||||
"bandpassFilterVoice": "Filtro passa-banda voce",
|
||||
"frequency": "Frequenza",
|
||||
"australia": "Australia",
|
||||
"australiaNarrow": "Australia (Stretto)",
|
||||
"australiaQld": "Australia: QLD",
|
||||
"australiaSaWa": "Australia: SA, WA",
|
||||
"newZealand": "Nuova Zelanda",
|
||||
"newZealandNarrow": "Nuova Zelanda (Stretto)",
|
||||
"switzerland": "Svizzera",
|
||||
"portugal433": "Portogallo 433",
|
||||
"portugal868": "Portogallo 868",
|
||||
"czechRepublicNarrow": "Repubblica Ceca (Stretto)",
|
||||
"eu433mhzLongRange": "UE 433MHz (Lungo Raggio)",
|
||||
"euukDeprecated": "UE/UK (Deprecato)",
|
||||
"euukNarrow": "UE/UK (Stretto)",
|
||||
"usacanadaRecommended": "USA/Canada (Consigliato)",
|
||||
"vietnamDeprecated": "Vietnam (Deprecato)",
|
||||
"vietnamNarrow": "Vietnam (Stretto)",
|
||||
"active": "Attivo",
|
||||
"addContact": "Aggiungi contatto",
|
||||
"all": "Tutto",
|
||||
"autoResolve": "Risolvi automaticamente",
|
||||
"clearAllLabel": "Cancella tutto",
|
||||
"clearRelays": "Cancella relay",
|
||||
"clearFilters": "Cancella filtri",
|
||||
"clearRoute": "Cancella percorso",
|
||||
"clearMessages": "Cancella messaggi",
|
||||
"clearScale": "Cancella scala",
|
||||
"clearDiscoveries": "Cancella scoperte",
|
||||
"clearOnlineTraceDatabase": "Cancella database tracce",
|
||||
"clearAllChannels": "Cancella tutti i canali",
|
||||
"clearAllContacts": "Cancella tutti i contatti",
|
||||
"clearChannels": "Cancella canali",
|
||||
"clearContacts": "Cancella contatti",
|
||||
"clearPathOnMaxRetry": "Cancella percorso al max ritentativo",
|
||||
"create": "Crea",
|
||||
"custom": "Personalizzato",
|
||||
"defaultValue": "Predefinito",
|
||||
"duplicate": "Duplica",
|
||||
"editName": "Modifica nome",
|
||||
"open": "Apri",
|
||||
"paste": "Incolla",
|
||||
"preview": "Anteprima",
|
||||
"remove": "Rimuovi",
|
||||
"rename": "Rinomina",
|
||||
"resolveAll": "Risolvi tutto",
|
||||
"send": "Invia",
|
||||
"sendAnyway": "Invia comunque",
|
||||
"share": "Condividi",
|
||||
"shareContact": "Condividi contatto",
|
||||
"trace": "Traccia",
|
||||
"use": "Usa",
|
||||
"useSelectedFrequency": "Usa frequenza selezionata",
|
||||
"discovery": "Scoperta",
|
||||
"discoverRepeaters": "Scopri ripetitori",
|
||||
"discoverSensors": "Scopri sensori",
|
||||
"repeaterDiscoverySent": "Scoperta ripetitori inviata",
|
||||
"sensorDiscoverySent": "Scoperta sensori inviata",
|
||||
"clearedPendingDiscoveries": "Scoperte in attesa cancellate.",
|
||||
"autoDiscovery": "Scoperta automatica",
|
||||
"enableAutomaticAdding": "Abilita aggiunta automatica",
|
||||
"autoaddRepeaters": "Aggiungi ripetitori automaticamente",
|
||||
"autoaddRoomServers": "Aggiungi server stanze automaticamente",
|
||||
"autoaddSensors": "Aggiungi sensori automaticamente",
|
||||
"autoaddUsers": "Aggiungi utenti automaticamente",
|
||||
"overwriteOldestWhenFull": "Sovrascrivi i più vecchi quando pieno",
|
||||
"storage": "Archiviazione",
|
||||
"dangerZone": "Zona pericolosa",
|
||||
"profiles": "Profili",
|
||||
"favourites": "Preferiti",
|
||||
"sensors": "Sensori",
|
||||
"others": "Altri",
|
||||
"gpsModule": "Modulo GPS",
|
||||
"liveTraffic": "Traffico in tempo reale",
|
||||
"repeatersMap": "Mappa ripetitori",
|
||||
"spectrumScan": "Scansione spettro",
|
||||
"blePacketLogs": "Log pacchetti BLE",
|
||||
"onlineTraceDatabase": "Database tracce",
|
||||
"routePathByteSize": "Dimensione percorso in byte",
|
||||
"messageNotifications": "Notifiche messaggi",
|
||||
"sarAlerts": "Avvisi SAR",
|
||||
"discoveryNotifications": "Notifiche scoperte",
|
||||
"updateNotifications": "Notifiche aggiornamenti",
|
||||
"muteWhileAppIsOpen": "Silenzia con l'app aperta",
|
||||
"disableContacts": "Disabilita contatti",
|
||||
"enableSensorsTab": "Abilita scheda Sensori",
|
||||
"enableProfiles": "Abilita profili",
|
||||
"autoRouteRotation": "Rotazione automatica percorso",
|
||||
"nearestRepeaterFallback": "Ripetitore più vicino come fallback",
|
||||
"deleteAllStoredMessageHistory": "Elimina tutta la cronologia messaggi",
|
||||
"messageFontSize": "Dimensione carattere messaggi",
|
||||
"rotateMapWithHeading": "Ruota mappa con direzione",
|
||||
"showMapDebugInfo": "Mostra info debug mappa",
|
||||
"openMapInFullscreen": "Apri mappa a schermo intero",
|
||||
"showSarMarkersLabel": "Mostra marcatori SAR",
|
||||
"displaySarMarkersOnTheMainMap": "Mostra marcatori SAR sulla mappa principale",
|
||||
"showAllContactTrailsLabel": "Mostra tutti i percorsi contatti",
|
||||
"hideRepeatersOnMap": "Nascondi ripetitori sulla mappa",
|
||||
"setMapScale": "Imposta scala mappa",
|
||||
"customMapScaleSaved": "Scala mappa personalizzata salvata",
|
||||
"voiceBitrate": "Bitrate voce",
|
||||
"voiceCompressor": "Compressore voce",
|
||||
"balancesQuietAndLoudSpeechLevels": "Bilancia i livelli di voce bassi e alti",
|
||||
"voiceLimiter": "Limitatore voce",
|
||||
"preventsClippingPeaksBeforeEncoding": "Previene il clipping prima della codifica",
|
||||
"micAutoGain": "Guadagno automatico microfono",
|
||||
"letsTheRecorderAdjustInputLevel": "Permette al registratore di regolare il livello",
|
||||
"echoCancellation": "Cancellazione eco",
|
||||
"noiseSuppression": "Soppressione rumore",
|
||||
"trimSilenceInVoiceMessages": "Taglia silenzio nei messaggi vocali",
|
||||
"compressor": "Compressore",
|
||||
"limiter": "Limitatore",
|
||||
"autoGain": "Guadagno automatico",
|
||||
"echoCancel": "Eco",
|
||||
"noiseSuppress": "Rumore",
|
||||
"silenceTrim": "Silenzio",
|
||||
"maxImageSize": "Dimensione massima immagine",
|
||||
"imageCompression": "Compressione immagine",
|
||||
"grayscale": "Scala di grigi",
|
||||
"ultraMode": "Modalità ultra",
|
||||
"fastPrivateGpsUpdates": "Aggiornamenti GPS privati veloci",
|
||||
"movementThreshold": "Soglia di movimento",
|
||||
"fastGpsMovementThreshold": "Soglia movimento GPS veloce",
|
||||
"fastGpsActiveuseInterval": "Intervallo uso attivo GPS veloce",
|
||||
"activeuseUpdateInterval": "Intervallo aggiornamento uso attivo",
|
||||
"repeatNearbyTraffic": "Ripeti traffico vicino",
|
||||
"relayThroughRepeatersAcrossTheMesh": "Inoltra tramite ripetitori nella rete",
|
||||
"nearbyOnlyWithoutRepeaterFlooding": "Solo vicini, senza flood dei ripetitori",
|
||||
"multihop": "Multi-hop",
|
||||
"createProfile": "Crea profilo",
|
||||
"renameProfile": "Rinomina profilo",
|
||||
"newProfile": "Nuovo profilo",
|
||||
"manageProfiles": "Gestisci profili",
|
||||
"enableProfilesToStartManagingThem": "Abilita i profili per iniziare a gestirli.",
|
||||
"openMessage": "Apri messaggio",
|
||||
"jumpToTheRelatedSarMessage": "Vai al messaggio SAR correlato",
|
||||
"removeSarMarker": "Rimuovi marcatore SAR",
|
||||
"pleaseSelectADestinationToSendSarMarker": "Seleziona una destinazione per inviare il marcatore SAR",
|
||||
"sarMarkerBroadcastToPublicChannel": "Marcatore SAR trasmesso al canale pubblico",
|
||||
"sarMarkerSentToRoom": "Marcatore SAR inviato alla stanza",
|
||||
"loadFromGallery": "Carica dalla galleria",
|
||||
"replaceImage": "Sostituisci immagine",
|
||||
"selectFromGallery": "Seleziona dalla galleria",
|
||||
"team": "Squadra",
|
||||
"found": "Trovato",
|
||||
"staging": "Area di raccolta",
|
||||
"object": "Oggetto",
|
||||
"quiet": "Silenzioso",
|
||||
"moderate": "Moderato",
|
||||
"busy": "Occupato",
|
||||
"spectrumScanReturnedNoCandidateFrequencies": "La scansione spettro non ha trovato frequenze candidate",
|
||||
"searchMessages": "Cerca messaggi",
|
||||
"sendImageFromGallery": "Invia immagine dalla galleria",
|
||||
"takePhoto": "Scatta foto",
|
||||
"dmOnly": "Solo messaggio diretto",
|
||||
"allMessages": "Tutti i messaggi",
|
||||
"sendToPublicChannel": "Inviare al canale pubblico?",
|
||||
"selectMarkerTypeAndDestination": "Seleziona tipo marcatore e destinazione",
|
||||
"noDestinationsAvailableLabel": "Nessuna destinazione disponibile",
|
||||
"image": "Immagine",
|
||||
"format": "Formato",
|
||||
"dimensions": "Dimensioni",
|
||||
"segments": "Segmenti",
|
||||
"transfers": "Trasferimenti",
|
||||
"downloadedBy": "Scaricato da",
|
||||
"saveDiscoverySettings": "Salva impostazioni scoperta",
|
||||
"savePublicInfo": "Salva info pubbliche",
|
||||
"saveRadioSettings": "Salva impostazioni radio",
|
||||
"savePath": "Salva percorso",
|
||||
"wipeDeviceData": "Cancella dati dispositivo",
|
||||
"wipeDevice": "Cancella dispositivo",
|
||||
"destructiveDeviceActions": "Azioni distruttive del dispositivo.",
|
||||
"chooseAPresetOrFinetuneCustomRadioSettings": "Scegli un preset o personalizza le impostazioni radio.",
|
||||
"chooseTheNameAndLocationThisDeviceShares": "Scegli il nome e la posizione condivisi da questo dispositivo.",
|
||||
"availableSpaceOnThisDevice": "Spazio disponibile su questo dispositivo.",
|
||||
"used": "Usato",
|
||||
"total": "Totale",
|
||||
"renameValue": "Rinomina valore",
|
||||
"customizeFields": "Personalizza campi",
|
||||
"livePreview": "Anteprima dal vivo",
|
||||
"refreshSchedule": "Programma aggiornamento",
|
||||
"noResponse": "Nessuna risposta",
|
||||
"refreshing": "Aggiornamento",
|
||||
"unavailable": "Non disponibile",
|
||||
"pickARelayOrNodeToWatchInSensors": "Scegli un relay o nodo da monitorare.",
|
||||
"publicKeyLabel": "Chiave pubblica",
|
||||
"alreadyInContacts": "Già nei contatti",
|
||||
"connectToADeviceBeforeAddingContacts": "Connettiti a un dispositivo prima di aggiungere contatti",
|
||||
"fromContacts": "Dai contatti",
|
||||
"onlineOnly": "Solo online",
|
||||
"inBoth": "In entrambi",
|
||||
"source": "Origine",
|
||||
"manualRouteEdit": "Modifica manuale percorso",
|
||||
"observedMeshRoute": "Percorso mesh osservato",
|
||||
"allMessagesCleared": "Tutti i messaggi cancellati",
|
||||
"onlineTraceDatabaseCleared": "Database tracce cancellata",
|
||||
"packetLogsCleared": "Log pacchetti cancellati",
|
||||
"hexDataCopiedToClipboard": "Dati hex copiati negli appunti",
|
||||
"developerModeEnabled": "Modalità sviluppatore attivata",
|
||||
"developerModeDisabled": "Modalità sviluppatore disattivata",
|
||||
"clipboardIsEmpty": "Gli appunti sono vuoti",
|
||||
"contactImported": "Contatto importato",
|
||||
"contactLinkCopiedToClipboard": "Link contatto copiato negli appunti",
|
||||
"failedToExportContact": "Esportazione contatto fallita",
|
||||
"noLogsToExport": "Nessun log da esportare",
|
||||
"exportAsCsv": "Esporta come CSV",
|
||||
"exportAsText": "Esporta come testo",
|
||||
"receivedRfc3339": "Ricevuto (RFC3339)",
|
||||
"buildTime": "Ora build",
|
||||
"downloadUrlNotAvailable": "URL di download non disponibile",
|
||||
"cannotOpenDownloadUrl": "Impossibile aprire URL di download",
|
||||
"updateCheckIsOnlyAvailableOnAndroid": "Il controllo aggiornamenti è disponibile solo su Android",
|
||||
"youAreRunningTheLatestVersion": "Stai utilizzando l'ultima versione",
|
||||
"updateAvailableButDownloadUrlNotFound": "Aggiornamento disponibile ma URL di download non trovato",
|
||||
"startTictactoe": "Avvia Tic-Tac-Toe",
|
||||
"tictactoeUnavailable": "Tic-Tac-Toe non disponibile",
|
||||
"tictactoeOpponentUnknown": "Tic-Tac-Toe: avversario sconosciuto",
|
||||
"tictactoeWaitingForStart": "Tic-Tac-Toe: in attesa dell'inizio",
|
||||
"acceptsShareLinks": "Accetta link condivisi",
|
||||
"supportsRawHex": "Supporta hex grezzo",
|
||||
"clipboardfriendly": "Compatibile con appunti",
|
||||
"captured": "Catturato",
|
||||
"size": "Dimensione",
|
||||
"noCustomChannelsToClear": "Nessun canale personalizzato da cancellare.",
|
||||
"noDeviceContactsToClear": "Nessun contatto dispositivo da cancellare."
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1734,4 +1734,968 @@ class AppLocalizationsDe extends AppLocalizations {
|
||||
|
||||
@override
|
||||
String get requestingVoice => 'Sprachnachricht wird angefordert';
|
||||
|
||||
@override
|
||||
String get device => 'Gerät';
|
||||
|
||||
@override
|
||||
String get change => 'Ändern';
|
||||
|
||||
@override
|
||||
String get wizardOverviewDescription =>
|
||||
'Diese App vereint MeshCore-Nachrichten, SAR-Feldupdates, Karten und Gerätetools an einem Ort.';
|
||||
|
||||
@override
|
||||
String get wizardOverviewFeature1 =>
|
||||
'Sende Direkt-, Raum- und Kanalnachrichten aus dem Haupttab \"Nachrichten\".';
|
||||
|
||||
@override
|
||||
String get wizardOverviewFeature2 =>
|
||||
'Teile SAR-Markierungen, Kartenzeichnungen, Sprachclips und Bilder über das Mesh.';
|
||||
|
||||
@override
|
||||
String get wizardOverviewFeature3 =>
|
||||
'Verbinde dich per BLE oder TCP und verwalte dann das Begleitfunkgerät direkt in der App.';
|
||||
|
||||
@override
|
||||
String get wizardMessagingTitle => 'Nachrichten und Feldberichte';
|
||||
|
||||
@override
|
||||
String get wizardMessagingDescription =>
|
||||
'Nachrichten sind hier mehr als nur Klartext. Die App unterstützt bereits mehrere operative Datentypen und Übertragungsabläufe.';
|
||||
|
||||
@override
|
||||
String get wizardMessagingFeature1 =>
|
||||
'Sende Direktnachrichten, Raumbeiträge und Kanalverkehr aus einem einzigen Editor.';
|
||||
|
||||
@override
|
||||
String get wizardMessagingFeature2 =>
|
||||
'Erstelle SAR-Updates und wiederverwendbare SAR-Vorlagen für häufige Feldberichte.';
|
||||
|
||||
@override
|
||||
String get wizardMessagingFeature3 =>
|
||||
'Übertrage Sprachsitzungen und Bilder mit Fortschritts- und Airtime-Anzeigen in der Oberfläche.';
|
||||
|
||||
@override
|
||||
String get wizardConnectDeviceTitle => 'Gerät verbinden';
|
||||
|
||||
@override
|
||||
String get wizardConnectDeviceDescription =>
|
||||
'Verbinde dein MeshCore-Funkgerät, wähle einen Namen und wende ein Funkprofil an, bevor du fortfährst.';
|
||||
|
||||
@override
|
||||
String get wizardSetupBadge => 'Einrichtung';
|
||||
|
||||
@override
|
||||
String get wizardOverviewBadge => 'Überblick';
|
||||
|
||||
@override
|
||||
String wizardConnectedToDevice(String deviceName) {
|
||||
return 'Verbunden mit $deviceName';
|
||||
}
|
||||
|
||||
@override
|
||||
String get wizardNoDeviceConnected => 'Noch kein Gerät verbunden';
|
||||
|
||||
@override
|
||||
String get wizardSkipForNow => 'Vorerst überspringen';
|
||||
|
||||
@override
|
||||
String get wizardDeviceNameLabel => 'Gerätename';
|
||||
|
||||
@override
|
||||
String get wizardDeviceNameHelp =>
|
||||
'Dieser Name wird an andere MeshCore-Nutzer ausgesendet.';
|
||||
|
||||
@override
|
||||
String get wizardConfigRegionLabel => 'Konfigurationsregion';
|
||||
|
||||
@override
|
||||
String get wizardConfigRegionHelp =>
|
||||
'Verwendet die vollständige offizielle Liste der MeshCore-Voreinstellungen. Standard ist EU/UK (Narrow).';
|
||||
|
||||
@override
|
||||
String get wizardPresetNote1 =>
|
||||
'Stelle sicher, dass die ausgewählte Voreinstellung zu deinen lokalen Funkvorschriften passt.';
|
||||
|
||||
@override
|
||||
String get wizardPresetNote2 =>
|
||||
'Die Liste entspricht dem offiziellen Preset-Feed des MeshCore-Konfigurationstools.';
|
||||
|
||||
@override
|
||||
String get wizardPresetNote3 =>
|
||||
'EU/UK (Narrow) bleibt standardmäßig für das Onboarding ausgewählt.';
|
||||
|
||||
@override
|
||||
String get wizardSaving => 'Wird gespeichert...';
|
||||
|
||||
@override
|
||||
String get wizardSaveAndContinue => 'Speichern und fortfahren';
|
||||
|
||||
@override
|
||||
String get wizardEnterDeviceName =>
|
||||
'Gib vor dem Fortfahren einen Gerätenamen ein.';
|
||||
|
||||
@override
|
||||
String wizardDeviceSetupSaved(String deviceName, String presetName) {
|
||||
return '$deviceName mit $presetName gespeichert.';
|
||||
}
|
||||
|
||||
@override
|
||||
String get wizardNetworkTitle => 'Kontakte, Räume und Repeater';
|
||||
|
||||
@override
|
||||
String get wizardNetworkDescription =>
|
||||
'Der Kontakte-Tab organisiert das Netzwerk, das du entdeckst, und die Routen, die im Laufe der Zeit gelernt werden.';
|
||||
|
||||
@override
|
||||
String get wizardNetworkFeature1 =>
|
||||
'Prüfe Teammitglieder, Repeater, Räume, Kanäle und ausstehende Adverts in einer Liste.';
|
||||
|
||||
@override
|
||||
String get wizardNetworkFeature2 =>
|
||||
'Nutze Smart Ping, Raum-Login, gelernte Pfade und Pfad-Reset-Tools, wenn die Verbindung unübersichtlich wird.';
|
||||
|
||||
@override
|
||||
String get wizardNetworkFeature3 =>
|
||||
'Erstelle Kanäle und verwalte Netzwerkziele, ohne die App zu verlassen.';
|
||||
|
||||
@override
|
||||
String get wizardMapOpsTitle => 'Karte, Spuren und geteilte Geometrie';
|
||||
|
||||
@override
|
||||
String get wizardMapOpsDescription =>
|
||||
'Die Karte der App ist direkt mit Nachrichten, Tracking und SAR-Overlays verbunden, statt nur ein separater Viewer zu sein.';
|
||||
|
||||
@override
|
||||
String get wizardMapOpsFeature1 =>
|
||||
'Verfolge deine eigene Position, Teamstandorte und Bewegungsspuren auf der Karte.';
|
||||
|
||||
@override
|
||||
String get wizardMapOpsFeature2 =>
|
||||
'Öffne Zeichnungen aus Nachrichten, prüfe sie inline und entferne sie bei Bedarf von der Karte.';
|
||||
|
||||
@override
|
||||
String get wizardMapOpsFeature3 =>
|
||||
'Nutze Repeater-Kartenansichten und geteilte Overlays, um die Netzabdeckung im Feld zu verstehen.';
|
||||
|
||||
@override
|
||||
String get wizardToolsTitle => 'Werkzeuge jenseits von Nachrichten';
|
||||
|
||||
@override
|
||||
String get wizardToolsDescription =>
|
||||
'Es gibt hier mehr als nur die vier Haupttabs. Die App enthält außerdem Konfiguration, Diagnose und optionale Sensor-Workflows.';
|
||||
|
||||
@override
|
||||
String get wizardToolsFeature1 =>
|
||||
'Öffne die Gerätekonfiguration, um Funkparameter, Telemetrie, Sendeleistung und Begleitdetails zu ändern.';
|
||||
|
||||
@override
|
||||
String get wizardToolsFeature2 =>
|
||||
'Aktiviere den Sensoren-Tab, wenn du überwachte Sensor-Dashboards und schnelle Aktualisierungen möchtest.';
|
||||
|
||||
@override
|
||||
String get wizardToolsFeature3 =>
|
||||
'Nutze Paketprotokolle, Spektrumsuche und Entwicklerdiagnosen zur Fehlersuche im Mesh.';
|
||||
|
||||
@override
|
||||
String get contactInSensors => 'In Sensoren';
|
||||
|
||||
@override
|
||||
String get contactAddToSensors => 'Zu Sensoren hinzufügen';
|
||||
|
||||
@override
|
||||
String get contactSetPath => 'Pfad festlegen';
|
||||
|
||||
@override
|
||||
String contactAddedToSensors(String contactName) {
|
||||
return '$contactName zu Sensoren hinzugefügt';
|
||||
}
|
||||
|
||||
@override
|
||||
String contactFailedToClearRoute(String error) {
|
||||
return 'Pfad konnte nicht gelöscht werden: $error';
|
||||
}
|
||||
|
||||
@override
|
||||
String get contactRouteCleared => 'Pfad gelöscht';
|
||||
|
||||
@override
|
||||
String contactRouteSet(String route) {
|
||||
return 'Pfad gesetzt: $route';
|
||||
}
|
||||
|
||||
@override
|
||||
String contactFailedToSetRoute(String error) {
|
||||
return 'Pfad konnte nicht gesetzt werden: $error';
|
||||
}
|
||||
|
||||
@override
|
||||
String get rssi => 'RSSI';
|
||||
|
||||
@override
|
||||
String get snr => 'SNR';
|
||||
|
||||
@override
|
||||
String get ackTimeout => 'ACK-Zeitüberschreitung';
|
||||
|
||||
@override
|
||||
String get opcode => 'Opcode';
|
||||
|
||||
@override
|
||||
String get payload => 'Nutzlast';
|
||||
|
||||
@override
|
||||
String get hops => 'Hops';
|
||||
|
||||
@override
|
||||
String get hashSize => 'Hash-Größe';
|
||||
|
||||
@override
|
||||
String get pathBytes => 'Pfad-Bytes';
|
||||
|
||||
@override
|
||||
String get selectedPath => 'Ausgewählter Pfad';
|
||||
|
||||
@override
|
||||
String get estimatedTx => 'Geschätzte Sendung';
|
||||
|
||||
@override
|
||||
String get senderToReceipt => 'Sender bis Empfang';
|
||||
|
||||
@override
|
||||
String get receivedCopies => 'Empfangene Kopien';
|
||||
|
||||
@override
|
||||
String get retryCause => 'Wiederholungsursache';
|
||||
|
||||
@override
|
||||
String get retryMode => 'Wiederholungsmodus';
|
||||
|
||||
@override
|
||||
String get retryResult => 'Wiederholungsergebnis';
|
||||
|
||||
@override
|
||||
String get lastRetry => 'Letzter Versuch';
|
||||
|
||||
@override
|
||||
String get rxPackets => 'RX-Pakete';
|
||||
|
||||
@override
|
||||
String get mesh => 'Mesh';
|
||||
|
||||
@override
|
||||
String get rate => 'Rate';
|
||||
|
||||
@override
|
||||
String get window => 'Fenster';
|
||||
|
||||
@override
|
||||
String get posttxDelay => 'Sendeverzögerung';
|
||||
|
||||
@override
|
||||
String get bandpass => 'Bandpass';
|
||||
|
||||
@override
|
||||
String get bandpassFilterVoice => 'Bandpassfilter Sprache';
|
||||
|
||||
@override
|
||||
String get frequency => 'Frequenz';
|
||||
|
||||
@override
|
||||
String get australia => 'Australien';
|
||||
|
||||
@override
|
||||
String get australiaNarrow => 'Australien (Schmal)';
|
||||
|
||||
@override
|
||||
String get australiaQld => 'Australien: QLD';
|
||||
|
||||
@override
|
||||
String get australiaSaWa => 'Australien: SA, WA';
|
||||
|
||||
@override
|
||||
String get newZealand => 'Neuseeland';
|
||||
|
||||
@override
|
||||
String get newZealandNarrow => 'Neuseeland (Schmal)';
|
||||
|
||||
@override
|
||||
String get switzerland => 'Schweiz';
|
||||
|
||||
@override
|
||||
String get portugal433 => 'Portugal 433';
|
||||
|
||||
@override
|
||||
String get portugal868 => 'Portugal 868';
|
||||
|
||||
@override
|
||||
String get czechRepublicNarrow => 'Tschechien (Schmal)';
|
||||
|
||||
@override
|
||||
String get eu433mhzLongRange => 'EU 433MHz (Langstrecke)';
|
||||
|
||||
@override
|
||||
String get euukDeprecated => 'EU/UK (Veraltet)';
|
||||
|
||||
@override
|
||||
String get euukNarrow => 'EU/UK (Schmal)';
|
||||
|
||||
@override
|
||||
String get usacanadaRecommended => 'USA/Kanada (Empfohlen)';
|
||||
|
||||
@override
|
||||
String get vietnamDeprecated => 'Vietnam (Veraltet)';
|
||||
|
||||
@override
|
||||
String get vietnamNarrow => 'Vietnam (Schmal)';
|
||||
|
||||
@override
|
||||
String get active => 'Aktiv';
|
||||
|
||||
@override
|
||||
String get addContact => 'Kontakt hinzufügen';
|
||||
|
||||
@override
|
||||
String get all => 'Alle';
|
||||
|
||||
@override
|
||||
String get autoResolve => 'Automatisch auflösen';
|
||||
|
||||
@override
|
||||
String get clearAllLabel => 'Alle löschen';
|
||||
|
||||
@override
|
||||
String get clearRelays => 'Relais löschen';
|
||||
|
||||
@override
|
||||
String get clearFilters => 'Filter löschen';
|
||||
|
||||
@override
|
||||
String get clearRoute => 'Route löschen';
|
||||
|
||||
@override
|
||||
String get clearMessages => 'Nachrichten löschen';
|
||||
|
||||
@override
|
||||
String get clearScale => 'Maßstab löschen';
|
||||
|
||||
@override
|
||||
String get clearDiscoveries => 'Entdeckungen löschen';
|
||||
|
||||
@override
|
||||
String get clearOnlineTraceDatabase => 'Online-Trace-Datenbank löschen';
|
||||
|
||||
@override
|
||||
String get clearAllChannels => 'Alle Kanäle löschen';
|
||||
|
||||
@override
|
||||
String get clearAllContacts => 'Alle Kontakte löschen';
|
||||
|
||||
@override
|
||||
String get clearChannels => 'Kanäle löschen';
|
||||
|
||||
@override
|
||||
String get clearContacts => 'Kontakte löschen';
|
||||
|
||||
@override
|
||||
String get clearPathOnMaxRetry => 'Pfad bei max. Wiederholung löschen';
|
||||
|
||||
@override
|
||||
String get create => 'Erstellen';
|
||||
|
||||
@override
|
||||
String get custom => 'Benutzerdefiniert';
|
||||
|
||||
@override
|
||||
String get defaultValue => 'Standard';
|
||||
|
||||
@override
|
||||
String get duplicate => 'Duplizieren';
|
||||
|
||||
@override
|
||||
String get editName => 'Name bearbeiten';
|
||||
|
||||
@override
|
||||
String get open => 'Öffnen';
|
||||
|
||||
@override
|
||||
String get paste => 'Einfügen';
|
||||
|
||||
@override
|
||||
String get preview => 'Vorschau';
|
||||
|
||||
@override
|
||||
String get remove => 'Entfernen';
|
||||
|
||||
@override
|
||||
String get rename => 'Umbenennen';
|
||||
|
||||
@override
|
||||
String get resolveAll => 'Alle auflösen';
|
||||
|
||||
@override
|
||||
String get send => 'Senden';
|
||||
|
||||
@override
|
||||
String get sendAnyway => 'Trotzdem senden';
|
||||
|
||||
@override
|
||||
String get share => 'Teilen';
|
||||
|
||||
@override
|
||||
String get shareContact => 'Kontakt teilen';
|
||||
|
||||
@override
|
||||
String get trace => 'Trace';
|
||||
|
||||
@override
|
||||
String get use => 'Verwenden';
|
||||
|
||||
@override
|
||||
String get useSelectedFrequency => 'Ausgewählte Frequenz verwenden';
|
||||
|
||||
@override
|
||||
String get discovery => 'Erkennung';
|
||||
|
||||
@override
|
||||
String get discoverRepeaters => 'Repeater entdecken';
|
||||
|
||||
@override
|
||||
String get discoverSensors => 'Sensoren entdecken';
|
||||
|
||||
@override
|
||||
String get repeaterDiscoverySent => 'Repeater-Erkennung gesendet';
|
||||
|
||||
@override
|
||||
String get sensorDiscoverySent => 'Sensor-Erkennung gesendet';
|
||||
|
||||
@override
|
||||
String get clearedPendingDiscoveries => 'Ausstehende Entdeckungen gelöscht.';
|
||||
|
||||
@override
|
||||
String get autoDiscovery => 'Automatische Erkennung';
|
||||
|
||||
@override
|
||||
String get enableAutomaticAdding => 'Automatisches Hinzufügen aktivieren';
|
||||
|
||||
@override
|
||||
String get autoaddRepeaters => 'Repeater automatisch hinzufügen';
|
||||
|
||||
@override
|
||||
String get autoaddRoomServers => 'Raumserver automatisch hinzufügen';
|
||||
|
||||
@override
|
||||
String get autoaddSensors => 'Sensoren automatisch hinzufügen';
|
||||
|
||||
@override
|
||||
String get autoaddUsers => 'Benutzer automatisch hinzufügen';
|
||||
|
||||
@override
|
||||
String get overwriteOldestWhenFull =>
|
||||
'Älteste bei vollem Speicher überschreiben';
|
||||
|
||||
@override
|
||||
String get storage => 'Speicher';
|
||||
|
||||
@override
|
||||
String get dangerZone => 'Gefahrenbereich';
|
||||
|
||||
@override
|
||||
String get profiles => 'Profile';
|
||||
|
||||
@override
|
||||
String get favourites => 'Favoriten';
|
||||
|
||||
@override
|
||||
String get sensors => 'Sensoren';
|
||||
|
||||
@override
|
||||
String get others => 'Andere';
|
||||
|
||||
@override
|
||||
String get gpsModule => 'GPS-Modul';
|
||||
|
||||
@override
|
||||
String get liveTraffic => 'Live-Verkehr';
|
||||
|
||||
@override
|
||||
String get repeatersMap => 'Repeater-Karte';
|
||||
|
||||
@override
|
||||
String get spectrumScan => 'Spektrum-Scan';
|
||||
|
||||
@override
|
||||
String get blePacketLogs => 'BLE-Paketprotokolle';
|
||||
|
||||
@override
|
||||
String get onlineTraceDatabase => 'Online-Trace-Datenbank';
|
||||
|
||||
@override
|
||||
String get routePathByteSize => 'Pfad-Bytegröße';
|
||||
|
||||
@override
|
||||
String get messageNotifications => 'Nachrichtenbenachrichtigungen';
|
||||
|
||||
@override
|
||||
String get sarAlerts => 'SAR-Alarme';
|
||||
|
||||
@override
|
||||
String get discoveryNotifications => 'Erkennungsbenachrichtigungen';
|
||||
|
||||
@override
|
||||
String get updateNotifications => 'Updatebenachrichtigungen';
|
||||
|
||||
@override
|
||||
String get muteWhileAppIsOpen => 'Stumm bei geöffneter App';
|
||||
|
||||
@override
|
||||
String get disableContacts => 'Kontakte deaktivieren';
|
||||
|
||||
@override
|
||||
String get enableSensorsTab => 'Sensoren-Tab aktivieren';
|
||||
|
||||
@override
|
||||
String get enableProfiles => 'Profile aktivieren';
|
||||
|
||||
@override
|
||||
String get autoRouteRotation => 'Automatische Routenrotation';
|
||||
|
||||
@override
|
||||
String get nearestRepeaterFallback => 'Nächster Repeater als Fallback';
|
||||
|
||||
@override
|
||||
String get deleteAllStoredMessageHistory =>
|
||||
'Gesamten Nachrichtenverlauf löschen';
|
||||
|
||||
@override
|
||||
String get messageFontSize => 'Nachrichtenschriftgröße';
|
||||
|
||||
@override
|
||||
String get rotateMapWithHeading => 'Karte mit Richtung drehen';
|
||||
|
||||
@override
|
||||
String get showMapDebugInfo => 'Karten-Debug-Info anzeigen';
|
||||
|
||||
@override
|
||||
String get openMapInFullscreen => 'Karte im Vollbild öffnen';
|
||||
|
||||
@override
|
||||
String get showSarMarkersLabel => 'SAR-Markierungen anzeigen';
|
||||
|
||||
@override
|
||||
String get displaySarMarkersOnTheMainMap =>
|
||||
'SAR-Markierungen auf der Hauptkarte anzeigen';
|
||||
|
||||
@override
|
||||
String get showAllContactTrailsLabel => 'Alle Kontaktpfade anzeigen';
|
||||
|
||||
@override
|
||||
String get hideRepeatersOnMap => 'Repeater auf Karte ausblenden';
|
||||
|
||||
@override
|
||||
String get setMapScale => 'Kartenmaßstab einstellen';
|
||||
|
||||
@override
|
||||
String get customMapScaleSaved =>
|
||||
'Benutzerdefinierter Kartenmaßstab gespeichert';
|
||||
|
||||
@override
|
||||
String get voiceBitrate => 'Sprach-Bitrate';
|
||||
|
||||
@override
|
||||
String get voiceCompressor => 'Sprachkompressor';
|
||||
|
||||
@override
|
||||
String get balancesQuietAndLoudSpeechLevels =>
|
||||
'Gleicht leise und laute Sprache aus';
|
||||
|
||||
@override
|
||||
String get voiceLimiter => 'Sprach-Limiter';
|
||||
|
||||
@override
|
||||
String get preventsClippingPeaksBeforeEncoding =>
|
||||
'Verhindert Clipping-Spitzen vor der Kodierung';
|
||||
|
||||
@override
|
||||
String get micAutoGain => 'Mikrofon-Automatik';
|
||||
|
||||
@override
|
||||
String get letsTheRecorderAdjustInputLevel =>
|
||||
'Lässt den Rekorder den Eingangspegel anpassen';
|
||||
|
||||
@override
|
||||
String get echoCancellation => 'Echounterdrückung';
|
||||
|
||||
@override
|
||||
String get noiseSuppression => 'Rauschunterdrückung';
|
||||
|
||||
@override
|
||||
String get trimSilenceInVoiceMessages =>
|
||||
'Stille in Sprachnachrichten trimmen';
|
||||
|
||||
@override
|
||||
String get compressor => 'Kompressor';
|
||||
|
||||
@override
|
||||
String get limiter => 'Limiter';
|
||||
|
||||
@override
|
||||
String get autoGain => 'Automatische Verstärkung';
|
||||
|
||||
@override
|
||||
String get echoCancel => 'Echo';
|
||||
|
||||
@override
|
||||
String get noiseSuppress => 'Rauschen';
|
||||
|
||||
@override
|
||||
String get silenceTrim => 'Stille';
|
||||
|
||||
@override
|
||||
String get maxImageSize => 'Maximale Bildgröße';
|
||||
|
||||
@override
|
||||
String get imageCompression => 'Bildkomprimierung';
|
||||
|
||||
@override
|
||||
String get grayscale => 'Graustufen';
|
||||
|
||||
@override
|
||||
String get ultraMode => 'Ultra-Modus';
|
||||
|
||||
@override
|
||||
String get fastPrivateGpsUpdates => 'Schnelle private GPS-Updates';
|
||||
|
||||
@override
|
||||
String get movementThreshold => 'Bewegungsschwelle';
|
||||
|
||||
@override
|
||||
String get fastGpsMovementThreshold => 'Schneller GPS-Bewegungsschwellenwert';
|
||||
|
||||
@override
|
||||
String get fastGpsActiveuseInterval =>
|
||||
'Schnelles GPS aktive Nutzung Intervall';
|
||||
|
||||
@override
|
||||
String get activeuseUpdateInterval => 'Aktive Nutzung Updateintervall';
|
||||
|
||||
@override
|
||||
String get repeatNearbyTraffic => 'Nahverkehr wiederholen';
|
||||
|
||||
@override
|
||||
String get relayThroughRepeatersAcrossTheMesh =>
|
||||
'Über Repeater im Mesh weiterleiten';
|
||||
|
||||
@override
|
||||
String get nearbyOnlyWithoutRepeaterFlooding =>
|
||||
'Nur in der Nähe, ohne Repeater-Flooding';
|
||||
|
||||
@override
|
||||
String get multihop => 'Multi-Hop';
|
||||
|
||||
@override
|
||||
String get createProfile => 'Profil erstellen';
|
||||
|
||||
@override
|
||||
String get renameProfile => 'Profil umbenennen';
|
||||
|
||||
@override
|
||||
String get newProfile => 'Neues Profil';
|
||||
|
||||
@override
|
||||
String get manageProfiles => 'Profile verwalten';
|
||||
|
||||
@override
|
||||
String get enableProfilesToStartManagingThem =>
|
||||
'Aktivieren Sie Profile, um sie zu verwalten.';
|
||||
|
||||
@override
|
||||
String get openMessage => 'Nachricht öffnen';
|
||||
|
||||
@override
|
||||
String get jumpToTheRelatedSarMessage =>
|
||||
'Zur zugehörigen SAR-Nachricht springen';
|
||||
|
||||
@override
|
||||
String get removeSarMarker => 'SAR-Markierung entfernen';
|
||||
|
||||
@override
|
||||
String get pleaseSelectADestinationToSendSarMarker =>
|
||||
'Bitte wählen Sie ein Ziel zum Senden der SAR-Markierung';
|
||||
|
||||
@override
|
||||
String get sarMarkerBroadcastToPublicChannel =>
|
||||
'SAR-Markierung an öffentlichen Kanal gesendet';
|
||||
|
||||
@override
|
||||
String get sarMarkerSentToRoom => 'SAR-Markierung an Raum gesendet';
|
||||
|
||||
@override
|
||||
String get loadFromGallery => 'Aus Galerie laden';
|
||||
|
||||
@override
|
||||
String get replaceImage => 'Bild ersetzen';
|
||||
|
||||
@override
|
||||
String get selectFromGallery => 'Aus Galerie auswählen';
|
||||
|
||||
@override
|
||||
String get team => 'Team';
|
||||
|
||||
@override
|
||||
String get found => 'Gefunden';
|
||||
|
||||
@override
|
||||
String get staging => 'Sammelpunkt';
|
||||
|
||||
@override
|
||||
String get object => 'Objekt';
|
||||
|
||||
@override
|
||||
String get quiet => 'Ruhig';
|
||||
|
||||
@override
|
||||
String get moderate => 'Mäßig';
|
||||
|
||||
@override
|
||||
String get busy => 'Beschäftigt';
|
||||
|
||||
@override
|
||||
String get spectrumScanReturnedNoCandidateFrequencies =>
|
||||
'Spektrum-Scan hat keine Kandidatenfrequenzen gefunden';
|
||||
|
||||
@override
|
||||
String get searchMessages => 'Nachrichten suchen';
|
||||
|
||||
@override
|
||||
String get sendImageFromGallery => 'Bild aus Galerie senden';
|
||||
|
||||
@override
|
||||
String get takePhoto => 'Foto aufnehmen';
|
||||
|
||||
@override
|
||||
String get dmOnly => 'Nur Direktnachricht';
|
||||
|
||||
@override
|
||||
String get allMessages => 'Alle Nachrichten';
|
||||
|
||||
@override
|
||||
String get sendToPublicChannel => 'An öffentlichen Kanal senden?';
|
||||
|
||||
@override
|
||||
String get selectMarkerTypeAndDestination =>
|
||||
'Markierungstyp und Ziel auswählen';
|
||||
|
||||
@override
|
||||
String get noDestinationsAvailableLabel => 'Keine Ziele verfügbar';
|
||||
|
||||
@override
|
||||
String get image => 'Bild';
|
||||
|
||||
@override
|
||||
String get format => 'Format';
|
||||
|
||||
@override
|
||||
String get dimensions => 'Abmessungen';
|
||||
|
||||
@override
|
||||
String get segments => 'Segmente';
|
||||
|
||||
@override
|
||||
String get transfers => 'Übertragungen';
|
||||
|
||||
@override
|
||||
String get downloadedBy => 'Heruntergeladen von';
|
||||
|
||||
@override
|
||||
String get saveDiscoverySettings => 'Erkennungseinstellungen speichern';
|
||||
|
||||
@override
|
||||
String get savePublicInfo => 'Öffentliche Info speichern';
|
||||
|
||||
@override
|
||||
String get saveRadioSettings => 'Funkeinstellungen speichern';
|
||||
|
||||
@override
|
||||
String get savePath => 'Pfad speichern';
|
||||
|
||||
@override
|
||||
String get wipeDeviceData => 'Gerätedaten löschen';
|
||||
|
||||
@override
|
||||
String get wipeDevice => 'Gerät löschen';
|
||||
|
||||
@override
|
||||
String get destructiveDeviceActions => 'Destruktive Geräteaktionen.';
|
||||
|
||||
@override
|
||||
String get chooseAPresetOrFinetuneCustomRadioSettings =>
|
||||
'Wählen Sie eine Voreinstellung oder passen Sie die Funkeinstellungen an.';
|
||||
|
||||
@override
|
||||
String get chooseTheNameAndLocationThisDeviceShares =>
|
||||
'Wählen Sie den Namen und Standort, den dieses Gerät teilt.';
|
||||
|
||||
@override
|
||||
String get availableSpaceOnThisDevice =>
|
||||
'Verfügbarer Speicherplatz auf diesem Gerät.';
|
||||
|
||||
@override
|
||||
String get used => 'Belegt';
|
||||
|
||||
@override
|
||||
String get total => 'Gesamt';
|
||||
|
||||
@override
|
||||
String get renameValue => 'Wert umbenennen';
|
||||
|
||||
@override
|
||||
String get customizeFields => 'Felder anpassen';
|
||||
|
||||
@override
|
||||
String get livePreview => 'Live-Vorschau';
|
||||
|
||||
@override
|
||||
String get refreshSchedule => 'Aktualisierungsplan';
|
||||
|
||||
@override
|
||||
String get noResponse => 'Keine Antwort';
|
||||
|
||||
@override
|
||||
String get refreshing => 'Wird aktualisiert';
|
||||
|
||||
@override
|
||||
String get unavailable => 'Nicht verfügbar';
|
||||
|
||||
@override
|
||||
String get pickARelayOrNodeToWatchInSensors =>
|
||||
'Wählen Sie einen Relay oder Knoten zur Beobachtung.';
|
||||
|
||||
@override
|
||||
String get publicKeyLabel => 'Öffentlicher Schlüssel';
|
||||
|
||||
@override
|
||||
String get alreadyInContacts => 'Bereits in Kontakten';
|
||||
|
||||
@override
|
||||
String get connectToADeviceBeforeAddingContacts =>
|
||||
'Verbinden Sie sich mit einem Gerät, bevor Sie Kontakte hinzufügen';
|
||||
|
||||
@override
|
||||
String get fromContacts => 'Aus Kontakten';
|
||||
|
||||
@override
|
||||
String get onlineOnly => 'Nur online';
|
||||
|
||||
@override
|
||||
String get inBoth => 'In beiden';
|
||||
|
||||
@override
|
||||
String get source => 'Quelle';
|
||||
|
||||
@override
|
||||
String get manualRouteEdit => 'Manuelle Routenbearbeitung';
|
||||
|
||||
@override
|
||||
String get observedMeshRoute => 'Beobachtete Mesh-Route';
|
||||
|
||||
@override
|
||||
String get allMessagesCleared => 'Alle Nachrichten gelöscht';
|
||||
|
||||
@override
|
||||
String get onlineTraceDatabaseCleared => 'Online-Trace-Datenbank gelöscht';
|
||||
|
||||
@override
|
||||
String get packetLogsCleared => 'Paketprotokolle gelöscht';
|
||||
|
||||
@override
|
||||
String get hexDataCopiedToClipboard =>
|
||||
'Hex-Daten in die Zwischenablage kopiert';
|
||||
|
||||
@override
|
||||
String get developerModeEnabled => 'Entwicklermodus aktiviert';
|
||||
|
||||
@override
|
||||
String get developerModeDisabled => 'Entwicklermodus deaktiviert';
|
||||
|
||||
@override
|
||||
String get clipboardIsEmpty => 'Zwischenablage ist leer';
|
||||
|
||||
@override
|
||||
String get contactImported => 'Kontakt importiert';
|
||||
|
||||
@override
|
||||
String get contactLinkCopiedToClipboard =>
|
||||
'Kontaktlink in Zwischenablage kopiert';
|
||||
|
||||
@override
|
||||
String get failedToExportContact => 'Kontaktexport fehlgeschlagen';
|
||||
|
||||
@override
|
||||
String get noLogsToExport => 'Keine Protokolle zum Exportieren';
|
||||
|
||||
@override
|
||||
String get exportAsCsv => 'Als CSV exportieren';
|
||||
|
||||
@override
|
||||
String get exportAsText => 'Als Text exportieren';
|
||||
|
||||
@override
|
||||
String get receivedRfc3339 => 'Empfangen (RFC3339)';
|
||||
|
||||
@override
|
||||
String get buildTime => 'Build-Zeit';
|
||||
|
||||
@override
|
||||
String get downloadUrlNotAvailable => 'Download-URL nicht verfügbar';
|
||||
|
||||
@override
|
||||
String get cannotOpenDownloadUrl => 'Download-URL kann nicht geöffnet werden';
|
||||
|
||||
@override
|
||||
String get updateCheckIsOnlyAvailableOnAndroid =>
|
||||
'Update-Prüfung nur auf Android verfügbar';
|
||||
|
||||
@override
|
||||
String get youAreRunningTheLatestVersion =>
|
||||
'Sie verwenden die neueste Version';
|
||||
|
||||
@override
|
||||
String get updateAvailableButDownloadUrlNotFound =>
|
||||
'Update verfügbar, aber Download-URL nicht gefunden';
|
||||
|
||||
@override
|
||||
String get startTictactoe => 'Tic-Tac-Toe starten';
|
||||
|
||||
@override
|
||||
String get tictactoeUnavailable => 'Tic-Tac-Toe nicht verfügbar';
|
||||
|
||||
@override
|
||||
String get tictactoeOpponentUnknown => 'Tic-Tac-Toe: Gegner unbekannt';
|
||||
|
||||
@override
|
||||
String get tictactoeWaitingForStart => 'Tic-Tac-Toe: Warten auf Start';
|
||||
|
||||
@override
|
||||
String get acceptsShareLinks => 'Akzeptiert freigegebene Links';
|
||||
|
||||
@override
|
||||
String get supportsRawHex => 'Unterstützt rohes Hex';
|
||||
|
||||
@override
|
||||
String get clipboardfriendly => 'Zwischenablage-freundlich';
|
||||
|
||||
@override
|
||||
String get captured => 'Erfasst';
|
||||
|
||||
@override
|
||||
String get size => 'Größe';
|
||||
|
||||
@override
|
||||
String get noCustomChannelsToClear =>
|
||||
'Keine benutzerdefinierten Kanäle zum Löschen.';
|
||||
|
||||
@override
|
||||
String get noDeviceContactsToClear => 'Keine Gerätekontakte zum Löschen.';
|
||||
}
|
||||
|
||||
@@ -1741,4 +1741,963 @@ class AppLocalizationsEl extends AppLocalizations {
|
||||
|
||||
@override
|
||||
String get requestingVoice => 'Αίτηση φωνής';
|
||||
|
||||
@override
|
||||
String get device => 'συσκευή';
|
||||
|
||||
@override
|
||||
String get change => 'Αλλαγή';
|
||||
|
||||
@override
|
||||
String get wizardOverviewDescription =>
|
||||
'Αυτή η εφαρμογή συνδυάζει μηνύματα MeshCore, ενημερώσεις πεδίου SAR, χαρτογράφηση και εργαλεία συσκευής σε ένα σημείο.';
|
||||
|
||||
@override
|
||||
String get wizardOverviewFeature1 =>
|
||||
'Στείλτε άμεσα μηνύματα, δημοσιεύσεις δωματίου και μηνύματα καναλιού από την κύρια καρτέλα Μηνύματα.';
|
||||
|
||||
@override
|
||||
String get wizardOverviewFeature2 =>
|
||||
'Μοιραστείτε δείκτες SAR, σχεδιάσεις χάρτη, ηχητικά αποσπάσματα και εικόνες μέσω του mesh.';
|
||||
|
||||
@override
|
||||
String get wizardOverviewFeature3 =>
|
||||
'Συνδεθείτε μέσω BLE ή TCP και στη συνέχεια διαχειριστείτε το συνοδευτικό ραδιόφωνο μέσα από την εφαρμογή.';
|
||||
|
||||
@override
|
||||
String get wizardMessagingTitle => 'Μηνύματα και αναφορές πεδίου';
|
||||
|
||||
@override
|
||||
String get wizardMessagingDescription =>
|
||||
'Τα μηνύματα εδώ είναι κάτι περισσότερο από απλό κείμενο. Η εφαρμογή υποστηρίζει ήδη πολλαπλά επιχειρησιακά φορτία και ροές μεταφοράς.';
|
||||
|
||||
@override
|
||||
String get wizardMessagingFeature1 =>
|
||||
'Στείλτε άμεσα μηνύματα, δημοσιεύσεις δωματίου και κίνηση καναλιού από έναν μόνο συντάκτη.';
|
||||
|
||||
@override
|
||||
String get wizardMessagingFeature2 =>
|
||||
'Δημιουργήστε ενημερώσεις SAR και επαναχρησιμοποιήσιμα πρότυπα SAR για συνηθισμένες αναφορές πεδίου.';
|
||||
|
||||
@override
|
||||
String get wizardMessagingFeature3 =>
|
||||
'Μεταφέρετε φωνητικές συνεδρίες και εικόνες, με ένδειξη προόδου και εκτιμήσεις χρόνου εκπομπής στο περιβάλλον.';
|
||||
|
||||
@override
|
||||
String get wizardConnectDeviceTitle => 'Σύνδεση συσκευής';
|
||||
|
||||
@override
|
||||
String get wizardConnectDeviceDescription =>
|
||||
'Συνδέστε το ραδιόφωνο MeshCore, επιλέξτε όνομα και εφαρμόστε μια προρύθμιση ραδιοφώνου πριν συνεχίσετε.';
|
||||
|
||||
@override
|
||||
String get wizardSetupBadge => 'Ρύθμιση';
|
||||
|
||||
@override
|
||||
String get wizardOverviewBadge => 'Επισκόπηση';
|
||||
|
||||
@override
|
||||
String wizardConnectedToDevice(String deviceName) {
|
||||
return 'Συνδέθηκε με το $deviceName';
|
||||
}
|
||||
|
||||
@override
|
||||
String get wizardNoDeviceConnected => 'Δεν έχει συνδεθεί ακόμη συσκευή';
|
||||
|
||||
@override
|
||||
String get wizardSkipForNow => 'Παράλειψη προς το παρόν';
|
||||
|
||||
@override
|
||||
String get wizardDeviceNameLabel => 'Όνομα συσκευής';
|
||||
|
||||
@override
|
||||
String get wizardDeviceNameHelp =>
|
||||
'Αυτό το όνομα ανακοινώνεται σε άλλους χρήστες του MeshCore.';
|
||||
|
||||
@override
|
||||
String get wizardConfigRegionLabel => 'Περιοχή ρύθμισης';
|
||||
|
||||
@override
|
||||
String get wizardConfigRegionHelp =>
|
||||
'Χρησιμοποιεί την πλήρη επίσημη λίστα προρυθμίσεων MeshCore. Η προεπιλογή είναι EU/UK (Narrow).';
|
||||
|
||||
@override
|
||||
String get wizardPresetNote1 =>
|
||||
'Βεβαιωθείτε ότι η επιλεγμένη προρύθμιση ταιριάζει με τους τοπικούς ραδιοφωνικούς κανονισμούς.';
|
||||
|
||||
@override
|
||||
String get wizardPresetNote2 =>
|
||||
'Η λίστα αντιστοιχεί στην επίσημη ροή προρυθμίσεων του εργαλείου ρύθμισης MeshCore.';
|
||||
|
||||
@override
|
||||
String get wizardPresetNote3 =>
|
||||
'Το EU/UK (Narrow) παραμένει επιλεγμένο από προεπιλογή κατά την αρχική ρύθμιση.';
|
||||
|
||||
@override
|
||||
String get wizardSaving => 'Αποθήκευση...';
|
||||
|
||||
@override
|
||||
String get wizardSaveAndContinue => 'Αποθήκευση και συνέχεια';
|
||||
|
||||
@override
|
||||
String get wizardEnterDeviceName =>
|
||||
'Εισαγάγετε όνομα συσκευής πριν συνεχίσετε.';
|
||||
|
||||
@override
|
||||
String wizardDeviceSetupSaved(String deviceName, String presetName) {
|
||||
return 'Αποθηκεύτηκε το $deviceName με $presetName.';
|
||||
}
|
||||
|
||||
@override
|
||||
String get wizardNetworkTitle => 'Επαφές, δωμάτια και αναμεταδότες';
|
||||
|
||||
@override
|
||||
String get wizardNetworkDescription =>
|
||||
'Η καρτέλα Επαφές οργανώνει το δίκτυο που ανακαλύπτετε και τις διαδρομές που μαθαίνετε με τον χρόνο.';
|
||||
|
||||
@override
|
||||
String get wizardNetworkFeature1 =>
|
||||
'Ελέγξτε μέλη ομάδας, αναμεταδότες, δωμάτια, κανάλια και εκκρεμείς ανακοινώσεις σε μία λίστα.';
|
||||
|
||||
@override
|
||||
String get wizardNetworkFeature2 =>
|
||||
'Χρησιμοποιήστε smart ping, σύνδεση σε δωμάτια, μαθημένες διαδρομές και εργαλεία επαναφοράς διαδρομών όταν η συνδεσιμότητα γίνεται χαοτική.';
|
||||
|
||||
@override
|
||||
String get wizardNetworkFeature3 =>
|
||||
'Δημιουργήστε κανάλια και διαχειριστείτε προορισμούς δικτύου χωρίς να φύγετε από την εφαρμογή.';
|
||||
|
||||
@override
|
||||
String get wizardMapOpsTitle => 'Χάρτης, ίχνη και κοινόχρηστη γεωμετρία';
|
||||
|
||||
@override
|
||||
String get wizardMapOpsDescription =>
|
||||
'Ο χάρτης της εφαρμογής συνδέεται άμεσα με τα μηνύματα, την παρακολούθηση και τα επικαλύμματα SAR αντί να είναι ξεχωριστός προβολέας.';
|
||||
|
||||
@override
|
||||
String get wizardMapOpsFeature1 =>
|
||||
'Παρακολουθήστε τη θέση σας, τις τοποθεσίες της ομάδας και τα ίχνη κίνησης στον χάρτη.';
|
||||
|
||||
@override
|
||||
String get wizardMapOpsFeature2 =>
|
||||
'Ανοίξτε σχεδιάσεις από μηνύματα, προεπισκοπήστε τες επιτόπου και αφαιρέστε τες από τον χάρτη όταν χρειάζεται.';
|
||||
|
||||
@override
|
||||
String get wizardMapOpsFeature3 =>
|
||||
'Χρησιμοποιήστε προβολές χάρτη αναμεταδοτών και κοινόχρηστα επικαλύμματα για να κατανοήσετε την εμβέλεια του δικτύου στο πεδίο.';
|
||||
|
||||
@override
|
||||
String get wizardToolsTitle => 'Εργαλεία πέρα από τα μηνύματα';
|
||||
|
||||
@override
|
||||
String get wizardToolsDescription =>
|
||||
'Υπάρχουν περισσότερα εδώ από τις τέσσερις κύριες καρτέλες. Η εφαρμογή περιλαμβάνει επίσης ρυθμίσεις, διαγνωστικά και προαιρετικές ροές αισθητήρων.';
|
||||
|
||||
@override
|
||||
String get wizardToolsFeature1 =>
|
||||
'Ανοίξτε τη ρύθμιση συσκευής για να αλλάξετε παραμέτρους ραδιοφώνου, τηλεμετρία, ισχύ TX και στοιχεία συνοδευτικής συσκευής.';
|
||||
|
||||
@override
|
||||
String get wizardToolsFeature2 =>
|
||||
'Ενεργοποιήστε την καρτέλα Αισθητήρες όταν θέλετε πίνακες παρακολούθησης και γρήγορες ενέργειες ανανέωσης.';
|
||||
|
||||
@override
|
||||
String get wizardToolsFeature3 =>
|
||||
'Χρησιμοποιήστε καταγραφές πακέτων, σάρωση φάσματος και διαγνωστικά προγραμματιστή κατά την αντιμετώπιση προβλημάτων του mesh.';
|
||||
|
||||
@override
|
||||
String get contactInSensors => 'Στους αισθητήρες';
|
||||
|
||||
@override
|
||||
String get contactAddToSensors => 'Προσθήκη στους αισθητήρες';
|
||||
|
||||
@override
|
||||
String get contactSetPath => 'Ορισμός διαδρομής';
|
||||
|
||||
@override
|
||||
String contactAddedToSensors(String contactName) {
|
||||
return 'Το $contactName προστέθηκε στους Αισθητήρες';
|
||||
}
|
||||
|
||||
@override
|
||||
String contactFailedToClearRoute(String error) {
|
||||
return 'Αποτυχία εκκαθάρισης διαδρομής: $error';
|
||||
}
|
||||
|
||||
@override
|
||||
String get contactRouteCleared => 'Η διαδρομή εκκαθαρίστηκε';
|
||||
|
||||
@override
|
||||
String contactRouteSet(String route) {
|
||||
return 'Ορίστηκε διαδρομή: $route';
|
||||
}
|
||||
|
||||
@override
|
||||
String contactFailedToSetRoute(String error) {
|
||||
return 'Αποτυχία ορισμού διαδρομής: $error';
|
||||
}
|
||||
|
||||
@override
|
||||
String get rssi => 'RSSI';
|
||||
|
||||
@override
|
||||
String get snr => 'SNR';
|
||||
|
||||
@override
|
||||
String get ackTimeout => 'Λήξη χρόνου ACK';
|
||||
|
||||
@override
|
||||
String get opcode => 'Opcode';
|
||||
|
||||
@override
|
||||
String get payload => 'Ωφέλιμο φορτίο';
|
||||
|
||||
@override
|
||||
String get hops => 'Αναπηδήσεις';
|
||||
|
||||
@override
|
||||
String get hashSize => 'Μέγεθος hash';
|
||||
|
||||
@override
|
||||
String get pathBytes => 'Bytes διαδρομής';
|
||||
|
||||
@override
|
||||
String get selectedPath => 'Επιλεγμένη διαδρομή';
|
||||
|
||||
@override
|
||||
String get estimatedTx => 'Εκτιμώμενη εκπομπή';
|
||||
|
||||
@override
|
||||
String get senderToReceipt => 'Αποστολέας σε απόδειξη';
|
||||
|
||||
@override
|
||||
String get receivedCopies => 'Ληφθέντα αντίγραφα';
|
||||
|
||||
@override
|
||||
String get retryCause => 'Αιτία επανάληψης';
|
||||
|
||||
@override
|
||||
String get retryMode => 'Λειτουργία επανάληψης';
|
||||
|
||||
@override
|
||||
String get retryResult => 'Αποτέλεσμα επανάληψης';
|
||||
|
||||
@override
|
||||
String get lastRetry => 'Τελευταία επανάληψη';
|
||||
|
||||
@override
|
||||
String get rxPackets => 'Πακέτα RX';
|
||||
|
||||
@override
|
||||
String get mesh => 'Mesh';
|
||||
|
||||
@override
|
||||
String get rate => 'Ρυθμός';
|
||||
|
||||
@override
|
||||
String get window => 'Παράθυρο';
|
||||
|
||||
@override
|
||||
String get posttxDelay => 'Καθυστέρηση μετά-tx';
|
||||
|
||||
@override
|
||||
String get bandpass => 'Ζωνοπερατό';
|
||||
|
||||
@override
|
||||
String get bandpassFilterVoice => 'Ζωνοπερατό φίλτρο φωνής';
|
||||
|
||||
@override
|
||||
String get frequency => 'Συχνότητα';
|
||||
|
||||
@override
|
||||
String get australia => 'Αυστραλία';
|
||||
|
||||
@override
|
||||
String get australiaNarrow => 'Αυστραλία (Στενό)';
|
||||
|
||||
@override
|
||||
String get australiaQld => 'Αυστραλία: QLD';
|
||||
|
||||
@override
|
||||
String get australiaSaWa => 'Αυστραλία: SA, WA';
|
||||
|
||||
@override
|
||||
String get newZealand => 'Νέα Ζηλανδία';
|
||||
|
||||
@override
|
||||
String get newZealandNarrow => 'Νέα Ζηλανδία (Στενό)';
|
||||
|
||||
@override
|
||||
String get switzerland => 'Ελβετία';
|
||||
|
||||
@override
|
||||
String get portugal433 => 'Πορτογαλία 433';
|
||||
|
||||
@override
|
||||
String get portugal868 => 'Πορτογαλία 868';
|
||||
|
||||
@override
|
||||
String get czechRepublicNarrow => 'Τσεχία (Στενό)';
|
||||
|
||||
@override
|
||||
String get eu433mhzLongRange => 'ΕΕ 433MHz (Μεγάλη Εμβέλεια)';
|
||||
|
||||
@override
|
||||
String get euukDeprecated => 'ΕΕ/ΗΒ (Παρωχημένο)';
|
||||
|
||||
@override
|
||||
String get euukNarrow => 'ΕΕ/ΗΒ (Στενό)';
|
||||
|
||||
@override
|
||||
String get usacanadaRecommended => 'ΗΠΑ/Καναδάς (Προτεινόμενο)';
|
||||
|
||||
@override
|
||||
String get vietnamDeprecated => 'Βιετνάμ (Παρωχημένο)';
|
||||
|
||||
@override
|
||||
String get vietnamNarrow => 'Βιετνάμ (Στενό)';
|
||||
|
||||
@override
|
||||
String get active => 'Ενεργό';
|
||||
|
||||
@override
|
||||
String get addContact => 'Προσθήκη επαφής';
|
||||
|
||||
@override
|
||||
String get all => 'Όλα';
|
||||
|
||||
@override
|
||||
String get autoResolve => 'Αυτόματη επίλυση';
|
||||
|
||||
@override
|
||||
String get clearAllLabel => 'Εκκαθάριση όλων';
|
||||
|
||||
@override
|
||||
String get clearRelays => 'Εκκαθάριση αναμεταδοτών';
|
||||
|
||||
@override
|
||||
String get clearFilters => 'Εκκαθάριση φίλτρων';
|
||||
|
||||
@override
|
||||
String get clearRoute => 'Εκκαθάριση διαδρομής';
|
||||
|
||||
@override
|
||||
String get clearMessages => 'Εκκαθάριση μηνυμάτων';
|
||||
|
||||
@override
|
||||
String get clearScale => 'Εκκαθάριση κλίμακας';
|
||||
|
||||
@override
|
||||
String get clearDiscoveries => 'Εκκαθάριση ανακαλύψεων';
|
||||
|
||||
@override
|
||||
String get clearOnlineTraceDatabase => 'Εκκαθάριση βάσης ιχνών';
|
||||
|
||||
@override
|
||||
String get clearAllChannels => 'Εκκαθάριση όλων των καναλιών';
|
||||
|
||||
@override
|
||||
String get clearAllContacts => 'Εκκαθάριση όλων των επαφών';
|
||||
|
||||
@override
|
||||
String get clearChannels => 'Εκκαθάριση καναλιών';
|
||||
|
||||
@override
|
||||
String get clearContacts => 'Εκκαθάριση επαφών';
|
||||
|
||||
@override
|
||||
String get clearPathOnMaxRetry => 'Εκκαθάριση μονοπατιού στο μέγιστο';
|
||||
|
||||
@override
|
||||
String get create => 'Δημιουργία';
|
||||
|
||||
@override
|
||||
String get custom => 'Προσαρμοσμένο';
|
||||
|
||||
@override
|
||||
String get defaultValue => 'Προεπιλογή';
|
||||
|
||||
@override
|
||||
String get duplicate => 'Αντιγραφή';
|
||||
|
||||
@override
|
||||
String get editName => 'Επεξεργασία ονόματος';
|
||||
|
||||
@override
|
||||
String get open => 'Άνοιγμα';
|
||||
|
||||
@override
|
||||
String get paste => 'Επικόλληση';
|
||||
|
||||
@override
|
||||
String get preview => 'Προεπισκόπηση';
|
||||
|
||||
@override
|
||||
String get remove => 'Αφαίρεση';
|
||||
|
||||
@override
|
||||
String get rename => 'Μετονομασία';
|
||||
|
||||
@override
|
||||
String get resolveAll => 'Επίλυση όλων';
|
||||
|
||||
@override
|
||||
String get send => 'Αποστολή';
|
||||
|
||||
@override
|
||||
String get sendAnyway => 'Αποστολή ούτως ή άλλως';
|
||||
|
||||
@override
|
||||
String get share => 'Κοινοποίηση';
|
||||
|
||||
@override
|
||||
String get shareContact => 'Κοινοποίηση επαφής';
|
||||
|
||||
@override
|
||||
String get trace => 'Ίχνος';
|
||||
|
||||
@override
|
||||
String get use => 'Χρήση';
|
||||
|
||||
@override
|
||||
String get useSelectedFrequency => 'Χρήση επιλεγμένης συχνότητας';
|
||||
|
||||
@override
|
||||
String get discovery => 'Ανακάλυψη';
|
||||
|
||||
@override
|
||||
String get discoverRepeaters => 'Ανακάλυψη αναμεταδοτών';
|
||||
|
||||
@override
|
||||
String get discoverSensors => 'Ανακάλυψη αισθητήρων';
|
||||
|
||||
@override
|
||||
String get repeaterDiscoverySent => 'Αποστολή ανακάλυψης αναμεταδοτών';
|
||||
|
||||
@override
|
||||
String get sensorDiscoverySent => 'Αποστολή ανακάλυψης αισθητήρων';
|
||||
|
||||
@override
|
||||
String get clearedPendingDiscoveries => 'Εκκαθάριση εκκρεμών ανακαλύψεων.';
|
||||
|
||||
@override
|
||||
String get autoDiscovery => 'Αυτόματη ανακάλυψη';
|
||||
|
||||
@override
|
||||
String get enableAutomaticAdding => 'Ενεργοποίηση αυτόματης προσθήκης';
|
||||
|
||||
@override
|
||||
String get autoaddRepeaters => 'Αυτόματη προσθήκη αναμεταδοτών';
|
||||
|
||||
@override
|
||||
String get autoaddRoomServers => 'Αυτόματη προσθήκη διακομιστών δωματίου';
|
||||
|
||||
@override
|
||||
String get autoaddSensors => 'Αυτόματη προσθήκη αισθητήρων';
|
||||
|
||||
@override
|
||||
String get autoaddUsers => 'Αυτόματη προσθήκη χρηστών';
|
||||
|
||||
@override
|
||||
String get overwriteOldestWhenFull =>
|
||||
'Αντικατάσταση παλαιότερων όταν γεμίσει';
|
||||
|
||||
@override
|
||||
String get storage => 'Αποθήκευση';
|
||||
|
||||
@override
|
||||
String get dangerZone => 'Ζώνη κινδύνου';
|
||||
|
||||
@override
|
||||
String get profiles => 'Προφίλ';
|
||||
|
||||
@override
|
||||
String get favourites => 'Αγαπημένα';
|
||||
|
||||
@override
|
||||
String get sensors => 'Αισθητήρες';
|
||||
|
||||
@override
|
||||
String get others => 'Άλλοι';
|
||||
|
||||
@override
|
||||
String get gpsModule => 'Μονάδα GPS';
|
||||
|
||||
@override
|
||||
String get liveTraffic => 'Ζωντανή κίνηση';
|
||||
|
||||
@override
|
||||
String get repeatersMap => 'Χάρτης αναμεταδοτών';
|
||||
|
||||
@override
|
||||
String get spectrumScan => 'Σάρωση φάσματος';
|
||||
|
||||
@override
|
||||
String get blePacketLogs => 'Αρχεία πακέτων BLE';
|
||||
|
||||
@override
|
||||
String get onlineTraceDatabase => 'Βάση δεδομένων ιχνών';
|
||||
|
||||
@override
|
||||
String get routePathByteSize => 'Μέγεθος διαδρομής σε bytes';
|
||||
|
||||
@override
|
||||
String get messageNotifications => 'Ειδοποιήσεις μηνυμάτων';
|
||||
|
||||
@override
|
||||
String get sarAlerts => 'Ειδοποιήσεις SAR';
|
||||
|
||||
@override
|
||||
String get discoveryNotifications => 'Ειδοποιήσεις ανακάλυψης';
|
||||
|
||||
@override
|
||||
String get updateNotifications => 'Ειδοποιήσεις ενημερώσεων';
|
||||
|
||||
@override
|
||||
String get muteWhileAppIsOpen => 'Σίγαση με ανοιχτή εφαρμογή';
|
||||
|
||||
@override
|
||||
String get disableContacts => 'Απενεργοποίηση επαφών';
|
||||
|
||||
@override
|
||||
String get enableSensorsTab => 'Ενεργοποίηση καρτέλας Αισθητήρων';
|
||||
|
||||
@override
|
||||
String get enableProfiles => 'Ενεργοποίηση προφίλ';
|
||||
|
||||
@override
|
||||
String get autoRouteRotation => 'Αυτόματη εναλλαγή διαδρομής';
|
||||
|
||||
@override
|
||||
String get nearestRepeaterFallback => 'Πλησιέστερος αναμεταδότης ως εφεδρεία';
|
||||
|
||||
@override
|
||||
String get deleteAllStoredMessageHistory => 'Διαγραφή όλου του ιστορικού';
|
||||
|
||||
@override
|
||||
String get messageFontSize => 'Μέγεθος γραμματοσειράς μηνυμάτων';
|
||||
|
||||
@override
|
||||
String get rotateMapWithHeading => 'Περιστροφή χάρτη με κατεύθυνση';
|
||||
|
||||
@override
|
||||
String get showMapDebugInfo => 'Εμφάνιση πληροφοριών αποσφαλμάτωσης';
|
||||
|
||||
@override
|
||||
String get openMapInFullscreen => 'Άνοιγμα χάρτη σε πλήρη οθόνη';
|
||||
|
||||
@override
|
||||
String get showSarMarkersLabel => 'Εμφάνιση δεικτών SAR';
|
||||
|
||||
@override
|
||||
String get displaySarMarkersOnTheMainMap =>
|
||||
'Εμφάνιση δεικτών SAR στον κύριο χάρτη';
|
||||
|
||||
@override
|
||||
String get showAllContactTrailsLabel => 'Εμφάνιση όλων των ιχνών επαφών';
|
||||
|
||||
@override
|
||||
String get hideRepeatersOnMap => 'Απόκρυψη αναμεταδοτών στον χάρτη';
|
||||
|
||||
@override
|
||||
String get setMapScale => 'Ρύθμιση κλίμακας χάρτη';
|
||||
|
||||
@override
|
||||
String get customMapScaleSaved => 'Προσαρμοσμένη κλίμακα χάρτη αποθηκεύτηκε';
|
||||
|
||||
@override
|
||||
String get voiceBitrate => 'Ρυθμός bit φωνής';
|
||||
|
||||
@override
|
||||
String get voiceCompressor => 'Συμπιεστής φωνής';
|
||||
|
||||
@override
|
||||
String get balancesQuietAndLoudSpeechLevels =>
|
||||
'Εξισορρόπηση χαμηλών και δυνατών επιπέδων';
|
||||
|
||||
@override
|
||||
String get voiceLimiter => 'Περιοριστής φωνής';
|
||||
|
||||
@override
|
||||
String get preventsClippingPeaksBeforeEncoding =>
|
||||
'Αποτρέπει κοπή κορυφών πριν την κωδικοποίηση';
|
||||
|
||||
@override
|
||||
String get micAutoGain => 'Αυτόματη ενίσχυση μικροφώνου';
|
||||
|
||||
@override
|
||||
String get letsTheRecorderAdjustInputLevel =>
|
||||
'Αφήνει τη συσκευή να ρυθμίσει την ένταση';
|
||||
|
||||
@override
|
||||
String get echoCancellation => 'Ακύρωση ηχούς';
|
||||
|
||||
@override
|
||||
String get noiseSuppression => 'Καταστολή θορύβου';
|
||||
|
||||
@override
|
||||
String get trimSilenceInVoiceMessages =>
|
||||
'Περικοπή σιωπής σε φωνητικά μηνύματα';
|
||||
|
||||
@override
|
||||
String get compressor => 'Συμπιεστής';
|
||||
|
||||
@override
|
||||
String get limiter => 'Περιοριστής';
|
||||
|
||||
@override
|
||||
String get autoGain => 'Αυτόματη ενίσχυση';
|
||||
|
||||
@override
|
||||
String get echoCancel => 'Ηχώ';
|
||||
|
||||
@override
|
||||
String get noiseSuppress => 'Θόρυβος';
|
||||
|
||||
@override
|
||||
String get silenceTrim => 'Σιωπή';
|
||||
|
||||
@override
|
||||
String get maxImageSize => 'Μέγιστο μέγεθος εικόνας';
|
||||
|
||||
@override
|
||||
String get imageCompression => 'Συμπίεση εικόνας';
|
||||
|
||||
@override
|
||||
String get grayscale => 'Κλίμακα του γκρι';
|
||||
|
||||
@override
|
||||
String get ultraMode => 'Λειτουργία ultra';
|
||||
|
||||
@override
|
||||
String get fastPrivateGpsUpdates => 'Γρήγορες ιδιωτικές ενημερώσεις GPS';
|
||||
|
||||
@override
|
||||
String get movementThreshold => 'Κατώφλι κίνησης';
|
||||
|
||||
@override
|
||||
String get fastGpsMovementThreshold => 'Κατώφλι κίνησης γρήγορου GPS';
|
||||
|
||||
@override
|
||||
String get fastGpsActiveuseInterval => 'Διάστημα ενεργής χρήσης γρήγορου GPS';
|
||||
|
||||
@override
|
||||
String get activeuseUpdateInterval => 'Διάστημα ενημέρωσης ενεργής χρήσης';
|
||||
|
||||
@override
|
||||
String get repeatNearbyTraffic => 'Επανάληψη κοντινής κίνησης';
|
||||
|
||||
@override
|
||||
String get relayThroughRepeatersAcrossTheMesh =>
|
||||
'Αναμετάδοση μέσω αναμεταδοτών στο δίκτυο';
|
||||
|
||||
@override
|
||||
String get nearbyOnlyWithoutRepeaterFlooding =>
|
||||
'Μόνο κοντά, χωρίς πλημμυρισμό';
|
||||
|
||||
@override
|
||||
String get multihop => 'Πολλαπλών αναπηδήσεων';
|
||||
|
||||
@override
|
||||
String get createProfile => 'Δημιουργία προφίλ';
|
||||
|
||||
@override
|
||||
String get renameProfile => 'Μετονομασία προφίλ';
|
||||
|
||||
@override
|
||||
String get newProfile => 'Νέο προφίλ';
|
||||
|
||||
@override
|
||||
String get manageProfiles => 'Διαχείριση προφίλ';
|
||||
|
||||
@override
|
||||
String get enableProfilesToStartManagingThem =>
|
||||
'Ενεργοποιήστε τα προφίλ για να τα διαχειριστείτε.';
|
||||
|
||||
@override
|
||||
String get openMessage => 'Άνοιγμα μηνύματος';
|
||||
|
||||
@override
|
||||
String get jumpToTheRelatedSarMessage => 'Μετάβαση στο σχετικό μήνυμα SAR';
|
||||
|
||||
@override
|
||||
String get removeSarMarker => 'Αφαίρεση δείκτη SAR';
|
||||
|
||||
@override
|
||||
String get pleaseSelectADestinationToSendSarMarker =>
|
||||
'Επιλέξτε προορισμό για αποστολή δείκτη SAR';
|
||||
|
||||
@override
|
||||
String get sarMarkerBroadcastToPublicChannel =>
|
||||
'Δείκτης SAR εκπεμπόμενος στο δημόσιο κανάλι';
|
||||
|
||||
@override
|
||||
String get sarMarkerSentToRoom => 'Δείκτης SAR εστάλη στο δωμάτιο';
|
||||
|
||||
@override
|
||||
String get loadFromGallery => 'Φόρτωση από τη γκαλερί';
|
||||
|
||||
@override
|
||||
String get replaceImage => 'Αντικατάσταση εικόνας';
|
||||
|
||||
@override
|
||||
String get selectFromGallery => 'Επιλογή από τη γκαλερί';
|
||||
|
||||
@override
|
||||
String get team => 'Ομάδα';
|
||||
|
||||
@override
|
||||
String get found => 'Βρέθηκε';
|
||||
|
||||
@override
|
||||
String get staging => 'Χώρος συγκέντρωσης';
|
||||
|
||||
@override
|
||||
String get object => 'Αντικείμενο';
|
||||
|
||||
@override
|
||||
String get quiet => 'Ήσυχο';
|
||||
|
||||
@override
|
||||
String get moderate => 'Μέτριο';
|
||||
|
||||
@override
|
||||
String get busy => 'Απασχολημένο';
|
||||
|
||||
@override
|
||||
String get spectrumScanReturnedNoCandidateFrequencies =>
|
||||
'Η σάρωση φάσματος δεν βρήκε υποψήφιες συχνότητες';
|
||||
|
||||
@override
|
||||
String get searchMessages => 'Αναζήτηση μηνυμάτων';
|
||||
|
||||
@override
|
||||
String get sendImageFromGallery => 'Αποστολή εικόνας από τη γκαλερί';
|
||||
|
||||
@override
|
||||
String get takePhoto => 'Λήψη φωτογραφίας';
|
||||
|
||||
@override
|
||||
String get dmOnly => 'Μόνο απευθείας';
|
||||
|
||||
@override
|
||||
String get allMessages => 'Όλα τα μηνύματα';
|
||||
|
||||
@override
|
||||
String get sendToPublicChannel => 'Αποστολή στο δημόσιο κανάλι;';
|
||||
|
||||
@override
|
||||
String get selectMarkerTypeAndDestination =>
|
||||
'Επιλέξτε τύπο δείκτη και προορισμό';
|
||||
|
||||
@override
|
||||
String get noDestinationsAvailableLabel =>
|
||||
'Δεν υπάρχουν διαθέσιμοι προορισμοί';
|
||||
|
||||
@override
|
||||
String get image => 'Εικόνα';
|
||||
|
||||
@override
|
||||
String get format => 'Μορφή';
|
||||
|
||||
@override
|
||||
String get dimensions => 'Διαστάσεις';
|
||||
|
||||
@override
|
||||
String get segments => 'Τμήματα';
|
||||
|
||||
@override
|
||||
String get transfers => 'Μεταφορές';
|
||||
|
||||
@override
|
||||
String get downloadedBy => 'Λήφθηκε από';
|
||||
|
||||
@override
|
||||
String get saveDiscoverySettings => 'Αποθήκευση ρυθμίσεων ανακάλυψης';
|
||||
|
||||
@override
|
||||
String get savePublicInfo => 'Αποθήκευση δημόσιων πληροφοριών';
|
||||
|
||||
@override
|
||||
String get saveRadioSettings => 'Αποθήκευση ρυθμίσεων ραδιοφώνου';
|
||||
|
||||
@override
|
||||
String get savePath => 'Αποθήκευση διαδρομής';
|
||||
|
||||
@override
|
||||
String get wipeDeviceData => 'Διαγραφή δεδομένων συσκευής';
|
||||
|
||||
@override
|
||||
String get wipeDevice => 'Διαγραφή συσκευής';
|
||||
|
||||
@override
|
||||
String get destructiveDeviceActions => 'Καταστροφικές ενέργειες συσκευής.';
|
||||
|
||||
@override
|
||||
String get chooseAPresetOrFinetuneCustomRadioSettings =>
|
||||
'Επιλέξτε μια προεπιλογή ή ρυθμίστε τις ρυθμίσεις ραδιοφώνου.';
|
||||
|
||||
@override
|
||||
String get chooseTheNameAndLocationThisDeviceShares =>
|
||||
'Επιλέξτε το όνομα και την τοποθεσία που μοιράζεται αυτή η συσκευή.';
|
||||
|
||||
@override
|
||||
String get availableSpaceOnThisDevice =>
|
||||
'Διαθέσιμος χώρος σε αυτή τη συσκευή.';
|
||||
|
||||
@override
|
||||
String get used => 'Χρησιμοποιήθηκε';
|
||||
|
||||
@override
|
||||
String get total => 'Σύνολο';
|
||||
|
||||
@override
|
||||
String get renameValue => 'Μετονομασία τιμής';
|
||||
|
||||
@override
|
||||
String get customizeFields => 'Προσαρμογή πεδίων';
|
||||
|
||||
@override
|
||||
String get livePreview => 'Ζωντανή προεπισκόπηση';
|
||||
|
||||
@override
|
||||
String get refreshSchedule => 'Χρονοδιάγραμμα ανανέωσης';
|
||||
|
||||
@override
|
||||
String get noResponse => 'Χωρίς απάντηση';
|
||||
|
||||
@override
|
||||
String get refreshing => 'Ανανέωση';
|
||||
|
||||
@override
|
||||
String get unavailable => 'Μη διαθέσιμο';
|
||||
|
||||
@override
|
||||
String get pickARelayOrNodeToWatchInSensors =>
|
||||
'Επιλέξτε ένα relay ή κόμβο για παρακολούθηση.';
|
||||
|
||||
@override
|
||||
String get publicKeyLabel => 'Δημόσιο κλειδί';
|
||||
|
||||
@override
|
||||
String get alreadyInContacts => 'Ήδη στις επαφές';
|
||||
|
||||
@override
|
||||
String get connectToADeviceBeforeAddingContacts =>
|
||||
'Συνδεθείτε σε μια συσκευή πριν προσθέσετε επαφές';
|
||||
|
||||
@override
|
||||
String get fromContacts => 'Από τις επαφές';
|
||||
|
||||
@override
|
||||
String get onlineOnly => 'Μόνο συνδεδεμένοι';
|
||||
|
||||
@override
|
||||
String get inBoth => 'Σε αμφότερα';
|
||||
|
||||
@override
|
||||
String get source => 'Πηγή';
|
||||
|
||||
@override
|
||||
String get manualRouteEdit => 'Χειροκίνητη επεξεργασία διαδρομής';
|
||||
|
||||
@override
|
||||
String get observedMeshRoute => 'Παρατηρούμενη διαδρομή δικτύου';
|
||||
|
||||
@override
|
||||
String get allMessagesCleared => 'Όλα τα μηνύματα διαγράφηκαν';
|
||||
|
||||
@override
|
||||
String get onlineTraceDatabaseCleared => 'Βάση δεδομένων ιχνών εκκαθαρίστηκε';
|
||||
|
||||
@override
|
||||
String get packetLogsCleared => 'Τα αρχεία πακέτων εκκαθαρίστηκαν';
|
||||
|
||||
@override
|
||||
String get hexDataCopiedToClipboard => 'Τα δεδομένα hex αντιγράφηκαν';
|
||||
|
||||
@override
|
||||
String get developerModeEnabled => 'Λειτουργία προγραμματιστή ενεργοποιήθηκε';
|
||||
|
||||
@override
|
||||
String get developerModeDisabled =>
|
||||
'Λειτουργία προγραμματιστή απενεργοποιήθηκε';
|
||||
|
||||
@override
|
||||
String get clipboardIsEmpty => 'Το πρόχειρο είναι κενό';
|
||||
|
||||
@override
|
||||
String get contactImported => 'Επαφή εισήχθη';
|
||||
|
||||
@override
|
||||
String get contactLinkCopiedToClipboard => 'Ο σύνδεσμος επαφής αντιγράφηκε';
|
||||
|
||||
@override
|
||||
String get failedToExportContact => 'Αποτυχία εξαγωγής επαφής';
|
||||
|
||||
@override
|
||||
String get noLogsToExport => 'Δεν υπάρχουν αρχεία για εξαγωγή';
|
||||
|
||||
@override
|
||||
String get exportAsCsv => 'Εξαγωγή ως CSV';
|
||||
|
||||
@override
|
||||
String get exportAsText => 'Εξαγωγή ως κείμενο';
|
||||
|
||||
@override
|
||||
String get receivedRfc3339 => 'Ελήφθη (RFC3339)';
|
||||
|
||||
@override
|
||||
String get buildTime => 'Ώρα κατασκευής';
|
||||
|
||||
@override
|
||||
String get downloadUrlNotAvailable => 'Η URL λήψης δεν είναι διαθέσιμη';
|
||||
|
||||
@override
|
||||
String get cannotOpenDownloadUrl => 'Αδυναμία ανοίγματος URL λήψης';
|
||||
|
||||
@override
|
||||
String get updateCheckIsOnlyAvailableOnAndroid =>
|
||||
'Ο έλεγχος ενημερώσεων είναι διαθέσιμος μόνο σε Android';
|
||||
|
||||
@override
|
||||
String get youAreRunningTheLatestVersion =>
|
||||
'Χρησιμοποιείτε την πιο πρόσφατη έκδοση';
|
||||
|
||||
@override
|
||||
String get updateAvailableButDownloadUrlNotFound =>
|
||||
'Διαθέσιμη ενημέρωση αλλά η URL λήψης δεν βρέθηκε';
|
||||
|
||||
@override
|
||||
String get startTictactoe => 'Εκκίνηση Tic-Tac-Toe';
|
||||
|
||||
@override
|
||||
String get tictactoeUnavailable => 'Tic-Tac-Toe μη διαθέσιμο';
|
||||
|
||||
@override
|
||||
String get tictactoeOpponentUnknown => 'Tic-Tac-Toe: άγνωστος αντίπαλος';
|
||||
|
||||
@override
|
||||
String get tictactoeWaitingForStart => 'Tic-Tac-Toe: αναμονή για εκκίνηση';
|
||||
|
||||
@override
|
||||
String get acceptsShareLinks => 'Αποδέχεται κοινόχρηστους συνδέσμους';
|
||||
|
||||
@override
|
||||
String get supportsRawHex => 'Υποστηρίζει ακατέργαστο hex';
|
||||
|
||||
@override
|
||||
String get clipboardfriendly => 'Φιλικό προς πρόχειρο';
|
||||
|
||||
@override
|
||||
String get captured => 'Καταγράφηκε';
|
||||
|
||||
@override
|
||||
String get size => 'Μέγεθος';
|
||||
|
||||
@override
|
||||
String get noCustomChannelsToClear => 'Δεν υπάρχουν προσαρμοσμένα κανάλια.';
|
||||
|
||||
@override
|
||||
String get noDeviceContactsToClear => 'Δεν υπάρχουν επαφές συσκευής.';
|
||||
}
|
||||
|
||||
@@ -1708,4 +1708,958 @@ class AppLocalizationsEn extends AppLocalizations {
|
||||
|
||||
@override
|
||||
String get requestingVoice => 'Requesting voice';
|
||||
|
||||
@override
|
||||
String get device => 'device';
|
||||
|
||||
@override
|
||||
String get change => 'Change';
|
||||
|
||||
@override
|
||||
String get wizardOverviewDescription =>
|
||||
'This app combines MeshCore messaging, SAR field updates, mapping, and device tools in one place.';
|
||||
|
||||
@override
|
||||
String get wizardOverviewFeature1 =>
|
||||
'Send direct, room, and channel messages from the main Messages tab.';
|
||||
|
||||
@override
|
||||
String get wizardOverviewFeature2 =>
|
||||
'Share SAR markers, map drawings, voice clips, and images over the mesh.';
|
||||
|
||||
@override
|
||||
String get wizardOverviewFeature3 =>
|
||||
'Connect over BLE or TCP, then manage the companion radio from inside the app.';
|
||||
|
||||
@override
|
||||
String get wizardMessagingTitle => 'Messaging and Field Reports';
|
||||
|
||||
@override
|
||||
String get wizardMessagingDescription =>
|
||||
'Messages are more than plain text here. The app already supports several operational payloads and transfer workflows.';
|
||||
|
||||
@override
|
||||
String get wizardMessagingFeature1 =>
|
||||
'Send direct messages, room posts, and channel traffic from one composer.';
|
||||
|
||||
@override
|
||||
String get wizardMessagingFeature2 =>
|
||||
'Create SAR updates and reusable SAR templates for common field reports.';
|
||||
|
||||
@override
|
||||
String get wizardMessagingFeature3 =>
|
||||
'Transfer voice sessions and images, with progress and airtime estimates in the UI.';
|
||||
|
||||
@override
|
||||
String get wizardConnectDeviceTitle => 'Connect device';
|
||||
|
||||
@override
|
||||
String get wizardConnectDeviceDescription =>
|
||||
'Connect your MeshCore radio, choose a name, and apply a radio preset before continuing.';
|
||||
|
||||
@override
|
||||
String get wizardSetupBadge => 'Setup';
|
||||
|
||||
@override
|
||||
String get wizardOverviewBadge => 'Overview';
|
||||
|
||||
@override
|
||||
String wizardConnectedToDevice(String deviceName) {
|
||||
return 'Connected to $deviceName';
|
||||
}
|
||||
|
||||
@override
|
||||
String get wizardNoDeviceConnected => 'No device connected yet';
|
||||
|
||||
@override
|
||||
String get wizardSkipForNow => 'Skip for now';
|
||||
|
||||
@override
|
||||
String get wizardDeviceNameLabel => 'Device name';
|
||||
|
||||
@override
|
||||
String get wizardDeviceNameHelp =>
|
||||
'This name is advertised to other MeshCore users.';
|
||||
|
||||
@override
|
||||
String get wizardConfigRegionLabel => 'Config region';
|
||||
|
||||
@override
|
||||
String get wizardConfigRegionHelp =>
|
||||
'Uses the full official MeshCore preset list. Default is EU/UK (Narrow).';
|
||||
|
||||
@override
|
||||
String get wizardPresetNote1 =>
|
||||
'Make sure the selected preset matches your local radio regulations.';
|
||||
|
||||
@override
|
||||
String get wizardPresetNote2 =>
|
||||
'The list matches the official MeshCore config tool preset feed.';
|
||||
|
||||
@override
|
||||
String get wizardPresetNote3 =>
|
||||
'EU/UK (Narrow) stays selected by default for onboarding.';
|
||||
|
||||
@override
|
||||
String get wizardSaving => 'Saving...';
|
||||
|
||||
@override
|
||||
String get wizardSaveAndContinue => 'Save and continue';
|
||||
|
||||
@override
|
||||
String get wizardEnterDeviceName => 'Enter a device name before continuing.';
|
||||
|
||||
@override
|
||||
String wizardDeviceSetupSaved(String deviceName, String presetName) {
|
||||
return 'Saved $deviceName with $presetName.';
|
||||
}
|
||||
|
||||
@override
|
||||
String get wizardNetworkTitle => 'Contacts, Rooms, and Repeaters';
|
||||
|
||||
@override
|
||||
String get wizardNetworkDescription =>
|
||||
'The Contacts tab organizes the network you discover and the routes you learn over time.';
|
||||
|
||||
@override
|
||||
String get wizardNetworkFeature1 =>
|
||||
'Review team members, repeaters, rooms, channels, and pending adverts in one list.';
|
||||
|
||||
@override
|
||||
String get wizardNetworkFeature2 =>
|
||||
'Use smart ping, room login, learned paths, and route reset tools when connectivity gets messy.';
|
||||
|
||||
@override
|
||||
String get wizardNetworkFeature3 =>
|
||||
'Create channels and manage network destinations without leaving the app.';
|
||||
|
||||
@override
|
||||
String get wizardMapOpsTitle => 'Map, Trails, and Shared Geometry';
|
||||
|
||||
@override
|
||||
String get wizardMapOpsDescription =>
|
||||
'The app map is tied directly into messaging, tracking, and SAR overlays instead of being a separate viewer.';
|
||||
|
||||
@override
|
||||
String get wizardMapOpsFeature1 =>
|
||||
'Track your own position, teammate locations, and movement trails on the map.';
|
||||
|
||||
@override
|
||||
String get wizardMapOpsFeature2 =>
|
||||
'Open drawings from messages, preview them inline, and remove them from the map when needed.';
|
||||
|
||||
@override
|
||||
String get wizardMapOpsFeature3 =>
|
||||
'Use repeater map views and shared overlays to understand network reach in the field.';
|
||||
|
||||
@override
|
||||
String get wizardToolsTitle => 'Tools Beyond Messaging';
|
||||
|
||||
@override
|
||||
String get wizardToolsDescription =>
|
||||
'There is more here than the four main tabs. The app also includes configuration, diagnostics, and optional sensor workflows.';
|
||||
|
||||
@override
|
||||
String get wizardToolsFeature1 =>
|
||||
'Open device config to change radio settings, telemetry, TX power, and companion details.';
|
||||
|
||||
@override
|
||||
String get wizardToolsFeature2 =>
|
||||
'Enable the Sensors tab when you want watched sensor dashboards and quick refresh actions.';
|
||||
|
||||
@override
|
||||
String get wizardToolsFeature3 =>
|
||||
'Use packet logs, spectrum scan, and developer diagnostics when troubleshooting the mesh.';
|
||||
|
||||
@override
|
||||
String get contactInSensors => 'In Sensors';
|
||||
|
||||
@override
|
||||
String get contactAddToSensors => 'Add to Sensors';
|
||||
|
||||
@override
|
||||
String get contactSetPath => 'Set path';
|
||||
|
||||
@override
|
||||
String contactAddedToSensors(String contactName) {
|
||||
return '$contactName added to Sensors';
|
||||
}
|
||||
|
||||
@override
|
||||
String contactFailedToClearRoute(String error) {
|
||||
return 'Failed to clear route: $error';
|
||||
}
|
||||
|
||||
@override
|
||||
String get contactRouteCleared => 'Route cleared';
|
||||
|
||||
@override
|
||||
String contactRouteSet(String route) {
|
||||
return 'Route set: $route';
|
||||
}
|
||||
|
||||
@override
|
||||
String contactFailedToSetRoute(String error) {
|
||||
return 'Failed to set route: $error';
|
||||
}
|
||||
|
||||
@override
|
||||
String get rssi => 'RSSI';
|
||||
|
||||
@override
|
||||
String get snr => 'SNR';
|
||||
|
||||
@override
|
||||
String get ackTimeout => 'ACK timeout';
|
||||
|
||||
@override
|
||||
String get opcode => 'Opcode';
|
||||
|
||||
@override
|
||||
String get payload => 'Payload';
|
||||
|
||||
@override
|
||||
String get hops => 'Hops';
|
||||
|
||||
@override
|
||||
String get hashSize => 'Hash size';
|
||||
|
||||
@override
|
||||
String get pathBytes => 'Path bytes';
|
||||
|
||||
@override
|
||||
String get selectedPath => 'Selected path';
|
||||
|
||||
@override
|
||||
String get estimatedTx => 'Estimated tx';
|
||||
|
||||
@override
|
||||
String get senderToReceipt => 'Sender to receipt';
|
||||
|
||||
@override
|
||||
String get receivedCopies => 'Received copies';
|
||||
|
||||
@override
|
||||
String get retryCause => 'Retry cause';
|
||||
|
||||
@override
|
||||
String get retryMode => 'Retry mode';
|
||||
|
||||
@override
|
||||
String get retryResult => 'Retry result';
|
||||
|
||||
@override
|
||||
String get lastRetry => 'Last retry';
|
||||
|
||||
@override
|
||||
String get rxPackets => 'RX packets';
|
||||
|
||||
@override
|
||||
String get mesh => 'Mesh';
|
||||
|
||||
@override
|
||||
String get rate => 'Rate';
|
||||
|
||||
@override
|
||||
String get window => 'Window';
|
||||
|
||||
@override
|
||||
String get posttxDelay => 'Post-tx delay';
|
||||
|
||||
@override
|
||||
String get bandpass => 'Band-pass';
|
||||
|
||||
@override
|
||||
String get bandpassFilterVoice => 'Band-pass filter voice';
|
||||
|
||||
@override
|
||||
String get frequency => 'Frequency';
|
||||
|
||||
@override
|
||||
String get australia => 'Australia';
|
||||
|
||||
@override
|
||||
String get australiaNarrow => 'Australia (Narrow)';
|
||||
|
||||
@override
|
||||
String get australiaQld => 'Australia: QLD';
|
||||
|
||||
@override
|
||||
String get australiaSaWa => 'Australia: SA, WA';
|
||||
|
||||
@override
|
||||
String get newZealand => 'New Zealand';
|
||||
|
||||
@override
|
||||
String get newZealandNarrow => 'New Zealand (Narrow)';
|
||||
|
||||
@override
|
||||
String get switzerland => 'Switzerland';
|
||||
|
||||
@override
|
||||
String get portugal433 => 'Portugal 433';
|
||||
|
||||
@override
|
||||
String get portugal868 => 'Portugal 868';
|
||||
|
||||
@override
|
||||
String get czechRepublicNarrow => 'Czech Republic (Narrow)';
|
||||
|
||||
@override
|
||||
String get eu433mhzLongRange => 'EU 433MHz (Long Range)';
|
||||
|
||||
@override
|
||||
String get euukDeprecated => 'EU/UK (Deprecated)';
|
||||
|
||||
@override
|
||||
String get euukNarrow => 'EU/UK (Narrow)';
|
||||
|
||||
@override
|
||||
String get usacanadaRecommended => 'USA/Canada (Recommended)';
|
||||
|
||||
@override
|
||||
String get vietnamDeprecated => 'Vietnam (Deprecated)';
|
||||
|
||||
@override
|
||||
String get vietnamNarrow => 'Vietnam (Narrow)';
|
||||
|
||||
@override
|
||||
String get active => 'Active';
|
||||
|
||||
@override
|
||||
String get addContact => 'Add Contact';
|
||||
|
||||
@override
|
||||
String get all => 'All';
|
||||
|
||||
@override
|
||||
String get autoResolve => 'Auto resolve';
|
||||
|
||||
@override
|
||||
String get clearAllLabel => 'Clear all';
|
||||
|
||||
@override
|
||||
String get clearRelays => 'Clear relays';
|
||||
|
||||
@override
|
||||
String get clearFilters => 'Clear filters';
|
||||
|
||||
@override
|
||||
String get clearRoute => 'Clear Route';
|
||||
|
||||
@override
|
||||
String get clearMessages => 'Clear Messages';
|
||||
|
||||
@override
|
||||
String get clearScale => 'Clear scale';
|
||||
|
||||
@override
|
||||
String get clearDiscoveries => 'Clear discoveries';
|
||||
|
||||
@override
|
||||
String get clearOnlineTraceDatabase => 'Clear online trace database';
|
||||
|
||||
@override
|
||||
String get clearAllChannels => 'Clear all channels';
|
||||
|
||||
@override
|
||||
String get clearAllContacts => 'Clear all contacts';
|
||||
|
||||
@override
|
||||
String get clearChannels => 'Clear channels';
|
||||
|
||||
@override
|
||||
String get clearContacts => 'Clear contacts';
|
||||
|
||||
@override
|
||||
String get clearPathOnMaxRetry => 'Clear path on max retry';
|
||||
|
||||
@override
|
||||
String get create => 'Create';
|
||||
|
||||
@override
|
||||
String get custom => 'Custom';
|
||||
|
||||
@override
|
||||
String get defaultValue => 'Default';
|
||||
|
||||
@override
|
||||
String get duplicate => 'Duplicate';
|
||||
|
||||
@override
|
||||
String get editName => 'Edit name';
|
||||
|
||||
@override
|
||||
String get open => 'Open';
|
||||
|
||||
@override
|
||||
String get paste => 'Paste';
|
||||
|
||||
@override
|
||||
String get preview => 'Preview';
|
||||
|
||||
@override
|
||||
String get remove => 'Remove';
|
||||
|
||||
@override
|
||||
String get rename => 'Rename';
|
||||
|
||||
@override
|
||||
String get resolveAll => 'Resolve all';
|
||||
|
||||
@override
|
||||
String get send => 'Send';
|
||||
|
||||
@override
|
||||
String get sendAnyway => 'Send anyway';
|
||||
|
||||
@override
|
||||
String get share => 'Share';
|
||||
|
||||
@override
|
||||
String get shareContact => 'Share Contact';
|
||||
|
||||
@override
|
||||
String get trace => 'Trace';
|
||||
|
||||
@override
|
||||
String get use => 'Use';
|
||||
|
||||
@override
|
||||
String get useSelectedFrequency => 'Use selected frequency';
|
||||
|
||||
@override
|
||||
String get discovery => 'Discovery';
|
||||
|
||||
@override
|
||||
String get discoverRepeaters => 'Discover repeaters';
|
||||
|
||||
@override
|
||||
String get discoverSensors => 'Discover sensors';
|
||||
|
||||
@override
|
||||
String get repeaterDiscoverySent => 'Repeater discovery sent';
|
||||
|
||||
@override
|
||||
String get sensorDiscoverySent => 'Sensor discovery sent';
|
||||
|
||||
@override
|
||||
String get clearedPendingDiscoveries => 'Cleared pending discoveries.';
|
||||
|
||||
@override
|
||||
String get autoDiscovery => 'Auto discovery';
|
||||
|
||||
@override
|
||||
String get enableAutomaticAdding => 'Enable automatic adding';
|
||||
|
||||
@override
|
||||
String get autoaddRepeaters => 'Auto-add repeaters';
|
||||
|
||||
@override
|
||||
String get autoaddRoomServers => 'Auto-add room servers';
|
||||
|
||||
@override
|
||||
String get autoaddSensors => 'Auto-add sensors';
|
||||
|
||||
@override
|
||||
String get autoaddUsers => 'Auto-add users';
|
||||
|
||||
@override
|
||||
String get overwriteOldestWhenFull => 'Overwrite oldest when full';
|
||||
|
||||
@override
|
||||
String get storage => 'Storage';
|
||||
|
||||
@override
|
||||
String get dangerZone => 'Danger zone';
|
||||
|
||||
@override
|
||||
String get profiles => 'Profiles';
|
||||
|
||||
@override
|
||||
String get favourites => 'Favourites';
|
||||
|
||||
@override
|
||||
String get sensors => 'Sensors';
|
||||
|
||||
@override
|
||||
String get others => 'Others';
|
||||
|
||||
@override
|
||||
String get gpsModule => 'GPS Module';
|
||||
|
||||
@override
|
||||
String get liveTraffic => 'Live Traffic';
|
||||
|
||||
@override
|
||||
String get repeatersMap => 'Repeaters Map';
|
||||
|
||||
@override
|
||||
String get spectrumScan => 'Spectrum Scan';
|
||||
|
||||
@override
|
||||
String get blePacketLogs => 'BLE Packet Logs';
|
||||
|
||||
@override
|
||||
String get onlineTraceDatabase => 'Online trace database';
|
||||
|
||||
@override
|
||||
String get routePathByteSize => 'Route path byte size';
|
||||
|
||||
@override
|
||||
String get messageNotifications => 'Message notifications';
|
||||
|
||||
@override
|
||||
String get sarAlerts => 'SAR alerts';
|
||||
|
||||
@override
|
||||
String get discoveryNotifications => 'Discovery notifications';
|
||||
|
||||
@override
|
||||
String get updateNotifications => 'Update notifications';
|
||||
|
||||
@override
|
||||
String get muteWhileAppIsOpen => 'Mute while app is open';
|
||||
|
||||
@override
|
||||
String get disableContacts => 'Disable Contacts';
|
||||
|
||||
@override
|
||||
String get enableSensorsTab => 'Enable Sensors tab';
|
||||
|
||||
@override
|
||||
String get enableProfiles => 'Enable Profiles';
|
||||
|
||||
@override
|
||||
String get autoRouteRotation => 'Auto route rotation';
|
||||
|
||||
@override
|
||||
String get nearestRepeaterFallback => 'Nearest repeater fallback';
|
||||
|
||||
@override
|
||||
String get deleteAllStoredMessageHistory =>
|
||||
'Delete all stored message history';
|
||||
|
||||
@override
|
||||
String get messageFontSize => 'Message font size';
|
||||
|
||||
@override
|
||||
String get rotateMapWithHeading => 'Rotate map with heading';
|
||||
|
||||
@override
|
||||
String get showMapDebugInfo => 'Show map debug info';
|
||||
|
||||
@override
|
||||
String get openMapInFullscreen => 'Open map in fullscreen';
|
||||
|
||||
@override
|
||||
String get showSarMarkersLabel => 'Show SAR markers';
|
||||
|
||||
@override
|
||||
String get displaySarMarkersOnTheMainMap =>
|
||||
'Display SAR markers on the main map';
|
||||
|
||||
@override
|
||||
String get showAllContactTrailsLabel => 'Show all contact trails';
|
||||
|
||||
@override
|
||||
String get hideRepeatersOnMap => 'Hide repeaters on map';
|
||||
|
||||
@override
|
||||
String get setMapScale => 'Set map scale';
|
||||
|
||||
@override
|
||||
String get customMapScaleSaved => 'Custom map scale saved';
|
||||
|
||||
@override
|
||||
String get voiceBitrate => 'Voice bitrate';
|
||||
|
||||
@override
|
||||
String get voiceCompressor => 'Voice compressor';
|
||||
|
||||
@override
|
||||
String get balancesQuietAndLoudSpeechLevels =>
|
||||
'Balances quiet and loud speech levels';
|
||||
|
||||
@override
|
||||
String get voiceLimiter => 'Voice limiter';
|
||||
|
||||
@override
|
||||
String get preventsClippingPeaksBeforeEncoding =>
|
||||
'Prevents clipping peaks before encoding';
|
||||
|
||||
@override
|
||||
String get micAutoGain => 'Mic auto gain';
|
||||
|
||||
@override
|
||||
String get letsTheRecorderAdjustInputLevel =>
|
||||
'Lets the recorder adjust input level';
|
||||
|
||||
@override
|
||||
String get echoCancellation => 'Echo cancellation';
|
||||
|
||||
@override
|
||||
String get noiseSuppression => 'Noise suppression';
|
||||
|
||||
@override
|
||||
String get trimSilenceInVoiceMessages => 'Trim silence in voice messages';
|
||||
|
||||
@override
|
||||
String get compressor => 'Compressor';
|
||||
|
||||
@override
|
||||
String get limiter => 'Limiter';
|
||||
|
||||
@override
|
||||
String get autoGain => 'Auto gain';
|
||||
|
||||
@override
|
||||
String get echoCancel => 'Echo cancel';
|
||||
|
||||
@override
|
||||
String get noiseSuppress => 'Noise suppress';
|
||||
|
||||
@override
|
||||
String get silenceTrim => 'Silence trim';
|
||||
|
||||
@override
|
||||
String get maxImageSize => 'Max image size';
|
||||
|
||||
@override
|
||||
String get imageCompression => 'Image compression';
|
||||
|
||||
@override
|
||||
String get grayscale => 'Grayscale';
|
||||
|
||||
@override
|
||||
String get ultraMode => 'Ultra mode';
|
||||
|
||||
@override
|
||||
String get fastPrivateGpsUpdates => 'Fast private GPS updates';
|
||||
|
||||
@override
|
||||
String get movementThreshold => 'Movement threshold';
|
||||
|
||||
@override
|
||||
String get fastGpsMovementThreshold => 'Fast GPS movement threshold';
|
||||
|
||||
@override
|
||||
String get fastGpsActiveuseInterval => 'Fast GPS active-use interval';
|
||||
|
||||
@override
|
||||
String get activeuseUpdateInterval => 'Active-use update interval';
|
||||
|
||||
@override
|
||||
String get repeatNearbyTraffic => 'Repeat nearby traffic';
|
||||
|
||||
@override
|
||||
String get relayThroughRepeatersAcrossTheMesh =>
|
||||
'Relay through repeaters across the mesh';
|
||||
|
||||
@override
|
||||
String get nearbyOnlyWithoutRepeaterFlooding =>
|
||||
'Nearby only, without repeater flooding';
|
||||
|
||||
@override
|
||||
String get multihop => 'Multi-hop';
|
||||
|
||||
@override
|
||||
String get createProfile => 'Create Profile';
|
||||
|
||||
@override
|
||||
String get renameProfile => 'Rename Profile';
|
||||
|
||||
@override
|
||||
String get newProfile => 'New Profile';
|
||||
|
||||
@override
|
||||
String get manageProfiles => 'Manage profiles';
|
||||
|
||||
@override
|
||||
String get enableProfilesToStartManagingThem =>
|
||||
'Enable profiles to start managing them.';
|
||||
|
||||
@override
|
||||
String get openMessage => 'Open message';
|
||||
|
||||
@override
|
||||
String get jumpToTheRelatedSarMessage => 'Jump to the related SAR message';
|
||||
|
||||
@override
|
||||
String get removeSarMarker => 'Remove SAR marker';
|
||||
|
||||
@override
|
||||
String get pleaseSelectADestinationToSendSarMarker =>
|
||||
'Please select a destination to send SAR marker';
|
||||
|
||||
@override
|
||||
String get sarMarkerBroadcastToPublicChannel =>
|
||||
'SAR marker broadcast to public channel';
|
||||
|
||||
@override
|
||||
String get sarMarkerSentToRoom => 'SAR marker sent to room';
|
||||
|
||||
@override
|
||||
String get loadFromGallery => 'Load from gallery';
|
||||
|
||||
@override
|
||||
String get replaceImage => 'Replace image';
|
||||
|
||||
@override
|
||||
String get selectFromGallery => 'Select from gallery';
|
||||
|
||||
@override
|
||||
String get team => 'Team';
|
||||
|
||||
@override
|
||||
String get found => 'Found';
|
||||
|
||||
@override
|
||||
String get staging => 'Staging';
|
||||
|
||||
@override
|
||||
String get object => 'Object';
|
||||
|
||||
@override
|
||||
String get quiet => 'Quiet';
|
||||
|
||||
@override
|
||||
String get moderate => 'Moderate';
|
||||
|
||||
@override
|
||||
String get busy => 'Busy';
|
||||
|
||||
@override
|
||||
String get spectrumScanReturnedNoCandidateFrequencies =>
|
||||
'Spectrum scan returned no candidate frequencies';
|
||||
|
||||
@override
|
||||
String get searchMessages => 'Search messages';
|
||||
|
||||
@override
|
||||
String get sendImageFromGallery => 'Send image from gallery';
|
||||
|
||||
@override
|
||||
String get takePhoto => 'Take photo';
|
||||
|
||||
@override
|
||||
String get dmOnly => 'DM only';
|
||||
|
||||
@override
|
||||
String get allMessages => 'All messages';
|
||||
|
||||
@override
|
||||
String get sendToPublicChannel => 'Send to Public Channel?';
|
||||
|
||||
@override
|
||||
String get selectMarkerTypeAndDestination =>
|
||||
'Select marker type and destination';
|
||||
|
||||
@override
|
||||
String get noDestinationsAvailableLabel => 'No destinations available';
|
||||
|
||||
@override
|
||||
String get image => 'Image';
|
||||
|
||||
@override
|
||||
String get format => 'Format';
|
||||
|
||||
@override
|
||||
String get dimensions => 'Dimensions';
|
||||
|
||||
@override
|
||||
String get segments => 'Segments';
|
||||
|
||||
@override
|
||||
String get transfers => 'Transfers';
|
||||
|
||||
@override
|
||||
String get downloadedBy => 'Downloaded by';
|
||||
|
||||
@override
|
||||
String get saveDiscoverySettings => 'Save discovery settings';
|
||||
|
||||
@override
|
||||
String get savePublicInfo => 'Save public info';
|
||||
|
||||
@override
|
||||
String get saveRadioSettings => 'Save radio settings';
|
||||
|
||||
@override
|
||||
String get savePath => 'Save Path';
|
||||
|
||||
@override
|
||||
String get wipeDeviceData => 'Wipe device data';
|
||||
|
||||
@override
|
||||
String get wipeDevice => 'Wipe device';
|
||||
|
||||
@override
|
||||
String get destructiveDeviceActions => 'Destructive device actions.';
|
||||
|
||||
@override
|
||||
String get chooseAPresetOrFinetuneCustomRadioSettings =>
|
||||
'Choose a preset or fine-tune custom radio settings.';
|
||||
|
||||
@override
|
||||
String get chooseTheNameAndLocationThisDeviceShares =>
|
||||
'Choose the name and location this device shares.';
|
||||
|
||||
@override
|
||||
String get availableSpaceOnThisDevice => 'Available space on this device.';
|
||||
|
||||
@override
|
||||
String get used => 'Used';
|
||||
|
||||
@override
|
||||
String get total => 'Total';
|
||||
|
||||
@override
|
||||
String get renameValue => 'Rename value';
|
||||
|
||||
@override
|
||||
String get customizeFields => 'Customize fields';
|
||||
|
||||
@override
|
||||
String get livePreview => 'Live preview';
|
||||
|
||||
@override
|
||||
String get refreshSchedule => 'Refresh schedule';
|
||||
|
||||
@override
|
||||
String get noResponse => 'No response';
|
||||
|
||||
@override
|
||||
String get refreshing => 'Refreshing';
|
||||
|
||||
@override
|
||||
String get unavailable => 'Unavailable';
|
||||
|
||||
@override
|
||||
String get pickARelayOrNodeToWatchInSensors =>
|
||||
'Pick a relay or node to watch in Sensors.';
|
||||
|
||||
@override
|
||||
String get publicKeyLabel => 'Public key';
|
||||
|
||||
@override
|
||||
String get alreadyInContacts => 'Already in contacts';
|
||||
|
||||
@override
|
||||
String get connectToADeviceBeforeAddingContacts =>
|
||||
'Connect to a device before adding contacts';
|
||||
|
||||
@override
|
||||
String get fromContacts => 'From contacts';
|
||||
|
||||
@override
|
||||
String get onlineOnly => 'Online only';
|
||||
|
||||
@override
|
||||
String get inBoth => 'In both';
|
||||
|
||||
@override
|
||||
String get source => 'Source';
|
||||
|
||||
@override
|
||||
String get manualRouteEdit => 'Manual route edit';
|
||||
|
||||
@override
|
||||
String get observedMeshRoute => 'Observed mesh route';
|
||||
|
||||
@override
|
||||
String get allMessagesCleared => 'All messages cleared';
|
||||
|
||||
@override
|
||||
String get onlineTraceDatabaseCleared => 'Online trace database cleared';
|
||||
|
||||
@override
|
||||
String get packetLogsCleared => 'Packet logs cleared';
|
||||
|
||||
@override
|
||||
String get hexDataCopiedToClipboard => 'Hex data copied to clipboard';
|
||||
|
||||
@override
|
||||
String get developerModeEnabled => 'Developer mode enabled';
|
||||
|
||||
@override
|
||||
String get developerModeDisabled => 'Developer mode disabled';
|
||||
|
||||
@override
|
||||
String get clipboardIsEmpty => 'Clipboard is empty';
|
||||
|
||||
@override
|
||||
String get contactImported => 'Contact imported';
|
||||
|
||||
@override
|
||||
String get contactLinkCopiedToClipboard => 'Contact link copied to clipboard';
|
||||
|
||||
@override
|
||||
String get failedToExportContact => 'Failed to export contact';
|
||||
|
||||
@override
|
||||
String get noLogsToExport => 'No logs to export';
|
||||
|
||||
@override
|
||||
String get exportAsCsv => 'Export as CSV';
|
||||
|
||||
@override
|
||||
String get exportAsText => 'Export as Text';
|
||||
|
||||
@override
|
||||
String get receivedRfc3339 => 'Received (RFC3339)';
|
||||
|
||||
@override
|
||||
String get buildTime => 'Build Time';
|
||||
|
||||
@override
|
||||
String get downloadUrlNotAvailable => 'Download URL not available';
|
||||
|
||||
@override
|
||||
String get cannotOpenDownloadUrl => 'Cannot open download URL';
|
||||
|
||||
@override
|
||||
String get updateCheckIsOnlyAvailableOnAndroid =>
|
||||
'Update check is only available on Android';
|
||||
|
||||
@override
|
||||
String get youAreRunningTheLatestVersion =>
|
||||
'You are running the latest version';
|
||||
|
||||
@override
|
||||
String get updateAvailableButDownloadUrlNotFound =>
|
||||
'Update available but download URL not found';
|
||||
|
||||
@override
|
||||
String get startTictactoe => 'Start Tic-Tac-Toe';
|
||||
|
||||
@override
|
||||
String get tictactoeUnavailable => 'Tic-Tac-Toe unavailable';
|
||||
|
||||
@override
|
||||
String get tictactoeOpponentUnknown => 'Tic-Tac-Toe: opponent unknown';
|
||||
|
||||
@override
|
||||
String get tictactoeWaitingForStart => 'Tic-Tac-Toe: waiting for start';
|
||||
|
||||
@override
|
||||
String get acceptsShareLinks => 'Accepts share links';
|
||||
|
||||
@override
|
||||
String get supportsRawHex => 'Supports raw hex';
|
||||
|
||||
@override
|
||||
String get clipboardfriendly => 'Clipboard-friendly';
|
||||
|
||||
@override
|
||||
String get captured => 'Captured';
|
||||
|
||||
@override
|
||||
String get size => 'Size';
|
||||
|
||||
@override
|
||||
String get noCustomChannelsToClear => 'No custom channels to clear.';
|
||||
|
||||
@override
|
||||
String get noDeviceContactsToClear => 'No device contacts to clear.';
|
||||
}
|
||||
|
||||
@@ -1736,4 +1736,970 @@ class AppLocalizationsEs extends AppLocalizations {
|
||||
|
||||
@override
|
||||
String get requestingVoice => 'Solicitando voz';
|
||||
|
||||
@override
|
||||
String get device => 'dispositivo';
|
||||
|
||||
@override
|
||||
String get change => 'Cambiar';
|
||||
|
||||
@override
|
||||
String get wizardOverviewDescription =>
|
||||
'Esta aplicación reúne mensajería MeshCore, actualizaciones SAR de campo, mapas y herramientas del dispositivo en un solo lugar.';
|
||||
|
||||
@override
|
||||
String get wizardOverviewFeature1 =>
|
||||
'Envía mensajes directos, publicaciones en salas y mensajes de canal desde la pestaña principal Mensajes.';
|
||||
|
||||
@override
|
||||
String get wizardOverviewFeature2 =>
|
||||
'Comparte marcadores SAR, dibujos del mapa, clips de voz e imágenes a través de la malla.';
|
||||
|
||||
@override
|
||||
String get wizardOverviewFeature3 =>
|
||||
'Conéctate por BLE o TCP y luego gestiona la radio complementaria desde la aplicación.';
|
||||
|
||||
@override
|
||||
String get wizardMessagingTitle => 'Mensajería e informes de campo';
|
||||
|
||||
@override
|
||||
String get wizardMessagingDescription =>
|
||||
'Aquí los mensajes son más que texto plano. La aplicación ya admite varias cargas operativas y flujos de transferencia.';
|
||||
|
||||
@override
|
||||
String get wizardMessagingFeature1 =>
|
||||
'Envía mensajes directos, publicaciones en salas y tráfico de canal desde un solo editor.';
|
||||
|
||||
@override
|
||||
String get wizardMessagingFeature2 =>
|
||||
'Crea actualizaciones SAR y plantillas SAR reutilizables para informes de campo habituales.';
|
||||
|
||||
@override
|
||||
String get wizardMessagingFeature3 =>
|
||||
'Transfiere sesiones de voz e imágenes con progreso y estimaciones de tiempo de aire en la interfaz.';
|
||||
|
||||
@override
|
||||
String get wizardConnectDeviceTitle => 'Conectar dispositivo';
|
||||
|
||||
@override
|
||||
String get wizardConnectDeviceDescription =>
|
||||
'Conecta tu radio MeshCore, elige un nombre y aplica un ajuste predefinido antes de continuar.';
|
||||
|
||||
@override
|
||||
String get wizardSetupBadge => 'Configuración';
|
||||
|
||||
@override
|
||||
String get wizardOverviewBadge => 'Resumen';
|
||||
|
||||
@override
|
||||
String wizardConnectedToDevice(String deviceName) {
|
||||
return 'Conectado a $deviceName';
|
||||
}
|
||||
|
||||
@override
|
||||
String get wizardNoDeviceConnected =>
|
||||
'Aún no hay ningún dispositivo conectado';
|
||||
|
||||
@override
|
||||
String get wizardSkipForNow => 'Omitir por ahora';
|
||||
|
||||
@override
|
||||
String get wizardDeviceNameLabel => 'Nombre del dispositivo';
|
||||
|
||||
@override
|
||||
String get wizardDeviceNameHelp =>
|
||||
'Este nombre se anuncia a otros usuarios de MeshCore.';
|
||||
|
||||
@override
|
||||
String get wizardConfigRegionLabel => 'Región de configuración';
|
||||
|
||||
@override
|
||||
String get wizardConfigRegionHelp =>
|
||||
'Usa la lista oficial completa de ajustes predefinidos de MeshCore. El valor predeterminado es EU/UK (Narrow).';
|
||||
|
||||
@override
|
||||
String get wizardPresetNote1 =>
|
||||
'Asegúrate de que el ajuste seleccionado coincida con la normativa local de radio.';
|
||||
|
||||
@override
|
||||
String get wizardPresetNote2 =>
|
||||
'La lista coincide con la fuente oficial de ajustes predefinidos de la herramienta de configuración de MeshCore.';
|
||||
|
||||
@override
|
||||
String get wizardPresetNote3 =>
|
||||
'EU/UK (Narrow) sigue seleccionado por defecto durante la incorporación.';
|
||||
|
||||
@override
|
||||
String get wizardSaving => 'Guardando...';
|
||||
|
||||
@override
|
||||
String get wizardSaveAndContinue => 'Guardar y continuar';
|
||||
|
||||
@override
|
||||
String get wizardEnterDeviceName =>
|
||||
'Introduce un nombre de dispositivo antes de continuar.';
|
||||
|
||||
@override
|
||||
String wizardDeviceSetupSaved(String deviceName, String presetName) {
|
||||
return 'Se guardó $deviceName con $presetName.';
|
||||
}
|
||||
|
||||
@override
|
||||
String get wizardNetworkTitle => 'Contactos, salas y repetidores';
|
||||
|
||||
@override
|
||||
String get wizardNetworkDescription =>
|
||||
'La pestaña Contactos organiza la red que descubres y las rutas que aprendes con el tiempo.';
|
||||
|
||||
@override
|
||||
String get wizardNetworkFeature1 =>
|
||||
'Revisa miembros del equipo, repetidores, salas, canales y anuncios pendientes en una sola lista.';
|
||||
|
||||
@override
|
||||
String get wizardNetworkFeature2 =>
|
||||
'Usa ping inteligente, acceso a salas, rutas aprendidas y herramientas de reinicio de ruta cuando la conectividad se complique.';
|
||||
|
||||
@override
|
||||
String get wizardNetworkFeature3 =>
|
||||
'Crea canales y gestiona destinos de red sin salir de la aplicación.';
|
||||
|
||||
@override
|
||||
String get wizardMapOpsTitle => 'Mapa, rastros y geometría compartida';
|
||||
|
||||
@override
|
||||
String get wizardMapOpsDescription =>
|
||||
'El mapa de la aplicación está vinculado directamente a la mensajería, el seguimiento y las superposiciones SAR en lugar de ser un visor aparte.';
|
||||
|
||||
@override
|
||||
String get wizardMapOpsFeature1 =>
|
||||
'Sigue tu propia posición, las ubicaciones de tus compañeros y los rastros de movimiento en el mapa.';
|
||||
|
||||
@override
|
||||
String get wizardMapOpsFeature2 =>
|
||||
'Abre dibujos desde mensajes, prévisualos en línea y elimínalos del mapa cuando sea necesario.';
|
||||
|
||||
@override
|
||||
String get wizardMapOpsFeature3 =>
|
||||
'Usa vistas de mapa de repetidores y superposiciones compartidas para comprender el alcance de la red sobre el terreno.';
|
||||
|
||||
@override
|
||||
String get wizardToolsTitle => 'Herramientas más allá de la mensajería';
|
||||
|
||||
@override
|
||||
String get wizardToolsDescription =>
|
||||
'Aquí hay más que las cuatro pestañas principales. La aplicación también incluye configuración, diagnóstico y flujos opcionales de sensores.';
|
||||
|
||||
@override
|
||||
String get wizardToolsFeature1 =>
|
||||
'Abre la configuración del dispositivo para cambiar ajustes de radio, telemetría, potencia TX y detalles del equipo complementario.';
|
||||
|
||||
@override
|
||||
String get wizardToolsFeature2 =>
|
||||
'Activa la pestaña Sensores cuando quieras paneles de sensores vigilados y acciones de actualización rápida.';
|
||||
|
||||
@override
|
||||
String get wizardToolsFeature3 =>
|
||||
'Usa registros de paquetes, escaneo de espectro y diagnósticos de desarrollador al solucionar problemas de la malla.';
|
||||
|
||||
@override
|
||||
String get contactInSensors => 'En Sensores';
|
||||
|
||||
@override
|
||||
String get contactAddToSensors => 'Añadir a Sensores';
|
||||
|
||||
@override
|
||||
String get contactSetPath => 'Definir ruta';
|
||||
|
||||
@override
|
||||
String contactAddedToSensors(String contactName) {
|
||||
return '$contactName se añadió a Sensores';
|
||||
}
|
||||
|
||||
@override
|
||||
String contactFailedToClearRoute(String error) {
|
||||
return 'No se pudo borrar la ruta: $error';
|
||||
}
|
||||
|
||||
@override
|
||||
String get contactRouteCleared => 'Ruta borrada';
|
||||
|
||||
@override
|
||||
String contactRouteSet(String route) {
|
||||
return 'Ruta establecida: $route';
|
||||
}
|
||||
|
||||
@override
|
||||
String contactFailedToSetRoute(String error) {
|
||||
return 'No se pudo establecer la ruta: $error';
|
||||
}
|
||||
|
||||
@override
|
||||
String get rssi => 'RSSI';
|
||||
|
||||
@override
|
||||
String get snr => 'SNR';
|
||||
|
||||
@override
|
||||
String get ackTimeout => 'Tiempo de espera ACK';
|
||||
|
||||
@override
|
||||
String get opcode => 'Código de operación';
|
||||
|
||||
@override
|
||||
String get payload => 'Carga útil';
|
||||
|
||||
@override
|
||||
String get hops => 'Saltos';
|
||||
|
||||
@override
|
||||
String get hashSize => 'Tamaño del hash';
|
||||
|
||||
@override
|
||||
String get pathBytes => 'Bytes de ruta';
|
||||
|
||||
@override
|
||||
String get selectedPath => 'Ruta seleccionada';
|
||||
|
||||
@override
|
||||
String get estimatedTx => 'Transmisión estimada';
|
||||
|
||||
@override
|
||||
String get senderToReceipt => 'Del remitente al recibo';
|
||||
|
||||
@override
|
||||
String get receivedCopies => 'Copias recibidas';
|
||||
|
||||
@override
|
||||
String get retryCause => 'Causa del reintento';
|
||||
|
||||
@override
|
||||
String get retryMode => 'Modo de reintento';
|
||||
|
||||
@override
|
||||
String get retryResult => 'Resultado del reintento';
|
||||
|
||||
@override
|
||||
String get lastRetry => 'Último reintento';
|
||||
|
||||
@override
|
||||
String get rxPackets => 'Paquetes RX';
|
||||
|
||||
@override
|
||||
String get mesh => 'Mesh';
|
||||
|
||||
@override
|
||||
String get rate => 'Velocidad';
|
||||
|
||||
@override
|
||||
String get window => 'Ventana';
|
||||
|
||||
@override
|
||||
String get posttxDelay => 'Retardo post-tx';
|
||||
|
||||
@override
|
||||
String get bandpass => 'Paso de banda';
|
||||
|
||||
@override
|
||||
String get bandpassFilterVoice => 'Filtro de paso de banda de voz';
|
||||
|
||||
@override
|
||||
String get frequency => 'Frecuencia';
|
||||
|
||||
@override
|
||||
String get australia => 'Australia';
|
||||
|
||||
@override
|
||||
String get australiaNarrow => 'Australia (Estrecho)';
|
||||
|
||||
@override
|
||||
String get australiaQld => 'Australia: QLD';
|
||||
|
||||
@override
|
||||
String get australiaSaWa => 'Australia: SA, WA';
|
||||
|
||||
@override
|
||||
String get newZealand => 'Nueva Zelanda';
|
||||
|
||||
@override
|
||||
String get newZealandNarrow => 'Nueva Zelanda (Estrecho)';
|
||||
|
||||
@override
|
||||
String get switzerland => 'Suiza';
|
||||
|
||||
@override
|
||||
String get portugal433 => 'Portugal 433';
|
||||
|
||||
@override
|
||||
String get portugal868 => 'Portugal 868';
|
||||
|
||||
@override
|
||||
String get czechRepublicNarrow => 'República Checa (Estrecho)';
|
||||
|
||||
@override
|
||||
String get eu433mhzLongRange => 'EU 433MHz (Largo Alcance)';
|
||||
|
||||
@override
|
||||
String get euukDeprecated => 'UE/RU (Obsoleto)';
|
||||
|
||||
@override
|
||||
String get euukNarrow => 'UE/RU (Estrecho)';
|
||||
|
||||
@override
|
||||
String get usacanadaRecommended => 'EE.UU./Canadá (Recomendado)';
|
||||
|
||||
@override
|
||||
String get vietnamDeprecated => 'Vietnam (Obsoleto)';
|
||||
|
||||
@override
|
||||
String get vietnamNarrow => 'Vietnam (Estrecho)';
|
||||
|
||||
@override
|
||||
String get active => 'Activo';
|
||||
|
||||
@override
|
||||
String get addContact => 'Añadir contacto';
|
||||
|
||||
@override
|
||||
String get all => 'Todo';
|
||||
|
||||
@override
|
||||
String get autoResolve => 'Resolver automáticamente';
|
||||
|
||||
@override
|
||||
String get clearAllLabel => 'Borrar todo';
|
||||
|
||||
@override
|
||||
String get clearRelays => 'Borrar repetidores';
|
||||
|
||||
@override
|
||||
String get clearFilters => 'Borrar filtros';
|
||||
|
||||
@override
|
||||
String get clearRoute => 'Borrar ruta';
|
||||
|
||||
@override
|
||||
String get clearMessages => 'Borrar mensajes';
|
||||
|
||||
@override
|
||||
String get clearScale => 'Borrar escala';
|
||||
|
||||
@override
|
||||
String get clearDiscoveries => 'Borrar descubrimientos';
|
||||
|
||||
@override
|
||||
String get clearOnlineTraceDatabase => 'Borrar base de datos de trazas';
|
||||
|
||||
@override
|
||||
String get clearAllChannels => 'Borrar todos los canales';
|
||||
|
||||
@override
|
||||
String get clearAllContacts => 'Borrar todos los contactos';
|
||||
|
||||
@override
|
||||
String get clearChannels => 'Borrar canales';
|
||||
|
||||
@override
|
||||
String get clearContacts => 'Borrar contactos';
|
||||
|
||||
@override
|
||||
String get clearPathOnMaxRetry => 'Borrar ruta en máximo reintento';
|
||||
|
||||
@override
|
||||
String get create => 'Crear';
|
||||
|
||||
@override
|
||||
String get custom => 'Personalizado';
|
||||
|
||||
@override
|
||||
String get defaultValue => 'Predeterminado';
|
||||
|
||||
@override
|
||||
String get duplicate => 'Duplicar';
|
||||
|
||||
@override
|
||||
String get editName => 'Editar nombre';
|
||||
|
||||
@override
|
||||
String get open => 'Abrir';
|
||||
|
||||
@override
|
||||
String get paste => 'Pegar';
|
||||
|
||||
@override
|
||||
String get preview => 'Vista previa';
|
||||
|
||||
@override
|
||||
String get remove => 'Eliminar';
|
||||
|
||||
@override
|
||||
String get rename => 'Renombrar';
|
||||
|
||||
@override
|
||||
String get resolveAll => 'Resolver todo';
|
||||
|
||||
@override
|
||||
String get send => 'Enviar';
|
||||
|
||||
@override
|
||||
String get sendAnyway => 'Enviar de todos modos';
|
||||
|
||||
@override
|
||||
String get share => 'Compartir';
|
||||
|
||||
@override
|
||||
String get shareContact => 'Compartir contacto';
|
||||
|
||||
@override
|
||||
String get trace => 'Traza';
|
||||
|
||||
@override
|
||||
String get use => 'Usar';
|
||||
|
||||
@override
|
||||
String get useSelectedFrequency => 'Usar frecuencia seleccionada';
|
||||
|
||||
@override
|
||||
String get discovery => 'Descubrimiento';
|
||||
|
||||
@override
|
||||
String get discoverRepeaters => 'Descubrir repetidores';
|
||||
|
||||
@override
|
||||
String get discoverSensors => 'Descubrir sensores';
|
||||
|
||||
@override
|
||||
String get repeaterDiscoverySent => 'Descubrimiento de repetidores enviado';
|
||||
|
||||
@override
|
||||
String get sensorDiscoverySent => 'Descubrimiento de sensores enviado';
|
||||
|
||||
@override
|
||||
String get clearedPendingDiscoveries =>
|
||||
'Descubrimientos pendientes borrados.';
|
||||
|
||||
@override
|
||||
String get autoDiscovery => 'Descubrimiento automático';
|
||||
|
||||
@override
|
||||
String get enableAutomaticAdding => 'Habilitar adición automática';
|
||||
|
||||
@override
|
||||
String get autoaddRepeaters => 'Añadir repetidores automáticamente';
|
||||
|
||||
@override
|
||||
String get autoaddRoomServers => 'Añadir servidores de sala automáticamente';
|
||||
|
||||
@override
|
||||
String get autoaddSensors => 'Añadir sensores automáticamente';
|
||||
|
||||
@override
|
||||
String get autoaddUsers => 'Añadir usuarios automáticamente';
|
||||
|
||||
@override
|
||||
String get overwriteOldestWhenFull =>
|
||||
'Sobrescribir más antiguos cuando esté lleno';
|
||||
|
||||
@override
|
||||
String get storage => 'Almacenamiento';
|
||||
|
||||
@override
|
||||
String get dangerZone => 'Zona de peligro';
|
||||
|
||||
@override
|
||||
String get profiles => 'Perfiles';
|
||||
|
||||
@override
|
||||
String get favourites => 'Favoritos';
|
||||
|
||||
@override
|
||||
String get sensors => 'Sensores';
|
||||
|
||||
@override
|
||||
String get others => 'Otros';
|
||||
|
||||
@override
|
||||
String get gpsModule => 'Módulo GPS';
|
||||
|
||||
@override
|
||||
String get liveTraffic => 'Tráfico en vivo';
|
||||
|
||||
@override
|
||||
String get repeatersMap => 'Mapa de repetidores';
|
||||
|
||||
@override
|
||||
String get spectrumScan => 'Escaneo de espectro';
|
||||
|
||||
@override
|
||||
String get blePacketLogs => 'Registros de paquetes BLE';
|
||||
|
||||
@override
|
||||
String get onlineTraceDatabase => 'Base de datos de trazas';
|
||||
|
||||
@override
|
||||
String get routePathByteSize => 'Tamaño en bytes de la ruta';
|
||||
|
||||
@override
|
||||
String get messageNotifications => 'Notificaciones de mensajes';
|
||||
|
||||
@override
|
||||
String get sarAlerts => 'Alertas SAR';
|
||||
|
||||
@override
|
||||
String get discoveryNotifications => 'Notificaciones de descubrimiento';
|
||||
|
||||
@override
|
||||
String get updateNotifications => 'Notificaciones de actualización';
|
||||
|
||||
@override
|
||||
String get muteWhileAppIsOpen => 'Silenciar con la app abierta';
|
||||
|
||||
@override
|
||||
String get disableContacts => 'Desactivar contactos';
|
||||
|
||||
@override
|
||||
String get enableSensorsTab => 'Habilitar pestaña Sensores';
|
||||
|
||||
@override
|
||||
String get enableProfiles => 'Habilitar perfiles';
|
||||
|
||||
@override
|
||||
String get autoRouteRotation => 'Rotación automática de ruta';
|
||||
|
||||
@override
|
||||
String get nearestRepeaterFallback => 'Repetidor más cercano como reserva';
|
||||
|
||||
@override
|
||||
String get deleteAllStoredMessageHistory =>
|
||||
'Eliminar todo el historial de mensajes';
|
||||
|
||||
@override
|
||||
String get messageFontSize => 'Tamaño de fuente de mensajes';
|
||||
|
||||
@override
|
||||
String get rotateMapWithHeading => 'Rotar mapa con rumbo';
|
||||
|
||||
@override
|
||||
String get showMapDebugInfo => 'Mostrar info de depuración del mapa';
|
||||
|
||||
@override
|
||||
String get openMapInFullscreen => 'Abrir mapa en pantalla completa';
|
||||
|
||||
@override
|
||||
String get showSarMarkersLabel => 'Mostrar marcadores SAR';
|
||||
|
||||
@override
|
||||
String get displaySarMarkersOnTheMainMap =>
|
||||
'Mostrar marcadores SAR en el mapa principal';
|
||||
|
||||
@override
|
||||
String get showAllContactTrailsLabel =>
|
||||
'Mostrar todos los rastros de contactos';
|
||||
|
||||
@override
|
||||
String get hideRepeatersOnMap => 'Ocultar repetidores en el mapa';
|
||||
|
||||
@override
|
||||
String get setMapScale => 'Establecer escala del mapa';
|
||||
|
||||
@override
|
||||
String get customMapScaleSaved => 'Escala de mapa personalizada guardada';
|
||||
|
||||
@override
|
||||
String get voiceBitrate => 'Tasa de bits de voz';
|
||||
|
||||
@override
|
||||
String get voiceCompressor => 'Compresor de voz';
|
||||
|
||||
@override
|
||||
String get balancesQuietAndLoudSpeechLevels =>
|
||||
'Equilibra niveles de habla suaves y altos';
|
||||
|
||||
@override
|
||||
String get voiceLimiter => 'Limitador de voz';
|
||||
|
||||
@override
|
||||
String get preventsClippingPeaksBeforeEncoding =>
|
||||
'Previene recorte de picos antes de codificar';
|
||||
|
||||
@override
|
||||
String get micAutoGain => 'Ganancia automática del micrófono';
|
||||
|
||||
@override
|
||||
String get letsTheRecorderAdjustInputLevel =>
|
||||
'Permite al grabador ajustar el nivel de entrada';
|
||||
|
||||
@override
|
||||
String get echoCancellation => 'Cancelación de eco';
|
||||
|
||||
@override
|
||||
String get noiseSuppression => 'Supresión de ruido';
|
||||
|
||||
@override
|
||||
String get trimSilenceInVoiceMessages =>
|
||||
'Recortar silencio en mensajes de voz';
|
||||
|
||||
@override
|
||||
String get compressor => 'Compresor';
|
||||
|
||||
@override
|
||||
String get limiter => 'Limitador';
|
||||
|
||||
@override
|
||||
String get autoGain => 'Ganancia automática';
|
||||
|
||||
@override
|
||||
String get echoCancel => 'Eco';
|
||||
|
||||
@override
|
||||
String get noiseSuppress => 'Ruido';
|
||||
|
||||
@override
|
||||
String get silenceTrim => 'Silencio';
|
||||
|
||||
@override
|
||||
String get maxImageSize => 'Tamaño máximo de imagen';
|
||||
|
||||
@override
|
||||
String get imageCompression => 'Compresión de imagen';
|
||||
|
||||
@override
|
||||
String get grayscale => 'Escala de grises';
|
||||
|
||||
@override
|
||||
String get ultraMode => 'Modo ultra';
|
||||
|
||||
@override
|
||||
String get fastPrivateGpsUpdates => 'Actualizaciones GPS privadas rápidas';
|
||||
|
||||
@override
|
||||
String get movementThreshold => 'Umbral de movimiento';
|
||||
|
||||
@override
|
||||
String get fastGpsMovementThreshold => 'Umbral de movimiento GPS rápido';
|
||||
|
||||
@override
|
||||
String get fastGpsActiveuseInterval => 'Intervalo de uso activo GPS rápido';
|
||||
|
||||
@override
|
||||
String get activeuseUpdateInterval =>
|
||||
'Intervalo de actualización de uso activo';
|
||||
|
||||
@override
|
||||
String get repeatNearbyTraffic => 'Repetir tráfico cercano';
|
||||
|
||||
@override
|
||||
String get relayThroughRepeatersAcrossTheMesh =>
|
||||
'Retransmitir a través de repetidores en la red';
|
||||
|
||||
@override
|
||||
String get nearbyOnlyWithoutRepeaterFlooding =>
|
||||
'Solo cercanos, sin inundación de repetidores';
|
||||
|
||||
@override
|
||||
String get multihop => 'Multi-salto';
|
||||
|
||||
@override
|
||||
String get createProfile => 'Crear perfil';
|
||||
|
||||
@override
|
||||
String get renameProfile => 'Renombrar perfil';
|
||||
|
||||
@override
|
||||
String get newProfile => 'Nuevo perfil';
|
||||
|
||||
@override
|
||||
String get manageProfiles => 'Gestionar perfiles';
|
||||
|
||||
@override
|
||||
String get enableProfilesToStartManagingThem =>
|
||||
'Habilite los perfiles para comenzar a gestionarlos.';
|
||||
|
||||
@override
|
||||
String get openMessage => 'Abrir mensaje';
|
||||
|
||||
@override
|
||||
String get jumpToTheRelatedSarMessage => 'Ir al mensaje SAR relacionado';
|
||||
|
||||
@override
|
||||
String get removeSarMarker => 'Eliminar marcador SAR';
|
||||
|
||||
@override
|
||||
String get pleaseSelectADestinationToSendSarMarker =>
|
||||
'Seleccione un destino para enviar el marcador SAR';
|
||||
|
||||
@override
|
||||
String get sarMarkerBroadcastToPublicChannel =>
|
||||
'Marcador SAR transmitido al canal público';
|
||||
|
||||
@override
|
||||
String get sarMarkerSentToRoom => 'Marcador SAR enviado a la sala';
|
||||
|
||||
@override
|
||||
String get loadFromGallery => 'Cargar desde galería';
|
||||
|
||||
@override
|
||||
String get replaceImage => 'Reemplazar imagen';
|
||||
|
||||
@override
|
||||
String get selectFromGallery => 'Seleccionar de galería';
|
||||
|
||||
@override
|
||||
String get team => 'Equipo';
|
||||
|
||||
@override
|
||||
String get found => 'Encontrado';
|
||||
|
||||
@override
|
||||
String get staging => 'Área de reunión';
|
||||
|
||||
@override
|
||||
String get object => 'Objeto';
|
||||
|
||||
@override
|
||||
String get quiet => 'Silencioso';
|
||||
|
||||
@override
|
||||
String get moderate => 'Moderado';
|
||||
|
||||
@override
|
||||
String get busy => 'Ocupado';
|
||||
|
||||
@override
|
||||
String get spectrumScanReturnedNoCandidateFrequencies =>
|
||||
'El escaneo de espectro no encontró frecuencias candidatas';
|
||||
|
||||
@override
|
||||
String get searchMessages => 'Buscar mensajes';
|
||||
|
||||
@override
|
||||
String get sendImageFromGallery => 'Enviar imagen desde galería';
|
||||
|
||||
@override
|
||||
String get takePhoto => 'Tomar foto';
|
||||
|
||||
@override
|
||||
String get dmOnly => 'Solo mensaje directo';
|
||||
|
||||
@override
|
||||
String get allMessages => 'Todos los mensajes';
|
||||
|
||||
@override
|
||||
String get sendToPublicChannel => '¿Enviar al canal público?';
|
||||
|
||||
@override
|
||||
String get selectMarkerTypeAndDestination =>
|
||||
'Seleccione tipo de marcador y destino';
|
||||
|
||||
@override
|
||||
String get noDestinationsAvailableLabel => 'No hay destinos disponibles';
|
||||
|
||||
@override
|
||||
String get image => 'Imagen';
|
||||
|
||||
@override
|
||||
String get format => 'Formato';
|
||||
|
||||
@override
|
||||
String get dimensions => 'Dimensiones';
|
||||
|
||||
@override
|
||||
String get segments => 'Segmentos';
|
||||
|
||||
@override
|
||||
String get transfers => 'Transferencias';
|
||||
|
||||
@override
|
||||
String get downloadedBy => 'Descargado por';
|
||||
|
||||
@override
|
||||
String get saveDiscoverySettings => 'Guardar ajustes de descubrimiento';
|
||||
|
||||
@override
|
||||
String get savePublicInfo => 'Guardar información pública';
|
||||
|
||||
@override
|
||||
String get saveRadioSettings => 'Guardar ajustes de radio';
|
||||
|
||||
@override
|
||||
String get savePath => 'Guardar ruta';
|
||||
|
||||
@override
|
||||
String get wipeDeviceData => 'Borrar datos del dispositivo';
|
||||
|
||||
@override
|
||||
String get wipeDevice => 'Borrar dispositivo';
|
||||
|
||||
@override
|
||||
String get destructiveDeviceActions =>
|
||||
'Acciones destructivas del dispositivo.';
|
||||
|
||||
@override
|
||||
String get chooseAPresetOrFinetuneCustomRadioSettings =>
|
||||
'Elija un preajuste o ajuste la configuración de radio.';
|
||||
|
||||
@override
|
||||
String get chooseTheNameAndLocationThisDeviceShares =>
|
||||
'Elija el nombre y la ubicación que comparte este dispositivo.';
|
||||
|
||||
@override
|
||||
String get availableSpaceOnThisDevice =>
|
||||
'Espacio disponible en este dispositivo.';
|
||||
|
||||
@override
|
||||
String get used => 'Usado';
|
||||
|
||||
@override
|
||||
String get total => 'Total';
|
||||
|
||||
@override
|
||||
String get renameValue => 'Renombrar valor';
|
||||
|
||||
@override
|
||||
String get customizeFields => 'Personalizar campos';
|
||||
|
||||
@override
|
||||
String get livePreview => 'Vista previa en vivo';
|
||||
|
||||
@override
|
||||
String get refreshSchedule => 'Programación de actualización';
|
||||
|
||||
@override
|
||||
String get noResponse => 'Sin respuesta';
|
||||
|
||||
@override
|
||||
String get refreshing => 'Actualizando';
|
||||
|
||||
@override
|
||||
String get unavailable => 'No disponible';
|
||||
|
||||
@override
|
||||
String get pickARelayOrNodeToWatchInSensors =>
|
||||
'Elija un relay o nodo para observar.';
|
||||
|
||||
@override
|
||||
String get publicKeyLabel => 'Clave pública';
|
||||
|
||||
@override
|
||||
String get alreadyInContacts => 'Ya en contactos';
|
||||
|
||||
@override
|
||||
String get connectToADeviceBeforeAddingContacts =>
|
||||
'Conéctese a un dispositivo antes de añadir contactos';
|
||||
|
||||
@override
|
||||
String get fromContacts => 'De contactos';
|
||||
|
||||
@override
|
||||
String get onlineOnly => 'Solo en línea';
|
||||
|
||||
@override
|
||||
String get inBoth => 'En ambos';
|
||||
|
||||
@override
|
||||
String get source => 'Fuente';
|
||||
|
||||
@override
|
||||
String get manualRouteEdit => 'Edición manual de ruta';
|
||||
|
||||
@override
|
||||
String get observedMeshRoute => 'Ruta mesh observada';
|
||||
|
||||
@override
|
||||
String get allMessagesCleared => 'Todos los mensajes borrados';
|
||||
|
||||
@override
|
||||
String get onlineTraceDatabaseCleared => 'Base de datos de trazas borrada';
|
||||
|
||||
@override
|
||||
String get packetLogsCleared => 'Registros de paquetes borrados';
|
||||
|
||||
@override
|
||||
String get hexDataCopiedToClipboard => 'Datos hex copiados al portapapeles';
|
||||
|
||||
@override
|
||||
String get developerModeEnabled => 'Modo desarrollador activado';
|
||||
|
||||
@override
|
||||
String get developerModeDisabled => 'Modo desarrollador desactivado';
|
||||
|
||||
@override
|
||||
String get clipboardIsEmpty => 'El portapapeles está vacío';
|
||||
|
||||
@override
|
||||
String get contactImported => 'Contacto importado';
|
||||
|
||||
@override
|
||||
String get contactLinkCopiedToClipboard =>
|
||||
'Enlace de contacto copiado al portapapeles';
|
||||
|
||||
@override
|
||||
String get failedToExportContact => 'Error al exportar el contacto';
|
||||
|
||||
@override
|
||||
String get noLogsToExport => 'No hay registros para exportar';
|
||||
|
||||
@override
|
||||
String get exportAsCsv => 'Exportar como CSV';
|
||||
|
||||
@override
|
||||
String get exportAsText => 'Exportar como texto';
|
||||
|
||||
@override
|
||||
String get receivedRfc3339 => 'Recibido (RFC3339)';
|
||||
|
||||
@override
|
||||
String get buildTime => 'Hora de compilación';
|
||||
|
||||
@override
|
||||
String get downloadUrlNotAvailable => 'URL de descarga no disponible';
|
||||
|
||||
@override
|
||||
String get cannotOpenDownloadUrl => 'No se puede abrir la URL de descarga';
|
||||
|
||||
@override
|
||||
String get updateCheckIsOnlyAvailableOnAndroid =>
|
||||
'La comprobación de actualizaciones solo está disponible en Android';
|
||||
|
||||
@override
|
||||
String get youAreRunningTheLatestVersion =>
|
||||
'Está ejecutando la última versión';
|
||||
|
||||
@override
|
||||
String get updateAvailableButDownloadUrlNotFound =>
|
||||
'Actualización disponible pero URL de descarga no encontrada';
|
||||
|
||||
@override
|
||||
String get startTictactoe => 'Iniciar Tic-Tac-Toe';
|
||||
|
||||
@override
|
||||
String get tictactoeUnavailable => 'Tic-Tac-Toe no disponible';
|
||||
|
||||
@override
|
||||
String get tictactoeOpponentUnknown => 'Tic-Tac-Toe: oponente desconocido';
|
||||
|
||||
@override
|
||||
String get tictactoeWaitingForStart => 'Tic-Tac-Toe: esperando inicio';
|
||||
|
||||
@override
|
||||
String get acceptsShareLinks => 'Acepta enlaces compartidos';
|
||||
|
||||
@override
|
||||
String get supportsRawHex => 'Soporta hex sin procesar';
|
||||
|
||||
@override
|
||||
String get clipboardfriendly => 'Compatible con portapapeles';
|
||||
|
||||
@override
|
||||
String get captured => 'Capturado';
|
||||
|
||||
@override
|
||||
String get size => 'Tamaño';
|
||||
|
||||
@override
|
||||
String get noCustomChannelsToClear =>
|
||||
'No hay canales personalizados que borrar.';
|
||||
|
||||
@override
|
||||
String get noDeviceContactsToClear =>
|
||||
'No hay contactos del dispositivo que borrar.';
|
||||
}
|
||||
|
||||
@@ -1740,4 +1740,970 @@ class AppLocalizationsFr extends AppLocalizations {
|
||||
|
||||
@override
|
||||
String get requestingVoice => 'Demande de voix';
|
||||
|
||||
@override
|
||||
String get device => 'appareil';
|
||||
|
||||
@override
|
||||
String get change => 'Modifier';
|
||||
|
||||
@override
|
||||
String get wizardOverviewDescription =>
|
||||
'Cette application réunit la messagerie MeshCore, les mises à jour SAR sur le terrain, la cartographie et les outils de l\'appareil au même endroit.';
|
||||
|
||||
@override
|
||||
String get wizardOverviewFeature1 =>
|
||||
'Envoyez des messages directs, des publications de salon et des messages de canal depuis l\'onglet principal Messages.';
|
||||
|
||||
@override
|
||||
String get wizardOverviewFeature2 =>
|
||||
'Partagez des marqueurs SAR, des dessins de carte, des extraits vocaux et des images sur le réseau maillé.';
|
||||
|
||||
@override
|
||||
String get wizardOverviewFeature3 =>
|
||||
'Connectez-vous en BLE ou en TCP, puis gérez la radio compagnon directement dans l\'application.';
|
||||
|
||||
@override
|
||||
String get wizardMessagingTitle => 'Messagerie et rapports de terrain';
|
||||
|
||||
@override
|
||||
String get wizardMessagingDescription =>
|
||||
'Ici, les messages sont plus que du texte brut. L\'application prend déjà en charge plusieurs charges opérationnelles et flux de transfert.';
|
||||
|
||||
@override
|
||||
String get wizardMessagingFeature1 =>
|
||||
'Envoyez des messages directs, des publications de salon et du trafic de canal depuis un seul compositeur.';
|
||||
|
||||
@override
|
||||
String get wizardMessagingFeature2 =>
|
||||
'Créez des mises à jour SAR et des modèles SAR réutilisables pour les rapports de terrain courants.';
|
||||
|
||||
@override
|
||||
String get wizardMessagingFeature3 =>
|
||||
'Transférez des sessions vocales et des images avec la progression et les estimations de temps d\'antenne dans l\'interface.';
|
||||
|
||||
@override
|
||||
String get wizardConnectDeviceTitle => 'Connecter l\'appareil';
|
||||
|
||||
@override
|
||||
String get wizardConnectDeviceDescription =>
|
||||
'Connectez votre radio MeshCore, choisissez un nom et appliquez un préréglage radio avant de continuer.';
|
||||
|
||||
@override
|
||||
String get wizardSetupBadge => 'Configuration';
|
||||
|
||||
@override
|
||||
String get wizardOverviewBadge => 'Aperçu';
|
||||
|
||||
@override
|
||||
String wizardConnectedToDevice(String deviceName) {
|
||||
return 'Connecté à $deviceName';
|
||||
}
|
||||
|
||||
@override
|
||||
String get wizardNoDeviceConnected =>
|
||||
'Aucun appareil connecté pour le moment';
|
||||
|
||||
@override
|
||||
String get wizardSkipForNow => 'Ignorer pour l\'instant';
|
||||
|
||||
@override
|
||||
String get wizardDeviceNameLabel => 'Nom de l\'appareil';
|
||||
|
||||
@override
|
||||
String get wizardDeviceNameHelp =>
|
||||
'Ce nom est annoncé aux autres utilisateurs de MeshCore.';
|
||||
|
||||
@override
|
||||
String get wizardConfigRegionLabel => 'Région de configuration';
|
||||
|
||||
@override
|
||||
String get wizardConfigRegionHelp =>
|
||||
'Utilise la liste officielle complète des préréglages MeshCore. La valeur par défaut est EU/UK (Narrow).';
|
||||
|
||||
@override
|
||||
String get wizardPresetNote1 =>
|
||||
'Assurez-vous que le préréglage sélectionné correspond à votre réglementation radio locale.';
|
||||
|
||||
@override
|
||||
String get wizardPresetNote2 =>
|
||||
'La liste correspond au flux officiel des préréglages de l\'outil de configuration MeshCore.';
|
||||
|
||||
@override
|
||||
String get wizardPresetNote3 =>
|
||||
'EU/UK (Narrow) reste sélectionné par défaut pendant l\'onboarding.';
|
||||
|
||||
@override
|
||||
String get wizardSaving => 'Enregistrement...';
|
||||
|
||||
@override
|
||||
String get wizardSaveAndContinue => 'Enregistrer et continuer';
|
||||
|
||||
@override
|
||||
String get wizardEnterDeviceName =>
|
||||
'Saisissez un nom d\'appareil avant de continuer.';
|
||||
|
||||
@override
|
||||
String wizardDeviceSetupSaved(String deviceName, String presetName) {
|
||||
return '$deviceName enregistré avec $presetName.';
|
||||
}
|
||||
|
||||
@override
|
||||
String get wizardNetworkTitle => 'Contacts, salons et répéteurs';
|
||||
|
||||
@override
|
||||
String get wizardNetworkDescription =>
|
||||
'L\'onglet Contacts organise le réseau que vous découvrez et les routes apprises au fil du temps.';
|
||||
|
||||
@override
|
||||
String get wizardNetworkFeature1 =>
|
||||
'Consultez les membres de l\'équipe, les répéteurs, les salons, les canaux et les annonces en attente dans une seule liste.';
|
||||
|
||||
@override
|
||||
String get wizardNetworkFeature2 =>
|
||||
'Utilisez le smart ping, la connexion aux salons, les chemins appris et les outils de réinitialisation des routes lorsque la connectivité devient chaotique.';
|
||||
|
||||
@override
|
||||
String get wizardNetworkFeature3 =>
|
||||
'Créez des canaux et gérez les destinations réseau sans quitter l\'application.';
|
||||
|
||||
@override
|
||||
String get wizardMapOpsTitle => 'Carte, traces et géométrie partagée';
|
||||
|
||||
@override
|
||||
String get wizardMapOpsDescription =>
|
||||
'La carte de l\'application est directement liée à la messagerie, au suivi et aux couches SAR au lieu d\'être un simple visualiseur séparé.';
|
||||
|
||||
@override
|
||||
String get wizardMapOpsFeature1 =>
|
||||
'Suivez votre position, celles de vos coéquipiers et les traces de déplacement sur la carte.';
|
||||
|
||||
@override
|
||||
String get wizardMapOpsFeature2 =>
|
||||
'Ouvrez les dessins depuis les messages, prévisualisez-les en ligne et supprimez-les de la carte si nécessaire.';
|
||||
|
||||
@override
|
||||
String get wizardMapOpsFeature3 =>
|
||||
'Utilisez les vues de carte des répéteurs et les couches partagées pour comprendre la portée du réseau sur le terrain.';
|
||||
|
||||
@override
|
||||
String get wizardToolsTitle => 'Outils au-delà de la messagerie';
|
||||
|
||||
@override
|
||||
String get wizardToolsDescription =>
|
||||
'Il y a plus ici que les quatre onglets principaux. L\'application inclut aussi la configuration, le diagnostic et des workflows de capteurs optionnels.';
|
||||
|
||||
@override
|
||||
String get wizardToolsFeature1 =>
|
||||
'Ouvrez la configuration de l\'appareil pour modifier les réglages radio, la télémétrie, la puissance TX et les détails du compagnon.';
|
||||
|
||||
@override
|
||||
String get wizardToolsFeature2 =>
|
||||
'Activez l\'onglet Capteurs lorsque vous voulez des tableaux de bord surveillés et des actions d\'actualisation rapide.';
|
||||
|
||||
@override
|
||||
String get wizardToolsFeature3 =>
|
||||
'Utilisez les journaux de paquets, le balayage du spectre et les diagnostics développeur pour dépanner le réseau maillé.';
|
||||
|
||||
@override
|
||||
String get contactInSensors => 'Dans Capteurs';
|
||||
|
||||
@override
|
||||
String get contactAddToSensors => 'Ajouter aux capteurs';
|
||||
|
||||
@override
|
||||
String get contactSetPath => 'Définir le chemin';
|
||||
|
||||
@override
|
||||
String contactAddedToSensors(String contactName) {
|
||||
return '$contactName ajouté aux capteurs';
|
||||
}
|
||||
|
||||
@override
|
||||
String contactFailedToClearRoute(String error) {
|
||||
return 'Échec de l\'effacement de la route : $error';
|
||||
}
|
||||
|
||||
@override
|
||||
String get contactRouteCleared => 'Route effacée';
|
||||
|
||||
@override
|
||||
String contactRouteSet(String route) {
|
||||
return 'Route définie : $route';
|
||||
}
|
||||
|
||||
@override
|
||||
String contactFailedToSetRoute(String error) {
|
||||
return 'Échec du réglage de la route : $error';
|
||||
}
|
||||
|
||||
@override
|
||||
String get rssi => 'RSSI';
|
||||
|
||||
@override
|
||||
String get snr => 'SNR';
|
||||
|
||||
@override
|
||||
String get ackTimeout => 'Délai ACK expiré';
|
||||
|
||||
@override
|
||||
String get opcode => 'Opcode';
|
||||
|
||||
@override
|
||||
String get payload => 'Charge utile';
|
||||
|
||||
@override
|
||||
String get hops => 'Sauts';
|
||||
|
||||
@override
|
||||
String get hashSize => 'Taille du hash';
|
||||
|
||||
@override
|
||||
String get pathBytes => 'Octets du chemin';
|
||||
|
||||
@override
|
||||
String get selectedPath => 'Chemin sélectionné';
|
||||
|
||||
@override
|
||||
String get estimatedTx => 'Émission estimée';
|
||||
|
||||
@override
|
||||
String get senderToReceipt => 'Émetteur au reçu';
|
||||
|
||||
@override
|
||||
String get receivedCopies => 'Copies reçues';
|
||||
|
||||
@override
|
||||
String get retryCause => 'Cause de la relance';
|
||||
|
||||
@override
|
||||
String get retryMode => 'Mode de relance';
|
||||
|
||||
@override
|
||||
String get retryResult => 'Résultat de la relance';
|
||||
|
||||
@override
|
||||
String get lastRetry => 'Dernière relance';
|
||||
|
||||
@override
|
||||
String get rxPackets => 'Paquets RX';
|
||||
|
||||
@override
|
||||
String get mesh => 'Mesh';
|
||||
|
||||
@override
|
||||
String get rate => 'Débit';
|
||||
|
||||
@override
|
||||
String get window => 'Fenêtre';
|
||||
|
||||
@override
|
||||
String get posttxDelay => 'Délai post-émission';
|
||||
|
||||
@override
|
||||
String get bandpass => 'Passe-bande';
|
||||
|
||||
@override
|
||||
String get bandpassFilterVoice => 'Filtre passe-bande voix';
|
||||
|
||||
@override
|
||||
String get frequency => 'Fréquence';
|
||||
|
||||
@override
|
||||
String get australia => 'Australie';
|
||||
|
||||
@override
|
||||
String get australiaNarrow => 'Australie (Étroit)';
|
||||
|
||||
@override
|
||||
String get australiaQld => 'Australie: QLD';
|
||||
|
||||
@override
|
||||
String get australiaSaWa => 'Australie: SA, WA';
|
||||
|
||||
@override
|
||||
String get newZealand => 'Nouvelle-Zélande';
|
||||
|
||||
@override
|
||||
String get newZealandNarrow => 'Nouvelle-Zélande (Étroit)';
|
||||
|
||||
@override
|
||||
String get switzerland => 'Suisse';
|
||||
|
||||
@override
|
||||
String get portugal433 => 'Portugal 433';
|
||||
|
||||
@override
|
||||
String get portugal868 => 'Portugal 868';
|
||||
|
||||
@override
|
||||
String get czechRepublicNarrow => 'République tchèque (Étroit)';
|
||||
|
||||
@override
|
||||
String get eu433mhzLongRange => 'UE 433MHz (Longue Portée)';
|
||||
|
||||
@override
|
||||
String get euukDeprecated => 'UE/RU (Obsolète)';
|
||||
|
||||
@override
|
||||
String get euukNarrow => 'UE/RU (Étroit)';
|
||||
|
||||
@override
|
||||
String get usacanadaRecommended => 'USA/Canada (Recommandé)';
|
||||
|
||||
@override
|
||||
String get vietnamDeprecated => 'Vietnam (Obsolète)';
|
||||
|
||||
@override
|
||||
String get vietnamNarrow => 'Vietnam (Étroit)';
|
||||
|
||||
@override
|
||||
String get active => 'Actif';
|
||||
|
||||
@override
|
||||
String get addContact => 'Ajouter un contact';
|
||||
|
||||
@override
|
||||
String get all => 'Tout';
|
||||
|
||||
@override
|
||||
String get autoResolve => 'Résolution automatique';
|
||||
|
||||
@override
|
||||
String get clearAllLabel => 'Tout effacer';
|
||||
|
||||
@override
|
||||
String get clearRelays => 'Effacer les relais';
|
||||
|
||||
@override
|
||||
String get clearFilters => 'Effacer les filtres';
|
||||
|
||||
@override
|
||||
String get clearRoute => 'Effacer la route';
|
||||
|
||||
@override
|
||||
String get clearMessages => 'Effacer les messages';
|
||||
|
||||
@override
|
||||
String get clearScale => 'Effacer l\'échelle';
|
||||
|
||||
@override
|
||||
String get clearDiscoveries => 'Effacer les découvertes';
|
||||
|
||||
@override
|
||||
String get clearOnlineTraceDatabase => 'Effacer la base de traces';
|
||||
|
||||
@override
|
||||
String get clearAllChannels => 'Effacer tous les canaux';
|
||||
|
||||
@override
|
||||
String get clearAllContacts => 'Effacer tous les contacts';
|
||||
|
||||
@override
|
||||
String get clearChannels => 'Effacer les canaux';
|
||||
|
||||
@override
|
||||
String get clearContacts => 'Effacer les contacts';
|
||||
|
||||
@override
|
||||
String get clearPathOnMaxRetry => 'Effacer le chemin au max de relances';
|
||||
|
||||
@override
|
||||
String get create => 'Créer';
|
||||
|
||||
@override
|
||||
String get custom => 'Personnalisé';
|
||||
|
||||
@override
|
||||
String get defaultValue => 'Par défaut';
|
||||
|
||||
@override
|
||||
String get duplicate => 'Dupliquer';
|
||||
|
||||
@override
|
||||
String get editName => 'Modifier le nom';
|
||||
|
||||
@override
|
||||
String get open => 'Ouvrir';
|
||||
|
||||
@override
|
||||
String get paste => 'Coller';
|
||||
|
||||
@override
|
||||
String get preview => 'Aperçu';
|
||||
|
||||
@override
|
||||
String get remove => 'Supprimer';
|
||||
|
||||
@override
|
||||
String get rename => 'Renommer';
|
||||
|
||||
@override
|
||||
String get resolveAll => 'Résoudre tout';
|
||||
|
||||
@override
|
||||
String get send => 'Envoyer';
|
||||
|
||||
@override
|
||||
String get sendAnyway => 'Envoyer quand même';
|
||||
|
||||
@override
|
||||
String get share => 'Partager';
|
||||
|
||||
@override
|
||||
String get shareContact => 'Partager le contact';
|
||||
|
||||
@override
|
||||
String get trace => 'Trace';
|
||||
|
||||
@override
|
||||
String get use => 'Utiliser';
|
||||
|
||||
@override
|
||||
String get useSelectedFrequency => 'Utiliser la fréquence sélectionnée';
|
||||
|
||||
@override
|
||||
String get discovery => 'Découverte';
|
||||
|
||||
@override
|
||||
String get discoverRepeaters => 'Découvrir les répéteurs';
|
||||
|
||||
@override
|
||||
String get discoverSensors => 'Découvrir les capteurs';
|
||||
|
||||
@override
|
||||
String get repeaterDiscoverySent => 'Découverte de répéteurs envoyée';
|
||||
|
||||
@override
|
||||
String get sensorDiscoverySent => 'Découverte de capteurs envoyée';
|
||||
|
||||
@override
|
||||
String get clearedPendingDiscoveries => 'Découvertes en attente effacées.';
|
||||
|
||||
@override
|
||||
String get autoDiscovery => 'Découverte automatique';
|
||||
|
||||
@override
|
||||
String get enableAutomaticAdding => 'Activer l\'ajout automatique';
|
||||
|
||||
@override
|
||||
String get autoaddRepeaters => 'Ajout auto des répéteurs';
|
||||
|
||||
@override
|
||||
String get autoaddRoomServers => 'Ajout auto des serveurs de salle';
|
||||
|
||||
@override
|
||||
String get autoaddSensors => 'Ajout auto des capteurs';
|
||||
|
||||
@override
|
||||
String get autoaddUsers => 'Ajout auto des utilisateurs';
|
||||
|
||||
@override
|
||||
String get overwriteOldestWhenFull => 'Écraser les plus anciens si plein';
|
||||
|
||||
@override
|
||||
String get storage => 'Stockage';
|
||||
|
||||
@override
|
||||
String get dangerZone => 'Zone de danger';
|
||||
|
||||
@override
|
||||
String get profiles => 'Profils';
|
||||
|
||||
@override
|
||||
String get favourites => 'Favoris';
|
||||
|
||||
@override
|
||||
String get sensors => 'Capteurs';
|
||||
|
||||
@override
|
||||
String get others => 'Autres';
|
||||
|
||||
@override
|
||||
String get gpsModule => 'Module GPS';
|
||||
|
||||
@override
|
||||
String get liveTraffic => 'Trafic en direct';
|
||||
|
||||
@override
|
||||
String get repeatersMap => 'Carte des répéteurs';
|
||||
|
||||
@override
|
||||
String get spectrumScan => 'Scan spectral';
|
||||
|
||||
@override
|
||||
String get blePacketLogs => 'Journaux de paquets BLE';
|
||||
|
||||
@override
|
||||
String get onlineTraceDatabase => 'Base de données de traces';
|
||||
|
||||
@override
|
||||
String get routePathByteSize => 'Taille en octets du chemin';
|
||||
|
||||
@override
|
||||
String get messageNotifications => 'Notifications de messages';
|
||||
|
||||
@override
|
||||
String get sarAlerts => 'Alertes SAR';
|
||||
|
||||
@override
|
||||
String get discoveryNotifications => 'Notifications de découverte';
|
||||
|
||||
@override
|
||||
String get updateNotifications => 'Notifications de mise à jour';
|
||||
|
||||
@override
|
||||
String get muteWhileAppIsOpen => 'Muet quand l\'appli est ouverte';
|
||||
|
||||
@override
|
||||
String get disableContacts => 'Désactiver les contacts';
|
||||
|
||||
@override
|
||||
String get enableSensorsTab => 'Activer l\'onglet Capteurs';
|
||||
|
||||
@override
|
||||
String get enableProfiles => 'Activer les profils';
|
||||
|
||||
@override
|
||||
String get autoRouteRotation => 'Rotation automatique de route';
|
||||
|
||||
@override
|
||||
String get nearestRepeaterFallback => 'Répéteur le plus proche en repli';
|
||||
|
||||
@override
|
||||
String get deleteAllStoredMessageHistory => 'Supprimer tout l\'historique';
|
||||
|
||||
@override
|
||||
String get messageFontSize => 'Taille de police des messages';
|
||||
|
||||
@override
|
||||
String get rotateMapWithHeading => 'Tourner la carte avec le cap';
|
||||
|
||||
@override
|
||||
String get showMapDebugInfo => 'Afficher les infos de débogage';
|
||||
|
||||
@override
|
||||
String get openMapInFullscreen => 'Ouvrir la carte en plein écran';
|
||||
|
||||
@override
|
||||
String get showSarMarkersLabel => 'Afficher les marqueurs SAR';
|
||||
|
||||
@override
|
||||
String get displaySarMarkersOnTheMainMap =>
|
||||
'Afficher les marqueurs SAR sur la carte';
|
||||
|
||||
@override
|
||||
String get showAllContactTrailsLabel =>
|
||||
'Afficher toutes les traces de contacts';
|
||||
|
||||
@override
|
||||
String get hideRepeatersOnMap => 'Masquer les répéteurs sur la carte';
|
||||
|
||||
@override
|
||||
String get setMapScale => 'Définir l\'échelle de la carte';
|
||||
|
||||
@override
|
||||
String get customMapScaleSaved =>
|
||||
'Échelle de carte personnalisée enregistrée';
|
||||
|
||||
@override
|
||||
String get voiceBitrate => 'Débit vocal';
|
||||
|
||||
@override
|
||||
String get voiceCompressor => 'Compresseur vocal';
|
||||
|
||||
@override
|
||||
String get balancesQuietAndLoudSpeechLevels =>
|
||||
'Équilibre les niveaux faibles et forts';
|
||||
|
||||
@override
|
||||
String get voiceLimiter => 'Limiteur vocal';
|
||||
|
||||
@override
|
||||
String get preventsClippingPeaksBeforeEncoding =>
|
||||
'Empêche l\'écrêtage avant l\'encodage';
|
||||
|
||||
@override
|
||||
String get micAutoGain => 'Gain auto du micro';
|
||||
|
||||
@override
|
||||
String get letsTheRecorderAdjustInputLevel =>
|
||||
'Laisse l\'enregistreur ajuster le niveau d\'entrée';
|
||||
|
||||
@override
|
||||
String get echoCancellation => 'Annulation d\'écho';
|
||||
|
||||
@override
|
||||
String get noiseSuppression => 'Suppression du bruit';
|
||||
|
||||
@override
|
||||
String get trimSilenceInVoiceMessages =>
|
||||
'Couper le silence dans les messages vocaux';
|
||||
|
||||
@override
|
||||
String get compressor => 'Compresseur';
|
||||
|
||||
@override
|
||||
String get limiter => 'Limiteur';
|
||||
|
||||
@override
|
||||
String get autoGain => 'Gain automatique';
|
||||
|
||||
@override
|
||||
String get echoCancel => 'Écho';
|
||||
|
||||
@override
|
||||
String get noiseSuppress => 'Bruit';
|
||||
|
||||
@override
|
||||
String get silenceTrim => 'Silence';
|
||||
|
||||
@override
|
||||
String get maxImageSize => 'Taille d\'image maximale';
|
||||
|
||||
@override
|
||||
String get imageCompression => 'Compression d\'image';
|
||||
|
||||
@override
|
||||
String get grayscale => 'Niveaux de gris';
|
||||
|
||||
@override
|
||||
String get ultraMode => 'Mode ultra';
|
||||
|
||||
@override
|
||||
String get fastPrivateGpsUpdates => 'Mises à jour GPS privées rapides';
|
||||
|
||||
@override
|
||||
String get movementThreshold => 'Seuil de mouvement';
|
||||
|
||||
@override
|
||||
String get fastGpsMovementThreshold => 'Seuil de mouvement GPS rapide';
|
||||
|
||||
@override
|
||||
String get fastGpsActiveuseInterval =>
|
||||
'Intervalle d\'utilisation active GPS rapide';
|
||||
|
||||
@override
|
||||
String get activeuseUpdateInterval =>
|
||||
'Intervalle de mise à jour en utilisation active';
|
||||
|
||||
@override
|
||||
String get repeatNearbyTraffic => 'Répéter le trafic proche';
|
||||
|
||||
@override
|
||||
String get relayThroughRepeatersAcrossTheMesh =>
|
||||
'Relayer via les répéteurs du réseau';
|
||||
|
||||
@override
|
||||
String get nearbyOnlyWithoutRepeaterFlooding =>
|
||||
'Proximité seulement, sans inondation';
|
||||
|
||||
@override
|
||||
String get multihop => 'Multi-saut';
|
||||
|
||||
@override
|
||||
String get createProfile => 'Créer un profil';
|
||||
|
||||
@override
|
||||
String get renameProfile => 'Renommer le profil';
|
||||
|
||||
@override
|
||||
String get newProfile => 'Nouveau profil';
|
||||
|
||||
@override
|
||||
String get manageProfiles => 'Gérer les profils';
|
||||
|
||||
@override
|
||||
String get enableProfilesToStartManagingThem =>
|
||||
'Activez les profils pour commencer à les gérer.';
|
||||
|
||||
@override
|
||||
String get openMessage => 'Ouvrir le message';
|
||||
|
||||
@override
|
||||
String get jumpToTheRelatedSarMessage => 'Aller au message SAR associé';
|
||||
|
||||
@override
|
||||
String get removeSarMarker => 'Supprimer le marqueur SAR';
|
||||
|
||||
@override
|
||||
String get pleaseSelectADestinationToSendSarMarker =>
|
||||
'Sélectionnez une destination pour envoyer le marqueur';
|
||||
|
||||
@override
|
||||
String get sarMarkerBroadcastToPublicChannel =>
|
||||
'Marqueur SAR diffusé sur le canal public';
|
||||
|
||||
@override
|
||||
String get sarMarkerSentToRoom => 'Marqueur SAR envoyé à la salle';
|
||||
|
||||
@override
|
||||
String get loadFromGallery => 'Charger depuis la galerie';
|
||||
|
||||
@override
|
||||
String get replaceImage => 'Remplacer l\'image';
|
||||
|
||||
@override
|
||||
String get selectFromGallery => 'Sélectionner depuis la galerie';
|
||||
|
||||
@override
|
||||
String get team => 'Équipe';
|
||||
|
||||
@override
|
||||
String get found => 'Trouvé';
|
||||
|
||||
@override
|
||||
String get staging => 'Zone de regroupement';
|
||||
|
||||
@override
|
||||
String get object => 'Objet';
|
||||
|
||||
@override
|
||||
String get quiet => 'Calme';
|
||||
|
||||
@override
|
||||
String get moderate => 'Modéré';
|
||||
|
||||
@override
|
||||
String get busy => 'Occupé';
|
||||
|
||||
@override
|
||||
String get spectrumScanReturnedNoCandidateFrequencies =>
|
||||
'Le scan spectral n\'a trouvé aucune fréquence candidate';
|
||||
|
||||
@override
|
||||
String get searchMessages => 'Rechercher des messages';
|
||||
|
||||
@override
|
||||
String get sendImageFromGallery => 'Envoyer une image de la galerie';
|
||||
|
||||
@override
|
||||
String get takePhoto => 'Prendre une photo';
|
||||
|
||||
@override
|
||||
String get dmOnly => 'Message direct uniquement';
|
||||
|
||||
@override
|
||||
String get allMessages => 'Tous les messages';
|
||||
|
||||
@override
|
||||
String get sendToPublicChannel => 'Envoyer au canal public ?';
|
||||
|
||||
@override
|
||||
String get selectMarkerTypeAndDestination =>
|
||||
'Sélectionnez le type de marqueur et la destination';
|
||||
|
||||
@override
|
||||
String get noDestinationsAvailableLabel => 'Aucune destination disponible';
|
||||
|
||||
@override
|
||||
String get image => 'Image';
|
||||
|
||||
@override
|
||||
String get format => 'Format';
|
||||
|
||||
@override
|
||||
String get dimensions => 'Dimensions';
|
||||
|
||||
@override
|
||||
String get segments => 'Segments';
|
||||
|
||||
@override
|
||||
String get transfers => 'Transferts';
|
||||
|
||||
@override
|
||||
String get downloadedBy => 'Téléchargé par';
|
||||
|
||||
@override
|
||||
String get saveDiscoverySettings =>
|
||||
'Enregistrer les paramètres de découverte';
|
||||
|
||||
@override
|
||||
String get savePublicInfo => 'Enregistrer les infos publiques';
|
||||
|
||||
@override
|
||||
String get saveRadioSettings => 'Enregistrer les paramètres radio';
|
||||
|
||||
@override
|
||||
String get savePath => 'Enregistrer le chemin';
|
||||
|
||||
@override
|
||||
String get wipeDeviceData => 'Effacer les données de l\'appareil';
|
||||
|
||||
@override
|
||||
String get wipeDevice => 'Effacer l\'appareil';
|
||||
|
||||
@override
|
||||
String get destructiveDeviceActions => 'Actions destructives de l\'appareil.';
|
||||
|
||||
@override
|
||||
String get chooseAPresetOrFinetuneCustomRadioSettings =>
|
||||
'Choisissez un préréglage ou ajustez les paramètres radio.';
|
||||
|
||||
@override
|
||||
String get chooseTheNameAndLocationThisDeviceShares =>
|
||||
'Choisissez le nom et l\'emplacement partagés par cet appareil.';
|
||||
|
||||
@override
|
||||
String get availableSpaceOnThisDevice =>
|
||||
'Espace disponible sur cet appareil.';
|
||||
|
||||
@override
|
||||
String get used => 'Utilisé';
|
||||
|
||||
@override
|
||||
String get total => 'Total';
|
||||
|
||||
@override
|
||||
String get renameValue => 'Renommer la valeur';
|
||||
|
||||
@override
|
||||
String get customizeFields => 'Personnaliser les champs';
|
||||
|
||||
@override
|
||||
String get livePreview => 'Aperçu en direct';
|
||||
|
||||
@override
|
||||
String get refreshSchedule => 'Planification d\'actualisation';
|
||||
|
||||
@override
|
||||
String get noResponse => 'Pas de réponse';
|
||||
|
||||
@override
|
||||
String get refreshing => 'Actualisation';
|
||||
|
||||
@override
|
||||
String get unavailable => 'Indisponible';
|
||||
|
||||
@override
|
||||
String get pickARelayOrNodeToWatchInSensors =>
|
||||
'Choisissez un relais ou nœud à surveiller.';
|
||||
|
||||
@override
|
||||
String get publicKeyLabel => 'Clé publique';
|
||||
|
||||
@override
|
||||
String get alreadyInContacts => 'Déjà dans les contacts';
|
||||
|
||||
@override
|
||||
String get connectToADeviceBeforeAddingContacts =>
|
||||
'Connectez-vous à un appareil avant d\'ajouter des contacts';
|
||||
|
||||
@override
|
||||
String get fromContacts => 'Des contacts';
|
||||
|
||||
@override
|
||||
String get onlineOnly => 'En ligne uniquement';
|
||||
|
||||
@override
|
||||
String get inBoth => 'Dans les deux';
|
||||
|
||||
@override
|
||||
String get source => 'Source';
|
||||
|
||||
@override
|
||||
String get manualRouteEdit => 'Édition manuelle de route';
|
||||
|
||||
@override
|
||||
String get observedMeshRoute => 'Route mesh observée';
|
||||
|
||||
@override
|
||||
String get allMessagesCleared => 'Tous les messages effacés';
|
||||
|
||||
@override
|
||||
String get onlineTraceDatabaseCleared => 'Base de données de traces effacée';
|
||||
|
||||
@override
|
||||
String get packetLogsCleared => 'Journaux de paquets effacés';
|
||||
|
||||
@override
|
||||
String get hexDataCopiedToClipboard =>
|
||||
'Données hex copiées dans le presse-papiers';
|
||||
|
||||
@override
|
||||
String get developerModeEnabled => 'Mode développeur activé';
|
||||
|
||||
@override
|
||||
String get developerModeDisabled => 'Mode développeur désactivé';
|
||||
|
||||
@override
|
||||
String get clipboardIsEmpty => 'Le presse-papiers est vide';
|
||||
|
||||
@override
|
||||
String get contactImported => 'Contact importé';
|
||||
|
||||
@override
|
||||
String get contactLinkCopiedToClipboard =>
|
||||
'Lien du contact copié dans le presse-papiers';
|
||||
|
||||
@override
|
||||
String get failedToExportContact => 'Échec de l\'exportation du contact';
|
||||
|
||||
@override
|
||||
String get noLogsToExport => 'Aucun journal à exporter';
|
||||
|
||||
@override
|
||||
String get exportAsCsv => 'Exporter en CSV';
|
||||
|
||||
@override
|
||||
String get exportAsText => 'Exporter en texte';
|
||||
|
||||
@override
|
||||
String get receivedRfc3339 => 'Reçu (RFC3339)';
|
||||
|
||||
@override
|
||||
String get buildTime => 'Heure de build';
|
||||
|
||||
@override
|
||||
String get downloadUrlNotAvailable => 'URL de téléchargement non disponible';
|
||||
|
||||
@override
|
||||
String get cannotOpenDownloadUrl =>
|
||||
'Impossible d\'ouvrir l\'URL de téléchargement';
|
||||
|
||||
@override
|
||||
String get updateCheckIsOnlyAvailableOnAndroid =>
|
||||
'La vérification des mises à jour n\'est disponible que sur Android';
|
||||
|
||||
@override
|
||||
String get youAreRunningTheLatestVersion =>
|
||||
'Vous utilisez la dernière version';
|
||||
|
||||
@override
|
||||
String get updateAvailableButDownloadUrlNotFound =>
|
||||
'Mise à jour disponible mais URL de téléchargement introuvable';
|
||||
|
||||
@override
|
||||
String get startTictactoe => 'Démarrer Tic-Tac-Toe';
|
||||
|
||||
@override
|
||||
String get tictactoeUnavailable => 'Tic-Tac-Toe indisponible';
|
||||
|
||||
@override
|
||||
String get tictactoeOpponentUnknown => 'Tic-Tac-Toe: adversaire inconnu';
|
||||
|
||||
@override
|
||||
String get tictactoeWaitingForStart => 'Tic-Tac-Toe: en attente du début';
|
||||
|
||||
@override
|
||||
String get acceptsShareLinks => 'Accepte les liens partagés';
|
||||
|
||||
@override
|
||||
String get supportsRawHex => 'Supporte le hex brut';
|
||||
|
||||
@override
|
||||
String get clipboardfriendly => 'Compatible avec le presse-papiers';
|
||||
|
||||
@override
|
||||
String get captured => 'Capturé';
|
||||
|
||||
@override
|
||||
String get size => 'Taille';
|
||||
|
||||
@override
|
||||
String get noCustomChannelsToClear => 'Aucun canal personnalisé à effacer.';
|
||||
|
||||
@override
|
||||
String get noDeviceContactsToClear =>
|
||||
'Aucun contact de l\'appareil à effacer.';
|
||||
}
|
||||
|
||||
@@ -1718,4 +1718,961 @@ class AppLocalizationsHr extends AppLocalizations {
|
||||
|
||||
@override
|
||||
String get requestingVoice => 'Zahtjev za glasom';
|
||||
|
||||
@override
|
||||
String get device => 'uređaj';
|
||||
|
||||
@override
|
||||
String get change => 'Promijeni';
|
||||
|
||||
@override
|
||||
String get wizardOverviewDescription =>
|
||||
'Ova aplikacija objedinjuje MeshCore poruke, SAR ažuriranja s terena, karte i alate za uređaj na jednom mjestu.';
|
||||
|
||||
@override
|
||||
String get wizardOverviewFeature1 =>
|
||||
'Šaljite izravne poruke, objave u sobama i poruke kanala iz glavne kartice Poruke.';
|
||||
|
||||
@override
|
||||
String get wizardOverviewFeature2 =>
|
||||
'Dijelite SAR oznake, crteže karte, glasovne isječke i slike preko mesh mreže.';
|
||||
|
||||
@override
|
||||
String get wizardOverviewFeature3 =>
|
||||
'Povežite se putem BLE-a ili TCP-a, a zatim upravljajte pratećim radijem izravno iz aplikacije.';
|
||||
|
||||
@override
|
||||
String get wizardMessagingTitle => 'Poruke i terenska izvješća';
|
||||
|
||||
@override
|
||||
String get wizardMessagingDescription =>
|
||||
'Poruke ovdje nisu samo običan tekst. Aplikacija već podržava više operativnih sadržaja i tijekova prijenosa.';
|
||||
|
||||
@override
|
||||
String get wizardMessagingFeature1 =>
|
||||
'Šaljite izravne poruke, objave u sobama i promet kanala iz jednog uređivača.';
|
||||
|
||||
@override
|
||||
String get wizardMessagingFeature2 =>
|
||||
'Stvarajte SAR ažuriranja i višekratne SAR predloške za uobičajena terenska izvješća.';
|
||||
|
||||
@override
|
||||
String get wizardMessagingFeature3 =>
|
||||
'Prenosite glasovne sesije i slike uz prikaz napretka i procjene vremena prijenosa u sučelju.';
|
||||
|
||||
@override
|
||||
String get wizardConnectDeviceTitle => 'Poveži uređaj';
|
||||
|
||||
@override
|
||||
String get wizardConnectDeviceDescription =>
|
||||
'Povežite svoj MeshCore radio, odaberite naziv i primijenite radijski predložak prije nastavka.';
|
||||
|
||||
@override
|
||||
String get wizardSetupBadge => 'Postavljanje';
|
||||
|
||||
@override
|
||||
String get wizardOverviewBadge => 'Pregled';
|
||||
|
||||
@override
|
||||
String wizardConnectedToDevice(String deviceName) {
|
||||
return 'Povezano s uređajem $deviceName';
|
||||
}
|
||||
|
||||
@override
|
||||
String get wizardNoDeviceConnected => 'Još nema povezanog uređaja';
|
||||
|
||||
@override
|
||||
String get wizardSkipForNow => 'Preskoči za sada';
|
||||
|
||||
@override
|
||||
String get wizardDeviceNameLabel => 'Naziv uređaja';
|
||||
|
||||
@override
|
||||
String get wizardDeviceNameHelp =>
|
||||
'Taj se naziv oglašava drugim korisnicima MeshCorea.';
|
||||
|
||||
@override
|
||||
String get wizardConfigRegionLabel => 'Regija konfiguracije';
|
||||
|
||||
@override
|
||||
String get wizardConfigRegionHelp =>
|
||||
'Koristi puni službeni popis MeshCore predložaka. Zadana je opcija EU/UK (Narrow).';
|
||||
|
||||
@override
|
||||
String get wizardPresetNote1 =>
|
||||
'Provjerite odgovara li odabrani predložak lokalnim radijskim propisima.';
|
||||
|
||||
@override
|
||||
String get wizardPresetNote2 =>
|
||||
'Popis odgovara službenom izvoru predložaka alata MeshCore config.';
|
||||
|
||||
@override
|
||||
String get wizardPresetNote3 =>
|
||||
'EU/UK (Narrow) ostaje zadano odabran tijekom uvodnog postavljanja.';
|
||||
|
||||
@override
|
||||
String get wizardSaving => 'Spremanje...';
|
||||
|
||||
@override
|
||||
String get wizardSaveAndContinue => 'Spremi i nastavi';
|
||||
|
||||
@override
|
||||
String get wizardEnterDeviceName => 'Unesite naziv uređaja prije nastavka.';
|
||||
|
||||
@override
|
||||
String wizardDeviceSetupSaved(String deviceName, String presetName) {
|
||||
return 'Spremljeno $deviceName s postavkom $presetName.';
|
||||
}
|
||||
|
||||
@override
|
||||
String get wizardNetworkTitle => 'Kontakti, sobe i repetitori';
|
||||
|
||||
@override
|
||||
String get wizardNetworkDescription =>
|
||||
'Kartica Kontakti organizira mrežu koju otkrijete i rute koje s vremenom naučite.';
|
||||
|
||||
@override
|
||||
String get wizardNetworkFeature1 =>
|
||||
'Pregledajte članove tima, repetitore, sobe, kanale i oglase na čekanju na jednom popisu.';
|
||||
|
||||
@override
|
||||
String get wizardNetworkFeature2 =>
|
||||
'Koristite pametni ping, prijavu u sobe, naučene putanje i alate za resetiranje ruta kada povezanost postane neuredna.';
|
||||
|
||||
@override
|
||||
String get wizardNetworkFeature3 =>
|
||||
'Stvarajte kanale i upravljajte mrežnim odredištima bez napuštanja aplikacije.';
|
||||
|
||||
@override
|
||||
String get wizardMapOpsTitle => 'Karta, tragovi i dijeljena geometrija';
|
||||
|
||||
@override
|
||||
String get wizardMapOpsDescription =>
|
||||
'Karta u aplikaciji izravno je povezana s porukama, praćenjem i SAR slojevima umjesto da bude zaseban preglednik.';
|
||||
|
||||
@override
|
||||
String get wizardMapOpsFeature1 =>
|
||||
'Pratite vlastitu poziciju, lokacije suigrača i tragove kretanja na karti.';
|
||||
|
||||
@override
|
||||
String get wizardMapOpsFeature2 =>
|
||||
'Otvorite crteže iz poruka, pregledajte ih unutar sučelja i po potrebi ih uklonite s karte.';
|
||||
|
||||
@override
|
||||
String get wizardMapOpsFeature3 =>
|
||||
'Koristite karte repetitora i dijeljene slojeve kako biste razumjeli doseg mreže na terenu.';
|
||||
|
||||
@override
|
||||
String get wizardToolsTitle => 'Alati izvan poruka';
|
||||
|
||||
@override
|
||||
String get wizardToolsDescription =>
|
||||
'Ovdje postoji više od četiri glavne kartice. Aplikacija uključuje i konfiguraciju, dijagnostiku te opcionalne radne tokove senzora.';
|
||||
|
||||
@override
|
||||
String get wizardToolsFeature1 =>
|
||||
'Otvorite konfiguraciju uređaja kako biste promijenili radijske postavke, telemetriju, TX snagu i detalje pratećeg uređaja.';
|
||||
|
||||
@override
|
||||
String get wizardToolsFeature2 =>
|
||||
'Omogućite karticu Senzori kada želite nadzorne ploče praćenih senzora i brza osvježavanja.';
|
||||
|
||||
@override
|
||||
String get wizardToolsFeature3 =>
|
||||
'Koristite zapisnike paketa, skeniranje spektra i razvojnu dijagnostiku pri rješavanju problema s mesh mrežom.';
|
||||
|
||||
@override
|
||||
String get contactInSensors => 'U Senzorima';
|
||||
|
||||
@override
|
||||
String get contactAddToSensors => 'Dodaj u Senzore';
|
||||
|
||||
@override
|
||||
String get contactSetPath => 'Postavi putanju';
|
||||
|
||||
@override
|
||||
String contactAddedToSensors(String contactName) {
|
||||
return '$contactName dodan u Senzore';
|
||||
}
|
||||
|
||||
@override
|
||||
String contactFailedToClearRoute(String error) {
|
||||
return 'Brisanje rute nije uspjelo: $error';
|
||||
}
|
||||
|
||||
@override
|
||||
String get contactRouteCleared => 'Ruta obrisana';
|
||||
|
||||
@override
|
||||
String contactRouteSet(String route) {
|
||||
return 'Ruta postavljena: $route';
|
||||
}
|
||||
|
||||
@override
|
||||
String contactFailedToSetRoute(String error) {
|
||||
return 'Postavljanje rute nije uspjelo: $error';
|
||||
}
|
||||
|
||||
@override
|
||||
String get rssi => 'RSSI';
|
||||
|
||||
@override
|
||||
String get snr => 'SNR';
|
||||
|
||||
@override
|
||||
String get ackTimeout => 'ACK istek vremena';
|
||||
|
||||
@override
|
||||
String get opcode => 'Opkod';
|
||||
|
||||
@override
|
||||
String get payload => 'Sadržaj';
|
||||
|
||||
@override
|
||||
String get hops => 'Skokovi';
|
||||
|
||||
@override
|
||||
String get hashSize => 'Veličina hash-a';
|
||||
|
||||
@override
|
||||
String get pathBytes => 'Bajtovi putanje';
|
||||
|
||||
@override
|
||||
String get selectedPath => 'Odabrana putanja';
|
||||
|
||||
@override
|
||||
String get estimatedTx => 'Procijenjeno slanje';
|
||||
|
||||
@override
|
||||
String get senderToReceipt => 'Od pošiljatelja do primitka';
|
||||
|
||||
@override
|
||||
String get receivedCopies => 'Primljene kopije';
|
||||
|
||||
@override
|
||||
String get retryCause => 'Razlog ponovnog pokušaja';
|
||||
|
||||
@override
|
||||
String get retryMode => 'Način ponovnog pokušaja';
|
||||
|
||||
@override
|
||||
String get retryResult => 'Rezultat ponovnog pokušaja';
|
||||
|
||||
@override
|
||||
String get lastRetry => 'Zadnji pokušaj';
|
||||
|
||||
@override
|
||||
String get rxPackets => 'RX paketi';
|
||||
|
||||
@override
|
||||
String get mesh => 'Mesh';
|
||||
|
||||
@override
|
||||
String get rate => 'Brzina';
|
||||
|
||||
@override
|
||||
String get window => 'Prozor';
|
||||
|
||||
@override
|
||||
String get posttxDelay => 'Kašnjenje nakon slanja';
|
||||
|
||||
@override
|
||||
String get bandpass => 'Pojasni filtar';
|
||||
|
||||
@override
|
||||
String get bandpassFilterVoice => 'Pojasni filtar glasa';
|
||||
|
||||
@override
|
||||
String get frequency => 'Frekvencija';
|
||||
|
||||
@override
|
||||
String get australia => 'Australija';
|
||||
|
||||
@override
|
||||
String get australiaNarrow => 'Australija (usko)';
|
||||
|
||||
@override
|
||||
String get australiaQld => 'Australija: QLD';
|
||||
|
||||
@override
|
||||
String get australiaSaWa => 'Australija: SA, WA';
|
||||
|
||||
@override
|
||||
String get newZealand => 'Novi Zeland';
|
||||
|
||||
@override
|
||||
String get newZealandNarrow => 'Novi Zeland (usko)';
|
||||
|
||||
@override
|
||||
String get switzerland => 'Švicarska';
|
||||
|
||||
@override
|
||||
String get portugal433 => 'Portugal 433';
|
||||
|
||||
@override
|
||||
String get portugal868 => 'Portugal 868';
|
||||
|
||||
@override
|
||||
String get czechRepublicNarrow => 'Češka (usko)';
|
||||
|
||||
@override
|
||||
String get eu433mhzLongRange => 'EU 433MHz (veliki domet)';
|
||||
|
||||
@override
|
||||
String get euukDeprecated => 'EU/UK (zastarjelo)';
|
||||
|
||||
@override
|
||||
String get euukNarrow => 'EU/UK (usko)';
|
||||
|
||||
@override
|
||||
String get usacanadaRecommended => 'SAD/Kanada (preporučeno)';
|
||||
|
||||
@override
|
||||
String get vietnamDeprecated => 'Vijetnam (zastarjelo)';
|
||||
|
||||
@override
|
||||
String get vietnamNarrow => 'Vijetnam (usko)';
|
||||
|
||||
@override
|
||||
String get active => 'Aktivno';
|
||||
|
||||
@override
|
||||
String get addContact => 'Dodaj kontakt';
|
||||
|
||||
@override
|
||||
String get all => 'Sve';
|
||||
|
||||
@override
|
||||
String get autoResolve => 'Automatski razriješi';
|
||||
|
||||
@override
|
||||
String get clearAllLabel => 'Očisti sve';
|
||||
|
||||
@override
|
||||
String get clearRelays => 'Očisti posrednike';
|
||||
|
||||
@override
|
||||
String get clearFilters => 'Očisti filtre';
|
||||
|
||||
@override
|
||||
String get clearRoute => 'Očisti putanju';
|
||||
|
||||
@override
|
||||
String get clearMessages => 'Očisti poruke';
|
||||
|
||||
@override
|
||||
String get clearScale => 'Očisti mjerilo';
|
||||
|
||||
@override
|
||||
String get clearDiscoveries => 'Očisti otkrića';
|
||||
|
||||
@override
|
||||
String get clearOnlineTraceDatabase => 'Očisti bazu mrežnih tragova';
|
||||
|
||||
@override
|
||||
String get clearAllChannels => 'Očisti sve kanale';
|
||||
|
||||
@override
|
||||
String get clearAllContacts => 'Očisti sve kontakte';
|
||||
|
||||
@override
|
||||
String get clearChannels => 'Očisti kanale';
|
||||
|
||||
@override
|
||||
String get clearContacts => 'Očisti kontakte';
|
||||
|
||||
@override
|
||||
String get clearPathOnMaxRetry => 'Očisti putanju kod maks. pokušaja';
|
||||
|
||||
@override
|
||||
String get create => 'Stvori';
|
||||
|
||||
@override
|
||||
String get custom => 'Prilagođeno';
|
||||
|
||||
@override
|
||||
String get defaultValue => 'Zadano';
|
||||
|
||||
@override
|
||||
String get duplicate => 'Dupliciraj';
|
||||
|
||||
@override
|
||||
String get editName => 'Uredi ime';
|
||||
|
||||
@override
|
||||
String get open => 'Otvori';
|
||||
|
||||
@override
|
||||
String get paste => 'Zalijepi';
|
||||
|
||||
@override
|
||||
String get preview => 'Pregled';
|
||||
|
||||
@override
|
||||
String get remove => 'Ukloni';
|
||||
|
||||
@override
|
||||
String get rename => 'Preimenuj';
|
||||
|
||||
@override
|
||||
String get resolveAll => 'Razriješi sve';
|
||||
|
||||
@override
|
||||
String get send => 'Pošalji';
|
||||
|
||||
@override
|
||||
String get sendAnyway => 'Svejedno pošalji';
|
||||
|
||||
@override
|
||||
String get share => 'Dijeli';
|
||||
|
||||
@override
|
||||
String get shareContact => 'Dijeli kontakt';
|
||||
|
||||
@override
|
||||
String get trace => 'Praćenje';
|
||||
|
||||
@override
|
||||
String get use => 'Koristi';
|
||||
|
||||
@override
|
||||
String get useSelectedFrequency => 'Koristi odabranu frekvenciju';
|
||||
|
||||
@override
|
||||
String get discovery => 'Otkrivanje';
|
||||
|
||||
@override
|
||||
String get discoverRepeaters => 'Otkrij posrednike';
|
||||
|
||||
@override
|
||||
String get discoverSensors => 'Otkrij senzore';
|
||||
|
||||
@override
|
||||
String get repeaterDiscoverySent => 'Otkrivanje posrednika poslano';
|
||||
|
||||
@override
|
||||
String get sensorDiscoverySent => 'Otkrivanje senzora poslano';
|
||||
|
||||
@override
|
||||
String get clearedPendingDiscoveries => 'Očišćena čekajuća otkrića.';
|
||||
|
||||
@override
|
||||
String get autoDiscovery => 'Automatsko otkrivanje';
|
||||
|
||||
@override
|
||||
String get enableAutomaticAdding => 'Omogući automatsko dodavanje';
|
||||
|
||||
@override
|
||||
String get autoaddRepeaters => 'Automatski dodaj posrednike';
|
||||
|
||||
@override
|
||||
String get autoaddRoomServers => 'Automatski dodaj poslužitelje soba';
|
||||
|
||||
@override
|
||||
String get autoaddSensors => 'Automatski dodaj senzore';
|
||||
|
||||
@override
|
||||
String get autoaddUsers => 'Automatski dodaj korisnike';
|
||||
|
||||
@override
|
||||
String get overwriteOldestWhenFull => 'Prepiši najstarije kad je puno';
|
||||
|
||||
@override
|
||||
String get storage => 'Pohrana';
|
||||
|
||||
@override
|
||||
String get dangerZone => 'Opasna zona';
|
||||
|
||||
@override
|
||||
String get profiles => 'Profili';
|
||||
|
||||
@override
|
||||
String get favourites => 'Favoriti';
|
||||
|
||||
@override
|
||||
String get sensors => 'Senzori';
|
||||
|
||||
@override
|
||||
String get others => 'Ostali';
|
||||
|
||||
@override
|
||||
String get gpsModule => 'GPS modul';
|
||||
|
||||
@override
|
||||
String get liveTraffic => 'Promet uživo';
|
||||
|
||||
@override
|
||||
String get repeatersMap => 'Karta posrednika';
|
||||
|
||||
@override
|
||||
String get spectrumScan => 'Spektralno skeniranje';
|
||||
|
||||
@override
|
||||
String get blePacketLogs => 'BLE zapisi paketa';
|
||||
|
||||
@override
|
||||
String get onlineTraceDatabase => 'Baza mrežnih tragova';
|
||||
|
||||
@override
|
||||
String get routePathByteSize => 'Veličina putanje u bajtima';
|
||||
|
||||
@override
|
||||
String get messageNotifications => 'Obavijesti o porukama';
|
||||
|
||||
@override
|
||||
String get sarAlerts => 'SAR upozorenja';
|
||||
|
||||
@override
|
||||
String get discoveryNotifications => 'Obavijesti o otkrivanju';
|
||||
|
||||
@override
|
||||
String get updateNotifications => 'Obavijesti o ažuriranju';
|
||||
|
||||
@override
|
||||
String get muteWhileAppIsOpen => 'Utišaj dok je aplikacija otvorena';
|
||||
|
||||
@override
|
||||
String get disableContacts => 'Onemogući kontakte';
|
||||
|
||||
@override
|
||||
String get enableSensorsTab => 'Omogući karticu Senzori';
|
||||
|
||||
@override
|
||||
String get enableProfiles => 'Omogući profile';
|
||||
|
||||
@override
|
||||
String get autoRouteRotation => 'Automatsko rotiranje putanje';
|
||||
|
||||
@override
|
||||
String get nearestRepeaterFallback => 'Najbliži posrednik kao rezerva';
|
||||
|
||||
@override
|
||||
String get deleteAllStoredMessageHistory =>
|
||||
'Izbriši svu pohranjenu povijest poruka';
|
||||
|
||||
@override
|
||||
String get messageFontSize => 'Veličina fonta poruka';
|
||||
|
||||
@override
|
||||
String get rotateMapWithHeading => 'Rotiraj kartu prema smjeru';
|
||||
|
||||
@override
|
||||
String get showMapDebugInfo => 'Prikaži debug info karte';
|
||||
|
||||
@override
|
||||
String get openMapInFullscreen => 'Otvori kartu na cijelom zaslonu';
|
||||
|
||||
@override
|
||||
String get showSarMarkersLabel => 'Prikaži SAR oznake';
|
||||
|
||||
@override
|
||||
String get displaySarMarkersOnTheMainMap =>
|
||||
'Prikaži SAR oznake na glavnoj karti';
|
||||
|
||||
@override
|
||||
String get showAllContactTrailsLabel => 'Prikaži sve tragove kontakata';
|
||||
|
||||
@override
|
||||
String get hideRepeatersOnMap => 'Sakrij posrednike na karti';
|
||||
|
||||
@override
|
||||
String get setMapScale => 'Postavi mjerilo karte';
|
||||
|
||||
@override
|
||||
String get customMapScaleSaved => 'Prilagođeno mjerilo karte spremljeno';
|
||||
|
||||
@override
|
||||
String get voiceBitrate => 'Bitna brzina glasa';
|
||||
|
||||
@override
|
||||
String get voiceCompressor => 'Kompresor glasa';
|
||||
|
||||
@override
|
||||
String get balancesQuietAndLoudSpeechLevels =>
|
||||
'Izravnava tihe i glasne razine govora';
|
||||
|
||||
@override
|
||||
String get voiceLimiter => 'Ograničivač glasa';
|
||||
|
||||
@override
|
||||
String get preventsClippingPeaksBeforeEncoding =>
|
||||
'Sprječava rezanje vrhova prije kodiranja';
|
||||
|
||||
@override
|
||||
String get micAutoGain => 'Automatsko pojačanje mikrofona';
|
||||
|
||||
@override
|
||||
String get letsTheRecorderAdjustInputLevel =>
|
||||
'Dopušta snimaču prilagodbu razine ulaza';
|
||||
|
||||
@override
|
||||
String get echoCancellation => 'Poništavanje odjeka';
|
||||
|
||||
@override
|
||||
String get noiseSuppression => 'Potiskivanje šuma';
|
||||
|
||||
@override
|
||||
String get trimSilenceInVoiceMessages => 'Izreži tišinu u glasovnim porukama';
|
||||
|
||||
@override
|
||||
String get compressor => 'Kompresor';
|
||||
|
||||
@override
|
||||
String get limiter => 'Ograničivač';
|
||||
|
||||
@override
|
||||
String get autoGain => 'Automatsko pojačanje';
|
||||
|
||||
@override
|
||||
String get echoCancel => 'Odjek';
|
||||
|
||||
@override
|
||||
String get noiseSuppress => 'Šum';
|
||||
|
||||
@override
|
||||
String get silenceTrim => 'Tišina';
|
||||
|
||||
@override
|
||||
String get maxImageSize => 'Maksimalna veličina slike';
|
||||
|
||||
@override
|
||||
String get imageCompression => 'Kompresija slike';
|
||||
|
||||
@override
|
||||
String get grayscale => 'Sivine';
|
||||
|
||||
@override
|
||||
String get ultraMode => 'Ultra način';
|
||||
|
||||
@override
|
||||
String get fastPrivateGpsUpdates => 'Brza privatna GPS ažuriranja';
|
||||
|
||||
@override
|
||||
String get movementThreshold => 'Prag kretanja';
|
||||
|
||||
@override
|
||||
String get fastGpsMovementThreshold => 'Prag kretanja za brzi GPS';
|
||||
|
||||
@override
|
||||
String get fastGpsActiveuseInterval =>
|
||||
'Interval aktivnog korištenja brzog GPS-a';
|
||||
|
||||
@override
|
||||
String get activeuseUpdateInterval =>
|
||||
'Interval ažuriranja pri aktivnom korištenju';
|
||||
|
||||
@override
|
||||
String get repeatNearbyTraffic => 'Ponovi obližnji promet';
|
||||
|
||||
@override
|
||||
String get relayThroughRepeatersAcrossTheMesh =>
|
||||
'Proslijedi kroz posrednike kroz mrežu';
|
||||
|
||||
@override
|
||||
String get nearbyOnlyWithoutRepeaterFlooding =>
|
||||
'Samo obližnji, bez preplavljivanja posrednika';
|
||||
|
||||
@override
|
||||
String get multihop => 'Višeskočno';
|
||||
|
||||
@override
|
||||
String get createProfile => 'Stvori profil';
|
||||
|
||||
@override
|
||||
String get renameProfile => 'Preimenuj profil';
|
||||
|
||||
@override
|
||||
String get newProfile => 'Novi profil';
|
||||
|
||||
@override
|
||||
String get manageProfiles => 'Upravljaj profilima';
|
||||
|
||||
@override
|
||||
String get enableProfilesToStartManagingThem =>
|
||||
'Omogućite profile da biste ih počeli upravljati.';
|
||||
|
||||
@override
|
||||
String get openMessage => 'Otvori poruku';
|
||||
|
||||
@override
|
||||
String get jumpToTheRelatedSarMessage => 'Skoči na povezanu SAR poruku';
|
||||
|
||||
@override
|
||||
String get removeSarMarker => 'Ukloni SAR oznaku';
|
||||
|
||||
@override
|
||||
String get pleaseSelectADestinationToSendSarMarker =>
|
||||
'Odaberite odredište za slanje SAR oznake';
|
||||
|
||||
@override
|
||||
String get sarMarkerBroadcastToPublicChannel =>
|
||||
'SAR oznaka emitirana na javni kanal';
|
||||
|
||||
@override
|
||||
String get sarMarkerSentToRoom => 'SAR oznaka poslana u sobu';
|
||||
|
||||
@override
|
||||
String get loadFromGallery => 'Učitaj iz galerije';
|
||||
|
||||
@override
|
||||
String get replaceImage => 'Zamijeni sliku';
|
||||
|
||||
@override
|
||||
String get selectFromGallery => 'Odaberi iz galerije';
|
||||
|
||||
@override
|
||||
String get team => 'Tim';
|
||||
|
||||
@override
|
||||
String get found => 'Pronađeno';
|
||||
|
||||
@override
|
||||
String get staging => 'Sabirno mjesto';
|
||||
|
||||
@override
|
||||
String get object => 'Objekt';
|
||||
|
||||
@override
|
||||
String get quiet => 'Tiho';
|
||||
|
||||
@override
|
||||
String get moderate => 'Umjereno';
|
||||
|
||||
@override
|
||||
String get busy => 'Zauzeto';
|
||||
|
||||
@override
|
||||
String get spectrumScanReturnedNoCandidateFrequencies =>
|
||||
'Spektralno skeniranje nije pronašlo prikladne frekvencije';
|
||||
|
||||
@override
|
||||
String get searchMessages => 'Pretraži poruke';
|
||||
|
||||
@override
|
||||
String get sendImageFromGallery => 'Pošalji sliku iz galerije';
|
||||
|
||||
@override
|
||||
String get takePhoto => 'Snimi fotografiju';
|
||||
|
||||
@override
|
||||
String get dmOnly => 'Samo izravno';
|
||||
|
||||
@override
|
||||
String get allMessages => 'Sve poruke';
|
||||
|
||||
@override
|
||||
String get sendToPublicChannel => 'Pošalji na javni kanal?';
|
||||
|
||||
@override
|
||||
String get selectMarkerTypeAndDestination =>
|
||||
'Odaberite vrstu oznake i odredište';
|
||||
|
||||
@override
|
||||
String get noDestinationsAvailableLabel => 'Nema dostupnih odredišta';
|
||||
|
||||
@override
|
||||
String get image => 'Slika';
|
||||
|
||||
@override
|
||||
String get format => 'Format';
|
||||
|
||||
@override
|
||||
String get dimensions => 'Dimenzije';
|
||||
|
||||
@override
|
||||
String get segments => 'Segmenti';
|
||||
|
||||
@override
|
||||
String get transfers => 'Prijenosi';
|
||||
|
||||
@override
|
||||
String get downloadedBy => 'Preuzeo';
|
||||
|
||||
@override
|
||||
String get saveDiscoverySettings => 'Spremi postavke otkrivanja';
|
||||
|
||||
@override
|
||||
String get savePublicInfo => 'Spremi javne podatke';
|
||||
|
||||
@override
|
||||
String get saveRadioSettings => 'Spremi radijske postavke';
|
||||
|
||||
@override
|
||||
String get savePath => 'Spremi putanju';
|
||||
|
||||
@override
|
||||
String get wipeDeviceData => 'Obriši podatke uređaja';
|
||||
|
||||
@override
|
||||
String get wipeDevice => 'Obriši uređaj';
|
||||
|
||||
@override
|
||||
String get destructiveDeviceActions => 'Destruktivne radnje na uređaju.';
|
||||
|
||||
@override
|
||||
String get chooseAPresetOrFinetuneCustomRadioSettings =>
|
||||
'Odaberite predefiniranu postavku ili fino podesite radijske postavke.';
|
||||
|
||||
@override
|
||||
String get chooseTheNameAndLocationThisDeviceShares =>
|
||||
'Odaberite ime i lokaciju koju ovaj uređaj dijeli.';
|
||||
|
||||
@override
|
||||
String get availableSpaceOnThisDevice =>
|
||||
'Raspoloživi prostor na ovom uređaju.';
|
||||
|
||||
@override
|
||||
String get used => 'Korišteno';
|
||||
|
||||
@override
|
||||
String get total => 'Ukupno';
|
||||
|
||||
@override
|
||||
String get renameValue => 'Preimenuj vrijednost';
|
||||
|
||||
@override
|
||||
String get customizeFields => 'Prilagodi polja';
|
||||
|
||||
@override
|
||||
String get livePreview => 'Pregled uživo';
|
||||
|
||||
@override
|
||||
String get refreshSchedule => 'Raspored osvježavanja';
|
||||
|
||||
@override
|
||||
String get noResponse => 'Nema odgovora';
|
||||
|
||||
@override
|
||||
String get refreshing => 'Osvježavam';
|
||||
|
||||
@override
|
||||
String get unavailable => 'Nedostupno';
|
||||
|
||||
@override
|
||||
String get pickARelayOrNodeToWatchInSensors =>
|
||||
'Odaberite posrednika ili čvor za praćenje.';
|
||||
|
||||
@override
|
||||
String get publicKeyLabel => 'Javni ključ';
|
||||
|
||||
@override
|
||||
String get alreadyInContacts => 'Već u kontaktima';
|
||||
|
||||
@override
|
||||
String get connectToADeviceBeforeAddingContacts =>
|
||||
'Povežite se s uređajem prije dodavanja kontakata';
|
||||
|
||||
@override
|
||||
String get fromContacts => 'Iz kontakata';
|
||||
|
||||
@override
|
||||
String get onlineOnly => 'Samo povezani';
|
||||
|
||||
@override
|
||||
String get inBoth => 'U oba';
|
||||
|
||||
@override
|
||||
String get source => 'Izvor';
|
||||
|
||||
@override
|
||||
String get manualRouteEdit => 'Ručno uređivanje putanje';
|
||||
|
||||
@override
|
||||
String get observedMeshRoute => 'Opažena mrežna putanja';
|
||||
|
||||
@override
|
||||
String get allMessagesCleared => 'Sve poruke očišćene';
|
||||
|
||||
@override
|
||||
String get onlineTraceDatabaseCleared => 'Baza mrežnih tragova očišćena';
|
||||
|
||||
@override
|
||||
String get packetLogsCleared => 'Zapisi paketa očišćeni';
|
||||
|
||||
@override
|
||||
String get hexDataCopiedToClipboard => 'Hex podaci kopirani u međuspremnik';
|
||||
|
||||
@override
|
||||
String get developerModeEnabled => 'Razvojni način omogućen';
|
||||
|
||||
@override
|
||||
String get developerModeDisabled => 'Razvojni način onemogućen';
|
||||
|
||||
@override
|
||||
String get clipboardIsEmpty => 'Međuspremnik je prazan';
|
||||
|
||||
@override
|
||||
String get contactImported => 'Kontakt uvezen';
|
||||
|
||||
@override
|
||||
String get contactLinkCopiedToClipboard =>
|
||||
'Poveznica na kontakt kopirana u međuspremnik';
|
||||
|
||||
@override
|
||||
String get failedToExportContact => 'Izvoz kontakta nije uspio';
|
||||
|
||||
@override
|
||||
String get noLogsToExport => 'Nema zapisa za izvoz';
|
||||
|
||||
@override
|
||||
String get exportAsCsv => 'Izvezi kao CSV';
|
||||
|
||||
@override
|
||||
String get exportAsText => 'Izvezi kao tekst';
|
||||
|
||||
@override
|
||||
String get receivedRfc3339 => 'Primljeno (RFC3339)';
|
||||
|
||||
@override
|
||||
String get buildTime => 'Vrijeme gradnje';
|
||||
|
||||
@override
|
||||
String get downloadUrlNotAvailable => 'URL za preuzimanje nije dostupan';
|
||||
|
||||
@override
|
||||
String get cannotOpenDownloadUrl => 'Ne mogu otvoriti URL za preuzimanje';
|
||||
|
||||
@override
|
||||
String get updateCheckIsOnlyAvailableOnAndroid =>
|
||||
'Provjera ažuriranja dostupna je samo na Androidu';
|
||||
|
||||
@override
|
||||
String get youAreRunningTheLatestVersion => 'Koristite najnoviju verziju';
|
||||
|
||||
@override
|
||||
String get updateAvailableButDownloadUrlNotFound =>
|
||||
'Ažuriranje dostupno, ali URL za preuzimanje nije pronađen';
|
||||
|
||||
@override
|
||||
String get startTictactoe => 'Pokreni Tic-Tac-Toe';
|
||||
|
||||
@override
|
||||
String get tictactoeUnavailable => 'Tic-Tac-Toe nije dostupan';
|
||||
|
||||
@override
|
||||
String get tictactoeOpponentUnknown => 'Tic-Tac-Toe: protivnik nepoznat';
|
||||
|
||||
@override
|
||||
String get tictactoeWaitingForStart => 'Tic-Tac-Toe: čekanje na početak';
|
||||
|
||||
@override
|
||||
String get acceptsShareLinks => 'Prihvaća dijeljene veze';
|
||||
|
||||
@override
|
||||
String get supportsRawHex => 'Podržava sirovi hex';
|
||||
|
||||
@override
|
||||
String get clipboardfriendly => 'Pogodno za međuspremnik';
|
||||
|
||||
@override
|
||||
String get captured => 'Zabilježeno';
|
||||
|
||||
@override
|
||||
String get size => 'Veličina';
|
||||
|
||||
@override
|
||||
String get noCustomChannelsToClear => 'Nema prilagođenih kanala za brisanje.';
|
||||
|
||||
@override
|
||||
String get noDeviceContactsToClear => 'Nema kontakata uređaja za brisanje.';
|
||||
}
|
||||
|
||||
@@ -1732,4 +1732,964 @@ class AppLocalizationsIt extends AppLocalizations {
|
||||
|
||||
@override
|
||||
String get requestingVoice => 'Richiesta voce';
|
||||
|
||||
@override
|
||||
String get device => 'dispositivo';
|
||||
|
||||
@override
|
||||
String get change => 'Modifica';
|
||||
|
||||
@override
|
||||
String get wizardOverviewDescription =>
|
||||
'Questa app riunisce messaggistica MeshCore, aggiornamenti SAR sul campo, mappe e strumenti del dispositivo in un unico posto.';
|
||||
|
||||
@override
|
||||
String get wizardOverviewFeature1 =>
|
||||
'Invia messaggi diretti, post nelle stanze e messaggi di canale dalla scheda principale Messaggi.';
|
||||
|
||||
@override
|
||||
String get wizardOverviewFeature2 =>
|
||||
'Condividi marcatori SAR, disegni sulla mappa, clip vocali e immagini sulla mesh.';
|
||||
|
||||
@override
|
||||
String get wizardOverviewFeature3 =>
|
||||
'Connettiti tramite BLE o TCP e poi gestisci la radio companion direttamente dall\'app.';
|
||||
|
||||
@override
|
||||
String get wizardMessagingTitle => 'Messaggistica e rapporti sul campo';
|
||||
|
||||
@override
|
||||
String get wizardMessagingDescription =>
|
||||
'Qui i messaggi sono più del semplice testo. L\'app supporta già diversi payload operativi e flussi di trasferimento.';
|
||||
|
||||
@override
|
||||
String get wizardMessagingFeature1 =>
|
||||
'Invia messaggi diretti, post nelle stanze e traffico di canale da un unico compositore.';
|
||||
|
||||
@override
|
||||
String get wizardMessagingFeature2 =>
|
||||
'Crea aggiornamenti SAR e modelli SAR riutilizzabili per i rapporti sul campo più comuni.';
|
||||
|
||||
@override
|
||||
String get wizardMessagingFeature3 =>
|
||||
'Trasferisci sessioni vocali e immagini, con avanzamento e stime del tempo d\'aria nell\'interfaccia.';
|
||||
|
||||
@override
|
||||
String get wizardConnectDeviceTitle => 'Connetti dispositivo';
|
||||
|
||||
@override
|
||||
String get wizardConnectDeviceDescription =>
|
||||
'Connetti la tua radio MeshCore, scegli un nome e applica un preset radio prima di continuare.';
|
||||
|
||||
@override
|
||||
String get wizardSetupBadge => 'Configurazione';
|
||||
|
||||
@override
|
||||
String get wizardOverviewBadge => 'Panoramica';
|
||||
|
||||
@override
|
||||
String wizardConnectedToDevice(String deviceName) {
|
||||
return 'Connesso a $deviceName';
|
||||
}
|
||||
|
||||
@override
|
||||
String get wizardNoDeviceConnected => 'Nessun dispositivo ancora connesso';
|
||||
|
||||
@override
|
||||
String get wizardSkipForNow => 'Salta per ora';
|
||||
|
||||
@override
|
||||
String get wizardDeviceNameLabel => 'Nome del dispositivo';
|
||||
|
||||
@override
|
||||
String get wizardDeviceNameHelp =>
|
||||
'Questo nome viene annunciato agli altri utenti MeshCore.';
|
||||
|
||||
@override
|
||||
String get wizardConfigRegionLabel => 'Regione di configurazione';
|
||||
|
||||
@override
|
||||
String get wizardConfigRegionHelp =>
|
||||
'Usa l\'elenco ufficiale completo dei preset MeshCore. Il valore predefinito è EU/UK (Narrow).';
|
||||
|
||||
@override
|
||||
String get wizardPresetNote1 =>
|
||||
'Assicurati che il preset selezionato corrisponda alle normative radio locali.';
|
||||
|
||||
@override
|
||||
String get wizardPresetNote2 =>
|
||||
'L\'elenco corrisponde al feed ufficiale dei preset dello strumento di configurazione MeshCore.';
|
||||
|
||||
@override
|
||||
String get wizardPresetNote3 =>
|
||||
'EU/UK (Narrow) resta selezionato per impostazione predefinita durante l\'onboarding.';
|
||||
|
||||
@override
|
||||
String get wizardSaving => 'Salvataggio...';
|
||||
|
||||
@override
|
||||
String get wizardSaveAndContinue => 'Salva e continua';
|
||||
|
||||
@override
|
||||
String get wizardEnterDeviceName =>
|
||||
'Inserisci un nome dispositivo prima di continuare.';
|
||||
|
||||
@override
|
||||
String wizardDeviceSetupSaved(String deviceName, String presetName) {
|
||||
return '$deviceName salvato con $presetName.';
|
||||
}
|
||||
|
||||
@override
|
||||
String get wizardNetworkTitle => 'Contatti, stanze e ripetitori';
|
||||
|
||||
@override
|
||||
String get wizardNetworkDescription =>
|
||||
'La scheda Contatti organizza la rete che scopri e i percorsi che apprendi nel tempo.';
|
||||
|
||||
@override
|
||||
String get wizardNetworkFeature1 =>
|
||||
'Controlla membri del team, ripetitori, stanze, canali e annunci in sospeso in un unico elenco.';
|
||||
|
||||
@override
|
||||
String get wizardNetworkFeature2 =>
|
||||
'Usa smart ping, accesso alle stanze, percorsi appresi e strumenti di reset delle route quando la connettività diventa complicata.';
|
||||
|
||||
@override
|
||||
String get wizardNetworkFeature3 =>
|
||||
'Crea canali e gestisci le destinazioni di rete senza uscire dall\'app.';
|
||||
|
||||
@override
|
||||
String get wizardMapOpsTitle => 'Mappa, tracce e geometria condivisa';
|
||||
|
||||
@override
|
||||
String get wizardMapOpsDescription =>
|
||||
'La mappa dell\'app è collegata direttamente a messaggistica, tracciamento e overlay SAR invece di essere un visualizzatore separato.';
|
||||
|
||||
@override
|
||||
String get wizardMapOpsFeature1 =>
|
||||
'Monitora la tua posizione, le posizioni dei compagni e le tracce di movimento sulla mappa.';
|
||||
|
||||
@override
|
||||
String get wizardMapOpsFeature2 =>
|
||||
'Apri i disegni dai messaggi, visualizzali in anteprima in linea e rimuovili dalla mappa quando serve.';
|
||||
|
||||
@override
|
||||
String get wizardMapOpsFeature3 =>
|
||||
'Usa le viste mappa dei ripetitori e gli overlay condivisi per capire la copertura della rete sul campo.';
|
||||
|
||||
@override
|
||||
String get wizardToolsTitle => 'Strumenti oltre la messaggistica';
|
||||
|
||||
@override
|
||||
String get wizardToolsDescription =>
|
||||
'Qui c\'è più delle quattro schede principali. L\'app include anche configurazione, diagnostica e flussi sensori opzionali.';
|
||||
|
||||
@override
|
||||
String get wizardToolsFeature1 =>
|
||||
'Apri la configurazione del dispositivo per cambiare impostazioni radio, telemetria, potenza TX e dettagli del companion.';
|
||||
|
||||
@override
|
||||
String get wizardToolsFeature2 =>
|
||||
'Abilita la scheda Sensori quando vuoi dashboard monitorate e azioni di aggiornamento rapido.';
|
||||
|
||||
@override
|
||||
String get wizardToolsFeature3 =>
|
||||
'Usa log dei pacchetti, scansione dello spettro e diagnostica sviluppatore per risolvere problemi della mesh.';
|
||||
|
||||
@override
|
||||
String get contactInSensors => 'Nei Sensori';
|
||||
|
||||
@override
|
||||
String get contactAddToSensors => 'Aggiungi ai Sensori';
|
||||
|
||||
@override
|
||||
String get contactSetPath => 'Imposta percorso';
|
||||
|
||||
@override
|
||||
String contactAddedToSensors(String contactName) {
|
||||
return '$contactName aggiunto a Sensori';
|
||||
}
|
||||
|
||||
@override
|
||||
String contactFailedToClearRoute(String error) {
|
||||
return 'Impossibile cancellare il percorso: $error';
|
||||
}
|
||||
|
||||
@override
|
||||
String get contactRouteCleared => 'Percorso cancellato';
|
||||
|
||||
@override
|
||||
String contactRouteSet(String route) {
|
||||
return 'Percorso impostato: $route';
|
||||
}
|
||||
|
||||
@override
|
||||
String contactFailedToSetRoute(String error) {
|
||||
return 'Impossibile impostare il percorso: $error';
|
||||
}
|
||||
|
||||
@override
|
||||
String get rssi => 'RSSI';
|
||||
|
||||
@override
|
||||
String get snr => 'SNR';
|
||||
|
||||
@override
|
||||
String get ackTimeout => 'Timeout ACK';
|
||||
|
||||
@override
|
||||
String get opcode => 'Opcode';
|
||||
|
||||
@override
|
||||
String get payload => 'Payload';
|
||||
|
||||
@override
|
||||
String get hops => 'Hop';
|
||||
|
||||
@override
|
||||
String get hashSize => 'Dimensione hash';
|
||||
|
||||
@override
|
||||
String get pathBytes => 'Byte percorso';
|
||||
|
||||
@override
|
||||
String get selectedPath => 'Percorso selezionato';
|
||||
|
||||
@override
|
||||
String get estimatedTx => 'Trasmissione stimata';
|
||||
|
||||
@override
|
||||
String get senderToReceipt => 'Mittente a ricevuta';
|
||||
|
||||
@override
|
||||
String get receivedCopies => 'Copie ricevute';
|
||||
|
||||
@override
|
||||
String get retryCause => 'Causa del ritentativo';
|
||||
|
||||
@override
|
||||
String get retryMode => 'Modalità ritentativo';
|
||||
|
||||
@override
|
||||
String get retryResult => 'Risultato ritentativo';
|
||||
|
||||
@override
|
||||
String get lastRetry => 'Ultimo ritentativo';
|
||||
|
||||
@override
|
||||
String get rxPackets => 'Pacchetti RX';
|
||||
|
||||
@override
|
||||
String get mesh => 'Mesh';
|
||||
|
||||
@override
|
||||
String get rate => 'Velocità';
|
||||
|
||||
@override
|
||||
String get window => 'Finestra';
|
||||
|
||||
@override
|
||||
String get posttxDelay => 'Ritardo post-tx';
|
||||
|
||||
@override
|
||||
String get bandpass => 'Passa-banda';
|
||||
|
||||
@override
|
||||
String get bandpassFilterVoice => 'Filtro passa-banda voce';
|
||||
|
||||
@override
|
||||
String get frequency => 'Frequenza';
|
||||
|
||||
@override
|
||||
String get australia => 'Australia';
|
||||
|
||||
@override
|
||||
String get australiaNarrow => 'Australia (Stretto)';
|
||||
|
||||
@override
|
||||
String get australiaQld => 'Australia: QLD';
|
||||
|
||||
@override
|
||||
String get australiaSaWa => 'Australia: SA, WA';
|
||||
|
||||
@override
|
||||
String get newZealand => 'Nuova Zelanda';
|
||||
|
||||
@override
|
||||
String get newZealandNarrow => 'Nuova Zelanda (Stretto)';
|
||||
|
||||
@override
|
||||
String get switzerland => 'Svizzera';
|
||||
|
||||
@override
|
||||
String get portugal433 => 'Portogallo 433';
|
||||
|
||||
@override
|
||||
String get portugal868 => 'Portogallo 868';
|
||||
|
||||
@override
|
||||
String get czechRepublicNarrow => 'Repubblica Ceca (Stretto)';
|
||||
|
||||
@override
|
||||
String get eu433mhzLongRange => 'UE 433MHz (Lungo Raggio)';
|
||||
|
||||
@override
|
||||
String get euukDeprecated => 'UE/UK (Deprecato)';
|
||||
|
||||
@override
|
||||
String get euukNarrow => 'UE/UK (Stretto)';
|
||||
|
||||
@override
|
||||
String get usacanadaRecommended => 'USA/Canada (Consigliato)';
|
||||
|
||||
@override
|
||||
String get vietnamDeprecated => 'Vietnam (Deprecato)';
|
||||
|
||||
@override
|
||||
String get vietnamNarrow => 'Vietnam (Stretto)';
|
||||
|
||||
@override
|
||||
String get active => 'Attivo';
|
||||
|
||||
@override
|
||||
String get addContact => 'Aggiungi contatto';
|
||||
|
||||
@override
|
||||
String get all => 'Tutto';
|
||||
|
||||
@override
|
||||
String get autoResolve => 'Risolvi automaticamente';
|
||||
|
||||
@override
|
||||
String get clearAllLabel => 'Cancella tutto';
|
||||
|
||||
@override
|
||||
String get clearRelays => 'Cancella relay';
|
||||
|
||||
@override
|
||||
String get clearFilters => 'Cancella filtri';
|
||||
|
||||
@override
|
||||
String get clearRoute => 'Cancella percorso';
|
||||
|
||||
@override
|
||||
String get clearMessages => 'Cancella messaggi';
|
||||
|
||||
@override
|
||||
String get clearScale => 'Cancella scala';
|
||||
|
||||
@override
|
||||
String get clearDiscoveries => 'Cancella scoperte';
|
||||
|
||||
@override
|
||||
String get clearOnlineTraceDatabase => 'Cancella database tracce';
|
||||
|
||||
@override
|
||||
String get clearAllChannels => 'Cancella tutti i canali';
|
||||
|
||||
@override
|
||||
String get clearAllContacts => 'Cancella tutti i contatti';
|
||||
|
||||
@override
|
||||
String get clearChannels => 'Cancella canali';
|
||||
|
||||
@override
|
||||
String get clearContacts => 'Cancella contatti';
|
||||
|
||||
@override
|
||||
String get clearPathOnMaxRetry => 'Cancella percorso al max ritentativo';
|
||||
|
||||
@override
|
||||
String get create => 'Crea';
|
||||
|
||||
@override
|
||||
String get custom => 'Personalizzato';
|
||||
|
||||
@override
|
||||
String get defaultValue => 'Predefinito';
|
||||
|
||||
@override
|
||||
String get duplicate => 'Duplica';
|
||||
|
||||
@override
|
||||
String get editName => 'Modifica nome';
|
||||
|
||||
@override
|
||||
String get open => 'Apri';
|
||||
|
||||
@override
|
||||
String get paste => 'Incolla';
|
||||
|
||||
@override
|
||||
String get preview => 'Anteprima';
|
||||
|
||||
@override
|
||||
String get remove => 'Rimuovi';
|
||||
|
||||
@override
|
||||
String get rename => 'Rinomina';
|
||||
|
||||
@override
|
||||
String get resolveAll => 'Risolvi tutto';
|
||||
|
||||
@override
|
||||
String get send => 'Invia';
|
||||
|
||||
@override
|
||||
String get sendAnyway => 'Invia comunque';
|
||||
|
||||
@override
|
||||
String get share => 'Condividi';
|
||||
|
||||
@override
|
||||
String get shareContact => 'Condividi contatto';
|
||||
|
||||
@override
|
||||
String get trace => 'Traccia';
|
||||
|
||||
@override
|
||||
String get use => 'Usa';
|
||||
|
||||
@override
|
||||
String get useSelectedFrequency => 'Usa frequenza selezionata';
|
||||
|
||||
@override
|
||||
String get discovery => 'Scoperta';
|
||||
|
||||
@override
|
||||
String get discoverRepeaters => 'Scopri ripetitori';
|
||||
|
||||
@override
|
||||
String get discoverSensors => 'Scopri sensori';
|
||||
|
||||
@override
|
||||
String get repeaterDiscoverySent => 'Scoperta ripetitori inviata';
|
||||
|
||||
@override
|
||||
String get sensorDiscoverySent => 'Scoperta sensori inviata';
|
||||
|
||||
@override
|
||||
String get clearedPendingDiscoveries => 'Scoperte in attesa cancellate.';
|
||||
|
||||
@override
|
||||
String get autoDiscovery => 'Scoperta automatica';
|
||||
|
||||
@override
|
||||
String get enableAutomaticAdding => 'Abilita aggiunta automatica';
|
||||
|
||||
@override
|
||||
String get autoaddRepeaters => 'Aggiungi ripetitori automaticamente';
|
||||
|
||||
@override
|
||||
String get autoaddRoomServers => 'Aggiungi server stanze automaticamente';
|
||||
|
||||
@override
|
||||
String get autoaddSensors => 'Aggiungi sensori automaticamente';
|
||||
|
||||
@override
|
||||
String get autoaddUsers => 'Aggiungi utenti automaticamente';
|
||||
|
||||
@override
|
||||
String get overwriteOldestWhenFull => 'Sovrascrivi i più vecchi quando pieno';
|
||||
|
||||
@override
|
||||
String get storage => 'Archiviazione';
|
||||
|
||||
@override
|
||||
String get dangerZone => 'Zona pericolosa';
|
||||
|
||||
@override
|
||||
String get profiles => 'Profili';
|
||||
|
||||
@override
|
||||
String get favourites => 'Preferiti';
|
||||
|
||||
@override
|
||||
String get sensors => 'Sensori';
|
||||
|
||||
@override
|
||||
String get others => 'Altri';
|
||||
|
||||
@override
|
||||
String get gpsModule => 'Modulo GPS';
|
||||
|
||||
@override
|
||||
String get liveTraffic => 'Traffico in tempo reale';
|
||||
|
||||
@override
|
||||
String get repeatersMap => 'Mappa ripetitori';
|
||||
|
||||
@override
|
||||
String get spectrumScan => 'Scansione spettro';
|
||||
|
||||
@override
|
||||
String get blePacketLogs => 'Log pacchetti BLE';
|
||||
|
||||
@override
|
||||
String get onlineTraceDatabase => 'Database tracce';
|
||||
|
||||
@override
|
||||
String get routePathByteSize => 'Dimensione percorso in byte';
|
||||
|
||||
@override
|
||||
String get messageNotifications => 'Notifiche messaggi';
|
||||
|
||||
@override
|
||||
String get sarAlerts => 'Avvisi SAR';
|
||||
|
||||
@override
|
||||
String get discoveryNotifications => 'Notifiche scoperte';
|
||||
|
||||
@override
|
||||
String get updateNotifications => 'Notifiche aggiornamenti';
|
||||
|
||||
@override
|
||||
String get muteWhileAppIsOpen => 'Silenzia con l\'app aperta';
|
||||
|
||||
@override
|
||||
String get disableContacts => 'Disabilita contatti';
|
||||
|
||||
@override
|
||||
String get enableSensorsTab => 'Abilita scheda Sensori';
|
||||
|
||||
@override
|
||||
String get enableProfiles => 'Abilita profili';
|
||||
|
||||
@override
|
||||
String get autoRouteRotation => 'Rotazione automatica percorso';
|
||||
|
||||
@override
|
||||
String get nearestRepeaterFallback => 'Ripetitore più vicino come fallback';
|
||||
|
||||
@override
|
||||
String get deleteAllStoredMessageHistory =>
|
||||
'Elimina tutta la cronologia messaggi';
|
||||
|
||||
@override
|
||||
String get messageFontSize => 'Dimensione carattere messaggi';
|
||||
|
||||
@override
|
||||
String get rotateMapWithHeading => 'Ruota mappa con direzione';
|
||||
|
||||
@override
|
||||
String get showMapDebugInfo => 'Mostra info debug mappa';
|
||||
|
||||
@override
|
||||
String get openMapInFullscreen => 'Apri mappa a schermo intero';
|
||||
|
||||
@override
|
||||
String get showSarMarkersLabel => 'Mostra marcatori SAR';
|
||||
|
||||
@override
|
||||
String get displaySarMarkersOnTheMainMap =>
|
||||
'Mostra marcatori SAR sulla mappa principale';
|
||||
|
||||
@override
|
||||
String get showAllContactTrailsLabel => 'Mostra tutti i percorsi contatti';
|
||||
|
||||
@override
|
||||
String get hideRepeatersOnMap => 'Nascondi ripetitori sulla mappa';
|
||||
|
||||
@override
|
||||
String get setMapScale => 'Imposta scala mappa';
|
||||
|
||||
@override
|
||||
String get customMapScaleSaved => 'Scala mappa personalizzata salvata';
|
||||
|
||||
@override
|
||||
String get voiceBitrate => 'Bitrate voce';
|
||||
|
||||
@override
|
||||
String get voiceCompressor => 'Compressore voce';
|
||||
|
||||
@override
|
||||
String get balancesQuietAndLoudSpeechLevels =>
|
||||
'Bilancia i livelli di voce bassi e alti';
|
||||
|
||||
@override
|
||||
String get voiceLimiter => 'Limitatore voce';
|
||||
|
||||
@override
|
||||
String get preventsClippingPeaksBeforeEncoding =>
|
||||
'Previene il clipping prima della codifica';
|
||||
|
||||
@override
|
||||
String get micAutoGain => 'Guadagno automatico microfono';
|
||||
|
||||
@override
|
||||
String get letsTheRecorderAdjustInputLevel =>
|
||||
'Permette al registratore di regolare il livello';
|
||||
|
||||
@override
|
||||
String get echoCancellation => 'Cancellazione eco';
|
||||
|
||||
@override
|
||||
String get noiseSuppression => 'Soppressione rumore';
|
||||
|
||||
@override
|
||||
String get trimSilenceInVoiceMessages =>
|
||||
'Taglia silenzio nei messaggi vocali';
|
||||
|
||||
@override
|
||||
String get compressor => 'Compressore';
|
||||
|
||||
@override
|
||||
String get limiter => 'Limitatore';
|
||||
|
||||
@override
|
||||
String get autoGain => 'Guadagno automatico';
|
||||
|
||||
@override
|
||||
String get echoCancel => 'Eco';
|
||||
|
||||
@override
|
||||
String get noiseSuppress => 'Rumore';
|
||||
|
||||
@override
|
||||
String get silenceTrim => 'Silenzio';
|
||||
|
||||
@override
|
||||
String get maxImageSize => 'Dimensione massima immagine';
|
||||
|
||||
@override
|
||||
String get imageCompression => 'Compressione immagine';
|
||||
|
||||
@override
|
||||
String get grayscale => 'Scala di grigi';
|
||||
|
||||
@override
|
||||
String get ultraMode => 'Modalità ultra';
|
||||
|
||||
@override
|
||||
String get fastPrivateGpsUpdates => 'Aggiornamenti GPS privati veloci';
|
||||
|
||||
@override
|
||||
String get movementThreshold => 'Soglia di movimento';
|
||||
|
||||
@override
|
||||
String get fastGpsMovementThreshold => 'Soglia movimento GPS veloce';
|
||||
|
||||
@override
|
||||
String get fastGpsActiveuseInterval => 'Intervallo uso attivo GPS veloce';
|
||||
|
||||
@override
|
||||
String get activeuseUpdateInterval => 'Intervallo aggiornamento uso attivo';
|
||||
|
||||
@override
|
||||
String get repeatNearbyTraffic => 'Ripeti traffico vicino';
|
||||
|
||||
@override
|
||||
String get relayThroughRepeatersAcrossTheMesh =>
|
||||
'Inoltra tramite ripetitori nella rete';
|
||||
|
||||
@override
|
||||
String get nearbyOnlyWithoutRepeaterFlooding =>
|
||||
'Solo vicini, senza flood dei ripetitori';
|
||||
|
||||
@override
|
||||
String get multihop => 'Multi-hop';
|
||||
|
||||
@override
|
||||
String get createProfile => 'Crea profilo';
|
||||
|
||||
@override
|
||||
String get renameProfile => 'Rinomina profilo';
|
||||
|
||||
@override
|
||||
String get newProfile => 'Nuovo profilo';
|
||||
|
||||
@override
|
||||
String get manageProfiles => 'Gestisci profili';
|
||||
|
||||
@override
|
||||
String get enableProfilesToStartManagingThem =>
|
||||
'Abilita i profili per iniziare a gestirli.';
|
||||
|
||||
@override
|
||||
String get openMessage => 'Apri messaggio';
|
||||
|
||||
@override
|
||||
String get jumpToTheRelatedSarMessage => 'Vai al messaggio SAR correlato';
|
||||
|
||||
@override
|
||||
String get removeSarMarker => 'Rimuovi marcatore SAR';
|
||||
|
||||
@override
|
||||
String get pleaseSelectADestinationToSendSarMarker =>
|
||||
'Seleziona una destinazione per inviare il marcatore SAR';
|
||||
|
||||
@override
|
||||
String get sarMarkerBroadcastToPublicChannel =>
|
||||
'Marcatore SAR trasmesso al canale pubblico';
|
||||
|
||||
@override
|
||||
String get sarMarkerSentToRoom => 'Marcatore SAR inviato alla stanza';
|
||||
|
||||
@override
|
||||
String get loadFromGallery => 'Carica dalla galleria';
|
||||
|
||||
@override
|
||||
String get replaceImage => 'Sostituisci immagine';
|
||||
|
||||
@override
|
||||
String get selectFromGallery => 'Seleziona dalla galleria';
|
||||
|
||||
@override
|
||||
String get team => 'Squadra';
|
||||
|
||||
@override
|
||||
String get found => 'Trovato';
|
||||
|
||||
@override
|
||||
String get staging => 'Area di raccolta';
|
||||
|
||||
@override
|
||||
String get object => 'Oggetto';
|
||||
|
||||
@override
|
||||
String get quiet => 'Silenzioso';
|
||||
|
||||
@override
|
||||
String get moderate => 'Moderato';
|
||||
|
||||
@override
|
||||
String get busy => 'Occupato';
|
||||
|
||||
@override
|
||||
String get spectrumScanReturnedNoCandidateFrequencies =>
|
||||
'La scansione spettro non ha trovato frequenze candidate';
|
||||
|
||||
@override
|
||||
String get searchMessages => 'Cerca messaggi';
|
||||
|
||||
@override
|
||||
String get sendImageFromGallery => 'Invia immagine dalla galleria';
|
||||
|
||||
@override
|
||||
String get takePhoto => 'Scatta foto';
|
||||
|
||||
@override
|
||||
String get dmOnly => 'Solo messaggio diretto';
|
||||
|
||||
@override
|
||||
String get allMessages => 'Tutti i messaggi';
|
||||
|
||||
@override
|
||||
String get sendToPublicChannel => 'Inviare al canale pubblico?';
|
||||
|
||||
@override
|
||||
String get selectMarkerTypeAndDestination =>
|
||||
'Seleziona tipo marcatore e destinazione';
|
||||
|
||||
@override
|
||||
String get noDestinationsAvailableLabel => 'Nessuna destinazione disponibile';
|
||||
|
||||
@override
|
||||
String get image => 'Immagine';
|
||||
|
||||
@override
|
||||
String get format => 'Formato';
|
||||
|
||||
@override
|
||||
String get dimensions => 'Dimensioni';
|
||||
|
||||
@override
|
||||
String get segments => 'Segmenti';
|
||||
|
||||
@override
|
||||
String get transfers => 'Trasferimenti';
|
||||
|
||||
@override
|
||||
String get downloadedBy => 'Scaricato da';
|
||||
|
||||
@override
|
||||
String get saveDiscoverySettings => 'Salva impostazioni scoperta';
|
||||
|
||||
@override
|
||||
String get savePublicInfo => 'Salva info pubbliche';
|
||||
|
||||
@override
|
||||
String get saveRadioSettings => 'Salva impostazioni radio';
|
||||
|
||||
@override
|
||||
String get savePath => 'Salva percorso';
|
||||
|
||||
@override
|
||||
String get wipeDeviceData => 'Cancella dati dispositivo';
|
||||
|
||||
@override
|
||||
String get wipeDevice => 'Cancella dispositivo';
|
||||
|
||||
@override
|
||||
String get destructiveDeviceActions => 'Azioni distruttive del dispositivo.';
|
||||
|
||||
@override
|
||||
String get chooseAPresetOrFinetuneCustomRadioSettings =>
|
||||
'Scegli un preset o personalizza le impostazioni radio.';
|
||||
|
||||
@override
|
||||
String get chooseTheNameAndLocationThisDeviceShares =>
|
||||
'Scegli il nome e la posizione condivisi da questo dispositivo.';
|
||||
|
||||
@override
|
||||
String get availableSpaceOnThisDevice =>
|
||||
'Spazio disponibile su questo dispositivo.';
|
||||
|
||||
@override
|
||||
String get used => 'Usato';
|
||||
|
||||
@override
|
||||
String get total => 'Totale';
|
||||
|
||||
@override
|
||||
String get renameValue => 'Rinomina valore';
|
||||
|
||||
@override
|
||||
String get customizeFields => 'Personalizza campi';
|
||||
|
||||
@override
|
||||
String get livePreview => 'Anteprima dal vivo';
|
||||
|
||||
@override
|
||||
String get refreshSchedule => 'Programma aggiornamento';
|
||||
|
||||
@override
|
||||
String get noResponse => 'Nessuna risposta';
|
||||
|
||||
@override
|
||||
String get refreshing => 'Aggiornamento';
|
||||
|
||||
@override
|
||||
String get unavailable => 'Non disponibile';
|
||||
|
||||
@override
|
||||
String get pickARelayOrNodeToWatchInSensors =>
|
||||
'Scegli un relay o nodo da monitorare.';
|
||||
|
||||
@override
|
||||
String get publicKeyLabel => 'Chiave pubblica';
|
||||
|
||||
@override
|
||||
String get alreadyInContacts => 'Già nei contatti';
|
||||
|
||||
@override
|
||||
String get connectToADeviceBeforeAddingContacts =>
|
||||
'Connettiti a un dispositivo prima di aggiungere contatti';
|
||||
|
||||
@override
|
||||
String get fromContacts => 'Dai contatti';
|
||||
|
||||
@override
|
||||
String get onlineOnly => 'Solo online';
|
||||
|
||||
@override
|
||||
String get inBoth => 'In entrambi';
|
||||
|
||||
@override
|
||||
String get source => 'Origine';
|
||||
|
||||
@override
|
||||
String get manualRouteEdit => 'Modifica manuale percorso';
|
||||
|
||||
@override
|
||||
String get observedMeshRoute => 'Percorso mesh osservato';
|
||||
|
||||
@override
|
||||
String get allMessagesCleared => 'Tutti i messaggi cancellati';
|
||||
|
||||
@override
|
||||
String get onlineTraceDatabaseCleared => 'Database tracce cancellata';
|
||||
|
||||
@override
|
||||
String get packetLogsCleared => 'Log pacchetti cancellati';
|
||||
|
||||
@override
|
||||
String get hexDataCopiedToClipboard => 'Dati hex copiati negli appunti';
|
||||
|
||||
@override
|
||||
String get developerModeEnabled => 'Modalità sviluppatore attivata';
|
||||
|
||||
@override
|
||||
String get developerModeDisabled => 'Modalità sviluppatore disattivata';
|
||||
|
||||
@override
|
||||
String get clipboardIsEmpty => 'Gli appunti sono vuoti';
|
||||
|
||||
@override
|
||||
String get contactImported => 'Contatto importato';
|
||||
|
||||
@override
|
||||
String get contactLinkCopiedToClipboard =>
|
||||
'Link contatto copiato negli appunti';
|
||||
|
||||
@override
|
||||
String get failedToExportContact => 'Esportazione contatto fallita';
|
||||
|
||||
@override
|
||||
String get noLogsToExport => 'Nessun log da esportare';
|
||||
|
||||
@override
|
||||
String get exportAsCsv => 'Esporta come CSV';
|
||||
|
||||
@override
|
||||
String get exportAsText => 'Esporta come testo';
|
||||
|
||||
@override
|
||||
String get receivedRfc3339 => 'Ricevuto (RFC3339)';
|
||||
|
||||
@override
|
||||
String get buildTime => 'Ora build';
|
||||
|
||||
@override
|
||||
String get downloadUrlNotAvailable => 'URL di download non disponibile';
|
||||
|
||||
@override
|
||||
String get cannotOpenDownloadUrl => 'Impossibile aprire URL di download';
|
||||
|
||||
@override
|
||||
String get updateCheckIsOnlyAvailableOnAndroid =>
|
||||
'Il controllo aggiornamenti è disponibile solo su Android';
|
||||
|
||||
@override
|
||||
String get youAreRunningTheLatestVersion =>
|
||||
'Stai utilizzando l\'ultima versione';
|
||||
|
||||
@override
|
||||
String get updateAvailableButDownloadUrlNotFound =>
|
||||
'Aggiornamento disponibile ma URL di download non trovato';
|
||||
|
||||
@override
|
||||
String get startTictactoe => 'Avvia Tic-Tac-Toe';
|
||||
|
||||
@override
|
||||
String get tictactoeUnavailable => 'Tic-Tac-Toe non disponibile';
|
||||
|
||||
@override
|
||||
String get tictactoeOpponentUnknown => 'Tic-Tac-Toe: avversario sconosciuto';
|
||||
|
||||
@override
|
||||
String get tictactoeWaitingForStart => 'Tic-Tac-Toe: in attesa dell\'inizio';
|
||||
|
||||
@override
|
||||
String get acceptsShareLinks => 'Accetta link condivisi';
|
||||
|
||||
@override
|
||||
String get supportsRawHex => 'Supporta hex grezzo';
|
||||
|
||||
@override
|
||||
String get clipboardfriendly => 'Compatibile con appunti';
|
||||
|
||||
@override
|
||||
String get captured => 'Catturato';
|
||||
|
||||
@override
|
||||
String get size => 'Dimensione';
|
||||
|
||||
@override
|
||||
String get noCustomChannelsToClear =>
|
||||
'Nessun canale personalizzato da cancellare.';
|
||||
|
||||
@override
|
||||
String get noDeviceContactsToClear =>
|
||||
'Nessun contatto dispositivo da cancellare.';
|
||||
}
|
||||
|
||||
@@ -1729,4 +1729,964 @@ class AppLocalizationsPl extends AppLocalizations {
|
||||
|
||||
@override
|
||||
String get requestingVoice => 'Pobieranie głosu';
|
||||
|
||||
@override
|
||||
String get device => 'urządzenie';
|
||||
|
||||
@override
|
||||
String get change => 'Zmień';
|
||||
|
||||
@override
|
||||
String get wizardOverviewDescription =>
|
||||
'Ta aplikacja łączy wiadomości MeshCore, terenowe aktualizacje SAR, mapy i narzędzia urządzenia w jednym miejscu.';
|
||||
|
||||
@override
|
||||
String get wizardOverviewFeature1 =>
|
||||
'Wysyłaj wiadomości bezpośrednie, wpisy pokojowe i wiadomości kanałowe z głównej karty Wiadomości.';
|
||||
|
||||
@override
|
||||
String get wizardOverviewFeature2 =>
|
||||
'Udostępniaj znaczniki SAR, rysunki mapy, klipy głosowe i obrazy przez sieć mesh.';
|
||||
|
||||
@override
|
||||
String get wizardOverviewFeature3 =>
|
||||
'Połącz się przez BLE lub TCP, a następnie zarządzaj radiem towarzyszącym bezpośrednio z aplikacji.';
|
||||
|
||||
@override
|
||||
String get wizardMessagingTitle => 'Wiadomości i raporty terenowe';
|
||||
|
||||
@override
|
||||
String get wizardMessagingDescription =>
|
||||
'Wiadomości to tutaj coś więcej niż zwykły tekst. Aplikacja obsługuje już kilka typów danych operacyjnych i przepływów transferu.';
|
||||
|
||||
@override
|
||||
String get wizardMessagingFeature1 =>
|
||||
'Wysyłaj wiadomości bezpośrednie, wpisy pokojowe i ruch kanałowy z jednego edytora.';
|
||||
|
||||
@override
|
||||
String get wizardMessagingFeature2 =>
|
||||
'Twórz aktualizacje SAR i wielokrotnego użytku szablony SAR do typowych raportów terenowych.';
|
||||
|
||||
@override
|
||||
String get wizardMessagingFeature3 =>
|
||||
'Przesyłaj sesje głosowe i obrazy z postępem i szacowanym czasem transmisji w interfejsie.';
|
||||
|
||||
@override
|
||||
String get wizardConnectDeviceTitle => 'Połącz urządzenie';
|
||||
|
||||
@override
|
||||
String get wizardConnectDeviceDescription =>
|
||||
'Podłącz radio MeshCore, wybierz nazwę i zastosuj profil radiowy przed kontynuacją.';
|
||||
|
||||
@override
|
||||
String get wizardSetupBadge => 'Konfiguracja';
|
||||
|
||||
@override
|
||||
String get wizardOverviewBadge => 'Przegląd';
|
||||
|
||||
@override
|
||||
String wizardConnectedToDevice(String deviceName) {
|
||||
return 'Połączono z $deviceName';
|
||||
}
|
||||
|
||||
@override
|
||||
String get wizardNoDeviceConnected => 'Brak podłączonego urządzenia';
|
||||
|
||||
@override
|
||||
String get wizardSkipForNow => 'Pomiń na razie';
|
||||
|
||||
@override
|
||||
String get wizardDeviceNameLabel => 'Nazwa urządzenia';
|
||||
|
||||
@override
|
||||
String get wizardDeviceNameHelp =>
|
||||
'Ta nazwa jest rozgłaszana innym użytkownikom MeshCore.';
|
||||
|
||||
@override
|
||||
String get wizardConfigRegionLabel => 'Region konfiguracji';
|
||||
|
||||
@override
|
||||
String get wizardConfigRegionHelp =>
|
||||
'Używa pełnej oficjalnej listy presetów MeshCore. Domyślnie wybrane jest EU/UK (Narrow).';
|
||||
|
||||
@override
|
||||
String get wizardPresetNote1 =>
|
||||
'Upewnij się, że wybrany preset jest zgodny z lokalnymi przepisami radiowymi.';
|
||||
|
||||
@override
|
||||
String get wizardPresetNote2 =>
|
||||
'Lista odpowiada oficjalnemu źródłu presetów narzędzia konfiguracji MeshCore.';
|
||||
|
||||
@override
|
||||
String get wizardPresetNote3 =>
|
||||
'EU/UK (Narrow) pozostaje domyślnie wybrane podczas wdrożenia.';
|
||||
|
||||
@override
|
||||
String get wizardSaving => 'Zapisywanie...';
|
||||
|
||||
@override
|
||||
String get wizardSaveAndContinue => 'Zapisz i kontynuuj';
|
||||
|
||||
@override
|
||||
String get wizardEnterDeviceName =>
|
||||
'Wprowadź nazwę urządzenia przed kontynuacją.';
|
||||
|
||||
@override
|
||||
String wizardDeviceSetupSaved(String deviceName, String presetName) {
|
||||
return 'Zapisano $deviceName z ustawieniem $presetName.';
|
||||
}
|
||||
|
||||
@override
|
||||
String get wizardNetworkTitle => 'Kontakty, pokoje i repeatery';
|
||||
|
||||
@override
|
||||
String get wizardNetworkDescription =>
|
||||
'Karta Kontakty porządkuje sieć, którą odkrywasz, oraz trasy, których uczysz się z czasem.';
|
||||
|
||||
@override
|
||||
String get wizardNetworkFeature1 =>
|
||||
'Przeglądaj członków zespołu, repeatery, pokoje, kanały i oczekujące ogłoszenia na jednej liście.';
|
||||
|
||||
@override
|
||||
String get wizardNetworkFeature2 =>
|
||||
'Używaj smart ping, logowania do pokoi, poznanych ścieżek i narzędzi resetowania tras, gdy łączność staje się chaotyczna.';
|
||||
|
||||
@override
|
||||
String get wizardNetworkFeature3 =>
|
||||
'Twórz kanały i zarządzaj celami sieciowymi bez opuszczania aplikacji.';
|
||||
|
||||
@override
|
||||
String get wizardMapOpsTitle => 'Mapa, ślady i współdzielona geometria';
|
||||
|
||||
@override
|
||||
String get wizardMapOpsDescription =>
|
||||
'Mapa aplikacji jest bezpośrednio powiązana z wiadomościami, śledzeniem i nakładkami SAR zamiast być osobnym podglądem.';
|
||||
|
||||
@override
|
||||
String get wizardMapOpsFeature1 =>
|
||||
'Śledź własną pozycję, lokalizacje członków zespołu i ślady ruchu na mapie.';
|
||||
|
||||
@override
|
||||
String get wizardMapOpsFeature2 =>
|
||||
'Otwieraj rysunki z wiadomości, podglądaj je w miejscu i usuwaj z mapy w razie potrzeby.';
|
||||
|
||||
@override
|
||||
String get wizardMapOpsFeature3 =>
|
||||
'Korzystaj z widoków map repeaterów i współdzielonych nakładek, aby zrozumieć zasięg sieci w terenie.';
|
||||
|
||||
@override
|
||||
String get wizardToolsTitle => 'Narzędzia poza wiadomościami';
|
||||
|
||||
@override
|
||||
String get wizardToolsDescription =>
|
||||
'Jest tu więcej niż cztery główne karty. Aplikacja obejmuje także konfigurację, diagnostykę i opcjonalne przepływy czujników.';
|
||||
|
||||
@override
|
||||
String get wizardToolsFeature1 =>
|
||||
'Otwórz konfigurację urządzenia, aby zmienić ustawienia radia, telemetrię, moc TX i szczegóły urządzenia towarzyszącego.';
|
||||
|
||||
@override
|
||||
String get wizardToolsFeature2 =>
|
||||
'Włącz kartę Czujniki, gdy chcesz mieć pulpity monitorowanych czujników i szybkie odświeżanie.';
|
||||
|
||||
@override
|
||||
String get wizardToolsFeature3 =>
|
||||
'Używaj logów pakietów, skanowania widma i diagnostyki deweloperskiej podczas rozwiązywania problemów z mesh.';
|
||||
|
||||
@override
|
||||
String get contactInSensors => 'W Czujnikach';
|
||||
|
||||
@override
|
||||
String get contactAddToSensors => 'Dodaj do Czujników';
|
||||
|
||||
@override
|
||||
String get contactSetPath => 'Ustaw ścieżkę';
|
||||
|
||||
@override
|
||||
String contactAddedToSensors(String contactName) {
|
||||
return '$contactName dodano do Czujników';
|
||||
}
|
||||
|
||||
@override
|
||||
String contactFailedToClearRoute(String error) {
|
||||
return 'Nie udało się wyczyścić trasy: $error';
|
||||
}
|
||||
|
||||
@override
|
||||
String get contactRouteCleared => 'Trasa wyczyszczona';
|
||||
|
||||
@override
|
||||
String contactRouteSet(String route) {
|
||||
return 'Ustawiono trasę: $route';
|
||||
}
|
||||
|
||||
@override
|
||||
String contactFailedToSetRoute(String error) {
|
||||
return 'Nie udało się ustawić trasy: $error';
|
||||
}
|
||||
|
||||
@override
|
||||
String get rssi => 'RSSI';
|
||||
|
||||
@override
|
||||
String get snr => 'SNR';
|
||||
|
||||
@override
|
||||
String get ackTimeout => 'Limit czasu ACK';
|
||||
|
||||
@override
|
||||
String get opcode => 'Opcode';
|
||||
|
||||
@override
|
||||
String get payload => 'Ładunek';
|
||||
|
||||
@override
|
||||
String get hops => 'Skoki';
|
||||
|
||||
@override
|
||||
String get hashSize => 'Rozmiar hash';
|
||||
|
||||
@override
|
||||
String get pathBytes => 'Bajty ścieżki';
|
||||
|
||||
@override
|
||||
String get selectedPath => 'Wybrana ścieżka';
|
||||
|
||||
@override
|
||||
String get estimatedTx => 'Szacowane nadawanie';
|
||||
|
||||
@override
|
||||
String get senderToReceipt => 'Od nadawcy do odbioru';
|
||||
|
||||
@override
|
||||
String get receivedCopies => 'Otrzymane kopie';
|
||||
|
||||
@override
|
||||
String get retryCause => 'Przyczyna ponowienia';
|
||||
|
||||
@override
|
||||
String get retryMode => 'Tryb ponowienia';
|
||||
|
||||
@override
|
||||
String get retryResult => 'Wynik ponowienia';
|
||||
|
||||
@override
|
||||
String get lastRetry => 'Ostatnia próba';
|
||||
|
||||
@override
|
||||
String get rxPackets => 'Pakiety RX';
|
||||
|
||||
@override
|
||||
String get mesh => 'Mesh';
|
||||
|
||||
@override
|
||||
String get rate => 'Szybkość';
|
||||
|
||||
@override
|
||||
String get window => 'Okno';
|
||||
|
||||
@override
|
||||
String get posttxDelay => 'Opóźnienie po nadawaniu';
|
||||
|
||||
@override
|
||||
String get bandpass => 'Pasmowy';
|
||||
|
||||
@override
|
||||
String get bandpassFilterVoice => 'Filtr pasmowy głosu';
|
||||
|
||||
@override
|
||||
String get frequency => 'Częstotliwość';
|
||||
|
||||
@override
|
||||
String get australia => 'Australia';
|
||||
|
||||
@override
|
||||
String get australiaNarrow => 'Australia (wąski)';
|
||||
|
||||
@override
|
||||
String get australiaQld => 'Australia: QLD';
|
||||
|
||||
@override
|
||||
String get australiaSaWa => 'Australia: SA, WA';
|
||||
|
||||
@override
|
||||
String get newZealand => 'Nowa Zelandia';
|
||||
|
||||
@override
|
||||
String get newZealandNarrow => 'Nowa Zelandia (wąski)';
|
||||
|
||||
@override
|
||||
String get switzerland => 'Szwajcaria';
|
||||
|
||||
@override
|
||||
String get portugal433 => 'Portugalia 433';
|
||||
|
||||
@override
|
||||
String get portugal868 => 'Portugalia 868';
|
||||
|
||||
@override
|
||||
String get czechRepublicNarrow => 'Czechy (wąski)';
|
||||
|
||||
@override
|
||||
String get eu433mhzLongRange => 'EU 433MHz (daleki zasięg)';
|
||||
|
||||
@override
|
||||
String get euukDeprecated => 'EU/UK (przestarzały)';
|
||||
|
||||
@override
|
||||
String get euukNarrow => 'EU/UK (wąski)';
|
||||
|
||||
@override
|
||||
String get usacanadaRecommended => 'USA/Kanada (zalecany)';
|
||||
|
||||
@override
|
||||
String get vietnamDeprecated => 'Wietnam (przestarzały)';
|
||||
|
||||
@override
|
||||
String get vietnamNarrow => 'Wietnam (wąski)';
|
||||
|
||||
@override
|
||||
String get active => 'Aktywny';
|
||||
|
||||
@override
|
||||
String get addContact => 'Dodaj kontakt';
|
||||
|
||||
@override
|
||||
String get all => 'Wszystko';
|
||||
|
||||
@override
|
||||
String get autoResolve => 'Automatyczne rozwiązanie';
|
||||
|
||||
@override
|
||||
String get clearAllLabel => 'Wyczyść wszystko';
|
||||
|
||||
@override
|
||||
String get clearRelays => 'Wyczyść przekaźniki';
|
||||
|
||||
@override
|
||||
String get clearFilters => 'Wyczyść filtry';
|
||||
|
||||
@override
|
||||
String get clearRoute => 'Wyczyść trasę';
|
||||
|
||||
@override
|
||||
String get clearMessages => 'Wyczyść wiadomości';
|
||||
|
||||
@override
|
||||
String get clearScale => 'Wyczyść skalę';
|
||||
|
||||
@override
|
||||
String get clearDiscoveries => 'Wyczyść odkrycia';
|
||||
|
||||
@override
|
||||
String get clearOnlineTraceDatabase => 'Wyczyść bazę śladów';
|
||||
|
||||
@override
|
||||
String get clearAllChannels => 'Wyczyść wszystkie kanały';
|
||||
|
||||
@override
|
||||
String get clearAllContacts => 'Wyczyść wszystkie kontakty';
|
||||
|
||||
@override
|
||||
String get clearChannels => 'Wyczyść kanały';
|
||||
|
||||
@override
|
||||
String get clearContacts => 'Wyczyść kontakty';
|
||||
|
||||
@override
|
||||
String get clearPathOnMaxRetry => 'Wyczyść ścieżkę przy maks. próbie';
|
||||
|
||||
@override
|
||||
String get create => 'Utwórz';
|
||||
|
||||
@override
|
||||
String get custom => 'Niestandardowy';
|
||||
|
||||
@override
|
||||
String get defaultValue => 'Domyślny';
|
||||
|
||||
@override
|
||||
String get duplicate => 'Duplikuj';
|
||||
|
||||
@override
|
||||
String get editName => 'Edytuj nazwę';
|
||||
|
||||
@override
|
||||
String get open => 'Otwórz';
|
||||
|
||||
@override
|
||||
String get paste => 'Wklej';
|
||||
|
||||
@override
|
||||
String get preview => 'Podgląd';
|
||||
|
||||
@override
|
||||
String get remove => 'Usuń';
|
||||
|
||||
@override
|
||||
String get rename => 'Zmień nazwę';
|
||||
|
||||
@override
|
||||
String get resolveAll => 'Rozwiąż wszystko';
|
||||
|
||||
@override
|
||||
String get send => 'Wyślij';
|
||||
|
||||
@override
|
||||
String get sendAnyway => 'Wyślij mimo to';
|
||||
|
||||
@override
|
||||
String get share => 'Udostępnij';
|
||||
|
||||
@override
|
||||
String get shareContact => 'Udostępnij kontakt';
|
||||
|
||||
@override
|
||||
String get trace => 'Śledzenie';
|
||||
|
||||
@override
|
||||
String get use => 'Użyj';
|
||||
|
||||
@override
|
||||
String get useSelectedFrequency => 'Użyj wybranej częstotliwości';
|
||||
|
||||
@override
|
||||
String get discovery => 'Wykrywanie';
|
||||
|
||||
@override
|
||||
String get discoverRepeaters => 'Wykryj przekaźniki';
|
||||
|
||||
@override
|
||||
String get discoverSensors => 'Wykryj czujniki';
|
||||
|
||||
@override
|
||||
String get repeaterDiscoverySent => 'Wykrywanie przekaźników wysłane';
|
||||
|
||||
@override
|
||||
String get sensorDiscoverySent => 'Wykrywanie czujników wysłane';
|
||||
|
||||
@override
|
||||
String get clearedPendingDiscoveries => 'Wyczyszczono oczekujące odkrycia.';
|
||||
|
||||
@override
|
||||
String get autoDiscovery => 'Automatyczne wykrywanie';
|
||||
|
||||
@override
|
||||
String get enableAutomaticAdding => 'Włącz automatyczne dodawanie';
|
||||
|
||||
@override
|
||||
String get autoaddRepeaters => 'Automatycznie dodaj przekaźniki';
|
||||
|
||||
@override
|
||||
String get autoaddRoomServers => 'Automatycznie dodaj serwery pokoi';
|
||||
|
||||
@override
|
||||
String get autoaddSensors => 'Automatycznie dodaj czujniki';
|
||||
|
||||
@override
|
||||
String get autoaddUsers => 'Automatycznie dodaj użytkowników';
|
||||
|
||||
@override
|
||||
String get overwriteOldestWhenFull => 'Nadpisz najstarsze gdy pełne';
|
||||
|
||||
@override
|
||||
String get storage => 'Pamięć';
|
||||
|
||||
@override
|
||||
String get dangerZone => 'Strefa zagrożenia';
|
||||
|
||||
@override
|
||||
String get profiles => 'Profile';
|
||||
|
||||
@override
|
||||
String get favourites => 'Ulubione';
|
||||
|
||||
@override
|
||||
String get sensors => 'Czujniki';
|
||||
|
||||
@override
|
||||
String get others => 'Inne';
|
||||
|
||||
@override
|
||||
String get gpsModule => 'Moduł GPS';
|
||||
|
||||
@override
|
||||
String get liveTraffic => 'Ruch na żywo';
|
||||
|
||||
@override
|
||||
String get repeatersMap => 'Mapa przekaźników';
|
||||
|
||||
@override
|
||||
String get spectrumScan => 'Skanowanie spektrum';
|
||||
|
||||
@override
|
||||
String get blePacketLogs => 'Logi pakietów BLE';
|
||||
|
||||
@override
|
||||
String get onlineTraceDatabase => 'Baza śladów';
|
||||
|
||||
@override
|
||||
String get routePathByteSize => 'Rozmiar ścieżki w bajtach';
|
||||
|
||||
@override
|
||||
String get messageNotifications => 'Powiadomienia o wiadomościach';
|
||||
|
||||
@override
|
||||
String get sarAlerts => 'Alerty SAR';
|
||||
|
||||
@override
|
||||
String get discoveryNotifications => 'Powiadomienia o wykrywaniu';
|
||||
|
||||
@override
|
||||
String get updateNotifications => 'Powiadomienia o aktualizacjach';
|
||||
|
||||
@override
|
||||
String get muteWhileAppIsOpen => 'Wycisz gdy aplikacja jest otwarta';
|
||||
|
||||
@override
|
||||
String get disableContacts => 'Wyłącz kontakty';
|
||||
|
||||
@override
|
||||
String get enableSensorsTab => 'Włącz kartę Czujniki';
|
||||
|
||||
@override
|
||||
String get enableProfiles => 'Włącz profile';
|
||||
|
||||
@override
|
||||
String get autoRouteRotation => 'Automatyczna rotacja trasy';
|
||||
|
||||
@override
|
||||
String get nearestRepeaterFallback => 'Najbliższy przekaźnik awaryjny';
|
||||
|
||||
@override
|
||||
String get deleteAllStoredMessageHistory => 'Usuń całą historię wiadomości';
|
||||
|
||||
@override
|
||||
String get messageFontSize => 'Rozmiar czcionki wiadomości';
|
||||
|
||||
@override
|
||||
String get rotateMapWithHeading => 'Obracaj mapę z kierunkiem';
|
||||
|
||||
@override
|
||||
String get showMapDebugInfo => 'Pokaż info debugowania mapy';
|
||||
|
||||
@override
|
||||
String get openMapInFullscreen => 'Otwórz mapę na pełnym ekranie';
|
||||
|
||||
@override
|
||||
String get showSarMarkersLabel => 'Pokaż znaczniki SAR';
|
||||
|
||||
@override
|
||||
String get displaySarMarkersOnTheMainMap =>
|
||||
'Wyświetlaj znaczniki SAR na głównej mapie';
|
||||
|
||||
@override
|
||||
String get showAllContactTrailsLabel => 'Pokaż wszystkie ślady kontaktów';
|
||||
|
||||
@override
|
||||
String get hideRepeatersOnMap => 'Ukryj przekaźniki na mapie';
|
||||
|
||||
@override
|
||||
String get setMapScale => 'Ustaw skalę mapy';
|
||||
|
||||
@override
|
||||
String get customMapScaleSaved => 'Niestandardowa skala mapy zapisana';
|
||||
|
||||
@override
|
||||
String get voiceBitrate => 'Bitrate głosu';
|
||||
|
||||
@override
|
||||
String get voiceCompressor => 'Kompresor głosu';
|
||||
|
||||
@override
|
||||
String get balancesQuietAndLoudSpeechLevels =>
|
||||
'Wyrównuje ciche i głośne poziomy mowy';
|
||||
|
||||
@override
|
||||
String get voiceLimiter => 'Limiter głosu';
|
||||
|
||||
@override
|
||||
String get preventsClippingPeaksBeforeEncoding =>
|
||||
'Zapobiega przycinaniu szczytów przed kodowaniem';
|
||||
|
||||
@override
|
||||
String get micAutoGain => 'Automatyczne wzmocnienie mikrofonu';
|
||||
|
||||
@override
|
||||
String get letsTheRecorderAdjustInputLevel =>
|
||||
'Pozwala nagrywarkowi dostosować poziom wejścia';
|
||||
|
||||
@override
|
||||
String get echoCancellation => 'Usuwanie echa';
|
||||
|
||||
@override
|
||||
String get noiseSuppression => 'Tłumienie szumu';
|
||||
|
||||
@override
|
||||
String get trimSilenceInVoiceMessages =>
|
||||
'Przytnij ciszę w wiadomościach głosowych';
|
||||
|
||||
@override
|
||||
String get compressor => 'Kompresor';
|
||||
|
||||
@override
|
||||
String get limiter => 'Limiter';
|
||||
|
||||
@override
|
||||
String get autoGain => 'Automatyczne wzmocnienie';
|
||||
|
||||
@override
|
||||
String get echoCancel => 'Echo';
|
||||
|
||||
@override
|
||||
String get noiseSuppress => 'Szum';
|
||||
|
||||
@override
|
||||
String get silenceTrim => 'Cisza';
|
||||
|
||||
@override
|
||||
String get maxImageSize => 'Maksymalny rozmiar obrazu';
|
||||
|
||||
@override
|
||||
String get imageCompression => 'Kompresja obrazu';
|
||||
|
||||
@override
|
||||
String get grayscale => 'Skala szarości';
|
||||
|
||||
@override
|
||||
String get ultraMode => 'Tryb ultra';
|
||||
|
||||
@override
|
||||
String get fastPrivateGpsUpdates => 'Szybkie prywatne aktualizacje GPS';
|
||||
|
||||
@override
|
||||
String get movementThreshold => 'Próg ruchu';
|
||||
|
||||
@override
|
||||
String get fastGpsMovementThreshold => 'Próg ruchu szybkiego GPS';
|
||||
|
||||
@override
|
||||
String get fastGpsActiveuseInterval =>
|
||||
'Interwał aktywnego użycia szybkiego GPS';
|
||||
|
||||
@override
|
||||
String get activeuseUpdateInterval =>
|
||||
'Interwał aktualizacji przy aktywnym użyciu';
|
||||
|
||||
@override
|
||||
String get repeatNearbyTraffic => 'Powtarzaj pobliski ruch';
|
||||
|
||||
@override
|
||||
String get relayThroughRepeatersAcrossTheMesh =>
|
||||
'Przekazuj przez przekaźniki w sieci';
|
||||
|
||||
@override
|
||||
String get nearbyOnlyWithoutRepeaterFlooding =>
|
||||
'Tylko w pobliżu, bez zalewania przekaźników';
|
||||
|
||||
@override
|
||||
String get multihop => 'Wieloskokowy';
|
||||
|
||||
@override
|
||||
String get createProfile => 'Utwórz profil';
|
||||
|
||||
@override
|
||||
String get renameProfile => 'Zmień nazwę profilu';
|
||||
|
||||
@override
|
||||
String get newProfile => 'Nowy profil';
|
||||
|
||||
@override
|
||||
String get manageProfiles => 'Zarządzaj profilami';
|
||||
|
||||
@override
|
||||
String get enableProfilesToStartManagingThem =>
|
||||
'Włącz profile, aby zacząć nimi zarządzać.';
|
||||
|
||||
@override
|
||||
String get openMessage => 'Otwórz wiadomość';
|
||||
|
||||
@override
|
||||
String get jumpToTheRelatedSarMessage =>
|
||||
'Przejdź do powiązanej wiadomości SAR';
|
||||
|
||||
@override
|
||||
String get removeSarMarker => 'Usuń znacznik SAR';
|
||||
|
||||
@override
|
||||
String get pleaseSelectADestinationToSendSarMarker =>
|
||||
'Wybierz cel wysłania znacznika SAR';
|
||||
|
||||
@override
|
||||
String get sarMarkerBroadcastToPublicChannel =>
|
||||
'Znacznik SAR wysłany na kanał publiczny';
|
||||
|
||||
@override
|
||||
String get sarMarkerSentToRoom => 'Znacznik SAR wysłany do pokoju';
|
||||
|
||||
@override
|
||||
String get loadFromGallery => 'Załaduj z galerii';
|
||||
|
||||
@override
|
||||
String get replaceImage => 'Zamień obraz';
|
||||
|
||||
@override
|
||||
String get selectFromGallery => 'Wybierz z galerii';
|
||||
|
||||
@override
|
||||
String get team => 'Zespół';
|
||||
|
||||
@override
|
||||
String get found => 'Znaleziono';
|
||||
|
||||
@override
|
||||
String get staging => 'Punkt zbiórki';
|
||||
|
||||
@override
|
||||
String get object => 'Obiekt';
|
||||
|
||||
@override
|
||||
String get quiet => 'Cicho';
|
||||
|
||||
@override
|
||||
String get moderate => 'Umiarkowane';
|
||||
|
||||
@override
|
||||
String get busy => 'Zajęty';
|
||||
|
||||
@override
|
||||
String get spectrumScanReturnedNoCandidateFrequencies =>
|
||||
'Skanowanie spektrum nie znalazło częstotliwości kandydatów';
|
||||
|
||||
@override
|
||||
String get searchMessages => 'Szukaj wiadomości';
|
||||
|
||||
@override
|
||||
String get sendImageFromGallery => 'Wyślij obraz z galerii';
|
||||
|
||||
@override
|
||||
String get takePhoto => 'Zrób zdjęcie';
|
||||
|
||||
@override
|
||||
String get dmOnly => 'Tylko wiadomość bezpośrednia';
|
||||
|
||||
@override
|
||||
String get allMessages => 'Wszystkie wiadomości';
|
||||
|
||||
@override
|
||||
String get sendToPublicChannel => 'Wysłać na kanał publiczny?';
|
||||
|
||||
@override
|
||||
String get selectMarkerTypeAndDestination => 'Wybierz typ znacznika i cel';
|
||||
|
||||
@override
|
||||
String get noDestinationsAvailableLabel => 'Brak dostępnych celów';
|
||||
|
||||
@override
|
||||
String get image => 'Obraz';
|
||||
|
||||
@override
|
||||
String get format => 'Format';
|
||||
|
||||
@override
|
||||
String get dimensions => 'Wymiary';
|
||||
|
||||
@override
|
||||
String get segments => 'Segmenty';
|
||||
|
||||
@override
|
||||
String get transfers => 'Transfery';
|
||||
|
||||
@override
|
||||
String get downloadedBy => 'Pobrane przez';
|
||||
|
||||
@override
|
||||
String get saveDiscoverySettings => 'Zapisz ustawienia wykrywania';
|
||||
|
||||
@override
|
||||
String get savePublicInfo => 'Zapisz informacje publiczne';
|
||||
|
||||
@override
|
||||
String get saveRadioSettings => 'Zapisz ustawienia radia';
|
||||
|
||||
@override
|
||||
String get savePath => 'Zapisz ścieżkę';
|
||||
|
||||
@override
|
||||
String get wipeDeviceData => 'Wyczyść dane urządzenia';
|
||||
|
||||
@override
|
||||
String get wipeDevice => 'Wyczyść urządzenie';
|
||||
|
||||
@override
|
||||
String get destructiveDeviceActions => 'Destrukcyjne akcje na urządzeniu.';
|
||||
|
||||
@override
|
||||
String get chooseAPresetOrFinetuneCustomRadioSettings =>
|
||||
'Wybierz ustawienie wstępne lub dostosuj ustawienia radia.';
|
||||
|
||||
@override
|
||||
String get chooseTheNameAndLocationThisDeviceShares =>
|
||||
'Wybierz nazwę i lokalizację udostępnianą przez to urządzenie.';
|
||||
|
||||
@override
|
||||
String get availableSpaceOnThisDevice =>
|
||||
'Dostępne miejsce na tym urządzeniu.';
|
||||
|
||||
@override
|
||||
String get used => 'Użyto';
|
||||
|
||||
@override
|
||||
String get total => 'Razem';
|
||||
|
||||
@override
|
||||
String get renameValue => 'Zmień nazwę wartości';
|
||||
|
||||
@override
|
||||
String get customizeFields => 'Dostosuj pola';
|
||||
|
||||
@override
|
||||
String get livePreview => 'Podgląd na żywo';
|
||||
|
||||
@override
|
||||
String get refreshSchedule => 'Harmonogram odświeżania';
|
||||
|
||||
@override
|
||||
String get noResponse => 'Brak odpowiedzi';
|
||||
|
||||
@override
|
||||
String get refreshing => 'Odświeżanie';
|
||||
|
||||
@override
|
||||
String get unavailable => 'Niedostępny';
|
||||
|
||||
@override
|
||||
String get pickARelayOrNodeToWatchInSensors =>
|
||||
'Wybierz przekaźnik lub węzeł do obserwacji.';
|
||||
|
||||
@override
|
||||
String get publicKeyLabel => 'Klucz publiczny';
|
||||
|
||||
@override
|
||||
String get alreadyInContacts => 'Już w kontaktach';
|
||||
|
||||
@override
|
||||
String get connectToADeviceBeforeAddingContacts =>
|
||||
'Połącz się z urządzeniem przed dodaniem kontaktów';
|
||||
|
||||
@override
|
||||
String get fromContacts => 'Z kontaktów';
|
||||
|
||||
@override
|
||||
String get onlineOnly => 'Tylko online';
|
||||
|
||||
@override
|
||||
String get inBoth => 'W obu';
|
||||
|
||||
@override
|
||||
String get source => 'Źródło';
|
||||
|
||||
@override
|
||||
String get manualRouteEdit => 'Ręczna edycja trasy';
|
||||
|
||||
@override
|
||||
String get observedMeshRoute => 'Obserwowana trasa sieci';
|
||||
|
||||
@override
|
||||
String get allMessagesCleared => 'Wszystkie wiadomości wyczyszczone';
|
||||
|
||||
@override
|
||||
String get onlineTraceDatabaseCleared => 'Baza śladów wyczyszczona';
|
||||
|
||||
@override
|
||||
String get packetLogsCleared => 'Logi pakietów wyczyszczone';
|
||||
|
||||
@override
|
||||
String get hexDataCopiedToClipboard => 'Dane hex skopiowane do schowka';
|
||||
|
||||
@override
|
||||
String get developerModeEnabled => 'Tryb programisty włączony';
|
||||
|
||||
@override
|
||||
String get developerModeDisabled => 'Tryb programisty wyłączony';
|
||||
|
||||
@override
|
||||
String get clipboardIsEmpty => 'Schowek jest pusty';
|
||||
|
||||
@override
|
||||
String get contactImported => 'Kontakt zaimportowany';
|
||||
|
||||
@override
|
||||
String get contactLinkCopiedToClipboard =>
|
||||
'Link kontaktu skopiowany do schowka';
|
||||
|
||||
@override
|
||||
String get failedToExportContact => 'Nie udało się wyeksportować kontaktu';
|
||||
|
||||
@override
|
||||
String get noLogsToExport => 'Brak logów do eksportu';
|
||||
|
||||
@override
|
||||
String get exportAsCsv => 'Eksportuj jako CSV';
|
||||
|
||||
@override
|
||||
String get exportAsText => 'Eksportuj jako tekst';
|
||||
|
||||
@override
|
||||
String get receivedRfc3339 => 'Odebrano (RFC3339)';
|
||||
|
||||
@override
|
||||
String get buildTime => 'Czas budowania';
|
||||
|
||||
@override
|
||||
String get downloadUrlNotAvailable => 'URL pobierania niedostępny';
|
||||
|
||||
@override
|
||||
String get cannotOpenDownloadUrl => 'Nie można otworzyć URL pobierania';
|
||||
|
||||
@override
|
||||
String get updateCheckIsOnlyAvailableOnAndroid =>
|
||||
'Sprawdzanie aktualizacji dostępne tylko na Androidzie';
|
||||
|
||||
@override
|
||||
String get youAreRunningTheLatestVersion => 'Używasz najnowszej wersji';
|
||||
|
||||
@override
|
||||
String get updateAvailableButDownloadUrlNotFound =>
|
||||
'Aktualizacja dostępna, ale URL pobierania nie znaleziony';
|
||||
|
||||
@override
|
||||
String get startTictactoe => 'Rozpocznij Tic-Tac-Toe';
|
||||
|
||||
@override
|
||||
String get tictactoeUnavailable => 'Tic-Tac-Toe niedostępny';
|
||||
|
||||
@override
|
||||
String get tictactoeOpponentUnknown => 'Tic-Tac-Toe: przeciwnik nieznany';
|
||||
|
||||
@override
|
||||
String get tictactoeWaitingForStart => 'Tic-Tac-Toe: oczekiwanie na start';
|
||||
|
||||
@override
|
||||
String get acceptsShareLinks => 'Akceptuje udostępnione linki';
|
||||
|
||||
@override
|
||||
String get supportsRawHex => 'Obsługuje surowy hex';
|
||||
|
||||
@override
|
||||
String get clipboardfriendly => 'Przyjazne dla schowka';
|
||||
|
||||
@override
|
||||
String get captured => 'Przechwycone';
|
||||
|
||||
@override
|
||||
String get size => 'Rozmiar';
|
||||
|
||||
@override
|
||||
String get noCustomChannelsToClear =>
|
||||
'Brak niestandardowych kanałów do wyczyszczenia.';
|
||||
|
||||
@override
|
||||
String get noDeviceContactsToClear =>
|
||||
'Brak kontaktów urządzenia do wyczyszczenia.';
|
||||
}
|
||||
|
||||
@@ -1736,4 +1736,968 @@ class AppLocalizationsPt extends AppLocalizations {
|
||||
|
||||
@override
|
||||
String get requestingVoice => 'Solicitando voz';
|
||||
|
||||
@override
|
||||
String get device => 'dispositivo';
|
||||
|
||||
@override
|
||||
String get change => 'Alterar';
|
||||
|
||||
@override
|
||||
String get wizardOverviewDescription =>
|
||||
'Este aplicativo reúne mensagens MeshCore, atualizações SAR em campo, mapas e ferramentas do dispositivo em um só lugar.';
|
||||
|
||||
@override
|
||||
String get wizardOverviewFeature1 =>
|
||||
'Envie mensagens diretas, publicações em salas e mensagens de canal pela guia principal Mensagens.';
|
||||
|
||||
@override
|
||||
String get wizardOverviewFeature2 =>
|
||||
'Compartilhe marcadores SAR, desenhos de mapa, clipes de voz e imagens pela malha.';
|
||||
|
||||
@override
|
||||
String get wizardOverviewFeature3 =>
|
||||
'Conecte-se via BLE ou TCP e depois gerencie o rádio complementar dentro do aplicativo.';
|
||||
|
||||
@override
|
||||
String get wizardMessagingTitle => 'Mensagens e relatórios de campo';
|
||||
|
||||
@override
|
||||
String get wizardMessagingDescription =>
|
||||
'Aqui, as mensagens são mais do que texto simples. O aplicativo já oferece suporte a várias cargas operacionais e fluxos de transferência.';
|
||||
|
||||
@override
|
||||
String get wizardMessagingFeature1 =>
|
||||
'Envie mensagens diretas, publicações em salas e tráfego de canal a partir de um único compositor.';
|
||||
|
||||
@override
|
||||
String get wizardMessagingFeature2 =>
|
||||
'Crie atualizações SAR e modelos SAR reutilizáveis para relatórios de campo comuns.';
|
||||
|
||||
@override
|
||||
String get wizardMessagingFeature3 =>
|
||||
'Transfira sessões de voz e imagens com progresso e estimativas de tempo de transmissão na interface.';
|
||||
|
||||
@override
|
||||
String get wizardConnectDeviceTitle => 'Conectar dispositivo';
|
||||
|
||||
@override
|
||||
String get wizardConnectDeviceDescription =>
|
||||
'Conecte seu rádio MeshCore, escolha um nome e aplique uma predefinição de rádio antes de continuar.';
|
||||
|
||||
@override
|
||||
String get wizardSetupBadge => 'Configuração';
|
||||
|
||||
@override
|
||||
String get wizardOverviewBadge => 'Visão geral';
|
||||
|
||||
@override
|
||||
String wizardConnectedToDevice(String deviceName) {
|
||||
return 'Conectado a $deviceName';
|
||||
}
|
||||
|
||||
@override
|
||||
String get wizardNoDeviceConnected => 'Nenhum dispositivo conectado ainda';
|
||||
|
||||
@override
|
||||
String get wizardSkipForNow => 'Ignorar por enquanto';
|
||||
|
||||
@override
|
||||
String get wizardDeviceNameLabel => 'Nome do dispositivo';
|
||||
|
||||
@override
|
||||
String get wizardDeviceNameHelp =>
|
||||
'Esse nome é anunciado para outros usuários do MeshCore.';
|
||||
|
||||
@override
|
||||
String get wizardConfigRegionLabel => 'Região de configuração';
|
||||
|
||||
@override
|
||||
String get wizardConfigRegionHelp =>
|
||||
'Usa a lista oficial completa de predefinições do MeshCore. O padrão é EU/UK (Narrow).';
|
||||
|
||||
@override
|
||||
String get wizardPresetNote1 =>
|
||||
'Certifique-se de que a predefinição selecionada corresponda às regulamentações locais de rádio.';
|
||||
|
||||
@override
|
||||
String get wizardPresetNote2 =>
|
||||
'A lista corresponde ao feed oficial de predefinições da ferramenta de configuração do MeshCore.';
|
||||
|
||||
@override
|
||||
String get wizardPresetNote3 =>
|
||||
'EU/UK (Narrow) permanece selecionado por padrão durante a configuração inicial.';
|
||||
|
||||
@override
|
||||
String get wizardSaving => 'Salvando...';
|
||||
|
||||
@override
|
||||
String get wizardSaveAndContinue => 'Salvar e continuar';
|
||||
|
||||
@override
|
||||
String get wizardEnterDeviceName =>
|
||||
'Digite um nome para o dispositivo antes de continuar.';
|
||||
|
||||
@override
|
||||
String wizardDeviceSetupSaved(String deviceName, String presetName) {
|
||||
return '$deviceName salvo com $presetName.';
|
||||
}
|
||||
|
||||
@override
|
||||
String get wizardNetworkTitle => 'Contatos, salas e repetidores';
|
||||
|
||||
@override
|
||||
String get wizardNetworkDescription =>
|
||||
'A guia Contatos organiza a rede que você descobre e as rotas aprendidas ao longo do tempo.';
|
||||
|
||||
@override
|
||||
String get wizardNetworkFeature1 =>
|
||||
'Revise membros da equipe, repetidores, salas, canais e anúncios pendentes em uma única lista.';
|
||||
|
||||
@override
|
||||
String get wizardNetworkFeature2 =>
|
||||
'Use ping inteligente, login em salas, caminhos aprendidos e ferramentas de redefinição de rota quando a conectividade ficar confusa.';
|
||||
|
||||
@override
|
||||
String get wizardNetworkFeature3 =>
|
||||
'Crie canais e gerencie destinos de rede sem sair do aplicativo.';
|
||||
|
||||
@override
|
||||
String get wizardMapOpsTitle => 'Mapa, trilhas e geometria compartilhada';
|
||||
|
||||
@override
|
||||
String get wizardMapOpsDescription =>
|
||||
'O mapa do aplicativo está ligado diretamente às mensagens, ao rastreamento e às sobreposições SAR, em vez de ser um visualizador separado.';
|
||||
|
||||
@override
|
||||
String get wizardMapOpsFeature1 =>
|
||||
'Acompanhe sua própria posição, as localizações da equipe e as trilhas de movimento no mapa.';
|
||||
|
||||
@override
|
||||
String get wizardMapOpsFeature2 =>
|
||||
'Abra desenhos de mensagens, visualize-os em linha e remova-os do mapa quando necessário.';
|
||||
|
||||
@override
|
||||
String get wizardMapOpsFeature3 =>
|
||||
'Use vistas de mapa de repetidores e sobreposições compartilhadas para entender o alcance da rede em campo.';
|
||||
|
||||
@override
|
||||
String get wizardToolsTitle => 'Ferramentas além das mensagens';
|
||||
|
||||
@override
|
||||
String get wizardToolsDescription =>
|
||||
'Há mais aqui do que as quatro guias principais. O aplicativo também inclui configuração, diagnóstico e fluxos opcionais de sensores.';
|
||||
|
||||
@override
|
||||
String get wizardToolsFeature1 =>
|
||||
'Abra a configuração do dispositivo para alterar ajustes de rádio, telemetria, potência TX e detalhes do equipamento complementar.';
|
||||
|
||||
@override
|
||||
String get wizardToolsFeature2 =>
|
||||
'Ative a guia Sensores quando quiser painéis monitorados e ações de atualização rápida.';
|
||||
|
||||
@override
|
||||
String get wizardToolsFeature3 =>
|
||||
'Use logs de pacotes, varredura de espectro e diagnóstico de desenvolvedor ao solucionar problemas da malha.';
|
||||
|
||||
@override
|
||||
String get contactInSensors => 'Nos Sensores';
|
||||
|
||||
@override
|
||||
String get contactAddToSensors => 'Adicionar aos Sensores';
|
||||
|
||||
@override
|
||||
String get contactSetPath => 'Definir rota';
|
||||
|
||||
@override
|
||||
String contactAddedToSensors(String contactName) {
|
||||
return '$contactName adicionado a Sensores';
|
||||
}
|
||||
|
||||
@override
|
||||
String contactFailedToClearRoute(String error) {
|
||||
return 'Falha ao limpar a rota: $error';
|
||||
}
|
||||
|
||||
@override
|
||||
String get contactRouteCleared => 'Rota limpa';
|
||||
|
||||
@override
|
||||
String contactRouteSet(String route) {
|
||||
return 'Rota definida: $route';
|
||||
}
|
||||
|
||||
@override
|
||||
String contactFailedToSetRoute(String error) {
|
||||
return 'Falha ao definir a rota: $error';
|
||||
}
|
||||
|
||||
@override
|
||||
String get rssi => 'RSSI';
|
||||
|
||||
@override
|
||||
String get snr => 'SNR';
|
||||
|
||||
@override
|
||||
String get ackTimeout => 'Tempo limite ACK';
|
||||
|
||||
@override
|
||||
String get opcode => 'Opcode';
|
||||
|
||||
@override
|
||||
String get payload => 'Carga útil';
|
||||
|
||||
@override
|
||||
String get hops => 'Saltos';
|
||||
|
||||
@override
|
||||
String get hashSize => 'Tamanho do hash';
|
||||
|
||||
@override
|
||||
String get pathBytes => 'Bytes do caminho';
|
||||
|
||||
@override
|
||||
String get selectedPath => 'Caminho selecionado';
|
||||
|
||||
@override
|
||||
String get estimatedTx => 'Transmissão estimada';
|
||||
|
||||
@override
|
||||
String get senderToReceipt => 'Do remetente ao recibo';
|
||||
|
||||
@override
|
||||
String get receivedCopies => 'Cópias recebidas';
|
||||
|
||||
@override
|
||||
String get retryCause => 'Causa da nova tentativa';
|
||||
|
||||
@override
|
||||
String get retryMode => 'Modo de nova tentativa';
|
||||
|
||||
@override
|
||||
String get retryResult => 'Resultado da nova tentativa';
|
||||
|
||||
@override
|
||||
String get lastRetry => 'Última tentativa';
|
||||
|
||||
@override
|
||||
String get rxPackets => 'Pacotes RX';
|
||||
|
||||
@override
|
||||
String get mesh => 'Mesh';
|
||||
|
||||
@override
|
||||
String get rate => 'Taxa';
|
||||
|
||||
@override
|
||||
String get window => 'Janela';
|
||||
|
||||
@override
|
||||
String get posttxDelay => 'Atraso pós-tx';
|
||||
|
||||
@override
|
||||
String get bandpass => 'Passa-banda';
|
||||
|
||||
@override
|
||||
String get bandpassFilterVoice => 'Filtro passa-banda de voz';
|
||||
|
||||
@override
|
||||
String get frequency => 'Frequência';
|
||||
|
||||
@override
|
||||
String get australia => 'Austrália';
|
||||
|
||||
@override
|
||||
String get australiaNarrow => 'Austrália (Estreito)';
|
||||
|
||||
@override
|
||||
String get australiaQld => 'Austrália: QLD';
|
||||
|
||||
@override
|
||||
String get australiaSaWa => 'Austrália: SA, WA';
|
||||
|
||||
@override
|
||||
String get newZealand => 'Nova Zelândia';
|
||||
|
||||
@override
|
||||
String get newZealandNarrow => 'Nova Zelândia (Estreito)';
|
||||
|
||||
@override
|
||||
String get switzerland => 'Suíça';
|
||||
|
||||
@override
|
||||
String get portugal433 => 'Portugal 433';
|
||||
|
||||
@override
|
||||
String get portugal868 => 'Portugal 868';
|
||||
|
||||
@override
|
||||
String get czechRepublicNarrow => 'República Checa (Estreito)';
|
||||
|
||||
@override
|
||||
String get eu433mhzLongRange => 'UE 433MHz (Longo Alcance)';
|
||||
|
||||
@override
|
||||
String get euukDeprecated => 'UE/RU (Obsoleto)';
|
||||
|
||||
@override
|
||||
String get euukNarrow => 'UE/RU (Estreito)';
|
||||
|
||||
@override
|
||||
String get usacanadaRecommended => 'EUA/Canadá (Recomendado)';
|
||||
|
||||
@override
|
||||
String get vietnamDeprecated => 'Vietname (Obsoleto)';
|
||||
|
||||
@override
|
||||
String get vietnamNarrow => 'Vietname (Estreito)';
|
||||
|
||||
@override
|
||||
String get active => 'Ativo';
|
||||
|
||||
@override
|
||||
String get addContact => 'Adicionar contacto';
|
||||
|
||||
@override
|
||||
String get all => 'Tudo';
|
||||
|
||||
@override
|
||||
String get autoResolve => 'Resolver automaticamente';
|
||||
|
||||
@override
|
||||
String get clearAllLabel => 'Limpar tudo';
|
||||
|
||||
@override
|
||||
String get clearRelays => 'Limpar relays';
|
||||
|
||||
@override
|
||||
String get clearFilters => 'Limpar filtros';
|
||||
|
||||
@override
|
||||
String get clearRoute => 'Limpar rota';
|
||||
|
||||
@override
|
||||
String get clearMessages => 'Limpar mensagens';
|
||||
|
||||
@override
|
||||
String get clearScale => 'Limpar escala';
|
||||
|
||||
@override
|
||||
String get clearDiscoveries => 'Limpar descobertas';
|
||||
|
||||
@override
|
||||
String get clearOnlineTraceDatabase => 'Limpar base de dados de rastreio';
|
||||
|
||||
@override
|
||||
String get clearAllChannels => 'Limpar todos os canais';
|
||||
|
||||
@override
|
||||
String get clearAllContacts => 'Limpar todos os contactos';
|
||||
|
||||
@override
|
||||
String get clearChannels => 'Limpar canais';
|
||||
|
||||
@override
|
||||
String get clearContacts => 'Limpar contactos';
|
||||
|
||||
@override
|
||||
String get clearPathOnMaxRetry => 'Limpar caminho no máx. de tentativas';
|
||||
|
||||
@override
|
||||
String get create => 'Criar';
|
||||
|
||||
@override
|
||||
String get custom => 'Personalizado';
|
||||
|
||||
@override
|
||||
String get defaultValue => 'Padrão';
|
||||
|
||||
@override
|
||||
String get duplicate => 'Duplicar';
|
||||
|
||||
@override
|
||||
String get editName => 'Editar nome';
|
||||
|
||||
@override
|
||||
String get open => 'Abrir';
|
||||
|
||||
@override
|
||||
String get paste => 'Colar';
|
||||
|
||||
@override
|
||||
String get preview => 'Pré-visualização';
|
||||
|
||||
@override
|
||||
String get remove => 'Remover';
|
||||
|
||||
@override
|
||||
String get rename => 'Renomear';
|
||||
|
||||
@override
|
||||
String get resolveAll => 'Resolver tudo';
|
||||
|
||||
@override
|
||||
String get send => 'Enviar';
|
||||
|
||||
@override
|
||||
String get sendAnyway => 'Enviar mesmo assim';
|
||||
|
||||
@override
|
||||
String get share => 'Partilhar';
|
||||
|
||||
@override
|
||||
String get shareContact => 'Partilhar contacto';
|
||||
|
||||
@override
|
||||
String get trace => 'Rastreio';
|
||||
|
||||
@override
|
||||
String get use => 'Usar';
|
||||
|
||||
@override
|
||||
String get useSelectedFrequency => 'Usar frequência selecionada';
|
||||
|
||||
@override
|
||||
String get discovery => 'Descoberta';
|
||||
|
||||
@override
|
||||
String get discoverRepeaters => 'Descobrir repetidores';
|
||||
|
||||
@override
|
||||
String get discoverSensors => 'Descobrir sensores';
|
||||
|
||||
@override
|
||||
String get repeaterDiscoverySent => 'Descoberta de repetidores enviada';
|
||||
|
||||
@override
|
||||
String get sensorDiscoverySent => 'Descoberta de sensores enviada';
|
||||
|
||||
@override
|
||||
String get clearedPendingDiscoveries => 'Descobertas pendentes limpas.';
|
||||
|
||||
@override
|
||||
String get autoDiscovery => 'Descoberta automática';
|
||||
|
||||
@override
|
||||
String get enableAutomaticAdding => 'Ativar adição automática';
|
||||
|
||||
@override
|
||||
String get autoaddRepeaters => 'Adicionar repetidores automaticamente';
|
||||
|
||||
@override
|
||||
String get autoaddRoomServers =>
|
||||
'Adicionar servidores de sala automaticamente';
|
||||
|
||||
@override
|
||||
String get autoaddSensors => 'Adicionar sensores automaticamente';
|
||||
|
||||
@override
|
||||
String get autoaddUsers => 'Adicionar utilizadores automaticamente';
|
||||
|
||||
@override
|
||||
String get overwriteOldestWhenFull =>
|
||||
'Sobrescrever mais antigos quando cheio';
|
||||
|
||||
@override
|
||||
String get storage => 'Armazenamento';
|
||||
|
||||
@override
|
||||
String get dangerZone => 'Zona de perigo';
|
||||
|
||||
@override
|
||||
String get profiles => 'Perfis';
|
||||
|
||||
@override
|
||||
String get favourites => 'Favoritos';
|
||||
|
||||
@override
|
||||
String get sensors => 'Sensores';
|
||||
|
||||
@override
|
||||
String get others => 'Outros';
|
||||
|
||||
@override
|
||||
String get gpsModule => 'Módulo GPS';
|
||||
|
||||
@override
|
||||
String get liveTraffic => 'Tráfego ao vivo';
|
||||
|
||||
@override
|
||||
String get repeatersMap => 'Mapa de repetidores';
|
||||
|
||||
@override
|
||||
String get spectrumScan => 'Varredura de espectro';
|
||||
|
||||
@override
|
||||
String get blePacketLogs => 'Registos de pacotes BLE';
|
||||
|
||||
@override
|
||||
String get onlineTraceDatabase => 'Base de dados de rastreio';
|
||||
|
||||
@override
|
||||
String get routePathByteSize => 'Tamanho do caminho em bytes';
|
||||
|
||||
@override
|
||||
String get messageNotifications => 'Notificações de mensagens';
|
||||
|
||||
@override
|
||||
String get sarAlerts => 'Alertas SAR';
|
||||
|
||||
@override
|
||||
String get discoveryNotifications => 'Notificações de descoberta';
|
||||
|
||||
@override
|
||||
String get updateNotifications => 'Notificações de atualização';
|
||||
|
||||
@override
|
||||
String get muteWhileAppIsOpen => 'Silenciar com a app aberta';
|
||||
|
||||
@override
|
||||
String get disableContacts => 'Desativar contactos';
|
||||
|
||||
@override
|
||||
String get enableSensorsTab => 'Ativar separador Sensores';
|
||||
|
||||
@override
|
||||
String get enableProfiles => 'Ativar perfis';
|
||||
|
||||
@override
|
||||
String get autoRouteRotation => 'Rotação automática de rota';
|
||||
|
||||
@override
|
||||
String get nearestRepeaterFallback => 'Repetidor mais próximo como reserva';
|
||||
|
||||
@override
|
||||
String get deleteAllStoredMessageHistory =>
|
||||
'Apagar todo o histórico de mensagens';
|
||||
|
||||
@override
|
||||
String get messageFontSize => 'Tamanho da fonte de mensagens';
|
||||
|
||||
@override
|
||||
String get rotateMapWithHeading => 'Rodar mapa com direção';
|
||||
|
||||
@override
|
||||
String get showMapDebugInfo => 'Mostrar info de depuração do mapa';
|
||||
|
||||
@override
|
||||
String get openMapInFullscreen => 'Abrir mapa em ecrã inteiro';
|
||||
|
||||
@override
|
||||
String get showSarMarkersLabel => 'Mostrar marcadores SAR';
|
||||
|
||||
@override
|
||||
String get displaySarMarkersOnTheMainMap =>
|
||||
'Mostrar marcadores SAR no mapa principal';
|
||||
|
||||
@override
|
||||
String get showAllContactTrailsLabel =>
|
||||
'Mostrar todos os rastos de contactos';
|
||||
|
||||
@override
|
||||
String get hideRepeatersOnMap => 'Ocultar repetidores no mapa';
|
||||
|
||||
@override
|
||||
String get setMapScale => 'Definir escala do mapa';
|
||||
|
||||
@override
|
||||
String get customMapScaleSaved => 'Escala de mapa personalizada guardada';
|
||||
|
||||
@override
|
||||
String get voiceBitrate => 'Taxa de bits de voz';
|
||||
|
||||
@override
|
||||
String get voiceCompressor => 'Compressor de voz';
|
||||
|
||||
@override
|
||||
String get balancesQuietAndLoudSpeechLevels =>
|
||||
'Equilibra níveis de fala suaves e altos';
|
||||
|
||||
@override
|
||||
String get voiceLimiter => 'Limitador de voz';
|
||||
|
||||
@override
|
||||
String get preventsClippingPeaksBeforeEncoding =>
|
||||
'Previne corte de picos antes da codificação';
|
||||
|
||||
@override
|
||||
String get micAutoGain => 'Ganho automático do microfone';
|
||||
|
||||
@override
|
||||
String get letsTheRecorderAdjustInputLevel =>
|
||||
'Permite ao gravador ajustar o nível de entrada';
|
||||
|
||||
@override
|
||||
String get echoCancellation => 'Cancelamento de eco';
|
||||
|
||||
@override
|
||||
String get noiseSuppression => 'Supressão de ruído';
|
||||
|
||||
@override
|
||||
String get trimSilenceInVoiceMessages =>
|
||||
'Cortar silêncio em mensagens de voz';
|
||||
|
||||
@override
|
||||
String get compressor => 'Compressor';
|
||||
|
||||
@override
|
||||
String get limiter => 'Limitador';
|
||||
|
||||
@override
|
||||
String get autoGain => 'Ganho automático';
|
||||
|
||||
@override
|
||||
String get echoCancel => 'Eco';
|
||||
|
||||
@override
|
||||
String get noiseSuppress => 'Ruído';
|
||||
|
||||
@override
|
||||
String get silenceTrim => 'Silêncio';
|
||||
|
||||
@override
|
||||
String get maxImageSize => 'Tamanho máximo de imagem';
|
||||
|
||||
@override
|
||||
String get imageCompression => 'Compressão de imagem';
|
||||
|
||||
@override
|
||||
String get grayscale => 'Escala de cinzentos';
|
||||
|
||||
@override
|
||||
String get ultraMode => 'Modo ultra';
|
||||
|
||||
@override
|
||||
String get fastPrivateGpsUpdates => 'Atualizações GPS privadas rápidas';
|
||||
|
||||
@override
|
||||
String get movementThreshold => 'Limiar de movimento';
|
||||
|
||||
@override
|
||||
String get fastGpsMovementThreshold => 'Limiar de movimento GPS rápido';
|
||||
|
||||
@override
|
||||
String get fastGpsActiveuseInterval => 'Intervalo de uso ativo GPS rápido';
|
||||
|
||||
@override
|
||||
String get activeuseUpdateInterval => 'Intervalo de atualização de uso ativo';
|
||||
|
||||
@override
|
||||
String get repeatNearbyTraffic => 'Repetir tráfego próximo';
|
||||
|
||||
@override
|
||||
String get relayThroughRepeatersAcrossTheMesh =>
|
||||
'Retransmitir através de repetidores na rede';
|
||||
|
||||
@override
|
||||
String get nearbyOnlyWithoutRepeaterFlooding =>
|
||||
'Apenas próximos, sem flood de repetidores';
|
||||
|
||||
@override
|
||||
String get multihop => 'Multi-salto';
|
||||
|
||||
@override
|
||||
String get createProfile => 'Criar perfil';
|
||||
|
||||
@override
|
||||
String get renameProfile => 'Renomear perfil';
|
||||
|
||||
@override
|
||||
String get newProfile => 'Novo perfil';
|
||||
|
||||
@override
|
||||
String get manageProfiles => 'Gerir perfis';
|
||||
|
||||
@override
|
||||
String get enableProfilesToStartManagingThem =>
|
||||
'Ative os perfis para começar a geri-los.';
|
||||
|
||||
@override
|
||||
String get openMessage => 'Abrir mensagem';
|
||||
|
||||
@override
|
||||
String get jumpToTheRelatedSarMessage => 'Ir para a mensagem SAR relacionada';
|
||||
|
||||
@override
|
||||
String get removeSarMarker => 'Remover marcador SAR';
|
||||
|
||||
@override
|
||||
String get pleaseSelectADestinationToSendSarMarker =>
|
||||
'Selecione um destino para enviar o marcador SAR';
|
||||
|
||||
@override
|
||||
String get sarMarkerBroadcastToPublicChannel =>
|
||||
'Marcador SAR transmitido no canal público';
|
||||
|
||||
@override
|
||||
String get sarMarkerSentToRoom => 'Marcador SAR enviado para a sala';
|
||||
|
||||
@override
|
||||
String get loadFromGallery => 'Carregar da galeria';
|
||||
|
||||
@override
|
||||
String get replaceImage => 'Substituir imagem';
|
||||
|
||||
@override
|
||||
String get selectFromGallery => 'Selecionar da galeria';
|
||||
|
||||
@override
|
||||
String get team => 'Equipa';
|
||||
|
||||
@override
|
||||
String get found => 'Encontrado';
|
||||
|
||||
@override
|
||||
String get staging => 'Área de reunião';
|
||||
|
||||
@override
|
||||
String get object => 'Objeto';
|
||||
|
||||
@override
|
||||
String get quiet => 'Silencioso';
|
||||
|
||||
@override
|
||||
String get moderate => 'Moderado';
|
||||
|
||||
@override
|
||||
String get busy => 'Ocupado';
|
||||
|
||||
@override
|
||||
String get spectrumScanReturnedNoCandidateFrequencies =>
|
||||
'A varredura de espectro não encontrou frequências candidatas';
|
||||
|
||||
@override
|
||||
String get searchMessages => 'Pesquisar mensagens';
|
||||
|
||||
@override
|
||||
String get sendImageFromGallery => 'Enviar imagem da galeria';
|
||||
|
||||
@override
|
||||
String get takePhoto => 'Tirar foto';
|
||||
|
||||
@override
|
||||
String get dmOnly => 'Apenas mensagem direta';
|
||||
|
||||
@override
|
||||
String get allMessages => 'Todas as mensagens';
|
||||
|
||||
@override
|
||||
String get sendToPublicChannel => 'Enviar para o canal público?';
|
||||
|
||||
@override
|
||||
String get selectMarkerTypeAndDestination =>
|
||||
'Selecione tipo de marcador e destino';
|
||||
|
||||
@override
|
||||
String get noDestinationsAvailableLabel => 'Nenhum destino disponível';
|
||||
|
||||
@override
|
||||
String get image => 'Imagem';
|
||||
|
||||
@override
|
||||
String get format => 'Formato';
|
||||
|
||||
@override
|
||||
String get dimensions => 'Dimensões';
|
||||
|
||||
@override
|
||||
String get segments => 'Segmentos';
|
||||
|
||||
@override
|
||||
String get transfers => 'Transferências';
|
||||
|
||||
@override
|
||||
String get downloadedBy => 'Transferido por';
|
||||
|
||||
@override
|
||||
String get saveDiscoverySettings => 'Guardar definições de descoberta';
|
||||
|
||||
@override
|
||||
String get savePublicInfo => 'Guardar informação pública';
|
||||
|
||||
@override
|
||||
String get saveRadioSettings => 'Guardar definições de rádio';
|
||||
|
||||
@override
|
||||
String get savePath => 'Guardar caminho';
|
||||
|
||||
@override
|
||||
String get wipeDeviceData => 'Apagar dados do dispositivo';
|
||||
|
||||
@override
|
||||
String get wipeDevice => 'Apagar dispositivo';
|
||||
|
||||
@override
|
||||
String get destructiveDeviceActions => 'Ações destrutivas do dispositivo.';
|
||||
|
||||
@override
|
||||
String get chooseAPresetOrFinetuneCustomRadioSettings =>
|
||||
'Escolha uma predefinição ou ajuste as definições de rádio.';
|
||||
|
||||
@override
|
||||
String get chooseTheNameAndLocationThisDeviceShares =>
|
||||
'Escolha o nome e a localização que este dispositivo partilha.';
|
||||
|
||||
@override
|
||||
String get availableSpaceOnThisDevice =>
|
||||
'Espaço disponível neste dispositivo.';
|
||||
|
||||
@override
|
||||
String get used => 'Usado';
|
||||
|
||||
@override
|
||||
String get total => 'Total';
|
||||
|
||||
@override
|
||||
String get renameValue => 'Renomear valor';
|
||||
|
||||
@override
|
||||
String get customizeFields => 'Personalizar campos';
|
||||
|
||||
@override
|
||||
String get livePreview => 'Pré-visualização ao vivo';
|
||||
|
||||
@override
|
||||
String get refreshSchedule => 'Agenda de atualização';
|
||||
|
||||
@override
|
||||
String get noResponse => 'Sem resposta';
|
||||
|
||||
@override
|
||||
String get refreshing => 'A atualizar';
|
||||
|
||||
@override
|
||||
String get unavailable => 'Indisponível';
|
||||
|
||||
@override
|
||||
String get pickARelayOrNodeToWatchInSensors =>
|
||||
'Escolha um relay ou nó para observar.';
|
||||
|
||||
@override
|
||||
String get publicKeyLabel => 'Chave pública';
|
||||
|
||||
@override
|
||||
String get alreadyInContacts => 'Já nos contactos';
|
||||
|
||||
@override
|
||||
String get connectToADeviceBeforeAddingContacts =>
|
||||
'Conecte-se a um dispositivo antes de adicionar contactos';
|
||||
|
||||
@override
|
||||
String get fromContacts => 'Dos contactos';
|
||||
|
||||
@override
|
||||
String get onlineOnly => 'Apenas online';
|
||||
|
||||
@override
|
||||
String get inBoth => 'Em ambos';
|
||||
|
||||
@override
|
||||
String get source => 'Fonte';
|
||||
|
||||
@override
|
||||
String get manualRouteEdit => 'Edição manual de rota';
|
||||
|
||||
@override
|
||||
String get observedMeshRoute => 'Rota mesh observada';
|
||||
|
||||
@override
|
||||
String get allMessagesCleared => 'Todas as mensagens limpas';
|
||||
|
||||
@override
|
||||
String get onlineTraceDatabaseCleared => 'Base de dados de rastreio limpa';
|
||||
|
||||
@override
|
||||
String get packetLogsCleared => 'Registos de pacotes limpos';
|
||||
|
||||
@override
|
||||
String get hexDataCopiedToClipboard =>
|
||||
'Dados hex copiados para a área de transferência';
|
||||
|
||||
@override
|
||||
String get developerModeEnabled => 'Modo de programador ativado';
|
||||
|
||||
@override
|
||||
String get developerModeDisabled => 'Modo de programador desativado';
|
||||
|
||||
@override
|
||||
String get clipboardIsEmpty => 'A área de transferência está vazia';
|
||||
|
||||
@override
|
||||
String get contactImported => 'Contacto importado';
|
||||
|
||||
@override
|
||||
String get contactLinkCopiedToClipboard =>
|
||||
'Link do contacto copiado para a área de transferência';
|
||||
|
||||
@override
|
||||
String get failedToExportContact => 'Falha ao exportar contacto';
|
||||
|
||||
@override
|
||||
String get noLogsToExport => 'Sem registos para exportar';
|
||||
|
||||
@override
|
||||
String get exportAsCsv => 'Exportar como CSV';
|
||||
|
||||
@override
|
||||
String get exportAsText => 'Exportar como texto';
|
||||
|
||||
@override
|
||||
String get receivedRfc3339 => 'Recebido (RFC3339)';
|
||||
|
||||
@override
|
||||
String get buildTime => 'Hora de compilação';
|
||||
|
||||
@override
|
||||
String get downloadUrlNotAvailable => 'URL de download não disponível';
|
||||
|
||||
@override
|
||||
String get cannotOpenDownloadUrl => 'Não é possível abrir o URL de download';
|
||||
|
||||
@override
|
||||
String get updateCheckIsOnlyAvailableOnAndroid =>
|
||||
'A verificação de atualizações só está disponível no Android';
|
||||
|
||||
@override
|
||||
String get youAreRunningTheLatestVersion =>
|
||||
'Está a usar a versão mais recente';
|
||||
|
||||
@override
|
||||
String get updateAvailableButDownloadUrlNotFound =>
|
||||
'Atualização disponível mas URL de download não encontrado';
|
||||
|
||||
@override
|
||||
String get startTictactoe => 'Iniciar Tic-Tac-Toe';
|
||||
|
||||
@override
|
||||
String get tictactoeUnavailable => 'Tic-Tac-Toe indisponível';
|
||||
|
||||
@override
|
||||
String get tictactoeOpponentUnknown => 'Tic-Tac-Toe: oponente desconhecido';
|
||||
|
||||
@override
|
||||
String get tictactoeWaitingForStart => 'Tic-Tac-Toe: a aguardar início';
|
||||
|
||||
@override
|
||||
String get acceptsShareLinks => 'Aceita links partilhados';
|
||||
|
||||
@override
|
||||
String get supportsRawHex => 'Suporta hex bruto';
|
||||
|
||||
@override
|
||||
String get clipboardfriendly => 'Compatível com a área de transferência';
|
||||
|
||||
@override
|
||||
String get captured => 'Capturado';
|
||||
|
||||
@override
|
||||
String get size => 'Tamanho';
|
||||
|
||||
@override
|
||||
String get noCustomChannelsToClear =>
|
||||
'Sem canais personalizados para limpar.';
|
||||
|
||||
@override
|
||||
String get noDeviceContactsToClear =>
|
||||
'Sem contactos do dispositivo para limpar.';
|
||||
}
|
||||
|
||||
@@ -1726,4 +1726,963 @@ class AppLocalizationsRu extends AppLocalizations {
|
||||
|
||||
@override
|
||||
String get requestingVoice => 'Запрос голоса';
|
||||
|
||||
@override
|
||||
String get device => 'устройство';
|
||||
|
||||
@override
|
||||
String get change => 'Изменить';
|
||||
|
||||
@override
|
||||
String get wizardOverviewDescription =>
|
||||
'Это приложение объединяет сообщения MeshCore, полевые обновления SAR, карты и инструменты устройства в одном месте.';
|
||||
|
||||
@override
|
||||
String get wizardOverviewFeature1 =>
|
||||
'Отправляйте личные сообщения, сообщения комнат и сообщения каналов из основной вкладки «Сообщения».';
|
||||
|
||||
@override
|
||||
String get wizardOverviewFeature2 =>
|
||||
'Делитесь SAR-маркерами, рисунками на карте, голосовыми клипами и изображениями через mesh-сеть.';
|
||||
|
||||
@override
|
||||
String get wizardOverviewFeature3 =>
|
||||
'Подключайтесь по BLE или TCP, а затем управляйте сопутствующей радиостанцией прямо из приложения.';
|
||||
|
||||
@override
|
||||
String get wizardMessagingTitle => 'Сообщения и полевые отчёты';
|
||||
|
||||
@override
|
||||
String get wizardMessagingDescription =>
|
||||
'Здесь сообщения это больше, чем просто текст. Приложение уже поддерживает несколько видов операционных данных и сценариев передачи.';
|
||||
|
||||
@override
|
||||
String get wizardMessagingFeature1 =>
|
||||
'Отправляйте личные сообщения, сообщения комнат и трафик каналов из одного редактора.';
|
||||
|
||||
@override
|
||||
String get wizardMessagingFeature2 =>
|
||||
'Создавайте обновления SAR и повторно используемые шаблоны SAR для типовых полевых отчётов.';
|
||||
|
||||
@override
|
||||
String get wizardMessagingFeature3 =>
|
||||
'Передавайте голосовые сессии и изображения с индикатором прогресса и оценкой эфирного времени в интерфейсе.';
|
||||
|
||||
@override
|
||||
String get wizardConnectDeviceTitle => 'Подключить устройство';
|
||||
|
||||
@override
|
||||
String get wizardConnectDeviceDescription =>
|
||||
'Подключите свою радиостанцию MeshCore, выберите имя и примените радиопрофиль перед продолжением.';
|
||||
|
||||
@override
|
||||
String get wizardSetupBadge => 'Настройка';
|
||||
|
||||
@override
|
||||
String get wizardOverviewBadge => 'Обзор';
|
||||
|
||||
@override
|
||||
String wizardConnectedToDevice(String deviceName) {
|
||||
return 'Подключено к $deviceName';
|
||||
}
|
||||
|
||||
@override
|
||||
String get wizardNoDeviceConnected => 'Устройство ещё не подключено';
|
||||
|
||||
@override
|
||||
String get wizardSkipForNow => 'Пропустить пока';
|
||||
|
||||
@override
|
||||
String get wizardDeviceNameLabel => 'Имя устройства';
|
||||
|
||||
@override
|
||||
String get wizardDeviceNameHelp =>
|
||||
'Это имя транслируется другим пользователям MeshCore.';
|
||||
|
||||
@override
|
||||
String get wizardConfigRegionLabel => 'Регион конфигурации';
|
||||
|
||||
@override
|
||||
String get wizardConfigRegionHelp =>
|
||||
'Используется полный официальный список профилей MeshCore. По умолчанию выбран EU/UK (Narrow).';
|
||||
|
||||
@override
|
||||
String get wizardPresetNote1 =>
|
||||
'Убедитесь, что выбранный профиль соответствует местным радиоправилам.';
|
||||
|
||||
@override
|
||||
String get wizardPresetNote2 =>
|
||||
'Список соответствует официальному потоку профилей инструмента MeshCore config.';
|
||||
|
||||
@override
|
||||
String get wizardPresetNote3 =>
|
||||
'Для онбординга по умолчанию остаётся выбран EU/UK (Narrow).';
|
||||
|
||||
@override
|
||||
String get wizardSaving => 'Сохранение...';
|
||||
|
||||
@override
|
||||
String get wizardSaveAndContinue => 'Сохранить и продолжить';
|
||||
|
||||
@override
|
||||
String get wizardEnterDeviceName =>
|
||||
'Введите имя устройства перед продолжением.';
|
||||
|
||||
@override
|
||||
String wizardDeviceSetupSaved(String deviceName, String presetName) {
|
||||
return 'Сохранено: $deviceName, профиль $presetName.';
|
||||
}
|
||||
|
||||
@override
|
||||
String get wizardNetworkTitle => 'Контакты, комнаты и ретрансляторы';
|
||||
|
||||
@override
|
||||
String get wizardNetworkDescription =>
|
||||
'Вкладка «Контакты» организует сеть, которую вы обнаруживаете, и маршруты, которые вы узнаёте со временем.';
|
||||
|
||||
@override
|
||||
String get wizardNetworkFeature1 =>
|
||||
'Просматривайте участников команды, ретрансляторы, комнаты, каналы и ожидающие объявления в одном списке.';
|
||||
|
||||
@override
|
||||
String get wizardNetworkFeature2 =>
|
||||
'Используйте smart ping, вход в комнаты, изученные пути и инструменты сброса маршрутов, когда связь становится нестабильной.';
|
||||
|
||||
@override
|
||||
String get wizardNetworkFeature3 =>
|
||||
'Создавайте каналы и управляйте сетевыми назначениями, не покидая приложение.';
|
||||
|
||||
@override
|
||||
String get wizardMapOpsTitle => 'Карта, следы и общая геометрия';
|
||||
|
||||
@override
|
||||
String get wizardMapOpsDescription =>
|
||||
'Карта приложения напрямую связана с сообщениями, отслеживанием и SAR-слоями, а не является отдельным просмотрщиком.';
|
||||
|
||||
@override
|
||||
String get wizardMapOpsFeature1 =>
|
||||
'Отслеживайте свою позицию, местоположение команды и траектории движения на карте.';
|
||||
|
||||
@override
|
||||
String get wizardMapOpsFeature2 =>
|
||||
'Открывайте рисунки из сообщений, просматривайте их прямо в интерфейсе и удаляйте с карты при необходимости.';
|
||||
|
||||
@override
|
||||
String get wizardMapOpsFeature3 =>
|
||||
'Используйте карты ретрансляторов и общие слои, чтобы понимать покрытие сети в поле.';
|
||||
|
||||
@override
|
||||
String get wizardToolsTitle => 'Инструменты помимо сообщений';
|
||||
|
||||
@override
|
||||
String get wizardToolsDescription =>
|
||||
'Здесь есть больше, чем четыре основные вкладки. Приложение также включает настройку, диагностику и необязательные сценарии датчиков.';
|
||||
|
||||
@override
|
||||
String get wizardToolsFeature1 =>
|
||||
'Откройте настройки устройства, чтобы изменить параметры радио, телеметрию, мощность TX и сведения о сопутствующем устройстве.';
|
||||
|
||||
@override
|
||||
String get wizardToolsFeature2 =>
|
||||
'Включите вкладку «Датчики», если нужны панели наблюдаемых датчиков и быстрые действия обновления.';
|
||||
|
||||
@override
|
||||
String get wizardToolsFeature3 =>
|
||||
'Используйте журналы пакетов, сканирование спектра и диагностику разработчика для устранения проблем mesh-сети.';
|
||||
|
||||
@override
|
||||
String get contactInSensors => 'В датчиках';
|
||||
|
||||
@override
|
||||
String get contactAddToSensors => 'Добавить в датчики';
|
||||
|
||||
@override
|
||||
String get contactSetPath => 'Задать путь';
|
||||
|
||||
@override
|
||||
String contactAddedToSensors(String contactName) {
|
||||
return '$contactName добавлен в Датчики';
|
||||
}
|
||||
|
||||
@override
|
||||
String contactFailedToClearRoute(String error) {
|
||||
return 'Не удалось очистить маршрут: $error';
|
||||
}
|
||||
|
||||
@override
|
||||
String get contactRouteCleared => 'Маршрут очищен';
|
||||
|
||||
@override
|
||||
String contactRouteSet(String route) {
|
||||
return 'Маршрут задан: $route';
|
||||
}
|
||||
|
||||
@override
|
||||
String contactFailedToSetRoute(String error) {
|
||||
return 'Не удалось задать маршрут: $error';
|
||||
}
|
||||
|
||||
@override
|
||||
String get rssi => 'RSSI';
|
||||
|
||||
@override
|
||||
String get snr => 'SNR';
|
||||
|
||||
@override
|
||||
String get ackTimeout => 'Таймаут ACK';
|
||||
|
||||
@override
|
||||
String get opcode => 'Опкод';
|
||||
|
||||
@override
|
||||
String get payload => 'Полезная нагрузка';
|
||||
|
||||
@override
|
||||
String get hops => 'Хопы';
|
||||
|
||||
@override
|
||||
String get hashSize => 'Размер хеша';
|
||||
|
||||
@override
|
||||
String get pathBytes => 'Байты пути';
|
||||
|
||||
@override
|
||||
String get selectedPath => 'Выбранный путь';
|
||||
|
||||
@override
|
||||
String get estimatedTx => 'Ожидаемая передача';
|
||||
|
||||
@override
|
||||
String get senderToReceipt => 'От отправителя до получения';
|
||||
|
||||
@override
|
||||
String get receivedCopies => 'Полученные копии';
|
||||
|
||||
@override
|
||||
String get retryCause => 'Причина повтора';
|
||||
|
||||
@override
|
||||
String get retryMode => 'Режим повтора';
|
||||
|
||||
@override
|
||||
String get retryResult => 'Результат повтора';
|
||||
|
||||
@override
|
||||
String get lastRetry => 'Последний повтор';
|
||||
|
||||
@override
|
||||
String get rxPackets => 'RX-пакеты';
|
||||
|
||||
@override
|
||||
String get mesh => 'Mesh';
|
||||
|
||||
@override
|
||||
String get rate => 'Скорость';
|
||||
|
||||
@override
|
||||
String get window => 'Окно';
|
||||
|
||||
@override
|
||||
String get posttxDelay => 'Задержка после передачи';
|
||||
|
||||
@override
|
||||
String get bandpass => 'Полосовой';
|
||||
|
||||
@override
|
||||
String get bandpassFilterVoice => 'Полосовой фильтр голоса';
|
||||
|
||||
@override
|
||||
String get frequency => 'Частота';
|
||||
|
||||
@override
|
||||
String get australia => 'Австралия';
|
||||
|
||||
@override
|
||||
String get australiaNarrow => 'Австралия (Узкий)';
|
||||
|
||||
@override
|
||||
String get australiaQld => 'Австралия: QLD';
|
||||
|
||||
@override
|
||||
String get australiaSaWa => 'Австралия: SA, WA';
|
||||
|
||||
@override
|
||||
String get newZealand => 'Новая Зеландия';
|
||||
|
||||
@override
|
||||
String get newZealandNarrow => 'Новая Зеландия (Узкий)';
|
||||
|
||||
@override
|
||||
String get switzerland => 'Швейцария';
|
||||
|
||||
@override
|
||||
String get portugal433 => 'Португалия 433';
|
||||
|
||||
@override
|
||||
String get portugal868 => 'Португалия 868';
|
||||
|
||||
@override
|
||||
String get czechRepublicNarrow => 'Чехия (Узкий)';
|
||||
|
||||
@override
|
||||
String get eu433mhzLongRange => 'ЕС 433МГц (Дальний)';
|
||||
|
||||
@override
|
||||
String get euukDeprecated => 'ЕС/Великобритания (Устарело)';
|
||||
|
||||
@override
|
||||
String get euukNarrow => 'ЕС/Великобритания (Узкий)';
|
||||
|
||||
@override
|
||||
String get usacanadaRecommended => 'США/Канада (Рекомендуется)';
|
||||
|
||||
@override
|
||||
String get vietnamDeprecated => 'Вьетнам (Устарело)';
|
||||
|
||||
@override
|
||||
String get vietnamNarrow => 'Вьетнам (Узкий)';
|
||||
|
||||
@override
|
||||
String get active => 'Активно';
|
||||
|
||||
@override
|
||||
String get addContact => 'Добавить контакт';
|
||||
|
||||
@override
|
||||
String get all => 'Все';
|
||||
|
||||
@override
|
||||
String get autoResolve => 'Автоматическое разрешение';
|
||||
|
||||
@override
|
||||
String get clearAllLabel => 'Очистить все';
|
||||
|
||||
@override
|
||||
String get clearRelays => 'Очистить реле';
|
||||
|
||||
@override
|
||||
String get clearFilters => 'Очистить фильтры';
|
||||
|
||||
@override
|
||||
String get clearRoute => 'Очистить маршрут';
|
||||
|
||||
@override
|
||||
String get clearMessages => 'Очистить сообщения';
|
||||
|
||||
@override
|
||||
String get clearScale => 'Очистить масштаб';
|
||||
|
||||
@override
|
||||
String get clearDiscoveries => 'Очистить обнаружения';
|
||||
|
||||
@override
|
||||
String get clearOnlineTraceDatabase => 'Очистить базу трассировок';
|
||||
|
||||
@override
|
||||
String get clearAllChannels => 'Очистить все каналы';
|
||||
|
||||
@override
|
||||
String get clearAllContacts => 'Очистить все контакты';
|
||||
|
||||
@override
|
||||
String get clearChannels => 'Очистить каналы';
|
||||
|
||||
@override
|
||||
String get clearContacts => 'Очистить контакты';
|
||||
|
||||
@override
|
||||
String get clearPathOnMaxRetry => 'Очистить путь при макс. повторе';
|
||||
|
||||
@override
|
||||
String get create => 'Создать';
|
||||
|
||||
@override
|
||||
String get custom => 'Пользовательский';
|
||||
|
||||
@override
|
||||
String get defaultValue => 'По умолчанию';
|
||||
|
||||
@override
|
||||
String get duplicate => 'Дублировать';
|
||||
|
||||
@override
|
||||
String get editName => 'Редактировать имя';
|
||||
|
||||
@override
|
||||
String get open => 'Открыть';
|
||||
|
||||
@override
|
||||
String get paste => 'Вставить';
|
||||
|
||||
@override
|
||||
String get preview => 'Предпросмотр';
|
||||
|
||||
@override
|
||||
String get remove => 'Удалить';
|
||||
|
||||
@override
|
||||
String get rename => 'Переименовать';
|
||||
|
||||
@override
|
||||
String get resolveAll => 'Разрешить все';
|
||||
|
||||
@override
|
||||
String get send => 'Отправить';
|
||||
|
||||
@override
|
||||
String get sendAnyway => 'Отправить все равно';
|
||||
|
||||
@override
|
||||
String get share => 'Поделиться';
|
||||
|
||||
@override
|
||||
String get shareContact => 'Поделиться контактом';
|
||||
|
||||
@override
|
||||
String get trace => 'Трассировка';
|
||||
|
||||
@override
|
||||
String get use => 'Использовать';
|
||||
|
||||
@override
|
||||
String get useSelectedFrequency => 'Использовать выбранную частоту';
|
||||
|
||||
@override
|
||||
String get discovery => 'Обнаружение';
|
||||
|
||||
@override
|
||||
String get discoverRepeaters => 'Обнаружить повторители';
|
||||
|
||||
@override
|
||||
String get discoverSensors => 'Обнаружить датчики';
|
||||
|
||||
@override
|
||||
String get repeaterDiscoverySent => 'Обнаружение повторителей отправлено';
|
||||
|
||||
@override
|
||||
String get sensorDiscoverySent => 'Обнаружение датчиков отправлено';
|
||||
|
||||
@override
|
||||
String get clearedPendingDiscoveries => 'Ожидающие обнаружения очищены.';
|
||||
|
||||
@override
|
||||
String get autoDiscovery => 'Автоматическое обнаружение';
|
||||
|
||||
@override
|
||||
String get enableAutomaticAdding => 'Включить автоматическое добавление';
|
||||
|
||||
@override
|
||||
String get autoaddRepeaters => 'Автодобавление повторителей';
|
||||
|
||||
@override
|
||||
String get autoaddRoomServers => 'Автодобавление серверов комнат';
|
||||
|
||||
@override
|
||||
String get autoaddSensors => 'Автодобавление датчиков';
|
||||
|
||||
@override
|
||||
String get autoaddUsers => 'Автодобавление пользователей';
|
||||
|
||||
@override
|
||||
String get overwriteOldestWhenFull => 'Перезаписать старейшие при заполнении';
|
||||
|
||||
@override
|
||||
String get storage => 'Хранилище';
|
||||
|
||||
@override
|
||||
String get dangerZone => 'Опасная зона';
|
||||
|
||||
@override
|
||||
String get profiles => 'Профили';
|
||||
|
||||
@override
|
||||
String get favourites => 'Избранное';
|
||||
|
||||
@override
|
||||
String get sensors => 'Датчики';
|
||||
|
||||
@override
|
||||
String get others => 'Другие';
|
||||
|
||||
@override
|
||||
String get gpsModule => 'Модуль GPS';
|
||||
|
||||
@override
|
||||
String get liveTraffic => 'Живой трафик';
|
||||
|
||||
@override
|
||||
String get repeatersMap => 'Карта повторителей';
|
||||
|
||||
@override
|
||||
String get spectrumScan => 'Сканирование спектра';
|
||||
|
||||
@override
|
||||
String get blePacketLogs => 'Журналы BLE-пакетов';
|
||||
|
||||
@override
|
||||
String get onlineTraceDatabase => 'База трассировок';
|
||||
|
||||
@override
|
||||
String get routePathByteSize => 'Размер пути в байтах';
|
||||
|
||||
@override
|
||||
String get messageNotifications => 'Уведомления о сообщениях';
|
||||
|
||||
@override
|
||||
String get sarAlerts => 'SAR-оповещения';
|
||||
|
||||
@override
|
||||
String get discoveryNotifications => 'Уведомления об обнаружении';
|
||||
|
||||
@override
|
||||
String get updateNotifications => 'Уведомления об обновлениях';
|
||||
|
||||
@override
|
||||
String get muteWhileAppIsOpen => 'Без звука при открытом приложении';
|
||||
|
||||
@override
|
||||
String get disableContacts => 'Отключить контакты';
|
||||
|
||||
@override
|
||||
String get enableSensorsTab => 'Включить вкладку Датчики';
|
||||
|
||||
@override
|
||||
String get enableProfiles => 'Включить профили';
|
||||
|
||||
@override
|
||||
String get autoRouteRotation => 'Автоматическая ротация маршрута';
|
||||
|
||||
@override
|
||||
String get nearestRepeaterFallback => 'Ближайший повторитель как резерв';
|
||||
|
||||
@override
|
||||
String get deleteAllStoredMessageHistory => 'Удалить всю историю сообщений';
|
||||
|
||||
@override
|
||||
String get messageFontSize => 'Размер шрифта сообщений';
|
||||
|
||||
@override
|
||||
String get rotateMapWithHeading => 'Вращать карту по направлению';
|
||||
|
||||
@override
|
||||
String get showMapDebugInfo => 'Показать отладочную информацию карты';
|
||||
|
||||
@override
|
||||
String get openMapInFullscreen => 'Открыть карту на весь экран';
|
||||
|
||||
@override
|
||||
String get showSarMarkersLabel => 'Показать SAR-маркеры';
|
||||
|
||||
@override
|
||||
String get displaySarMarkersOnTheMainMap =>
|
||||
'Отображать SAR-маркеры на основной карте';
|
||||
|
||||
@override
|
||||
String get showAllContactTrailsLabel => 'Показать все следы контактов';
|
||||
|
||||
@override
|
||||
String get hideRepeatersOnMap => 'Скрыть повторители на карте';
|
||||
|
||||
@override
|
||||
String get setMapScale => 'Установить масштаб карты';
|
||||
|
||||
@override
|
||||
String get customMapScaleSaved => 'Пользовательский масштаб карты сохранён';
|
||||
|
||||
@override
|
||||
String get voiceBitrate => 'Битрейт голоса';
|
||||
|
||||
@override
|
||||
String get voiceCompressor => 'Компрессор голоса';
|
||||
|
||||
@override
|
||||
String get balancesQuietAndLoudSpeechLevels =>
|
||||
'Выравнивает тихую и громкую речь';
|
||||
|
||||
@override
|
||||
String get voiceLimiter => 'Лимитер голоса';
|
||||
|
||||
@override
|
||||
String get preventsClippingPeaksBeforeEncoding =>
|
||||
'Предотвращает обрезку пиков перед кодированием';
|
||||
|
||||
@override
|
||||
String get micAutoGain => 'Автоусиление микрофона';
|
||||
|
||||
@override
|
||||
String get letsTheRecorderAdjustInputLevel =>
|
||||
'Позволяет рекордеру регулировать уровень входа';
|
||||
|
||||
@override
|
||||
String get echoCancellation => 'Подавление эха';
|
||||
|
||||
@override
|
||||
String get noiseSuppression => 'Подавление шума';
|
||||
|
||||
@override
|
||||
String get trimSilenceInVoiceMessages =>
|
||||
'Обрезать тишину в голосовых сообщениях';
|
||||
|
||||
@override
|
||||
String get compressor => 'Компрессор';
|
||||
|
||||
@override
|
||||
String get limiter => 'Лимитер';
|
||||
|
||||
@override
|
||||
String get autoGain => 'Автоусиление';
|
||||
|
||||
@override
|
||||
String get echoCancel => 'Эхо';
|
||||
|
||||
@override
|
||||
String get noiseSuppress => 'Шум';
|
||||
|
||||
@override
|
||||
String get silenceTrim => 'Тишина';
|
||||
|
||||
@override
|
||||
String get maxImageSize => 'Максимальный размер изображения';
|
||||
|
||||
@override
|
||||
String get imageCompression => 'Сжатие изображения';
|
||||
|
||||
@override
|
||||
String get grayscale => 'Оттенки серого';
|
||||
|
||||
@override
|
||||
String get ultraMode => 'Режим ультра';
|
||||
|
||||
@override
|
||||
String get fastPrivateGpsUpdates => 'Быстрые приватные GPS-обновления';
|
||||
|
||||
@override
|
||||
String get movementThreshold => 'Порог движения';
|
||||
|
||||
@override
|
||||
String get fastGpsMovementThreshold => 'Порог движения быстрого GPS';
|
||||
|
||||
@override
|
||||
String get fastGpsActiveuseInterval =>
|
||||
'Интервал активного использования быстрого GPS';
|
||||
|
||||
@override
|
||||
String get activeuseUpdateInterval =>
|
||||
'Интервал обновления при активном использовании';
|
||||
|
||||
@override
|
||||
String get repeatNearbyTraffic => 'Повторять ближний трафик';
|
||||
|
||||
@override
|
||||
String get relayThroughRepeatersAcrossTheMesh =>
|
||||
'Ретранслировать через повторители по сети';
|
||||
|
||||
@override
|
||||
String get nearbyOnlyWithoutRepeaterFlooding =>
|
||||
'Только рядом, без flood повторителей';
|
||||
|
||||
@override
|
||||
String get multihop => 'Многохоповый';
|
||||
|
||||
@override
|
||||
String get createProfile => 'Создать профиль';
|
||||
|
||||
@override
|
||||
String get renameProfile => 'Переименовать профиль';
|
||||
|
||||
@override
|
||||
String get newProfile => 'Новый профиль';
|
||||
|
||||
@override
|
||||
String get manageProfiles => 'Управление профилями';
|
||||
|
||||
@override
|
||||
String get enableProfilesToStartManagingThem =>
|
||||
'Включите профили, чтобы начать управлять ими.';
|
||||
|
||||
@override
|
||||
String get openMessage => 'Открыть сообщение';
|
||||
|
||||
@override
|
||||
String get jumpToTheRelatedSarMessage => 'Перейти к связанному SAR-сообщению';
|
||||
|
||||
@override
|
||||
String get removeSarMarker => 'Удалить SAR-маркер';
|
||||
|
||||
@override
|
||||
String get pleaseSelectADestinationToSendSarMarker =>
|
||||
'Выберите назначение для отправки SAR-маркера';
|
||||
|
||||
@override
|
||||
String get sarMarkerBroadcastToPublicChannel =>
|
||||
'SAR-маркер отправлен в публичный канал';
|
||||
|
||||
@override
|
||||
String get sarMarkerSentToRoom => 'SAR-маркер отправлен в комнату';
|
||||
|
||||
@override
|
||||
String get loadFromGallery => 'Загрузить из галереи';
|
||||
|
||||
@override
|
||||
String get replaceImage => 'Заменить изображение';
|
||||
|
||||
@override
|
||||
String get selectFromGallery => 'Выбрать из галереи';
|
||||
|
||||
@override
|
||||
String get team => 'Команда';
|
||||
|
||||
@override
|
||||
String get found => 'Найдено';
|
||||
|
||||
@override
|
||||
String get staging => 'Место сбора';
|
||||
|
||||
@override
|
||||
String get object => 'Объект';
|
||||
|
||||
@override
|
||||
String get quiet => 'Тихо';
|
||||
|
||||
@override
|
||||
String get moderate => 'Умеренно';
|
||||
|
||||
@override
|
||||
String get busy => 'Занято';
|
||||
|
||||
@override
|
||||
String get spectrumScanReturnedNoCandidateFrequencies =>
|
||||
'Сканирование спектра не нашло частот-кандидатов';
|
||||
|
||||
@override
|
||||
String get searchMessages => 'Поиск сообщений';
|
||||
|
||||
@override
|
||||
String get sendImageFromGallery => 'Отправить изображение из галереи';
|
||||
|
||||
@override
|
||||
String get takePhoto => 'Сделать фото';
|
||||
|
||||
@override
|
||||
String get dmOnly => 'Только личные сообщения';
|
||||
|
||||
@override
|
||||
String get allMessages => 'Все сообщения';
|
||||
|
||||
@override
|
||||
String get sendToPublicChannel => 'Отправить в публичный канал?';
|
||||
|
||||
@override
|
||||
String get selectMarkerTypeAndDestination =>
|
||||
'Выберите тип маркера и назначение';
|
||||
|
||||
@override
|
||||
String get noDestinationsAvailableLabel => 'Нет доступных назначений';
|
||||
|
||||
@override
|
||||
String get image => 'Изображение';
|
||||
|
||||
@override
|
||||
String get format => 'Формат';
|
||||
|
||||
@override
|
||||
String get dimensions => 'Размеры';
|
||||
|
||||
@override
|
||||
String get segments => 'Сегменты';
|
||||
|
||||
@override
|
||||
String get transfers => 'Передачи';
|
||||
|
||||
@override
|
||||
String get downloadedBy => 'Загружено';
|
||||
|
||||
@override
|
||||
String get saveDiscoverySettings => 'Сохранить настройки обнаружения';
|
||||
|
||||
@override
|
||||
String get savePublicInfo => 'Сохранить публичную информацию';
|
||||
|
||||
@override
|
||||
String get saveRadioSettings => 'Сохранить настройки радио';
|
||||
|
||||
@override
|
||||
String get savePath => 'Сохранить путь';
|
||||
|
||||
@override
|
||||
String get wipeDeviceData => 'Стереть данные устройства';
|
||||
|
||||
@override
|
||||
String get wipeDevice => 'Стереть устройство';
|
||||
|
||||
@override
|
||||
String get destructiveDeviceActions => 'Деструктивные действия устройства.';
|
||||
|
||||
@override
|
||||
String get chooseAPresetOrFinetuneCustomRadioSettings =>
|
||||
'Выберите пресет или настройте радио вручную.';
|
||||
|
||||
@override
|
||||
String get chooseTheNameAndLocationThisDeviceShares =>
|
||||
'Выберите имя и местоположение, которыми делится устройство.';
|
||||
|
||||
@override
|
||||
String get availableSpaceOnThisDevice =>
|
||||
'Доступное место на этом устройстве.';
|
||||
|
||||
@override
|
||||
String get used => 'Использовано';
|
||||
|
||||
@override
|
||||
String get total => 'Всего';
|
||||
|
||||
@override
|
||||
String get renameValue => 'Переименовать значение';
|
||||
|
||||
@override
|
||||
String get customizeFields => 'Настроить поля';
|
||||
|
||||
@override
|
||||
String get livePreview => 'Предпросмотр в реальном времени';
|
||||
|
||||
@override
|
||||
String get refreshSchedule => 'Расписание обновления';
|
||||
|
||||
@override
|
||||
String get noResponse => 'Нет ответа';
|
||||
|
||||
@override
|
||||
String get refreshing => 'Обновление';
|
||||
|
||||
@override
|
||||
String get unavailable => 'Недоступно';
|
||||
|
||||
@override
|
||||
String get pickARelayOrNodeToWatchInSensors =>
|
||||
'Выберите реле или узел для наблюдения.';
|
||||
|
||||
@override
|
||||
String get publicKeyLabel => 'Публичный ключ';
|
||||
|
||||
@override
|
||||
String get alreadyInContacts => 'Уже в контактах';
|
||||
|
||||
@override
|
||||
String get connectToADeviceBeforeAddingContacts =>
|
||||
'Подключитесь к устройству перед добавлением контактов';
|
||||
|
||||
@override
|
||||
String get fromContacts => 'Из контактов';
|
||||
|
||||
@override
|
||||
String get onlineOnly => 'Только онлайн';
|
||||
|
||||
@override
|
||||
String get inBoth => 'В обоих';
|
||||
|
||||
@override
|
||||
String get source => 'Источник';
|
||||
|
||||
@override
|
||||
String get manualRouteEdit => 'Ручное редактирование маршрута';
|
||||
|
||||
@override
|
||||
String get observedMeshRoute => 'Наблюдаемый mesh-маршрут';
|
||||
|
||||
@override
|
||||
String get allMessagesCleared => 'Все сообщения очищены';
|
||||
|
||||
@override
|
||||
String get onlineTraceDatabaseCleared => 'База трассировок очищена';
|
||||
|
||||
@override
|
||||
String get packetLogsCleared => 'Журналы пакетов очищены';
|
||||
|
||||
@override
|
||||
String get hexDataCopiedToClipboard => 'Hex-данные скопированы в буфер';
|
||||
|
||||
@override
|
||||
String get developerModeEnabled => 'Режим разработчика включён';
|
||||
|
||||
@override
|
||||
String get developerModeDisabled => 'Режим разработчика отключён';
|
||||
|
||||
@override
|
||||
String get clipboardIsEmpty => 'Буфер обмена пуст';
|
||||
|
||||
@override
|
||||
String get contactImported => 'Контакт импортирован';
|
||||
|
||||
@override
|
||||
String get contactLinkCopiedToClipboard =>
|
||||
'Ссылка на контакт скопирована в буфер';
|
||||
|
||||
@override
|
||||
String get failedToExportContact => 'Не удалось экспортировать контакт';
|
||||
|
||||
@override
|
||||
String get noLogsToExport => 'Нет журналов для экспорта';
|
||||
|
||||
@override
|
||||
String get exportAsCsv => 'Экспортировать как CSV';
|
||||
|
||||
@override
|
||||
String get exportAsText => 'Экспортировать как текст';
|
||||
|
||||
@override
|
||||
String get receivedRfc3339 => 'Получено (RFC3339)';
|
||||
|
||||
@override
|
||||
String get buildTime => 'Время сборки';
|
||||
|
||||
@override
|
||||
String get downloadUrlNotAvailable => 'URL загрузки недоступен';
|
||||
|
||||
@override
|
||||
String get cannotOpenDownloadUrl => 'Не удаётся открыть URL загрузки';
|
||||
|
||||
@override
|
||||
String get updateCheckIsOnlyAvailableOnAndroid =>
|
||||
'Проверка обновлений доступна только на Android';
|
||||
|
||||
@override
|
||||
String get youAreRunningTheLatestVersion => 'Вы используете последнюю версию';
|
||||
|
||||
@override
|
||||
String get updateAvailableButDownloadUrlNotFound =>
|
||||
'Обновление доступно, но URL загрузки не найден';
|
||||
|
||||
@override
|
||||
String get startTictactoe => 'Начать Tic-Tac-Toe';
|
||||
|
||||
@override
|
||||
String get tictactoeUnavailable => 'Tic-Tac-Toe недоступно';
|
||||
|
||||
@override
|
||||
String get tictactoeOpponentUnknown => 'Tic-Tac-Toe: противник неизвестен';
|
||||
|
||||
@override
|
||||
String get tictactoeWaitingForStart => 'Tic-Tac-Toe: ожидание начала';
|
||||
|
||||
@override
|
||||
String get acceptsShareLinks => 'Принимает общие ссылки';
|
||||
|
||||
@override
|
||||
String get supportsRawHex => 'Поддерживает raw hex';
|
||||
|
||||
@override
|
||||
String get clipboardfriendly => 'Удобно для буфера';
|
||||
|
||||
@override
|
||||
String get captured => 'Захвачено';
|
||||
|
||||
@override
|
||||
String get size => 'Размер';
|
||||
|
||||
@override
|
||||
String get noCustomChannelsToClear =>
|
||||
'Нет пользовательских каналов для очистки.';
|
||||
|
||||
@override
|
||||
String get noDeviceContactsToClear => 'Нет контактов устройства для очистки.';
|
||||
}
|
||||
|
||||
@@ -1718,4 +1718,961 @@ class AppLocalizationsSl extends AppLocalizations {
|
||||
|
||||
@override
|
||||
String get requestingVoice => 'Zahteva za glasom';
|
||||
|
||||
@override
|
||||
String get device => 'naprava';
|
||||
|
||||
@override
|
||||
String get change => 'Spremeni';
|
||||
|
||||
@override
|
||||
String get wizardOverviewDescription =>
|
||||
'Ta aplikacija združuje sporočanje MeshCore, terenske SAR posodobitve, zemljevide in orodja za napravo na enem mestu.';
|
||||
|
||||
@override
|
||||
String get wizardOverviewFeature1 =>
|
||||
'Pošiljajte neposredna sporočila, objave v sobah in sporočila na kanalih iz glavnega zavihka Sporočila.';
|
||||
|
||||
@override
|
||||
String get wizardOverviewFeature2 =>
|
||||
'Delite SAR označevalce, risbe zemljevida, glasovne posnetke in slike prek mesh omrežja.';
|
||||
|
||||
@override
|
||||
String get wizardOverviewFeature3 =>
|
||||
'Povežite se prek BLE ali TCP in nato upravljajte spremljevalni radio kar iz aplikacije.';
|
||||
|
||||
@override
|
||||
String get wizardMessagingTitle => 'Sporočanje in terenska poročila';
|
||||
|
||||
@override
|
||||
String get wizardMessagingDescription =>
|
||||
'Sporočila tukaj niso le navadno besedilo. Aplikacija že podpira več operativnih vrst vsebin in prenosnih tokov.';
|
||||
|
||||
@override
|
||||
String get wizardMessagingFeature1 =>
|
||||
'Pošiljajte neposredna sporočila, objave v sobah in promet na kanalih iz enega urejevalnika.';
|
||||
|
||||
@override
|
||||
String get wizardMessagingFeature2 =>
|
||||
'Ustvarjajte SAR posodobitve in večkrat uporabne SAR predloge za pogosta terenska poročila.';
|
||||
|
||||
@override
|
||||
String get wizardMessagingFeature3 =>
|
||||
'Prenašajte glasovne seje in slike, vmesnik pa prikazuje napredek in oceno časa prenosa.';
|
||||
|
||||
@override
|
||||
String get wizardConnectDeviceTitle => 'Poveži napravo';
|
||||
|
||||
@override
|
||||
String get wizardConnectDeviceDescription =>
|
||||
'Povežite svoj MeshCore radio, izberite ime in uporabite radijski prednastavljeni profil, preden nadaljujete.';
|
||||
|
||||
@override
|
||||
String get wizardSetupBadge => 'Nastavitev';
|
||||
|
||||
@override
|
||||
String get wizardOverviewBadge => 'Pregled';
|
||||
|
||||
@override
|
||||
String wizardConnectedToDevice(String deviceName) {
|
||||
return 'Povezano z: $deviceName';
|
||||
}
|
||||
|
||||
@override
|
||||
String get wizardNoDeviceConnected => 'Nobena naprava še ni povezana';
|
||||
|
||||
@override
|
||||
String get wizardSkipForNow => 'Za zdaj preskoči';
|
||||
|
||||
@override
|
||||
String get wizardDeviceNameLabel => 'Ime naprave';
|
||||
|
||||
@override
|
||||
String get wizardDeviceNameHelp =>
|
||||
'To ime se oglašuje drugim uporabnikom MeshCore.';
|
||||
|
||||
@override
|
||||
String get wizardConfigRegionLabel => 'Konfiguracijska regija';
|
||||
|
||||
@override
|
||||
String get wizardConfigRegionHelp =>
|
||||
'Uporabi celoten uradni seznam prednastavitev MeshCore. Privzeto je izbran EU/UK (Narrow).';
|
||||
|
||||
@override
|
||||
String get wizardPresetNote1 =>
|
||||
'Prepričajte se, da izbrana prednastavitev ustreza lokalnim radijskim predpisom.';
|
||||
|
||||
@override
|
||||
String get wizardPresetNote2 =>
|
||||
'Seznam ustreza uradnemu viru prednastavitev orodja MeshCore config.';
|
||||
|
||||
@override
|
||||
String get wizardPresetNote3 =>
|
||||
'Za uvajanje ostane privzeto izbran EU/UK (Narrow).';
|
||||
|
||||
@override
|
||||
String get wizardSaving => 'Shranjujem...';
|
||||
|
||||
@override
|
||||
String get wizardSaveAndContinue => 'Shrani in nadaljuj';
|
||||
|
||||
@override
|
||||
String get wizardEnterDeviceName => 'Pred nadaljevanjem vnesite ime naprave.';
|
||||
|
||||
@override
|
||||
String wizardDeviceSetupSaved(String deviceName, String presetName) {
|
||||
return 'Shranjeno $deviceName z nastavitvijo $presetName.';
|
||||
}
|
||||
|
||||
@override
|
||||
String get wizardNetworkTitle => 'Stiki, sobe in repetitorji';
|
||||
|
||||
@override
|
||||
String get wizardNetworkDescription =>
|
||||
'Zavihek Stiki organizira omrežje, ki ga odkrijete, in poti, ki se jih aplikacija nauči skozi čas.';
|
||||
|
||||
@override
|
||||
String get wizardNetworkFeature1 =>
|
||||
'Na enem seznamu preglejte člane ekipe, repetitorje, sobe, kanale in čakajoče oglase.';
|
||||
|
||||
@override
|
||||
String get wizardNetworkFeature2 =>
|
||||
'Uporabite smart ping, prijavo v sobe, naučene poti in ponastavitev poti, ko povezljivost postane neurejena.';
|
||||
|
||||
@override
|
||||
String get wizardNetworkFeature3 =>
|
||||
'Ustvarjajte kanale in upravljajte omrežne cilje, ne da bi zapustili aplikacijo.';
|
||||
|
||||
@override
|
||||
String get wizardMapOpsTitle => 'Zemljevid, sledi in deljena geometrija';
|
||||
|
||||
@override
|
||||
String get wizardMapOpsDescription =>
|
||||
'Zemljevid v aplikaciji je neposredno povezan s sporočanjem, sledenjem in SAR prekrivanji, namesto da bi bil le ločen pregledovalnik.';
|
||||
|
||||
@override
|
||||
String get wizardMapOpsFeature1 =>
|
||||
'Spremljajte svoj položaj, lokacije sotekmovalcev in gibanje sledi na zemljevidu.';
|
||||
|
||||
@override
|
||||
String get wizardMapOpsFeature2 =>
|
||||
'Odprite risbe iz sporočil, jih predoglejte v vrstici in jih po potrebi odstranite z zemljevida.';
|
||||
|
||||
@override
|
||||
String get wizardMapOpsFeature3 =>
|
||||
'Uporabite poglede repetitorjev in deljena prekrivanja za razumevanje dosega omrežja na terenu.';
|
||||
|
||||
@override
|
||||
String get wizardToolsTitle => 'Orodja onkraj sporočanja';
|
||||
|
||||
@override
|
||||
String get wizardToolsDescription =>
|
||||
'Tu je več kot le štirje glavni zavihki. Aplikacija vključuje tudi konfiguracijo, diagnostiko in neobvezne senzorske poteke.';
|
||||
|
||||
@override
|
||||
String get wizardToolsFeature1 =>
|
||||
'Odprite nastavitve naprave za spremembo radijskih nastavitev, telemetrije, oddajne moči in podatkov spremljevalne naprave.';
|
||||
|
||||
@override
|
||||
String get wizardToolsFeature2 =>
|
||||
'Omogočite zavihek Senzorji, kadar želite nadzorne plošče in hitra osveževanja spremljanih senzorjev.';
|
||||
|
||||
@override
|
||||
String get wizardToolsFeature3 =>
|
||||
'Uporabite dnevnik paketov, pregled spektra in razvojno diagnostiko pri odpravljanju težav v mesh omrežju.';
|
||||
|
||||
@override
|
||||
String get contactInSensors => 'V senzorjih';
|
||||
|
||||
@override
|
||||
String get contactAddToSensors => 'Dodaj v senzorje';
|
||||
|
||||
@override
|
||||
String get contactSetPath => 'Nastavi pot';
|
||||
|
||||
@override
|
||||
String contactAddedToSensors(String contactName) {
|
||||
return '$contactName dodan v senzorje';
|
||||
}
|
||||
|
||||
@override
|
||||
String contactFailedToClearRoute(String error) {
|
||||
return 'Brisanje poti ni uspelo: $error';
|
||||
}
|
||||
|
||||
@override
|
||||
String get contactRouteCleared => 'Pot izbrisana';
|
||||
|
||||
@override
|
||||
String contactRouteSet(String route) {
|
||||
return 'Pot nastavljena: $route';
|
||||
}
|
||||
|
||||
@override
|
||||
String contactFailedToSetRoute(String error) {
|
||||
return 'Nastavitev poti ni uspela: $error';
|
||||
}
|
||||
|
||||
@override
|
||||
String get rssi => 'RSSI';
|
||||
|
||||
@override
|
||||
String get snr => 'SNR';
|
||||
|
||||
@override
|
||||
String get ackTimeout => 'Časovna omejitev ACK';
|
||||
|
||||
@override
|
||||
String get opcode => 'Opkoda';
|
||||
|
||||
@override
|
||||
String get payload => 'Vsebina';
|
||||
|
||||
@override
|
||||
String get hops => 'Skoki';
|
||||
|
||||
@override
|
||||
String get hashSize => 'Velikost zgoščevalne vrednosti';
|
||||
|
||||
@override
|
||||
String get pathBytes => 'Bajti poti';
|
||||
|
||||
@override
|
||||
String get selectedPath => 'Izbrana pot';
|
||||
|
||||
@override
|
||||
String get estimatedTx => 'Ocenjeni čas oddaje';
|
||||
|
||||
@override
|
||||
String get senderToReceipt => 'Od pošiljatelja do prejema';
|
||||
|
||||
@override
|
||||
String get receivedCopies => 'Prejete kopije';
|
||||
|
||||
@override
|
||||
String get retryCause => 'Razlog ponovitve';
|
||||
|
||||
@override
|
||||
String get retryMode => 'Način ponovitve';
|
||||
|
||||
@override
|
||||
String get retryResult => 'Rezultat ponovitve';
|
||||
|
||||
@override
|
||||
String get lastRetry => 'Zadnja ponovitev';
|
||||
|
||||
@override
|
||||
String get rxPackets => 'RX paketi';
|
||||
|
||||
@override
|
||||
String get mesh => 'Mesh';
|
||||
|
||||
@override
|
||||
String get rate => 'Hitrost';
|
||||
|
||||
@override
|
||||
String get window => 'Okno';
|
||||
|
||||
@override
|
||||
String get posttxDelay => 'Zakasnitev po oddaji';
|
||||
|
||||
@override
|
||||
String get bandpass => 'Pasovno sito';
|
||||
|
||||
@override
|
||||
String get bandpassFilterVoice => 'Pasovno sito za glas';
|
||||
|
||||
@override
|
||||
String get frequency => 'Frekvenca';
|
||||
|
||||
@override
|
||||
String get australia => 'Avstralija';
|
||||
|
||||
@override
|
||||
String get australiaNarrow => 'Avstralija (ozko)';
|
||||
|
||||
@override
|
||||
String get australiaQld => 'Avstralija: QLD';
|
||||
|
||||
@override
|
||||
String get australiaSaWa => 'Avstralija: SA, WA';
|
||||
|
||||
@override
|
||||
String get newZealand => 'Nova Zelandija';
|
||||
|
||||
@override
|
||||
String get newZealandNarrow => 'Nova Zelandija (ozko)';
|
||||
|
||||
@override
|
||||
String get switzerland => 'Švica';
|
||||
|
||||
@override
|
||||
String get portugal433 => 'Portugalska 433';
|
||||
|
||||
@override
|
||||
String get portugal868 => 'Portugalska 868';
|
||||
|
||||
@override
|
||||
String get czechRepublicNarrow => 'Češka (ozko)';
|
||||
|
||||
@override
|
||||
String get eu433mhzLongRange => 'EU 433MHz (dolg doseg)';
|
||||
|
||||
@override
|
||||
String get euukDeprecated => 'EU/UK (zastarelo)';
|
||||
|
||||
@override
|
||||
String get euukNarrow => 'EU/UK (ozko)';
|
||||
|
||||
@override
|
||||
String get usacanadaRecommended => 'ZDA/Kanada (priporočeno)';
|
||||
|
||||
@override
|
||||
String get vietnamDeprecated => 'Vietnam (zastarelo)';
|
||||
|
||||
@override
|
||||
String get vietnamNarrow => 'Vietnam (ozko)';
|
||||
|
||||
@override
|
||||
String get active => 'Aktivno';
|
||||
|
||||
@override
|
||||
String get addContact => 'Dodaj stik';
|
||||
|
||||
@override
|
||||
String get all => 'Vse';
|
||||
|
||||
@override
|
||||
String get autoResolve => 'Samodejno razreši';
|
||||
|
||||
@override
|
||||
String get clearAllLabel => 'Počisti vse';
|
||||
|
||||
@override
|
||||
String get clearRelays => 'Počisti posrednike';
|
||||
|
||||
@override
|
||||
String get clearFilters => 'Počisti filtre';
|
||||
|
||||
@override
|
||||
String get clearRoute => 'Počisti pot';
|
||||
|
||||
@override
|
||||
String get clearMessages => 'Počisti sporočila';
|
||||
|
||||
@override
|
||||
String get clearScale => 'Počisti merilo';
|
||||
|
||||
@override
|
||||
String get clearDiscoveries => 'Počisti odkritja';
|
||||
|
||||
@override
|
||||
String get clearOnlineTraceDatabase => 'Počisti bazo sledi';
|
||||
|
||||
@override
|
||||
String get clearAllChannels => 'Počisti vse kanale';
|
||||
|
||||
@override
|
||||
String get clearAllContacts => 'Počisti vse stike';
|
||||
|
||||
@override
|
||||
String get clearChannels => 'Počisti kanale';
|
||||
|
||||
@override
|
||||
String get clearContacts => 'Počisti stike';
|
||||
|
||||
@override
|
||||
String get clearPathOnMaxRetry => 'Počisti pot ob maks. ponovitvi';
|
||||
|
||||
@override
|
||||
String get create => 'Ustvari';
|
||||
|
||||
@override
|
||||
String get custom => 'Po meri';
|
||||
|
||||
@override
|
||||
String get defaultValue => 'Privzeto';
|
||||
|
||||
@override
|
||||
String get duplicate => 'Podvoji';
|
||||
|
||||
@override
|
||||
String get editName => 'Uredi ime';
|
||||
|
||||
@override
|
||||
String get open => 'Odpri';
|
||||
|
||||
@override
|
||||
String get paste => 'Prilepi';
|
||||
|
||||
@override
|
||||
String get preview => 'Predogled';
|
||||
|
||||
@override
|
||||
String get remove => 'Odstrani';
|
||||
|
||||
@override
|
||||
String get rename => 'Preimenuj';
|
||||
|
||||
@override
|
||||
String get resolveAll => 'Razreši vse';
|
||||
|
||||
@override
|
||||
String get send => 'Pošlji';
|
||||
|
||||
@override
|
||||
String get sendAnyway => 'Vseeno pošlji';
|
||||
|
||||
@override
|
||||
String get share => 'Deli';
|
||||
|
||||
@override
|
||||
String get shareContact => 'Deli stik';
|
||||
|
||||
@override
|
||||
String get trace => 'Sledenje';
|
||||
|
||||
@override
|
||||
String get use => 'Uporabi';
|
||||
|
||||
@override
|
||||
String get useSelectedFrequency => 'Uporabi izbrano frekvenco';
|
||||
|
||||
@override
|
||||
String get discovery => 'Odkrivanje';
|
||||
|
||||
@override
|
||||
String get discoverRepeaters => 'Odkrij posrednike';
|
||||
|
||||
@override
|
||||
String get discoverSensors => 'Odkrij senzorje';
|
||||
|
||||
@override
|
||||
String get repeaterDiscoverySent => 'Odkrivanje posrednikov poslano';
|
||||
|
||||
@override
|
||||
String get sensorDiscoverySent => 'Odkrivanje senzorjev poslano';
|
||||
|
||||
@override
|
||||
String get clearedPendingDiscoveries => 'Čakalna odkritja počiščena.';
|
||||
|
||||
@override
|
||||
String get autoDiscovery => 'Samodejno odkrivanje';
|
||||
|
||||
@override
|
||||
String get enableAutomaticAdding => 'Omogoči samodejno dodajanje';
|
||||
|
||||
@override
|
||||
String get autoaddRepeaters => 'Samodejno dodaj posrednike';
|
||||
|
||||
@override
|
||||
String get autoaddRoomServers => 'Samodejno dodaj strežnike sob';
|
||||
|
||||
@override
|
||||
String get autoaddSensors => 'Samodejno dodaj senzorje';
|
||||
|
||||
@override
|
||||
String get autoaddUsers => 'Samodejno dodaj uporabnike';
|
||||
|
||||
@override
|
||||
String get overwriteOldestWhenFull => 'Prepiši najstarejše ob polnem';
|
||||
|
||||
@override
|
||||
String get storage => 'Shramba';
|
||||
|
||||
@override
|
||||
String get dangerZone => 'Nevarno območje';
|
||||
|
||||
@override
|
||||
String get profiles => 'Profili';
|
||||
|
||||
@override
|
||||
String get favourites => 'Priljubljeni';
|
||||
|
||||
@override
|
||||
String get sensors => 'Senzorji';
|
||||
|
||||
@override
|
||||
String get others => 'Ostali';
|
||||
|
||||
@override
|
||||
String get gpsModule => 'GPS modul';
|
||||
|
||||
@override
|
||||
String get liveTraffic => 'Promet v živo';
|
||||
|
||||
@override
|
||||
String get repeatersMap => 'Zemljevid posrednikov';
|
||||
|
||||
@override
|
||||
String get spectrumScan => 'Spektralno skeniranje';
|
||||
|
||||
@override
|
||||
String get blePacketLogs => 'Dnevniki BLE paketov';
|
||||
|
||||
@override
|
||||
String get onlineTraceDatabase => 'Baza sledi';
|
||||
|
||||
@override
|
||||
String get routePathByteSize => 'Velikost poti v bajtih';
|
||||
|
||||
@override
|
||||
String get messageNotifications => 'Obvestila o sporočilih';
|
||||
|
||||
@override
|
||||
String get sarAlerts => 'SAR opozorila';
|
||||
|
||||
@override
|
||||
String get discoveryNotifications => 'Obvestila o odkritjih';
|
||||
|
||||
@override
|
||||
String get updateNotifications => 'Obvestila o posodobitvah';
|
||||
|
||||
@override
|
||||
String get muteWhileAppIsOpen => 'Utišaj ko je aplikacija odprta';
|
||||
|
||||
@override
|
||||
String get disableContacts => 'Onemogoči stike';
|
||||
|
||||
@override
|
||||
String get enableSensorsTab => 'Omogoči zavihek Senzorji';
|
||||
|
||||
@override
|
||||
String get enableProfiles => 'Omogoči profile';
|
||||
|
||||
@override
|
||||
String get autoRouteRotation => 'Samodejno kroženje poti';
|
||||
|
||||
@override
|
||||
String get nearestRepeaterFallback => 'Najbližji posrednik kot rezerva';
|
||||
|
||||
@override
|
||||
String get deleteAllStoredMessageHistory =>
|
||||
'Izbriši vso shranjeno zgodovino sporočil';
|
||||
|
||||
@override
|
||||
String get messageFontSize => 'Velikost pisave sporočil';
|
||||
|
||||
@override
|
||||
String get rotateMapWithHeading => 'Vrti zemljevid s smerjo';
|
||||
|
||||
@override
|
||||
String get showMapDebugInfo => 'Prikaži razhroščevalne info zemljevida';
|
||||
|
||||
@override
|
||||
String get openMapInFullscreen => 'Odpri zemljevid na cel zaslon';
|
||||
|
||||
@override
|
||||
String get showSarMarkersLabel => 'Prikaži SAR oznake';
|
||||
|
||||
@override
|
||||
String get displaySarMarkersOnTheMainMap =>
|
||||
'Prikaži SAR oznake na glavnem zemljevidu';
|
||||
|
||||
@override
|
||||
String get showAllContactTrailsLabel => 'Prikaži vse sledi stikov';
|
||||
|
||||
@override
|
||||
String get hideRepeatersOnMap => 'Skrij posrednike na zemljevidu';
|
||||
|
||||
@override
|
||||
String get setMapScale => 'Nastavi merilo zemljevida';
|
||||
|
||||
@override
|
||||
String get customMapScaleSaved => 'Merilo zemljevida shranjeno';
|
||||
|
||||
@override
|
||||
String get voiceBitrate => 'Bitna hitrost glasu';
|
||||
|
||||
@override
|
||||
String get voiceCompressor => 'Kompresor glasu';
|
||||
|
||||
@override
|
||||
String get balancesQuietAndLoudSpeechLevels =>
|
||||
'Izravnava tihe in glasne govorne ravni';
|
||||
|
||||
@override
|
||||
String get voiceLimiter => 'Omejevalnik glasu';
|
||||
|
||||
@override
|
||||
String get preventsClippingPeaksBeforeEncoding =>
|
||||
'Preprečuje rezanje vrhov pred kodiranjem';
|
||||
|
||||
@override
|
||||
String get micAutoGain => 'Samodejna ojačitev mikrofona';
|
||||
|
||||
@override
|
||||
String get letsTheRecorderAdjustInputLevel =>
|
||||
'Omogoči snemalniku prilagoditev vhodne ravni';
|
||||
|
||||
@override
|
||||
String get echoCancellation => 'Odstranjevanje odmeva';
|
||||
|
||||
@override
|
||||
String get noiseSuppression => 'Dušenje šuma';
|
||||
|
||||
@override
|
||||
String get trimSilenceInVoiceMessages =>
|
||||
'Obreži tišino v glasovnih sporočilih';
|
||||
|
||||
@override
|
||||
String get compressor => 'Kompresor';
|
||||
|
||||
@override
|
||||
String get limiter => 'Omejevalnik';
|
||||
|
||||
@override
|
||||
String get autoGain => 'Samodejna ojačitev';
|
||||
|
||||
@override
|
||||
String get echoCancel => 'Odmev';
|
||||
|
||||
@override
|
||||
String get noiseSuppress => 'Šum';
|
||||
|
||||
@override
|
||||
String get silenceTrim => 'Tišina';
|
||||
|
||||
@override
|
||||
String get maxImageSize => 'Največja velikost slike';
|
||||
|
||||
@override
|
||||
String get imageCompression => 'Stiskanje slik';
|
||||
|
||||
@override
|
||||
String get grayscale => 'Sivine';
|
||||
|
||||
@override
|
||||
String get ultraMode => 'Ultra način';
|
||||
|
||||
@override
|
||||
String get fastPrivateGpsUpdates => 'Hitri zasebni GPS podatki';
|
||||
|
||||
@override
|
||||
String get movementThreshold => 'Prag gibanja';
|
||||
|
||||
@override
|
||||
String get fastGpsMovementThreshold => 'Prag gibanja za hitri GPS';
|
||||
|
||||
@override
|
||||
String get fastGpsActiveuseInterval => 'Interval uporabe hitrega GPS-a';
|
||||
|
||||
@override
|
||||
String get activeuseUpdateInterval =>
|
||||
'Interval posodobitve pri aktivni uporabi';
|
||||
|
||||
@override
|
||||
String get repeatNearbyTraffic => 'Ponavljaj bližnji promet';
|
||||
|
||||
@override
|
||||
String get relayThroughRepeatersAcrossTheMesh =>
|
||||
'Posreduj skozi posrednike po mreži';
|
||||
|
||||
@override
|
||||
String get nearbyOnlyWithoutRepeaterFlooding =>
|
||||
'Samo bližnji, brez poplavljanja posrednikov';
|
||||
|
||||
@override
|
||||
String get multihop => 'Večskočno';
|
||||
|
||||
@override
|
||||
String get createProfile => 'Ustvari profil';
|
||||
|
||||
@override
|
||||
String get renameProfile => 'Preimenuj profil';
|
||||
|
||||
@override
|
||||
String get newProfile => 'Nov profil';
|
||||
|
||||
@override
|
||||
String get manageProfiles => 'Upravljaj profile';
|
||||
|
||||
@override
|
||||
String get enableProfilesToStartManagingThem =>
|
||||
'Omogočite profile, da jih začnete upravljati.';
|
||||
|
||||
@override
|
||||
String get openMessage => 'Odpri sporočilo';
|
||||
|
||||
@override
|
||||
String get jumpToTheRelatedSarMessage => 'Skoči na povezano SAR sporočilo';
|
||||
|
||||
@override
|
||||
String get removeSarMarker => 'Odstrani SAR oznako';
|
||||
|
||||
@override
|
||||
String get pleaseSelectADestinationToSendSarMarker =>
|
||||
'Izberite cilj za pošiljanje SAR oznake';
|
||||
|
||||
@override
|
||||
String get sarMarkerBroadcastToPublicChannel =>
|
||||
'SAR oznaka poslana na javni kanal';
|
||||
|
||||
@override
|
||||
String get sarMarkerSentToRoom => 'SAR oznaka poslana v sobo';
|
||||
|
||||
@override
|
||||
String get loadFromGallery => 'Naloži iz galerije';
|
||||
|
||||
@override
|
||||
String get replaceImage => 'Zamenjaj sliko';
|
||||
|
||||
@override
|
||||
String get selectFromGallery => 'Izberi iz galerije';
|
||||
|
||||
@override
|
||||
String get team => 'Ekipa';
|
||||
|
||||
@override
|
||||
String get found => 'Najdeno';
|
||||
|
||||
@override
|
||||
String get staging => 'Zbirno mesto';
|
||||
|
||||
@override
|
||||
String get object => 'Predmet';
|
||||
|
||||
@override
|
||||
String get quiet => 'Tiho';
|
||||
|
||||
@override
|
||||
String get moderate => 'Zmerno';
|
||||
|
||||
@override
|
||||
String get busy => 'Zasedeno';
|
||||
|
||||
@override
|
||||
String get spectrumScanReturnedNoCandidateFrequencies =>
|
||||
'Spektralno skeniranje ni našlo primernih frekvenc';
|
||||
|
||||
@override
|
||||
String get searchMessages => 'Iskanje sporočil';
|
||||
|
||||
@override
|
||||
String get sendImageFromGallery => 'Pošlji sliko iz galerije';
|
||||
|
||||
@override
|
||||
String get takePhoto => 'Posnemi fotografijo';
|
||||
|
||||
@override
|
||||
String get dmOnly => 'Samo neposredno';
|
||||
|
||||
@override
|
||||
String get allMessages => 'Vsa sporočila';
|
||||
|
||||
@override
|
||||
String get sendToPublicChannel => 'Pošlji na javni kanal?';
|
||||
|
||||
@override
|
||||
String get selectMarkerTypeAndDestination => 'Izberite vrsto oznake in cilj';
|
||||
|
||||
@override
|
||||
String get noDestinationsAvailableLabel => 'Ni razpoložljivih ciljev';
|
||||
|
||||
@override
|
||||
String get image => 'Slika';
|
||||
|
||||
@override
|
||||
String get format => 'Format';
|
||||
|
||||
@override
|
||||
String get dimensions => 'Dimenzije';
|
||||
|
||||
@override
|
||||
String get segments => 'Segmenti';
|
||||
|
||||
@override
|
||||
String get transfers => 'Prenosi';
|
||||
|
||||
@override
|
||||
String get downloadedBy => 'Preneseno od';
|
||||
|
||||
@override
|
||||
String get saveDiscoverySettings => 'Shrani nastavitve odkrivanja';
|
||||
|
||||
@override
|
||||
String get savePublicInfo => 'Shrani javne podatke';
|
||||
|
||||
@override
|
||||
String get saveRadioSettings => 'Shrani radijske nastavitve';
|
||||
|
||||
@override
|
||||
String get savePath => 'Shrani pot';
|
||||
|
||||
@override
|
||||
String get wipeDeviceData => 'Izbriši podatke naprave';
|
||||
|
||||
@override
|
||||
String get wipeDevice => 'Izbriši napravo';
|
||||
|
||||
@override
|
||||
String get destructiveDeviceActions => 'Destruktivna dejanja na napravi.';
|
||||
|
||||
@override
|
||||
String get chooseAPresetOrFinetuneCustomRadioSettings =>
|
||||
'Izberite prednastavitev ali natančno prilagodite radijske nastavitve.';
|
||||
|
||||
@override
|
||||
String get chooseTheNameAndLocationThisDeviceShares =>
|
||||
'Izberite ime in lokacijo, ki ju naprava deli.';
|
||||
|
||||
@override
|
||||
String get availableSpaceOnThisDevice =>
|
||||
'Razpoložljiv prostor na tej napravi.';
|
||||
|
||||
@override
|
||||
String get used => 'Uporabljeno';
|
||||
|
||||
@override
|
||||
String get total => 'Skupaj';
|
||||
|
||||
@override
|
||||
String get renameValue => 'Preimenuj vrednost';
|
||||
|
||||
@override
|
||||
String get customizeFields => 'Prilagodi polja';
|
||||
|
||||
@override
|
||||
String get livePreview => 'Predogled v živo';
|
||||
|
||||
@override
|
||||
String get refreshSchedule => 'Interval osvežitve';
|
||||
|
||||
@override
|
||||
String get noResponse => 'Ni odgovora';
|
||||
|
||||
@override
|
||||
String get refreshing => 'Osvežujem';
|
||||
|
||||
@override
|
||||
String get unavailable => 'Nedostopno';
|
||||
|
||||
@override
|
||||
String get pickARelayOrNodeToWatchInSensors =>
|
||||
'Izberite posrednika ali vozlišče za opazovanje.';
|
||||
|
||||
@override
|
||||
String get publicKeyLabel => 'Javni ključ';
|
||||
|
||||
@override
|
||||
String get alreadyInContacts => 'Že med stiki';
|
||||
|
||||
@override
|
||||
String get connectToADeviceBeforeAddingContacts =>
|
||||
'Povežite se z napravo pred dodajanjem stikov';
|
||||
|
||||
@override
|
||||
String get fromContacts => 'Iz stikov';
|
||||
|
||||
@override
|
||||
String get onlineOnly => 'Samo povezani';
|
||||
|
||||
@override
|
||||
String get inBoth => 'V obeh';
|
||||
|
||||
@override
|
||||
String get source => 'Vir';
|
||||
|
||||
@override
|
||||
String get manualRouteEdit => 'Ročno urejanje poti';
|
||||
|
||||
@override
|
||||
String get observedMeshRoute => 'Opazovana mrežna pot';
|
||||
|
||||
@override
|
||||
String get allMessagesCleared => 'Vsa sporočila počiščena';
|
||||
|
||||
@override
|
||||
String get onlineTraceDatabaseCleared => 'Baza sledi počiščena';
|
||||
|
||||
@override
|
||||
String get packetLogsCleared => 'Dnevniki paketov počiščeni';
|
||||
|
||||
@override
|
||||
String get hexDataCopiedToClipboard => 'Hex podatki kopirani v odložišče';
|
||||
|
||||
@override
|
||||
String get developerModeEnabled => 'Razvojni način omogočen';
|
||||
|
||||
@override
|
||||
String get developerModeDisabled => 'Razvojni način onemogočen';
|
||||
|
||||
@override
|
||||
String get clipboardIsEmpty => 'Odložišče je prazno';
|
||||
|
||||
@override
|
||||
String get contactImported => 'Stik uvožen';
|
||||
|
||||
@override
|
||||
String get contactLinkCopiedToClipboard =>
|
||||
'Povezava na stik kopirana v odložišče';
|
||||
|
||||
@override
|
||||
String get failedToExportContact => 'Izvoz stika ni uspel';
|
||||
|
||||
@override
|
||||
String get noLogsToExport => 'Ni dnevnikov za izvoz';
|
||||
|
||||
@override
|
||||
String get exportAsCsv => 'Izvozi kot CSV';
|
||||
|
||||
@override
|
||||
String get exportAsText => 'Izvozi kot besedilo';
|
||||
|
||||
@override
|
||||
String get receivedRfc3339 => 'Prejeto (RFC3339)';
|
||||
|
||||
@override
|
||||
String get buildTime => 'Čas gradnje';
|
||||
|
||||
@override
|
||||
String get downloadUrlNotAvailable => 'URL za prenos ni na voljo';
|
||||
|
||||
@override
|
||||
String get cannotOpenDownloadUrl => 'URL za prenos ni mogoče odpreti';
|
||||
|
||||
@override
|
||||
String get updateCheckIsOnlyAvailableOnAndroid =>
|
||||
'Preverjanje posodobitev je na voljo le na Androidu';
|
||||
|
||||
@override
|
||||
String get youAreRunningTheLatestVersion =>
|
||||
'Uporabljate najnovejšo različico';
|
||||
|
||||
@override
|
||||
String get updateAvailableButDownloadUrlNotFound =>
|
||||
'Posodobitev na voljo, a URL za prenos ni najden';
|
||||
|
||||
@override
|
||||
String get startTictactoe => 'Začni Tic-Tac-Toe';
|
||||
|
||||
@override
|
||||
String get tictactoeUnavailable => 'Tic-Tac-Toe ni na voljo';
|
||||
|
||||
@override
|
||||
String get tictactoeOpponentUnknown => 'Tic-Tac-Toe: nasprotnik neznan';
|
||||
|
||||
@override
|
||||
String get tictactoeWaitingForStart => 'Tic-Tac-Toe: čakanje na začetek';
|
||||
|
||||
@override
|
||||
String get acceptsShareLinks => 'Sprejema deljene povezave';
|
||||
|
||||
@override
|
||||
String get supportsRawHex => 'Podpira surove hex';
|
||||
|
||||
@override
|
||||
String get clipboardfriendly => 'Primerno za odložišče';
|
||||
|
||||
@override
|
||||
String get captured => 'Zajeto';
|
||||
|
||||
@override
|
||||
String get size => 'Velikost';
|
||||
|
||||
@override
|
||||
String get noCustomChannelsToClear => 'Ni prilagojenih kanalov za brisanje.';
|
||||
|
||||
@override
|
||||
String get noDeviceContactsToClear => 'Ni stikov naprave za brisanje.';
|
||||
}
|
||||
|
||||
@@ -1720,4 +1720,957 @@ class AppLocalizationsTr extends AppLocalizations {
|
||||
|
||||
@override
|
||||
String get requestingVoice => 'Ses isteniyor';
|
||||
|
||||
@override
|
||||
String get device => 'cihaz';
|
||||
|
||||
@override
|
||||
String get change => 'Değiştir';
|
||||
|
||||
@override
|
||||
String get wizardOverviewDescription =>
|
||||
'Bu uygulama MeshCore mesajlaşmasını, SAR saha güncellemelerini, haritalamayı ve cihaz araçlarını tek bir yerde birleştirir.';
|
||||
|
||||
@override
|
||||
String get wizardOverviewFeature1 =>
|
||||
'Ana Mesajlar sekmesinden doğrudan mesajlar, oda gönderileri ve kanal mesajları gönderin.';
|
||||
|
||||
@override
|
||||
String get wizardOverviewFeature2 =>
|
||||
'Mesh üzerinden SAR işaretleri, harita çizimleri, ses klipleri ve görseller paylaşın.';
|
||||
|
||||
@override
|
||||
String get wizardOverviewFeature3 =>
|
||||
'BLE veya TCP ile bağlanın, ardından yardımcı radyoyu uygulamanın içinden yönetin.';
|
||||
|
||||
@override
|
||||
String get wizardMessagingTitle => 'Mesajlaşma ve saha raporları';
|
||||
|
||||
@override
|
||||
String get wizardMessagingDescription =>
|
||||
'Buradaki mesajlar düz metinden fazlasıdır. Uygulama zaten çeşitli operasyonel yükleri ve aktarım akışlarını destekliyor.';
|
||||
|
||||
@override
|
||||
String get wizardMessagingFeature1 =>
|
||||
'Doğrudan mesajları, oda gönderilerini ve kanal trafiğini tek bir düzenleyiciden gönderin.';
|
||||
|
||||
@override
|
||||
String get wizardMessagingFeature2 =>
|
||||
'Yaygın saha raporları için SAR güncellemeleri ve yeniden kullanılabilir SAR şablonları oluşturun.';
|
||||
|
||||
@override
|
||||
String get wizardMessagingFeature3 =>
|
||||
'Arayüzde ilerleme ve yayın süresi tahminleriyle ses oturumları ve görseller aktarın.';
|
||||
|
||||
@override
|
||||
String get wizardConnectDeviceTitle => 'Cihazı bağla';
|
||||
|
||||
@override
|
||||
String get wizardConnectDeviceDescription =>
|
||||
'MeshCore radyonuzu bağlayın, bir ad seçin ve devam etmeden önce bir radyo ön ayarı uygulayın.';
|
||||
|
||||
@override
|
||||
String get wizardSetupBadge => 'Kurulum';
|
||||
|
||||
@override
|
||||
String get wizardOverviewBadge => 'Genel bakış';
|
||||
|
||||
@override
|
||||
String wizardConnectedToDevice(String deviceName) {
|
||||
return '$deviceName cihazına bağlandı';
|
||||
}
|
||||
|
||||
@override
|
||||
String get wizardNoDeviceConnected => 'Henüz bağlı cihaz yok';
|
||||
|
||||
@override
|
||||
String get wizardSkipForNow => 'Şimdilik atla';
|
||||
|
||||
@override
|
||||
String get wizardDeviceNameLabel => 'Cihaz adı';
|
||||
|
||||
@override
|
||||
String get wizardDeviceNameHelp =>
|
||||
'Bu ad diğer MeshCore kullanıcılarına duyurulur.';
|
||||
|
||||
@override
|
||||
String get wizardConfigRegionLabel => 'Yapılandırma bölgesi';
|
||||
|
||||
@override
|
||||
String get wizardConfigRegionHelp =>
|
||||
'MeshCore\'un resmi ön ayar listesinin tamamını kullanır. Varsayılan EU/UK (Narrow) olur.';
|
||||
|
||||
@override
|
||||
String get wizardPresetNote1 =>
|
||||
'Seçilen ön ayarın yerel radyo düzenlemelerinize uygun olduğundan emin olun.';
|
||||
|
||||
@override
|
||||
String get wizardPresetNote2 =>
|
||||
'Liste, MeshCore yapılandırma aracının resmi ön ayar akışıyla eşleşir.';
|
||||
|
||||
@override
|
||||
String get wizardPresetNote3 =>
|
||||
'Onboarding sırasında varsayılan olarak EU/UK (Narrow) seçili kalır.';
|
||||
|
||||
@override
|
||||
String get wizardSaving => 'Kaydediliyor...';
|
||||
|
||||
@override
|
||||
String get wizardSaveAndContinue => 'Kaydet ve devam et';
|
||||
|
||||
@override
|
||||
String get wizardEnterDeviceName => 'Devam etmeden önce bir cihaz adı girin.';
|
||||
|
||||
@override
|
||||
String wizardDeviceSetupSaved(String deviceName, String presetName) {
|
||||
return '$deviceName, $presetName ile kaydedildi.';
|
||||
}
|
||||
|
||||
@override
|
||||
String get wizardNetworkTitle => 'Kişiler, odalar ve tekrarlayıcılar';
|
||||
|
||||
@override
|
||||
String get wizardNetworkDescription =>
|
||||
'Kişiler sekmesi keşfettiğiniz ağı ve zamanla öğrendiğiniz rotaları düzenler.';
|
||||
|
||||
@override
|
||||
String get wizardNetworkFeature1 =>
|
||||
'Ekip üyelerini, tekrarlayıcıları, odaları, kanalları ve bekleyen duyuruları tek listede inceleyin.';
|
||||
|
||||
@override
|
||||
String get wizardNetworkFeature2 =>
|
||||
'Bağlantı karmaşıklaştığında smart ping, oda girişi, öğrenilen yollar ve rota sıfırlama araçlarını kullanın.';
|
||||
|
||||
@override
|
||||
String get wizardNetworkFeature3 =>
|
||||
'Uygulamadan çıkmadan kanallar oluşturun ve ağ hedeflerini yönetin.';
|
||||
|
||||
@override
|
||||
String get wizardMapOpsTitle => 'Harita, izler ve paylaşılan geometri';
|
||||
|
||||
@override
|
||||
String get wizardMapOpsDescription =>
|
||||
'Uygulama haritası ayrı bir görüntüleyici olmak yerine mesajlaşma, takip ve SAR katmanlarıyla doğrudan bağlantılıdır.';
|
||||
|
||||
@override
|
||||
String get wizardMapOpsFeature1 =>
|
||||
'Kendi konumunuzu, ekip arkadaşlarının konumlarını ve hareket izlerini harita üzerinde takip edin.';
|
||||
|
||||
@override
|
||||
String get wizardMapOpsFeature2 =>
|
||||
'Mesajlardaki çizimleri açın, satır içinde önizleyin ve gerektiğinde haritadan kaldırın.';
|
||||
|
||||
@override
|
||||
String get wizardMapOpsFeature3 =>
|
||||
'Sahadaki ağ kapsamasını anlamak için tekrarlayıcı harita görünümleri ve paylaşılan katmanları kullanın.';
|
||||
|
||||
@override
|
||||
String get wizardToolsTitle => 'Mesajlaşma dışındaki araçlar';
|
||||
|
||||
@override
|
||||
String get wizardToolsDescription =>
|
||||
'Burada dört ana sekmeden fazlası var. Uygulama ayrıca yapılandırma, tanılama ve isteğe bağlı sensör akışları içerir.';
|
||||
|
||||
@override
|
||||
String get wizardToolsFeature1 =>
|
||||
'Radyo ayarlarını, telemetriyi, TX gücünü ve yardımcı cihaz ayrıntılarını değiştirmek için cihaz yapılandırmasını açın.';
|
||||
|
||||
@override
|
||||
String get wizardToolsFeature2 =>
|
||||
'İzlenen sensör panoları ve hızlı yenileme eylemleri istediğinizde Sensörler sekmesini etkinleştirin.';
|
||||
|
||||
@override
|
||||
String get wizardToolsFeature3 =>
|
||||
'Mesh sorunlarını giderirken paket günlükleri, spektrum taraması ve geliştirici tanılamalarını kullanın.';
|
||||
|
||||
@override
|
||||
String get contactInSensors => 'Sensörlerde';
|
||||
|
||||
@override
|
||||
String get contactAddToSensors => 'Sensörlere ekle';
|
||||
|
||||
@override
|
||||
String get contactSetPath => 'Yol ayarla';
|
||||
|
||||
@override
|
||||
String contactAddedToSensors(String contactName) {
|
||||
return '$contactName Sensörlere eklendi';
|
||||
}
|
||||
|
||||
@override
|
||||
String contactFailedToClearRoute(String error) {
|
||||
return 'Rota temizlenemedi: $error';
|
||||
}
|
||||
|
||||
@override
|
||||
String get contactRouteCleared => 'Rota temizlendi';
|
||||
|
||||
@override
|
||||
String contactRouteSet(String route) {
|
||||
return 'Rota ayarlandı: $route';
|
||||
}
|
||||
|
||||
@override
|
||||
String contactFailedToSetRoute(String error) {
|
||||
return 'Rota ayarlanamadı: $error';
|
||||
}
|
||||
|
||||
@override
|
||||
String get rssi => 'RSSI';
|
||||
|
||||
@override
|
||||
String get snr => 'SNR';
|
||||
|
||||
@override
|
||||
String get ackTimeout => 'ACK zaman aşımı';
|
||||
|
||||
@override
|
||||
String get opcode => 'Opcode';
|
||||
|
||||
@override
|
||||
String get payload => 'Yük';
|
||||
|
||||
@override
|
||||
String get hops => 'Atlama';
|
||||
|
||||
@override
|
||||
String get hashSize => 'Hash boyutu';
|
||||
|
||||
@override
|
||||
String get pathBytes => 'Yol baytları';
|
||||
|
||||
@override
|
||||
String get selectedPath => 'Seçilen yol';
|
||||
|
||||
@override
|
||||
String get estimatedTx => 'Tahmini gönderim';
|
||||
|
||||
@override
|
||||
String get senderToReceipt => 'Gönderenden alındıya';
|
||||
|
||||
@override
|
||||
String get receivedCopies => 'Alınan kopyalar';
|
||||
|
||||
@override
|
||||
String get retryCause => 'Yeniden deneme nedeni';
|
||||
|
||||
@override
|
||||
String get retryMode => 'Yeniden deneme modu';
|
||||
|
||||
@override
|
||||
String get retryResult => 'Yeniden deneme sonucu';
|
||||
|
||||
@override
|
||||
String get lastRetry => 'Son deneme';
|
||||
|
||||
@override
|
||||
String get rxPackets => 'RX paketleri';
|
||||
|
||||
@override
|
||||
String get mesh => 'Mesh';
|
||||
|
||||
@override
|
||||
String get rate => 'Hız';
|
||||
|
||||
@override
|
||||
String get window => 'Pencere';
|
||||
|
||||
@override
|
||||
String get posttxDelay => 'Gönderim sonrası gecikme';
|
||||
|
||||
@override
|
||||
String get bandpass => 'Bant geçiren';
|
||||
|
||||
@override
|
||||
String get bandpassFilterVoice => 'Bant geçiren filtre ses';
|
||||
|
||||
@override
|
||||
String get frequency => 'Frekans';
|
||||
|
||||
@override
|
||||
String get australia => 'Avustralya';
|
||||
|
||||
@override
|
||||
String get australiaNarrow => 'Avustralya (Dar)';
|
||||
|
||||
@override
|
||||
String get australiaQld => 'Avustralya: QLD';
|
||||
|
||||
@override
|
||||
String get australiaSaWa => 'Avustralya: SA, WA';
|
||||
|
||||
@override
|
||||
String get newZealand => 'Yeni Zelanda';
|
||||
|
||||
@override
|
||||
String get newZealandNarrow => 'Yeni Zelanda (Dar)';
|
||||
|
||||
@override
|
||||
String get switzerland => 'İsviçre';
|
||||
|
||||
@override
|
||||
String get portugal433 => 'Portekiz 433';
|
||||
|
||||
@override
|
||||
String get portugal868 => 'Portekiz 868';
|
||||
|
||||
@override
|
||||
String get czechRepublicNarrow => 'Çek Cumhuriyeti (Dar)';
|
||||
|
||||
@override
|
||||
String get eu433mhzLongRange => 'AB 433MHz (Uzun Menzil)';
|
||||
|
||||
@override
|
||||
String get euukDeprecated => 'AB/BK (Kullanımdan kalktı)';
|
||||
|
||||
@override
|
||||
String get euukNarrow => 'AB/BK (Dar)';
|
||||
|
||||
@override
|
||||
String get usacanadaRecommended => 'ABD/Kanada (Önerilen)';
|
||||
|
||||
@override
|
||||
String get vietnamDeprecated => 'Vietnam (Kullanımdan kalktı)';
|
||||
|
||||
@override
|
||||
String get vietnamNarrow => 'Vietnam (Dar)';
|
||||
|
||||
@override
|
||||
String get active => 'Aktif';
|
||||
|
||||
@override
|
||||
String get addContact => 'Kişi ekle';
|
||||
|
||||
@override
|
||||
String get all => 'Tümü';
|
||||
|
||||
@override
|
||||
String get autoResolve => 'Otomatik çöz';
|
||||
|
||||
@override
|
||||
String get clearAllLabel => 'Tümünü temizle';
|
||||
|
||||
@override
|
||||
String get clearRelays => 'Röleleri temizle';
|
||||
|
||||
@override
|
||||
String get clearFilters => 'Filtreleri temizle';
|
||||
|
||||
@override
|
||||
String get clearRoute => 'Rotayı temizle';
|
||||
|
||||
@override
|
||||
String get clearMessages => 'Mesajları temizle';
|
||||
|
||||
@override
|
||||
String get clearScale => 'Ölçeği temizle';
|
||||
|
||||
@override
|
||||
String get clearDiscoveries => 'Keşifleri temizle';
|
||||
|
||||
@override
|
||||
String get clearOnlineTraceDatabase => 'Çevrimiçi iz veritabanını temizle';
|
||||
|
||||
@override
|
||||
String get clearAllChannels => 'Tüm kanalları temizle';
|
||||
|
||||
@override
|
||||
String get clearAllContacts => 'Tüm kişileri temizle';
|
||||
|
||||
@override
|
||||
String get clearChannels => 'Kanalları temizle';
|
||||
|
||||
@override
|
||||
String get clearContacts => 'Kişileri temizle';
|
||||
|
||||
@override
|
||||
String get clearPathOnMaxRetry => 'Maks denemede yolu temizle';
|
||||
|
||||
@override
|
||||
String get create => 'Oluştur';
|
||||
|
||||
@override
|
||||
String get custom => 'Özel';
|
||||
|
||||
@override
|
||||
String get defaultValue => 'Varsayılan';
|
||||
|
||||
@override
|
||||
String get duplicate => 'Çoğalt';
|
||||
|
||||
@override
|
||||
String get editName => 'Adı düzenle';
|
||||
|
||||
@override
|
||||
String get open => 'Aç';
|
||||
|
||||
@override
|
||||
String get paste => 'Yapıştır';
|
||||
|
||||
@override
|
||||
String get preview => 'Önizleme';
|
||||
|
||||
@override
|
||||
String get remove => 'Kaldır';
|
||||
|
||||
@override
|
||||
String get rename => 'Yeniden adlandır';
|
||||
|
||||
@override
|
||||
String get resolveAll => 'Tümünü çöz';
|
||||
|
||||
@override
|
||||
String get send => 'Gönder';
|
||||
|
||||
@override
|
||||
String get sendAnyway => 'Yine de gönder';
|
||||
|
||||
@override
|
||||
String get share => 'Paylaş';
|
||||
|
||||
@override
|
||||
String get shareContact => 'Kişiyi paylaş';
|
||||
|
||||
@override
|
||||
String get trace => 'İz';
|
||||
|
||||
@override
|
||||
String get use => 'Kullan';
|
||||
|
||||
@override
|
||||
String get useSelectedFrequency => 'Seçilen frekansı kullan';
|
||||
|
||||
@override
|
||||
String get discovery => 'Keşif';
|
||||
|
||||
@override
|
||||
String get discoverRepeaters => 'Tekrarlayıcıları keşfet';
|
||||
|
||||
@override
|
||||
String get discoverSensors => 'Sensörleri keşfet';
|
||||
|
||||
@override
|
||||
String get repeaterDiscoverySent => 'Tekrarlayıcı keşfi gönderildi';
|
||||
|
||||
@override
|
||||
String get sensorDiscoverySent => 'Sensör keşfi gönderildi';
|
||||
|
||||
@override
|
||||
String get clearedPendingDiscoveries => 'Bekleyen keşifler temizlendi.';
|
||||
|
||||
@override
|
||||
String get autoDiscovery => 'Otomatik keşif';
|
||||
|
||||
@override
|
||||
String get enableAutomaticAdding => 'Otomatik eklemeyi etkinleştir';
|
||||
|
||||
@override
|
||||
String get autoaddRepeaters => 'Tekrarlayıcıları otomatik ekle';
|
||||
|
||||
@override
|
||||
String get autoaddRoomServers => 'Oda sunucularını otomatik ekle';
|
||||
|
||||
@override
|
||||
String get autoaddSensors => 'Sensörleri otomatik ekle';
|
||||
|
||||
@override
|
||||
String get autoaddUsers => 'Kullanıcıları otomatik ekle';
|
||||
|
||||
@override
|
||||
String get overwriteOldestWhenFull => 'Dolduğunda en eskiyi üzerine yaz';
|
||||
|
||||
@override
|
||||
String get storage => 'Depolama';
|
||||
|
||||
@override
|
||||
String get dangerZone => 'Tehlikeli bölge';
|
||||
|
||||
@override
|
||||
String get profiles => 'Profiller';
|
||||
|
||||
@override
|
||||
String get favourites => 'Favoriler';
|
||||
|
||||
@override
|
||||
String get sensors => 'Sensörler';
|
||||
|
||||
@override
|
||||
String get others => 'Diğerleri';
|
||||
|
||||
@override
|
||||
String get gpsModule => 'GPS Modülü';
|
||||
|
||||
@override
|
||||
String get liveTraffic => 'Canlı trafik';
|
||||
|
||||
@override
|
||||
String get repeatersMap => 'Tekrarlayıcılar haritası';
|
||||
|
||||
@override
|
||||
String get spectrumScan => 'Spektrum taraması';
|
||||
|
||||
@override
|
||||
String get blePacketLogs => 'BLE paket günlükleri';
|
||||
|
||||
@override
|
||||
String get onlineTraceDatabase => 'Çevrimiçi iz veritabanı';
|
||||
|
||||
@override
|
||||
String get routePathByteSize => 'Yol bayt boyutu';
|
||||
|
||||
@override
|
||||
String get messageNotifications => 'Mesaj bildirimleri';
|
||||
|
||||
@override
|
||||
String get sarAlerts => 'SAR uyarıları';
|
||||
|
||||
@override
|
||||
String get discoveryNotifications => 'Keşif bildirimleri';
|
||||
|
||||
@override
|
||||
String get updateNotifications => 'Güncelleme bildirimleri';
|
||||
|
||||
@override
|
||||
String get muteWhileAppIsOpen => 'Uygulama açıkken sessize al';
|
||||
|
||||
@override
|
||||
String get disableContacts => 'Kişileri devre dışı bırak';
|
||||
|
||||
@override
|
||||
String get enableSensorsTab => 'Sensörler sekmesini etkinleştir';
|
||||
|
||||
@override
|
||||
String get enableProfiles => 'Profilleri etkinleştir';
|
||||
|
||||
@override
|
||||
String get autoRouteRotation => 'Otomatik rota rotasyonu';
|
||||
|
||||
@override
|
||||
String get nearestRepeaterFallback => 'En yakın tekrarlayıcıya geri dönüş';
|
||||
|
||||
@override
|
||||
String get deleteAllStoredMessageHistory => 'Tüm mesaj geçmişini sil';
|
||||
|
||||
@override
|
||||
String get messageFontSize => 'Mesaj yazı tipi boyutu';
|
||||
|
||||
@override
|
||||
String get rotateMapWithHeading => 'Haritayı yön ile döndür';
|
||||
|
||||
@override
|
||||
String get showMapDebugInfo => 'Harita hata ayıklama bilgisini göster';
|
||||
|
||||
@override
|
||||
String get openMapInFullscreen => 'Haritayı tam ekranda aç';
|
||||
|
||||
@override
|
||||
String get showSarMarkersLabel => 'SAR işaretçilerini göster';
|
||||
|
||||
@override
|
||||
String get displaySarMarkersOnTheMainMap =>
|
||||
'SAR işaretçilerini ana haritada göster';
|
||||
|
||||
@override
|
||||
String get showAllContactTrailsLabel => 'Tüm kişi izlerini göster';
|
||||
|
||||
@override
|
||||
String get hideRepeatersOnMap => 'Haritada tekrarlayıcıları gizle';
|
||||
|
||||
@override
|
||||
String get setMapScale => 'Harita ölçeğini ayarla';
|
||||
|
||||
@override
|
||||
String get customMapScaleSaved => 'Özel harita ölçeği kaydedildi';
|
||||
|
||||
@override
|
||||
String get voiceBitrate => 'Ses bit hızı';
|
||||
|
||||
@override
|
||||
String get voiceCompressor => 'Ses sıkıştırıcı';
|
||||
|
||||
@override
|
||||
String get balancesQuietAndLoudSpeechLevels =>
|
||||
'Sessiz ve yüksek konuşma seviyelerini dengeler';
|
||||
|
||||
@override
|
||||
String get voiceLimiter => 'Ses sınırlayıcı';
|
||||
|
||||
@override
|
||||
String get preventsClippingPeaksBeforeEncoding =>
|
||||
'Kodlama öncesi tepe kırpmasını önler';
|
||||
|
||||
@override
|
||||
String get micAutoGain => 'Mikrofon otomatik kazancı';
|
||||
|
||||
@override
|
||||
String get letsTheRecorderAdjustInputLevel =>
|
||||
'Kaydedicinin giriş seviyesini ayarlamasına izin verir';
|
||||
|
||||
@override
|
||||
String get echoCancellation => 'Yankı iptali';
|
||||
|
||||
@override
|
||||
String get noiseSuppression => 'Gürültü bastırma';
|
||||
|
||||
@override
|
||||
String get trimSilenceInVoiceMessages => 'Sesli mesajlarda sessizliği kırp';
|
||||
|
||||
@override
|
||||
String get compressor => 'Sıkıştırıcı';
|
||||
|
||||
@override
|
||||
String get limiter => 'Sınırlayıcı';
|
||||
|
||||
@override
|
||||
String get autoGain => 'Otomatik kazanç';
|
||||
|
||||
@override
|
||||
String get echoCancel => 'Yankı';
|
||||
|
||||
@override
|
||||
String get noiseSuppress => 'Gürültü';
|
||||
|
||||
@override
|
||||
String get silenceTrim => 'Sessizlik';
|
||||
|
||||
@override
|
||||
String get maxImageSize => 'Maksimum resim boyutu';
|
||||
|
||||
@override
|
||||
String get imageCompression => 'Resim sıkıştırma';
|
||||
|
||||
@override
|
||||
String get grayscale => 'Gri tonlama';
|
||||
|
||||
@override
|
||||
String get ultraMode => 'Ultra mod';
|
||||
|
||||
@override
|
||||
String get fastPrivateGpsUpdates => 'Hızlı özel GPS güncellemeleri';
|
||||
|
||||
@override
|
||||
String get movementThreshold => 'Hareket eşiği';
|
||||
|
||||
@override
|
||||
String get fastGpsMovementThreshold => 'Hızlı GPS hareket eşiği';
|
||||
|
||||
@override
|
||||
String get fastGpsActiveuseInterval => 'Hızlı GPS aktif kullanım aralığı';
|
||||
|
||||
@override
|
||||
String get activeuseUpdateInterval => 'Aktif kullanım güncelleme aralığı';
|
||||
|
||||
@override
|
||||
String get repeatNearbyTraffic => 'Yakın trafiği tekrarla';
|
||||
|
||||
@override
|
||||
String get relayThroughRepeatersAcrossTheMesh =>
|
||||
'Ağ üzerindeki tekrarlayıcılar aracılığıyla ilet';
|
||||
|
||||
@override
|
||||
String get nearbyOnlyWithoutRepeaterFlooding =>
|
||||
'Yalnızca yakın, tekrarlayıcı taşkını olmadan';
|
||||
|
||||
@override
|
||||
String get multihop => 'Çok atlamalı';
|
||||
|
||||
@override
|
||||
String get createProfile => 'Profil oluştur';
|
||||
|
||||
@override
|
||||
String get renameProfile => 'Profili yeniden adlandır';
|
||||
|
||||
@override
|
||||
String get newProfile => 'Yeni profil';
|
||||
|
||||
@override
|
||||
String get manageProfiles => 'Profilleri yönet';
|
||||
|
||||
@override
|
||||
String get enableProfilesToStartManagingThem =>
|
||||
'Profilleri yönetmeye başlamak için etkinleştirin.';
|
||||
|
||||
@override
|
||||
String get openMessage => 'Mesajı aç';
|
||||
|
||||
@override
|
||||
String get jumpToTheRelatedSarMessage => 'İlgili SAR mesajına git';
|
||||
|
||||
@override
|
||||
String get removeSarMarker => 'SAR işaretçisini kaldır';
|
||||
|
||||
@override
|
||||
String get pleaseSelectADestinationToSendSarMarker =>
|
||||
'SAR işaretçisi göndermek için bir hedef seçin';
|
||||
|
||||
@override
|
||||
String get sarMarkerBroadcastToPublicChannel =>
|
||||
'SAR işaretçisi genel kanala yayınlandı';
|
||||
|
||||
@override
|
||||
String get sarMarkerSentToRoom => 'SAR işaretçisi odaya gönderildi';
|
||||
|
||||
@override
|
||||
String get loadFromGallery => 'Galeriden yükle';
|
||||
|
||||
@override
|
||||
String get replaceImage => 'Resmi değiştir';
|
||||
|
||||
@override
|
||||
String get selectFromGallery => 'Galeriden seç';
|
||||
|
||||
@override
|
||||
String get team => 'Takım';
|
||||
|
||||
@override
|
||||
String get found => 'Bulundu';
|
||||
|
||||
@override
|
||||
String get staging => 'Toplanma alanı';
|
||||
|
||||
@override
|
||||
String get object => 'Nesne';
|
||||
|
||||
@override
|
||||
String get quiet => 'Sessiz';
|
||||
|
||||
@override
|
||||
String get moderate => 'Orta';
|
||||
|
||||
@override
|
||||
String get busy => 'Meşgul';
|
||||
|
||||
@override
|
||||
String get spectrumScanReturnedNoCandidateFrequencies =>
|
||||
'Spektrum taraması aday frekans bulamadı';
|
||||
|
||||
@override
|
||||
String get searchMessages => 'Mesajlarda ara';
|
||||
|
||||
@override
|
||||
String get sendImageFromGallery => 'Galeriden resim gönder';
|
||||
|
||||
@override
|
||||
String get takePhoto => 'Fotoğraf çek';
|
||||
|
||||
@override
|
||||
String get dmOnly => 'Yalnızca DM';
|
||||
|
||||
@override
|
||||
String get allMessages => 'Tüm mesajlar';
|
||||
|
||||
@override
|
||||
String get sendToPublicChannel => 'Genel kanala gönderilsin mi?';
|
||||
|
||||
@override
|
||||
String get selectMarkerTypeAndDestination =>
|
||||
'İşaretçi türünü ve hedefi seçin';
|
||||
|
||||
@override
|
||||
String get noDestinationsAvailableLabel => 'Kullanılabilir hedef yok';
|
||||
|
||||
@override
|
||||
String get image => 'Resim';
|
||||
|
||||
@override
|
||||
String get format => 'Biçim';
|
||||
|
||||
@override
|
||||
String get dimensions => 'Boyutlar';
|
||||
|
||||
@override
|
||||
String get segments => 'Segmentler';
|
||||
|
||||
@override
|
||||
String get transfers => 'Transferler';
|
||||
|
||||
@override
|
||||
String get downloadedBy => 'İndiren';
|
||||
|
||||
@override
|
||||
String get saveDiscoverySettings => 'Keşif ayarlarını kaydet';
|
||||
|
||||
@override
|
||||
String get savePublicInfo => 'Genel bilgileri kaydet';
|
||||
|
||||
@override
|
||||
String get saveRadioSettings => 'Radyo ayarlarını kaydet';
|
||||
|
||||
@override
|
||||
String get savePath => 'Yolu kaydet';
|
||||
|
||||
@override
|
||||
String get wipeDeviceData => 'Cihaz verilerini sil';
|
||||
|
||||
@override
|
||||
String get wipeDevice => 'Cihazı sil';
|
||||
|
||||
@override
|
||||
String get destructiveDeviceActions => 'Yıkıcı cihaz işlemleri.';
|
||||
|
||||
@override
|
||||
String get chooseAPresetOrFinetuneCustomRadioSettings =>
|
||||
'Bir ön ayar seçin veya radyo ayarlarını ince ayarlayın.';
|
||||
|
||||
@override
|
||||
String get chooseTheNameAndLocationThisDeviceShares =>
|
||||
'Bu cihazın paylaştığı ad ve konumu seçin.';
|
||||
|
||||
@override
|
||||
String get availableSpaceOnThisDevice => 'Bu cihazda kullanılabilir alan.';
|
||||
|
||||
@override
|
||||
String get used => 'Kullanılan';
|
||||
|
||||
@override
|
||||
String get total => 'Toplam';
|
||||
|
||||
@override
|
||||
String get renameValue => 'Değeri yeniden adlandır';
|
||||
|
||||
@override
|
||||
String get customizeFields => 'Alanları özelleştir';
|
||||
|
||||
@override
|
||||
String get livePreview => 'Canlı önizleme';
|
||||
|
||||
@override
|
||||
String get refreshSchedule => 'Yenileme programı';
|
||||
|
||||
@override
|
||||
String get noResponse => 'Yanıt yok';
|
||||
|
||||
@override
|
||||
String get refreshing => 'Yenileniyor';
|
||||
|
||||
@override
|
||||
String get unavailable => 'Kullanılamaz';
|
||||
|
||||
@override
|
||||
String get pickARelayOrNodeToWatchInSensors =>
|
||||
'İzlemek için bir röle veya düğüm seçin.';
|
||||
|
||||
@override
|
||||
String get publicKeyLabel => 'Genel anahtar';
|
||||
|
||||
@override
|
||||
String get alreadyInContacts => 'Zaten kişilerde';
|
||||
|
||||
@override
|
||||
String get connectToADeviceBeforeAddingContacts =>
|
||||
'Kişi eklemeden önce bir cihaza bağlanın';
|
||||
|
||||
@override
|
||||
String get fromContacts => 'Kişilerden';
|
||||
|
||||
@override
|
||||
String get onlineOnly => 'Yalnızca çevrimiçi';
|
||||
|
||||
@override
|
||||
String get inBoth => 'Her ikisinde';
|
||||
|
||||
@override
|
||||
String get source => 'Kaynak';
|
||||
|
||||
@override
|
||||
String get manualRouteEdit => 'Manuel rota düzenle';
|
||||
|
||||
@override
|
||||
String get observedMeshRoute => 'Gözlemlenen mesh rotası';
|
||||
|
||||
@override
|
||||
String get allMessagesCleared => 'Tüm mesajlar temizlendi';
|
||||
|
||||
@override
|
||||
String get onlineTraceDatabaseCleared => 'Çevrimiçi iz veritabanı temizlendi';
|
||||
|
||||
@override
|
||||
String get packetLogsCleared => 'Paket günlükleri temizlendi';
|
||||
|
||||
@override
|
||||
String get hexDataCopiedToClipboard => 'Hex verisi panoya kopyalandı';
|
||||
|
||||
@override
|
||||
String get developerModeEnabled => 'Geliştirici modu etkinleştirildi';
|
||||
|
||||
@override
|
||||
String get developerModeDisabled => 'Geliştirici modu devre dışı bırakıldı';
|
||||
|
||||
@override
|
||||
String get clipboardIsEmpty => 'Pano boş';
|
||||
|
||||
@override
|
||||
String get contactImported => 'Kişi içe aktarıldı';
|
||||
|
||||
@override
|
||||
String get contactLinkCopiedToClipboard =>
|
||||
'Kişi bağlantısı panoya kopyalandı';
|
||||
|
||||
@override
|
||||
String get failedToExportContact => 'Kişi dışa aktarılamadı';
|
||||
|
||||
@override
|
||||
String get noLogsToExport => 'Dışa aktarılacak günlük yok';
|
||||
|
||||
@override
|
||||
String get exportAsCsv => 'CSV olarak dışa aktar';
|
||||
|
||||
@override
|
||||
String get exportAsText => 'Metin olarak dışa aktar';
|
||||
|
||||
@override
|
||||
String get receivedRfc3339 => 'Alındı (RFC3339)';
|
||||
|
||||
@override
|
||||
String get buildTime => 'Derleme zamanı';
|
||||
|
||||
@override
|
||||
String get downloadUrlNotAvailable => 'İndirme URL\'si mevcut değil';
|
||||
|
||||
@override
|
||||
String get cannotOpenDownloadUrl => 'İndirme URL\'si açılamıyor';
|
||||
|
||||
@override
|
||||
String get updateCheckIsOnlyAvailableOnAndroid =>
|
||||
'Güncelleme kontrolü yalnızca Android\'de kullanılabilir';
|
||||
|
||||
@override
|
||||
String get youAreRunningTheLatestVersion => 'En son sürümü kullanıyorsunuz';
|
||||
|
||||
@override
|
||||
String get updateAvailableButDownloadUrlNotFound =>
|
||||
'Güncelleme mevcut ancak indirme URL\'si bulunamadı';
|
||||
|
||||
@override
|
||||
String get startTictactoe => 'Tic-Tac-Toe başlat';
|
||||
|
||||
@override
|
||||
String get tictactoeUnavailable => 'Tic-Tac-Toe kullanılamıyor';
|
||||
|
||||
@override
|
||||
String get tictactoeOpponentUnknown => 'Tic-Tac-Toe: rakip bilinmiyor';
|
||||
|
||||
@override
|
||||
String get tictactoeWaitingForStart => 'Tic-Tac-Toe: başlangıç bekleniyor';
|
||||
|
||||
@override
|
||||
String get acceptsShareLinks => 'Paylaşım bağlantılarını kabul eder';
|
||||
|
||||
@override
|
||||
String get supportsRawHex => 'Ham hex destekler';
|
||||
|
||||
@override
|
||||
String get clipboardfriendly => 'Pano dostu';
|
||||
|
||||
@override
|
||||
String get captured => 'Yakalandı';
|
||||
|
||||
@override
|
||||
String get size => 'Boyut';
|
||||
|
||||
@override
|
||||
String get noCustomChannelsToClear => 'Temizlenecek özel kanal yok.';
|
||||
|
||||
@override
|
||||
String get noDeviceContactsToClear => 'Temizlenecek cihaz kişisi yok.';
|
||||
}
|
||||
|
||||
@@ -1732,4 +1732,968 @@ class AppLocalizationsUk extends AppLocalizations {
|
||||
|
||||
@override
|
||||
String get requestingVoice => 'Запит голосу';
|
||||
|
||||
@override
|
||||
String get device => 'пристрій';
|
||||
|
||||
@override
|
||||
String get change => 'Змінити';
|
||||
|
||||
@override
|
||||
String get wizardOverviewDescription =>
|
||||
'Ця програма поєднує повідомлення MeshCore, польові оновлення SAR, карти та інструменти пристрою в одному місці.';
|
||||
|
||||
@override
|
||||
String get wizardOverviewFeature1 =>
|
||||
'Надсилайте особисті повідомлення, повідомлення кімнат і повідомлення каналів з основної вкладки «Повідомлення».';
|
||||
|
||||
@override
|
||||
String get wizardOverviewFeature2 =>
|
||||
'Діліться SAR-маркерами, малюнками на карті, голосовими кліпами та зображеннями через mesh-мережу.';
|
||||
|
||||
@override
|
||||
String get wizardOverviewFeature3 =>
|
||||
'Підключайтеся через BLE або TCP, а потім керуйте супутнім радіопристроєм прямо з програми.';
|
||||
|
||||
@override
|
||||
String get wizardMessagingTitle => 'Повідомлення та польові звіти';
|
||||
|
||||
@override
|
||||
String get wizardMessagingDescription =>
|
||||
'Повідомлення тут це більше, ніж просто текст. Програма вже підтримує кілька типів операційних даних і сценаріїв передачі.';
|
||||
|
||||
@override
|
||||
String get wizardMessagingFeature1 =>
|
||||
'Надсилайте особисті повідомлення, повідомлення кімнат і трафік каналів з одного редактора.';
|
||||
|
||||
@override
|
||||
String get wizardMessagingFeature2 =>
|
||||
'Створюйте оновлення SAR і багаторазові шаблони SAR для типових польових звітів.';
|
||||
|
||||
@override
|
||||
String get wizardMessagingFeature3 =>
|
||||
'Передавайте голосові сесії та зображення з індикатором прогресу й оцінками ефірного часу в інтерфейсі.';
|
||||
|
||||
@override
|
||||
String get wizardConnectDeviceTitle => 'Підключити пристрій';
|
||||
|
||||
@override
|
||||
String get wizardConnectDeviceDescription =>
|
||||
'Підключіть своє радіо MeshCore, виберіть назву та застосуйте радіопрофіль перед продовженням.';
|
||||
|
||||
@override
|
||||
String get wizardSetupBadge => 'Налаштування';
|
||||
|
||||
@override
|
||||
String get wizardOverviewBadge => 'Огляд';
|
||||
|
||||
@override
|
||||
String wizardConnectedToDevice(String deviceName) {
|
||||
return 'Підключено до $deviceName';
|
||||
}
|
||||
|
||||
@override
|
||||
String get wizardNoDeviceConnected => 'Ще немає підключеного пристрою';
|
||||
|
||||
@override
|
||||
String get wizardSkipForNow => 'Пропустити поки що';
|
||||
|
||||
@override
|
||||
String get wizardDeviceNameLabel => 'Назва пристрою';
|
||||
|
||||
@override
|
||||
String get wizardDeviceNameHelp =>
|
||||
'Ця назва оголошується іншим користувачам MeshCore.';
|
||||
|
||||
@override
|
||||
String get wizardConfigRegionLabel => 'Регіон конфігурації';
|
||||
|
||||
@override
|
||||
String get wizardConfigRegionHelp =>
|
||||
'Використовується повний офіційний список профілів MeshCore. Типово вибрано EU/UK (Narrow).';
|
||||
|
||||
@override
|
||||
String get wizardPresetNote1 =>
|
||||
'Переконайтеся, що вибраний профіль відповідає місцевим радіоправилам.';
|
||||
|
||||
@override
|
||||
String get wizardPresetNote2 =>
|
||||
'Список відповідає офіційному потоку профілів інструмента MeshCore config.';
|
||||
|
||||
@override
|
||||
String get wizardPresetNote3 =>
|
||||
'Для онбордингу типовим залишається EU/UK (Narrow).';
|
||||
|
||||
@override
|
||||
String get wizardSaving => 'Збереження...';
|
||||
|
||||
@override
|
||||
String get wizardSaveAndContinue => 'Зберегти й продовжити';
|
||||
|
||||
@override
|
||||
String get wizardEnterDeviceName =>
|
||||
'Введіть назву пристрою перед продовженням.';
|
||||
|
||||
@override
|
||||
String wizardDeviceSetupSaved(String deviceName, String presetName) {
|
||||
return 'Збережено $deviceName з профілем $presetName.';
|
||||
}
|
||||
|
||||
@override
|
||||
String get wizardNetworkTitle => 'Контакти, кімнати та ретранслятори';
|
||||
|
||||
@override
|
||||
String get wizardNetworkDescription =>
|
||||
'Вкладка «Контакти» організовує мережу, яку ви виявляєте, і маршрути, які вивчаєте з часом.';
|
||||
|
||||
@override
|
||||
String get wizardNetworkFeature1 =>
|
||||
'Переглядайте членів команди, ретранслятори, кімнати, канали та оголошення в очікуванні в одному списку.';
|
||||
|
||||
@override
|
||||
String get wizardNetworkFeature2 =>
|
||||
'Використовуйте smart ping, вхід до кімнат, вивчені шляхи й інструменти скидання маршрутів, коли зв\'язок стає нестабільним.';
|
||||
|
||||
@override
|
||||
String get wizardNetworkFeature3 =>
|
||||
'Створюйте канали та керуйте мережевими призначеннями, не виходячи з програми.';
|
||||
|
||||
@override
|
||||
String get wizardMapOpsTitle => 'Мапа, сліди та спільна геометрія';
|
||||
|
||||
@override
|
||||
String get wizardMapOpsDescription =>
|
||||
'Мапа програми напряму пов\'язана з повідомленнями, відстеженням і SAR-накладками, а не є окремим переглядачем.';
|
||||
|
||||
@override
|
||||
String get wizardMapOpsFeature1 =>
|
||||
'Відстежуйте власну позицію, місця розташування команди та сліди руху на мапі.';
|
||||
|
||||
@override
|
||||
String get wizardMapOpsFeature2 =>
|
||||
'Відкривайте малюнки з повідомлень, переглядайте їх в інтерфейсі та видаляйте з мапи за потреби.';
|
||||
|
||||
@override
|
||||
String get wizardMapOpsFeature3 =>
|
||||
'Використовуйте мапи ретрансляторів і спільні накладки, щоб розуміти покриття мережі в полі.';
|
||||
|
||||
@override
|
||||
String get wizardToolsTitle => 'Інструменти поза повідомленнями';
|
||||
|
||||
@override
|
||||
String get wizardToolsDescription =>
|
||||
'Тут є більше, ніж чотири основні вкладки. Програма також включає налаштування, діагностику та необов\'язкові сценарії датчиків.';
|
||||
|
||||
@override
|
||||
String get wizardToolsFeature1 =>
|
||||
'Відкрийте налаштування пристрою, щоб змінити параметри радіо, телеметрію, потужність TX і дані супутнього пристрою.';
|
||||
|
||||
@override
|
||||
String get wizardToolsFeature2 =>
|
||||
'Увімкніть вкладку «Датчики», коли потрібні панелі моніторингу та швидкі дії оновлення.';
|
||||
|
||||
@override
|
||||
String get wizardToolsFeature3 =>
|
||||
'Використовуйте журнали пакетів, сканування спектра та діагностику розробника для усунення проблем mesh-мережі.';
|
||||
|
||||
@override
|
||||
String get contactInSensors => 'У датчиках';
|
||||
|
||||
@override
|
||||
String get contactAddToSensors => 'Додати до датчиків';
|
||||
|
||||
@override
|
||||
String get contactSetPath => 'Задати шлях';
|
||||
|
||||
@override
|
||||
String contactAddedToSensors(String contactName) {
|
||||
return '$contactName додано до Датчиків';
|
||||
}
|
||||
|
||||
@override
|
||||
String contactFailedToClearRoute(String error) {
|
||||
return 'Не вдалося очистити маршрут: $error';
|
||||
}
|
||||
|
||||
@override
|
||||
String get contactRouteCleared => 'Маршрут очищено';
|
||||
|
||||
@override
|
||||
String contactRouteSet(String route) {
|
||||
return 'Маршрут задано: $route';
|
||||
}
|
||||
|
||||
@override
|
||||
String contactFailedToSetRoute(String error) {
|
||||
return 'Не вдалося задати маршрут: $error';
|
||||
}
|
||||
|
||||
@override
|
||||
String get rssi => 'RSSI';
|
||||
|
||||
@override
|
||||
String get snr => 'SNR';
|
||||
|
||||
@override
|
||||
String get ackTimeout => 'Тайм-аут ACK';
|
||||
|
||||
@override
|
||||
String get opcode => 'Опкод';
|
||||
|
||||
@override
|
||||
String get payload => 'Корисне навантаження';
|
||||
|
||||
@override
|
||||
String get hops => 'Хопи';
|
||||
|
||||
@override
|
||||
String get hashSize => 'Розмір хешу';
|
||||
|
||||
@override
|
||||
String get pathBytes => 'Байти шляху';
|
||||
|
||||
@override
|
||||
String get selectedPath => 'Обраний шлях';
|
||||
|
||||
@override
|
||||
String get estimatedTx => 'Очікувана передача';
|
||||
|
||||
@override
|
||||
String get senderToReceipt => 'Від відправника до отримання';
|
||||
|
||||
@override
|
||||
String get receivedCopies => 'Отримані копії';
|
||||
|
||||
@override
|
||||
String get retryCause => 'Причина повтору';
|
||||
|
||||
@override
|
||||
String get retryMode => 'Режим повтору';
|
||||
|
||||
@override
|
||||
String get retryResult => 'Результат повтору';
|
||||
|
||||
@override
|
||||
String get lastRetry => 'Остання спроба';
|
||||
|
||||
@override
|
||||
String get rxPackets => 'RX-пакети';
|
||||
|
||||
@override
|
||||
String get mesh => 'Mesh';
|
||||
|
||||
@override
|
||||
String get rate => 'Швидкість';
|
||||
|
||||
@override
|
||||
String get window => 'Вікно';
|
||||
|
||||
@override
|
||||
String get posttxDelay => 'Затримка після передачі';
|
||||
|
||||
@override
|
||||
String get bandpass => 'Смуговий';
|
||||
|
||||
@override
|
||||
String get bandpassFilterVoice => 'Смуговий фільтр голосу';
|
||||
|
||||
@override
|
||||
String get frequency => 'Частота';
|
||||
|
||||
@override
|
||||
String get australia => 'Австралія';
|
||||
|
||||
@override
|
||||
String get australiaNarrow => 'Австралія (Вузький)';
|
||||
|
||||
@override
|
||||
String get australiaQld => 'Австралія: QLD';
|
||||
|
||||
@override
|
||||
String get australiaSaWa => 'Австралія: SA, WA';
|
||||
|
||||
@override
|
||||
String get newZealand => 'Нова Зеландія';
|
||||
|
||||
@override
|
||||
String get newZealandNarrow => 'Нова Зеландія (Вузький)';
|
||||
|
||||
@override
|
||||
String get switzerland => 'Швейцарія';
|
||||
|
||||
@override
|
||||
String get portugal433 => 'Португалія 433';
|
||||
|
||||
@override
|
||||
String get portugal868 => 'Португалія 868';
|
||||
|
||||
@override
|
||||
String get czechRepublicNarrow => 'Чехія (Вузький)';
|
||||
|
||||
@override
|
||||
String get eu433mhzLongRange => 'ЄС 433МГц (Далекий)';
|
||||
|
||||
@override
|
||||
String get euukDeprecated => 'ЄС/Великобританія (Застаріле)';
|
||||
|
||||
@override
|
||||
String get euukNarrow => 'ЄС/Великобританія (Вузький)';
|
||||
|
||||
@override
|
||||
String get usacanadaRecommended => 'США/Канада (Рекомендовано)';
|
||||
|
||||
@override
|
||||
String get vietnamDeprecated => 'В\'єтнам (Застаріле)';
|
||||
|
||||
@override
|
||||
String get vietnamNarrow => 'В\'єтнам (Вузький)';
|
||||
|
||||
@override
|
||||
String get active => 'Активно';
|
||||
|
||||
@override
|
||||
String get addContact => 'Додати контакт';
|
||||
|
||||
@override
|
||||
String get all => 'Усі';
|
||||
|
||||
@override
|
||||
String get autoResolve => 'Автоматичне вирішення';
|
||||
|
||||
@override
|
||||
String get clearAllLabel => 'Очистити все';
|
||||
|
||||
@override
|
||||
String get clearRelays => 'Очистити реле';
|
||||
|
||||
@override
|
||||
String get clearFilters => 'Очистити фільтри';
|
||||
|
||||
@override
|
||||
String get clearRoute => 'Очистити маршрут';
|
||||
|
||||
@override
|
||||
String get clearMessages => 'Очистити повідомлення';
|
||||
|
||||
@override
|
||||
String get clearScale => 'Очистити масштаб';
|
||||
|
||||
@override
|
||||
String get clearDiscoveries => 'Очистити виявлення';
|
||||
|
||||
@override
|
||||
String get clearOnlineTraceDatabase => 'Очистити базу трасувань';
|
||||
|
||||
@override
|
||||
String get clearAllChannels => 'Очистити всі канали';
|
||||
|
||||
@override
|
||||
String get clearAllContacts => 'Очистити всі контакти';
|
||||
|
||||
@override
|
||||
String get clearChannels => 'Очистити канали';
|
||||
|
||||
@override
|
||||
String get clearContacts => 'Очистити контакти';
|
||||
|
||||
@override
|
||||
String get clearPathOnMaxRetry => 'Очистити шлях при макс. спробі';
|
||||
|
||||
@override
|
||||
String get create => 'Створити';
|
||||
|
||||
@override
|
||||
String get custom => 'Користувацький';
|
||||
|
||||
@override
|
||||
String get defaultValue => 'За замовчуванням';
|
||||
|
||||
@override
|
||||
String get duplicate => 'Дублювати';
|
||||
|
||||
@override
|
||||
String get editName => 'Редагувати ім\'я';
|
||||
|
||||
@override
|
||||
String get open => 'Відкрити';
|
||||
|
||||
@override
|
||||
String get paste => 'Вставити';
|
||||
|
||||
@override
|
||||
String get preview => 'Попередній перегляд';
|
||||
|
||||
@override
|
||||
String get remove => 'Видалити';
|
||||
|
||||
@override
|
||||
String get rename => 'Перейменувати';
|
||||
|
||||
@override
|
||||
String get resolveAll => 'Вирішити все';
|
||||
|
||||
@override
|
||||
String get send => 'Надіслати';
|
||||
|
||||
@override
|
||||
String get sendAnyway => 'Надіслати все одно';
|
||||
|
||||
@override
|
||||
String get share => 'Поділитися';
|
||||
|
||||
@override
|
||||
String get shareContact => 'Поділитися контактом';
|
||||
|
||||
@override
|
||||
String get trace => 'Трасування';
|
||||
|
||||
@override
|
||||
String get use => 'Використати';
|
||||
|
||||
@override
|
||||
String get useSelectedFrequency => 'Використати обрану частоту';
|
||||
|
||||
@override
|
||||
String get discovery => 'Виявлення';
|
||||
|
||||
@override
|
||||
String get discoverRepeaters => 'Виявити повторювачі';
|
||||
|
||||
@override
|
||||
String get discoverSensors => 'Виявити датчики';
|
||||
|
||||
@override
|
||||
String get repeaterDiscoverySent => 'Виявлення повторювачів надіслано';
|
||||
|
||||
@override
|
||||
String get sensorDiscoverySent => 'Виявлення датчиків надіслано';
|
||||
|
||||
@override
|
||||
String get clearedPendingDiscoveries => 'Очікувані виявлення очищено.';
|
||||
|
||||
@override
|
||||
String get autoDiscovery => 'Автоматичне виявлення';
|
||||
|
||||
@override
|
||||
String get enableAutomaticAdding => 'Увімкнути автоматичне додавання';
|
||||
|
||||
@override
|
||||
String get autoaddRepeaters => 'Автододавання повторювачів';
|
||||
|
||||
@override
|
||||
String get autoaddRoomServers => 'Автододавання серверів кімнат';
|
||||
|
||||
@override
|
||||
String get autoaddSensors => 'Автододавання датчиків';
|
||||
|
||||
@override
|
||||
String get autoaddUsers => 'Автододавання користувачів';
|
||||
|
||||
@override
|
||||
String get overwriteOldestWhenFull =>
|
||||
'Перезаписати найстаріші при заповненні';
|
||||
|
||||
@override
|
||||
String get storage => 'Сховище';
|
||||
|
||||
@override
|
||||
String get dangerZone => 'Небезпечна зона';
|
||||
|
||||
@override
|
||||
String get profiles => 'Профілі';
|
||||
|
||||
@override
|
||||
String get favourites => 'Обране';
|
||||
|
||||
@override
|
||||
String get sensors => 'Датчики';
|
||||
|
||||
@override
|
||||
String get others => 'Інші';
|
||||
|
||||
@override
|
||||
String get gpsModule => 'Модуль GPS';
|
||||
|
||||
@override
|
||||
String get liveTraffic => 'Живий трафік';
|
||||
|
||||
@override
|
||||
String get repeatersMap => 'Карта повторювачів';
|
||||
|
||||
@override
|
||||
String get spectrumScan => 'Сканування спектру';
|
||||
|
||||
@override
|
||||
String get blePacketLogs => 'Журнали BLE-пакетів';
|
||||
|
||||
@override
|
||||
String get onlineTraceDatabase => 'База трасувань';
|
||||
|
||||
@override
|
||||
String get routePathByteSize => 'Розмір шляху в байтах';
|
||||
|
||||
@override
|
||||
String get messageNotifications => 'Сповіщення про повідомлення';
|
||||
|
||||
@override
|
||||
String get sarAlerts => 'SAR-сповіщення';
|
||||
|
||||
@override
|
||||
String get discoveryNotifications => 'Сповіщення про виявлення';
|
||||
|
||||
@override
|
||||
String get updateNotifications => 'Сповіщення про оновлення';
|
||||
|
||||
@override
|
||||
String get muteWhileAppIsOpen => 'Без звуку при відкритому додатку';
|
||||
|
||||
@override
|
||||
String get disableContacts => 'Вимкнути контакти';
|
||||
|
||||
@override
|
||||
String get enableSensorsTab => 'Увімкнути вкладку Датчики';
|
||||
|
||||
@override
|
||||
String get enableProfiles => 'Увімкнути профілі';
|
||||
|
||||
@override
|
||||
String get autoRouteRotation => 'Автоматична ротація маршруту';
|
||||
|
||||
@override
|
||||
String get nearestRepeaterFallback => 'Найближчий повторювач як резерв';
|
||||
|
||||
@override
|
||||
String get deleteAllStoredMessageHistory =>
|
||||
'Видалити всю історію повідомлень';
|
||||
|
||||
@override
|
||||
String get messageFontSize => 'Розмір шрифту повідомлень';
|
||||
|
||||
@override
|
||||
String get rotateMapWithHeading => 'Обертати карту за напрямком';
|
||||
|
||||
@override
|
||||
String get showMapDebugInfo => 'Показати налагоджувальну інфо карти';
|
||||
|
||||
@override
|
||||
String get openMapInFullscreen => 'Відкрити карту на повний екран';
|
||||
|
||||
@override
|
||||
String get showSarMarkersLabel => 'Показати SAR-маркери';
|
||||
|
||||
@override
|
||||
String get displaySarMarkersOnTheMainMap =>
|
||||
'Показувати SAR-маркери на головній карті';
|
||||
|
||||
@override
|
||||
String get showAllContactTrailsLabel => 'Показати всі сліди контактів';
|
||||
|
||||
@override
|
||||
String get hideRepeatersOnMap => 'Сховати повторювачі на карті';
|
||||
|
||||
@override
|
||||
String get setMapScale => 'Встановити масштаб карти';
|
||||
|
||||
@override
|
||||
String get customMapScaleSaved => 'Користувацький масштаб карти збережено';
|
||||
|
||||
@override
|
||||
String get voiceBitrate => 'Бітрейт голосу';
|
||||
|
||||
@override
|
||||
String get voiceCompressor => 'Компресор голосу';
|
||||
|
||||
@override
|
||||
String get balancesQuietAndLoudSpeechLevels =>
|
||||
'Вирівнює тихий і гучний рівні мови';
|
||||
|
||||
@override
|
||||
String get voiceLimiter => 'Лімітер голосу';
|
||||
|
||||
@override
|
||||
String get preventsClippingPeaksBeforeEncoding =>
|
||||
'Запобігає обрізанню піків перед кодуванням';
|
||||
|
||||
@override
|
||||
String get micAutoGain => 'Автопідсилення мікрофона';
|
||||
|
||||
@override
|
||||
String get letsTheRecorderAdjustInputLevel =>
|
||||
'Дозволяє рекордеру налаштувати рівень входу';
|
||||
|
||||
@override
|
||||
String get echoCancellation => 'Придушення ехо';
|
||||
|
||||
@override
|
||||
String get noiseSuppression => 'Придушення шуму';
|
||||
|
||||
@override
|
||||
String get trimSilenceInVoiceMessages =>
|
||||
'Обрізати тишу в голосових повідомленнях';
|
||||
|
||||
@override
|
||||
String get compressor => 'Компресор';
|
||||
|
||||
@override
|
||||
String get limiter => 'Лімітер';
|
||||
|
||||
@override
|
||||
String get autoGain => 'Автопідсилення';
|
||||
|
||||
@override
|
||||
String get echoCancel => 'Ехо';
|
||||
|
||||
@override
|
||||
String get noiseSuppress => 'Шум';
|
||||
|
||||
@override
|
||||
String get silenceTrim => 'Тиша';
|
||||
|
||||
@override
|
||||
String get maxImageSize => 'Максимальний розмір зображення';
|
||||
|
||||
@override
|
||||
String get imageCompression => 'Стиснення зображення';
|
||||
|
||||
@override
|
||||
String get grayscale => 'Відтінки сірого';
|
||||
|
||||
@override
|
||||
String get ultraMode => 'Ультра режим';
|
||||
|
||||
@override
|
||||
String get fastPrivateGpsUpdates => 'Швидкі приватні GPS-оновлення';
|
||||
|
||||
@override
|
||||
String get movementThreshold => 'Поріг руху';
|
||||
|
||||
@override
|
||||
String get fastGpsMovementThreshold => 'Поріг руху швидкого GPS';
|
||||
|
||||
@override
|
||||
String get fastGpsActiveuseInterval =>
|
||||
'Інтервал активного використання швидкого GPS';
|
||||
|
||||
@override
|
||||
String get activeuseUpdateInterval =>
|
||||
'Інтервал оновлення при активному використанні';
|
||||
|
||||
@override
|
||||
String get repeatNearbyTraffic => 'Повторювати близький трафік';
|
||||
|
||||
@override
|
||||
String get relayThroughRepeatersAcrossTheMesh =>
|
||||
'Ретранслювати через повторювачі по мережі';
|
||||
|
||||
@override
|
||||
String get nearbyOnlyWithoutRepeaterFlooding =>
|
||||
'Лише поблизу, без flood повторювачів';
|
||||
|
||||
@override
|
||||
String get multihop => 'Багатохоповий';
|
||||
|
||||
@override
|
||||
String get createProfile => 'Створити профіль';
|
||||
|
||||
@override
|
||||
String get renameProfile => 'Перейменувати профіль';
|
||||
|
||||
@override
|
||||
String get newProfile => 'Новий профіль';
|
||||
|
||||
@override
|
||||
String get manageProfiles => 'Керувати профілями';
|
||||
|
||||
@override
|
||||
String get enableProfilesToStartManagingThem =>
|
||||
'Увімкніть профілі, щоб почати керувати ними.';
|
||||
|
||||
@override
|
||||
String get openMessage => 'Відкрити повідомлення';
|
||||
|
||||
@override
|
||||
String get jumpToTheRelatedSarMessage =>
|
||||
'Перейти до пов\'язаного SAR-повідомлення';
|
||||
|
||||
@override
|
||||
String get removeSarMarker => 'Видалити SAR-маркер';
|
||||
|
||||
@override
|
||||
String get pleaseSelectADestinationToSendSarMarker =>
|
||||
'Виберіть призначення для надсилання SAR-маркера';
|
||||
|
||||
@override
|
||||
String get sarMarkerBroadcastToPublicChannel =>
|
||||
'SAR-маркер надіслано на публічний канал';
|
||||
|
||||
@override
|
||||
String get sarMarkerSentToRoom => 'SAR-маркер надіслано в кімнату';
|
||||
|
||||
@override
|
||||
String get loadFromGallery => 'Завантажити з галереї';
|
||||
|
||||
@override
|
||||
String get replaceImage => 'Замінити зображення';
|
||||
|
||||
@override
|
||||
String get selectFromGallery => 'Вибрати з галереї';
|
||||
|
||||
@override
|
||||
String get team => 'Команда';
|
||||
|
||||
@override
|
||||
String get found => 'Знайдено';
|
||||
|
||||
@override
|
||||
String get staging => 'Місце збору';
|
||||
|
||||
@override
|
||||
String get object => 'Об\'єкт';
|
||||
|
||||
@override
|
||||
String get quiet => 'Тихо';
|
||||
|
||||
@override
|
||||
String get moderate => 'Помірно';
|
||||
|
||||
@override
|
||||
String get busy => 'Зайнято';
|
||||
|
||||
@override
|
||||
String get spectrumScanReturnedNoCandidateFrequencies =>
|
||||
'Сканування спектру не знайшло частот-кандидатів';
|
||||
|
||||
@override
|
||||
String get searchMessages => 'Пошук повідомлень';
|
||||
|
||||
@override
|
||||
String get sendImageFromGallery => 'Надіслати зображення з галереї';
|
||||
|
||||
@override
|
||||
String get takePhoto => 'Зробити фото';
|
||||
|
||||
@override
|
||||
String get dmOnly => 'Лише особисті повідомлення';
|
||||
|
||||
@override
|
||||
String get allMessages => 'Усі повідомлення';
|
||||
|
||||
@override
|
||||
String get sendToPublicChannel => 'Надіслати в публічний канал?';
|
||||
|
||||
@override
|
||||
String get selectMarkerTypeAndDestination =>
|
||||
'Виберіть тип маркера та призначення';
|
||||
|
||||
@override
|
||||
String get noDestinationsAvailableLabel => 'Немає доступних призначень';
|
||||
|
||||
@override
|
||||
String get image => 'Зображення';
|
||||
|
||||
@override
|
||||
String get format => 'Формат';
|
||||
|
||||
@override
|
||||
String get dimensions => 'Розміри';
|
||||
|
||||
@override
|
||||
String get segments => 'Сегменти';
|
||||
|
||||
@override
|
||||
String get transfers => 'Передачі';
|
||||
|
||||
@override
|
||||
String get downloadedBy => 'Завантажено';
|
||||
|
||||
@override
|
||||
String get saveDiscoverySettings => 'Зберегти налаштування виявлення';
|
||||
|
||||
@override
|
||||
String get savePublicInfo => 'Зберегти публічну інформацію';
|
||||
|
||||
@override
|
||||
String get saveRadioSettings => 'Зберегти налаштування радіо';
|
||||
|
||||
@override
|
||||
String get savePath => 'Зберегти шлях';
|
||||
|
||||
@override
|
||||
String get wipeDeviceData => 'Стерти дані пристрою';
|
||||
|
||||
@override
|
||||
String get wipeDevice => 'Стерти пристрій';
|
||||
|
||||
@override
|
||||
String get destructiveDeviceActions => 'Деструктивні дії пристрою.';
|
||||
|
||||
@override
|
||||
String get chooseAPresetOrFinetuneCustomRadioSettings =>
|
||||
'Оберіть пресет або налаштуйте радіо вручну.';
|
||||
|
||||
@override
|
||||
String get chooseTheNameAndLocationThisDeviceShares =>
|
||||
'Оберіть ім\'я та місцеположення, якими ділиться пристрій.';
|
||||
|
||||
@override
|
||||
String get availableSpaceOnThisDevice =>
|
||||
'Доступний простір на цьому пристрої.';
|
||||
|
||||
@override
|
||||
String get used => 'Використано';
|
||||
|
||||
@override
|
||||
String get total => 'Загалом';
|
||||
|
||||
@override
|
||||
String get renameValue => 'Перейменувати значення';
|
||||
|
||||
@override
|
||||
String get customizeFields => 'Налаштувати поля';
|
||||
|
||||
@override
|
||||
String get livePreview => 'Попередній перегляд наживо';
|
||||
|
||||
@override
|
||||
String get refreshSchedule => 'Розклад оновлення';
|
||||
|
||||
@override
|
||||
String get noResponse => 'Немає відповіді';
|
||||
|
||||
@override
|
||||
String get refreshing => 'Оновлення';
|
||||
|
||||
@override
|
||||
String get unavailable => 'Недоступно';
|
||||
|
||||
@override
|
||||
String get pickARelayOrNodeToWatchInSensors =>
|
||||
'Оберіть реле або вузол для спостереження.';
|
||||
|
||||
@override
|
||||
String get publicKeyLabel => 'Публічний ключ';
|
||||
|
||||
@override
|
||||
String get alreadyInContacts => 'Вже в контактах';
|
||||
|
||||
@override
|
||||
String get connectToADeviceBeforeAddingContacts =>
|
||||
'Підключіться до пристрою перед додаванням контактів';
|
||||
|
||||
@override
|
||||
String get fromContacts => 'З контактів';
|
||||
|
||||
@override
|
||||
String get onlineOnly => 'Лише онлайн';
|
||||
|
||||
@override
|
||||
String get inBoth => 'В обох';
|
||||
|
||||
@override
|
||||
String get source => 'Джерело';
|
||||
|
||||
@override
|
||||
String get manualRouteEdit => 'Ручне редагування маршруту';
|
||||
|
||||
@override
|
||||
String get observedMeshRoute => 'Спостережуваний mesh-маршрут';
|
||||
|
||||
@override
|
||||
String get allMessagesCleared => 'Усі повідомлення очищено';
|
||||
|
||||
@override
|
||||
String get onlineTraceDatabaseCleared => 'Базу трасувань очищено';
|
||||
|
||||
@override
|
||||
String get packetLogsCleared => 'Журнали пакетів очищено';
|
||||
|
||||
@override
|
||||
String get hexDataCopiedToClipboard => 'Hex-дані скопійовано в буфер';
|
||||
|
||||
@override
|
||||
String get developerModeEnabled => 'Режим розробника увімкнено';
|
||||
|
||||
@override
|
||||
String get developerModeDisabled => 'Режим розробника вимкнено';
|
||||
|
||||
@override
|
||||
String get clipboardIsEmpty => 'Буфер обміну порожній';
|
||||
|
||||
@override
|
||||
String get contactImported => 'Контакт імпортовано';
|
||||
|
||||
@override
|
||||
String get contactLinkCopiedToClipboard =>
|
||||
'Посилання на контакт скопійовано в буфер';
|
||||
|
||||
@override
|
||||
String get failedToExportContact => 'Не вдалося експортувати контакт';
|
||||
|
||||
@override
|
||||
String get noLogsToExport => 'Немає журналів для експорту';
|
||||
|
||||
@override
|
||||
String get exportAsCsv => 'Експортувати як CSV';
|
||||
|
||||
@override
|
||||
String get exportAsText => 'Експортувати як текст';
|
||||
|
||||
@override
|
||||
String get receivedRfc3339 => 'Отримано (RFC3339)';
|
||||
|
||||
@override
|
||||
String get buildTime => 'Час збірки';
|
||||
|
||||
@override
|
||||
String get downloadUrlNotAvailable => 'URL завантаження недоступний';
|
||||
|
||||
@override
|
||||
String get cannotOpenDownloadUrl => 'Не вдається відкрити URL завантаження';
|
||||
|
||||
@override
|
||||
String get updateCheckIsOnlyAvailableOnAndroid =>
|
||||
'Перевірка оновлень доступна лише на Android';
|
||||
|
||||
@override
|
||||
String get youAreRunningTheLatestVersion =>
|
||||
'Ви використовуєте останню версію';
|
||||
|
||||
@override
|
||||
String get updateAvailableButDownloadUrlNotFound =>
|
||||
'Оновлення доступне, але URL завантаження не знайдено';
|
||||
|
||||
@override
|
||||
String get startTictactoe => 'Почати Tic-Tac-Toe';
|
||||
|
||||
@override
|
||||
String get tictactoeUnavailable => 'Tic-Tac-Toe недоступно';
|
||||
|
||||
@override
|
||||
String get tictactoeOpponentUnknown => 'Tic-Tac-Toe: суперник невідомий';
|
||||
|
||||
@override
|
||||
String get tictactoeWaitingForStart => 'Tic-Tac-Toe: очікування початку';
|
||||
|
||||
@override
|
||||
String get acceptsShareLinks => 'Приймає посилання для обміну';
|
||||
|
||||
@override
|
||||
String get supportsRawHex => 'Підтримує raw hex';
|
||||
|
||||
@override
|
||||
String get clipboardfriendly => 'Зручно для буфера';
|
||||
|
||||
@override
|
||||
String get captured => 'Захоплено';
|
||||
|
||||
@override
|
||||
String get size => 'Розмір';
|
||||
|
||||
@override
|
||||
String get noCustomChannelsToClear =>
|
||||
'Немає користувацьких каналів для очищення.';
|
||||
|
||||
@override
|
||||
String get noDeviceContactsToClear =>
|
||||
'Немає контактів пристрою для очищення.';
|
||||
}
|
||||
|
||||
@@ -1656,4 +1656,916 @@ class AppLocalizationsZh extends AppLocalizations {
|
||||
|
||||
@override
|
||||
String get requestingVoice => '正在请求语音';
|
||||
|
||||
@override
|
||||
String get device => '设备';
|
||||
|
||||
@override
|
||||
String get change => '更改';
|
||||
|
||||
@override
|
||||
String get wizardOverviewDescription =>
|
||||
'此应用将 MeshCore 消息、SAR 现场更新、地图功能和设备工具整合到同一处。';
|
||||
|
||||
@override
|
||||
String get wizardOverviewFeature1 => '在主消息标签页中发送私聊消息、房间消息和频道消息。';
|
||||
|
||||
@override
|
||||
String get wizardOverviewFeature2 => '通过 mesh 网络共享 SAR 标记、地图绘图、语音片段和图像。';
|
||||
|
||||
@override
|
||||
String get wizardOverviewFeature3 => '通过 BLE 或 TCP 连接,然后直接在应用中管理配套电台。';
|
||||
|
||||
@override
|
||||
String get wizardMessagingTitle => '消息与现场报告';
|
||||
|
||||
@override
|
||||
String get wizardMessagingDescription => '这里的消息不只是纯文本。应用已经支持多种操作载荷和传输流程。';
|
||||
|
||||
@override
|
||||
String get wizardMessagingFeature1 => '通过一个编辑器发送私聊消息、房间消息和频道流量。';
|
||||
|
||||
@override
|
||||
String get wizardMessagingFeature2 => '为常见现场报告创建 SAR 更新和可复用的 SAR 模板。';
|
||||
|
||||
@override
|
||||
String get wizardMessagingFeature3 => '传输语音会话和图像,并在界面中显示进度和空口时间估算。';
|
||||
|
||||
@override
|
||||
String get wizardConnectDeviceTitle => '连接设备';
|
||||
|
||||
@override
|
||||
String get wizardConnectDeviceDescription =>
|
||||
'连接你的 MeshCore 电台,选择名称,并在继续前应用无线电预设。';
|
||||
|
||||
@override
|
||||
String get wizardSetupBadge => '设置';
|
||||
|
||||
@override
|
||||
String get wizardOverviewBadge => '概览';
|
||||
|
||||
@override
|
||||
String wizardConnectedToDevice(String deviceName) {
|
||||
return '已连接到 $deviceName';
|
||||
}
|
||||
|
||||
@override
|
||||
String get wizardNoDeviceConnected => '尚未连接设备';
|
||||
|
||||
@override
|
||||
String get wizardSkipForNow => '暂时跳过';
|
||||
|
||||
@override
|
||||
String get wizardDeviceNameLabel => '设备名称';
|
||||
|
||||
@override
|
||||
String get wizardDeviceNameHelp => '此名称会广播给其他 MeshCore 用户。';
|
||||
|
||||
@override
|
||||
String get wizardConfigRegionLabel => '配置区域';
|
||||
|
||||
@override
|
||||
String get wizardConfigRegionHelp =>
|
||||
'使用完整的官方 MeshCore 预设列表。默认值为 EU/UK (Narrow)。';
|
||||
|
||||
@override
|
||||
String get wizardPresetNote1 => '请确认所选预设符合你所在地区的无线电规定。';
|
||||
|
||||
@override
|
||||
String get wizardPresetNote2 => '该列表与 MeshCore 配置工具的官方预设源保持一致。';
|
||||
|
||||
@override
|
||||
String get wizardPresetNote3 => '在引导设置中默认保持选中 EU/UK (Narrow)。';
|
||||
|
||||
@override
|
||||
String get wizardSaving => '正在保存...';
|
||||
|
||||
@override
|
||||
String get wizardSaveAndContinue => '保存并继续';
|
||||
|
||||
@override
|
||||
String get wizardEnterDeviceName => '继续前请输入设备名称。';
|
||||
|
||||
@override
|
||||
String wizardDeviceSetupSaved(String deviceName, String presetName) {
|
||||
return '已保存 $deviceName,配置为 $presetName。';
|
||||
}
|
||||
|
||||
@override
|
||||
String get wizardNetworkTitle => '联系人、房间和中继器';
|
||||
|
||||
@override
|
||||
String get wizardNetworkDescription => '联系人标签页会整理你发现的网络以及随着时间学习到的路由。';
|
||||
|
||||
@override
|
||||
String get wizardNetworkFeature1 => '在一个列表中查看团队成员、中继器、房间、频道和待处理广播。';
|
||||
|
||||
@override
|
||||
String get wizardNetworkFeature2 => '当连接变得混乱时,使用智能 ping、房间登录、已学习路径和路由重置工具。';
|
||||
|
||||
@override
|
||||
String get wizardNetworkFeature3 => '无需离开应用即可创建频道并管理网络目的地。';
|
||||
|
||||
@override
|
||||
String get wizardMapOpsTitle => '地图、轨迹与共享几何';
|
||||
|
||||
@override
|
||||
String get wizardMapOpsDescription => '应用地图直接与消息、跟踪和 SAR 叠加层关联,而不是单独的查看器。';
|
||||
|
||||
@override
|
||||
String get wizardMapOpsFeature1 => '在地图上跟踪你自己的位置、队友位置和移动轨迹。';
|
||||
|
||||
@override
|
||||
String get wizardMapOpsFeature2 => '打开消息中的绘图,内联预览,并在需要时将其从地图上移除。';
|
||||
|
||||
@override
|
||||
String get wizardMapOpsFeature3 => '使用中继器地图视图和共享叠加层来了解现场网络覆盖范围。';
|
||||
|
||||
@override
|
||||
String get wizardToolsTitle => '消息之外的工具';
|
||||
|
||||
@override
|
||||
String get wizardToolsDescription => '这里不只有四个主标签页。应用还包含配置、诊断和可选的传感器工作流。';
|
||||
|
||||
@override
|
||||
String get wizardToolsFeature1 => '打开设备配置以更改无线电设置、遥测、TX 功率和配套设备详情。';
|
||||
|
||||
@override
|
||||
String get wizardToolsFeature2 => '当你需要受监控的传感器面板和快速刷新操作时,启用传感器标签页。';
|
||||
|
||||
@override
|
||||
String get wizardToolsFeature3 => '在排查 mesh 网络问题时使用数据包日志、频谱扫描和开发者诊断。';
|
||||
|
||||
@override
|
||||
String get contactInSensors => '在传感器中';
|
||||
|
||||
@override
|
||||
String get contactAddToSensors => '添加到传感器';
|
||||
|
||||
@override
|
||||
String get contactSetPath => '设置路径';
|
||||
|
||||
@override
|
||||
String contactAddedToSensors(String contactName) {
|
||||
return '已将 $contactName 添加到传感器';
|
||||
}
|
||||
|
||||
@override
|
||||
String contactFailedToClearRoute(String error) {
|
||||
return '清除路由失败:$error';
|
||||
}
|
||||
|
||||
@override
|
||||
String get contactRouteCleared => '路由已清除';
|
||||
|
||||
@override
|
||||
String contactRouteSet(String route) {
|
||||
return '路由已设置:$route';
|
||||
}
|
||||
|
||||
@override
|
||||
String contactFailedToSetRoute(String error) {
|
||||
return '设置路由失败:$error';
|
||||
}
|
||||
|
||||
@override
|
||||
String get rssi => 'RSSI';
|
||||
|
||||
@override
|
||||
String get snr => 'SNR';
|
||||
|
||||
@override
|
||||
String get ackTimeout => 'ACK 超时';
|
||||
|
||||
@override
|
||||
String get opcode => '操作码';
|
||||
|
||||
@override
|
||||
String get payload => '有效载荷';
|
||||
|
||||
@override
|
||||
String get hops => '跳数';
|
||||
|
||||
@override
|
||||
String get hashSize => '哈希大小';
|
||||
|
||||
@override
|
||||
String get pathBytes => '路径字节';
|
||||
|
||||
@override
|
||||
String get selectedPath => '选定路径';
|
||||
|
||||
@override
|
||||
String get estimatedTx => '预计传输';
|
||||
|
||||
@override
|
||||
String get senderToReceipt => '发送到接收';
|
||||
|
||||
@override
|
||||
String get receivedCopies => '收到的副本';
|
||||
|
||||
@override
|
||||
String get retryCause => '重试原因';
|
||||
|
||||
@override
|
||||
String get retryMode => '重试模式';
|
||||
|
||||
@override
|
||||
String get retryResult => '重试结果';
|
||||
|
||||
@override
|
||||
String get lastRetry => '最后重试';
|
||||
|
||||
@override
|
||||
String get rxPackets => 'RX 数据包';
|
||||
|
||||
@override
|
||||
String get mesh => 'Mesh';
|
||||
|
||||
@override
|
||||
String get rate => '速率';
|
||||
|
||||
@override
|
||||
String get window => '窗口';
|
||||
|
||||
@override
|
||||
String get posttxDelay => '发送后延迟';
|
||||
|
||||
@override
|
||||
String get bandpass => '带通';
|
||||
|
||||
@override
|
||||
String get bandpassFilterVoice => '带通语音滤波';
|
||||
|
||||
@override
|
||||
String get frequency => '频率';
|
||||
|
||||
@override
|
||||
String get australia => '澳大利亚';
|
||||
|
||||
@override
|
||||
String get australiaNarrow => '澳大利亚(窄带)';
|
||||
|
||||
@override
|
||||
String get australiaQld => '澳大利亚:QLD';
|
||||
|
||||
@override
|
||||
String get australiaSaWa => '澳大利亚:SA, WA';
|
||||
|
||||
@override
|
||||
String get newZealand => '新西兰';
|
||||
|
||||
@override
|
||||
String get newZealandNarrow => '新西兰(窄带)';
|
||||
|
||||
@override
|
||||
String get switzerland => '瑞士';
|
||||
|
||||
@override
|
||||
String get portugal433 => '葡萄牙 433';
|
||||
|
||||
@override
|
||||
String get portugal868 => '葡萄牙 868';
|
||||
|
||||
@override
|
||||
String get czechRepublicNarrow => '捷克(窄带)';
|
||||
|
||||
@override
|
||||
String get eu433mhzLongRange => '欧盟 433MHz(远距离)';
|
||||
|
||||
@override
|
||||
String get euukDeprecated => '欧盟/英国(已弃用)';
|
||||
|
||||
@override
|
||||
String get euukNarrow => '欧盟/英国(窄带)';
|
||||
|
||||
@override
|
||||
String get usacanadaRecommended => '美国/加拿大(推荐)';
|
||||
|
||||
@override
|
||||
String get vietnamDeprecated => '越南(已弃用)';
|
||||
|
||||
@override
|
||||
String get vietnamNarrow => '越南(窄带)';
|
||||
|
||||
@override
|
||||
String get active => '活动';
|
||||
|
||||
@override
|
||||
String get addContact => '添加联系人';
|
||||
|
||||
@override
|
||||
String get all => '全部';
|
||||
|
||||
@override
|
||||
String get autoResolve => '自动解析';
|
||||
|
||||
@override
|
||||
String get clearAllLabel => '清除全部';
|
||||
|
||||
@override
|
||||
String get clearRelays => '清除中继';
|
||||
|
||||
@override
|
||||
String get clearFilters => '清除过滤器';
|
||||
|
||||
@override
|
||||
String get clearRoute => '清除路由';
|
||||
|
||||
@override
|
||||
String get clearMessages => '清除消息';
|
||||
|
||||
@override
|
||||
String get clearScale => '清除比例';
|
||||
|
||||
@override
|
||||
String get clearDiscoveries => '清除发现';
|
||||
|
||||
@override
|
||||
String get clearOnlineTraceDatabase => '清除在线追踪数据库';
|
||||
|
||||
@override
|
||||
String get clearAllChannels => '清除所有频道';
|
||||
|
||||
@override
|
||||
String get clearAllContacts => '清除所有联系人';
|
||||
|
||||
@override
|
||||
String get clearChannels => '清除频道';
|
||||
|
||||
@override
|
||||
String get clearContacts => '清除联系人';
|
||||
|
||||
@override
|
||||
String get clearPathOnMaxRetry => '最大重试时清除路径';
|
||||
|
||||
@override
|
||||
String get create => '创建';
|
||||
|
||||
@override
|
||||
String get custom => '自定义';
|
||||
|
||||
@override
|
||||
String get defaultValue => '默认';
|
||||
|
||||
@override
|
||||
String get duplicate => '复制';
|
||||
|
||||
@override
|
||||
String get editName => '编辑名称';
|
||||
|
||||
@override
|
||||
String get open => '打开';
|
||||
|
||||
@override
|
||||
String get paste => '粘贴';
|
||||
|
||||
@override
|
||||
String get preview => '预览';
|
||||
|
||||
@override
|
||||
String get remove => '移除';
|
||||
|
||||
@override
|
||||
String get rename => '重命名';
|
||||
|
||||
@override
|
||||
String get resolveAll => '解析全部';
|
||||
|
||||
@override
|
||||
String get send => '发送';
|
||||
|
||||
@override
|
||||
String get sendAnyway => '仍然发送';
|
||||
|
||||
@override
|
||||
String get share => '分享';
|
||||
|
||||
@override
|
||||
String get shareContact => '分享联系人';
|
||||
|
||||
@override
|
||||
String get trace => '追踪';
|
||||
|
||||
@override
|
||||
String get use => '使用';
|
||||
|
||||
@override
|
||||
String get useSelectedFrequency => '使用选定频率';
|
||||
|
||||
@override
|
||||
String get discovery => '发现';
|
||||
|
||||
@override
|
||||
String get discoverRepeaters => '发现中继器';
|
||||
|
||||
@override
|
||||
String get discoverSensors => '发现传感器';
|
||||
|
||||
@override
|
||||
String get repeaterDiscoverySent => '中继器发现已发送';
|
||||
|
||||
@override
|
||||
String get sensorDiscoverySent => '传感器发现已发送';
|
||||
|
||||
@override
|
||||
String get clearedPendingDiscoveries => '已清除待处理的发现。';
|
||||
|
||||
@override
|
||||
String get autoDiscovery => '自动发现';
|
||||
|
||||
@override
|
||||
String get enableAutomaticAdding => '启用自动添加';
|
||||
|
||||
@override
|
||||
String get autoaddRepeaters => '自动添加中继器';
|
||||
|
||||
@override
|
||||
String get autoaddRoomServers => '自动添加房间服务器';
|
||||
|
||||
@override
|
||||
String get autoaddSensors => '自动添加传感器';
|
||||
|
||||
@override
|
||||
String get autoaddUsers => '自动添加用户';
|
||||
|
||||
@override
|
||||
String get overwriteOldestWhenFull => '满时覆盖最旧的';
|
||||
|
||||
@override
|
||||
String get storage => '存储';
|
||||
|
||||
@override
|
||||
String get dangerZone => '危险区域';
|
||||
|
||||
@override
|
||||
String get profiles => '配置文件';
|
||||
|
||||
@override
|
||||
String get favourites => '收藏夹';
|
||||
|
||||
@override
|
||||
String get sensors => '传感器';
|
||||
|
||||
@override
|
||||
String get others => '其他';
|
||||
|
||||
@override
|
||||
String get gpsModule => 'GPS 模块';
|
||||
|
||||
@override
|
||||
String get liveTraffic => '实时流量';
|
||||
|
||||
@override
|
||||
String get repeatersMap => '中继器地图';
|
||||
|
||||
@override
|
||||
String get spectrumScan => '频谱扫描';
|
||||
|
||||
@override
|
||||
String get blePacketLogs => 'BLE 数据包日志';
|
||||
|
||||
@override
|
||||
String get onlineTraceDatabase => '在线追踪数据库';
|
||||
|
||||
@override
|
||||
String get routePathByteSize => '路由路径字节大小';
|
||||
|
||||
@override
|
||||
String get messageNotifications => '消息通知';
|
||||
|
||||
@override
|
||||
String get sarAlerts => 'SAR 警报';
|
||||
|
||||
@override
|
||||
String get discoveryNotifications => '发现通知';
|
||||
|
||||
@override
|
||||
String get updateNotifications => '更新通知';
|
||||
|
||||
@override
|
||||
String get muteWhileAppIsOpen => '应用打开时静音';
|
||||
|
||||
@override
|
||||
String get disableContacts => '禁用联系人';
|
||||
|
||||
@override
|
||||
String get enableSensorsTab => '启用传感器标签';
|
||||
|
||||
@override
|
||||
String get enableProfiles => '启用配置文件';
|
||||
|
||||
@override
|
||||
String get autoRouteRotation => '自动路由轮换';
|
||||
|
||||
@override
|
||||
String get nearestRepeaterFallback => '最近中继器回退';
|
||||
|
||||
@override
|
||||
String get deleteAllStoredMessageHistory => '删除所有存储的消息历史';
|
||||
|
||||
@override
|
||||
String get messageFontSize => '消息字体大小';
|
||||
|
||||
@override
|
||||
String get rotateMapWithHeading => '随航向旋转地图';
|
||||
|
||||
@override
|
||||
String get showMapDebugInfo => '显示地图调试信息';
|
||||
|
||||
@override
|
||||
String get openMapInFullscreen => '全屏打开地图';
|
||||
|
||||
@override
|
||||
String get showSarMarkersLabel => '显示 SAR 标记';
|
||||
|
||||
@override
|
||||
String get displaySarMarkersOnTheMainMap => '在主地图上显示 SAR 标记';
|
||||
|
||||
@override
|
||||
String get showAllContactTrailsLabel => '显示所有联系人轨迹';
|
||||
|
||||
@override
|
||||
String get hideRepeatersOnMap => '在地图上隐藏中继器';
|
||||
|
||||
@override
|
||||
String get setMapScale => '设置地图比例';
|
||||
|
||||
@override
|
||||
String get customMapScaleSaved => '自定义地图比例已保存';
|
||||
|
||||
@override
|
||||
String get voiceBitrate => '语音比特率';
|
||||
|
||||
@override
|
||||
String get voiceCompressor => '语音压缩器';
|
||||
|
||||
@override
|
||||
String get balancesQuietAndLoudSpeechLevels => '平衡安静和响亮的语音级别';
|
||||
|
||||
@override
|
||||
String get voiceLimiter => '语音限制器';
|
||||
|
||||
@override
|
||||
String get preventsClippingPeaksBeforeEncoding => '防止编码前的削波';
|
||||
|
||||
@override
|
||||
String get micAutoGain => '麦克风自动增益';
|
||||
|
||||
@override
|
||||
String get letsTheRecorderAdjustInputLevel => '让录音器调整输入级别';
|
||||
|
||||
@override
|
||||
String get echoCancellation => '回声消除';
|
||||
|
||||
@override
|
||||
String get noiseSuppression => '噪音抑制';
|
||||
|
||||
@override
|
||||
String get trimSilenceInVoiceMessages => '修剪语音消息中的静音';
|
||||
|
||||
@override
|
||||
String get compressor => '压缩器';
|
||||
|
||||
@override
|
||||
String get limiter => '限制器';
|
||||
|
||||
@override
|
||||
String get autoGain => '自动增益';
|
||||
|
||||
@override
|
||||
String get echoCancel => '回声';
|
||||
|
||||
@override
|
||||
String get noiseSuppress => '噪声';
|
||||
|
||||
@override
|
||||
String get silenceTrim => '静音';
|
||||
|
||||
@override
|
||||
String get maxImageSize => '最大图片大小';
|
||||
|
||||
@override
|
||||
String get imageCompression => '图片压缩';
|
||||
|
||||
@override
|
||||
String get grayscale => '灰度';
|
||||
|
||||
@override
|
||||
String get ultraMode => '超级模式';
|
||||
|
||||
@override
|
||||
String get fastPrivateGpsUpdates => '快速私有 GPS 更新';
|
||||
|
||||
@override
|
||||
String get movementThreshold => '移动阈值';
|
||||
|
||||
@override
|
||||
String get fastGpsMovementThreshold => '快速 GPS 移动阈值';
|
||||
|
||||
@override
|
||||
String get fastGpsActiveuseInterval => '快速 GPS 活动使用间隔';
|
||||
|
||||
@override
|
||||
String get activeuseUpdateInterval => '活动使用更新间隔';
|
||||
|
||||
@override
|
||||
String get repeatNearbyTraffic => '转发附近流量';
|
||||
|
||||
@override
|
||||
String get relayThroughRepeatersAcrossTheMesh => '通过网格中的中继器转发';
|
||||
|
||||
@override
|
||||
String get nearbyOnlyWithoutRepeaterFlooding => '仅附近,无中继器泛洪';
|
||||
|
||||
@override
|
||||
String get multihop => '多跳';
|
||||
|
||||
@override
|
||||
String get createProfile => '创建配置文件';
|
||||
|
||||
@override
|
||||
String get renameProfile => '重命名配置文件';
|
||||
|
||||
@override
|
||||
String get newProfile => '新配置文件';
|
||||
|
||||
@override
|
||||
String get manageProfiles => '管理配置文件';
|
||||
|
||||
@override
|
||||
String get enableProfilesToStartManagingThem => '启用配置文件以开始管理它们。';
|
||||
|
||||
@override
|
||||
String get openMessage => '打开消息';
|
||||
|
||||
@override
|
||||
String get jumpToTheRelatedSarMessage => '跳转到相关的 SAR 消息';
|
||||
|
||||
@override
|
||||
String get removeSarMarker => '移除 SAR 标记';
|
||||
|
||||
@override
|
||||
String get pleaseSelectADestinationToSendSarMarker => '请选择目标以发送 SAR 标记';
|
||||
|
||||
@override
|
||||
String get sarMarkerBroadcastToPublicChannel => 'SAR 标记已广播到公共频道';
|
||||
|
||||
@override
|
||||
String get sarMarkerSentToRoom => 'SAR 标记已发送到房间';
|
||||
|
||||
@override
|
||||
String get loadFromGallery => '从图库加载';
|
||||
|
||||
@override
|
||||
String get replaceImage => '替换图片';
|
||||
|
||||
@override
|
||||
String get selectFromGallery => '从图库选择';
|
||||
|
||||
@override
|
||||
String get team => '团队';
|
||||
|
||||
@override
|
||||
String get found => '已找到';
|
||||
|
||||
@override
|
||||
String get staging => '集结区';
|
||||
|
||||
@override
|
||||
String get object => '物体';
|
||||
|
||||
@override
|
||||
String get quiet => '安静';
|
||||
|
||||
@override
|
||||
String get moderate => '中等';
|
||||
|
||||
@override
|
||||
String get busy => '繁忙';
|
||||
|
||||
@override
|
||||
String get spectrumScanReturnedNoCandidateFrequencies => '频谱扫描未找到候选频率';
|
||||
|
||||
@override
|
||||
String get searchMessages => '搜索消息';
|
||||
|
||||
@override
|
||||
String get sendImageFromGallery => '从图库发送图片';
|
||||
|
||||
@override
|
||||
String get takePhoto => '拍照';
|
||||
|
||||
@override
|
||||
String get dmOnly => '仅私信';
|
||||
|
||||
@override
|
||||
String get allMessages => '所有消息';
|
||||
|
||||
@override
|
||||
String get sendToPublicChannel => '发送到公共频道?';
|
||||
|
||||
@override
|
||||
String get selectMarkerTypeAndDestination => '选择标记类型和目标';
|
||||
|
||||
@override
|
||||
String get noDestinationsAvailableLabel => '没有可用的目标';
|
||||
|
||||
@override
|
||||
String get image => '图片';
|
||||
|
||||
@override
|
||||
String get format => '格式';
|
||||
|
||||
@override
|
||||
String get dimensions => '尺寸';
|
||||
|
||||
@override
|
||||
String get segments => '分段';
|
||||
|
||||
@override
|
||||
String get transfers => '传输';
|
||||
|
||||
@override
|
||||
String get downloadedBy => '已下载';
|
||||
|
||||
@override
|
||||
String get saveDiscoverySettings => '保存发现设置';
|
||||
|
||||
@override
|
||||
String get savePublicInfo => '保存公共信息';
|
||||
|
||||
@override
|
||||
String get saveRadioSettings => '保存无线电设置';
|
||||
|
||||
@override
|
||||
String get savePath => '保存路径';
|
||||
|
||||
@override
|
||||
String get wipeDeviceData => '擦除设备数据';
|
||||
|
||||
@override
|
||||
String get wipeDevice => '擦除设备';
|
||||
|
||||
@override
|
||||
String get destructiveDeviceActions => '破坏性设备操作。';
|
||||
|
||||
@override
|
||||
String get chooseAPresetOrFinetuneCustomRadioSettings => '选择预设或微调自定义无线电设置。';
|
||||
|
||||
@override
|
||||
String get chooseTheNameAndLocationThisDeviceShares => '选择此设备共享的名称和位置。';
|
||||
|
||||
@override
|
||||
String get availableSpaceOnThisDevice => '此设备上的可用空间。';
|
||||
|
||||
@override
|
||||
String get used => '已使用';
|
||||
|
||||
@override
|
||||
String get total => '总计';
|
||||
|
||||
@override
|
||||
String get renameValue => '重命名值';
|
||||
|
||||
@override
|
||||
String get customizeFields => '自定义字段';
|
||||
|
||||
@override
|
||||
String get livePreview => '实时预览';
|
||||
|
||||
@override
|
||||
String get refreshSchedule => '刷新计划';
|
||||
|
||||
@override
|
||||
String get noResponse => '无响应';
|
||||
|
||||
@override
|
||||
String get refreshing => '刷新中';
|
||||
|
||||
@override
|
||||
String get unavailable => '不可用';
|
||||
|
||||
@override
|
||||
String get pickARelayOrNodeToWatchInSensors => '选择要在传感器中监视的中继或节点。';
|
||||
|
||||
@override
|
||||
String get publicKeyLabel => '公钥';
|
||||
|
||||
@override
|
||||
String get alreadyInContacts => '已在联系人中';
|
||||
|
||||
@override
|
||||
String get connectToADeviceBeforeAddingContacts => '添加联系人前请连接到设备';
|
||||
|
||||
@override
|
||||
String get fromContacts => '来自联系人';
|
||||
|
||||
@override
|
||||
String get onlineOnly => '仅在线';
|
||||
|
||||
@override
|
||||
String get inBoth => '两者中';
|
||||
|
||||
@override
|
||||
String get source => '来源';
|
||||
|
||||
@override
|
||||
String get manualRouteEdit => '手动路由编辑';
|
||||
|
||||
@override
|
||||
String get observedMeshRoute => '观察到的网格路由';
|
||||
|
||||
@override
|
||||
String get allMessagesCleared => '所有消息已清除';
|
||||
|
||||
@override
|
||||
String get onlineTraceDatabaseCleared => '在线追踪数据库已清除';
|
||||
|
||||
@override
|
||||
String get packetLogsCleared => '数据包日志已清除';
|
||||
|
||||
@override
|
||||
String get hexDataCopiedToClipboard => '十六进制数据已复制到剪贴板';
|
||||
|
||||
@override
|
||||
String get developerModeEnabled => '开发者模式已启用';
|
||||
|
||||
@override
|
||||
String get developerModeDisabled => '开发者模式已禁用';
|
||||
|
||||
@override
|
||||
String get clipboardIsEmpty => '剪贴板为空';
|
||||
|
||||
@override
|
||||
String get contactImported => '联系人已导入';
|
||||
|
||||
@override
|
||||
String get contactLinkCopiedToClipboard => '联系人链接已复制到剪贴板';
|
||||
|
||||
@override
|
||||
String get failedToExportContact => '导出联系人失败';
|
||||
|
||||
@override
|
||||
String get noLogsToExport => '没有日志可导出';
|
||||
|
||||
@override
|
||||
String get exportAsCsv => '导出为 CSV';
|
||||
|
||||
@override
|
||||
String get exportAsText => '导出为文本';
|
||||
|
||||
@override
|
||||
String get receivedRfc3339 => '收到 (RFC3339)';
|
||||
|
||||
@override
|
||||
String get buildTime => '构建时间';
|
||||
|
||||
@override
|
||||
String get downloadUrlNotAvailable => '下载链接不可用';
|
||||
|
||||
@override
|
||||
String get cannotOpenDownloadUrl => '无法打开下载链接';
|
||||
|
||||
@override
|
||||
String get updateCheckIsOnlyAvailableOnAndroid => '更新检查仅在 Android 上可用';
|
||||
|
||||
@override
|
||||
String get youAreRunningTheLatestVersion => '您正在使用最新版本';
|
||||
|
||||
@override
|
||||
String get updateAvailableButDownloadUrlNotFound => '有更新可用但未找到下载链接';
|
||||
|
||||
@override
|
||||
String get startTictactoe => '开始井字棋';
|
||||
|
||||
@override
|
||||
String get tictactoeUnavailable => '井字棋不可用';
|
||||
|
||||
@override
|
||||
String get tictactoeOpponentUnknown => '井字棋:对手未知';
|
||||
|
||||
@override
|
||||
String get tictactoeWaitingForStart => '井字棋:等待开始';
|
||||
|
||||
@override
|
||||
String get acceptsShareLinks => '接受分享链接';
|
||||
|
||||
@override
|
||||
String get supportsRawHex => '支持原始十六进制';
|
||||
|
||||
@override
|
||||
String get clipboardfriendly => '剪贴板友好';
|
||||
|
||||
@override
|
||||
String get captured => '已捕获';
|
||||
|
||||
@override
|
||||
String get size => '大小';
|
||||
|
||||
@override
|
||||
String get noCustomChannelsToClear => '没有自定义频道可清除。';
|
||||
|
||||
@override
|
||||
String get noDeviceContactsToClear => '没有设备联系人可清除。';
|
||||
}
|
||||
|
||||
@@ -2276,5 +2276,304 @@
|
||||
"voiceUnavailable": "Głos jest obecnie niedostępny",
|
||||
"@voiceUnavailable": {},
|
||||
"requestingVoice": "Pobieranie głosu",
|
||||
"@requestingVoice": {}
|
||||
"@requestingVoice": {},
|
||||
"device": "urządzenie",
|
||||
"change": "Zmień",
|
||||
"wizardOverviewDescription": "Ta aplikacja łączy wiadomości MeshCore, terenowe aktualizacje SAR, mapy i narzędzia urządzenia w jednym miejscu.",
|
||||
"wizardOverviewFeature1": "Wysyłaj wiadomości bezpośrednie, wpisy pokojowe i wiadomości kanałowe z głównej karty Wiadomości.",
|
||||
"wizardOverviewFeature2": "Udostępniaj znaczniki SAR, rysunki mapy, klipy głosowe i obrazy przez sieć mesh.",
|
||||
"wizardOverviewFeature3": "Połącz się przez BLE lub TCP, a następnie zarządzaj radiem towarzyszącym bezpośrednio z aplikacji.",
|
||||
"wizardMessagingTitle": "Wiadomości i raporty terenowe",
|
||||
"wizardMessagingDescription": "Wiadomości to tutaj coś więcej niż zwykły tekst. Aplikacja obsługuje już kilka typów danych operacyjnych i przepływów transferu.",
|
||||
"wizardMessagingFeature1": "Wysyłaj wiadomości bezpośrednie, wpisy pokojowe i ruch kanałowy z jednego edytora.",
|
||||
"wizardMessagingFeature2": "Twórz aktualizacje SAR i wielokrotnego użytku szablony SAR do typowych raportów terenowych.",
|
||||
"wizardMessagingFeature3": "Przesyłaj sesje głosowe i obrazy z postępem i szacowanym czasem transmisji w interfejsie.",
|
||||
"wizardConnectDeviceTitle": "Połącz urządzenie",
|
||||
"wizardConnectDeviceDescription": "Podłącz radio MeshCore, wybierz nazwę i zastosuj profil radiowy przed kontynuacją.",
|
||||
"wizardSetupBadge": "Konfiguracja",
|
||||
"wizardOverviewBadge": "Przegląd",
|
||||
"wizardConnectedToDevice": "Połączono z {deviceName}",
|
||||
"wizardNoDeviceConnected": "Brak podłączonego urządzenia",
|
||||
"wizardSkipForNow": "Pomiń na razie",
|
||||
"wizardDeviceNameLabel": "Nazwa urządzenia",
|
||||
"wizardDeviceNameHelp": "Ta nazwa jest rozgłaszana innym użytkownikom MeshCore.",
|
||||
"wizardConfigRegionLabel": "Region konfiguracji",
|
||||
"wizardConfigRegionHelp": "Używa pełnej oficjalnej listy presetów MeshCore. Domyślnie wybrane jest EU/UK (Narrow).",
|
||||
"wizardPresetNote1": "Upewnij się, że wybrany preset jest zgodny z lokalnymi przepisami radiowymi.",
|
||||
"wizardPresetNote2": "Lista odpowiada oficjalnemu źródłu presetów narzędzia konfiguracji MeshCore.",
|
||||
"wizardPresetNote3": "EU/UK (Narrow) pozostaje domyślnie wybrane podczas wdrożenia.",
|
||||
"wizardSaving": "Zapisywanie...",
|
||||
"wizardSaveAndContinue": "Zapisz i kontynuuj",
|
||||
"wizardEnterDeviceName": "Wprowadź nazwę urządzenia przed kontynuacją.",
|
||||
"wizardDeviceSetupSaved": "Zapisano {deviceName} z ustawieniem {presetName}.",
|
||||
"wizardNetworkTitle": "Kontakty, pokoje i repeatery",
|
||||
"wizardNetworkDescription": "Karta Kontakty porządkuje sieć, którą odkrywasz, oraz trasy, których uczysz się z czasem.",
|
||||
"wizardNetworkFeature1": "Przeglądaj członków zespołu, repeatery, pokoje, kanały i oczekujące ogłoszenia na jednej liście.",
|
||||
"wizardNetworkFeature2": "Używaj smart ping, logowania do pokoi, poznanych ścieżek i narzędzi resetowania tras, gdy łączność staje się chaotyczna.",
|
||||
"wizardNetworkFeature3": "Twórz kanały i zarządzaj celami sieciowymi bez opuszczania aplikacji.",
|
||||
"wizardMapOpsTitle": "Mapa, ślady i współdzielona geometria",
|
||||
"wizardMapOpsDescription": "Mapa aplikacji jest bezpośrednio powiązana z wiadomościami, śledzeniem i nakładkami SAR zamiast być osobnym podglądem.",
|
||||
"wizardMapOpsFeature1": "Śledź własną pozycję, lokalizacje członków zespołu i ślady ruchu na mapie.",
|
||||
"wizardMapOpsFeature2": "Otwieraj rysunki z wiadomości, podglądaj je w miejscu i usuwaj z mapy w razie potrzeby.",
|
||||
"wizardMapOpsFeature3": "Korzystaj z widoków map repeaterów i współdzielonych nakładek, aby zrozumieć zasięg sieci w terenie.",
|
||||
"wizardToolsTitle": "Narzędzia poza wiadomościami",
|
||||
"wizardToolsDescription": "Jest tu więcej niż cztery główne karty. Aplikacja obejmuje także konfigurację, diagnostykę i opcjonalne przepływy czujników.",
|
||||
"wizardToolsFeature1": "Otwórz konfigurację urządzenia, aby zmienić ustawienia radia, telemetrię, moc TX i szczegóły urządzenia towarzyszącego.",
|
||||
"wizardToolsFeature2": "Włącz kartę Czujniki, gdy chcesz mieć pulpity monitorowanych czujników i szybkie odświeżanie.",
|
||||
"wizardToolsFeature3": "Używaj logów pakietów, skanowania widma i diagnostyki deweloperskiej podczas rozwiązywania problemów z mesh.",
|
||||
"contactInSensors": "W Czujnikach",
|
||||
"contactAddToSensors": "Dodaj do Czujników",
|
||||
"contactSetPath": "Ustaw ścieżkę",
|
||||
"contactAddedToSensors": "{contactName} dodano do Czujników",
|
||||
"contactFailedToClearRoute": "Nie udało się wyczyścić trasy: {error}",
|
||||
"contactRouteCleared": "Trasa wyczyszczona",
|
||||
"contactRouteSet": "Ustawiono trasę: {route}",
|
||||
"contactFailedToSetRoute": "Nie udało się ustawić trasy: {error}",
|
||||
"rssi": "RSSI",
|
||||
"snr": "SNR",
|
||||
"ackTimeout": "Limit czasu ACK",
|
||||
"opcode": "Opcode",
|
||||
"payload": "Ładunek",
|
||||
"hops": "Skoki",
|
||||
"hashSize": "Rozmiar hash",
|
||||
"pathBytes": "Bajty ścieżki",
|
||||
"selectedPath": "Wybrana ścieżka",
|
||||
"estimatedTx": "Szacowane nadawanie",
|
||||
"senderToReceipt": "Od nadawcy do odbioru",
|
||||
"receivedCopies": "Otrzymane kopie",
|
||||
"retryCause": "Przyczyna ponowienia",
|
||||
"retryMode": "Tryb ponowienia",
|
||||
"retryResult": "Wynik ponowienia",
|
||||
"lastRetry": "Ostatnia próba",
|
||||
"rxPackets": "Pakiety RX",
|
||||
"mesh": "Mesh",
|
||||
"rate": "Szybkość",
|
||||
"window": "Okno",
|
||||
"posttxDelay": "Opóźnienie po nadawaniu",
|
||||
"bandpass": "Pasmowy",
|
||||
"bandpassFilterVoice": "Filtr pasmowy głosu",
|
||||
"frequency": "Częstotliwość",
|
||||
"australia": "Australia",
|
||||
"australiaNarrow": "Australia (wąski)",
|
||||
"australiaQld": "Australia: QLD",
|
||||
"australiaSaWa": "Australia: SA, WA",
|
||||
"newZealand": "Nowa Zelandia",
|
||||
"newZealandNarrow": "Nowa Zelandia (wąski)",
|
||||
"switzerland": "Szwajcaria",
|
||||
"portugal433": "Portugalia 433",
|
||||
"portugal868": "Portugalia 868",
|
||||
"czechRepublicNarrow": "Czechy (wąski)",
|
||||
"eu433mhzLongRange": "EU 433MHz (daleki zasięg)",
|
||||
"euukDeprecated": "EU/UK (przestarzały)",
|
||||
"euukNarrow": "EU/UK (wąski)",
|
||||
"usacanadaRecommended": "USA/Kanada (zalecany)",
|
||||
"vietnamDeprecated": "Wietnam (przestarzały)",
|
||||
"vietnamNarrow": "Wietnam (wąski)",
|
||||
"active": "Aktywny",
|
||||
"addContact": "Dodaj kontakt",
|
||||
"all": "Wszystko",
|
||||
"autoResolve": "Automatyczne rozwiązanie",
|
||||
"clearAllLabel": "Wyczyść wszystko",
|
||||
"clearRelays": "Wyczyść przekaźniki",
|
||||
"clearFilters": "Wyczyść filtry",
|
||||
"clearRoute": "Wyczyść trasę",
|
||||
"clearMessages": "Wyczyść wiadomości",
|
||||
"clearScale": "Wyczyść skalę",
|
||||
"clearDiscoveries": "Wyczyść odkrycia",
|
||||
"clearOnlineTraceDatabase": "Wyczyść bazę śladów",
|
||||
"clearAllChannels": "Wyczyść wszystkie kanały",
|
||||
"clearAllContacts": "Wyczyść wszystkie kontakty",
|
||||
"clearChannels": "Wyczyść kanały",
|
||||
"clearContacts": "Wyczyść kontakty",
|
||||
"clearPathOnMaxRetry": "Wyczyść ścieżkę przy maks. próbie",
|
||||
"create": "Utwórz",
|
||||
"custom": "Niestandardowy",
|
||||
"defaultValue": "Domyślny",
|
||||
"duplicate": "Duplikuj",
|
||||
"editName": "Edytuj nazwę",
|
||||
"open": "Otwórz",
|
||||
"paste": "Wklej",
|
||||
"preview": "Podgląd",
|
||||
"remove": "Usuń",
|
||||
"rename": "Zmień nazwę",
|
||||
"resolveAll": "Rozwiąż wszystko",
|
||||
"send": "Wyślij",
|
||||
"sendAnyway": "Wyślij mimo to",
|
||||
"share": "Udostępnij",
|
||||
"shareContact": "Udostępnij kontakt",
|
||||
"trace": "Śledzenie",
|
||||
"use": "Użyj",
|
||||
"useSelectedFrequency": "Użyj wybranej częstotliwości",
|
||||
"discovery": "Wykrywanie",
|
||||
"discoverRepeaters": "Wykryj przekaźniki",
|
||||
"discoverSensors": "Wykryj czujniki",
|
||||
"repeaterDiscoverySent": "Wykrywanie przekaźników wysłane",
|
||||
"sensorDiscoverySent": "Wykrywanie czujników wysłane",
|
||||
"clearedPendingDiscoveries": "Wyczyszczono oczekujące odkrycia.",
|
||||
"autoDiscovery": "Automatyczne wykrywanie",
|
||||
"enableAutomaticAdding": "Włącz automatyczne dodawanie",
|
||||
"autoaddRepeaters": "Automatycznie dodaj przekaźniki",
|
||||
"autoaddRoomServers": "Automatycznie dodaj serwery pokoi",
|
||||
"autoaddSensors": "Automatycznie dodaj czujniki",
|
||||
"autoaddUsers": "Automatycznie dodaj użytkowników",
|
||||
"overwriteOldestWhenFull": "Nadpisz najstarsze gdy pełne",
|
||||
"storage": "Pamięć",
|
||||
"dangerZone": "Strefa zagrożenia",
|
||||
"profiles": "Profile",
|
||||
"favourites": "Ulubione",
|
||||
"sensors": "Czujniki",
|
||||
"others": "Inne",
|
||||
"gpsModule": "Moduł GPS",
|
||||
"liveTraffic": "Ruch na żywo",
|
||||
"repeatersMap": "Mapa przekaźników",
|
||||
"spectrumScan": "Skanowanie spektrum",
|
||||
"blePacketLogs": "Logi pakietów BLE",
|
||||
"onlineTraceDatabase": "Baza śladów",
|
||||
"routePathByteSize": "Rozmiar ścieżki w bajtach",
|
||||
"messageNotifications": "Powiadomienia o wiadomościach",
|
||||
"sarAlerts": "Alerty SAR",
|
||||
"discoveryNotifications": "Powiadomienia o wykrywaniu",
|
||||
"updateNotifications": "Powiadomienia o aktualizacjach",
|
||||
"muteWhileAppIsOpen": "Wycisz gdy aplikacja jest otwarta",
|
||||
"disableContacts": "Wyłącz kontakty",
|
||||
"enableSensorsTab": "Włącz kartę Czujniki",
|
||||
"enableProfiles": "Włącz profile",
|
||||
"autoRouteRotation": "Automatyczna rotacja trasy",
|
||||
"nearestRepeaterFallback": "Najbliższy przekaźnik awaryjny",
|
||||
"deleteAllStoredMessageHistory": "Usuń całą historię wiadomości",
|
||||
"messageFontSize": "Rozmiar czcionki wiadomości",
|
||||
"rotateMapWithHeading": "Obracaj mapę z kierunkiem",
|
||||
"showMapDebugInfo": "Pokaż info debugowania mapy",
|
||||
"openMapInFullscreen": "Otwórz mapę na pełnym ekranie",
|
||||
"showSarMarkersLabel": "Pokaż znaczniki SAR",
|
||||
"displaySarMarkersOnTheMainMap": "Wyświetlaj znaczniki SAR na głównej mapie",
|
||||
"showAllContactTrailsLabel": "Pokaż wszystkie ślady kontaktów",
|
||||
"hideRepeatersOnMap": "Ukryj przekaźniki na mapie",
|
||||
"setMapScale": "Ustaw skalę mapy",
|
||||
"customMapScaleSaved": "Niestandardowa skala mapy zapisana",
|
||||
"voiceBitrate": "Bitrate głosu",
|
||||
"voiceCompressor": "Kompresor głosu",
|
||||
"balancesQuietAndLoudSpeechLevels": "Wyrównuje ciche i głośne poziomy mowy",
|
||||
"voiceLimiter": "Limiter głosu",
|
||||
"preventsClippingPeaksBeforeEncoding": "Zapobiega przycinaniu szczytów przed kodowaniem",
|
||||
"micAutoGain": "Automatyczne wzmocnienie mikrofonu",
|
||||
"letsTheRecorderAdjustInputLevel": "Pozwala nagrywarkowi dostosować poziom wejścia",
|
||||
"echoCancellation": "Usuwanie echa",
|
||||
"noiseSuppression": "Tłumienie szumu",
|
||||
"trimSilenceInVoiceMessages": "Przytnij ciszę w wiadomościach głosowych",
|
||||
"compressor": "Kompresor",
|
||||
"limiter": "Limiter",
|
||||
"autoGain": "Automatyczne wzmocnienie",
|
||||
"echoCancel": "Echo",
|
||||
"noiseSuppress": "Szum",
|
||||
"silenceTrim": "Cisza",
|
||||
"maxImageSize": "Maksymalny rozmiar obrazu",
|
||||
"imageCompression": "Kompresja obrazu",
|
||||
"grayscale": "Skala szarości",
|
||||
"ultraMode": "Tryb ultra",
|
||||
"fastPrivateGpsUpdates": "Szybkie prywatne aktualizacje GPS",
|
||||
"movementThreshold": "Próg ruchu",
|
||||
"fastGpsMovementThreshold": "Próg ruchu szybkiego GPS",
|
||||
"fastGpsActiveuseInterval": "Interwał aktywnego użycia szybkiego GPS",
|
||||
"activeuseUpdateInterval": "Interwał aktualizacji przy aktywnym użyciu",
|
||||
"repeatNearbyTraffic": "Powtarzaj pobliski ruch",
|
||||
"relayThroughRepeatersAcrossTheMesh": "Przekazuj przez przekaźniki w sieci",
|
||||
"nearbyOnlyWithoutRepeaterFlooding": "Tylko w pobliżu, bez zalewania przekaźników",
|
||||
"multihop": "Wieloskokowy",
|
||||
"createProfile": "Utwórz profil",
|
||||
"renameProfile": "Zmień nazwę profilu",
|
||||
"newProfile": "Nowy profil",
|
||||
"manageProfiles": "Zarządzaj profilami",
|
||||
"enableProfilesToStartManagingThem": "Włącz profile, aby zacząć nimi zarządzać.",
|
||||
"openMessage": "Otwórz wiadomość",
|
||||
"jumpToTheRelatedSarMessage": "Przejdź do powiązanej wiadomości SAR",
|
||||
"removeSarMarker": "Usuń znacznik SAR",
|
||||
"pleaseSelectADestinationToSendSarMarker": "Wybierz cel wysłania znacznika SAR",
|
||||
"sarMarkerBroadcastToPublicChannel": "Znacznik SAR wysłany na kanał publiczny",
|
||||
"sarMarkerSentToRoom": "Znacznik SAR wysłany do pokoju",
|
||||
"loadFromGallery": "Załaduj z galerii",
|
||||
"replaceImage": "Zamień obraz",
|
||||
"selectFromGallery": "Wybierz z galerii",
|
||||
"team": "Zespół",
|
||||
"found": "Znaleziono",
|
||||
"staging": "Punkt zbiórki",
|
||||
"object": "Obiekt",
|
||||
"quiet": "Cicho",
|
||||
"moderate": "Umiarkowane",
|
||||
"busy": "Zajęty",
|
||||
"spectrumScanReturnedNoCandidateFrequencies": "Skanowanie spektrum nie znalazło częstotliwości kandydatów",
|
||||
"searchMessages": "Szukaj wiadomości",
|
||||
"sendImageFromGallery": "Wyślij obraz z galerii",
|
||||
"takePhoto": "Zrób zdjęcie",
|
||||
"dmOnly": "Tylko wiadomość bezpośrednia",
|
||||
"allMessages": "Wszystkie wiadomości",
|
||||
"sendToPublicChannel": "Wysłać na kanał publiczny?",
|
||||
"selectMarkerTypeAndDestination": "Wybierz typ znacznika i cel",
|
||||
"noDestinationsAvailableLabel": "Brak dostępnych celów",
|
||||
"image": "Obraz",
|
||||
"format": "Format",
|
||||
"dimensions": "Wymiary",
|
||||
"segments": "Segmenty",
|
||||
"transfers": "Transfery",
|
||||
"downloadedBy": "Pobrane przez",
|
||||
"saveDiscoverySettings": "Zapisz ustawienia wykrywania",
|
||||
"savePublicInfo": "Zapisz informacje publiczne",
|
||||
"saveRadioSettings": "Zapisz ustawienia radia",
|
||||
"savePath": "Zapisz ścieżkę",
|
||||
"wipeDeviceData": "Wyczyść dane urządzenia",
|
||||
"wipeDevice": "Wyczyść urządzenie",
|
||||
"destructiveDeviceActions": "Destrukcyjne akcje na urządzeniu.",
|
||||
"chooseAPresetOrFinetuneCustomRadioSettings": "Wybierz ustawienie wstępne lub dostosuj ustawienia radia.",
|
||||
"chooseTheNameAndLocationThisDeviceShares": "Wybierz nazwę i lokalizację udostępnianą przez to urządzenie.",
|
||||
"availableSpaceOnThisDevice": "Dostępne miejsce na tym urządzeniu.",
|
||||
"used": "Użyto",
|
||||
"total": "Razem",
|
||||
"renameValue": "Zmień nazwę wartości",
|
||||
"customizeFields": "Dostosuj pola",
|
||||
"livePreview": "Podgląd na żywo",
|
||||
"refreshSchedule": "Harmonogram odświeżania",
|
||||
"noResponse": "Brak odpowiedzi",
|
||||
"refreshing": "Odświeżanie",
|
||||
"unavailable": "Niedostępny",
|
||||
"pickARelayOrNodeToWatchInSensors": "Wybierz przekaźnik lub węzeł do obserwacji.",
|
||||
"publicKeyLabel": "Klucz publiczny",
|
||||
"alreadyInContacts": "Już w kontaktach",
|
||||
"connectToADeviceBeforeAddingContacts": "Połącz się z urządzeniem przed dodaniem kontaktów",
|
||||
"fromContacts": "Z kontaktów",
|
||||
"onlineOnly": "Tylko online",
|
||||
"inBoth": "W obu",
|
||||
"source": "Źródło",
|
||||
"manualRouteEdit": "Ręczna edycja trasy",
|
||||
"observedMeshRoute": "Obserwowana trasa sieci",
|
||||
"allMessagesCleared": "Wszystkie wiadomości wyczyszczone",
|
||||
"onlineTraceDatabaseCleared": "Baza śladów wyczyszczona",
|
||||
"packetLogsCleared": "Logi pakietów wyczyszczone",
|
||||
"hexDataCopiedToClipboard": "Dane hex skopiowane do schowka",
|
||||
"developerModeEnabled": "Tryb programisty włączony",
|
||||
"developerModeDisabled": "Tryb programisty wyłączony",
|
||||
"clipboardIsEmpty": "Schowek jest pusty",
|
||||
"contactImported": "Kontakt zaimportowany",
|
||||
"contactLinkCopiedToClipboard": "Link kontaktu skopiowany do schowka",
|
||||
"failedToExportContact": "Nie udało się wyeksportować kontaktu",
|
||||
"noLogsToExport": "Brak logów do eksportu",
|
||||
"exportAsCsv": "Eksportuj jako CSV",
|
||||
"exportAsText": "Eksportuj jako tekst",
|
||||
"receivedRfc3339": "Odebrano (RFC3339)",
|
||||
"buildTime": "Czas budowania",
|
||||
"downloadUrlNotAvailable": "URL pobierania niedostępny",
|
||||
"cannotOpenDownloadUrl": "Nie można otworzyć URL pobierania",
|
||||
"updateCheckIsOnlyAvailableOnAndroid": "Sprawdzanie aktualizacji dostępne tylko na Androidzie",
|
||||
"youAreRunningTheLatestVersion": "Używasz najnowszej wersji",
|
||||
"updateAvailableButDownloadUrlNotFound": "Aktualizacja dostępna, ale URL pobierania nie znaleziony",
|
||||
"startTictactoe": "Rozpocznij Tic-Tac-Toe",
|
||||
"tictactoeUnavailable": "Tic-Tac-Toe niedostępny",
|
||||
"tictactoeOpponentUnknown": "Tic-Tac-Toe: przeciwnik nieznany",
|
||||
"tictactoeWaitingForStart": "Tic-Tac-Toe: oczekiwanie na start",
|
||||
"acceptsShareLinks": "Akceptuje udostępnione linki",
|
||||
"supportsRawHex": "Obsługuje surowy hex",
|
||||
"clipboardfriendly": "Przyjazne dla schowka",
|
||||
"captured": "Przechwycone",
|
||||
"size": "Rozmiar",
|
||||
"noCustomChannelsToClear": "Brak niestandardowych kanałów do wyczyszczenia.",
|
||||
"noDeviceContactsToClear": "Brak kontaktów urządzenia do wyczyszczenia."
|
||||
}
|
||||
|
||||
@@ -2276,5 +2276,304 @@
|
||||
"voiceUnavailable": "A voz não está disponível no momento",
|
||||
"@voiceUnavailable": {},
|
||||
"requestingVoice": "Solicitando voz",
|
||||
"@requestingVoice": {}
|
||||
"@requestingVoice": {},
|
||||
"device": "dispositivo",
|
||||
"change": "Alterar",
|
||||
"wizardOverviewDescription": "Este aplicativo reúne mensagens MeshCore, atualizações SAR em campo, mapas e ferramentas do dispositivo em um só lugar.",
|
||||
"wizardOverviewFeature1": "Envie mensagens diretas, publicações em salas e mensagens de canal pela guia principal Mensagens.",
|
||||
"wizardOverviewFeature2": "Compartilhe marcadores SAR, desenhos de mapa, clipes de voz e imagens pela malha.",
|
||||
"wizardOverviewFeature3": "Conecte-se via BLE ou TCP e depois gerencie o rádio complementar dentro do aplicativo.",
|
||||
"wizardMessagingTitle": "Mensagens e relatórios de campo",
|
||||
"wizardMessagingDescription": "Aqui, as mensagens são mais do que texto simples. O aplicativo já oferece suporte a várias cargas operacionais e fluxos de transferência.",
|
||||
"wizardMessagingFeature1": "Envie mensagens diretas, publicações em salas e tráfego de canal a partir de um único compositor.",
|
||||
"wizardMessagingFeature2": "Crie atualizações SAR e modelos SAR reutilizáveis para relatórios de campo comuns.",
|
||||
"wizardMessagingFeature3": "Transfira sessões de voz e imagens com progresso e estimativas de tempo de transmissão na interface.",
|
||||
"wizardConnectDeviceTitle": "Conectar dispositivo",
|
||||
"wizardConnectDeviceDescription": "Conecte seu rádio MeshCore, escolha um nome e aplique uma predefinição de rádio antes de continuar.",
|
||||
"wizardSetupBadge": "Configuração",
|
||||
"wizardOverviewBadge": "Visão geral",
|
||||
"wizardConnectedToDevice": "Conectado a {deviceName}",
|
||||
"wizardNoDeviceConnected": "Nenhum dispositivo conectado ainda",
|
||||
"wizardSkipForNow": "Ignorar por enquanto",
|
||||
"wizardDeviceNameLabel": "Nome do dispositivo",
|
||||
"wizardDeviceNameHelp": "Esse nome é anunciado para outros usuários do MeshCore.",
|
||||
"wizardConfigRegionLabel": "Região de configuração",
|
||||
"wizardConfigRegionHelp": "Usa a lista oficial completa de predefinições do MeshCore. O padrão é EU/UK (Narrow).",
|
||||
"wizardPresetNote1": "Certifique-se de que a predefinição selecionada corresponda às regulamentações locais de rádio.",
|
||||
"wizardPresetNote2": "A lista corresponde ao feed oficial de predefinições da ferramenta de configuração do MeshCore.",
|
||||
"wizardPresetNote3": "EU/UK (Narrow) permanece selecionado por padrão durante a configuração inicial.",
|
||||
"wizardSaving": "Salvando...",
|
||||
"wizardSaveAndContinue": "Salvar e continuar",
|
||||
"wizardEnterDeviceName": "Digite um nome para o dispositivo antes de continuar.",
|
||||
"wizardDeviceSetupSaved": "{deviceName} salvo com {presetName}.",
|
||||
"wizardNetworkTitle": "Contatos, salas e repetidores",
|
||||
"wizardNetworkDescription": "A guia Contatos organiza a rede que você descobre e as rotas aprendidas ao longo do tempo.",
|
||||
"wizardNetworkFeature1": "Revise membros da equipe, repetidores, salas, canais e anúncios pendentes em uma única lista.",
|
||||
"wizardNetworkFeature2": "Use ping inteligente, login em salas, caminhos aprendidos e ferramentas de redefinição de rota quando a conectividade ficar confusa.",
|
||||
"wizardNetworkFeature3": "Crie canais e gerencie destinos de rede sem sair do aplicativo.",
|
||||
"wizardMapOpsTitle": "Mapa, trilhas e geometria compartilhada",
|
||||
"wizardMapOpsDescription": "O mapa do aplicativo está ligado diretamente às mensagens, ao rastreamento e às sobreposições SAR, em vez de ser um visualizador separado.",
|
||||
"wizardMapOpsFeature1": "Acompanhe sua própria posição, as localizações da equipe e as trilhas de movimento no mapa.",
|
||||
"wizardMapOpsFeature2": "Abra desenhos de mensagens, visualize-os em linha e remova-os do mapa quando necessário.",
|
||||
"wizardMapOpsFeature3": "Use vistas de mapa de repetidores e sobreposições compartilhadas para entender o alcance da rede em campo.",
|
||||
"wizardToolsTitle": "Ferramentas além das mensagens",
|
||||
"wizardToolsDescription": "Há mais aqui do que as quatro guias principais. O aplicativo também inclui configuração, diagnóstico e fluxos opcionais de sensores.",
|
||||
"wizardToolsFeature1": "Abra a configuração do dispositivo para alterar ajustes de rádio, telemetria, potência TX e detalhes do equipamento complementar.",
|
||||
"wizardToolsFeature2": "Ative a guia Sensores quando quiser painéis monitorados e ações de atualização rápida.",
|
||||
"wizardToolsFeature3": "Use logs de pacotes, varredura de espectro e diagnóstico de desenvolvedor ao solucionar problemas da malha.",
|
||||
"contactInSensors": "Nos Sensores",
|
||||
"contactAddToSensors": "Adicionar aos Sensores",
|
||||
"contactSetPath": "Definir rota",
|
||||
"contactAddedToSensors": "{contactName} adicionado a Sensores",
|
||||
"contactFailedToClearRoute": "Falha ao limpar a rota: {error}",
|
||||
"contactRouteCleared": "Rota limpa",
|
||||
"contactRouteSet": "Rota definida: {route}",
|
||||
"contactFailedToSetRoute": "Falha ao definir a rota: {error}",
|
||||
"rssi": "RSSI",
|
||||
"snr": "SNR",
|
||||
"ackTimeout": "Tempo limite ACK",
|
||||
"opcode": "Opcode",
|
||||
"payload": "Carga útil",
|
||||
"hops": "Saltos",
|
||||
"hashSize": "Tamanho do hash",
|
||||
"pathBytes": "Bytes do caminho",
|
||||
"selectedPath": "Caminho selecionado",
|
||||
"estimatedTx": "Transmissão estimada",
|
||||
"senderToReceipt": "Do remetente ao recibo",
|
||||
"receivedCopies": "Cópias recebidas",
|
||||
"retryCause": "Causa da nova tentativa",
|
||||
"retryMode": "Modo de nova tentativa",
|
||||
"retryResult": "Resultado da nova tentativa",
|
||||
"lastRetry": "Última tentativa",
|
||||
"rxPackets": "Pacotes RX",
|
||||
"mesh": "Mesh",
|
||||
"rate": "Taxa",
|
||||
"window": "Janela",
|
||||
"posttxDelay": "Atraso pós-tx",
|
||||
"bandpass": "Passa-banda",
|
||||
"bandpassFilterVoice": "Filtro passa-banda de voz",
|
||||
"frequency": "Frequência",
|
||||
"australia": "Austrália",
|
||||
"australiaNarrow": "Austrália (Estreito)",
|
||||
"australiaQld": "Austrália: QLD",
|
||||
"australiaSaWa": "Austrália: SA, WA",
|
||||
"newZealand": "Nova Zelândia",
|
||||
"newZealandNarrow": "Nova Zelândia (Estreito)",
|
||||
"switzerland": "Suíça",
|
||||
"portugal433": "Portugal 433",
|
||||
"portugal868": "Portugal 868",
|
||||
"czechRepublicNarrow": "República Checa (Estreito)",
|
||||
"eu433mhzLongRange": "UE 433MHz (Longo Alcance)",
|
||||
"euukDeprecated": "UE/RU (Obsoleto)",
|
||||
"euukNarrow": "UE/RU (Estreito)",
|
||||
"usacanadaRecommended": "EUA/Canadá (Recomendado)",
|
||||
"vietnamDeprecated": "Vietname (Obsoleto)",
|
||||
"vietnamNarrow": "Vietname (Estreito)",
|
||||
"active": "Ativo",
|
||||
"addContact": "Adicionar contacto",
|
||||
"all": "Tudo",
|
||||
"autoResolve": "Resolver automaticamente",
|
||||
"clearAllLabel": "Limpar tudo",
|
||||
"clearRelays": "Limpar relays",
|
||||
"clearFilters": "Limpar filtros",
|
||||
"clearRoute": "Limpar rota",
|
||||
"clearMessages": "Limpar mensagens",
|
||||
"clearScale": "Limpar escala",
|
||||
"clearDiscoveries": "Limpar descobertas",
|
||||
"clearOnlineTraceDatabase": "Limpar base de dados de rastreio",
|
||||
"clearAllChannels": "Limpar todos os canais",
|
||||
"clearAllContacts": "Limpar todos os contactos",
|
||||
"clearChannels": "Limpar canais",
|
||||
"clearContacts": "Limpar contactos",
|
||||
"clearPathOnMaxRetry": "Limpar caminho no máx. de tentativas",
|
||||
"create": "Criar",
|
||||
"custom": "Personalizado",
|
||||
"defaultValue": "Padrão",
|
||||
"duplicate": "Duplicar",
|
||||
"editName": "Editar nome",
|
||||
"open": "Abrir",
|
||||
"paste": "Colar",
|
||||
"preview": "Pré-visualização",
|
||||
"remove": "Remover",
|
||||
"rename": "Renomear",
|
||||
"resolveAll": "Resolver tudo",
|
||||
"send": "Enviar",
|
||||
"sendAnyway": "Enviar mesmo assim",
|
||||
"share": "Partilhar",
|
||||
"shareContact": "Partilhar contacto",
|
||||
"trace": "Rastreio",
|
||||
"use": "Usar",
|
||||
"useSelectedFrequency": "Usar frequência selecionada",
|
||||
"discovery": "Descoberta",
|
||||
"discoverRepeaters": "Descobrir repetidores",
|
||||
"discoverSensors": "Descobrir sensores",
|
||||
"repeaterDiscoverySent": "Descoberta de repetidores enviada",
|
||||
"sensorDiscoverySent": "Descoberta de sensores enviada",
|
||||
"clearedPendingDiscoveries": "Descobertas pendentes limpas.",
|
||||
"autoDiscovery": "Descoberta automática",
|
||||
"enableAutomaticAdding": "Ativar adição automática",
|
||||
"autoaddRepeaters": "Adicionar repetidores automaticamente",
|
||||
"autoaddRoomServers": "Adicionar servidores de sala automaticamente",
|
||||
"autoaddSensors": "Adicionar sensores automaticamente",
|
||||
"autoaddUsers": "Adicionar utilizadores automaticamente",
|
||||
"overwriteOldestWhenFull": "Sobrescrever mais antigos quando cheio",
|
||||
"storage": "Armazenamento",
|
||||
"dangerZone": "Zona de perigo",
|
||||
"profiles": "Perfis",
|
||||
"favourites": "Favoritos",
|
||||
"sensors": "Sensores",
|
||||
"others": "Outros",
|
||||
"gpsModule": "Módulo GPS",
|
||||
"liveTraffic": "Tráfego ao vivo",
|
||||
"repeatersMap": "Mapa de repetidores",
|
||||
"spectrumScan": "Varredura de espectro",
|
||||
"blePacketLogs": "Registos de pacotes BLE",
|
||||
"onlineTraceDatabase": "Base de dados de rastreio",
|
||||
"routePathByteSize": "Tamanho do caminho em bytes",
|
||||
"messageNotifications": "Notificações de mensagens",
|
||||
"sarAlerts": "Alertas SAR",
|
||||
"discoveryNotifications": "Notificações de descoberta",
|
||||
"updateNotifications": "Notificações de atualização",
|
||||
"muteWhileAppIsOpen": "Silenciar com a app aberta",
|
||||
"disableContacts": "Desativar contactos",
|
||||
"enableSensorsTab": "Ativar separador Sensores",
|
||||
"enableProfiles": "Ativar perfis",
|
||||
"autoRouteRotation": "Rotação automática de rota",
|
||||
"nearestRepeaterFallback": "Repetidor mais próximo como reserva",
|
||||
"deleteAllStoredMessageHistory": "Apagar todo o histórico de mensagens",
|
||||
"messageFontSize": "Tamanho da fonte de mensagens",
|
||||
"rotateMapWithHeading": "Rodar mapa com direção",
|
||||
"showMapDebugInfo": "Mostrar info de depuração do mapa",
|
||||
"openMapInFullscreen": "Abrir mapa em ecrã inteiro",
|
||||
"showSarMarkersLabel": "Mostrar marcadores SAR",
|
||||
"displaySarMarkersOnTheMainMap": "Mostrar marcadores SAR no mapa principal",
|
||||
"showAllContactTrailsLabel": "Mostrar todos os rastos de contactos",
|
||||
"hideRepeatersOnMap": "Ocultar repetidores no mapa",
|
||||
"setMapScale": "Definir escala do mapa",
|
||||
"customMapScaleSaved": "Escala de mapa personalizada guardada",
|
||||
"voiceBitrate": "Taxa de bits de voz",
|
||||
"voiceCompressor": "Compressor de voz",
|
||||
"balancesQuietAndLoudSpeechLevels": "Equilibra níveis de fala suaves e altos",
|
||||
"voiceLimiter": "Limitador de voz",
|
||||
"preventsClippingPeaksBeforeEncoding": "Previne corte de picos antes da codificação",
|
||||
"micAutoGain": "Ganho automático do microfone",
|
||||
"letsTheRecorderAdjustInputLevel": "Permite ao gravador ajustar o nível de entrada",
|
||||
"echoCancellation": "Cancelamento de eco",
|
||||
"noiseSuppression": "Supressão de ruído",
|
||||
"trimSilenceInVoiceMessages": "Cortar silêncio em mensagens de voz",
|
||||
"compressor": "Compressor",
|
||||
"limiter": "Limitador",
|
||||
"autoGain": "Ganho automático",
|
||||
"echoCancel": "Eco",
|
||||
"noiseSuppress": "Ruído",
|
||||
"silenceTrim": "Silêncio",
|
||||
"maxImageSize": "Tamanho máximo de imagem",
|
||||
"imageCompression": "Compressão de imagem",
|
||||
"grayscale": "Escala de cinzentos",
|
||||
"ultraMode": "Modo ultra",
|
||||
"fastPrivateGpsUpdates": "Atualizações GPS privadas rápidas",
|
||||
"movementThreshold": "Limiar de movimento",
|
||||
"fastGpsMovementThreshold": "Limiar de movimento GPS rápido",
|
||||
"fastGpsActiveuseInterval": "Intervalo de uso ativo GPS rápido",
|
||||
"activeuseUpdateInterval": "Intervalo de atualização de uso ativo",
|
||||
"repeatNearbyTraffic": "Repetir tráfego próximo",
|
||||
"relayThroughRepeatersAcrossTheMesh": "Retransmitir através de repetidores na rede",
|
||||
"nearbyOnlyWithoutRepeaterFlooding": "Apenas próximos, sem flood de repetidores",
|
||||
"multihop": "Multi-salto",
|
||||
"createProfile": "Criar perfil",
|
||||
"renameProfile": "Renomear perfil",
|
||||
"newProfile": "Novo perfil",
|
||||
"manageProfiles": "Gerir perfis",
|
||||
"enableProfilesToStartManagingThem": "Ative os perfis para começar a geri-los.",
|
||||
"openMessage": "Abrir mensagem",
|
||||
"jumpToTheRelatedSarMessage": "Ir para a mensagem SAR relacionada",
|
||||
"removeSarMarker": "Remover marcador SAR",
|
||||
"pleaseSelectADestinationToSendSarMarker": "Selecione um destino para enviar o marcador SAR",
|
||||
"sarMarkerBroadcastToPublicChannel": "Marcador SAR transmitido no canal público",
|
||||
"sarMarkerSentToRoom": "Marcador SAR enviado para a sala",
|
||||
"loadFromGallery": "Carregar da galeria",
|
||||
"replaceImage": "Substituir imagem",
|
||||
"selectFromGallery": "Selecionar da galeria",
|
||||
"team": "Equipa",
|
||||
"found": "Encontrado",
|
||||
"staging": "Área de reunião",
|
||||
"object": "Objeto",
|
||||
"quiet": "Silencioso",
|
||||
"moderate": "Moderado",
|
||||
"busy": "Ocupado",
|
||||
"spectrumScanReturnedNoCandidateFrequencies": "A varredura de espectro não encontrou frequências candidatas",
|
||||
"searchMessages": "Pesquisar mensagens",
|
||||
"sendImageFromGallery": "Enviar imagem da galeria",
|
||||
"takePhoto": "Tirar foto",
|
||||
"dmOnly": "Apenas mensagem direta",
|
||||
"allMessages": "Todas as mensagens",
|
||||
"sendToPublicChannel": "Enviar para o canal público?",
|
||||
"selectMarkerTypeAndDestination": "Selecione tipo de marcador e destino",
|
||||
"noDestinationsAvailableLabel": "Nenhum destino disponível",
|
||||
"image": "Imagem",
|
||||
"format": "Formato",
|
||||
"dimensions": "Dimensões",
|
||||
"segments": "Segmentos",
|
||||
"transfers": "Transferências",
|
||||
"downloadedBy": "Transferido por",
|
||||
"saveDiscoverySettings": "Guardar definições de descoberta",
|
||||
"savePublicInfo": "Guardar informação pública",
|
||||
"saveRadioSettings": "Guardar definições de rádio",
|
||||
"savePath": "Guardar caminho",
|
||||
"wipeDeviceData": "Apagar dados do dispositivo",
|
||||
"wipeDevice": "Apagar dispositivo",
|
||||
"destructiveDeviceActions": "Ações destrutivas do dispositivo.",
|
||||
"chooseAPresetOrFinetuneCustomRadioSettings": "Escolha uma predefinição ou ajuste as definições de rádio.",
|
||||
"chooseTheNameAndLocationThisDeviceShares": "Escolha o nome e a localização que este dispositivo partilha.",
|
||||
"availableSpaceOnThisDevice": "Espaço disponível neste dispositivo.",
|
||||
"used": "Usado",
|
||||
"total": "Total",
|
||||
"renameValue": "Renomear valor",
|
||||
"customizeFields": "Personalizar campos",
|
||||
"livePreview": "Pré-visualização ao vivo",
|
||||
"refreshSchedule": "Agenda de atualização",
|
||||
"noResponse": "Sem resposta",
|
||||
"refreshing": "A atualizar",
|
||||
"unavailable": "Indisponível",
|
||||
"pickARelayOrNodeToWatchInSensors": "Escolha um relay ou nó para observar.",
|
||||
"publicKeyLabel": "Chave pública",
|
||||
"alreadyInContacts": "Já nos contactos",
|
||||
"connectToADeviceBeforeAddingContacts": "Conecte-se a um dispositivo antes de adicionar contactos",
|
||||
"fromContacts": "Dos contactos",
|
||||
"onlineOnly": "Apenas online",
|
||||
"inBoth": "Em ambos",
|
||||
"source": "Fonte",
|
||||
"manualRouteEdit": "Edição manual de rota",
|
||||
"observedMeshRoute": "Rota mesh observada",
|
||||
"allMessagesCleared": "Todas as mensagens limpas",
|
||||
"onlineTraceDatabaseCleared": "Base de dados de rastreio limpa",
|
||||
"packetLogsCleared": "Registos de pacotes limpos",
|
||||
"hexDataCopiedToClipboard": "Dados hex copiados para a área de transferência",
|
||||
"developerModeEnabled": "Modo de programador ativado",
|
||||
"developerModeDisabled": "Modo de programador desativado",
|
||||
"clipboardIsEmpty": "A área de transferência está vazia",
|
||||
"contactImported": "Contacto importado",
|
||||
"contactLinkCopiedToClipboard": "Link do contacto copiado para a área de transferência",
|
||||
"failedToExportContact": "Falha ao exportar contacto",
|
||||
"noLogsToExport": "Sem registos para exportar",
|
||||
"exportAsCsv": "Exportar como CSV",
|
||||
"exportAsText": "Exportar como texto",
|
||||
"receivedRfc3339": "Recebido (RFC3339)",
|
||||
"buildTime": "Hora de compilação",
|
||||
"downloadUrlNotAvailable": "URL de download não disponível",
|
||||
"cannotOpenDownloadUrl": "Não é possível abrir o URL de download",
|
||||
"updateCheckIsOnlyAvailableOnAndroid": "A verificação de atualizações só está disponível no Android",
|
||||
"youAreRunningTheLatestVersion": "Está a usar a versão mais recente",
|
||||
"updateAvailableButDownloadUrlNotFound": "Atualização disponível mas URL de download não encontrado",
|
||||
"startTictactoe": "Iniciar Tic-Tac-Toe",
|
||||
"tictactoeUnavailable": "Tic-Tac-Toe indisponível",
|
||||
"tictactoeOpponentUnknown": "Tic-Tac-Toe: oponente desconhecido",
|
||||
"tictactoeWaitingForStart": "Tic-Tac-Toe: a aguardar início",
|
||||
"acceptsShareLinks": "Aceita links partilhados",
|
||||
"supportsRawHex": "Suporta hex bruto",
|
||||
"clipboardfriendly": "Compatível com a área de transferência",
|
||||
"captured": "Capturado",
|
||||
"size": "Tamanho",
|
||||
"noCustomChannelsToClear": "Sem canais personalizados para limpar.",
|
||||
"noDeviceContactsToClear": "Sem contactos do dispositivo para limpar."
|
||||
}
|
||||
|
||||
@@ -511,5 +511,304 @@
|
||||
"rawDump": "Необработанные данные",
|
||||
"cannotRetryMissingRecipient": "Повтор невозможен: нет информации о получателе",
|
||||
"voiceUnavailable": "Голос сейчас недоступен",
|
||||
"requestingVoice": "Запрос голоса"
|
||||
"requestingVoice": "Запрос голоса",
|
||||
"device": "устройство",
|
||||
"change": "Изменить",
|
||||
"wizardOverviewDescription": "Это приложение объединяет сообщения MeshCore, полевые обновления SAR, карты и инструменты устройства в одном месте.",
|
||||
"wizardOverviewFeature1": "Отправляйте личные сообщения, сообщения комнат и сообщения каналов из основной вкладки «Сообщения».",
|
||||
"wizardOverviewFeature2": "Делитесь SAR-маркерами, рисунками на карте, голосовыми клипами и изображениями через mesh-сеть.",
|
||||
"wizardOverviewFeature3": "Подключайтесь по BLE или TCP, а затем управляйте сопутствующей радиостанцией прямо из приложения.",
|
||||
"wizardMessagingTitle": "Сообщения и полевые отчёты",
|
||||
"wizardMessagingDescription": "Здесь сообщения это больше, чем просто текст. Приложение уже поддерживает несколько видов операционных данных и сценариев передачи.",
|
||||
"wizardMessagingFeature1": "Отправляйте личные сообщения, сообщения комнат и трафик каналов из одного редактора.",
|
||||
"wizardMessagingFeature2": "Создавайте обновления SAR и повторно используемые шаблоны SAR для типовых полевых отчётов.",
|
||||
"wizardMessagingFeature3": "Передавайте голосовые сессии и изображения с индикатором прогресса и оценкой эфирного времени в интерфейсе.",
|
||||
"wizardConnectDeviceTitle": "Подключить устройство",
|
||||
"wizardConnectDeviceDescription": "Подключите свою радиостанцию MeshCore, выберите имя и примените радиопрофиль перед продолжением.",
|
||||
"wizardSetupBadge": "Настройка",
|
||||
"wizardOverviewBadge": "Обзор",
|
||||
"wizardConnectedToDevice": "Подключено к {deviceName}",
|
||||
"wizardNoDeviceConnected": "Устройство ещё не подключено",
|
||||
"wizardSkipForNow": "Пропустить пока",
|
||||
"wizardDeviceNameLabel": "Имя устройства",
|
||||
"wizardDeviceNameHelp": "Это имя транслируется другим пользователям MeshCore.",
|
||||
"wizardConfigRegionLabel": "Регион конфигурации",
|
||||
"wizardConfigRegionHelp": "Используется полный официальный список профилей MeshCore. По умолчанию выбран EU/UK (Narrow).",
|
||||
"wizardPresetNote1": "Убедитесь, что выбранный профиль соответствует местным радиоправилам.",
|
||||
"wizardPresetNote2": "Список соответствует официальному потоку профилей инструмента MeshCore config.",
|
||||
"wizardPresetNote3": "Для онбординга по умолчанию остаётся выбран EU/UK (Narrow).",
|
||||
"wizardSaving": "Сохранение...",
|
||||
"wizardSaveAndContinue": "Сохранить и продолжить",
|
||||
"wizardEnterDeviceName": "Введите имя устройства перед продолжением.",
|
||||
"wizardDeviceSetupSaved": "Сохранено: {deviceName}, профиль {presetName}.",
|
||||
"wizardNetworkTitle": "Контакты, комнаты и ретрансляторы",
|
||||
"wizardNetworkDescription": "Вкладка «Контакты» организует сеть, которую вы обнаруживаете, и маршруты, которые вы узнаёте со временем.",
|
||||
"wizardNetworkFeature1": "Просматривайте участников команды, ретрансляторы, комнаты, каналы и ожидающие объявления в одном списке.",
|
||||
"wizardNetworkFeature2": "Используйте smart ping, вход в комнаты, изученные пути и инструменты сброса маршрутов, когда связь становится нестабильной.",
|
||||
"wizardNetworkFeature3": "Создавайте каналы и управляйте сетевыми назначениями, не покидая приложение.",
|
||||
"wizardMapOpsTitle": "Карта, следы и общая геометрия",
|
||||
"wizardMapOpsDescription": "Карта приложения напрямую связана с сообщениями, отслеживанием и SAR-слоями, а не является отдельным просмотрщиком.",
|
||||
"wizardMapOpsFeature1": "Отслеживайте свою позицию, местоположение команды и траектории движения на карте.",
|
||||
"wizardMapOpsFeature2": "Открывайте рисунки из сообщений, просматривайте их прямо в интерфейсе и удаляйте с карты при необходимости.",
|
||||
"wizardMapOpsFeature3": "Используйте карты ретрансляторов и общие слои, чтобы понимать покрытие сети в поле.",
|
||||
"wizardToolsTitle": "Инструменты помимо сообщений",
|
||||
"wizardToolsDescription": "Здесь есть больше, чем четыре основные вкладки. Приложение также включает настройку, диагностику и необязательные сценарии датчиков.",
|
||||
"wizardToolsFeature1": "Откройте настройки устройства, чтобы изменить параметры радио, телеметрию, мощность TX и сведения о сопутствующем устройстве.",
|
||||
"wizardToolsFeature2": "Включите вкладку «Датчики», если нужны панели наблюдаемых датчиков и быстрые действия обновления.",
|
||||
"wizardToolsFeature3": "Используйте журналы пакетов, сканирование спектра и диагностику разработчика для устранения проблем mesh-сети.",
|
||||
"contactInSensors": "В датчиках",
|
||||
"contactAddToSensors": "Добавить в датчики",
|
||||
"contactSetPath": "Задать путь",
|
||||
"contactAddedToSensors": "{contactName} добавлен в Датчики",
|
||||
"contactFailedToClearRoute": "Не удалось очистить маршрут: {error}",
|
||||
"contactRouteCleared": "Маршрут очищен",
|
||||
"contactRouteSet": "Маршрут задан: {route}",
|
||||
"contactFailedToSetRoute": "Не удалось задать маршрут: {error}",
|
||||
"rssi": "RSSI",
|
||||
"snr": "SNR",
|
||||
"ackTimeout": "Таймаут ACK",
|
||||
"opcode": "Опкод",
|
||||
"payload": "Полезная нагрузка",
|
||||
"hops": "Хопы",
|
||||
"hashSize": "Размер хеша",
|
||||
"pathBytes": "Байты пути",
|
||||
"selectedPath": "Выбранный путь",
|
||||
"estimatedTx": "Ожидаемая передача",
|
||||
"senderToReceipt": "От отправителя до получения",
|
||||
"receivedCopies": "Полученные копии",
|
||||
"retryCause": "Причина повтора",
|
||||
"retryMode": "Режим повтора",
|
||||
"retryResult": "Результат повтора",
|
||||
"lastRetry": "Последний повтор",
|
||||
"rxPackets": "RX-пакеты",
|
||||
"mesh": "Mesh",
|
||||
"rate": "Скорость",
|
||||
"window": "Окно",
|
||||
"posttxDelay": "Задержка после передачи",
|
||||
"bandpass": "Полосовой",
|
||||
"bandpassFilterVoice": "Полосовой фильтр голоса",
|
||||
"frequency": "Частота",
|
||||
"australia": "Австралия",
|
||||
"australiaNarrow": "Австралия (Узкий)",
|
||||
"australiaQld": "Австралия: QLD",
|
||||
"australiaSaWa": "Австралия: SA, WA",
|
||||
"newZealand": "Новая Зеландия",
|
||||
"newZealandNarrow": "Новая Зеландия (Узкий)",
|
||||
"switzerland": "Швейцария",
|
||||
"portugal433": "Португалия 433",
|
||||
"portugal868": "Португалия 868",
|
||||
"czechRepublicNarrow": "Чехия (Узкий)",
|
||||
"eu433mhzLongRange": "ЕС 433МГц (Дальний)",
|
||||
"euukDeprecated": "ЕС/Великобритания (Устарело)",
|
||||
"euukNarrow": "ЕС/Великобритания (Узкий)",
|
||||
"usacanadaRecommended": "США/Канада (Рекомендуется)",
|
||||
"vietnamDeprecated": "Вьетнам (Устарело)",
|
||||
"vietnamNarrow": "Вьетнам (Узкий)",
|
||||
"active": "Активно",
|
||||
"addContact": "Добавить контакт",
|
||||
"all": "Все",
|
||||
"autoResolve": "Автоматическое разрешение",
|
||||
"clearAllLabel": "Очистить все",
|
||||
"clearRelays": "Очистить реле",
|
||||
"clearFilters": "Очистить фильтры",
|
||||
"clearRoute": "Очистить маршрут",
|
||||
"clearMessages": "Очистить сообщения",
|
||||
"clearScale": "Очистить масштаб",
|
||||
"clearDiscoveries": "Очистить обнаружения",
|
||||
"clearOnlineTraceDatabase": "Очистить базу трассировок",
|
||||
"clearAllChannels": "Очистить все каналы",
|
||||
"clearAllContacts": "Очистить все контакты",
|
||||
"clearChannels": "Очистить каналы",
|
||||
"clearContacts": "Очистить контакты",
|
||||
"clearPathOnMaxRetry": "Очистить путь при макс. повторе",
|
||||
"create": "Создать",
|
||||
"custom": "Пользовательский",
|
||||
"defaultValue": "По умолчанию",
|
||||
"duplicate": "Дублировать",
|
||||
"editName": "Редактировать имя",
|
||||
"open": "Открыть",
|
||||
"paste": "Вставить",
|
||||
"preview": "Предпросмотр",
|
||||
"remove": "Удалить",
|
||||
"rename": "Переименовать",
|
||||
"resolveAll": "Разрешить все",
|
||||
"send": "Отправить",
|
||||
"sendAnyway": "Отправить все равно",
|
||||
"share": "Поделиться",
|
||||
"shareContact": "Поделиться контактом",
|
||||
"trace": "Трассировка",
|
||||
"use": "Использовать",
|
||||
"useSelectedFrequency": "Использовать выбранную частоту",
|
||||
"discovery": "Обнаружение",
|
||||
"discoverRepeaters": "Обнаружить повторители",
|
||||
"discoverSensors": "Обнаружить датчики",
|
||||
"repeaterDiscoverySent": "Обнаружение повторителей отправлено",
|
||||
"sensorDiscoverySent": "Обнаружение датчиков отправлено",
|
||||
"clearedPendingDiscoveries": "Ожидающие обнаружения очищены.",
|
||||
"autoDiscovery": "Автоматическое обнаружение",
|
||||
"enableAutomaticAdding": "Включить автоматическое добавление",
|
||||
"autoaddRepeaters": "Автодобавление повторителей",
|
||||
"autoaddRoomServers": "Автодобавление серверов комнат",
|
||||
"autoaddSensors": "Автодобавление датчиков",
|
||||
"autoaddUsers": "Автодобавление пользователей",
|
||||
"overwriteOldestWhenFull": "Перезаписать старейшие при заполнении",
|
||||
"storage": "Хранилище",
|
||||
"dangerZone": "Опасная зона",
|
||||
"profiles": "Профили",
|
||||
"favourites": "Избранное",
|
||||
"sensors": "Датчики",
|
||||
"others": "Другие",
|
||||
"gpsModule": "Модуль GPS",
|
||||
"liveTraffic": "Живой трафик",
|
||||
"repeatersMap": "Карта повторителей",
|
||||
"spectrumScan": "Сканирование спектра",
|
||||
"blePacketLogs": "Журналы BLE-пакетов",
|
||||
"onlineTraceDatabase": "База трассировок",
|
||||
"routePathByteSize": "Размер пути в байтах",
|
||||
"messageNotifications": "Уведомления о сообщениях",
|
||||
"sarAlerts": "SAR-оповещения",
|
||||
"discoveryNotifications": "Уведомления об обнаружении",
|
||||
"updateNotifications": "Уведомления об обновлениях",
|
||||
"muteWhileAppIsOpen": "Без звука при открытом приложении",
|
||||
"disableContacts": "Отключить контакты",
|
||||
"enableSensorsTab": "Включить вкладку Датчики",
|
||||
"enableProfiles": "Включить профили",
|
||||
"autoRouteRotation": "Автоматическая ротация маршрута",
|
||||
"nearestRepeaterFallback": "Ближайший повторитель как резерв",
|
||||
"deleteAllStoredMessageHistory": "Удалить всю историю сообщений",
|
||||
"messageFontSize": "Размер шрифта сообщений",
|
||||
"rotateMapWithHeading": "Вращать карту по направлению",
|
||||
"showMapDebugInfo": "Показать отладочную информацию карты",
|
||||
"openMapInFullscreen": "Открыть карту на весь экран",
|
||||
"showSarMarkersLabel": "Показать SAR-маркеры",
|
||||
"displaySarMarkersOnTheMainMap": "Отображать SAR-маркеры на основной карте",
|
||||
"showAllContactTrailsLabel": "Показать все следы контактов",
|
||||
"hideRepeatersOnMap": "Скрыть повторители на карте",
|
||||
"setMapScale": "Установить масштаб карты",
|
||||
"customMapScaleSaved": "Пользовательский масштаб карты сохранён",
|
||||
"voiceBitrate": "Битрейт голоса",
|
||||
"voiceCompressor": "Компрессор голоса",
|
||||
"balancesQuietAndLoudSpeechLevels": "Выравнивает тихую и громкую речь",
|
||||
"voiceLimiter": "Лимитер голоса",
|
||||
"preventsClippingPeaksBeforeEncoding": "Предотвращает обрезку пиков перед кодированием",
|
||||
"micAutoGain": "Автоусиление микрофона",
|
||||
"letsTheRecorderAdjustInputLevel": "Позволяет рекордеру регулировать уровень входа",
|
||||
"echoCancellation": "Подавление эха",
|
||||
"noiseSuppression": "Подавление шума",
|
||||
"trimSilenceInVoiceMessages": "Обрезать тишину в голосовых сообщениях",
|
||||
"compressor": "Компрессор",
|
||||
"limiter": "Лимитер",
|
||||
"autoGain": "Автоусиление",
|
||||
"echoCancel": "Эхо",
|
||||
"noiseSuppress": "Шум",
|
||||
"silenceTrim": "Тишина",
|
||||
"maxImageSize": "Максимальный размер изображения",
|
||||
"imageCompression": "Сжатие изображения",
|
||||
"grayscale": "Оттенки серого",
|
||||
"ultraMode": "Режим ультра",
|
||||
"fastPrivateGpsUpdates": "Быстрые приватные GPS-обновления",
|
||||
"movementThreshold": "Порог движения",
|
||||
"fastGpsMovementThreshold": "Порог движения быстрого GPS",
|
||||
"fastGpsActiveuseInterval": "Интервал активного использования быстрого GPS",
|
||||
"activeuseUpdateInterval": "Интервал обновления при активном использовании",
|
||||
"repeatNearbyTraffic": "Повторять ближний трафик",
|
||||
"relayThroughRepeatersAcrossTheMesh": "Ретранслировать через повторители по сети",
|
||||
"nearbyOnlyWithoutRepeaterFlooding": "Только рядом, без flood повторителей",
|
||||
"multihop": "Многохоповый",
|
||||
"createProfile": "Создать профиль",
|
||||
"renameProfile": "Переименовать профиль",
|
||||
"newProfile": "Новый профиль",
|
||||
"manageProfiles": "Управление профилями",
|
||||
"enableProfilesToStartManagingThem": "Включите профили, чтобы начать управлять ими.",
|
||||
"openMessage": "Открыть сообщение",
|
||||
"jumpToTheRelatedSarMessage": "Перейти к связанному SAR-сообщению",
|
||||
"removeSarMarker": "Удалить SAR-маркер",
|
||||
"pleaseSelectADestinationToSendSarMarker": "Выберите назначение для отправки SAR-маркера",
|
||||
"sarMarkerBroadcastToPublicChannel": "SAR-маркер отправлен в публичный канал",
|
||||
"sarMarkerSentToRoom": "SAR-маркер отправлен в комнату",
|
||||
"loadFromGallery": "Загрузить из галереи",
|
||||
"replaceImage": "Заменить изображение",
|
||||
"selectFromGallery": "Выбрать из галереи",
|
||||
"team": "Команда",
|
||||
"found": "Найдено",
|
||||
"staging": "Место сбора",
|
||||
"object": "Объект",
|
||||
"quiet": "Тихо",
|
||||
"moderate": "Умеренно",
|
||||
"busy": "Занято",
|
||||
"spectrumScanReturnedNoCandidateFrequencies": "Сканирование спектра не нашло частот-кандидатов",
|
||||
"searchMessages": "Поиск сообщений",
|
||||
"sendImageFromGallery": "Отправить изображение из галереи",
|
||||
"takePhoto": "Сделать фото",
|
||||
"dmOnly": "Только личные сообщения",
|
||||
"allMessages": "Все сообщения",
|
||||
"sendToPublicChannel": "Отправить в публичный канал?",
|
||||
"selectMarkerTypeAndDestination": "Выберите тип маркера и назначение",
|
||||
"noDestinationsAvailableLabel": "Нет доступных назначений",
|
||||
"image": "Изображение",
|
||||
"format": "Формат",
|
||||
"dimensions": "Размеры",
|
||||
"segments": "Сегменты",
|
||||
"transfers": "Передачи",
|
||||
"downloadedBy": "Загружено",
|
||||
"saveDiscoverySettings": "Сохранить настройки обнаружения",
|
||||
"savePublicInfo": "Сохранить публичную информацию",
|
||||
"saveRadioSettings": "Сохранить настройки радио",
|
||||
"savePath": "Сохранить путь",
|
||||
"wipeDeviceData": "Стереть данные устройства",
|
||||
"wipeDevice": "Стереть устройство",
|
||||
"destructiveDeviceActions": "Деструктивные действия устройства.",
|
||||
"chooseAPresetOrFinetuneCustomRadioSettings": "Выберите пресет или настройте радио вручную.",
|
||||
"chooseTheNameAndLocationThisDeviceShares": "Выберите имя и местоположение, которыми делится устройство.",
|
||||
"availableSpaceOnThisDevice": "Доступное место на этом устройстве.",
|
||||
"used": "Использовано",
|
||||
"total": "Всего",
|
||||
"renameValue": "Переименовать значение",
|
||||
"customizeFields": "Настроить поля",
|
||||
"livePreview": "Предпросмотр в реальном времени",
|
||||
"refreshSchedule": "Расписание обновления",
|
||||
"noResponse": "Нет ответа",
|
||||
"refreshing": "Обновление",
|
||||
"unavailable": "Недоступно",
|
||||
"pickARelayOrNodeToWatchInSensors": "Выберите реле или узел для наблюдения.",
|
||||
"publicKeyLabel": "Публичный ключ",
|
||||
"alreadyInContacts": "Уже в контактах",
|
||||
"connectToADeviceBeforeAddingContacts": "Подключитесь к устройству перед добавлением контактов",
|
||||
"fromContacts": "Из контактов",
|
||||
"onlineOnly": "Только онлайн",
|
||||
"inBoth": "В обоих",
|
||||
"source": "Источник",
|
||||
"manualRouteEdit": "Ручное редактирование маршрута",
|
||||
"observedMeshRoute": "Наблюдаемый mesh-маршрут",
|
||||
"allMessagesCleared": "Все сообщения очищены",
|
||||
"onlineTraceDatabaseCleared": "База трассировок очищена",
|
||||
"packetLogsCleared": "Журналы пакетов очищены",
|
||||
"hexDataCopiedToClipboard": "Hex-данные скопированы в буфер",
|
||||
"developerModeEnabled": "Режим разработчика включён",
|
||||
"developerModeDisabled": "Режим разработчика отключён",
|
||||
"clipboardIsEmpty": "Буфер обмена пуст",
|
||||
"contactImported": "Контакт импортирован",
|
||||
"contactLinkCopiedToClipboard": "Ссылка на контакт скопирована в буфер",
|
||||
"failedToExportContact": "Не удалось экспортировать контакт",
|
||||
"noLogsToExport": "Нет журналов для экспорта",
|
||||
"exportAsCsv": "Экспортировать как CSV",
|
||||
"exportAsText": "Экспортировать как текст",
|
||||
"receivedRfc3339": "Получено (RFC3339)",
|
||||
"buildTime": "Время сборки",
|
||||
"downloadUrlNotAvailable": "URL загрузки недоступен",
|
||||
"cannotOpenDownloadUrl": "Не удаётся открыть URL загрузки",
|
||||
"updateCheckIsOnlyAvailableOnAndroid": "Проверка обновлений доступна только на Android",
|
||||
"youAreRunningTheLatestVersion": "Вы используете последнюю версию",
|
||||
"updateAvailableButDownloadUrlNotFound": "Обновление доступно, но URL загрузки не найден",
|
||||
"startTictactoe": "Начать Tic-Tac-Toe",
|
||||
"tictactoeUnavailable": "Tic-Tac-Toe недоступно",
|
||||
"tictactoeOpponentUnknown": "Tic-Tac-Toe: противник неизвестен",
|
||||
"tictactoeWaitingForStart": "Tic-Tac-Toe: ожидание начала",
|
||||
"acceptsShareLinks": "Принимает общие ссылки",
|
||||
"supportsRawHex": "Поддерживает raw hex",
|
||||
"clipboardfriendly": "Удобно для буфера",
|
||||
"captured": "Захвачено",
|
||||
"size": "Размер",
|
||||
"noCustomChannelsToClear": "Нет пользовательских каналов для очистки.",
|
||||
"noDeviceContactsToClear": "Нет контактов устройства для очистки."
|
||||
}
|
||||
|
||||
@@ -511,5 +511,304 @@
|
||||
"rawDump": "Surovi izpis",
|
||||
"cannotRetryMissingRecipient": "Ponovnega pošiljanja ni mogoče: informacije o prejemniku manjkajo",
|
||||
"voiceUnavailable": "Glas trenutno ni na voljo",
|
||||
"requestingVoice": "Zahteva za glasom"
|
||||
"requestingVoice": "Zahteva za glasom",
|
||||
"device": "naprava",
|
||||
"change": "Spremeni",
|
||||
"wizardOverviewDescription": "Ta aplikacija združuje sporočanje MeshCore, terenske SAR posodobitve, zemljevide in orodja za napravo na enem mestu.",
|
||||
"wizardOverviewFeature1": "Pošiljajte neposredna sporočila, objave v sobah in sporočila na kanalih iz glavnega zavihka Sporočila.",
|
||||
"wizardOverviewFeature2": "Delite SAR označevalce, risbe zemljevida, glasovne posnetke in slike prek mesh omrežja.",
|
||||
"wizardOverviewFeature3": "Povežite se prek BLE ali TCP in nato upravljajte spremljevalni radio kar iz aplikacije.",
|
||||
"wizardMessagingTitle": "Sporočanje in terenska poročila",
|
||||
"wizardMessagingDescription": "Sporočila tukaj niso le navadno besedilo. Aplikacija že podpira več operativnih vrst vsebin in prenosnih tokov.",
|
||||
"wizardMessagingFeature1": "Pošiljajte neposredna sporočila, objave v sobah in promet na kanalih iz enega urejevalnika.",
|
||||
"wizardMessagingFeature2": "Ustvarjajte SAR posodobitve in večkrat uporabne SAR predloge za pogosta terenska poročila.",
|
||||
"wizardMessagingFeature3": "Prenašajte glasovne seje in slike, vmesnik pa prikazuje napredek in oceno časa prenosa.",
|
||||
"wizardConnectDeviceTitle": "Poveži napravo",
|
||||
"wizardConnectDeviceDescription": "Povežite svoj MeshCore radio, izberite ime in uporabite radijski prednastavljeni profil, preden nadaljujete.",
|
||||
"wizardSetupBadge": "Nastavitev",
|
||||
"wizardOverviewBadge": "Pregled",
|
||||
"wizardConnectedToDevice": "Povezano z: {deviceName}",
|
||||
"wizardNoDeviceConnected": "Nobena naprava še ni povezana",
|
||||
"wizardSkipForNow": "Za zdaj preskoči",
|
||||
"wizardDeviceNameLabel": "Ime naprave",
|
||||
"wizardDeviceNameHelp": "To ime se oglašuje drugim uporabnikom MeshCore.",
|
||||
"wizardConfigRegionLabel": "Konfiguracijska regija",
|
||||
"wizardConfigRegionHelp": "Uporabi celoten uradni seznam prednastavitev MeshCore. Privzeto je izbran EU/UK (Narrow).",
|
||||
"wizardPresetNote1": "Prepričajte se, da izbrana prednastavitev ustreza lokalnim radijskim predpisom.",
|
||||
"wizardPresetNote2": "Seznam ustreza uradnemu viru prednastavitev orodja MeshCore config.",
|
||||
"wizardPresetNote3": "Za uvajanje ostane privzeto izbran EU/UK (Narrow).",
|
||||
"wizardSaving": "Shranjujem...",
|
||||
"wizardSaveAndContinue": "Shrani in nadaljuj",
|
||||
"wizardEnterDeviceName": "Pred nadaljevanjem vnesite ime naprave.",
|
||||
"wizardDeviceSetupSaved": "Shranjeno {deviceName} z nastavitvijo {presetName}.",
|
||||
"wizardNetworkTitle": "Stiki, sobe in repetitorji",
|
||||
"wizardNetworkDescription": "Zavihek Stiki organizira omrežje, ki ga odkrijete, in poti, ki se jih aplikacija nauči skozi čas.",
|
||||
"wizardNetworkFeature1": "Na enem seznamu preglejte člane ekipe, repetitorje, sobe, kanale in čakajoče oglase.",
|
||||
"wizardNetworkFeature2": "Uporabite smart ping, prijavo v sobe, naučene poti in ponastavitev poti, ko povezljivost postane neurejena.",
|
||||
"wizardNetworkFeature3": "Ustvarjajte kanale in upravljajte omrežne cilje, ne da bi zapustili aplikacijo.",
|
||||
"wizardMapOpsTitle": "Zemljevid, sledi in deljena geometrija",
|
||||
"wizardMapOpsDescription": "Zemljevid v aplikaciji je neposredno povezan s sporočanjem, sledenjem in SAR prekrivanji, namesto da bi bil le ločen pregledovalnik.",
|
||||
"wizardMapOpsFeature1": "Spremljajte svoj položaj, lokacije sotekmovalcev in gibanje sledi na zemljevidu.",
|
||||
"wizardMapOpsFeature2": "Odprite risbe iz sporočil, jih predoglejte v vrstici in jih po potrebi odstranite z zemljevida.",
|
||||
"wizardMapOpsFeature3": "Uporabite poglede repetitorjev in deljena prekrivanja za razumevanje dosega omrežja na terenu.",
|
||||
"wizardToolsTitle": "Orodja onkraj sporočanja",
|
||||
"wizardToolsDescription": "Tu je več kot le štirje glavni zavihki. Aplikacija vključuje tudi konfiguracijo, diagnostiko in neobvezne senzorske poteke.",
|
||||
"wizardToolsFeature1": "Odprite nastavitve naprave za spremembo radijskih nastavitev, telemetrije, oddajne moči in podatkov spremljevalne naprave.",
|
||||
"wizardToolsFeature2": "Omogočite zavihek Senzorji, kadar želite nadzorne plošče in hitra osveževanja spremljanih senzorjev.",
|
||||
"wizardToolsFeature3": "Uporabite dnevnik paketov, pregled spektra in razvojno diagnostiko pri odpravljanju težav v mesh omrežju.",
|
||||
"contactInSensors": "V senzorjih",
|
||||
"contactAddToSensors": "Dodaj v senzorje",
|
||||
"contactSetPath": "Nastavi pot",
|
||||
"contactAddedToSensors": "{contactName} dodan v senzorje",
|
||||
"contactFailedToClearRoute": "Brisanje poti ni uspelo: {error}",
|
||||
"contactRouteCleared": "Pot izbrisana",
|
||||
"contactRouteSet": "Pot nastavljena: {route}",
|
||||
"contactFailedToSetRoute": "Nastavitev poti ni uspela: {error}",
|
||||
"rssi": "RSSI",
|
||||
"snr": "SNR",
|
||||
"ackTimeout": "Časovna omejitev ACK",
|
||||
"opcode": "Opkoda",
|
||||
"payload": "Vsebina",
|
||||
"hops": "Skoki",
|
||||
"hashSize": "Velikost zgoščevalne vrednosti",
|
||||
"pathBytes": "Bajti poti",
|
||||
"selectedPath": "Izbrana pot",
|
||||
"estimatedTx": "Ocenjeni čas oddaje",
|
||||
"senderToReceipt": "Od pošiljatelja do prejema",
|
||||
"receivedCopies": "Prejete kopije",
|
||||
"retryCause": "Razlog ponovitve",
|
||||
"retryMode": "Način ponovitve",
|
||||
"retryResult": "Rezultat ponovitve",
|
||||
"lastRetry": "Zadnja ponovitev",
|
||||
"rxPackets": "RX paketi",
|
||||
"mesh": "Mesh",
|
||||
"rate": "Hitrost",
|
||||
"window": "Okno",
|
||||
"posttxDelay": "Zakasnitev po oddaji",
|
||||
"bandpass": "Pasovno sito",
|
||||
"bandpassFilterVoice": "Pasovno sito za glas",
|
||||
"frequency": "Frekvenca",
|
||||
"australia": "Avstralija",
|
||||
"australiaNarrow": "Avstralija (ozko)",
|
||||
"australiaQld": "Avstralija: QLD",
|
||||
"australiaSaWa": "Avstralija: SA, WA",
|
||||
"newZealand": "Nova Zelandija",
|
||||
"newZealandNarrow": "Nova Zelandija (ozko)",
|
||||
"switzerland": "Švica",
|
||||
"portugal433": "Portugalska 433",
|
||||
"portugal868": "Portugalska 868",
|
||||
"czechRepublicNarrow": "Češka (ozko)",
|
||||
"eu433mhzLongRange": "EU 433MHz (dolg doseg)",
|
||||
"euukDeprecated": "EU/UK (zastarelo)",
|
||||
"euukNarrow": "EU/UK (ozko)",
|
||||
"usacanadaRecommended": "ZDA/Kanada (priporočeno)",
|
||||
"vietnamDeprecated": "Vietnam (zastarelo)",
|
||||
"vietnamNarrow": "Vietnam (ozko)",
|
||||
"active": "Aktivno",
|
||||
"addContact": "Dodaj stik",
|
||||
"all": "Vse",
|
||||
"autoResolve": "Samodejno razreši",
|
||||
"clearAllLabel": "Počisti vse",
|
||||
"clearRelays": "Počisti posrednike",
|
||||
"clearFilters": "Počisti filtre",
|
||||
"clearRoute": "Počisti pot",
|
||||
"clearMessages": "Počisti sporočila",
|
||||
"clearScale": "Počisti merilo",
|
||||
"clearDiscoveries": "Počisti odkritja",
|
||||
"clearOnlineTraceDatabase": "Počisti bazo sledi",
|
||||
"clearAllChannels": "Počisti vse kanale",
|
||||
"clearAllContacts": "Počisti vse stike",
|
||||
"clearChannels": "Počisti kanale",
|
||||
"clearContacts": "Počisti stike",
|
||||
"clearPathOnMaxRetry": "Počisti pot ob maks. ponovitvi",
|
||||
"create": "Ustvari",
|
||||
"custom": "Po meri",
|
||||
"defaultValue": "Privzeto",
|
||||
"duplicate": "Podvoji",
|
||||
"editName": "Uredi ime",
|
||||
"open": "Odpri",
|
||||
"paste": "Prilepi",
|
||||
"preview": "Predogled",
|
||||
"remove": "Odstrani",
|
||||
"rename": "Preimenuj",
|
||||
"resolveAll": "Razreši vse",
|
||||
"send": "Pošlji",
|
||||
"sendAnyway": "Vseeno pošlji",
|
||||
"share": "Deli",
|
||||
"shareContact": "Deli stik",
|
||||
"trace": "Sledenje",
|
||||
"use": "Uporabi",
|
||||
"useSelectedFrequency": "Uporabi izbrano frekvenco",
|
||||
"discovery": "Odkrivanje",
|
||||
"discoverRepeaters": "Odkrij posrednike",
|
||||
"discoverSensors": "Odkrij senzorje",
|
||||
"repeaterDiscoverySent": "Odkrivanje posrednikov poslano",
|
||||
"sensorDiscoverySent": "Odkrivanje senzorjev poslano",
|
||||
"clearedPendingDiscoveries": "Čakalna odkritja počiščena.",
|
||||
"autoDiscovery": "Samodejno odkrivanje",
|
||||
"enableAutomaticAdding": "Omogoči samodejno dodajanje",
|
||||
"autoaddRepeaters": "Samodejno dodaj posrednike",
|
||||
"autoaddRoomServers": "Samodejno dodaj strežnike sob",
|
||||
"autoaddSensors": "Samodejno dodaj senzorje",
|
||||
"autoaddUsers": "Samodejno dodaj uporabnike",
|
||||
"overwriteOldestWhenFull": "Prepiši najstarejše ob polnem",
|
||||
"storage": "Shramba",
|
||||
"dangerZone": "Nevarno območje",
|
||||
"profiles": "Profili",
|
||||
"favourites": "Priljubljeni",
|
||||
"sensors": "Senzorji",
|
||||
"others": "Ostali",
|
||||
"gpsModule": "GPS modul",
|
||||
"liveTraffic": "Promet v živo",
|
||||
"repeatersMap": "Zemljevid posrednikov",
|
||||
"spectrumScan": "Spektralno skeniranje",
|
||||
"blePacketLogs": "Dnevniki BLE paketov",
|
||||
"onlineTraceDatabase": "Baza sledi",
|
||||
"routePathByteSize": "Velikost poti v bajtih",
|
||||
"messageNotifications": "Obvestila o sporočilih",
|
||||
"sarAlerts": "SAR opozorila",
|
||||
"discoveryNotifications": "Obvestila o odkritjih",
|
||||
"updateNotifications": "Obvestila o posodobitvah",
|
||||
"muteWhileAppIsOpen": "Utišaj ko je aplikacija odprta",
|
||||
"disableContacts": "Onemogoči stike",
|
||||
"enableSensorsTab": "Omogoči zavihek Senzorji",
|
||||
"enableProfiles": "Omogoči profile",
|
||||
"autoRouteRotation": "Samodejno kroženje poti",
|
||||
"nearestRepeaterFallback": "Najbližji posrednik kot rezerva",
|
||||
"deleteAllStoredMessageHistory": "Izbriši vso shranjeno zgodovino sporočil",
|
||||
"messageFontSize": "Velikost pisave sporočil",
|
||||
"rotateMapWithHeading": "Vrti zemljevid s smerjo",
|
||||
"showMapDebugInfo": "Prikaži razhroščevalne info zemljevida",
|
||||
"openMapInFullscreen": "Odpri zemljevid na cel zaslon",
|
||||
"showSarMarkersLabel": "Prikaži SAR oznake",
|
||||
"displaySarMarkersOnTheMainMap": "Prikaži SAR oznake na glavnem zemljevidu",
|
||||
"showAllContactTrailsLabel": "Prikaži vse sledi stikov",
|
||||
"hideRepeatersOnMap": "Skrij posrednike na zemljevidu",
|
||||
"setMapScale": "Nastavi merilo zemljevida",
|
||||
"customMapScaleSaved": "Merilo zemljevida shranjeno",
|
||||
"voiceBitrate": "Bitna hitrost glasu",
|
||||
"voiceCompressor": "Kompresor glasu",
|
||||
"balancesQuietAndLoudSpeechLevels": "Izravnava tihe in glasne govorne ravni",
|
||||
"voiceLimiter": "Omejevalnik glasu",
|
||||
"preventsClippingPeaksBeforeEncoding": "Preprečuje rezanje vrhov pred kodiranjem",
|
||||
"micAutoGain": "Samodejna ojačitev mikrofona",
|
||||
"letsTheRecorderAdjustInputLevel": "Omogoči snemalniku prilagoditev vhodne ravni",
|
||||
"echoCancellation": "Odstranjevanje odmeva",
|
||||
"noiseSuppression": "Dušenje šuma",
|
||||
"trimSilenceInVoiceMessages": "Obreži tišino v glasovnih sporočilih",
|
||||
"compressor": "Kompresor",
|
||||
"limiter": "Omejevalnik",
|
||||
"autoGain": "Samodejna ojačitev",
|
||||
"echoCancel": "Odmev",
|
||||
"noiseSuppress": "Šum",
|
||||
"silenceTrim": "Tišina",
|
||||
"maxImageSize": "Največja velikost slike",
|
||||
"imageCompression": "Stiskanje slik",
|
||||
"grayscale": "Sivine",
|
||||
"ultraMode": "Ultra način",
|
||||
"fastPrivateGpsUpdates": "Hitri zasebni GPS podatki",
|
||||
"movementThreshold": "Prag gibanja",
|
||||
"fastGpsMovementThreshold": "Prag gibanja za hitri GPS",
|
||||
"fastGpsActiveuseInterval": "Interval uporabe hitrega GPS-a",
|
||||
"activeuseUpdateInterval": "Interval posodobitve pri aktivni uporabi",
|
||||
"repeatNearbyTraffic": "Ponavljaj bližnji promet",
|
||||
"relayThroughRepeatersAcrossTheMesh": "Posreduj skozi posrednike po mreži",
|
||||
"nearbyOnlyWithoutRepeaterFlooding": "Samo bližnji, brez poplavljanja posrednikov",
|
||||
"multihop": "Večskočno",
|
||||
"createProfile": "Ustvari profil",
|
||||
"renameProfile": "Preimenuj profil",
|
||||
"newProfile": "Nov profil",
|
||||
"manageProfiles": "Upravljaj profile",
|
||||
"enableProfilesToStartManagingThem": "Omogočite profile, da jih začnete upravljati.",
|
||||
"openMessage": "Odpri sporočilo",
|
||||
"jumpToTheRelatedSarMessage": "Skoči na povezano SAR sporočilo",
|
||||
"removeSarMarker": "Odstrani SAR oznako",
|
||||
"pleaseSelectADestinationToSendSarMarker": "Izberite cilj za pošiljanje SAR oznake",
|
||||
"sarMarkerBroadcastToPublicChannel": "SAR oznaka poslana na javni kanal",
|
||||
"sarMarkerSentToRoom": "SAR oznaka poslana v sobo",
|
||||
"loadFromGallery": "Naloži iz galerije",
|
||||
"replaceImage": "Zamenjaj sliko",
|
||||
"selectFromGallery": "Izberi iz galerije",
|
||||
"team": "Ekipa",
|
||||
"found": "Najdeno",
|
||||
"staging": "Zbirno mesto",
|
||||
"object": "Predmet",
|
||||
"quiet": "Tiho",
|
||||
"moderate": "Zmerno",
|
||||
"busy": "Zasedeno",
|
||||
"spectrumScanReturnedNoCandidateFrequencies": "Spektralno skeniranje ni našlo primernih frekvenc",
|
||||
"searchMessages": "Iskanje sporočil",
|
||||
"sendImageFromGallery": "Pošlji sliko iz galerije",
|
||||
"takePhoto": "Posnemi fotografijo",
|
||||
"dmOnly": "Samo neposredno",
|
||||
"allMessages": "Vsa sporočila",
|
||||
"sendToPublicChannel": "Pošlji na javni kanal?",
|
||||
"selectMarkerTypeAndDestination": "Izberite vrsto oznake in cilj",
|
||||
"noDestinationsAvailableLabel": "Ni razpoložljivih ciljev",
|
||||
"image": "Slika",
|
||||
"format": "Format",
|
||||
"dimensions": "Dimenzije",
|
||||
"segments": "Segmenti",
|
||||
"transfers": "Prenosi",
|
||||
"downloadedBy": "Preneseno od",
|
||||
"saveDiscoverySettings": "Shrani nastavitve odkrivanja",
|
||||
"savePublicInfo": "Shrani javne podatke",
|
||||
"saveRadioSettings": "Shrani radijske nastavitve",
|
||||
"savePath": "Shrani pot",
|
||||
"wipeDeviceData": "Izbriši podatke naprave",
|
||||
"wipeDevice": "Izbriši napravo",
|
||||
"destructiveDeviceActions": "Destruktivna dejanja na napravi.",
|
||||
"chooseAPresetOrFinetuneCustomRadioSettings": "Izberite prednastavitev ali natančno prilagodite radijske nastavitve.",
|
||||
"chooseTheNameAndLocationThisDeviceShares": "Izberite ime in lokacijo, ki ju naprava deli.",
|
||||
"availableSpaceOnThisDevice": "Razpoložljiv prostor na tej napravi.",
|
||||
"used": "Uporabljeno",
|
||||
"total": "Skupaj",
|
||||
"renameValue": "Preimenuj vrednost",
|
||||
"customizeFields": "Prilagodi polja",
|
||||
"livePreview": "Predogled v živo",
|
||||
"refreshSchedule": "Interval osvežitve",
|
||||
"noResponse": "Ni odgovora",
|
||||
"refreshing": "Osvežujem",
|
||||
"unavailable": "Nedostopno",
|
||||
"pickARelayOrNodeToWatchInSensors": "Izberite posrednika ali vozlišče za opazovanje.",
|
||||
"publicKeyLabel": "Javni ključ",
|
||||
"alreadyInContacts": "Že med stiki",
|
||||
"connectToADeviceBeforeAddingContacts": "Povežite se z napravo pred dodajanjem stikov",
|
||||
"fromContacts": "Iz stikov",
|
||||
"onlineOnly": "Samo povezani",
|
||||
"inBoth": "V obeh",
|
||||
"source": "Vir",
|
||||
"manualRouteEdit": "Ročno urejanje poti",
|
||||
"observedMeshRoute": "Opazovana mrežna pot",
|
||||
"allMessagesCleared": "Vsa sporočila počiščena",
|
||||
"onlineTraceDatabaseCleared": "Baza sledi počiščena",
|
||||
"packetLogsCleared": "Dnevniki paketov počiščeni",
|
||||
"hexDataCopiedToClipboard": "Hex podatki kopirani v odložišče",
|
||||
"developerModeEnabled": "Razvojni način omogočen",
|
||||
"developerModeDisabled": "Razvojni način onemogočen",
|
||||
"clipboardIsEmpty": "Odložišče je prazno",
|
||||
"contactImported": "Stik uvožen",
|
||||
"contactLinkCopiedToClipboard": "Povezava na stik kopirana v odložišče",
|
||||
"failedToExportContact": "Izvoz stika ni uspel",
|
||||
"noLogsToExport": "Ni dnevnikov za izvoz",
|
||||
"exportAsCsv": "Izvozi kot CSV",
|
||||
"exportAsText": "Izvozi kot besedilo",
|
||||
"receivedRfc3339": "Prejeto (RFC3339)",
|
||||
"buildTime": "Čas gradnje",
|
||||
"downloadUrlNotAvailable": "URL za prenos ni na voljo",
|
||||
"cannotOpenDownloadUrl": "URL za prenos ni mogoče odpreti",
|
||||
"updateCheckIsOnlyAvailableOnAndroid": "Preverjanje posodobitev je na voljo le na Androidu",
|
||||
"youAreRunningTheLatestVersion": "Uporabljate najnovejšo različico",
|
||||
"updateAvailableButDownloadUrlNotFound": "Posodobitev na voljo, a URL za prenos ni najden",
|
||||
"startTictactoe": "Začni Tic-Tac-Toe",
|
||||
"tictactoeUnavailable": "Tic-Tac-Toe ni na voljo",
|
||||
"tictactoeOpponentUnknown": "Tic-Tac-Toe: nasprotnik neznan",
|
||||
"tictactoeWaitingForStart": "Tic-Tac-Toe: čakanje na začetek",
|
||||
"acceptsShareLinks": "Sprejema deljene povezave",
|
||||
"supportsRawHex": "Podpira surove hex",
|
||||
"clipboardfriendly": "Primerno za odložišče",
|
||||
"captured": "Zajeto",
|
||||
"size": "Velikost",
|
||||
"noCustomChannelsToClear": "Ni prilagojenih kanalov za brisanje.",
|
||||
"noDeviceContactsToClear": "Ni stikov naprave za brisanje."
|
||||
}
|
||||
|
||||
@@ -2276,5 +2276,304 @@
|
||||
"voiceUnavailable": "Ses şu anda kullanılamıyor",
|
||||
"@voiceUnavailable": {},
|
||||
"requestingVoice": "Ses isteniyor",
|
||||
"@requestingVoice": {}
|
||||
"@requestingVoice": {},
|
||||
"device": "cihaz",
|
||||
"change": "Değiştir",
|
||||
"wizardOverviewDescription": "Bu uygulama MeshCore mesajlaşmasını, SAR saha güncellemelerini, haritalamayı ve cihaz araçlarını tek bir yerde birleştirir.",
|
||||
"wizardOverviewFeature1": "Ana Mesajlar sekmesinden doğrudan mesajlar, oda gönderileri ve kanal mesajları gönderin.",
|
||||
"wizardOverviewFeature2": "Mesh üzerinden SAR işaretleri, harita çizimleri, ses klipleri ve görseller paylaşın.",
|
||||
"wizardOverviewFeature3": "BLE veya TCP ile bağlanın, ardından yardımcı radyoyu uygulamanın içinden yönetin.",
|
||||
"wizardMessagingTitle": "Mesajlaşma ve saha raporları",
|
||||
"wizardMessagingDescription": "Buradaki mesajlar düz metinden fazlasıdır. Uygulama zaten çeşitli operasyonel yükleri ve aktarım akışlarını destekliyor.",
|
||||
"wizardMessagingFeature1": "Doğrudan mesajları, oda gönderilerini ve kanal trafiğini tek bir düzenleyiciden gönderin.",
|
||||
"wizardMessagingFeature2": "Yaygın saha raporları için SAR güncellemeleri ve yeniden kullanılabilir SAR şablonları oluşturun.",
|
||||
"wizardMessagingFeature3": "Arayüzde ilerleme ve yayın süresi tahminleriyle ses oturumları ve görseller aktarın.",
|
||||
"wizardConnectDeviceTitle": "Cihazı bağla",
|
||||
"wizardConnectDeviceDescription": "MeshCore radyonuzu bağlayın, bir ad seçin ve devam etmeden önce bir radyo ön ayarı uygulayın.",
|
||||
"wizardSetupBadge": "Kurulum",
|
||||
"wizardOverviewBadge": "Genel bakış",
|
||||
"wizardConnectedToDevice": "{deviceName} cihazına bağlandı",
|
||||
"wizardNoDeviceConnected": "Henüz bağlı cihaz yok",
|
||||
"wizardSkipForNow": "Şimdilik atla",
|
||||
"wizardDeviceNameLabel": "Cihaz adı",
|
||||
"wizardDeviceNameHelp": "Bu ad diğer MeshCore kullanıcılarına duyurulur.",
|
||||
"wizardConfigRegionLabel": "Yapılandırma bölgesi",
|
||||
"wizardConfigRegionHelp": "MeshCore'un resmi ön ayar listesinin tamamını kullanır. Varsayılan EU/UK (Narrow) olur.",
|
||||
"wizardPresetNote1": "Seçilen ön ayarın yerel radyo düzenlemelerinize uygun olduğundan emin olun.",
|
||||
"wizardPresetNote2": "Liste, MeshCore yapılandırma aracının resmi ön ayar akışıyla eşleşir.",
|
||||
"wizardPresetNote3": "Onboarding sırasında varsayılan olarak EU/UK (Narrow) seçili kalır.",
|
||||
"wizardSaving": "Kaydediliyor...",
|
||||
"wizardSaveAndContinue": "Kaydet ve devam et",
|
||||
"wizardEnterDeviceName": "Devam etmeden önce bir cihaz adı girin.",
|
||||
"wizardDeviceSetupSaved": "{deviceName}, {presetName} ile kaydedildi.",
|
||||
"wizardNetworkTitle": "Kişiler, odalar ve tekrarlayıcılar",
|
||||
"wizardNetworkDescription": "Kişiler sekmesi keşfettiğiniz ağı ve zamanla öğrendiğiniz rotaları düzenler.",
|
||||
"wizardNetworkFeature1": "Ekip üyelerini, tekrarlayıcıları, odaları, kanalları ve bekleyen duyuruları tek listede inceleyin.",
|
||||
"wizardNetworkFeature2": "Bağlantı karmaşıklaştığında smart ping, oda girişi, öğrenilen yollar ve rota sıfırlama araçlarını kullanın.",
|
||||
"wizardNetworkFeature3": "Uygulamadan çıkmadan kanallar oluşturun ve ağ hedeflerini yönetin.",
|
||||
"wizardMapOpsTitle": "Harita, izler ve paylaşılan geometri",
|
||||
"wizardMapOpsDescription": "Uygulama haritası ayrı bir görüntüleyici olmak yerine mesajlaşma, takip ve SAR katmanlarıyla doğrudan bağlantılıdır.",
|
||||
"wizardMapOpsFeature1": "Kendi konumunuzu, ekip arkadaşlarının konumlarını ve hareket izlerini harita üzerinde takip edin.",
|
||||
"wizardMapOpsFeature2": "Mesajlardaki çizimleri açın, satır içinde önizleyin ve gerektiğinde haritadan kaldırın.",
|
||||
"wizardMapOpsFeature3": "Sahadaki ağ kapsamasını anlamak için tekrarlayıcı harita görünümleri ve paylaşılan katmanları kullanın.",
|
||||
"wizardToolsTitle": "Mesajlaşma dışındaki araçlar",
|
||||
"wizardToolsDescription": "Burada dört ana sekmeden fazlası var. Uygulama ayrıca yapılandırma, tanılama ve isteğe bağlı sensör akışları içerir.",
|
||||
"wizardToolsFeature1": "Radyo ayarlarını, telemetriyi, TX gücünü ve yardımcı cihaz ayrıntılarını değiştirmek için cihaz yapılandırmasını açın.",
|
||||
"wizardToolsFeature2": "İzlenen sensör panoları ve hızlı yenileme eylemleri istediğinizde Sensörler sekmesini etkinleştirin.",
|
||||
"wizardToolsFeature3": "Mesh sorunlarını giderirken paket günlükleri, spektrum taraması ve geliştirici tanılamalarını kullanın.",
|
||||
"contactInSensors": "Sensörlerde",
|
||||
"contactAddToSensors": "Sensörlere ekle",
|
||||
"contactSetPath": "Yol ayarla",
|
||||
"contactAddedToSensors": "{contactName} Sensörlere eklendi",
|
||||
"contactFailedToClearRoute": "Rota temizlenemedi: {error}",
|
||||
"contactRouteCleared": "Rota temizlendi",
|
||||
"contactRouteSet": "Rota ayarlandı: {route}",
|
||||
"contactFailedToSetRoute": "Rota ayarlanamadı: {error}",
|
||||
"rssi": "RSSI",
|
||||
"snr": "SNR",
|
||||
"ackTimeout": "ACK zaman aşımı",
|
||||
"opcode": "Opcode",
|
||||
"payload": "Yük",
|
||||
"hops": "Atlama",
|
||||
"hashSize": "Hash boyutu",
|
||||
"pathBytes": "Yol baytları",
|
||||
"selectedPath": "Seçilen yol",
|
||||
"estimatedTx": "Tahmini gönderim",
|
||||
"senderToReceipt": "Gönderenden alındıya",
|
||||
"receivedCopies": "Alınan kopyalar",
|
||||
"retryCause": "Yeniden deneme nedeni",
|
||||
"retryMode": "Yeniden deneme modu",
|
||||
"retryResult": "Yeniden deneme sonucu",
|
||||
"lastRetry": "Son deneme",
|
||||
"rxPackets": "RX paketleri",
|
||||
"mesh": "Mesh",
|
||||
"rate": "Hız",
|
||||
"window": "Pencere",
|
||||
"posttxDelay": "Gönderim sonrası gecikme",
|
||||
"bandpass": "Bant geçiren",
|
||||
"bandpassFilterVoice": "Bant geçiren filtre ses",
|
||||
"frequency": "Frekans",
|
||||
"australia": "Avustralya",
|
||||
"australiaNarrow": "Avustralya (Dar)",
|
||||
"australiaQld": "Avustralya: QLD",
|
||||
"australiaSaWa": "Avustralya: SA, WA",
|
||||
"newZealand": "Yeni Zelanda",
|
||||
"newZealandNarrow": "Yeni Zelanda (Dar)",
|
||||
"switzerland": "İsviçre",
|
||||
"portugal433": "Portekiz 433",
|
||||
"portugal868": "Portekiz 868",
|
||||
"czechRepublicNarrow": "Çek Cumhuriyeti (Dar)",
|
||||
"eu433mhzLongRange": "AB 433MHz (Uzun Menzil)",
|
||||
"euukDeprecated": "AB/BK (Kullanımdan kalktı)",
|
||||
"euukNarrow": "AB/BK (Dar)",
|
||||
"usacanadaRecommended": "ABD/Kanada (Önerilen)",
|
||||
"vietnamDeprecated": "Vietnam (Kullanımdan kalktı)",
|
||||
"vietnamNarrow": "Vietnam (Dar)",
|
||||
"active": "Aktif",
|
||||
"addContact": "Kişi ekle",
|
||||
"all": "Tümü",
|
||||
"autoResolve": "Otomatik çöz",
|
||||
"clearAllLabel": "Tümünü temizle",
|
||||
"clearRelays": "Röleleri temizle",
|
||||
"clearFilters": "Filtreleri temizle",
|
||||
"clearRoute": "Rotayı temizle",
|
||||
"clearMessages": "Mesajları temizle",
|
||||
"clearScale": "Ölçeği temizle",
|
||||
"clearDiscoveries": "Keşifleri temizle",
|
||||
"clearOnlineTraceDatabase": "Çevrimiçi iz veritabanını temizle",
|
||||
"clearAllChannels": "Tüm kanalları temizle",
|
||||
"clearAllContacts": "Tüm kişileri temizle",
|
||||
"clearChannels": "Kanalları temizle",
|
||||
"clearContacts": "Kişileri temizle",
|
||||
"clearPathOnMaxRetry": "Maks denemede yolu temizle",
|
||||
"create": "Oluştur",
|
||||
"custom": "Özel",
|
||||
"defaultValue": "Varsayılan",
|
||||
"duplicate": "Çoğalt",
|
||||
"editName": "Adı düzenle",
|
||||
"open": "Aç",
|
||||
"paste": "Yapıştır",
|
||||
"preview": "Önizleme",
|
||||
"remove": "Kaldır",
|
||||
"rename": "Yeniden adlandır",
|
||||
"resolveAll": "Tümünü çöz",
|
||||
"send": "Gönder",
|
||||
"sendAnyway": "Yine de gönder",
|
||||
"share": "Paylaş",
|
||||
"shareContact": "Kişiyi paylaş",
|
||||
"trace": "İz",
|
||||
"use": "Kullan",
|
||||
"useSelectedFrequency": "Seçilen frekansı kullan",
|
||||
"discovery": "Keşif",
|
||||
"discoverRepeaters": "Tekrarlayıcıları keşfet",
|
||||
"discoverSensors": "Sensörleri keşfet",
|
||||
"repeaterDiscoverySent": "Tekrarlayıcı keşfi gönderildi",
|
||||
"sensorDiscoverySent": "Sensör keşfi gönderildi",
|
||||
"clearedPendingDiscoveries": "Bekleyen keşifler temizlendi.",
|
||||
"autoDiscovery": "Otomatik keşif",
|
||||
"enableAutomaticAdding": "Otomatik eklemeyi etkinleştir",
|
||||
"autoaddRepeaters": "Tekrarlayıcıları otomatik ekle",
|
||||
"autoaddRoomServers": "Oda sunucularını otomatik ekle",
|
||||
"autoaddSensors": "Sensörleri otomatik ekle",
|
||||
"autoaddUsers": "Kullanıcıları otomatik ekle",
|
||||
"overwriteOldestWhenFull": "Dolduğunda en eskiyi üzerine yaz",
|
||||
"storage": "Depolama",
|
||||
"dangerZone": "Tehlikeli bölge",
|
||||
"profiles": "Profiller",
|
||||
"favourites": "Favoriler",
|
||||
"sensors": "Sensörler",
|
||||
"others": "Diğerleri",
|
||||
"gpsModule": "GPS Modülü",
|
||||
"liveTraffic": "Canlı trafik",
|
||||
"repeatersMap": "Tekrarlayıcılar haritası",
|
||||
"spectrumScan": "Spektrum taraması",
|
||||
"blePacketLogs": "BLE paket günlükleri",
|
||||
"onlineTraceDatabase": "Çevrimiçi iz veritabanı",
|
||||
"routePathByteSize": "Yol bayt boyutu",
|
||||
"messageNotifications": "Mesaj bildirimleri",
|
||||
"sarAlerts": "SAR uyarıları",
|
||||
"discoveryNotifications": "Keşif bildirimleri",
|
||||
"updateNotifications": "Güncelleme bildirimleri",
|
||||
"muteWhileAppIsOpen": "Uygulama açıkken sessize al",
|
||||
"disableContacts": "Kişileri devre dışı bırak",
|
||||
"enableSensorsTab": "Sensörler sekmesini etkinleştir",
|
||||
"enableProfiles": "Profilleri etkinleştir",
|
||||
"autoRouteRotation": "Otomatik rota rotasyonu",
|
||||
"nearestRepeaterFallback": "En yakın tekrarlayıcıya geri dönüş",
|
||||
"deleteAllStoredMessageHistory": "Tüm mesaj geçmişini sil",
|
||||
"messageFontSize": "Mesaj yazı tipi boyutu",
|
||||
"rotateMapWithHeading": "Haritayı yön ile döndür",
|
||||
"showMapDebugInfo": "Harita hata ayıklama bilgisini göster",
|
||||
"openMapInFullscreen": "Haritayı tam ekranda aç",
|
||||
"showSarMarkersLabel": "SAR işaretçilerini göster",
|
||||
"displaySarMarkersOnTheMainMap": "SAR işaretçilerini ana haritada göster",
|
||||
"showAllContactTrailsLabel": "Tüm kişi izlerini göster",
|
||||
"hideRepeatersOnMap": "Haritada tekrarlayıcıları gizle",
|
||||
"setMapScale": "Harita ölçeğini ayarla",
|
||||
"customMapScaleSaved": "Özel harita ölçeği kaydedildi",
|
||||
"voiceBitrate": "Ses bit hızı",
|
||||
"voiceCompressor": "Ses sıkıştırıcı",
|
||||
"balancesQuietAndLoudSpeechLevels": "Sessiz ve yüksek konuşma seviyelerini dengeler",
|
||||
"voiceLimiter": "Ses sınırlayıcı",
|
||||
"preventsClippingPeaksBeforeEncoding": "Kodlama öncesi tepe kırpmasını önler",
|
||||
"micAutoGain": "Mikrofon otomatik kazancı",
|
||||
"letsTheRecorderAdjustInputLevel": "Kaydedicinin giriş seviyesini ayarlamasına izin verir",
|
||||
"echoCancellation": "Yankı iptali",
|
||||
"noiseSuppression": "Gürültü bastırma",
|
||||
"trimSilenceInVoiceMessages": "Sesli mesajlarda sessizliği kırp",
|
||||
"compressor": "Sıkıştırıcı",
|
||||
"limiter": "Sınırlayıcı",
|
||||
"autoGain": "Otomatik kazanç",
|
||||
"echoCancel": "Yankı",
|
||||
"noiseSuppress": "Gürültü",
|
||||
"silenceTrim": "Sessizlik",
|
||||
"maxImageSize": "Maksimum resim boyutu",
|
||||
"imageCompression": "Resim sıkıştırma",
|
||||
"grayscale": "Gri tonlama",
|
||||
"ultraMode": "Ultra mod",
|
||||
"fastPrivateGpsUpdates": "Hızlı özel GPS güncellemeleri",
|
||||
"movementThreshold": "Hareket eşiği",
|
||||
"fastGpsMovementThreshold": "Hızlı GPS hareket eşiği",
|
||||
"fastGpsActiveuseInterval": "Hızlı GPS aktif kullanım aralığı",
|
||||
"activeuseUpdateInterval": "Aktif kullanım güncelleme aralığı",
|
||||
"repeatNearbyTraffic": "Yakın trafiği tekrarla",
|
||||
"relayThroughRepeatersAcrossTheMesh": "Ağ üzerindeki tekrarlayıcılar aracılığıyla ilet",
|
||||
"nearbyOnlyWithoutRepeaterFlooding": "Yalnızca yakın, tekrarlayıcı taşkını olmadan",
|
||||
"multihop": "Çok atlamalı",
|
||||
"createProfile": "Profil oluştur",
|
||||
"renameProfile": "Profili yeniden adlandır",
|
||||
"newProfile": "Yeni profil",
|
||||
"manageProfiles": "Profilleri yönet",
|
||||
"enableProfilesToStartManagingThem": "Profilleri yönetmeye başlamak için etkinleştirin.",
|
||||
"openMessage": "Mesajı aç",
|
||||
"jumpToTheRelatedSarMessage": "İlgili SAR mesajına git",
|
||||
"removeSarMarker": "SAR işaretçisini kaldır",
|
||||
"pleaseSelectADestinationToSendSarMarker": "SAR işaretçisi göndermek için bir hedef seçin",
|
||||
"sarMarkerBroadcastToPublicChannel": "SAR işaretçisi genel kanala yayınlandı",
|
||||
"sarMarkerSentToRoom": "SAR işaretçisi odaya gönderildi",
|
||||
"loadFromGallery": "Galeriden yükle",
|
||||
"replaceImage": "Resmi değiştir",
|
||||
"selectFromGallery": "Galeriden seç",
|
||||
"team": "Takım",
|
||||
"found": "Bulundu",
|
||||
"staging": "Toplanma alanı",
|
||||
"object": "Nesne",
|
||||
"quiet": "Sessiz",
|
||||
"moderate": "Orta",
|
||||
"busy": "Meşgul",
|
||||
"spectrumScanReturnedNoCandidateFrequencies": "Spektrum taraması aday frekans bulamadı",
|
||||
"searchMessages": "Mesajlarda ara",
|
||||
"sendImageFromGallery": "Galeriden resim gönder",
|
||||
"takePhoto": "Fotoğraf çek",
|
||||
"dmOnly": "Yalnızca DM",
|
||||
"allMessages": "Tüm mesajlar",
|
||||
"sendToPublicChannel": "Genel kanala gönderilsin mi?",
|
||||
"selectMarkerTypeAndDestination": "İşaretçi türünü ve hedefi seçin",
|
||||
"noDestinationsAvailableLabel": "Kullanılabilir hedef yok",
|
||||
"image": "Resim",
|
||||
"format": "Biçim",
|
||||
"dimensions": "Boyutlar",
|
||||
"segments": "Segmentler",
|
||||
"transfers": "Transferler",
|
||||
"downloadedBy": "İndiren",
|
||||
"saveDiscoverySettings": "Keşif ayarlarını kaydet",
|
||||
"savePublicInfo": "Genel bilgileri kaydet",
|
||||
"saveRadioSettings": "Radyo ayarlarını kaydet",
|
||||
"savePath": "Yolu kaydet",
|
||||
"wipeDeviceData": "Cihaz verilerini sil",
|
||||
"wipeDevice": "Cihazı sil",
|
||||
"destructiveDeviceActions": "Yıkıcı cihaz işlemleri.",
|
||||
"chooseAPresetOrFinetuneCustomRadioSettings": "Bir ön ayar seçin veya radyo ayarlarını ince ayarlayın.",
|
||||
"chooseTheNameAndLocationThisDeviceShares": "Bu cihazın paylaştığı ad ve konumu seçin.",
|
||||
"availableSpaceOnThisDevice": "Bu cihazda kullanılabilir alan.",
|
||||
"used": "Kullanılan",
|
||||
"total": "Toplam",
|
||||
"renameValue": "Değeri yeniden adlandır",
|
||||
"customizeFields": "Alanları özelleştir",
|
||||
"livePreview": "Canlı önizleme",
|
||||
"refreshSchedule": "Yenileme programı",
|
||||
"noResponse": "Yanıt yok",
|
||||
"refreshing": "Yenileniyor",
|
||||
"unavailable": "Kullanılamaz",
|
||||
"pickARelayOrNodeToWatchInSensors": "İzlemek için bir röle veya düğüm seçin.",
|
||||
"publicKeyLabel": "Genel anahtar",
|
||||
"alreadyInContacts": "Zaten kişilerde",
|
||||
"connectToADeviceBeforeAddingContacts": "Kişi eklemeden önce bir cihaza bağlanın",
|
||||
"fromContacts": "Kişilerden",
|
||||
"onlineOnly": "Yalnızca çevrimiçi",
|
||||
"inBoth": "Her ikisinde",
|
||||
"source": "Kaynak",
|
||||
"manualRouteEdit": "Manuel rota düzenle",
|
||||
"observedMeshRoute": "Gözlemlenen mesh rotası",
|
||||
"allMessagesCleared": "Tüm mesajlar temizlendi",
|
||||
"onlineTraceDatabaseCleared": "Çevrimiçi iz veritabanı temizlendi",
|
||||
"packetLogsCleared": "Paket günlükleri temizlendi",
|
||||
"hexDataCopiedToClipboard": "Hex verisi panoya kopyalandı",
|
||||
"developerModeEnabled": "Geliştirici modu etkinleştirildi",
|
||||
"developerModeDisabled": "Geliştirici modu devre dışı bırakıldı",
|
||||
"clipboardIsEmpty": "Pano boş",
|
||||
"contactImported": "Kişi içe aktarıldı",
|
||||
"contactLinkCopiedToClipboard": "Kişi bağlantısı panoya kopyalandı",
|
||||
"failedToExportContact": "Kişi dışa aktarılamadı",
|
||||
"noLogsToExport": "Dışa aktarılacak günlük yok",
|
||||
"exportAsCsv": "CSV olarak dışa aktar",
|
||||
"exportAsText": "Metin olarak dışa aktar",
|
||||
"receivedRfc3339": "Alındı (RFC3339)",
|
||||
"buildTime": "Derleme zamanı",
|
||||
"downloadUrlNotAvailable": "İndirme URL'si mevcut değil",
|
||||
"cannotOpenDownloadUrl": "İndirme URL'si açılamıyor",
|
||||
"updateCheckIsOnlyAvailableOnAndroid": "Güncelleme kontrolü yalnızca Android'de kullanılabilir",
|
||||
"youAreRunningTheLatestVersion": "En son sürümü kullanıyorsunuz",
|
||||
"updateAvailableButDownloadUrlNotFound": "Güncelleme mevcut ancak indirme URL'si bulunamadı",
|
||||
"startTictactoe": "Tic-Tac-Toe başlat",
|
||||
"tictactoeUnavailable": "Tic-Tac-Toe kullanılamıyor",
|
||||
"tictactoeOpponentUnknown": "Tic-Tac-Toe: rakip bilinmiyor",
|
||||
"tictactoeWaitingForStart": "Tic-Tac-Toe: başlangıç bekleniyor",
|
||||
"acceptsShareLinks": "Paylaşım bağlantılarını kabul eder",
|
||||
"supportsRawHex": "Ham hex destekler",
|
||||
"clipboardfriendly": "Pano dostu",
|
||||
"captured": "Yakalandı",
|
||||
"size": "Boyut",
|
||||
"noCustomChannelsToClear": "Temizlenecek özel kanal yok.",
|
||||
"noDeviceContactsToClear": "Temizlenecek cihaz kişisi yok."
|
||||
}
|
||||
|
||||
@@ -2276,5 +2276,304 @@
|
||||
"voiceUnavailable": "Голос зараз недоступний",
|
||||
"@voiceUnavailable": {},
|
||||
"requestingVoice": "Запит голосу",
|
||||
"@requestingVoice": {}
|
||||
"@requestingVoice": {},
|
||||
"device": "пристрій",
|
||||
"change": "Змінити",
|
||||
"wizardOverviewDescription": "Ця програма поєднує повідомлення MeshCore, польові оновлення SAR, карти та інструменти пристрою в одному місці.",
|
||||
"wizardOverviewFeature1": "Надсилайте особисті повідомлення, повідомлення кімнат і повідомлення каналів з основної вкладки «Повідомлення».",
|
||||
"wizardOverviewFeature2": "Діліться SAR-маркерами, малюнками на карті, голосовими кліпами та зображеннями через mesh-мережу.",
|
||||
"wizardOverviewFeature3": "Підключайтеся через BLE або TCP, а потім керуйте супутнім радіопристроєм прямо з програми.",
|
||||
"wizardMessagingTitle": "Повідомлення та польові звіти",
|
||||
"wizardMessagingDescription": "Повідомлення тут це більше, ніж просто текст. Програма вже підтримує кілька типів операційних даних і сценаріїв передачі.",
|
||||
"wizardMessagingFeature1": "Надсилайте особисті повідомлення, повідомлення кімнат і трафік каналів з одного редактора.",
|
||||
"wizardMessagingFeature2": "Створюйте оновлення SAR і багаторазові шаблони SAR для типових польових звітів.",
|
||||
"wizardMessagingFeature3": "Передавайте голосові сесії та зображення з індикатором прогресу й оцінками ефірного часу в інтерфейсі.",
|
||||
"wizardConnectDeviceTitle": "Підключити пристрій",
|
||||
"wizardConnectDeviceDescription": "Підключіть своє радіо MeshCore, виберіть назву та застосуйте радіопрофіль перед продовженням.",
|
||||
"wizardSetupBadge": "Налаштування",
|
||||
"wizardOverviewBadge": "Огляд",
|
||||
"wizardConnectedToDevice": "Підключено до {deviceName}",
|
||||
"wizardNoDeviceConnected": "Ще немає підключеного пристрою",
|
||||
"wizardSkipForNow": "Пропустити поки що",
|
||||
"wizardDeviceNameLabel": "Назва пристрою",
|
||||
"wizardDeviceNameHelp": "Ця назва оголошується іншим користувачам MeshCore.",
|
||||
"wizardConfigRegionLabel": "Регіон конфігурації",
|
||||
"wizardConfigRegionHelp": "Використовується повний офіційний список профілів MeshCore. Типово вибрано EU/UK (Narrow).",
|
||||
"wizardPresetNote1": "Переконайтеся, що вибраний профіль відповідає місцевим радіоправилам.",
|
||||
"wizardPresetNote2": "Список відповідає офіційному потоку профілів інструмента MeshCore config.",
|
||||
"wizardPresetNote3": "Для онбордингу типовим залишається EU/UK (Narrow).",
|
||||
"wizardSaving": "Збереження...",
|
||||
"wizardSaveAndContinue": "Зберегти й продовжити",
|
||||
"wizardEnterDeviceName": "Введіть назву пристрою перед продовженням.",
|
||||
"wizardDeviceSetupSaved": "Збережено {deviceName} з профілем {presetName}.",
|
||||
"wizardNetworkTitle": "Контакти, кімнати та ретранслятори",
|
||||
"wizardNetworkDescription": "Вкладка «Контакти» організовує мережу, яку ви виявляєте, і маршрути, які вивчаєте з часом.",
|
||||
"wizardNetworkFeature1": "Переглядайте членів команди, ретранслятори, кімнати, канали та оголошення в очікуванні в одному списку.",
|
||||
"wizardNetworkFeature2": "Використовуйте smart ping, вхід до кімнат, вивчені шляхи й інструменти скидання маршрутів, коли зв'язок стає нестабільним.",
|
||||
"wizardNetworkFeature3": "Створюйте канали та керуйте мережевими призначеннями, не виходячи з програми.",
|
||||
"wizardMapOpsTitle": "Мапа, сліди та спільна геометрія",
|
||||
"wizardMapOpsDescription": "Мапа програми напряму пов'язана з повідомленнями, відстеженням і SAR-накладками, а не є окремим переглядачем.",
|
||||
"wizardMapOpsFeature1": "Відстежуйте власну позицію, місця розташування команди та сліди руху на мапі.",
|
||||
"wizardMapOpsFeature2": "Відкривайте малюнки з повідомлень, переглядайте їх в інтерфейсі та видаляйте з мапи за потреби.",
|
||||
"wizardMapOpsFeature3": "Використовуйте мапи ретрансляторів і спільні накладки, щоб розуміти покриття мережі в полі.",
|
||||
"wizardToolsTitle": "Інструменти поза повідомленнями",
|
||||
"wizardToolsDescription": "Тут є більше, ніж чотири основні вкладки. Програма також включає налаштування, діагностику та необов'язкові сценарії датчиків.",
|
||||
"wizardToolsFeature1": "Відкрийте налаштування пристрою, щоб змінити параметри радіо, телеметрію, потужність TX і дані супутнього пристрою.",
|
||||
"wizardToolsFeature2": "Увімкніть вкладку «Датчики», коли потрібні панелі моніторингу та швидкі дії оновлення.",
|
||||
"wizardToolsFeature3": "Використовуйте журнали пакетів, сканування спектра та діагностику розробника для усунення проблем mesh-мережі.",
|
||||
"contactInSensors": "У датчиках",
|
||||
"contactAddToSensors": "Додати до датчиків",
|
||||
"contactSetPath": "Задати шлях",
|
||||
"contactAddedToSensors": "{contactName} додано до Датчиків",
|
||||
"contactFailedToClearRoute": "Не вдалося очистити маршрут: {error}",
|
||||
"contactRouteCleared": "Маршрут очищено",
|
||||
"contactRouteSet": "Маршрут задано: {route}",
|
||||
"contactFailedToSetRoute": "Не вдалося задати маршрут: {error}",
|
||||
"rssi": "RSSI",
|
||||
"snr": "SNR",
|
||||
"ackTimeout": "Тайм-аут ACK",
|
||||
"opcode": "Опкод",
|
||||
"payload": "Корисне навантаження",
|
||||
"hops": "Хопи",
|
||||
"hashSize": "Розмір хешу",
|
||||
"pathBytes": "Байти шляху",
|
||||
"selectedPath": "Обраний шлях",
|
||||
"estimatedTx": "Очікувана передача",
|
||||
"senderToReceipt": "Від відправника до отримання",
|
||||
"receivedCopies": "Отримані копії",
|
||||
"retryCause": "Причина повтору",
|
||||
"retryMode": "Режим повтору",
|
||||
"retryResult": "Результат повтору",
|
||||
"lastRetry": "Остання спроба",
|
||||
"rxPackets": "RX-пакети",
|
||||
"mesh": "Mesh",
|
||||
"rate": "Швидкість",
|
||||
"window": "Вікно",
|
||||
"posttxDelay": "Затримка після передачі",
|
||||
"bandpass": "Смуговий",
|
||||
"bandpassFilterVoice": "Смуговий фільтр голосу",
|
||||
"frequency": "Частота",
|
||||
"australia": "Австралія",
|
||||
"australiaNarrow": "Австралія (Вузький)",
|
||||
"australiaQld": "Австралія: QLD",
|
||||
"australiaSaWa": "Австралія: SA, WA",
|
||||
"newZealand": "Нова Зеландія",
|
||||
"newZealandNarrow": "Нова Зеландія (Вузький)",
|
||||
"switzerland": "Швейцарія",
|
||||
"portugal433": "Португалія 433",
|
||||
"portugal868": "Португалія 868",
|
||||
"czechRepublicNarrow": "Чехія (Вузький)",
|
||||
"eu433mhzLongRange": "ЄС 433МГц (Далекий)",
|
||||
"euukDeprecated": "ЄС/Великобританія (Застаріле)",
|
||||
"euukNarrow": "ЄС/Великобританія (Вузький)",
|
||||
"usacanadaRecommended": "США/Канада (Рекомендовано)",
|
||||
"vietnamDeprecated": "В'єтнам (Застаріле)",
|
||||
"vietnamNarrow": "В'єтнам (Вузький)",
|
||||
"active": "Активно",
|
||||
"addContact": "Додати контакт",
|
||||
"all": "Усі",
|
||||
"autoResolve": "Автоматичне вирішення",
|
||||
"clearAllLabel": "Очистити все",
|
||||
"clearRelays": "Очистити реле",
|
||||
"clearFilters": "Очистити фільтри",
|
||||
"clearRoute": "Очистити маршрут",
|
||||
"clearMessages": "Очистити повідомлення",
|
||||
"clearScale": "Очистити масштаб",
|
||||
"clearDiscoveries": "Очистити виявлення",
|
||||
"clearOnlineTraceDatabase": "Очистити базу трасувань",
|
||||
"clearAllChannels": "Очистити всі канали",
|
||||
"clearAllContacts": "Очистити всі контакти",
|
||||
"clearChannels": "Очистити канали",
|
||||
"clearContacts": "Очистити контакти",
|
||||
"clearPathOnMaxRetry": "Очистити шлях при макс. спробі",
|
||||
"create": "Створити",
|
||||
"custom": "Користувацький",
|
||||
"defaultValue": "За замовчуванням",
|
||||
"duplicate": "Дублювати",
|
||||
"editName": "Редагувати ім'я",
|
||||
"open": "Відкрити",
|
||||
"paste": "Вставити",
|
||||
"preview": "Попередній перегляд",
|
||||
"remove": "Видалити",
|
||||
"rename": "Перейменувати",
|
||||
"resolveAll": "Вирішити все",
|
||||
"send": "Надіслати",
|
||||
"sendAnyway": "Надіслати все одно",
|
||||
"share": "Поділитися",
|
||||
"shareContact": "Поділитися контактом",
|
||||
"trace": "Трасування",
|
||||
"use": "Використати",
|
||||
"useSelectedFrequency": "Використати обрану частоту",
|
||||
"discovery": "Виявлення",
|
||||
"discoverRepeaters": "Виявити повторювачі",
|
||||
"discoverSensors": "Виявити датчики",
|
||||
"repeaterDiscoverySent": "Виявлення повторювачів надіслано",
|
||||
"sensorDiscoverySent": "Виявлення датчиків надіслано",
|
||||
"clearedPendingDiscoveries": "Очікувані виявлення очищено.",
|
||||
"autoDiscovery": "Автоматичне виявлення",
|
||||
"enableAutomaticAdding": "Увімкнути автоматичне додавання",
|
||||
"autoaddRepeaters": "Автододавання повторювачів",
|
||||
"autoaddRoomServers": "Автододавання серверів кімнат",
|
||||
"autoaddSensors": "Автододавання датчиків",
|
||||
"autoaddUsers": "Автододавання користувачів",
|
||||
"overwriteOldestWhenFull": "Перезаписати найстаріші при заповненні",
|
||||
"storage": "Сховище",
|
||||
"dangerZone": "Небезпечна зона",
|
||||
"profiles": "Профілі",
|
||||
"favourites": "Обране",
|
||||
"sensors": "Датчики",
|
||||
"others": "Інші",
|
||||
"gpsModule": "Модуль GPS",
|
||||
"liveTraffic": "Живий трафік",
|
||||
"repeatersMap": "Карта повторювачів",
|
||||
"spectrumScan": "Сканування спектру",
|
||||
"blePacketLogs": "Журнали BLE-пакетів",
|
||||
"onlineTraceDatabase": "База трасувань",
|
||||
"routePathByteSize": "Розмір шляху в байтах",
|
||||
"messageNotifications": "Сповіщення про повідомлення",
|
||||
"sarAlerts": "SAR-сповіщення",
|
||||
"discoveryNotifications": "Сповіщення про виявлення",
|
||||
"updateNotifications": "Сповіщення про оновлення",
|
||||
"muteWhileAppIsOpen": "Без звуку при відкритому додатку",
|
||||
"disableContacts": "Вимкнути контакти",
|
||||
"enableSensorsTab": "Увімкнути вкладку Датчики",
|
||||
"enableProfiles": "Увімкнути профілі",
|
||||
"autoRouteRotation": "Автоматична ротація маршруту",
|
||||
"nearestRepeaterFallback": "Найближчий повторювач як резерв",
|
||||
"deleteAllStoredMessageHistory": "Видалити всю історію повідомлень",
|
||||
"messageFontSize": "Розмір шрифту повідомлень",
|
||||
"rotateMapWithHeading": "Обертати карту за напрямком",
|
||||
"showMapDebugInfo": "Показати налагоджувальну інфо карти",
|
||||
"openMapInFullscreen": "Відкрити карту на повний екран",
|
||||
"showSarMarkersLabel": "Показати SAR-маркери",
|
||||
"displaySarMarkersOnTheMainMap": "Показувати SAR-маркери на головній карті",
|
||||
"showAllContactTrailsLabel": "Показати всі сліди контактів",
|
||||
"hideRepeatersOnMap": "Сховати повторювачі на карті",
|
||||
"setMapScale": "Встановити масштаб карти",
|
||||
"customMapScaleSaved": "Користувацький масштаб карти збережено",
|
||||
"voiceBitrate": "Бітрейт голосу",
|
||||
"voiceCompressor": "Компресор голосу",
|
||||
"balancesQuietAndLoudSpeechLevels": "Вирівнює тихий і гучний рівні мови",
|
||||
"voiceLimiter": "Лімітер голосу",
|
||||
"preventsClippingPeaksBeforeEncoding": "Запобігає обрізанню піків перед кодуванням",
|
||||
"micAutoGain": "Автопідсилення мікрофона",
|
||||
"letsTheRecorderAdjustInputLevel": "Дозволяє рекордеру налаштувати рівень входу",
|
||||
"echoCancellation": "Придушення ехо",
|
||||
"noiseSuppression": "Придушення шуму",
|
||||
"trimSilenceInVoiceMessages": "Обрізати тишу в голосових повідомленнях",
|
||||
"compressor": "Компресор",
|
||||
"limiter": "Лімітер",
|
||||
"autoGain": "Автопідсилення",
|
||||
"echoCancel": "Ехо",
|
||||
"noiseSuppress": "Шум",
|
||||
"silenceTrim": "Тиша",
|
||||
"maxImageSize": "Максимальний розмір зображення",
|
||||
"imageCompression": "Стиснення зображення",
|
||||
"grayscale": "Відтінки сірого",
|
||||
"ultraMode": "Ультра режим",
|
||||
"fastPrivateGpsUpdates": "Швидкі приватні GPS-оновлення",
|
||||
"movementThreshold": "Поріг руху",
|
||||
"fastGpsMovementThreshold": "Поріг руху швидкого GPS",
|
||||
"fastGpsActiveuseInterval": "Інтервал активного використання швидкого GPS",
|
||||
"activeuseUpdateInterval": "Інтервал оновлення при активному використанні",
|
||||
"repeatNearbyTraffic": "Повторювати близький трафік",
|
||||
"relayThroughRepeatersAcrossTheMesh": "Ретранслювати через повторювачі по мережі",
|
||||
"nearbyOnlyWithoutRepeaterFlooding": "Лише поблизу, без flood повторювачів",
|
||||
"multihop": "Багатохоповий",
|
||||
"createProfile": "Створити профіль",
|
||||
"renameProfile": "Перейменувати профіль",
|
||||
"newProfile": "Новий профіль",
|
||||
"manageProfiles": "Керувати профілями",
|
||||
"enableProfilesToStartManagingThem": "Увімкніть профілі, щоб почати керувати ними.",
|
||||
"openMessage": "Відкрити повідомлення",
|
||||
"jumpToTheRelatedSarMessage": "Перейти до пов'язаного SAR-повідомлення",
|
||||
"removeSarMarker": "Видалити SAR-маркер",
|
||||
"pleaseSelectADestinationToSendSarMarker": "Виберіть призначення для надсилання SAR-маркера",
|
||||
"sarMarkerBroadcastToPublicChannel": "SAR-маркер надіслано на публічний канал",
|
||||
"sarMarkerSentToRoom": "SAR-маркер надіслано в кімнату",
|
||||
"loadFromGallery": "Завантажити з галереї",
|
||||
"replaceImage": "Замінити зображення",
|
||||
"selectFromGallery": "Вибрати з галереї",
|
||||
"team": "Команда",
|
||||
"found": "Знайдено",
|
||||
"staging": "Місце збору",
|
||||
"object": "Об'єкт",
|
||||
"quiet": "Тихо",
|
||||
"moderate": "Помірно",
|
||||
"busy": "Зайнято",
|
||||
"spectrumScanReturnedNoCandidateFrequencies": "Сканування спектру не знайшло частот-кандидатів",
|
||||
"searchMessages": "Пошук повідомлень",
|
||||
"sendImageFromGallery": "Надіслати зображення з галереї",
|
||||
"takePhoto": "Зробити фото",
|
||||
"dmOnly": "Лише особисті повідомлення",
|
||||
"allMessages": "Усі повідомлення",
|
||||
"sendToPublicChannel": "Надіслати в публічний канал?",
|
||||
"selectMarkerTypeAndDestination": "Виберіть тип маркера та призначення",
|
||||
"noDestinationsAvailableLabel": "Немає доступних призначень",
|
||||
"image": "Зображення",
|
||||
"format": "Формат",
|
||||
"dimensions": "Розміри",
|
||||
"segments": "Сегменти",
|
||||
"transfers": "Передачі",
|
||||
"downloadedBy": "Завантажено",
|
||||
"saveDiscoverySettings": "Зберегти налаштування виявлення",
|
||||
"savePublicInfo": "Зберегти публічну інформацію",
|
||||
"saveRadioSettings": "Зберегти налаштування радіо",
|
||||
"savePath": "Зберегти шлях",
|
||||
"wipeDeviceData": "Стерти дані пристрою",
|
||||
"wipeDevice": "Стерти пристрій",
|
||||
"destructiveDeviceActions": "Деструктивні дії пристрою.",
|
||||
"chooseAPresetOrFinetuneCustomRadioSettings": "Оберіть пресет або налаштуйте радіо вручну.",
|
||||
"chooseTheNameAndLocationThisDeviceShares": "Оберіть ім'я та місцеположення, якими ділиться пристрій.",
|
||||
"availableSpaceOnThisDevice": "Доступний простір на цьому пристрої.",
|
||||
"used": "Використано",
|
||||
"total": "Загалом",
|
||||
"renameValue": "Перейменувати значення",
|
||||
"customizeFields": "Налаштувати поля",
|
||||
"livePreview": "Попередній перегляд наживо",
|
||||
"refreshSchedule": "Розклад оновлення",
|
||||
"noResponse": "Немає відповіді",
|
||||
"refreshing": "Оновлення",
|
||||
"unavailable": "Недоступно",
|
||||
"pickARelayOrNodeToWatchInSensors": "Оберіть реле або вузол для спостереження.",
|
||||
"publicKeyLabel": "Публічний ключ",
|
||||
"alreadyInContacts": "Вже в контактах",
|
||||
"connectToADeviceBeforeAddingContacts": "Підключіться до пристрою перед додаванням контактів",
|
||||
"fromContacts": "З контактів",
|
||||
"onlineOnly": "Лише онлайн",
|
||||
"inBoth": "В обох",
|
||||
"source": "Джерело",
|
||||
"manualRouteEdit": "Ручне редагування маршруту",
|
||||
"observedMeshRoute": "Спостережуваний mesh-маршрут",
|
||||
"allMessagesCleared": "Усі повідомлення очищено",
|
||||
"onlineTraceDatabaseCleared": "Базу трасувань очищено",
|
||||
"packetLogsCleared": "Журнали пакетів очищено",
|
||||
"hexDataCopiedToClipboard": "Hex-дані скопійовано в буфер",
|
||||
"developerModeEnabled": "Режим розробника увімкнено",
|
||||
"developerModeDisabled": "Режим розробника вимкнено",
|
||||
"clipboardIsEmpty": "Буфер обміну порожній",
|
||||
"contactImported": "Контакт імпортовано",
|
||||
"contactLinkCopiedToClipboard": "Посилання на контакт скопійовано в буфер",
|
||||
"failedToExportContact": "Не вдалося експортувати контакт",
|
||||
"noLogsToExport": "Немає журналів для експорту",
|
||||
"exportAsCsv": "Експортувати як CSV",
|
||||
"exportAsText": "Експортувати як текст",
|
||||
"receivedRfc3339": "Отримано (RFC3339)",
|
||||
"buildTime": "Час збірки",
|
||||
"downloadUrlNotAvailable": "URL завантаження недоступний",
|
||||
"cannotOpenDownloadUrl": "Не вдається відкрити URL завантаження",
|
||||
"updateCheckIsOnlyAvailableOnAndroid": "Перевірка оновлень доступна лише на Android",
|
||||
"youAreRunningTheLatestVersion": "Ви використовуєте останню версію",
|
||||
"updateAvailableButDownloadUrlNotFound": "Оновлення доступне, але URL завантаження не знайдено",
|
||||
"startTictactoe": "Почати Tic-Tac-Toe",
|
||||
"tictactoeUnavailable": "Tic-Tac-Toe недоступно",
|
||||
"tictactoeOpponentUnknown": "Tic-Tac-Toe: суперник невідомий",
|
||||
"tictactoeWaitingForStart": "Tic-Tac-Toe: очікування початку",
|
||||
"acceptsShareLinks": "Приймає посилання для обміну",
|
||||
"supportsRawHex": "Підтримує raw hex",
|
||||
"clipboardfriendly": "Зручно для буфера",
|
||||
"captured": "Захоплено",
|
||||
"size": "Розмір",
|
||||
"noCustomChannelsToClear": "Немає користувацьких каналів для очищення.",
|
||||
"noDeviceContactsToClear": "Немає контактів пристрою для очищення."
|
||||
}
|
||||
|
||||
@@ -2228,78 +2228,352 @@
|
||||
"description": "没有可发送的聊天联系人时的消息"
|
||||
},
|
||||
"reply": "回复",
|
||||
"@reply": {
|
||||
},
|
||||
"@reply": {},
|
||||
"technicalDetails": "技术细节",
|
||||
"@technicalDetails": {
|
||||
},
|
||||
"@technicalDetails": {},
|
||||
"messageTechnicalDetails": "消息技术细节",
|
||||
"@messageTechnicalDetails": {
|
||||
},
|
||||
"@messageTechnicalDetails": {},
|
||||
"linkQuality": "链路质量",
|
||||
"@linkQuality": {
|
||||
},
|
||||
"@linkQuality": {},
|
||||
"delivery": "投递",
|
||||
"@delivery": {
|
||||
},
|
||||
"@delivery": {},
|
||||
"status": "状态",
|
||||
"@status": {
|
||||
},
|
||||
"@status": {},
|
||||
"expectedAckTag": "预期的 ACK 标签",
|
||||
"@expectedAckTag": {
|
||||
},
|
||||
"@expectedAckTag": {},
|
||||
"roundTrip": "往返时间",
|
||||
"@roundTrip": {
|
||||
},
|
||||
"@roundTrip": {},
|
||||
"retryAttempt": "重试尝试",
|
||||
"@retryAttempt": {
|
||||
},
|
||||
"@retryAttempt": {},
|
||||
"floodFallback": "泛洪回退",
|
||||
"@floodFallback": {
|
||||
},
|
||||
"@floodFallback": {},
|
||||
"identity": "身份",
|
||||
"@identity": {
|
||||
},
|
||||
"@identity": {},
|
||||
"messageId": "消息 ID",
|
||||
"@messageId": {
|
||||
},
|
||||
"@messageId": {},
|
||||
"sender": "发送者",
|
||||
"@sender": {
|
||||
},
|
||||
"@sender": {},
|
||||
"senderKey": "发送者密钥",
|
||||
"@senderKey": {
|
||||
},
|
||||
"@senderKey": {},
|
||||
"recipient": "接收者",
|
||||
"@recipient": {
|
||||
},
|
||||
"@recipient": {},
|
||||
"recipientKey": "接收者密钥",
|
||||
"@recipientKey": {
|
||||
},
|
||||
"@recipientKey": {},
|
||||
"voice": "语音",
|
||||
"@voice": {
|
||||
},
|
||||
"@voice": {},
|
||||
"voiceId": "语音 ID",
|
||||
"@voiceId": {
|
||||
},
|
||||
"@voiceId": {},
|
||||
"envelope": "信封",
|
||||
"@envelope": {
|
||||
},
|
||||
"@envelope": {},
|
||||
"sessionProgress": "会话进度",
|
||||
"@sessionProgress": {
|
||||
},
|
||||
"@sessionProgress": {},
|
||||
"complete": "完成",
|
||||
"@complete": {
|
||||
},
|
||||
"@complete": {},
|
||||
"rawDump": "原始数据",
|
||||
"@rawDump": {
|
||||
},
|
||||
"@rawDump": {},
|
||||
"cannotRetryMissingRecipient": "无法重试:缺少接收者信息",
|
||||
"@cannotRetryMissingRecipient": {
|
||||
},
|
||||
"@cannotRetryMissingRecipient": {},
|
||||
"voiceUnavailable": "当前语音不可用",
|
||||
"@voiceUnavailable": {
|
||||
},
|
||||
"@voiceUnavailable": {},
|
||||
"requestingVoice": "正在请求语音",
|
||||
"@requestingVoice": {
|
||||
}
|
||||
"@requestingVoice": {},
|
||||
"device": "设备",
|
||||
"change": "更改",
|
||||
"wizardOverviewDescription": "此应用将 MeshCore 消息、SAR 现场更新、地图功能和设备工具整合到同一处。",
|
||||
"wizardOverviewFeature1": "在主消息标签页中发送私聊消息、房间消息和频道消息。",
|
||||
"wizardOverviewFeature2": "通过 mesh 网络共享 SAR 标记、地图绘图、语音片段和图像。",
|
||||
"wizardOverviewFeature3": "通过 BLE 或 TCP 连接,然后直接在应用中管理配套电台。",
|
||||
"wizardMessagingTitle": "消息与现场报告",
|
||||
"wizardMessagingDescription": "这里的消息不只是纯文本。应用已经支持多种操作载荷和传输流程。",
|
||||
"wizardMessagingFeature1": "通过一个编辑器发送私聊消息、房间消息和频道流量。",
|
||||
"wizardMessagingFeature2": "为常见现场报告创建 SAR 更新和可复用的 SAR 模板。",
|
||||
"wizardMessagingFeature3": "传输语音会话和图像,并在界面中显示进度和空口时间估算。",
|
||||
"wizardConnectDeviceTitle": "连接设备",
|
||||
"wizardConnectDeviceDescription": "连接你的 MeshCore 电台,选择名称,并在继续前应用无线电预设。",
|
||||
"wizardSetupBadge": "设置",
|
||||
"wizardOverviewBadge": "概览",
|
||||
"wizardConnectedToDevice": "已连接到 {deviceName}",
|
||||
"wizardNoDeviceConnected": "尚未连接设备",
|
||||
"wizardSkipForNow": "暂时跳过",
|
||||
"wizardDeviceNameLabel": "设备名称",
|
||||
"wizardDeviceNameHelp": "此名称会广播给其他 MeshCore 用户。",
|
||||
"wizardConfigRegionLabel": "配置区域",
|
||||
"wizardConfigRegionHelp": "使用完整的官方 MeshCore 预设列表。默认值为 EU/UK (Narrow)。",
|
||||
"wizardPresetNote1": "请确认所选预设符合你所在地区的无线电规定。",
|
||||
"wizardPresetNote2": "该列表与 MeshCore 配置工具的官方预设源保持一致。",
|
||||
"wizardPresetNote3": "在引导设置中默认保持选中 EU/UK (Narrow)。",
|
||||
"wizardSaving": "正在保存...",
|
||||
"wizardSaveAndContinue": "保存并继续",
|
||||
"wizardEnterDeviceName": "继续前请输入设备名称。",
|
||||
"wizardDeviceSetupSaved": "已保存 {deviceName},配置为 {presetName}。",
|
||||
"wizardNetworkTitle": "联系人、房间和中继器",
|
||||
"wizardNetworkDescription": "联系人标签页会整理你发现的网络以及随着时间学习到的路由。",
|
||||
"wizardNetworkFeature1": "在一个列表中查看团队成员、中继器、房间、频道和待处理广播。",
|
||||
"wizardNetworkFeature2": "当连接变得混乱时,使用智能 ping、房间登录、已学习路径和路由重置工具。",
|
||||
"wizardNetworkFeature3": "无需离开应用即可创建频道并管理网络目的地。",
|
||||
"wizardMapOpsTitle": "地图、轨迹与共享几何",
|
||||
"wizardMapOpsDescription": "应用地图直接与消息、跟踪和 SAR 叠加层关联,而不是单独的查看器。",
|
||||
"wizardMapOpsFeature1": "在地图上跟踪你自己的位置、队友位置和移动轨迹。",
|
||||
"wizardMapOpsFeature2": "打开消息中的绘图,内联预览,并在需要时将其从地图上移除。",
|
||||
"wizardMapOpsFeature3": "使用中继器地图视图和共享叠加层来了解现场网络覆盖范围。",
|
||||
"wizardToolsTitle": "消息之外的工具",
|
||||
"wizardToolsDescription": "这里不只有四个主标签页。应用还包含配置、诊断和可选的传感器工作流。",
|
||||
"wizardToolsFeature1": "打开设备配置以更改无线电设置、遥测、TX 功率和配套设备详情。",
|
||||
"wizardToolsFeature2": "当你需要受监控的传感器面板和快速刷新操作时,启用传感器标签页。",
|
||||
"wizardToolsFeature3": "在排查 mesh 网络问题时使用数据包日志、频谱扫描和开发者诊断。",
|
||||
"contactInSensors": "在传感器中",
|
||||
"contactAddToSensors": "添加到传感器",
|
||||
"contactSetPath": "设置路径",
|
||||
"contactAddedToSensors": "已将 {contactName} 添加到传感器",
|
||||
"contactFailedToClearRoute": "清除路由失败:{error}",
|
||||
"contactRouteCleared": "路由已清除",
|
||||
"contactRouteSet": "路由已设置:{route}",
|
||||
"contactFailedToSetRoute": "设置路由失败:{error}",
|
||||
"rssi": "RSSI",
|
||||
"snr": "SNR",
|
||||
"ackTimeout": "ACK 超时",
|
||||
"opcode": "操作码",
|
||||
"payload": "有效载荷",
|
||||
"hops": "跳数",
|
||||
"hashSize": "哈希大小",
|
||||
"pathBytes": "路径字节",
|
||||
"selectedPath": "选定路径",
|
||||
"estimatedTx": "预计传输",
|
||||
"senderToReceipt": "发送到接收",
|
||||
"receivedCopies": "收到的副本",
|
||||
"retryCause": "重试原因",
|
||||
"retryMode": "重试模式",
|
||||
"retryResult": "重试结果",
|
||||
"lastRetry": "最后重试",
|
||||
"rxPackets": "RX 数据包",
|
||||
"mesh": "Mesh",
|
||||
"rate": "速率",
|
||||
"window": "窗口",
|
||||
"posttxDelay": "发送后延迟",
|
||||
"bandpass": "带通",
|
||||
"bandpassFilterVoice": "带通语音滤波",
|
||||
"frequency": "频率",
|
||||
"australia": "澳大利亚",
|
||||
"australiaNarrow": "澳大利亚(窄带)",
|
||||
"australiaQld": "澳大利亚:QLD",
|
||||
"australiaSaWa": "澳大利亚:SA, WA",
|
||||
"newZealand": "新西兰",
|
||||
"newZealandNarrow": "新西兰(窄带)",
|
||||
"switzerland": "瑞士",
|
||||
"portugal433": "葡萄牙 433",
|
||||
"portugal868": "葡萄牙 868",
|
||||
"czechRepublicNarrow": "捷克(窄带)",
|
||||
"eu433mhzLongRange": "欧盟 433MHz(远距离)",
|
||||
"euukDeprecated": "欧盟/英国(已弃用)",
|
||||
"euukNarrow": "欧盟/英国(窄带)",
|
||||
"usacanadaRecommended": "美国/加拿大(推荐)",
|
||||
"vietnamDeprecated": "越南(已弃用)",
|
||||
"vietnamNarrow": "越南(窄带)",
|
||||
"active": "活动",
|
||||
"addContact": "添加联系人",
|
||||
"all": "全部",
|
||||
"autoResolve": "自动解析",
|
||||
"clearAllLabel": "清除全部",
|
||||
"clearRelays": "清除中继",
|
||||
"clearFilters": "清除过滤器",
|
||||
"clearRoute": "清除路由",
|
||||
"clearMessages": "清除消息",
|
||||
"clearScale": "清除比例",
|
||||
"clearDiscoveries": "清除发现",
|
||||
"clearOnlineTraceDatabase": "清除在线追踪数据库",
|
||||
"clearAllChannels": "清除所有频道",
|
||||
"clearAllContacts": "清除所有联系人",
|
||||
"clearChannels": "清除频道",
|
||||
"clearContacts": "清除联系人",
|
||||
"clearPathOnMaxRetry": "最大重试时清除路径",
|
||||
"create": "创建",
|
||||
"custom": "自定义",
|
||||
"defaultValue": "默认",
|
||||
"duplicate": "复制",
|
||||
"editName": "编辑名称",
|
||||
"open": "打开",
|
||||
"paste": "粘贴",
|
||||
"preview": "预览",
|
||||
"remove": "移除",
|
||||
"rename": "重命名",
|
||||
"resolveAll": "解析全部",
|
||||
"send": "发送",
|
||||
"sendAnyway": "仍然发送",
|
||||
"share": "分享",
|
||||
"shareContact": "分享联系人",
|
||||
"trace": "追踪",
|
||||
"use": "使用",
|
||||
"useSelectedFrequency": "使用选定频率",
|
||||
"discovery": "发现",
|
||||
"discoverRepeaters": "发现中继器",
|
||||
"discoverSensors": "发现传感器",
|
||||
"repeaterDiscoverySent": "中继器发现已发送",
|
||||
"sensorDiscoverySent": "传感器发现已发送",
|
||||
"clearedPendingDiscoveries": "已清除待处理的发现。",
|
||||
"autoDiscovery": "自动发现",
|
||||
"enableAutomaticAdding": "启用自动添加",
|
||||
"autoaddRepeaters": "自动添加中继器",
|
||||
"autoaddRoomServers": "自动添加房间服务器",
|
||||
"autoaddSensors": "自动添加传感器",
|
||||
"autoaddUsers": "自动添加用户",
|
||||
"overwriteOldestWhenFull": "满时覆盖最旧的",
|
||||
"storage": "存储",
|
||||
"dangerZone": "危险区域",
|
||||
"profiles": "配置文件",
|
||||
"favourites": "收藏夹",
|
||||
"sensors": "传感器",
|
||||
"others": "其他",
|
||||
"gpsModule": "GPS 模块",
|
||||
"liveTraffic": "实时流量",
|
||||
"repeatersMap": "中继器地图",
|
||||
"spectrumScan": "频谱扫描",
|
||||
"blePacketLogs": "BLE 数据包日志",
|
||||
"onlineTraceDatabase": "在线追踪数据库",
|
||||
"routePathByteSize": "路由路径字节大小",
|
||||
"messageNotifications": "消息通知",
|
||||
"sarAlerts": "SAR 警报",
|
||||
"discoveryNotifications": "发现通知",
|
||||
"updateNotifications": "更新通知",
|
||||
"muteWhileAppIsOpen": "应用打开时静音",
|
||||
"disableContacts": "禁用联系人",
|
||||
"enableSensorsTab": "启用传感器标签",
|
||||
"enableProfiles": "启用配置文件",
|
||||
"autoRouteRotation": "自动路由轮换",
|
||||
"nearestRepeaterFallback": "最近中继器回退",
|
||||
"deleteAllStoredMessageHistory": "删除所有存储的消息历史",
|
||||
"messageFontSize": "消息字体大小",
|
||||
"rotateMapWithHeading": "随航向旋转地图",
|
||||
"showMapDebugInfo": "显示地图调试信息",
|
||||
"openMapInFullscreen": "全屏打开地图",
|
||||
"showSarMarkersLabel": "显示 SAR 标记",
|
||||
"displaySarMarkersOnTheMainMap": "在主地图上显示 SAR 标记",
|
||||
"showAllContactTrailsLabel": "显示所有联系人轨迹",
|
||||
"hideRepeatersOnMap": "在地图上隐藏中继器",
|
||||
"setMapScale": "设置地图比例",
|
||||
"customMapScaleSaved": "自定义地图比例已保存",
|
||||
"voiceBitrate": "语音比特率",
|
||||
"voiceCompressor": "语音压缩器",
|
||||
"balancesQuietAndLoudSpeechLevels": "平衡安静和响亮的语音级别",
|
||||
"voiceLimiter": "语音限制器",
|
||||
"preventsClippingPeaksBeforeEncoding": "防止编码前的削波",
|
||||
"micAutoGain": "麦克风自动增益",
|
||||
"letsTheRecorderAdjustInputLevel": "让录音器调整输入级别",
|
||||
"echoCancellation": "回声消除",
|
||||
"noiseSuppression": "噪音抑制",
|
||||
"trimSilenceInVoiceMessages": "修剪语音消息中的静音",
|
||||
"compressor": "压缩器",
|
||||
"limiter": "限制器",
|
||||
"autoGain": "自动增益",
|
||||
"echoCancel": "回声",
|
||||
"noiseSuppress": "噪声",
|
||||
"silenceTrim": "静音",
|
||||
"maxImageSize": "最大图片大小",
|
||||
"imageCompression": "图片压缩",
|
||||
"grayscale": "灰度",
|
||||
"ultraMode": "超级模式",
|
||||
"fastPrivateGpsUpdates": "快速私有 GPS 更新",
|
||||
"movementThreshold": "移动阈值",
|
||||
"fastGpsMovementThreshold": "快速 GPS 移动阈值",
|
||||
"fastGpsActiveuseInterval": "快速 GPS 活动使用间隔",
|
||||
"activeuseUpdateInterval": "活动使用更新间隔",
|
||||
"repeatNearbyTraffic": "转发附近流量",
|
||||
"relayThroughRepeatersAcrossTheMesh": "通过网格中的中继器转发",
|
||||
"nearbyOnlyWithoutRepeaterFlooding": "仅附近,无中继器泛洪",
|
||||
"multihop": "多跳",
|
||||
"createProfile": "创建配置文件",
|
||||
"renameProfile": "重命名配置文件",
|
||||
"newProfile": "新配置文件",
|
||||
"manageProfiles": "管理配置文件",
|
||||
"enableProfilesToStartManagingThem": "启用配置文件以开始管理它们。",
|
||||
"openMessage": "打开消息",
|
||||
"jumpToTheRelatedSarMessage": "跳转到相关的 SAR 消息",
|
||||
"removeSarMarker": "移除 SAR 标记",
|
||||
"pleaseSelectADestinationToSendSarMarker": "请选择目标以发送 SAR 标记",
|
||||
"sarMarkerBroadcastToPublicChannel": "SAR 标记已广播到公共频道",
|
||||
"sarMarkerSentToRoom": "SAR 标记已发送到房间",
|
||||
"loadFromGallery": "从图库加载",
|
||||
"replaceImage": "替换图片",
|
||||
"selectFromGallery": "从图库选择",
|
||||
"team": "团队",
|
||||
"found": "已找到",
|
||||
"staging": "集结区",
|
||||
"object": "物体",
|
||||
"quiet": "安静",
|
||||
"moderate": "中等",
|
||||
"busy": "繁忙",
|
||||
"spectrumScanReturnedNoCandidateFrequencies": "频谱扫描未找到候选频率",
|
||||
"searchMessages": "搜索消息",
|
||||
"sendImageFromGallery": "从图库发送图片",
|
||||
"takePhoto": "拍照",
|
||||
"dmOnly": "仅私信",
|
||||
"allMessages": "所有消息",
|
||||
"sendToPublicChannel": "发送到公共频道?",
|
||||
"selectMarkerTypeAndDestination": "选择标记类型和目标",
|
||||
"noDestinationsAvailableLabel": "没有可用的目标",
|
||||
"image": "图片",
|
||||
"format": "格式",
|
||||
"dimensions": "尺寸",
|
||||
"segments": "分段",
|
||||
"transfers": "传输",
|
||||
"downloadedBy": "已下载",
|
||||
"saveDiscoverySettings": "保存发现设置",
|
||||
"savePublicInfo": "保存公共信息",
|
||||
"saveRadioSettings": "保存无线电设置",
|
||||
"savePath": "保存路径",
|
||||
"wipeDeviceData": "擦除设备数据",
|
||||
"wipeDevice": "擦除设备",
|
||||
"destructiveDeviceActions": "破坏性设备操作。",
|
||||
"chooseAPresetOrFinetuneCustomRadioSettings": "选择预设或微调自定义无线电设置。",
|
||||
"chooseTheNameAndLocationThisDeviceShares": "选择此设备共享的名称和位置。",
|
||||
"availableSpaceOnThisDevice": "此设备上的可用空间。",
|
||||
"used": "已使用",
|
||||
"total": "总计",
|
||||
"renameValue": "重命名值",
|
||||
"customizeFields": "自定义字段",
|
||||
"livePreview": "实时预览",
|
||||
"refreshSchedule": "刷新计划",
|
||||
"noResponse": "无响应",
|
||||
"refreshing": "刷新中",
|
||||
"unavailable": "不可用",
|
||||
"pickARelayOrNodeToWatchInSensors": "选择要在传感器中监视的中继或节点。",
|
||||
"publicKeyLabel": "公钥",
|
||||
"alreadyInContacts": "已在联系人中",
|
||||
"connectToADeviceBeforeAddingContacts": "添加联系人前请连接到设备",
|
||||
"fromContacts": "来自联系人",
|
||||
"onlineOnly": "仅在线",
|
||||
"inBoth": "两者中",
|
||||
"source": "来源",
|
||||
"manualRouteEdit": "手动路由编辑",
|
||||
"observedMeshRoute": "观察到的网格路由",
|
||||
"allMessagesCleared": "所有消息已清除",
|
||||
"onlineTraceDatabaseCleared": "在线追踪数据库已清除",
|
||||
"packetLogsCleared": "数据包日志已清除",
|
||||
"hexDataCopiedToClipboard": "十六进制数据已复制到剪贴板",
|
||||
"developerModeEnabled": "开发者模式已启用",
|
||||
"developerModeDisabled": "开发者模式已禁用",
|
||||
"clipboardIsEmpty": "剪贴板为空",
|
||||
"contactImported": "联系人已导入",
|
||||
"contactLinkCopiedToClipboard": "联系人链接已复制到剪贴板",
|
||||
"failedToExportContact": "导出联系人失败",
|
||||
"noLogsToExport": "没有日志可导出",
|
||||
"exportAsCsv": "导出为 CSV",
|
||||
"exportAsText": "导出为文本",
|
||||
"receivedRfc3339": "收到 (RFC3339)",
|
||||
"buildTime": "构建时间",
|
||||
"downloadUrlNotAvailable": "下载链接不可用",
|
||||
"cannotOpenDownloadUrl": "无法打开下载链接",
|
||||
"updateCheckIsOnlyAvailableOnAndroid": "更新检查仅在 Android 上可用",
|
||||
"youAreRunningTheLatestVersion": "您正在使用最新版本",
|
||||
"updateAvailableButDownloadUrlNotFound": "有更新可用但未找到下载链接",
|
||||
"startTictactoe": "开始井字棋",
|
||||
"tictactoeUnavailable": "井字棋不可用",
|
||||
"tictactoeOpponentUnknown": "井字棋:对手未知",
|
||||
"tictactoeWaitingForStart": "井字棋:等待开始",
|
||||
"acceptsShareLinks": "接受分享链接",
|
||||
"supportsRawHex": "支持原始十六进制",
|
||||
"clipboardfriendly": "剪贴板友好",
|
||||
"captured": "已捕获",
|
||||
"size": "大小",
|
||||
"noCustomChannelsToClear": "没有自定义频道可清除。",
|
||||
"noDeviceContactsToClear": "没有设备联系人可清除。"
|
||||
}
|
||||
|
||||
@@ -1,10 +1,14 @@
|
||||
import 'dart:io' show Platform;
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/foundation.dart'
|
||||
show TargetPlatform, defaultTargetPlatform, kIsWeb;
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_localizations/flutter_localizations.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import 'package:geolocator/geolocator.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
import 'package:wakelock_plus/wakelock_plus.dart';
|
||||
import 'providers/connection_provider.dart';
|
||||
import 'providers/contacts_provider.dart';
|
||||
import 'providers/messages_provider.dart';
|
||||
@@ -19,14 +23,21 @@ import 'services/voice_codec_service.dart';
|
||||
import 'services/voice_player_service.dart';
|
||||
import 'services/notification_service.dart';
|
||||
import 'services/locale_preferences.dart';
|
||||
import 'services/mesh_map_nodes_service.dart';
|
||||
import 'services/profile_manager.dart';
|
||||
import 'services/profile_workspace_coordinator.dart';
|
||||
import 'services/profiles_feature_service.dart';
|
||||
import 'services/update_checker_service.dart';
|
||||
import 'services/wizard_preferences.dart';
|
||||
import 'screens/discovery_screen.dart';
|
||||
import 'screens/home_screen.dart';
|
||||
import 'screens/welcome_wizard_screen.dart';
|
||||
import 'theme/app_theme.dart';
|
||||
import 'l10n/app_localizations.dart';
|
||||
|
||||
void main() {
|
||||
Future<void> main() async {
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
await WakelockPlus.enable();
|
||||
runApp(const MeshCoreSarApp());
|
||||
}
|
||||
|
||||
@@ -38,11 +49,13 @@ class MeshCoreSarApp extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _MeshCoreSarAppState extends State<MeshCoreSarApp> {
|
||||
final GlobalKey<NavigatorState> _navigatorKey = GlobalKey<NavigatorState>();
|
||||
AppThemeMode _themeMode = AppThemeMode.system;
|
||||
Locale? _locale;
|
||||
bool _isInitialized = false;
|
||||
bool _shouldShowPermissionDialog = false;
|
||||
bool _wizardCompleted = true; // Will be updated in _initializeApp()
|
||||
String? _pendingNotificationPayload;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
@@ -51,6 +64,14 @@ class _MeshCoreSarAppState extends State<MeshCoreSarApp> {
|
||||
}
|
||||
|
||||
Future<void> _initializeApp() async {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
final profilesEnabled = await ProfilesFeatureService.isEnabled();
|
||||
final activeProfileId =
|
||||
prefs.getString(ProfileManager.activeProfileIdKey) ?? 'default';
|
||||
await ProfileStorageScope.bootstrap(
|
||||
profilesEnabled: profilesEnabled,
|
||||
activeProfileId: activeProfileId,
|
||||
);
|
||||
await _loadThemePreference();
|
||||
await _loadLocalePreference();
|
||||
|
||||
@@ -62,6 +83,7 @@ class _MeshCoreSarAppState extends State<MeshCoreSarApp> {
|
||||
|
||||
// Set up notification tap handler for update notifications
|
||||
NotificationService().onNotificationTapped = _handleNotificationTap;
|
||||
_pendingNotificationPayload = NotificationService().consumeLaunchPayload();
|
||||
|
||||
// Check if we need to request location permissions
|
||||
await _checkLocationPermissions();
|
||||
@@ -70,10 +92,21 @@ class _MeshCoreSarAppState extends State<MeshCoreSarApp> {
|
||||
// Shows notification if update is available
|
||||
_checkForUpdates();
|
||||
|
||||
// Refresh remote mesh node cache in background when the app starts.
|
||||
unawaited(MeshMapNodesService.syncInBackgroundIfStale());
|
||||
|
||||
setState(() {
|
||||
_wizardCompleted = wizardCompleted;
|
||||
_isInitialized = true;
|
||||
});
|
||||
|
||||
if (_pendingNotificationPayload != null) {
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
final payload = _pendingNotificationPayload;
|
||||
_pendingNotificationPayload = null;
|
||||
_handleNotificationTap(payload);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// Handle notification tap
|
||||
@@ -82,10 +115,27 @@ class _MeshCoreSarAppState extends State<MeshCoreSarApp> {
|
||||
|
||||
debugPrint('[Main] Notification tapped: $payload');
|
||||
|
||||
if (!_wizardCompleted) {
|
||||
_pendingNotificationPayload = payload;
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle update notification tap
|
||||
if (payload.startsWith('update:')) {
|
||||
final downloadUrl = payload.substring(7); // Remove 'update:' prefix
|
||||
_launchUpdateDownload(downloadUrl);
|
||||
return;
|
||||
}
|
||||
|
||||
if (payload.startsWith('discovery:')) {
|
||||
final navigator = _navigatorKey.currentState;
|
||||
if (navigator == null) {
|
||||
_pendingNotificationPayload = payload;
|
||||
return;
|
||||
}
|
||||
navigator.push(
|
||||
MaterialPageRoute(builder: (context) => const DiscoveryScreen()),
|
||||
);
|
||||
}
|
||||
// SAR and message notifications handled by their respective providers
|
||||
}
|
||||
@@ -158,7 +208,7 @@ class _MeshCoreSarAppState extends State<MeshCoreSarApp> {
|
||||
/// Shows notification if update is available
|
||||
Future<void> _checkForUpdates() async {
|
||||
// Only check for updates on Android
|
||||
if (!Platform.isAndroid) {
|
||||
if (kIsWeb || defaultTargetPlatform != TargetPlatform.android) {
|
||||
debugPrint('[UpdateChecker] Skipping update check (not Android)');
|
||||
return;
|
||||
}
|
||||
@@ -235,6 +285,13 @@ class _MeshCoreSarAppState extends State<MeshCoreSarApp> {
|
||||
),
|
||||
ChangeNotifierProvider(create: (_) => ChannelsProvider()),
|
||||
ChangeNotifierProvider(create: (_) => SensorsProvider()),
|
||||
ChangeNotifierProvider(
|
||||
create: (_) {
|
||||
final manager = ProfileManager();
|
||||
manager.initialize();
|
||||
return manager;
|
||||
},
|
||||
),
|
||||
|
||||
// Voice provider (packet reassembly + playback)
|
||||
ChangeNotifierProvider(
|
||||
@@ -287,6 +344,39 @@ class _MeshCoreSarAppState extends State<MeshCoreSarApp> {
|
||||
imageProvider: context.read<ip.ImageProvider>(),
|
||||
),
|
||||
),
|
||||
ProxyProvider6<
|
||||
ProfileManager,
|
||||
AppProvider,
|
||||
ConnectionProvider,
|
||||
ContactsProvider,
|
||||
MessagesProvider,
|
||||
MapProvider,
|
||||
ProfileWorkspaceCoordinator
|
||||
>(
|
||||
update:
|
||||
(
|
||||
context,
|
||||
profileManager,
|
||||
appProvider,
|
||||
connectionProvider,
|
||||
contactsProvider,
|
||||
messagesProvider,
|
||||
mapProvider,
|
||||
previous,
|
||||
) => ProfileWorkspaceCoordinator(
|
||||
profileManager: profileManager,
|
||||
connectionProvider: connectionProvider,
|
||||
contactsProvider: contactsProvider,
|
||||
messagesProvider: messagesProvider,
|
||||
sensorsProvider: context.read<SensorsProvider>(),
|
||||
mapProvider: mapProvider,
|
||||
drawingProvider: context.read<DrawingProvider>(),
|
||||
channelsProvider: context.read<ChannelsProvider>(),
|
||||
voiceProvider: context.read<VoiceProvider>(),
|
||||
imageProvider: context.read<ip.ImageProvider>(),
|
||||
appProvider: appProvider,
|
||||
),
|
||||
),
|
||||
],
|
||||
child: _buildMaterialApp(),
|
||||
);
|
||||
@@ -297,6 +387,7 @@ class _MeshCoreSarAppState extends State<MeshCoreSarApp> {
|
||||
builder: (context) {
|
||||
final systemBrightness = MediaQuery.platformBrightnessOf(context);
|
||||
final materialApp = MaterialApp(
|
||||
navigatorKey: _navigatorKey,
|
||||
key: ValueKey<String?>(
|
||||
'${_locale?.languageCode ?? 'system'}_${_themeMode.name}',
|
||||
),
|
||||
@@ -324,7 +415,7 @@ class _MeshCoreSarAppState extends State<MeshCoreSarApp> {
|
||||
|
||||
// Wrap in SafeArea for Android only to fix navigation bar overlap (API ≥36)
|
||||
// iOS doesn't need SafeArea wrapping (causes extra black space at bottom)
|
||||
if (Platform.isAndroid) {
|
||||
if (!kIsWeb && defaultTargetPlatform == TargetPlatform.android) {
|
||||
return SafeArea(
|
||||
left: false,
|
||||
right: false,
|
||||
|
||||
@@ -57,9 +57,7 @@ class Channel {
|
||||
} else {
|
||||
// Normal channel: require explicit secret
|
||||
if (explicitSecret == null || explicitSecret.length != 16) {
|
||||
throw ArgumentError(
|
||||
'Normal channels require a 16-byte secret',
|
||||
);
|
||||
throw ArgumentError('Normal channels require a 16-byte secret');
|
||||
}
|
||||
return Channel(
|
||||
index: index,
|
||||
@@ -78,6 +76,18 @@ class Channel {
|
||||
return Uint8List.fromList(digest.bytes.sublist(0, 16));
|
||||
}
|
||||
|
||||
static bool isHashChannelName(String channelName) {
|
||||
return channelName.trim().startsWith('#');
|
||||
}
|
||||
|
||||
static String pskBase64ForHashChannelName(String channelName) {
|
||||
final normalized = channelName.trim();
|
||||
if (!isHashChannelName(normalized)) {
|
||||
throw ArgumentError('Only #channels can export derived psk_base64');
|
||||
}
|
||||
return base64.encode(_generateHashChannelSecret(normalized));
|
||||
}
|
||||
|
||||
/// Create the default public channel (channel 0)
|
||||
/// Uses the well-known pre-shared key from MeshCore
|
||||
factory Channel.publicChannel() {
|
||||
@@ -85,8 +95,22 @@ class Channel {
|
||||
index: 0,
|
||||
name: 'Public Channel',
|
||||
secret: Uint8List.fromList([
|
||||
0x8b, 0x33, 0x87, 0xe9, 0xc5, 0xcd, 0xea, 0x6a,
|
||||
0xc9, 0xe5, 0xed, 0xba, 0xa1, 0x15, 0xcd, 0x72,
|
||||
0x8b,
|
||||
0x33,
|
||||
0x87,
|
||||
0xe9,
|
||||
0xc5,
|
||||
0xcd,
|
||||
0xea,
|
||||
0x6a,
|
||||
0xc9,
|
||||
0xe5,
|
||||
0xed,
|
||||
0xba,
|
||||
0xa1,
|
||||
0x15,
|
||||
0xcd,
|
||||
0x72,
|
||||
]),
|
||||
flags: null,
|
||||
);
|
||||
@@ -95,6 +119,9 @@ class Channel {
|
||||
/// Check if this is a hash-based channel (name starts with '#')
|
||||
bool get isHashChannel => name.startsWith('#');
|
||||
|
||||
/// Base64-encoded PSK for sharing with firmware CLI and related tooling.
|
||||
String get pskBase64 => base64.encode(secret);
|
||||
|
||||
/// Display name for the channel
|
||||
/// Returns "Public" for channel 0, otherwise returns the custom name or "Channel N"
|
||||
String get displayName {
|
||||
@@ -131,12 +158,7 @@ class Channel {
|
||||
}
|
||||
|
||||
/// Create a copy with modified fields
|
||||
Channel copyWith({
|
||||
int? index,
|
||||
String? name,
|
||||
Uint8List? secret,
|
||||
int? flags,
|
||||
}) {
|
||||
Channel copyWith({int? index, String? name, Uint8List? secret, int? flags}) {
|
||||
return Channel(
|
||||
index: index ?? this.index,
|
||||
name: name ?? this.name,
|
||||
|
||||
523
lib/models/config_profile.dart
Normal file
523
lib/models/config_profile.dart
Normal file
@@ -0,0 +1,523 @@
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'channel.dart';
|
||||
|
||||
@immutable
|
||||
class DeviceConfigProfileSection {
|
||||
final int? frequencyKhz;
|
||||
final int? bandwidth;
|
||||
final int? spreadingFactor;
|
||||
final int? codingRate;
|
||||
final bool? repeatEnabled;
|
||||
final int? txPower;
|
||||
final int? telemetryModes;
|
||||
final int? advertLocationPolicy;
|
||||
final int? multiAcks;
|
||||
final bool? manualAddContacts;
|
||||
final bool? autoAddUsers;
|
||||
final bool? autoAddRepeaters;
|
||||
final bool? autoAddRoomServers;
|
||||
final bool? autoAddSensors;
|
||||
final bool? autoAddOverwriteOldest;
|
||||
final double? publicLatitude;
|
||||
final double? publicLongitude;
|
||||
|
||||
const DeviceConfigProfileSection({
|
||||
this.frequencyKhz,
|
||||
this.bandwidth,
|
||||
this.spreadingFactor,
|
||||
this.codingRate,
|
||||
this.repeatEnabled,
|
||||
this.txPower,
|
||||
this.telemetryModes,
|
||||
this.advertLocationPolicy,
|
||||
this.multiAcks,
|
||||
this.manualAddContacts,
|
||||
this.autoAddUsers,
|
||||
this.autoAddRepeaters,
|
||||
this.autoAddRoomServers,
|
||||
this.autoAddSensors,
|
||||
this.autoAddOverwriteOldest,
|
||||
this.publicLatitude,
|
||||
this.publicLongitude,
|
||||
});
|
||||
|
||||
bool get isEmpty =>
|
||||
frequencyKhz == null &&
|
||||
bandwidth == null &&
|
||||
spreadingFactor == null &&
|
||||
codingRate == null &&
|
||||
repeatEnabled == null &&
|
||||
txPower == null &&
|
||||
telemetryModes == null &&
|
||||
advertLocationPolicy == null &&
|
||||
multiAcks == null &&
|
||||
manualAddContacts == null &&
|
||||
autoAddUsers == null &&
|
||||
autoAddRepeaters == null &&
|
||||
autoAddRoomServers == null &&
|
||||
autoAddSensors == null &&
|
||||
autoAddOverwriteOldest == null &&
|
||||
publicLatitude == null &&
|
||||
publicLongitude == null;
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
'frequencyKhz': frequencyKhz,
|
||||
'bandwidth': bandwidth,
|
||||
'spreadingFactor': spreadingFactor,
|
||||
'codingRate': codingRate,
|
||||
'repeatEnabled': repeatEnabled,
|
||||
'txPower': txPower,
|
||||
'telemetryModes': telemetryModes,
|
||||
'advertLocationPolicy': advertLocationPolicy,
|
||||
'multiAcks': multiAcks,
|
||||
'manualAddContacts': manualAddContacts,
|
||||
'autoAddUsers': autoAddUsers,
|
||||
'autoAddRepeaters': autoAddRepeaters,
|
||||
'autoAddRoomServers': autoAddRoomServers,
|
||||
'autoAddSensors': autoAddSensors,
|
||||
'autoAddOverwriteOldest': autoAddOverwriteOldest,
|
||||
'publicLatitude': publicLatitude,
|
||||
'publicLongitude': publicLongitude,
|
||||
};
|
||||
|
||||
factory DeviceConfigProfileSection.fromJson(Map<String, dynamic> json) {
|
||||
return DeviceConfigProfileSection(
|
||||
frequencyKhz: json['frequencyKhz'] as int?,
|
||||
bandwidth: json['bandwidth'] as int?,
|
||||
spreadingFactor: json['spreadingFactor'] as int?,
|
||||
codingRate: json['codingRate'] as int?,
|
||||
repeatEnabled: json['repeatEnabled'] as bool?,
|
||||
txPower: json['txPower'] as int?,
|
||||
telemetryModes: json['telemetryModes'] as int?,
|
||||
advertLocationPolicy: json['advertLocationPolicy'] as int?,
|
||||
multiAcks: json['multiAcks'] as int?,
|
||||
manualAddContacts: json['manualAddContacts'] as bool?,
|
||||
autoAddUsers: json['autoAddUsers'] as bool?,
|
||||
autoAddRepeaters: json['autoAddRepeaters'] as bool?,
|
||||
autoAddRoomServers: json['autoAddRoomServers'] as bool?,
|
||||
autoAddSensors: json['autoAddSensors'] as bool?,
|
||||
autoAddOverwriteOldest: json['autoAddOverwriteOldest'] as bool?,
|
||||
publicLatitude: (json['publicLatitude'] as num?)?.toDouble(),
|
||||
publicLongitude: (json['publicLongitude'] as num?)?.toDouble(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@immutable
|
||||
class AppSettingsProfileSection {
|
||||
final bool? mapEnabled;
|
||||
final bool? contactsEnabled;
|
||||
final bool? sensorsEnabled;
|
||||
final bool? voiceSilenceTrimmingEnabled;
|
||||
final bool? voiceBandPassFilterEnabled;
|
||||
final bool? voiceCompressorEnabled;
|
||||
final bool? voiceLimiterEnabled;
|
||||
final bool? voiceAutoGainEnabled;
|
||||
final bool? voiceEchoCancellationEnabled;
|
||||
final bool? voiceNoiseSuppressionEnabled;
|
||||
final double? messageFontScale;
|
||||
final bool? autoRouteRotationEnabled;
|
||||
final bool? clearPathOnMaxRetry;
|
||||
final bool? nearestRelayFallbackEnabled;
|
||||
final int? voiceBitrate;
|
||||
final int? routeHashSize;
|
||||
final int? imageMaxSize;
|
||||
final int? imageCompression;
|
||||
final bool? imageGrayscale;
|
||||
final bool? imageUltraMode;
|
||||
final bool? showRxTxIndicators;
|
||||
final bool? fastLocationUpdatesEnabled;
|
||||
final double? fastLocationMovementThresholdMeters;
|
||||
final int? fastLocationActiveCadenceSeconds;
|
||||
|
||||
const AppSettingsProfileSection({
|
||||
this.mapEnabled,
|
||||
this.contactsEnabled,
|
||||
this.sensorsEnabled,
|
||||
this.voiceSilenceTrimmingEnabled,
|
||||
this.voiceBandPassFilterEnabled,
|
||||
this.voiceCompressorEnabled,
|
||||
this.voiceLimiterEnabled,
|
||||
this.voiceAutoGainEnabled,
|
||||
this.voiceEchoCancellationEnabled,
|
||||
this.voiceNoiseSuppressionEnabled,
|
||||
this.messageFontScale,
|
||||
this.autoRouteRotationEnabled,
|
||||
this.clearPathOnMaxRetry,
|
||||
this.nearestRelayFallbackEnabled,
|
||||
this.voiceBitrate,
|
||||
this.routeHashSize,
|
||||
this.imageMaxSize,
|
||||
this.imageCompression,
|
||||
this.imageGrayscale,
|
||||
this.imageUltraMode,
|
||||
this.showRxTxIndicators,
|
||||
this.fastLocationUpdatesEnabled,
|
||||
this.fastLocationMovementThresholdMeters,
|
||||
this.fastLocationActiveCadenceSeconds,
|
||||
});
|
||||
|
||||
bool get isEmpty =>
|
||||
mapEnabled == null &&
|
||||
contactsEnabled == null &&
|
||||
sensorsEnabled == null &&
|
||||
voiceSilenceTrimmingEnabled == null &&
|
||||
voiceBandPassFilterEnabled == null &&
|
||||
voiceCompressorEnabled == null &&
|
||||
voiceLimiterEnabled == null &&
|
||||
voiceAutoGainEnabled == null &&
|
||||
voiceEchoCancellationEnabled == null &&
|
||||
voiceNoiseSuppressionEnabled == null &&
|
||||
messageFontScale == null &&
|
||||
autoRouteRotationEnabled == null &&
|
||||
clearPathOnMaxRetry == null &&
|
||||
nearestRelayFallbackEnabled == null &&
|
||||
voiceBitrate == null &&
|
||||
routeHashSize == null &&
|
||||
imageMaxSize == null &&
|
||||
imageCompression == null &&
|
||||
imageGrayscale == null &&
|
||||
imageUltraMode == null &&
|
||||
showRxTxIndicators == null &&
|
||||
fastLocationUpdatesEnabled == null &&
|
||||
fastLocationMovementThresholdMeters == null &&
|
||||
fastLocationActiveCadenceSeconds == null;
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
'mapEnabled': mapEnabled,
|
||||
'contactsEnabled': contactsEnabled,
|
||||
'sensorsEnabled': sensorsEnabled,
|
||||
'voiceSilenceTrimmingEnabled': voiceSilenceTrimmingEnabled,
|
||||
'voiceBandPassFilterEnabled': voiceBandPassFilterEnabled,
|
||||
'voiceCompressorEnabled': voiceCompressorEnabled,
|
||||
'voiceLimiterEnabled': voiceLimiterEnabled,
|
||||
'voiceAutoGainEnabled': voiceAutoGainEnabled,
|
||||
'voiceEchoCancellationEnabled': voiceEchoCancellationEnabled,
|
||||
'voiceNoiseSuppressionEnabled': voiceNoiseSuppressionEnabled,
|
||||
'messageFontScale': messageFontScale,
|
||||
'autoRouteRotationEnabled': autoRouteRotationEnabled,
|
||||
'clearPathOnMaxRetry': clearPathOnMaxRetry,
|
||||
'nearestRelayFallbackEnabled': nearestRelayFallbackEnabled,
|
||||
'voiceBitrate': voiceBitrate,
|
||||
'routeHashSize': routeHashSize,
|
||||
'imageMaxSize': imageMaxSize,
|
||||
'imageCompression': imageCompression,
|
||||
'imageGrayscale': imageGrayscale,
|
||||
'imageUltraMode': imageUltraMode,
|
||||
'showRxTxIndicators': showRxTxIndicators,
|
||||
'fastLocationUpdatesEnabled': fastLocationUpdatesEnabled,
|
||||
'fastLocationMovementThresholdMeters': fastLocationMovementThresholdMeters,
|
||||
'fastLocationActiveCadenceSeconds': fastLocationActiveCadenceSeconds,
|
||||
};
|
||||
|
||||
factory AppSettingsProfileSection.fromJson(Map<String, dynamic> json) {
|
||||
return AppSettingsProfileSection(
|
||||
mapEnabled: json['mapEnabled'] as bool?,
|
||||
contactsEnabled: json['contactsEnabled'] as bool?,
|
||||
sensorsEnabled: json['sensorsEnabled'] as bool?,
|
||||
voiceSilenceTrimmingEnabled: json['voiceSilenceTrimmingEnabled'] as bool?,
|
||||
voiceBandPassFilterEnabled: json['voiceBandPassFilterEnabled'] as bool?,
|
||||
voiceCompressorEnabled: json['voiceCompressorEnabled'] as bool?,
|
||||
voiceLimiterEnabled: json['voiceLimiterEnabled'] as bool?,
|
||||
voiceAutoGainEnabled: json['voiceAutoGainEnabled'] as bool?,
|
||||
voiceEchoCancellationEnabled:
|
||||
json['voiceEchoCancellationEnabled'] as bool?,
|
||||
voiceNoiseSuppressionEnabled:
|
||||
json['voiceNoiseSuppressionEnabled'] as bool?,
|
||||
messageFontScale: (json['messageFontScale'] as num?)?.toDouble(),
|
||||
autoRouteRotationEnabled: json['autoRouteRotationEnabled'] as bool?,
|
||||
clearPathOnMaxRetry: json['clearPathOnMaxRetry'] as bool?,
|
||||
nearestRelayFallbackEnabled: json['nearestRelayFallbackEnabled'] as bool?,
|
||||
voiceBitrate: json['voiceBitrate'] as int?,
|
||||
routeHashSize: json['routeHashSize'] as int?,
|
||||
imageMaxSize: json['imageMaxSize'] as int?,
|
||||
imageCompression: json['imageCompression'] as int?,
|
||||
imageGrayscale: json['imageGrayscale'] as bool?,
|
||||
imageUltraMode: json['imageUltraMode'] as bool?,
|
||||
showRxTxIndicators: json['showRxTxIndicators'] as bool?,
|
||||
fastLocationUpdatesEnabled: json['fastLocationUpdatesEnabled'] as bool?,
|
||||
fastLocationMovementThresholdMeters:
|
||||
(json['fastLocationMovementThresholdMeters'] as num?)?.toDouble(),
|
||||
fastLocationActiveCadenceSeconds:
|
||||
json['fastLocationActiveCadenceSeconds'] as int?,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@immutable
|
||||
class MapWorkspaceProfileSection {
|
||||
final Map<String, dynamic>? mapPrefs;
|
||||
final List<Map<String, dynamic>> drawings;
|
||||
final Map<String, dynamic>? currentTrail;
|
||||
final List<Map<String, dynamic>> trailHistory;
|
||||
final Map<String, dynamic>? importedTrail;
|
||||
final bool? isTrailVisible;
|
||||
final bool? showCadastralOverlay;
|
||||
final bool? showForestRoadsOverlay;
|
||||
final bool? showHikingTrailsOverlay;
|
||||
final bool? showMainRoadsOverlay;
|
||||
final bool? showHouseNumbersOverlay;
|
||||
final bool? showFireHazardZonesOverlay;
|
||||
final bool? showHistoricalFiresOverlay;
|
||||
final bool? showFirebreaksOverlay;
|
||||
final bool? showKrasFireZonesOverlay;
|
||||
final bool? showPlaceNamesOverlay;
|
||||
final bool? showMunicipalityBordersOverlay;
|
||||
final bool? hideRepeatersOnMap;
|
||||
|
||||
const MapWorkspaceProfileSection({
|
||||
this.mapPrefs,
|
||||
this.drawings = const [],
|
||||
this.currentTrail,
|
||||
this.trailHistory = const [],
|
||||
this.importedTrail,
|
||||
this.isTrailVisible,
|
||||
this.showCadastralOverlay,
|
||||
this.showForestRoadsOverlay,
|
||||
this.showHikingTrailsOverlay,
|
||||
this.showMainRoadsOverlay,
|
||||
this.showHouseNumbersOverlay,
|
||||
this.showFireHazardZonesOverlay,
|
||||
this.showHistoricalFiresOverlay,
|
||||
this.showFirebreaksOverlay,
|
||||
this.showKrasFireZonesOverlay,
|
||||
this.showPlaceNamesOverlay,
|
||||
this.showMunicipalityBordersOverlay,
|
||||
this.hideRepeatersOnMap,
|
||||
});
|
||||
|
||||
bool get isEmpty =>
|
||||
mapPrefs == null &&
|
||||
drawings.isEmpty &&
|
||||
currentTrail == null &&
|
||||
trailHistory.isEmpty &&
|
||||
importedTrail == null &&
|
||||
isTrailVisible == null &&
|
||||
showCadastralOverlay == null &&
|
||||
showForestRoadsOverlay == null &&
|
||||
showHikingTrailsOverlay == null &&
|
||||
showMainRoadsOverlay == null &&
|
||||
showHouseNumbersOverlay == null &&
|
||||
showFireHazardZonesOverlay == null &&
|
||||
showHistoricalFiresOverlay == null &&
|
||||
showFirebreaksOverlay == null &&
|
||||
showKrasFireZonesOverlay == null &&
|
||||
showPlaceNamesOverlay == null &&
|
||||
showMunicipalityBordersOverlay == null &&
|
||||
hideRepeatersOnMap == null;
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
'mapPrefs': mapPrefs,
|
||||
'drawings': drawings,
|
||||
'currentTrail': currentTrail,
|
||||
'trailHistory': trailHistory,
|
||||
'importedTrail': importedTrail,
|
||||
'isTrailVisible': isTrailVisible,
|
||||
'showCadastralOverlay': showCadastralOverlay,
|
||||
'showForestRoadsOverlay': showForestRoadsOverlay,
|
||||
'showHikingTrailsOverlay': showHikingTrailsOverlay,
|
||||
'showMainRoadsOverlay': showMainRoadsOverlay,
|
||||
'showHouseNumbersOverlay': showHouseNumbersOverlay,
|
||||
'showFireHazardZonesOverlay': showFireHazardZonesOverlay,
|
||||
'showHistoricalFiresOverlay': showHistoricalFiresOverlay,
|
||||
'showFirebreaksOverlay': showFirebreaksOverlay,
|
||||
'showKrasFireZonesOverlay': showKrasFireZonesOverlay,
|
||||
'showPlaceNamesOverlay': showPlaceNamesOverlay,
|
||||
'showMunicipalityBordersOverlay': showMunicipalityBordersOverlay,
|
||||
'hideRepeatersOnMap': hideRepeatersOnMap,
|
||||
};
|
||||
|
||||
factory MapWorkspaceProfileSection.fromJson(Map<String, dynamic> json) {
|
||||
List<Map<String, dynamic>> decodeList(String key) {
|
||||
final raw = json[key] as List<dynamic>? ?? const [];
|
||||
return raw.whereType<Map<String, dynamic>>().toList();
|
||||
}
|
||||
|
||||
return MapWorkspaceProfileSection(
|
||||
mapPrefs: json['mapPrefs'] as Map<String, dynamic>?,
|
||||
drawings: decodeList('drawings'),
|
||||
currentTrail: json['currentTrail'] as Map<String, dynamic>?,
|
||||
trailHistory: decodeList('trailHistory'),
|
||||
importedTrail: json['importedTrail'] as Map<String, dynamic>?,
|
||||
isTrailVisible: json['isTrailVisible'] as bool?,
|
||||
showCadastralOverlay: json['showCadastralOverlay'] as bool?,
|
||||
showForestRoadsOverlay: json['showForestRoadsOverlay'] as bool?,
|
||||
showHikingTrailsOverlay: json['showHikingTrailsOverlay'] as bool?,
|
||||
showMainRoadsOverlay: json['showMainRoadsOverlay'] as bool?,
|
||||
showHouseNumbersOverlay: json['showHouseNumbersOverlay'] as bool?,
|
||||
showFireHazardZonesOverlay: json['showFireHazardZonesOverlay'] as bool?,
|
||||
showHistoricalFiresOverlay: json['showHistoricalFiresOverlay'] as bool?,
|
||||
showFirebreaksOverlay: json['showFirebreaksOverlay'] as bool?,
|
||||
showKrasFireZonesOverlay: json['showKrasFireZonesOverlay'] as bool?,
|
||||
showPlaceNamesOverlay: json['showPlaceNamesOverlay'] as bool?,
|
||||
showMunicipalityBordersOverlay:
|
||||
json['showMunicipalityBordersOverlay'] as bool?,
|
||||
hideRepeatersOnMap: json['hideRepeatersOnMap'] as bool?,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@immutable
|
||||
class ConfigProfileSections {
|
||||
final DeviceConfigProfileSection? deviceConfig;
|
||||
final AppSettingsProfileSection? appSettings;
|
||||
final MapWorkspaceProfileSection? mapWorkspace;
|
||||
final List<Channel> channels;
|
||||
|
||||
const ConfigProfileSections({
|
||||
this.deviceConfig,
|
||||
this.appSettings,
|
||||
this.mapWorkspace,
|
||||
this.channels = const [],
|
||||
});
|
||||
|
||||
bool get isEmpty =>
|
||||
(deviceConfig == null || deviceConfig!.isEmpty) &&
|
||||
(appSettings == null || appSettings!.isEmpty) &&
|
||||
(mapWorkspace == null || mapWorkspace!.isEmpty) &&
|
||||
channels.isEmpty;
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
'deviceConfig': deviceConfig?.toJson(),
|
||||
'appSettings': appSettings?.toJson(),
|
||||
'mapWorkspace': mapWorkspace?.toJson(),
|
||||
'channels': channels.map((channel) => channel.toJson()).toList(),
|
||||
};
|
||||
|
||||
factory ConfigProfileSections.fromJson(Map<String, dynamic> json) {
|
||||
final rawChannels = json['channels'] as List<dynamic>? ?? const [];
|
||||
return ConfigProfileSections(
|
||||
deviceConfig: json['deviceConfig'] is Map<String, dynamic>
|
||||
? DeviceConfigProfileSection.fromJson(
|
||||
json['deviceConfig'] as Map<String, dynamic>,
|
||||
)
|
||||
: null,
|
||||
appSettings: json['appSettings'] is Map<String, dynamic>
|
||||
? AppSettingsProfileSection.fromJson(
|
||||
json['appSettings'] as Map<String, dynamic>,
|
||||
)
|
||||
: null,
|
||||
mapWorkspace: json['mapWorkspace'] is Map<String, dynamic>
|
||||
? MapWorkspaceProfileSection.fromJson(
|
||||
json['mapWorkspace'] as Map<String, dynamic>,
|
||||
)
|
||||
: null,
|
||||
channels: rawChannels
|
||||
.whereType<Map<String, dynamic>>()
|
||||
.map(Channel.fromJson)
|
||||
.toList(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@immutable
|
||||
class ConfigProfile {
|
||||
static const String defaultProfileId = 'default';
|
||||
|
||||
final String id;
|
||||
final String name;
|
||||
final DateTime createdAt;
|
||||
final DateTime updatedAt;
|
||||
final String? notes;
|
||||
final ConfigProfileSections sections;
|
||||
|
||||
const ConfigProfile({
|
||||
required this.id,
|
||||
required this.name,
|
||||
required this.createdAt,
|
||||
required this.updatedAt,
|
||||
required this.sections,
|
||||
this.notes,
|
||||
});
|
||||
|
||||
bool get isDefault => id == defaultProfileId;
|
||||
|
||||
factory ConfigProfile.defaultProfile() {
|
||||
final now = DateTime.now();
|
||||
return ConfigProfile(
|
||||
id: defaultProfileId,
|
||||
name: 'Default',
|
||||
createdAt: now,
|
||||
updatedAt: now,
|
||||
sections: const ConfigProfileSections(),
|
||||
);
|
||||
}
|
||||
|
||||
ConfigProfile copyWith({
|
||||
String? id,
|
||||
String? name,
|
||||
DateTime? createdAt,
|
||||
DateTime? updatedAt,
|
||||
String? notes,
|
||||
ConfigProfileSections? sections,
|
||||
}) {
|
||||
return ConfigProfile(
|
||||
id: id ?? this.id,
|
||||
name: name ?? this.name,
|
||||
createdAt: createdAt ?? this.createdAt,
|
||||
updatedAt: updatedAt ?? this.updatedAt,
|
||||
notes: notes ?? this.notes,
|
||||
sections: sections ?? this.sections,
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
'id': id,
|
||||
'name': name,
|
||||
'createdAt': createdAt.toIso8601String(),
|
||||
'updatedAt': updatedAt.toIso8601String(),
|
||||
'notes': notes,
|
||||
'sections': sections.toJson(),
|
||||
};
|
||||
|
||||
factory ConfigProfile.fromJson(Map<String, dynamic> json) {
|
||||
return ConfigProfile(
|
||||
id: json['id'] as String,
|
||||
name: json['name'] as String,
|
||||
createdAt: DateTime.parse(json['createdAt'] as String),
|
||||
updatedAt: DateTime.parse(json['updatedAt'] as String),
|
||||
notes: json['notes'] as String?,
|
||||
sections: ConfigProfileSections.fromJson(
|
||||
json['sections'] as Map<String, dynamic>? ?? const {},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@immutable
|
||||
class ProfileDiff {
|
||||
final List<String> changedSections;
|
||||
|
||||
const ProfileDiff({required this.changedSections});
|
||||
}
|
||||
|
||||
@immutable
|
||||
class ProfileTransferRecord {
|
||||
final String profileId;
|
||||
final String direction;
|
||||
final DateTime timestamp;
|
||||
final String detail;
|
||||
|
||||
const ProfileTransferRecord({
|
||||
required this.profileId,
|
||||
required this.direction,
|
||||
required this.timestamp,
|
||||
required this.detail,
|
||||
});
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
'profileId': profileId,
|
||||
'direction': direction,
|
||||
'timestamp': timestamp.toIso8601String(),
|
||||
'detail': detail,
|
||||
};
|
||||
|
||||
factory ProfileTransferRecord.fromJson(Map<String, dynamic> json) {
|
||||
return ProfileTransferRecord(
|
||||
profileId: json['profileId'] as String,
|
||||
direction: json['direction'] as String,
|
||||
timestamp: DateTime.parse(json['timestamp'] as String),
|
||||
detail: json['detail'] as String,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -141,6 +141,23 @@ class ContactRouteCodec {
|
||||
);
|
||||
}
|
||||
|
||||
static ParsedContactRoute direct({int hashSize = 1}) {
|
||||
if (hashSize < 1 || hashSize > maxHashSize) {
|
||||
throw ArgumentError.value(hashSize, 'hashSize', 'Must be 1, 2, or 3.');
|
||||
}
|
||||
|
||||
final encodedPathLen = (hashSize - 1) << 6;
|
||||
return ParsedContactRoute(
|
||||
canonicalText: '',
|
||||
hashSize: hashSize,
|
||||
hopCount: 0,
|
||||
encodedPathLen: encodedPathLen,
|
||||
signedEncodedPathLen: toSignedDescriptor(encodedPathLen),
|
||||
pathBytes: Uint8List(0),
|
||||
paddedPathBytes: Uint8List(maxPathBytes),
|
||||
);
|
||||
}
|
||||
|
||||
static ParsedContactRoute? fromContact(Contact contact) {
|
||||
if (!contact.routeHasPath || contact.routeHopCount == 0) {
|
||||
return null;
|
||||
@@ -240,6 +257,5 @@ extension ContactLocalization on Contact {
|
||||
return '$routeHopCount hop${routeHopCount == 1 ? '' : 's'} via $routeHashSize-byte hashes';
|
||||
}
|
||||
|
||||
bool get routeSupportsLegacyRawTransport =>
|
||||
routeHasPath && routeSignedPathLen >= 0;
|
||||
bool get routeSupportsLegacyRawTransport => routeHasPath;
|
||||
}
|
||||
|
||||
39
lib/models/contact_group.dart
Normal file
39
lib/models/contact_group.dart
Normal file
@@ -0,0 +1,39 @@
|
||||
class SavedContactGroup {
|
||||
final String id;
|
||||
final String sectionKey;
|
||||
final String label;
|
||||
final String query;
|
||||
final DateTime createdAt;
|
||||
final List<String>? matchPrefixes;
|
||||
final bool isAutoGroup;
|
||||
|
||||
const SavedContactGroup({
|
||||
required this.id,
|
||||
required this.sectionKey,
|
||||
required this.label,
|
||||
required this.query,
|
||||
required this.createdAt,
|
||||
this.matchPrefixes,
|
||||
this.isAutoGroup = false,
|
||||
});
|
||||
|
||||
SavedContactGroup copyWith({
|
||||
String? id,
|
||||
String? sectionKey,
|
||||
String? label,
|
||||
String? query,
|
||||
DateTime? createdAt,
|
||||
List<String>? matchPrefixes,
|
||||
bool? isAutoGroup,
|
||||
}) {
|
||||
return SavedContactGroup(
|
||||
id: id ?? this.id,
|
||||
sectionKey: sectionKey ?? this.sectionKey,
|
||||
label: label ?? this.label,
|
||||
query: query ?? this.query,
|
||||
createdAt: createdAt ?? this.createdAt,
|
||||
matchPrefixes: matchPrefixes ?? this.matchPrefixes,
|
||||
isAutoGroup: isAutoGroup ?? this.isAutoGroup,
|
||||
);
|
||||
}
|
||||
}
|
||||
151
lib/models/custom_map_config.dart
Normal file
151
lib/models/custom_map_config.dart
Normal file
@@ -0,0 +1,151 @@
|
||||
import 'dart:math' as math;
|
||||
|
||||
import 'package:flutter_map/flutter_map.dart';
|
||||
import 'package:latlong2/latlong.dart';
|
||||
|
||||
import '../utils/custom_map_id.dart';
|
||||
|
||||
class CustomMapConfig {
|
||||
final String filePath;
|
||||
final String displayName;
|
||||
final String mapId;
|
||||
final int imageWidth;
|
||||
final int imageHeight;
|
||||
final double? metersPerPixel;
|
||||
final LatLng? calibrationPointA;
|
||||
final LatLng? calibrationPointB;
|
||||
|
||||
const CustomMapConfig({
|
||||
required this.filePath,
|
||||
required this.displayName,
|
||||
required this.mapId,
|
||||
required this.imageWidth,
|
||||
required this.imageHeight,
|
||||
this.metersPerPixel,
|
||||
this.calibrationPointA,
|
||||
this.calibrationPointB,
|
||||
});
|
||||
|
||||
bool get isCalibrated => metersPerPixel != null && metersPerPixel! > 0;
|
||||
|
||||
double get _displayScale {
|
||||
final heightScale = imageHeight / 90.0;
|
||||
final widthScale = imageWidth / 180.0;
|
||||
return math.max(1.0, math.max(heightScale, widthScale));
|
||||
}
|
||||
|
||||
double get _displayHeight => imageHeight / _displayScale;
|
||||
|
||||
double get _displayWidth => imageWidth / _displayScale;
|
||||
|
||||
LatLngBounds get bounds =>
|
||||
LatLngBounds(const LatLng(0, 0), LatLng(_displayHeight, _displayWidth));
|
||||
|
||||
LatLngBounds get displayBounds =>
|
||||
LatLngBounds(LatLng(_displayHeight, 0), LatLng(0, _displayWidth));
|
||||
|
||||
LatLng toDisplayPoint(LatLng point) {
|
||||
return LatLng(
|
||||
_displayHeight - (point.latitude / _displayScale),
|
||||
point.longitude / _displayScale,
|
||||
);
|
||||
}
|
||||
|
||||
LatLng fromDisplayPoint(LatLng point) {
|
||||
return LatLng(
|
||||
(_displayHeight - point.latitude) * _displayScale,
|
||||
point.longitude * _displayScale,
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'filePath': filePath,
|
||||
'displayName': displayName,
|
||||
'mapId': mapId,
|
||||
'imageWidth': imageWidth,
|
||||
'imageHeight': imageHeight,
|
||||
'metersPerPixel': metersPerPixel,
|
||||
'calibrationPointA': calibrationPointA != null
|
||||
? {
|
||||
'lat': calibrationPointA!.latitude,
|
||||
'lon': calibrationPointA!.longitude,
|
||||
}
|
||||
: null,
|
||||
'calibrationPointB': calibrationPointB != null
|
||||
? {
|
||||
'lat': calibrationPointB!.latitude,
|
||||
'lon': calibrationPointB!.longitude,
|
||||
}
|
||||
: null,
|
||||
};
|
||||
}
|
||||
|
||||
CustomMapConfig copyWith({
|
||||
String? filePath,
|
||||
String? displayName,
|
||||
String? mapId,
|
||||
int? imageWidth,
|
||||
int? imageHeight,
|
||||
double? metersPerPixel,
|
||||
bool clearMetersPerPixel = false,
|
||||
LatLng? calibrationPointA,
|
||||
bool clearCalibrationPointA = false,
|
||||
LatLng? calibrationPointB,
|
||||
bool clearCalibrationPointB = false,
|
||||
}) {
|
||||
return CustomMapConfig(
|
||||
filePath: filePath ?? this.filePath,
|
||||
displayName: displayName ?? this.displayName,
|
||||
mapId: normalizeCustomMapId(mapId) ?? this.mapId,
|
||||
imageWidth: imageWidth ?? this.imageWidth,
|
||||
imageHeight: imageHeight ?? this.imageHeight,
|
||||
metersPerPixel: clearMetersPerPixel
|
||||
? null
|
||||
: (metersPerPixel ?? this.metersPerPixel),
|
||||
calibrationPointA: clearCalibrationPointA
|
||||
? null
|
||||
: (calibrationPointA ?? this.calibrationPointA),
|
||||
calibrationPointB: clearCalibrationPointB
|
||||
? null
|
||||
: (calibrationPointB ?? this.calibrationPointB),
|
||||
);
|
||||
}
|
||||
|
||||
static CustomMapConfig? fromJson(Map<String, dynamic>? json) {
|
||||
if (json == null) return null;
|
||||
|
||||
final filePath = json['filePath'];
|
||||
final displayName = json['displayName'];
|
||||
final mapId = json['mapId'];
|
||||
final imageWidth = json['imageWidth'];
|
||||
final imageHeight = json['imageHeight'];
|
||||
if (filePath is! String ||
|
||||
displayName is! String ||
|
||||
mapId is! String ||
|
||||
imageWidth is! int ||
|
||||
imageHeight is! int) {
|
||||
return null;
|
||||
}
|
||||
|
||||
LatLng? parsePoint(dynamic raw) {
|
||||
if (raw is! Map<String, dynamic>) return null;
|
||||
final lat = raw['lat'];
|
||||
final lon = raw['lon'];
|
||||
if (lat is! num || lon is! num) return null;
|
||||
return LatLng(lat.toDouble(), lon.toDouble());
|
||||
}
|
||||
|
||||
final metersPerPixel = json['metersPerPixel'];
|
||||
return CustomMapConfig(
|
||||
filePath: filePath,
|
||||
displayName: displayName,
|
||||
mapId: normalizeCustomMapId(mapId) ?? mapId,
|
||||
imageWidth: imageWidth,
|
||||
imageHeight: imageHeight,
|
||||
metersPerPixel: metersPerPixel is num ? metersPerPixel.toDouble() : null,
|
||||
calibrationPointA: parsePoint(json['calibrationPointA']),
|
||||
calibrationPointB: parsePoint(json['calibrationPointB']),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -14,11 +14,11 @@ enum ConnectionMode {
|
||||
/// Direct BLE connection to MeshCore device (default)
|
||||
ble,
|
||||
|
||||
/// Act as SSE server - share BLE device with multiple clients
|
||||
sseServer,
|
||||
|
||||
/// Direct TCP/WiFi connection to MeshCore device (port 5000)
|
||||
tcp,
|
||||
|
||||
/// USB serial connection (Android OTG or Web Serial API)
|
||||
usb,
|
||||
}
|
||||
|
||||
extension ConnectionModeExtension on ConnectionMode {
|
||||
@@ -26,10 +26,10 @@ extension ConnectionModeExtension on ConnectionMode {
|
||||
switch (this) {
|
||||
case ConnectionMode.ble:
|
||||
return 'Direct (BLE)';
|
||||
case ConnectionMode.sseServer:
|
||||
return 'Share Device (Server)';
|
||||
case ConnectionMode.tcp:
|
||||
return 'Direct (WiFi)';
|
||||
case ConnectionMode.usb:
|
||||
return 'Direct (Serial)';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,10 +37,10 @@ extension ConnectionModeExtension on ConnectionMode {
|
||||
switch (this) {
|
||||
case ConnectionMode.ble:
|
||||
return 'Direct BLE connection to MeshCore device';
|
||||
case ConnectionMode.sseServer:
|
||||
return 'Share BLE device with multiple clients over network';
|
||||
case ConnectionMode.tcp:
|
||||
return 'Direct WiFi/TCP connection to MeshCore device';
|
||||
case ConnectionMode.usb:
|
||||
return 'Direct serial connection to MeshCore device';
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -66,6 +66,12 @@ class DeviceInfo {
|
||||
final int? advLat;
|
||||
final int? advLon;
|
||||
final bool? manualAddContacts;
|
||||
final bool? autoAddUsers;
|
||||
final bool? autoAddRepeaters;
|
||||
final bool? autoAddRoomServers;
|
||||
final bool? autoAddSensors;
|
||||
final bool? autoAddOverwriteOldest;
|
||||
final int? autoAddMaxHops;
|
||||
final int? radioFreq;
|
||||
final int? radioBw;
|
||||
final int? radioSf;
|
||||
@@ -90,6 +96,7 @@ class DeviceInfo {
|
||||
|
||||
// Repeat mode (firmware v9+)
|
||||
final bool? clientRepeat;
|
||||
final int? pathHashMode;
|
||||
final bool? supportsSpectrumScan;
|
||||
final int? spectrumScanMinKhz;
|
||||
final int? spectrumScanMaxKhz;
|
||||
@@ -113,6 +120,12 @@ class DeviceInfo {
|
||||
this.advLat,
|
||||
this.advLon,
|
||||
this.manualAddContacts,
|
||||
this.autoAddUsers,
|
||||
this.autoAddRepeaters,
|
||||
this.autoAddRoomServers,
|
||||
this.autoAddSensors,
|
||||
this.autoAddOverwriteOldest,
|
||||
this.autoAddMaxHops,
|
||||
this.radioFreq,
|
||||
this.radioBw,
|
||||
this.radioSf,
|
||||
@@ -129,6 +142,7 @@ class DeviceInfo {
|
||||
this.manufacturerModel,
|
||||
this.semanticVersion,
|
||||
this.clientRepeat,
|
||||
this.pathHashMode,
|
||||
this.supportsSpectrumScan,
|
||||
this.spectrumScanMinKhz,
|
||||
this.spectrumScanMaxKhz,
|
||||
@@ -239,6 +253,12 @@ class DeviceInfo {
|
||||
int? advLat,
|
||||
int? advLon,
|
||||
bool? manualAddContacts,
|
||||
bool? autoAddUsers,
|
||||
bool? autoAddRepeaters,
|
||||
bool? autoAddRoomServers,
|
||||
bool? autoAddSensors,
|
||||
bool? autoAddOverwriteOldest,
|
||||
int? autoAddMaxHops,
|
||||
int? radioFreq,
|
||||
int? radioBw,
|
||||
int? radioSf,
|
||||
@@ -255,6 +275,7 @@ class DeviceInfo {
|
||||
String? manufacturerModel,
|
||||
String? semanticVersion,
|
||||
bool? clientRepeat,
|
||||
int? pathHashMode,
|
||||
bool? supportsSpectrumScan,
|
||||
int? spectrumScanMinKhz,
|
||||
int? spectrumScanMaxKhz,
|
||||
@@ -278,6 +299,13 @@ class DeviceInfo {
|
||||
advLat: advLat ?? this.advLat,
|
||||
advLon: advLon ?? this.advLon,
|
||||
manualAddContacts: manualAddContacts ?? this.manualAddContacts,
|
||||
autoAddUsers: autoAddUsers ?? this.autoAddUsers,
|
||||
autoAddRepeaters: autoAddRepeaters ?? this.autoAddRepeaters,
|
||||
autoAddRoomServers: autoAddRoomServers ?? this.autoAddRoomServers,
|
||||
autoAddSensors: autoAddSensors ?? this.autoAddSensors,
|
||||
autoAddOverwriteOldest:
|
||||
autoAddOverwriteOldest ?? this.autoAddOverwriteOldest,
|
||||
autoAddMaxHops: autoAddMaxHops ?? this.autoAddMaxHops,
|
||||
radioFreq: radioFreq ?? this.radioFreq,
|
||||
radioBw: radioBw ?? this.radioBw,
|
||||
radioSf: radioSf ?? this.radioSf,
|
||||
@@ -294,6 +322,7 @@ class DeviceInfo {
|
||||
manufacturerModel: manufacturerModel ?? this.manufacturerModel,
|
||||
semanticVersion: semanticVersion ?? this.semanticVersion,
|
||||
clientRepeat: clientRepeat ?? this.clientRepeat,
|
||||
pathHashMode: pathHashMode ?? this.pathHashMode,
|
||||
supportsSpectrumScan: supportsSpectrumScan ?? this.supportsSpectrumScan,
|
||||
spectrumScanMinKhz: spectrumScanMinKhz ?? this.spectrumScanMinKhz,
|
||||
spectrumScanMaxKhz: spectrumScanMaxKhz ?? this.spectrumScanMaxKhz,
|
||||
|
||||
11
lib/models/map_coordinate_space.dart
Normal file
11
lib/models/map_coordinate_space.dart
Normal file
@@ -0,0 +1,11 @@
|
||||
enum MapCoordinateSpace {
|
||||
geo,
|
||||
customMap;
|
||||
|
||||
static MapCoordinateSpace fromName(String? value) {
|
||||
return MapCoordinateSpace.values.firstWhere(
|
||||
(space) => space.name == value,
|
||||
orElse: () => MapCoordinateSpace.geo,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -2,35 +2,26 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter_map/flutter_map.dart';
|
||||
import 'package:latlong2/latlong.dart';
|
||||
|
||||
import 'map_coordinate_space.dart';
|
||||
import '../utils/custom_map_id.dart';
|
||||
|
||||
/// Drawing shape type
|
||||
enum DrawingShapeType {
|
||||
line,
|
||||
rectangle,
|
||||
}
|
||||
enum DrawingShapeType { line, rectangle }
|
||||
|
||||
/// Drawing color enum for compact network transmission
|
||||
enum DrawingColor {
|
||||
red, // 0
|
||||
blue, // 1
|
||||
green, // 2
|
||||
yellow, // 3
|
||||
orange, // 4
|
||||
purple, // 5
|
||||
pink, // 6
|
||||
cyan, // 7
|
||||
}
|
||||
enum DrawingColor { red, blue, green, yellow, orange, purple, pink, cyan }
|
||||
|
||||
/// Drawing colors available for user selection
|
||||
class DrawingColors {
|
||||
static const List<Color> palette = [
|
||||
Colors.red, // index 0
|
||||
Colors.blue, // index 1
|
||||
Colors.green, // index 2
|
||||
Colors.yellow, // index 3
|
||||
Colors.orange, // index 4
|
||||
Colors.purple, // index 5
|
||||
Colors.pink, // index 6
|
||||
Colors.cyan, // index 7
|
||||
Colors.red,
|
||||
Colors.blue,
|
||||
Colors.green,
|
||||
Colors.yellow,
|
||||
Colors.orange,
|
||||
Colors.purple,
|
||||
Colors.pink,
|
||||
Colors.cyan,
|
||||
];
|
||||
|
||||
static String colorToName(Color color) {
|
||||
@@ -45,37 +36,36 @@ class DrawingColors {
|
||||
return 'Unknown';
|
||||
}
|
||||
|
||||
/// Convert Color to enum index for network transmission
|
||||
static int colorToIndex(Color color) {
|
||||
for (int i = 0; i < palette.length; i++) {
|
||||
if (palette[i].toARGB32() == color.toARGB32()) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return 0; // Default to red if not found
|
||||
return 0;
|
||||
}
|
||||
|
||||
/// Convert enum index to Color for network reception
|
||||
static Color indexToColor(int index) {
|
||||
if (index >= 0 && index < palette.length) {
|
||||
return palette[index];
|
||||
}
|
||||
return palette[0]; // Default to red if invalid index
|
||||
return palette[0];
|
||||
}
|
||||
}
|
||||
|
||||
/// Base class for map drawings
|
||||
abstract class MapDrawing {
|
||||
final String id;
|
||||
final DrawingShapeType type;
|
||||
final Color color;
|
||||
final DateTime createdAt;
|
||||
final String? senderName; // Name of sender (null if local drawing)
|
||||
final bool isReceived; // True if drawing was received from another node
|
||||
final String? messageId; // ID of the source message (for navigation)
|
||||
final bool isShared; // Whether drawing has been broadcast over mesh
|
||||
final bool isSent; // Whether this is a sent drawing (vs received)
|
||||
final bool isHidden; // Temporary visibility toggle (session only, not persisted)
|
||||
final String? senderName;
|
||||
final bool isReceived;
|
||||
final String? messageId;
|
||||
final bool isShared;
|
||||
final bool isSent;
|
||||
final bool isHidden;
|
||||
final MapCoordinateSpace coordinateSpace;
|
||||
final String? mapId;
|
||||
|
||||
MapDrawing({
|
||||
required this.id,
|
||||
@@ -88,79 +78,79 @@ abstract class MapDrawing {
|
||||
this.isShared = false,
|
||||
this.isSent = false,
|
||||
this.isHidden = false,
|
||||
this.coordinateSpace = MapCoordinateSpace.geo,
|
||||
this.mapId,
|
||||
});
|
||||
|
||||
/// Convert to JSON for persistence
|
||||
bool get isCustomMap => coordinateSpace == MapCoordinateSpace.customMap;
|
||||
|
||||
Map<String, dynamic> toJson();
|
||||
|
||||
/// Convert to JSON for network transmission (compact format)
|
||||
/// Uses short field names and excludes createdAt to minimize message size
|
||||
/// Sender will be fetched from packet metadata
|
||||
Map<String, dynamic> toNetworkJson();
|
||||
|
||||
/// Parse network JSON (compact format)
|
||||
/// senderName and messageId will be populated from packet metadata
|
||||
static MapDrawing? fromNetworkJson(
|
||||
Map<String, dynamic> json, {
|
||||
String? senderName,
|
||||
String? messageId,
|
||||
MapCoordinateSpace coordinateSpace = MapCoordinateSpace.geo,
|
||||
String? mapId,
|
||||
}) {
|
||||
final typeNum = json['t'] as int?;
|
||||
if (typeNum == null || typeNum < 0 || typeNum >= DrawingShapeType.values.length) {
|
||||
if (typeNum == null ||
|
||||
typeNum < 0 ||
|
||||
typeNum >= DrawingShapeType.values.length) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
final type = DrawingShapeType.values[typeNum];
|
||||
|
||||
switch (type) {
|
||||
case DrawingShapeType.line:
|
||||
return LineDrawing.fromNetworkJson(
|
||||
json,
|
||||
senderName: senderName,
|
||||
messageId: messageId,
|
||||
coordinateSpace: coordinateSpace,
|
||||
mapId: mapId,
|
||||
);
|
||||
case DrawingShapeType.rectangle:
|
||||
return RectangleDrawing.fromNetworkJson(
|
||||
json,
|
||||
senderName: senderName,
|
||||
messageId: messageId,
|
||||
coordinateSpace: coordinateSpace,
|
||||
mapId: mapId,
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
} catch (_) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// Create from JSON
|
||||
static MapDrawing? fromJson(Map<String, dynamic> json) {
|
||||
final typeStr = json['type'] as String?;
|
||||
if (typeStr == null) return null;
|
||||
|
||||
try {
|
||||
final type = DrawingShapeType.values.firstWhere(
|
||||
(e) => e.toString() == 'DrawingShapeType.$typeStr',
|
||||
(value) => value.name == typeStr,
|
||||
);
|
||||
|
||||
switch (type) {
|
||||
case DrawingShapeType.line:
|
||||
return LineDrawing.fromJson(json);
|
||||
case DrawingShapeType.rectangle:
|
||||
return RectangleDrawing.fromJson(json);
|
||||
}
|
||||
} catch (e) {
|
||||
} catch (_) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the center point of the drawing
|
||||
LatLng getCenter();
|
||||
|
||||
/// Get the bounds of the drawing
|
||||
LatLngBounds getBounds();
|
||||
}
|
||||
|
||||
/// Line drawing on map
|
||||
class LineDrawing extends MapDrawing {
|
||||
final List<LatLng> points;
|
||||
|
||||
@@ -175,6 +165,8 @@ class LineDrawing extends MapDrawing {
|
||||
super.isShared,
|
||||
super.isSent,
|
||||
super.isHidden,
|
||||
super.coordinateSpace,
|
||||
super.mapId,
|
||||
}) : super(type: DrawingShapeType.line);
|
||||
|
||||
@override
|
||||
@@ -184,31 +176,51 @@ class LineDrawing extends MapDrawing {
|
||||
'type': type.name,
|
||||
'color': color.toARGB32(),
|
||||
'createdAt': createdAt.toIso8601String(),
|
||||
'points': points.map((p) => {'lat': p.latitude, 'lon': p.longitude}).toList(),
|
||||
'points': points
|
||||
.map((point) => {'lat': point.latitude, 'lon': point.longitude})
|
||||
.toList(),
|
||||
'isShared': isShared,
|
||||
// Note: isHidden is not persisted - it's session-only
|
||||
'coordinateSpace': coordinateSpace.name,
|
||||
'mapId': normalizeCustomMapId(mapId),
|
||||
};
|
||||
}
|
||||
|
||||
@override
|
||||
Map<String, dynamic> toNetworkJson() {
|
||||
// Ultra-compact format: t=type (0=line, 1=rect), c=color index (0-7), p=points
|
||||
// Points are encoded as flat array [lat1,lon1,lat2,lon2,...]
|
||||
// Coordinates rounded to 5 decimal places (~1m precision, like SAR markers)
|
||||
// Sender is fetched from packet metadata, not included in JSON
|
||||
return {
|
||||
final payload = <String, dynamic>{
|
||||
't': type.index,
|
||||
'c': DrawingColors.colorToIndex(color),
|
||||
'p': points.expand((p) => [
|
||||
double.parse(p.latitude.toStringAsFixed(5)),
|
||||
double.parse(p.longitude.toStringAsFixed(5)),
|
||||
]).toList(),
|
||||
};
|
||||
|
||||
if (coordinateSpace == MapCoordinateSpace.customMap) {
|
||||
payload['m'] = normalizeCustomMapId(mapId);
|
||||
payload['p'] = points
|
||||
.expand((point) => [point.latitude.round(), point.longitude.round()])
|
||||
.toList();
|
||||
return payload;
|
||||
}
|
||||
|
||||
payload['p'] = points
|
||||
.expand(
|
||||
(point) => [
|
||||
double.parse(point.latitude.toStringAsFixed(5)),
|
||||
double.parse(point.longitude.toStringAsFixed(5)),
|
||||
],
|
||||
)
|
||||
.toList();
|
||||
return payload;
|
||||
}
|
||||
|
||||
static LineDrawing fromJson(Map<String, dynamic> json) {
|
||||
final pointsJson = json['points'] as List<dynamic>;
|
||||
final points = pointsJson.map((p) => LatLng(p['lat'] as double, p['lon'] as double)).toList();
|
||||
final points = pointsJson
|
||||
.map(
|
||||
(point) => LatLng(
|
||||
(point['lat'] as num).toDouble(),
|
||||
(point['lon'] as num).toDouble(),
|
||||
),
|
||||
)
|
||||
.toList();
|
||||
final senderName = json['sender'] as String?;
|
||||
|
||||
return LineDrawing(
|
||||
@@ -217,8 +229,12 @@ class LineDrawing extends MapDrawing {
|
||||
createdAt: DateTime.parse(json['createdAt'] as String),
|
||||
points: points,
|
||||
senderName: senderName,
|
||||
isReceived: senderName != null, // Mark as received if sender is present
|
||||
isReceived: senderName != null,
|
||||
isShared: json['isShared'] as bool? ?? false,
|
||||
coordinateSpace: MapCoordinateSpace.fromName(
|
||||
json['coordinateSpace'] as String?,
|
||||
),
|
||||
mapId: normalizeCustomMapId(json['mapId'] as String?),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -226,42 +242,60 @@ class LineDrawing extends MapDrawing {
|
||||
Map<String, dynamic> json, {
|
||||
String? senderName,
|
||||
String? messageId,
|
||||
MapCoordinateSpace coordinateSpace = MapCoordinateSpace.geo,
|
||||
String? mapId,
|
||||
}) {
|
||||
// Parse ultra-compact format
|
||||
final pointsFlat = (json['p'] as List<dynamic>).cast<double>();
|
||||
final flatPoints = (json['p'] as List<dynamic>).cast<num>();
|
||||
final points = <LatLng>[];
|
||||
for (int i = 0; i < pointsFlat.length; i += 2) {
|
||||
points.add(LatLng(pointsFlat[i], pointsFlat[i + 1]));
|
||||
for (int i = 0; i < flatPoints.length; i += 2) {
|
||||
points.add(
|
||||
LatLng(flatPoints[i].toDouble(), flatPoints[i + 1].toDouble()),
|
||||
);
|
||||
}
|
||||
|
||||
return LineDrawing(
|
||||
id: DateTime.now().millisecondsSinceEpoch.toString(), // Generate new ID
|
||||
id: DateTime.now().millisecondsSinceEpoch.toString(),
|
||||
color: DrawingColors.indexToColor(json['c'] as int),
|
||||
createdAt: DateTime.now(),
|
||||
points: points,
|
||||
senderName: senderName,
|
||||
isReceived: true,
|
||||
messageId: messageId, // Link to source message
|
||||
isShared: false, // Received drawings are not marked as shared
|
||||
messageId: messageId,
|
||||
coordinateSpace: coordinateSpace,
|
||||
mapId: normalizeCustomMapId(mapId),
|
||||
);
|
||||
}
|
||||
|
||||
/// Create a copy with updated points
|
||||
LineDrawing copyWith({List<LatLng>? points}) {
|
||||
LineDrawing copyWith({
|
||||
List<LatLng>? points,
|
||||
bool? isHidden,
|
||||
bool? isShared,
|
||||
bool? isReceived,
|
||||
String? messageId,
|
||||
String? senderName,
|
||||
MapCoordinateSpace? coordinateSpace,
|
||||
String? mapId,
|
||||
}) {
|
||||
return LineDrawing(
|
||||
id: id,
|
||||
color: color,
|
||||
createdAt: createdAt,
|
||||
points: points ?? this.points,
|
||||
isHidden: isHidden ?? this.isHidden,
|
||||
isShared: isShared ?? this.isShared,
|
||||
isReceived: isReceived ?? this.isReceived,
|
||||
messageId: messageId ?? this.messageId,
|
||||
senderName: senderName ?? this.senderName,
|
||||
coordinateSpace: coordinateSpace ?? this.coordinateSpace,
|
||||
mapId: mapId ?? this.mapId,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
LatLng getCenter() {
|
||||
if (points.isEmpty) return LatLng(0, 0);
|
||||
if (points.isEmpty) return const LatLng(0, 0);
|
||||
if (points.length == 1) return points[0];
|
||||
|
||||
// Calculate center as average of all points
|
||||
double sumLat = 0;
|
||||
double sumLon = 0;
|
||||
for (final point in points) {
|
||||
@@ -273,8 +307,12 @@ class LineDrawing extends MapDrawing {
|
||||
|
||||
@override
|
||||
LatLngBounds getBounds() {
|
||||
if (points.isEmpty) return LatLngBounds(LatLng(0, 0), LatLng(0, 0));
|
||||
if (points.length == 1) return LatLngBounds(points[0], points[0]);
|
||||
if (points.isEmpty) {
|
||||
return LatLngBounds(const LatLng(0, 0), const LatLng(0, 0));
|
||||
}
|
||||
if (points.length == 1) {
|
||||
return LatLngBounds(points[0], points[0]);
|
||||
}
|
||||
|
||||
double minLat = points[0].latitude;
|
||||
double maxLat = points[0].latitude;
|
||||
@@ -292,7 +330,6 @@ class LineDrawing extends MapDrawing {
|
||||
}
|
||||
}
|
||||
|
||||
/// Rectangle drawing on map
|
||||
class RectangleDrawing extends MapDrawing {
|
||||
final LatLng topLeft;
|
||||
final LatLng bottomRight;
|
||||
@@ -309,16 +346,17 @@ class RectangleDrawing extends MapDrawing {
|
||||
super.isShared,
|
||||
super.isSent,
|
||||
super.isHidden,
|
||||
super.coordinateSpace,
|
||||
super.mapId,
|
||||
}) : super(type: DrawingShapeType.rectangle);
|
||||
|
||||
/// Get all corner points for rendering
|
||||
List<LatLng> get corners => [
|
||||
topLeft,
|
||||
LatLng(topLeft.latitude, bottomRight.longitude), // top right
|
||||
bottomRight,
|
||||
LatLng(bottomRight.latitude, topLeft.longitude), // bottom left
|
||||
topLeft, // close the rectangle
|
||||
];
|
||||
topLeft,
|
||||
LatLng(topLeft.latitude, bottomRight.longitude),
|
||||
bottomRight,
|
||||
LatLng(bottomRight.latitude, topLeft.longitude),
|
||||
topLeft,
|
||||
];
|
||||
|
||||
@override
|
||||
Map<String, dynamic> toJson() {
|
||||
@@ -328,27 +366,41 @@ class RectangleDrawing extends MapDrawing {
|
||||
'color': color.toARGB32(),
|
||||
'createdAt': createdAt.toIso8601String(),
|
||||
'topLeft': {'lat': topLeft.latitude, 'lon': topLeft.longitude},
|
||||
'bottomRight': {'lat': bottomRight.latitude, 'lon': bottomRight.longitude},
|
||||
'bottomRight': {
|
||||
'lat': bottomRight.latitude,
|
||||
'lon': bottomRight.longitude,
|
||||
},
|
||||
'isShared': isShared,
|
||||
// Note: isHidden is not persisted - it's session-only
|
||||
'coordinateSpace': coordinateSpace.name,
|
||||
'mapId': normalizeCustomMapId(mapId),
|
||||
};
|
||||
}
|
||||
|
||||
@override
|
||||
Map<String, dynamic> toNetworkJson() {
|
||||
// Ultra-compact format: t=type (0=line, 1=rect), c=color index (0-7), b=bounds [lat1,lon1,lat2,lon2]
|
||||
// Coordinates rounded to 5 decimal places (~1m precision, like SAR markers)
|
||||
// Sender is fetched from packet metadata, not included in JSON
|
||||
return {
|
||||
final payload = <String, dynamic>{
|
||||
't': type.index,
|
||||
'c': DrawingColors.colorToIndex(color),
|
||||
'b': [
|
||||
double.parse(topLeft.latitude.toStringAsFixed(5)),
|
||||
double.parse(topLeft.longitude.toStringAsFixed(5)),
|
||||
double.parse(bottomRight.latitude.toStringAsFixed(5)),
|
||||
double.parse(bottomRight.longitude.toStringAsFixed(5)),
|
||||
],
|
||||
};
|
||||
|
||||
if (coordinateSpace == MapCoordinateSpace.customMap) {
|
||||
payload['m'] = normalizeCustomMapId(mapId);
|
||||
payload['b'] = [
|
||||
topLeft.latitude.round(),
|
||||
topLeft.longitude.round(),
|
||||
bottomRight.latitude.round(),
|
||||
bottomRight.longitude.round(),
|
||||
];
|
||||
return payload;
|
||||
}
|
||||
|
||||
payload['b'] = [
|
||||
double.parse(topLeft.latitude.toStringAsFixed(5)),
|
||||
double.parse(topLeft.longitude.toStringAsFixed(5)),
|
||||
double.parse(bottomRight.latitude.toStringAsFixed(5)),
|
||||
double.parse(bottomRight.longitude.toStringAsFixed(5)),
|
||||
];
|
||||
return payload;
|
||||
}
|
||||
|
||||
static RectangleDrawing fromJson(Map<String, dynamic> json) {
|
||||
@@ -360,11 +412,21 @@ class RectangleDrawing extends MapDrawing {
|
||||
id: json['id'] as String,
|
||||
color: Color(json['color'] as int),
|
||||
createdAt: DateTime.parse(json['createdAt'] as String),
|
||||
topLeft: LatLng(topLeftJson['lat'] as double, topLeftJson['lon'] as double),
|
||||
bottomRight: LatLng(bottomRightJson['lat'] as double, bottomRightJson['lon'] as double),
|
||||
topLeft: LatLng(
|
||||
(topLeftJson['lat'] as num).toDouble(),
|
||||
(topLeftJson['lon'] as num).toDouble(),
|
||||
),
|
||||
bottomRight: LatLng(
|
||||
(bottomRightJson['lat'] as num).toDouble(),
|
||||
(bottomRightJson['lon'] as num).toDouble(),
|
||||
),
|
||||
senderName: senderName,
|
||||
isReceived: senderName != null, // Mark as received if sender is present
|
||||
isReceived: senderName != null,
|
||||
isShared: json['isShared'] as bool? ?? false,
|
||||
coordinateSpace: MapCoordinateSpace.fromName(
|
||||
json['coordinateSpace'] as String?,
|
||||
),
|
||||
mapId: normalizeCustomMapId(json['mapId'] as String?),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -372,27 +434,35 @@ class RectangleDrawing extends MapDrawing {
|
||||
Map<String, dynamic> json, {
|
||||
String? senderName,
|
||||
String? messageId,
|
||||
MapCoordinateSpace coordinateSpace = MapCoordinateSpace.geo,
|
||||
String? mapId,
|
||||
}) {
|
||||
// Parse ultra-compact format
|
||||
final bounds = (json['b'] as List<dynamic>).cast<double>();
|
||||
final bounds = (json['b'] as List<dynamic>).cast<num>();
|
||||
|
||||
return RectangleDrawing(
|
||||
id: DateTime.now().millisecondsSinceEpoch.toString(), // Generate new ID
|
||||
id: DateTime.now().millisecondsSinceEpoch.toString(),
|
||||
color: DrawingColors.indexToColor(json['c'] as int),
|
||||
createdAt: DateTime.now(),
|
||||
topLeft: LatLng(bounds[0], bounds[1]),
|
||||
bottomRight: LatLng(bounds[2], bounds[3]),
|
||||
topLeft: LatLng(bounds[0].toDouble(), bounds[1].toDouble()),
|
||||
bottomRight: LatLng(bounds[2].toDouble(), bounds[3].toDouble()),
|
||||
senderName: senderName,
|
||||
isReceived: true,
|
||||
messageId: messageId, // Link to source message
|
||||
isShared: false, // Received drawings are not marked as shared
|
||||
messageId: messageId,
|
||||
coordinateSpace: coordinateSpace,
|
||||
mapId: normalizeCustomMapId(mapId),
|
||||
);
|
||||
}
|
||||
|
||||
/// Create a copy with updated corners
|
||||
RectangleDrawing copyWith({
|
||||
LatLng? topLeft,
|
||||
LatLng? bottomRight,
|
||||
bool? isHidden,
|
||||
bool? isShared,
|
||||
bool? isReceived,
|
||||
String? messageId,
|
||||
String? senderName,
|
||||
MapCoordinateSpace? coordinateSpace,
|
||||
String? mapId,
|
||||
}) {
|
||||
return RectangleDrawing(
|
||||
id: id,
|
||||
@@ -400,12 +470,18 @@ class RectangleDrawing extends MapDrawing {
|
||||
createdAt: createdAt,
|
||||
topLeft: topLeft ?? this.topLeft,
|
||||
bottomRight: bottomRight ?? this.bottomRight,
|
||||
isHidden: isHidden ?? this.isHidden,
|
||||
isShared: isShared ?? this.isShared,
|
||||
isReceived: isReceived ?? this.isReceived,
|
||||
messageId: messageId ?? this.messageId,
|
||||
senderName: senderName ?? this.senderName,
|
||||
coordinateSpace: coordinateSpace ?? this.coordinateSpace,
|
||||
mapId: mapId ?? this.mapId,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
LatLng getCenter() {
|
||||
// Center is the midpoint between top-left and bottom-right
|
||||
return LatLng(
|
||||
(topLeft.latitude + bottomRight.latitude) / 2,
|
||||
(topLeft.longitude + bottomRight.longitude) / 2,
|
||||
@@ -414,7 +490,6 @@ class RectangleDrawing extends MapDrawing {
|
||||
|
||||
@override
|
||||
LatLngBounds getBounds() {
|
||||
// Bounds are simply the two corners
|
||||
return LatLngBounds(topLeft, bottomRight);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ export 'package:meshcore_client/meshcore_client.dart'
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:meshcore_client/meshcore_client.dart';
|
||||
import 'sar_marker.dart';
|
||||
import 'map_coordinate_space.dart';
|
||||
import '../utils/voice_message_parser.dart';
|
||||
|
||||
extension MessageVoiceExtension on Message {
|
||||
@@ -47,7 +48,7 @@ extension MessageSarExtension on Message {
|
||||
|
||||
/// Convert to a [SarMarker] if this message contains SAR data.
|
||||
SarMarker? toSarMarker() {
|
||||
if (!isSarMarker || sarMarkerType == null || sarGpsCoordinates == null) {
|
||||
if (!isSarMarker || sarMarkerType == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -57,16 +58,26 @@ extension MessageSarExtension on Message {
|
||||
debugPrint(' message.sarMarkerType: $sarMarkerType');
|
||||
debugPrint(' message.sarCustomEmoji: "$sarCustomEmoji"');
|
||||
|
||||
final coordinateSpace = sarCustomMapPoint != null && sarCustomMapId != null
|
||||
? MapCoordinateSpace.customMap
|
||||
: MapCoordinateSpace.geo;
|
||||
final location = sarCustomMapPoint ?? sarGpsCoordinates;
|
||||
if (location == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return SarMarker(
|
||||
id: id,
|
||||
type: sarMarkerType!,
|
||||
location: sarGpsCoordinates!,
|
||||
location: location,
|
||||
timestamp: sentAt,
|
||||
senderPublicKey: senderPublicKeyPrefix,
|
||||
senderName: senderName,
|
||||
notes: sarNotes,
|
||||
customEmoji: sarCustomEmoji,
|
||||
colorIndex: sarColorIndex,
|
||||
coordinateSpace: coordinateSpace,
|
||||
mapId: sarCustomMapId,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,6 +30,7 @@ class MessageReceptionDetails {
|
||||
final int? senderToReceiptMs;
|
||||
final int? estimatedTransmitMs;
|
||||
final int? postTransmitDelayMs;
|
||||
final int receivedCopies;
|
||||
|
||||
const MessageReceptionDetails({
|
||||
required this.capturedAt,
|
||||
@@ -40,6 +41,7 @@ class MessageReceptionDetails {
|
||||
this.senderToReceiptMs,
|
||||
this.estimatedTransmitMs,
|
||||
this.postTransmitDelayMs,
|
||||
this.receivedCopies = 1,
|
||||
});
|
||||
|
||||
String? get pathBytesHex => pathBytes == null || pathBytes!.isEmpty
|
||||
@@ -56,9 +58,60 @@ class MessageReceptionDetails {
|
||||
'senderToReceiptMs': senderToReceiptMs,
|
||||
'estimatedTransmitMs': estimatedTransmitMs,
|
||||
'postTransmitDelayMs': postTransmitDelayMs,
|
||||
'receivedCopies': receivedCopies,
|
||||
};
|
||||
}
|
||||
|
||||
MessageReceptionDetails copyWith({
|
||||
DateTime? capturedAt,
|
||||
DateTime? packetLoggedAt,
|
||||
int? rssiDbm,
|
||||
double? snrDb,
|
||||
List<int>? pathBytes,
|
||||
int? senderToReceiptMs,
|
||||
int? estimatedTransmitMs,
|
||||
int? postTransmitDelayMs,
|
||||
int? receivedCopies,
|
||||
}) {
|
||||
return MessageReceptionDetails(
|
||||
capturedAt: capturedAt ?? this.capturedAt,
|
||||
packetLoggedAt: packetLoggedAt ?? this.packetLoggedAt,
|
||||
rssiDbm: rssiDbm ?? this.rssiDbm,
|
||||
snrDb: snrDb ?? this.snrDb,
|
||||
pathBytes: pathBytes ?? this.pathBytes,
|
||||
senderToReceiptMs: senderToReceiptMs ?? this.senderToReceiptMs,
|
||||
estimatedTransmitMs: estimatedTransmitMs ?? this.estimatedTransmitMs,
|
||||
postTransmitDelayMs: postTransmitDelayMs ?? this.postTransmitDelayMs,
|
||||
receivedCopies: receivedCopies ?? this.receivedCopies,
|
||||
);
|
||||
}
|
||||
|
||||
static MessageReceptionDetails mergeDuplicate({
|
||||
MessageReceptionDetails? existing,
|
||||
MessageReceptionDetails? incoming,
|
||||
}) {
|
||||
final base =
|
||||
existing ??
|
||||
incoming ??
|
||||
MessageReceptionDetails(capturedAt: DateTime.now());
|
||||
|
||||
return base.copyWith(
|
||||
capturedAt: incoming?.capturedAt ?? existing?.capturedAt,
|
||||
packetLoggedAt: incoming?.packetLoggedAt ?? existing?.packetLoggedAt,
|
||||
rssiDbm: incoming?.rssiDbm ?? existing?.rssiDbm,
|
||||
snrDb: incoming?.snrDb ?? existing?.snrDb,
|
||||
pathBytes: incoming?.pathBytes ?? existing?.pathBytes,
|
||||
senderToReceiptMs:
|
||||
incoming?.senderToReceiptMs ?? existing?.senderToReceiptMs,
|
||||
estimatedTransmitMs:
|
||||
incoming?.estimatedTransmitMs ?? existing?.estimatedTransmitMs,
|
||||
postTransmitDelayMs:
|
||||
incoming?.postTransmitDelayMs ?? existing?.postTransmitDelayMs,
|
||||
receivedCopies:
|
||||
(existing?.receivedCopies ?? 1) + (incoming?.receivedCopies ?? 1),
|
||||
);
|
||||
}
|
||||
|
||||
static MessageReceptionDetails? fromJson(Map<String, dynamic> json) {
|
||||
final capturedAtMillis = json['capturedAtMillis'];
|
||||
if (capturedAtMillis is! int) {
|
||||
@@ -81,6 +134,7 @@ class MessageReceptionDetails {
|
||||
senderToReceiptMs: json['senderToReceiptMs'] as int?,
|
||||
estimatedTransmitMs: json['estimatedTransmitMs'] as int?,
|
||||
postTransmitDelayMs: json['postTransmitDelayMs'] as int?,
|
||||
receivedCopies: json['receivedCopies'] as int? ?? 1,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
enum PathRecordSource { learned, observed }
|
||||
|
||||
class PathRecord {
|
||||
final List<int> pathBytes;
|
||||
final int hopCount;
|
||||
final int hashSize;
|
||||
final PathRecordSource source;
|
||||
final int successCount;
|
||||
final int failureCount;
|
||||
final int lastRoundTripTimeMs;
|
||||
@@ -11,6 +14,7 @@ class PathRecord {
|
||||
required this.pathBytes,
|
||||
required this.hopCount,
|
||||
required this.hashSize,
|
||||
required this.source,
|
||||
required this.successCount,
|
||||
required this.failureCount,
|
||||
required this.lastRoundTripTimeMs,
|
||||
@@ -27,6 +31,7 @@ class PathRecord {
|
||||
List<int>? pathBytes,
|
||||
int? hopCount,
|
||||
int? hashSize,
|
||||
PathRecordSource? source,
|
||||
int? successCount,
|
||||
int? failureCount,
|
||||
int? lastRoundTripTimeMs,
|
||||
@@ -36,6 +41,7 @@ class PathRecord {
|
||||
pathBytes: pathBytes ?? this.pathBytes,
|
||||
hopCount: hopCount ?? this.hopCount,
|
||||
hashSize: hashSize ?? this.hashSize,
|
||||
source: source ?? this.source,
|
||||
successCount: successCount ?? this.successCount,
|
||||
failureCount: failureCount ?? this.failureCount,
|
||||
lastRoundTripTimeMs: lastRoundTripTimeMs ?? this.lastRoundTripTimeMs,
|
||||
@@ -48,6 +54,7 @@ class PathRecord {
|
||||
'path_bytes': pathBytes,
|
||||
'hop_count': hopCount,
|
||||
'hash_size': hashSize,
|
||||
'source': source.name,
|
||||
'success_count': successCount,
|
||||
'failure_count': failureCount,
|
||||
'last_round_trip_time_ms': lastRoundTripTimeMs,
|
||||
@@ -62,6 +69,10 @@ class PathRecord {
|
||||
.toList(),
|
||||
hopCount: json['hop_count'] as int? ?? 0,
|
||||
hashSize: json['hash_size'] as int? ?? 1,
|
||||
source: PathRecordSource.values.firstWhere(
|
||||
(value) => value.name == (json['source'] as String? ?? 'learned'),
|
||||
orElse: () => PathRecordSource.learned,
|
||||
),
|
||||
successCount: json['success_count'] as int? ?? 0,
|
||||
failureCount: json['failure_count'] as int? ?? 0,
|
||||
lastRoundTripTimeMs: json['last_round_trip_time_ms'] as int? ?? 0,
|
||||
@@ -163,6 +174,10 @@ class ContactPathHistory {
|
||||
};
|
||||
}
|
||||
|
||||
List<PathRecord> get observedPaths => directPaths
|
||||
.where((record) => record.source == PathRecordSource.observed)
|
||||
.toList();
|
||||
|
||||
factory ContactPathHistory.fromJson(
|
||||
String contactPublicKeyHex,
|
||||
Map<String, dynamic> json,
|
||||
|
||||
@@ -2,6 +2,7 @@ import 'dart:typed_data';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:latlong2/latlong.dart';
|
||||
import '../l10n/app_localizations.dart';
|
||||
import 'map_coordinate_space.dart';
|
||||
import '../services/sar_template_service.dart';
|
||||
|
||||
/// SAR (Search & Rescue) marker types
|
||||
@@ -78,6 +79,8 @@ class SarMarker {
|
||||
final String? notes;
|
||||
final String? customEmoji; // For custom SAR markers not in predefined types
|
||||
final int? colorIndex; // Color index (0-7) from standard palette
|
||||
final MapCoordinateSpace coordinateSpace;
|
||||
final String? mapId;
|
||||
|
||||
SarMarker({
|
||||
required this.id,
|
||||
@@ -89,8 +92,13 @@ class SarMarker {
|
||||
this.notes,
|
||||
this.customEmoji,
|
||||
this.colorIndex,
|
||||
this.coordinateSpace = MapCoordinateSpace.geo,
|
||||
this.mapId,
|
||||
});
|
||||
|
||||
bool get isCustomMapMarker =>
|
||||
coordinateSpace == MapCoordinateSpace.customMap && mapId != null;
|
||||
|
||||
/// Get sender public key as hex string (short)
|
||||
String? get senderKeyShort {
|
||||
if (senderPublicKey == null || senderPublicKey!.length < 8) return null;
|
||||
@@ -167,6 +175,8 @@ class SarMarker {
|
||||
String? notes,
|
||||
String? customEmoji,
|
||||
int? colorIndex,
|
||||
MapCoordinateSpace? coordinateSpace,
|
||||
String? mapId,
|
||||
}) {
|
||||
return SarMarker(
|
||||
id: id ?? this.id,
|
||||
@@ -178,6 +188,8 @@ class SarMarker {
|
||||
notes: notes ?? this.notes,
|
||||
customEmoji: customEmoji ?? this.customEmoji,
|
||||
colorIndex: colorIndex ?? this.colorIndex,
|
||||
coordinateSpace: coordinateSpace ?? this.coordinateSpace,
|
||||
mapId: mapId ?? this.mapId,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,71 +0,0 @@
|
||||
/// SSE Server Configuration Model
|
||||
///
|
||||
/// Configuration for the SSE (Server-Sent Events) web server that enables
|
||||
/// multiple app instances to share a single MeshCore BLE device.
|
||||
class SseServerConfig {
|
||||
/// Server bind address (e.g., "0.0.0.0" for all interfaces, "127.0.0.1" for localhost)
|
||||
final String host;
|
||||
|
||||
/// Server port (default: 12929)
|
||||
final int port;
|
||||
|
||||
/// Whether the SSE server is enabled
|
||||
final bool enabled;
|
||||
|
||||
/// Optional authentication token for basic security
|
||||
/// Clients must include this token in Authorization header
|
||||
final String? authToken;
|
||||
|
||||
const SseServerConfig({
|
||||
this.host = '0.0.0.0',
|
||||
this.port = 12929,
|
||||
this.enabled = false,
|
||||
this.authToken,
|
||||
});
|
||||
|
||||
/// Create a copy with updated fields
|
||||
SseServerConfig copyWith({
|
||||
String? host,
|
||||
int? port,
|
||||
bool? enabled,
|
||||
String? authToken,
|
||||
}) {
|
||||
return SseServerConfig(
|
||||
host: host ?? this.host,
|
||||
port: port ?? this.port,
|
||||
enabled: enabled ?? this.enabled,
|
||||
authToken: authToken ?? this.authToken,
|
||||
);
|
||||
}
|
||||
|
||||
/// Get server URL for clients to connect to
|
||||
String getServerUrl({String? ipAddress}) {
|
||||
final ip = ipAddress ?? host;
|
||||
return 'http://$ip:$port';
|
||||
}
|
||||
|
||||
/// Convert to JSON for persistence
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'host': host,
|
||||
'port': port,
|
||||
'enabled': enabled,
|
||||
'authToken': authToken,
|
||||
};
|
||||
}
|
||||
|
||||
/// Create from JSON
|
||||
factory SseServerConfig.fromJson(Map<String, dynamic> json) {
|
||||
return SseServerConfig(
|
||||
host: json['host'] as String? ?? '0.0.0.0',
|
||||
port: json['port'] as int? ?? 12929,
|
||||
enabled: json['enabled'] as bool? ?? false,
|
||||
authToken: json['authToken'] as String?,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'SseServerConfig(host: $host, port: $port, enabled: $enabled, hasAuth: ${authToken != null})';
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -7,7 +7,8 @@ class ChannelsProvider with ChangeNotifier {
|
||||
int _selectedChannelIndex = 0; // Default to public channel
|
||||
|
||||
/// Get all channels
|
||||
List<Channel> get channels => _channels.values.toList()..sort((a, b) => a.index.compareTo(b.index));
|
||||
List<Channel> get channels =>
|
||||
_channels.values.toList()..sort((a, b) => a.index.compareTo(b.index));
|
||||
|
||||
/// Get a specific channel by index
|
||||
Channel? getChannel(int index) => _channels[index];
|
||||
@@ -54,12 +55,12 @@ class ChannelsProvider with ChangeNotifier {
|
||||
void removeChannel(int index) {
|
||||
if (_channels.containsKey(index)) {
|
||||
_channels.remove(index);
|
||||
|
||||
|
||||
// If the deleted channel was selected, switch to public channel
|
||||
if (_selectedChannelIndex == index) {
|
||||
_selectedChannelIndex = 0;
|
||||
}
|
||||
|
||||
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
@@ -96,6 +97,13 @@ class ChannelsProvider with ChangeNotifier {
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
/// Clear runtime channel state before a live device sync begins.
|
||||
void prepareForDeviceSync() {
|
||||
_channels.clear();
|
||||
_selectedChannelIndex = 0;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
/// Check if channels have been loaded
|
||||
bool get hasChannels => _channels.isNotEmpty;
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,8 +1,11 @@
|
||||
import 'dart:convert';
|
||||
import 'dart:math' as math;
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:latlong2/latlong.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import '../models/map_drawing.dart';
|
||||
import '../models/map_coordinate_space.dart';
|
||||
import '../services/profiles_feature_service.dart';
|
||||
import '../utils/drawing_message_parser.dart';
|
||||
|
||||
/// Drawing mode state
|
||||
@@ -11,6 +14,8 @@ enum DrawingMode { none, line, rectangle, measure }
|
||||
/// Provider for managing map drawings
|
||||
class DrawingProvider with ChangeNotifier {
|
||||
static const String _storageKey = 'map_drawings';
|
||||
static const String _showReceivedDrawingsKey = 'map_show_received_drawings';
|
||||
static const String _showSarMarkersKey = 'map_show_sar_markers';
|
||||
|
||||
// Drawing state
|
||||
DrawingMode _drawingMode = DrawingMode.none;
|
||||
@@ -20,6 +25,7 @@ class DrawingProvider with ChangeNotifier {
|
||||
|
||||
// Completed drawings
|
||||
final List<MapDrawing> _drawings = [];
|
||||
bool _isInitialized = false;
|
||||
|
||||
// In-progress drawing
|
||||
MapDrawing? _currentDrawing;
|
||||
@@ -30,12 +36,18 @@ class DrawingProvider with ChangeNotifier {
|
||||
LatLng? _measurementPoint1;
|
||||
LatLng? _measurementPoint2;
|
||||
double? _measuredDistance; // in meters
|
||||
MapCoordinateSpace _activeCoordinateSpace = MapCoordinateSpace.geo;
|
||||
String? _activeMapId;
|
||||
double? _activeMetersPerPixel;
|
||||
|
||||
// Getters
|
||||
DrawingMode get drawingMode => _drawingMode;
|
||||
Color get selectedColor => _selectedColor;
|
||||
bool get showReceivedDrawings => _showReceivedDrawings;
|
||||
bool get showSarMarkers => _showSarMarkers;
|
||||
MapCoordinateSpace get activeCoordinateSpace => _activeCoordinateSpace;
|
||||
String? get activeMapId => _activeMapId;
|
||||
double? get activeMetersPerPixel => _activeMetersPerPixel;
|
||||
List<MapDrawing> get drawings {
|
||||
// Filter out hidden drawings first
|
||||
var visibleDrawings = _drawings.where((d) => !d.isHidden);
|
||||
@@ -45,19 +57,62 @@ class DrawingProvider with ChangeNotifier {
|
||||
visibleDrawings = visibleDrawings.where((d) => !d.isReceived);
|
||||
}
|
||||
|
||||
visibleDrawings = visibleDrawings.where((drawing) {
|
||||
if (drawing.coordinateSpace != _activeCoordinateSpace) {
|
||||
return false;
|
||||
}
|
||||
if (drawing.coordinateSpace == MapCoordinateSpace.customMap) {
|
||||
return drawing.mapId != null && drawing.mapId == _activeMapId;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
||||
return List.unmodifiable(visibleDrawings.toList());
|
||||
}
|
||||
|
||||
MapDrawing? get currentDrawing => _currentDrawing;
|
||||
List<LatLng> get currentLinePoints => List.unmodifiable(_currentLinePoints);
|
||||
LatLng? get rectangleStartPoint => _rectangleStartPoint;
|
||||
bool get isDrawing => _drawingMode != DrawingMode.none;
|
||||
bool get isInitialized => _isInitialized;
|
||||
LatLng? get measurementPoint1 => _measurementPoint1;
|
||||
LatLng? get measurementPoint2 => _measurementPoint2;
|
||||
double? get measuredDistance => _measuredDistance;
|
||||
|
||||
/// Initialize and load saved drawings
|
||||
Future<void> initialize() async {
|
||||
await _loadPreferences();
|
||||
await _loadDrawings();
|
||||
_isInitialized = true;
|
||||
}
|
||||
|
||||
Future<void> reloadProfileScopedState() async {
|
||||
await _loadPreferences();
|
||||
await _loadDrawings();
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
void setMapContext({
|
||||
required MapCoordinateSpace coordinateSpace,
|
||||
String? mapId,
|
||||
double? metersPerPixel,
|
||||
}) {
|
||||
final changed =
|
||||
coordinateSpace != _activeCoordinateSpace ||
|
||||
mapId != _activeMapId ||
|
||||
metersPerPixel != _activeMetersPerPixel;
|
||||
_activeCoordinateSpace = coordinateSpace;
|
||||
_activeMapId = mapId;
|
||||
_activeMetersPerPixel = metersPerPixel;
|
||||
if (_measurementPoint1 != null && _measurementPoint2 != null) {
|
||||
_measuredDistance = _calculateDistance(
|
||||
_measurementPoint1!,
|
||||
_measurementPoint2!,
|
||||
);
|
||||
}
|
||||
if (changed) {
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
|
||||
/// Set drawing mode
|
||||
@@ -77,15 +132,34 @@ class DrawingProvider with ChangeNotifier {
|
||||
}
|
||||
|
||||
/// Toggle visibility of received drawings
|
||||
void toggleReceivedDrawings() {
|
||||
Future<void> toggleReceivedDrawings() async {
|
||||
_showReceivedDrawings = !_showReceivedDrawings;
|
||||
notifyListeners();
|
||||
await _savePreferences();
|
||||
}
|
||||
|
||||
/// Toggle visibility of SAR markers
|
||||
void toggleSarMarkers() {
|
||||
Future<void> toggleSarMarkers() async {
|
||||
_showSarMarkers = !_showSarMarkers;
|
||||
notifyListeners();
|
||||
await _savePreferences();
|
||||
}
|
||||
|
||||
Future<void> _loadPreferences() async {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
_showReceivedDrawings =
|
||||
prefs.getBool(_scopedKey(_showReceivedDrawingsKey)) ?? true;
|
||||
_showSarMarkers = prefs.getBool(_scopedKey(_showSarMarkersKey)) ?? true;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
Future<void> _savePreferences() async {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
await prefs.setBool(
|
||||
_scopedKey(_showReceivedDrawingsKey),
|
||||
_showReceivedDrawings,
|
||||
);
|
||||
await prefs.setBool(_scopedKey(_showSarMarkersKey), _showSarMarkers);
|
||||
}
|
||||
|
||||
/// Start drawing a line
|
||||
@@ -116,6 +190,10 @@ class DrawingProvider with ChangeNotifier {
|
||||
color: _selectedColor,
|
||||
createdAt: DateTime.now(),
|
||||
points: List.from(_currentLinePoints),
|
||||
coordinateSpace: _activeCoordinateSpace,
|
||||
mapId: _activeCoordinateSpace == MapCoordinateSpace.customMap
|
||||
? _activeMapId
|
||||
: null,
|
||||
);
|
||||
|
||||
_drawings.add(drawing);
|
||||
@@ -159,6 +237,10 @@ class DrawingProvider with ChangeNotifier {
|
||||
? _rectangleStartPoint!.longitude
|
||||
: endPoint.longitude,
|
||||
),
|
||||
coordinateSpace: _activeCoordinateSpace,
|
||||
mapId: _activeCoordinateSpace == MapCoordinateSpace.customMap
|
||||
? _activeMapId
|
||||
: null,
|
||||
);
|
||||
notifyListeners();
|
||||
}
|
||||
@@ -195,6 +277,10 @@ class DrawingProvider with ChangeNotifier {
|
||||
createdAt: DateTime.now(),
|
||||
topLeft: topLeft,
|
||||
bottomRight: bottomRight,
|
||||
coordinateSpace: _activeCoordinateSpace,
|
||||
mapId: _activeCoordinateSpace == MapCoordinateSpace.customMap
|
||||
? _activeMapId
|
||||
: null,
|
||||
);
|
||||
|
||||
_drawings.add(drawing);
|
||||
@@ -216,7 +302,9 @@ class DrawingProvider with ChangeNotifier {
|
||||
|
||||
/// Set second measurement point and calculate distance
|
||||
void setMeasurementPoint2(LatLng point) {
|
||||
if (_drawingMode != DrawingMode.measure || _measurementPoint1 == null) return;
|
||||
if (_drawingMode != DrawingMode.measure || _measurementPoint1 == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
_measurementPoint2 = point;
|
||||
_measuredDistance = _calculateDistance(_measurementPoint1!, point);
|
||||
@@ -225,6 +313,13 @@ class DrawingProvider with ChangeNotifier {
|
||||
|
||||
/// Calculate distance between two points using Haversine formula
|
||||
double _calculateDistance(LatLng point1, LatLng point2) {
|
||||
if (_activeCoordinateSpace == MapCoordinateSpace.customMap &&
|
||||
_activeMetersPerPixel != null) {
|
||||
final dy = point2.latitude - point1.latitude;
|
||||
final dx = point2.longitude - point1.longitude;
|
||||
final pixelDistance = math.sqrt((dx * dx) + (dy * dy));
|
||||
return pixelDistance * _activeMetersPerPixel!;
|
||||
}
|
||||
const Distance distance = Distance();
|
||||
return distance.as(LengthUnit.Meter, point1, point2);
|
||||
}
|
||||
@@ -281,7 +376,7 @@ class DrawingProvider with ChangeNotifier {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
final jsonList = _drawings.map((d) => d.toJson()).toList();
|
||||
final jsonString = jsonEncode(jsonList);
|
||||
await prefs.setString(_storageKey, jsonString);
|
||||
await prefs.setString(_scopedKey(_storageKey), jsonString);
|
||||
} catch (e) {
|
||||
debugPrint('Error saving drawings: $e');
|
||||
}
|
||||
@@ -291,8 +386,11 @@ class DrawingProvider with ChangeNotifier {
|
||||
Future<void> _loadDrawings() async {
|
||||
try {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
final jsonString = prefs.getString(_storageKey);
|
||||
if (jsonString == null) return;
|
||||
final jsonString = prefs.getString(_scopedKey(_storageKey));
|
||||
if (jsonString == null || jsonString.isEmpty) {
|
||||
_drawings.clear();
|
||||
return;
|
||||
}
|
||||
|
||||
final jsonList = jsonDecode(jsonString) as List<dynamic>;
|
||||
_drawings.clear();
|
||||
@@ -318,6 +416,10 @@ class DrawingProvider with ChangeNotifier {
|
||||
color: _selectedColor,
|
||||
createdAt: DateTime.now(),
|
||||
points: _currentLinePoints,
|
||||
coordinateSpace: _activeCoordinateSpace,
|
||||
mapId: _activeCoordinateSpace == MapCoordinateSpace.customMap
|
||||
? _activeMapId
|
||||
: null,
|
||||
);
|
||||
} else if (_drawingMode == DrawingMode.rectangle &&
|
||||
_currentDrawing != null) {
|
||||
@@ -355,6 +457,8 @@ class DrawingProvider with ChangeNotifier {
|
||||
isShared: drawing.isShared,
|
||||
isSent: drawing.isSent,
|
||||
isHidden: drawing.isHidden,
|
||||
coordinateSpace: drawing.coordinateSpace,
|
||||
mapId: drawing.mapId,
|
||||
);
|
||||
} else if (drawing is RectangleDrawing) {
|
||||
return RectangleDrawing(
|
||||
@@ -369,6 +473,8 @@ class DrawingProvider with ChangeNotifier {
|
||||
isShared: drawing.isShared,
|
||||
isSent: drawing.isSent,
|
||||
isHidden: drawing.isHidden,
|
||||
coordinateSpace: drawing.coordinateSpace,
|
||||
mapId: drawing.mapId,
|
||||
);
|
||||
}
|
||||
return drawing;
|
||||
@@ -383,9 +489,27 @@ class DrawingProvider with ChangeNotifier {
|
||||
}
|
||||
}
|
||||
|
||||
List<Map<String, dynamic>> exportDrawingsJson() {
|
||||
return _drawings.map((drawing) => drawing.toJson()).toList();
|
||||
}
|
||||
|
||||
Future<void> replaceDrawingsFromJson(
|
||||
List<Map<String, dynamic>> jsonList,
|
||||
) async {
|
||||
_drawings
|
||||
..clear()
|
||||
..addAll(jsonList.map(MapDrawing.fromJson).whereType<MapDrawing>());
|
||||
await _saveDrawings();
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
String _scopedKey(String baseKey) {
|
||||
return ProfileStorageScope.scopedKey(baseKey);
|
||||
}
|
||||
|
||||
/// Get all unshared drawings (local drawings not yet sent)
|
||||
List<MapDrawing> getUnsharedDrawings() {
|
||||
return _drawings.where((d) => !d.isShared && !d.isReceived).toList();
|
||||
return drawings.where((d) => !d.isShared && !d.isReceived).toList();
|
||||
}
|
||||
|
||||
/// Mark a drawing as shared
|
||||
@@ -407,6 +531,8 @@ class DrawingProvider with ChangeNotifier {
|
||||
isShared: true,
|
||||
isSent: drawing.isSent,
|
||||
isHidden: drawing.isHidden,
|
||||
coordinateSpace: drawing.coordinateSpace,
|
||||
mapId: drawing.mapId,
|
||||
);
|
||||
} else if (drawing is RectangleDrawing) {
|
||||
_drawings[index] = RectangleDrawing(
|
||||
@@ -421,6 +547,8 @@ class DrawingProvider with ChangeNotifier {
|
||||
isShared: true,
|
||||
isSent: drawing.isSent,
|
||||
isHidden: drawing.isHidden,
|
||||
coordinateSpace: drawing.coordinateSpace,
|
||||
mapId: drawing.mapId,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -448,6 +576,8 @@ class DrawingProvider with ChangeNotifier {
|
||||
isShared: drawing.isShared,
|
||||
isSent: drawing.isSent,
|
||||
isHidden: !drawing.isHidden,
|
||||
coordinateSpace: drawing.coordinateSpace,
|
||||
mapId: drawing.mapId,
|
||||
);
|
||||
} else if (drawing is RectangleDrawing) {
|
||||
_drawings[index] = RectangleDrawing(
|
||||
@@ -462,6 +592,8 @@ class DrawingProvider with ChangeNotifier {
|
||||
isShared: drawing.isShared,
|
||||
isSent: drawing.isSent,
|
||||
isHidden: !drawing.isHidden,
|
||||
coordinateSpace: drawing.coordinateSpace,
|
||||
mapId: drawing.mapId,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import 'dart:async';
|
||||
import 'dart:typed_data';
|
||||
|
||||
/// Message delivery tracking helper
|
||||
@@ -30,6 +31,9 @@ class MessageDeliveryTracker {
|
||||
/// Map of ACK tag to timestamp for timeout cleanup
|
||||
final Map<int, DateTime> _ackTagTimestamps = {};
|
||||
|
||||
/// Completer signalled when a pending ACK slot is freed (delivery or removal).
|
||||
Completer<void>? _slotFreedCompleter;
|
||||
|
||||
/// Track a pending message ID before sending
|
||||
///
|
||||
/// This is called BEFORE sending the message. When RESP_CODE_SENT
|
||||
@@ -112,6 +116,7 @@ class MessageDeliveryTracker {
|
||||
_messageIdToAckTag.remove(messageId);
|
||||
}
|
||||
_ackTagTimestamps.remove(ackCode);
|
||||
_notifySlotFreed();
|
||||
}
|
||||
|
||||
/// Remove ACK tag mapping by message ID
|
||||
@@ -122,6 +127,7 @@ class MessageDeliveryTracker {
|
||||
if (ackTag != null) {
|
||||
_ackTagToMessageId.remove(ackTag);
|
||||
_ackTagTimestamps.remove(ackTag);
|
||||
_notifySlotFreed();
|
||||
}
|
||||
_pendingMessageIds.remove(messageId);
|
||||
final emptyKeys = <String>[];
|
||||
@@ -166,6 +172,7 @@ class MessageDeliveryTracker {
|
||||
_ackTagToMessageId.clear();
|
||||
_messageIdToAckTag.clear();
|
||||
_ackTagTimestamps.clear();
|
||||
_notifySlotFreed();
|
||||
}
|
||||
|
||||
/// Get count of pending ACK tags
|
||||
@@ -179,6 +186,27 @@ class MessageDeliveryTracker {
|
||||
/// Returns true if >= 7 pending ACKs (stay under firmware limit of 8)
|
||||
bool get shouldRateLimit => pendingCount >= 7;
|
||||
|
||||
/// Wait until a pending ACK slot is freed, or [timeout] elapses.
|
||||
///
|
||||
/// Returns immediately if not at the rate limit.
|
||||
Future<void> waitForSlot({
|
||||
Duration timeout = const Duration(milliseconds: 500),
|
||||
}) async {
|
||||
if (!shouldRateLimit) return;
|
||||
_slotFreedCompleter ??= Completer<void>();
|
||||
await _slotFreedCompleter!.future.timeout(
|
||||
timeout,
|
||||
onTimeout: () {},
|
||||
);
|
||||
}
|
||||
|
||||
void _notifySlotFreed() {
|
||||
if (_slotFreedCompleter != null && !_slotFreedCompleter!.isCompleted) {
|
||||
_slotFreedCompleter!.complete();
|
||||
}
|
||||
_slotFreedCompleter = null;
|
||||
}
|
||||
|
||||
/// Get oldest pending ACK timestamp (for debugging)
|
||||
DateTime? get oldestPendingTimestamp {
|
||||
if (_ackTagTimestamps.isEmpty) return null;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import 'dart:convert';
|
||||
import 'dart:math' as math;
|
||||
|
||||
import '../../models/message.dart';
|
||||
import '../../models/contact.dart';
|
||||
@@ -19,10 +20,19 @@ class MessageRetryManager {
|
||||
final Map<String, DateTime> _lastRetryTimes = {};
|
||||
final Map<String, int> _pathFailureStreaks = {};
|
||||
|
||||
static const int maxRetryAttempts = 4;
|
||||
/// Max retry attempts when the contact has a known path.
|
||||
/// Official MeshCore app uses 5 (with auto-retry) or 3 (without).
|
||||
static const int maxRetryAttemptsWithPath = 5;
|
||||
|
||||
/// No retries for flood-only contacts (no known path).
|
||||
/// Value 0 means: don't retry at all, go straight to fallback/fail.
|
||||
static const int maxRetryAttemptsFloodOnly = 0;
|
||||
|
||||
@Deprecated('Use maxRetryAttemptsForContact instead')
|
||||
static const int maxRetryAttempts = maxRetryAttemptsWithPath;
|
||||
|
||||
// Retry backoff values in milliseconds.
|
||||
static const List<int> _retryDelays = [1000, 2000, 4000, 8000];
|
||||
static const List<int> _retryDelays = [1000, 2000, 4000, 8000, 8000];
|
||||
static const int _defaultLoRaSf = 10;
|
||||
static const int _defaultLoRaCr = 5;
|
||||
static const int _defaultLoRaBwHz = 250000;
|
||||
@@ -38,32 +48,57 @@ class MessageRetryManager {
|
||||
return _retryDelays[attempt];
|
||||
}
|
||||
|
||||
/// Calculate a conservative delivery-ACK timeout when firmware doesn't
|
||||
/// provide one or returns an invalid value.
|
||||
static final math.Random _rng = math.Random();
|
||||
|
||||
/// Calculate a delivery-ACK timeout with random jitter.
|
||||
///
|
||||
/// Matches the official MeshCore app: `suggestedTimeout + random(1-8s)`.
|
||||
/// The jitter prevents collision when multiple messages are in flight.
|
||||
int calculateAckTimeoutMs({
|
||||
required String text,
|
||||
required Contact? contact,
|
||||
int? suggestedTimeoutMs,
|
||||
}) {
|
||||
int baseTimeout;
|
||||
if (suggestedTimeoutMs != null && suggestedTimeoutMs > 0) {
|
||||
return suggestedTimeoutMs;
|
||||
baseTimeout = suggestedTimeoutMs;
|
||||
} else {
|
||||
final payloadBytes = utf8.encode(text).length;
|
||||
final airtimeMs = _estimateLoRaAirtimeMs(payloadBytes);
|
||||
final hopCount = contact?.routeHasPath == true
|
||||
? contact!.routeHopCount
|
||||
: -1;
|
||||
|
||||
if (hopCount < 0) {
|
||||
baseTimeout = ((airtimeMs * 10) + 4000).clamp(10000, 30000);
|
||||
} else {
|
||||
baseTimeout = ((airtimeMs * (hopCount + 1) * 2) + 1500).clamp(4000, 20000);
|
||||
}
|
||||
}
|
||||
|
||||
final payloadBytes = utf8.encode(text).length;
|
||||
final airtimeMs = _estimateLoRaAirtimeMs(payloadBytes);
|
||||
final hopCount = contact?.routeHasPath == true
|
||||
? contact!.routeHopCount
|
||||
: -1;
|
||||
// Add random jitter: 500-2000ms to avoid collision
|
||||
final jitterMs = 500 + _rng.nextInt(1501);
|
||||
return baseTimeout + jitterMs;
|
||||
}
|
||||
|
||||
if (hopCount < 0) {
|
||||
return ((airtimeMs * 10) + 4000).clamp(10000, 30000);
|
||||
}
|
||||
|
||||
return ((airtimeMs * (hopCount + 1) * 2) + 1500).clamp(4000, 20000);
|
||||
/// Max attempts for a given contact based on whether it has a known path.
|
||||
static int maxRetryAttemptsForContact(Contact? contact) {
|
||||
final hasPath = contact?.routeHasPath ?? false;
|
||||
return hasPath ? maxRetryAttemptsWithPath : maxRetryAttemptsFloodOnly;
|
||||
}
|
||||
|
||||
bool canRetry(Message message, Contact contact) {
|
||||
return message.retryAttempt < maxRetryAttempts;
|
||||
return message.retryAttempt < maxRetryAttemptsForContact(contact);
|
||||
}
|
||||
|
||||
/// Whether this is the last retry attempt.
|
||||
/// When true, the caller should reset the path to force flood mode.
|
||||
///
|
||||
/// Note: called AFTER retryAttempt has been incremented, so we compare
|
||||
/// directly against maxAttempts (not +1).
|
||||
bool isLastAttempt(Message message, Contact contact) {
|
||||
final maxAttempts = maxRetryAttemptsForContact(contact);
|
||||
return maxAttempts > 1 && message.retryAttempt >= maxAttempts;
|
||||
}
|
||||
|
||||
/// Track a retry attempt for a message
|
||||
|
||||
@@ -38,12 +38,6 @@ Future<bool> serveCachedSessionFragments<T>({
|
||||
);
|
||||
return false;
|
||||
}
|
||||
if (!requester.routeSupportsLegacyRawTransport) {
|
||||
debugPrint(
|
||||
'⚠️ [$providerLabel] ${requester.advName} route uses unsupported 3-byte raw transport on current client',
|
||||
);
|
||||
return false;
|
||||
}
|
||||
if (requester.outPath.isEmpty) {
|
||||
debugPrint(
|
||||
'⚠️ [$providerLabel] ${requester.advName} has empty outPath payload',
|
||||
@@ -64,7 +58,7 @@ Future<bool> serveCachedSessionFragments<T>({
|
||||
try {
|
||||
await sendRawPacket(
|
||||
contactPath: requester.outPath,
|
||||
contactPathLen: requester.routeSignedPathLen,
|
||||
contactPathLen: requester.routeEncodedPathLen,
|
||||
payload: encodeBinary(fragment),
|
||||
);
|
||||
servedCount++;
|
||||
|
||||
@@ -3,6 +3,7 @@ import 'dart:convert';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import '../models/contact.dart';
|
||||
import '../services/profiles_feature_service.dart';
|
||||
import 'helpers/raw_session_retransmit.dart';
|
||||
import '../utils/image_message_parser.dart';
|
||||
|
||||
@@ -75,6 +76,8 @@ class ImageProvider with ChangeNotifier {
|
||||
_restore();
|
||||
}
|
||||
|
||||
String _scopedStorageKey() => ProfileStorageScope.scopedKey(_storageKey);
|
||||
|
||||
// ── Accessors ────────────────────────────────────────────────────────────
|
||||
|
||||
ImageSession? session(String sessionId) => _sessions[sessionId];
|
||||
@@ -292,18 +295,22 @@ class ImageProvider with ChangeNotifier {
|
||||
// ── Persistence ──────────────────────────────────────────────────────────
|
||||
|
||||
Future<void> clearAll() async {
|
||||
_sessions.clear();
|
||||
_outgoing.clear();
|
||||
_ignoredIncomingSessions.clear();
|
||||
_resetInMemoryState();
|
||||
notifyListeners();
|
||||
try {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
await prefs.remove(_storageKey);
|
||||
await prefs.remove(_scopedStorageKey());
|
||||
} catch (e) {
|
||||
debugPrint('❌ [ImageProvider] Failed to clear storage: $e');
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> reloadProfileScopedState() async {
|
||||
_resetInMemoryState();
|
||||
await _restore();
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
void _evictExpiredOutgoing() {
|
||||
final now = DateTime.now();
|
||||
_outgoing.removeWhere((_, s) => now.difference(s.cachedAt) > _outgoingTtl);
|
||||
@@ -343,7 +350,7 @@ class ImageProvider with ChangeNotifier {
|
||||
)
|
||||
.toList(),
|
||||
};
|
||||
await prefs.setString(_storageKey, jsonEncode(payload));
|
||||
await prefs.setString(_scopedStorageKey(), jsonEncode(payload));
|
||||
} catch (e) {
|
||||
debugPrint('❌ [ImageProvider] Failed to persist: $e');
|
||||
}
|
||||
@@ -352,7 +359,7 @@ class ImageProvider with ChangeNotifier {
|
||||
Future<void> _restore() async {
|
||||
try {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
final raw = prefs.getString(_storageKey);
|
||||
final raw = prefs.getString(_scopedStorageKey());
|
||||
if (raw == null || raw.isEmpty) return;
|
||||
|
||||
final parsed = jsonDecode(raw) as Map<String, dynamic>;
|
||||
@@ -428,6 +435,12 @@ class ImageProvider with ChangeNotifier {
|
||||
debugPrint('❌ [ImageProvider] Failed to restore: $e');
|
||||
}
|
||||
}
|
||||
|
||||
void _resetInMemoryState() {
|
||||
_sessions.clear();
|
||||
_outgoing.clear();
|
||||
_ignoredIncomingSessions.clear();
|
||||
}
|
||||
}
|
||||
|
||||
class _OutgoingSession {
|
||||
|
||||
@@ -1,24 +1,46 @@
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
import 'dart:ui' as ui;
|
||||
|
||||
import 'package:crypto/crypto.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter_map/flutter_map.dart';
|
||||
import 'package:image_picker/image_picker.dart';
|
||||
import 'package:latlong2/latlong.dart';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
|
||||
import '../models/custom_map_config.dart';
|
||||
import '../models/location_trail.dart';
|
||||
import '../models/map_coordinate_space.dart';
|
||||
import '../models/map_drawing.dart';
|
||||
import '../models/sar_marker.dart';
|
||||
import '../services/profiles_feature_service.dart';
|
||||
import '../utils/custom_map_id.dart';
|
||||
|
||||
class MapProvider with ChangeNotifier {
|
||||
static const String _customMapConfigKey = 'custom_map_config_v1';
|
||||
static const String _customMapModeKey = 'custom_map_mode_v1';
|
||||
static const String _customMapsDirName = 'custom_maps';
|
||||
|
||||
MapProvider() {
|
||||
unawaited(_loadInitialState());
|
||||
}
|
||||
|
||||
final ImagePicker _imagePicker = ImagePicker();
|
||||
|
||||
LatLng? _targetLocation;
|
||||
LatLngBounds? _targetBounds;
|
||||
double? _targetZoom;
|
||||
bool _shouldAnimate = false;
|
||||
MapCoordinateSpace _targetCoordinateSpace = MapCoordinateSpace.geo;
|
||||
String? _targetMapId;
|
||||
|
||||
// Track which contact paths are currently visible
|
||||
final Set<String> _visibleContactPaths = {};
|
||||
|
||||
// Location trail tracking
|
||||
LocationTrail? _currentTrail;
|
||||
bool _isTrailVisible = true;
|
||||
final List<LocationTrail> _trailHistory = [];
|
||||
|
||||
// WMS overlay toggles
|
||||
bool _showCadastralOverlay = false;
|
||||
bool _showForestRoadsOverlay = false;
|
||||
bool _showHikingTrailsOverlay = false;
|
||||
@@ -31,28 +53,27 @@ class MapProvider with ChangeNotifier {
|
||||
bool _showPlaceNamesOverlay = false;
|
||||
bool _showMunicipalityBordersOverlay = false;
|
||||
|
||||
// Contact trail toggles
|
||||
bool _showAllContactTrails = true; // Default to showing all contact trails
|
||||
bool _hideRepeatersOnMap = false;
|
||||
|
||||
// Imported trail (from GPX)
|
||||
LocationTrail? _importedTrail;
|
||||
|
||||
// Download area selection
|
||||
bool _isSelectingDownloadArea = false;
|
||||
LatLngBounds? _downloadAreaBounds;
|
||||
|
||||
CustomMapConfig? _customMapConfig;
|
||||
bool _isUsingCustomMap = false;
|
||||
|
||||
LatLng? get targetLocation => _targetLocation;
|
||||
LatLngBounds? get targetBounds => _targetBounds;
|
||||
double? get targetZoom => _targetZoom;
|
||||
bool get shouldAnimate => _shouldAnimate;
|
||||
Set<String> get visibleContactPaths => Set.unmodifiable(_visibleContactPaths);
|
||||
|
||||
// Trail getters
|
||||
MapCoordinateSpace get targetCoordinateSpace => _targetCoordinateSpace;
|
||||
String? get targetMapId => _targetMapId;
|
||||
LocationTrail? get currentTrail => _currentTrail;
|
||||
bool get isTrailVisible => _isTrailVisible;
|
||||
List<LocationTrail> get trailHistory => List.unmodifiable(_trailHistory);
|
||||
bool get isTrailActive => _currentTrail?.isActive ?? false;
|
||||
|
||||
// WMS overlay getters
|
||||
bool get showCadastralOverlay => _showCadastralOverlay;
|
||||
bool get showForestRoadsOverlay => _showForestRoadsOverlay;
|
||||
bool get showHikingTrailsOverlay => _showHikingTrailsOverlay;
|
||||
@@ -65,68 +86,138 @@ class MapProvider with ChangeNotifier {
|
||||
bool get showPlaceNamesOverlay => _showPlaceNamesOverlay;
|
||||
bool get showMunicipalityBordersOverlay => _showMunicipalityBordersOverlay;
|
||||
|
||||
// Contact trail getters
|
||||
bool get showAllContactTrails => _showAllContactTrails;
|
||||
bool get hideRepeatersOnMap => _hideRepeatersOnMap;
|
||||
|
||||
// Imported trail getters
|
||||
LocationTrail? get importedTrail => _importedTrail;
|
||||
|
||||
// Download area getters
|
||||
bool get isSelectingDownloadArea => _isSelectingDownloadArea;
|
||||
LatLngBounds? get downloadAreaBounds => _downloadAreaBounds;
|
||||
|
||||
CustomMapConfig? get customMapConfig => _customMapConfig;
|
||||
bool get hasCustomMap => _customMapConfig != null;
|
||||
bool get isUsingCustomMap => _isUsingCustomMap && _customMapConfig != null;
|
||||
bool get shouldHideGpsData => isUsingCustomMap;
|
||||
|
||||
LatLngBounds? get customMapBounds => _customMapConfig?.bounds;
|
||||
|
||||
Future<void> reloadProfileScopedState() async {
|
||||
await _loadInitialState();
|
||||
}
|
||||
|
||||
bool matchesActiveCustomMap(String? mapId) {
|
||||
return hasCustomMap &&
|
||||
normalizeCustomMapId(_customMapConfig!.mapId) ==
|
||||
normalizeCustomMapId(mapId);
|
||||
}
|
||||
|
||||
void navigateToLocation({
|
||||
required LatLng location,
|
||||
double zoom = 15.0,
|
||||
bool animate = true,
|
||||
}) {
|
||||
if (_isUsingCustomMap) {
|
||||
_isUsingCustomMap = false;
|
||||
unawaited(_saveCustomMapState());
|
||||
}
|
||||
_targetLocation = location;
|
||||
_targetBounds = null;
|
||||
_targetZoom = zoom;
|
||||
_shouldAnimate = animate;
|
||||
_targetCoordinateSpace = MapCoordinateSpace.geo;
|
||||
_targetMapId = null;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
String? navigateToMapPoint({
|
||||
required LatLng point,
|
||||
required MapCoordinateSpace coordinateSpace,
|
||||
String? mapId,
|
||||
double zoom = 15.0,
|
||||
bool animate = true,
|
||||
}) {
|
||||
if (coordinateSpace == MapCoordinateSpace.customMap) {
|
||||
if (!matchesActiveCustomMap(mapId)) {
|
||||
return 'Load the matching custom map to view this item.';
|
||||
}
|
||||
if (!_isUsingCustomMap) {
|
||||
_isUsingCustomMap = true;
|
||||
unawaited(_saveCustomMapState());
|
||||
}
|
||||
} else if (_isUsingCustomMap) {
|
||||
_isUsingCustomMap = false;
|
||||
unawaited(_saveCustomMapState());
|
||||
}
|
||||
|
||||
_targetLocation = point;
|
||||
_targetBounds = null;
|
||||
_targetZoom = zoom;
|
||||
_shouldAnimate = animate;
|
||||
_targetCoordinateSpace = coordinateSpace;
|
||||
_targetMapId = mapId;
|
||||
notifyListeners();
|
||||
return null;
|
||||
}
|
||||
|
||||
String? navigateToBounds({
|
||||
required LatLngBounds bounds,
|
||||
required MapCoordinateSpace coordinateSpace,
|
||||
String? mapId,
|
||||
bool animate = true,
|
||||
}) {
|
||||
if (coordinateSpace == MapCoordinateSpace.customMap) {
|
||||
if (!matchesActiveCustomMap(mapId)) {
|
||||
return 'Load the matching custom map to view this item.';
|
||||
}
|
||||
if (!_isUsingCustomMap) {
|
||||
_isUsingCustomMap = true;
|
||||
unawaited(_saveCustomMapState());
|
||||
}
|
||||
} else if (_isUsingCustomMap) {
|
||||
_isUsingCustomMap = false;
|
||||
unawaited(_saveCustomMapState());
|
||||
}
|
||||
|
||||
_targetBounds = bounds;
|
||||
_targetLocation = bounds.center;
|
||||
_targetZoom = null;
|
||||
_shouldAnimate = animate;
|
||||
_targetCoordinateSpace = coordinateSpace;
|
||||
_targetMapId = mapId;
|
||||
notifyListeners();
|
||||
return null;
|
||||
}
|
||||
|
||||
void clearNavigation() {
|
||||
_targetLocation = null;
|
||||
_targetBounds = null;
|
||||
_targetZoom = null;
|
||||
_shouldAnimate = false;
|
||||
// Don't notify listeners to avoid rebuilds
|
||||
_targetCoordinateSpace = MapCoordinateSpace.geo;
|
||||
_targetMapId = null;
|
||||
}
|
||||
|
||||
/// Navigate to a drawing by its ID
|
||||
void navigateToDrawing(String drawingId, dynamic drawingProvider) {
|
||||
debugPrint('🗺️ [MapProvider] navigateToDrawing called with ID: $drawingId');
|
||||
// Find the drawing in the provider
|
||||
final drawings = drawingProvider.drawings as List;
|
||||
debugPrint('🗺️ [MapProvider] Total drawings in provider: ${drawings.length}');
|
||||
final drawing = drawings.cast<dynamic>().firstWhere(
|
||||
(d) => d.id == drawingId,
|
||||
orElse: () => null,
|
||||
);
|
||||
|
||||
String? navigateToDrawing(String drawingId, dynamic drawingProvider) {
|
||||
final drawing = drawingProvider.getDrawingById(drawingId) as MapDrawing?;
|
||||
if (drawing == null) {
|
||||
debugPrint('⚠️ [MapProvider] Drawing $drawingId not found');
|
||||
debugPrint('⚠️ [MapProvider] Available drawing IDs: ${drawings.map((d) => d.id).toList()}');
|
||||
return;
|
||||
return 'Drawing not found.';
|
||||
}
|
||||
|
||||
// Use MapDrawing's built-in getCenter and getBounds methods
|
||||
final center = drawing.getCenter();
|
||||
final bounds = drawing.getBounds();
|
||||
if (drawing.coordinateSpace == MapCoordinateSpace.customMap) {
|
||||
if (!matchesActiveCustomMap(drawing.mapId)) {
|
||||
return 'Load the matching custom map to view this drawing.';
|
||||
}
|
||||
return navigateToBounds(
|
||||
bounds: drawing.getBounds(),
|
||||
coordinateSpace: drawing.coordinateSpace,
|
||||
mapId: drawing.mapId,
|
||||
);
|
||||
}
|
||||
|
||||
// Calculate appropriate zoom level based on bounds
|
||||
// For larger drawings, use lower zoom to fit the whole drawing
|
||||
// For smaller drawings, use higher zoom for better detail
|
||||
final bounds = drawing.getBounds();
|
||||
final latDiff = (bounds.north - bounds.south).abs();
|
||||
final lonDiff = (bounds.east - bounds.west).abs();
|
||||
final maxDiff = latDiff > lonDiff ? latDiff : lonDiff;
|
||||
|
||||
// Zoom scale: smaller drawings get higher zoom
|
||||
// 0.001 degrees (~100m) -> zoom 17
|
||||
// 0.005 degrees (~500m) -> zoom 16
|
||||
// 0.01 degrees (~1km) -> zoom 15
|
||||
// 0.05 degrees (~5km) -> zoom 13
|
||||
// 0.1 degrees (~10km) -> zoom 12
|
||||
double zoom = 15.0;
|
||||
if (maxDiff < 0.001) {
|
||||
zoom = 17.0;
|
||||
@@ -142,9 +233,125 @@ class MapProvider with ChangeNotifier {
|
||||
zoom = 10.0;
|
||||
}
|
||||
|
||||
final typeStr = drawing is LineDrawing ? 'line' : 'rectangle';
|
||||
debugPrint('🗺️ [MapProvider] Navigating to drawing: $typeStr, zoom: $zoom');
|
||||
navigateToLocation(location: center, zoom: zoom, animate: true);
|
||||
navigateToLocation(
|
||||
location: drawing.getCenter(),
|
||||
zoom: zoom,
|
||||
animate: true,
|
||||
);
|
||||
return null;
|
||||
}
|
||||
|
||||
String? navigateToSarMarker(SarMarker marker) {
|
||||
if (marker.coordinateSpace == MapCoordinateSpace.customMap) {
|
||||
return navigateToMapPoint(
|
||||
point: marker.location,
|
||||
coordinateSpace: MapCoordinateSpace.customMap,
|
||||
mapId: marker.mapId,
|
||||
);
|
||||
}
|
||||
navigateToLocation(location: marker.location, zoom: 15.0, animate: true);
|
||||
return null;
|
||||
}
|
||||
|
||||
Future<bool> loadCustomMapFromGallery() async {
|
||||
final picked = await _imagePicker.pickImage(source: ImageSource.gallery);
|
||||
if (picked == null) {
|
||||
return false;
|
||||
}
|
||||
final bytes = await picked.readAsBytes();
|
||||
await setCustomMapImage(bytes: bytes, displayName: picked.name);
|
||||
return true;
|
||||
}
|
||||
|
||||
Future<void> replaceCustomMap() async {
|
||||
await loadCustomMapFromGallery();
|
||||
}
|
||||
|
||||
Future<void> setCustomMapImage({
|
||||
required Uint8List bytes,
|
||||
required String displayName,
|
||||
}) async {
|
||||
final dimensions = await _decodeImageSize(bytes);
|
||||
final mapId = normalizeCustomMapId(sha256.convert(bytes).toString())!;
|
||||
final documentsDir = await getApplicationDocumentsDirectory();
|
||||
final dir = Directory('${documentsDir.path}/$_customMapsDirName');
|
||||
if (!await dir.exists()) {
|
||||
await dir.create(recursive: true);
|
||||
}
|
||||
|
||||
final extension = _normalizedExtension(displayName);
|
||||
final nextPath = '${dir.path}/custom_map_$mapId.$extension';
|
||||
final nextFile = File(nextPath);
|
||||
await nextFile.writeAsBytes(bytes, flush: true);
|
||||
|
||||
final previousPath = _customMapConfig?.filePath;
|
||||
_customMapConfig = CustomMapConfig(
|
||||
filePath: nextPath,
|
||||
displayName: displayName,
|
||||
mapId: mapId,
|
||||
imageWidth: dimensions.$1,
|
||||
imageHeight: dimensions.$2,
|
||||
);
|
||||
_isUsingCustomMap = true;
|
||||
await _saveCustomMapState();
|
||||
|
||||
if (previousPath != null && previousPath != nextPath) {
|
||||
unawaited(_deleteFileIfExists(previousPath));
|
||||
}
|
||||
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
Future<void> setCustomMapCalibration({
|
||||
required LatLng pointA,
|
||||
required LatLng pointB,
|
||||
required double metersPerPixel,
|
||||
}) async {
|
||||
if (_customMapConfig == null) return;
|
||||
_customMapConfig = _customMapConfig!.copyWith(
|
||||
calibrationPointA: pointA,
|
||||
calibrationPointB: pointB,
|
||||
metersPerPixel: metersPerPixel,
|
||||
);
|
||||
await _saveCustomMapState();
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
Future<void> clearCustomMapCalibration() async {
|
||||
if (_customMapConfig == null) return;
|
||||
_customMapConfig = _customMapConfig!.copyWith(
|
||||
clearMetersPerPixel: true,
|
||||
clearCalibrationPointA: true,
|
||||
clearCalibrationPointB: true,
|
||||
);
|
||||
await _saveCustomMapState();
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
Future<void> removeCustomMap() async {
|
||||
final filePath = _customMapConfig?.filePath;
|
||||
_customMapConfig = null;
|
||||
_isUsingCustomMap = false;
|
||||
clearNavigation();
|
||||
await _saveCustomMapState();
|
||||
if (filePath != null) {
|
||||
await _deleteFileIfExists(filePath);
|
||||
}
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
Future<void> enterCustomMapMode() async {
|
||||
if (!hasCustomMap || _isUsingCustomMap) return;
|
||||
_isUsingCustomMap = true;
|
||||
await _saveCustomMapState();
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
Future<void> exitCustomMapMode() async {
|
||||
if (!_isUsingCustomMap) return;
|
||||
_isUsingCustomMap = false;
|
||||
await _saveCustomMapState();
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
void updateZoom(double zoom) {
|
||||
@@ -152,37 +359,7 @@ class MapProvider with ChangeNotifier {
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
/// Toggle path visibility for a contact
|
||||
void toggleContactPath(String publicKeyHex) {
|
||||
if (_visibleContactPaths.contains(publicKeyHex)) {
|
||||
_visibleContactPaths.remove(publicKeyHex);
|
||||
} else {
|
||||
_visibleContactPaths.add(publicKeyHex);
|
||||
}
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
/// Check if a contact's path is visible
|
||||
bool isContactPathVisible(String publicKeyHex) {
|
||||
return _visibleContactPaths.contains(publicKeyHex);
|
||||
}
|
||||
|
||||
/// Hide all contact paths
|
||||
void hideAllPaths() {
|
||||
_visibleContactPaths.clear();
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
/// Show path for specific contact (hide all others)
|
||||
void showOnlyPath(String publicKeyHex) {
|
||||
_visibleContactPaths.clear();
|
||||
_visibleContactPaths.add(publicKeyHex);
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
/// Start a new location trail
|
||||
void startTrail() {
|
||||
// End current trail if active
|
||||
if (_currentTrail != null && _currentTrail!.isActive) {
|
||||
endTrail();
|
||||
}
|
||||
@@ -195,22 +372,22 @@ class MapProvider with ChangeNotifier {
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
/// Add a point to the current trail
|
||||
void addTrailPoint(LatLng position, {double? accuracy, double? speed}) {
|
||||
if (_currentTrail == null || !_currentTrail!.isActive) {
|
||||
startTrail();
|
||||
}
|
||||
|
||||
_currentTrail!.addPoint(TrailPoint(
|
||||
position: position,
|
||||
timestamp: DateTime.now(),
|
||||
accuracy: accuracy,
|
||||
speed: speed,
|
||||
));
|
||||
_currentTrail!.addPoint(
|
||||
TrailPoint(
|
||||
position: position,
|
||||
timestamp: DateTime.now(),
|
||||
accuracy: accuracy,
|
||||
speed: speed,
|
||||
),
|
||||
);
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
/// End the current trail
|
||||
void endTrail() {
|
||||
if (_currentTrail != null) {
|
||||
_currentTrail!.isActive = false;
|
||||
@@ -223,13 +400,11 @@ class MapProvider with ChangeNotifier {
|
||||
}
|
||||
}
|
||||
|
||||
/// Toggle trail visibility
|
||||
void toggleTrailVisibility() {
|
||||
_isTrailVisible = !_isTrailVisible;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
/// Clear the current trail
|
||||
void clearCurrentTrail() {
|
||||
if (_currentTrail != null) {
|
||||
_currentTrail = null;
|
||||
@@ -237,197 +412,344 @@ class MapProvider with ChangeNotifier {
|
||||
}
|
||||
}
|
||||
|
||||
/// Clear all trail history
|
||||
void clearAllTrails() {
|
||||
_currentTrail = null;
|
||||
_trailHistory.clear();
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
/// Get total trail distance in meters
|
||||
double get totalTrailDistance {
|
||||
if (_currentTrail == null) return 0;
|
||||
return _currentTrail!.totalDistance;
|
||||
}
|
||||
|
||||
/// Get trail duration
|
||||
Duration get trailDuration {
|
||||
if (_currentTrail == null) return Duration.zero;
|
||||
return _currentTrail!.duration;
|
||||
}
|
||||
|
||||
/// Toggle cadastral parcels overlay
|
||||
Future<void> toggleCadastralOverlay() async {
|
||||
_showCadastralOverlay = !_showCadastralOverlay;
|
||||
notifyListeners();
|
||||
await _saveOverlayState();
|
||||
}
|
||||
|
||||
/// Toggle forest roads overlay
|
||||
Future<void> toggleForestRoadsOverlay() async {
|
||||
_showForestRoadsOverlay = !_showForestRoadsOverlay;
|
||||
notifyListeners();
|
||||
await _saveOverlayState();
|
||||
}
|
||||
|
||||
/// Toggle hiking trails overlay
|
||||
Future<void> toggleHikingTrailsOverlay() async {
|
||||
_showHikingTrailsOverlay = !_showHikingTrailsOverlay;
|
||||
notifyListeners();
|
||||
await _saveOverlayState();
|
||||
}
|
||||
|
||||
/// Toggle main roads overlay
|
||||
Future<void> toggleMainRoadsOverlay() async {
|
||||
_showMainRoadsOverlay = !_showMainRoadsOverlay;
|
||||
notifyListeners();
|
||||
await _saveOverlayState();
|
||||
}
|
||||
|
||||
/// Toggle house numbers overlay
|
||||
Future<void> toggleHouseNumbersOverlay() async {
|
||||
_showHouseNumbersOverlay = !_showHouseNumbersOverlay;
|
||||
notifyListeners();
|
||||
await _saveOverlayState();
|
||||
}
|
||||
|
||||
/// Toggle fire hazard zones overlay
|
||||
Future<void> toggleFireHazardZonesOverlay() async {
|
||||
_showFireHazardZonesOverlay = !_showFireHazardZonesOverlay;
|
||||
notifyListeners();
|
||||
await _saveOverlayState();
|
||||
}
|
||||
|
||||
/// Toggle historical fires overlay
|
||||
Future<void> toggleHistoricalFiresOverlay() async {
|
||||
_showHistoricalFiresOverlay = !_showHistoricalFiresOverlay;
|
||||
notifyListeners();
|
||||
await _saveOverlayState();
|
||||
}
|
||||
|
||||
/// Toggle firebreaks overlay
|
||||
Future<void> toggleFirebreaksOverlay() async {
|
||||
_showFirebreaksOverlay = !_showFirebreaksOverlay;
|
||||
notifyListeners();
|
||||
await _saveOverlayState();
|
||||
}
|
||||
|
||||
/// Toggle Kras fire zones overlay
|
||||
Future<void> toggleKrasFireZonesOverlay() async {
|
||||
_showKrasFireZonesOverlay = !_showKrasFireZonesOverlay;
|
||||
notifyListeners();
|
||||
await _saveOverlayState();
|
||||
}
|
||||
|
||||
/// Toggle place names overlay
|
||||
Future<void> togglePlaceNamesOverlay() async {
|
||||
_showPlaceNamesOverlay = !_showPlaceNamesOverlay;
|
||||
notifyListeners();
|
||||
await _saveOverlayState();
|
||||
}
|
||||
|
||||
/// Toggle municipality borders overlay
|
||||
Future<void> toggleMunicipalityBordersOverlay() async {
|
||||
_showMunicipalityBordersOverlay = !_showMunicipalityBordersOverlay;
|
||||
notifyListeners();
|
||||
await _saveOverlayState();
|
||||
}
|
||||
|
||||
/// Load overlay state from SharedPreferences
|
||||
Future<void> loadOverlayState() async {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
_showCadastralOverlay = prefs.getBool('map_show_cadastral_overlay') ?? false;
|
||||
_showForestRoadsOverlay = prefs.getBool('map_show_forest_roads_overlay') ?? false;
|
||||
_showHikingTrailsOverlay = prefs.getBool('map_show_hiking_trails_overlay') ?? false;
|
||||
_showMainRoadsOverlay = prefs.getBool('map_show_main_roads_overlay') ?? false;
|
||||
_showHouseNumbersOverlay = prefs.getBool('map_show_house_numbers_overlay') ?? false;
|
||||
_showFireHazardZonesOverlay = prefs.getBool('map_show_fire_hazard_zones_overlay') ?? false;
|
||||
_showHistoricalFiresOverlay = prefs.getBool('map_show_historical_fires_overlay') ?? false;
|
||||
_showFirebreaksOverlay = prefs.getBool('map_show_firebreaks_overlay') ?? false;
|
||||
_showKrasFireZonesOverlay = prefs.getBool('map_show_kras_fire_zones_overlay') ?? false;
|
||||
_showPlaceNamesOverlay = prefs.getBool('map_show_place_names_overlay') ?? false;
|
||||
_showMunicipalityBordersOverlay = prefs.getBool('map_show_municipality_borders_overlay') ?? false;
|
||||
_showCadastralOverlay =
|
||||
prefs.getBool(_scopedKey('map_show_cadastral_overlay')) ?? false;
|
||||
_showForestRoadsOverlay =
|
||||
prefs.getBool(_scopedKey('map_show_forest_roads_overlay')) ?? false;
|
||||
_showHikingTrailsOverlay =
|
||||
prefs.getBool(_scopedKey('map_show_hiking_trails_overlay')) ?? false;
|
||||
_showMainRoadsOverlay =
|
||||
prefs.getBool(_scopedKey('map_show_main_roads_overlay')) ?? false;
|
||||
_showHouseNumbersOverlay =
|
||||
prefs.getBool(_scopedKey('map_show_house_numbers_overlay')) ?? false;
|
||||
_showFireHazardZonesOverlay =
|
||||
prefs.getBool(_scopedKey('map_show_fire_hazard_zones_overlay')) ??
|
||||
false;
|
||||
_showHistoricalFiresOverlay =
|
||||
prefs.getBool(_scopedKey('map_show_historical_fires_overlay')) ?? false;
|
||||
_showFirebreaksOverlay =
|
||||
prefs.getBool(_scopedKey('map_show_firebreaks_overlay')) ?? false;
|
||||
_showKrasFireZonesOverlay =
|
||||
prefs.getBool(_scopedKey('map_show_kras_fire_zones_overlay')) ?? false;
|
||||
_showPlaceNamesOverlay =
|
||||
prefs.getBool(_scopedKey('map_show_place_names_overlay')) ?? false;
|
||||
_showMunicipalityBordersOverlay =
|
||||
prefs.getBool(_scopedKey('map_show_municipality_borders_overlay')) ??
|
||||
false;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
/// Save overlay state to SharedPreferences
|
||||
Future<void> _loadInitialState() async {
|
||||
await Future.wait([
|
||||
loadOverlayState(),
|
||||
loadRepeaterVisibilitySettings(),
|
||||
_loadCustomMapState(),
|
||||
_removeDeprecatedSettings(),
|
||||
]);
|
||||
}
|
||||
|
||||
Future<void> _removeDeprecatedSettings() async {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
await prefs.remove(_scopedKey('map_show_all_contact_trails'));
|
||||
}
|
||||
|
||||
Future<void> _saveOverlayState() async {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
await prefs.setBool('map_show_cadastral_overlay', _showCadastralOverlay);
|
||||
await prefs.setBool('map_show_forest_roads_overlay', _showForestRoadsOverlay);
|
||||
await prefs.setBool('map_show_hiking_trails_overlay', _showHikingTrailsOverlay);
|
||||
await prefs.setBool('map_show_main_roads_overlay', _showMainRoadsOverlay);
|
||||
await prefs.setBool('map_show_house_numbers_overlay', _showHouseNumbersOverlay);
|
||||
await prefs.setBool('map_show_fire_hazard_zones_overlay', _showFireHazardZonesOverlay);
|
||||
await prefs.setBool('map_show_historical_fires_overlay', _showHistoricalFiresOverlay);
|
||||
await prefs.setBool('map_show_firebreaks_overlay', _showFirebreaksOverlay);
|
||||
await prefs.setBool('map_show_kras_fire_zones_overlay', _showKrasFireZonesOverlay);
|
||||
await prefs.setBool('map_show_place_names_overlay', _showPlaceNamesOverlay);
|
||||
await prefs.setBool('map_show_municipality_borders_overlay', _showMunicipalityBordersOverlay);
|
||||
await prefs.setBool(
|
||||
_scopedKey('map_show_cadastral_overlay'),
|
||||
_showCadastralOverlay,
|
||||
);
|
||||
await prefs.setBool(
|
||||
_scopedKey('map_show_forest_roads_overlay'),
|
||||
_showForestRoadsOverlay,
|
||||
);
|
||||
await prefs.setBool(
|
||||
_scopedKey('map_show_hiking_trails_overlay'),
|
||||
_showHikingTrailsOverlay,
|
||||
);
|
||||
await prefs.setBool(
|
||||
_scopedKey('map_show_main_roads_overlay'),
|
||||
_showMainRoadsOverlay,
|
||||
);
|
||||
await prefs.setBool(
|
||||
_scopedKey('map_show_house_numbers_overlay'),
|
||||
_showHouseNumbersOverlay,
|
||||
);
|
||||
await prefs.setBool(
|
||||
_scopedKey('map_show_fire_hazard_zones_overlay'),
|
||||
_showFireHazardZonesOverlay,
|
||||
);
|
||||
await prefs.setBool(
|
||||
_scopedKey('map_show_historical_fires_overlay'),
|
||||
_showHistoricalFiresOverlay,
|
||||
);
|
||||
await prefs.setBool(
|
||||
_scopedKey('map_show_firebreaks_overlay'),
|
||||
_showFirebreaksOverlay,
|
||||
);
|
||||
await prefs.setBool(
|
||||
_scopedKey('map_show_kras_fire_zones_overlay'),
|
||||
_showKrasFireZonesOverlay,
|
||||
);
|
||||
await prefs.setBool(
|
||||
_scopedKey('map_show_place_names_overlay'),
|
||||
_showPlaceNamesOverlay,
|
||||
);
|
||||
await prefs.setBool(
|
||||
_scopedKey('map_show_municipality_borders_overlay'),
|
||||
_showMunicipalityBordersOverlay,
|
||||
);
|
||||
}
|
||||
|
||||
/// Toggle all contact trails on/off
|
||||
Future<void> toggleAllContactTrails() async {
|
||||
_showAllContactTrails = !_showAllContactTrails;
|
||||
Future<void> setHideRepeatersOnMap(bool hide) async {
|
||||
if (_hideRepeatersOnMap == hide) return;
|
||||
_hideRepeatersOnMap = hide;
|
||||
notifyListeners();
|
||||
await _saveTrailSettings();
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
await prefs.setBool(_scopedKey('map_hide_repeaters'), _hideRepeatersOnMap);
|
||||
}
|
||||
|
||||
/// Load trail settings from SharedPreferences
|
||||
Future<void> loadTrailSettings() async {
|
||||
Future<void> loadRepeaterVisibilitySettings() async {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
_showAllContactTrails = prefs.getBool('map_show_all_contact_trails') ?? true; // Default to true (show all)
|
||||
_hideRepeatersOnMap =
|
||||
prefs.getBool(_scopedKey('map_hide_repeaters')) ?? false;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
/// Save trail settings to SharedPreferences
|
||||
Future<void> _saveTrailSettings() async {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
await prefs.setBool('map_show_all_contact_trails', _showAllContactTrails);
|
||||
}
|
||||
|
||||
/// Set imported trail (from GPX import)
|
||||
void setImportedTrail(LocationTrail trail) {
|
||||
_importedTrail = trail;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
/// Clear imported trail
|
||||
void clearImportedTrail() {
|
||||
_importedTrail = null;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
/// Replace current trail with imported trail
|
||||
void replaceCurrentTrailWithImport(LocationTrail importedTrail) {
|
||||
// End current trail if active
|
||||
if (_currentTrail != null && _currentTrail!.isActive) {
|
||||
endTrail();
|
||||
}
|
||||
|
||||
// Set imported trail as current trail
|
||||
_currentTrail = importedTrail;
|
||||
_isTrailVisible = true;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
/// Enter download area selection mode with initial bounds
|
||||
void enterDownloadAreaMode(LatLngBounds initialBounds) {
|
||||
_isSelectingDownloadArea = true;
|
||||
_downloadAreaBounds = initialBounds;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
/// Exit download area selection mode
|
||||
void exitDownloadAreaMode() {
|
||||
_isSelectingDownloadArea = false;
|
||||
_downloadAreaBounds = null;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
/// Update the download area bounds (while dragging/resizing)
|
||||
void updateDownloadAreaBounds(LatLngBounds bounds) {
|
||||
_downloadAreaBounds = bounds;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
Future<void> _loadCustomMapState() async {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
final configJson = prefs.getString(_scopedKey(_customMapConfigKey));
|
||||
if (configJson != null && configJson.isNotEmpty) {
|
||||
final decoded = jsonDecode(configJson);
|
||||
if (decoded is Map<String, dynamic>) {
|
||||
final config = CustomMapConfig.fromJson(decoded);
|
||||
if (config != null && await File(config.filePath).exists()) {
|
||||
_customMapConfig = config;
|
||||
} else {
|
||||
_customMapConfig = null;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
_customMapConfig = null;
|
||||
}
|
||||
_isUsingCustomMap =
|
||||
(prefs.getBool(_scopedKey(_customMapModeKey)) ?? false) &&
|
||||
_customMapConfig != null;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
Future<void> _saveCustomMapState() async {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
if (_customMapConfig == null) {
|
||||
await prefs.remove(_scopedKey(_customMapConfigKey));
|
||||
} else {
|
||||
await prefs.setString(
|
||||
_scopedKey(_customMapConfigKey),
|
||||
jsonEncode(_customMapConfig!.toJson()),
|
||||
);
|
||||
}
|
||||
await prefs.setBool(_scopedKey(_customMapModeKey), _isUsingCustomMap);
|
||||
}
|
||||
|
||||
Future<(int, int)> _decodeImageSize(Uint8List bytes) async {
|
||||
final codec = await ui.instantiateImageCodec(bytes);
|
||||
final frame = await codec.getNextFrame();
|
||||
return (frame.image.width, frame.image.height);
|
||||
}
|
||||
|
||||
String _normalizedExtension(String displayName) {
|
||||
final dotIndex = displayName.lastIndexOf('.');
|
||||
if (dotIndex == -1 || dotIndex == displayName.length - 1) {
|
||||
return 'png';
|
||||
}
|
||||
return displayName.substring(dotIndex + 1).toLowerCase();
|
||||
}
|
||||
|
||||
Future<void> _deleteFileIfExists(String path) async {
|
||||
final file = File(path);
|
||||
if (await file.exists()) {
|
||||
await file.delete();
|
||||
}
|
||||
}
|
||||
|
||||
Map<String, dynamic> exportWorkspaceJson() {
|
||||
return {
|
||||
'currentTrail': _currentTrail?.toJson(),
|
||||
'trailHistory': _trailHistory.map((trail) => trail.toJson()).toList(),
|
||||
'importedTrail': _importedTrail?.toJson(),
|
||||
'isTrailVisible': _isTrailVisible,
|
||||
'showCadastralOverlay': _showCadastralOverlay,
|
||||
'showForestRoadsOverlay': _showForestRoadsOverlay,
|
||||
'showHikingTrailsOverlay': _showHikingTrailsOverlay,
|
||||
'showMainRoadsOverlay': _showMainRoadsOverlay,
|
||||
'showHouseNumbersOverlay': _showHouseNumbersOverlay,
|
||||
'showFireHazardZonesOverlay': _showFireHazardZonesOverlay,
|
||||
'showHistoricalFiresOverlay': _showHistoricalFiresOverlay,
|
||||
'showFirebreaksOverlay': _showFirebreaksOverlay,
|
||||
'showKrasFireZonesOverlay': _showKrasFireZonesOverlay,
|
||||
'showPlaceNamesOverlay': _showPlaceNamesOverlay,
|
||||
'showMunicipalityBordersOverlay': _showMunicipalityBordersOverlay,
|
||||
'hideRepeatersOnMap': _hideRepeatersOnMap,
|
||||
};
|
||||
}
|
||||
|
||||
void applyWorkspaceJson(Map<String, dynamic> json) {
|
||||
_currentTrail = json['currentTrail'] is Map<String, dynamic>
|
||||
? LocationTrail.fromJson(json['currentTrail'] as Map<String, dynamic>)
|
||||
: null;
|
||||
_trailHistory
|
||||
..clear()
|
||||
..addAll(
|
||||
(json['trailHistory'] as List<dynamic>? ?? const [])
|
||||
.whereType<Map<String, dynamic>>()
|
||||
.map(LocationTrail.fromJson),
|
||||
);
|
||||
_importedTrail = json['importedTrail'] is Map<String, dynamic>
|
||||
? LocationTrail.fromJson(json['importedTrail'] as Map<String, dynamic>)
|
||||
: null;
|
||||
_isTrailVisible = json['isTrailVisible'] as bool? ?? true;
|
||||
_showCadastralOverlay = json['showCadastralOverlay'] as bool? ?? false;
|
||||
_showForestRoadsOverlay = json['showForestRoadsOverlay'] as bool? ?? false;
|
||||
_showHikingTrailsOverlay =
|
||||
json['showHikingTrailsOverlay'] as bool? ?? false;
|
||||
_showMainRoadsOverlay = json['showMainRoadsOverlay'] as bool? ?? false;
|
||||
_showHouseNumbersOverlay =
|
||||
json['showHouseNumbersOverlay'] as bool? ?? false;
|
||||
_showFireHazardZonesOverlay =
|
||||
json['showFireHazardZonesOverlay'] as bool? ?? false;
|
||||
_showHistoricalFiresOverlay =
|
||||
json['showHistoricalFiresOverlay'] as bool? ?? false;
|
||||
_showFirebreaksOverlay = json['showFirebreaksOverlay'] as bool? ?? false;
|
||||
_showKrasFireZonesOverlay =
|
||||
json['showKrasFireZonesOverlay'] as bool? ?? false;
|
||||
_showPlaceNamesOverlay = json['showPlaceNamesOverlay'] as bool? ?? false;
|
||||
_showMunicipalityBordersOverlay =
|
||||
json['showMunicipalityBordersOverlay'] as bool? ?? false;
|
||||
_hideRepeatersOnMap = json['hideRepeatersOnMap'] as bool? ?? false;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
String _scopedKey(String baseKey) {
|
||||
return ProfileStorageScope.scopedKey(baseKey);
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -5,6 +5,8 @@ import 'package:flutter/foundation.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
|
||||
import '../models/contact.dart';
|
||||
import '../services/profiles_feature_service.dart';
|
||||
import '../widgets/sensors/sensor_telemetry_card.dart';
|
||||
import 'connection_provider.dart';
|
||||
import 'contacts_provider.dart';
|
||||
|
||||
@@ -12,9 +14,23 @@ enum SensorRefreshState { idle, refreshing, success, timeout, unavailable }
|
||||
|
||||
class SensorsProvider with ChangeNotifier {
|
||||
static const Duration _successStateRetention = Duration(minutes: 1);
|
||||
static const int selfAutoRefreshMinutes = 1;
|
||||
static const String _watchedSensorsKey = 'watched_sensor_keys';
|
||||
static const String _visibleSensorMetricsKey = 'visible_sensor_metrics';
|
||||
static const String _fieldSpanKey = 'sensor_field_spans';
|
||||
static const String _metricLabelKey = 'sensor_metric_labels';
|
||||
static const String _metricOrderKey = 'sensor_metric_order';
|
||||
static const String _autoRefreshMinutesKey = 'sensor_auto_refresh_minutes';
|
||||
static const List<int> supportedAutoRefreshIntervals = <int>[
|
||||
0,
|
||||
5,
|
||||
15,
|
||||
30,
|
||||
60,
|
||||
360,
|
||||
720,
|
||||
1440,
|
||||
];
|
||||
static const Set<String> _defaultVisibleFields = <String>{
|
||||
'voltage',
|
||||
'battery',
|
||||
@@ -23,6 +39,14 @@ class SensorsProvider with ChangeNotifier {
|
||||
'pressure',
|
||||
'gps',
|
||||
};
|
||||
static const List<String> _defaultMetricOrder = <String>[
|
||||
'voltage',
|
||||
'battery',
|
||||
'temperature',
|
||||
'humidity',
|
||||
'pressure',
|
||||
'gps',
|
||||
];
|
||||
|
||||
final List<String> _watchedSensorKeys = <String>[];
|
||||
final Map<String, SensorRefreshState> _refreshStates =
|
||||
@@ -32,13 +56,22 @@ class SensorsProvider with ChangeNotifier {
|
||||
<String, Set<String>>{};
|
||||
final Map<String, Map<String, int>> _fieldSpansBySensor =
|
||||
<String, Map<String, int>>{};
|
||||
final Map<String, Map<String, String>> _metricLabelsBySensor =
|
||||
<String, Map<String, String>>{};
|
||||
final Map<String, List<String>> _metricOrderBySensor =
|
||||
<String, List<String>>{};
|
||||
final Map<String, int> _autoRefreshMinutesBySensor = <String, int>{};
|
||||
final Map<String, DateTime> _lastRefreshAttemptAt = <String, DateTime>{};
|
||||
bool _isLoaded = false;
|
||||
bool _isRefreshingAll = false;
|
||||
bool _isRunningAutoRefreshTick = false;
|
||||
|
||||
SensorsProvider() {
|
||||
unawaited(_loadWatchedSensors());
|
||||
}
|
||||
|
||||
String _key(String baseKey) => ProfileStorageScope.scopedKey(baseKey);
|
||||
|
||||
List<String> get watchedSensorKeys => List.unmodifiable(_watchedSensorKeys);
|
||||
bool get isLoaded => _isLoaded;
|
||||
bool get isRefreshingAll => _isRefreshingAll;
|
||||
@@ -49,14 +82,26 @@ class SensorsProvider with ChangeNotifier {
|
||||
Future<void> _loadWatchedSensors() async {
|
||||
try {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
final stored = prefs.getStringList(_watchedSensorsKey) ?? <String>[];
|
||||
final storedMetricsJson = prefs.getString(_visibleSensorMetricsKey);
|
||||
final storedSpansJson = prefs.getString(_fieldSpanKey);
|
||||
final stored =
|
||||
prefs.getStringList(_key(_watchedSensorsKey)) ?? <String>[];
|
||||
final storedMetricsJson = prefs.getString(_key(_visibleSensorMetricsKey));
|
||||
final storedSpansJson = prefs.getString(_key(_fieldSpanKey));
|
||||
final storedLabelsJson = prefs.getString(_key(_metricLabelKey));
|
||||
final storedOrderJson = prefs.getString(_key(_metricOrderKey));
|
||||
final storedAutoRefreshJson = prefs.getString(
|
||||
_key(_autoRefreshMinutesKey),
|
||||
);
|
||||
_watchedSensorKeys
|
||||
..clear()
|
||||
..addAll(stored);
|
||||
_refreshStates.clear();
|
||||
_refreshStateUpdatedAt.clear();
|
||||
_visibleFieldsBySensor.clear();
|
||||
_fieldSpansBySensor.clear();
|
||||
_metricLabelsBySensor.clear();
|
||||
_metricOrderBySensor.clear();
|
||||
_autoRefreshMinutesBySensor.clear();
|
||||
_lastRefreshAttemptAt.clear();
|
||||
if (storedMetricsJson != null && storedMetricsJson.isNotEmpty) {
|
||||
final decoded = jsonDecode(storedMetricsJson) as Map<String, dynamic>;
|
||||
for (final entry in decoded.entries) {
|
||||
@@ -72,12 +117,49 @@ class SensorsProvider with ChangeNotifier {
|
||||
.map((key, value) => MapEntry(key, value as int));
|
||||
}
|
||||
}
|
||||
if (storedLabelsJson != null && storedLabelsJson.isNotEmpty) {
|
||||
final decoded = jsonDecode(storedLabelsJson) as Map<String, dynamic>;
|
||||
for (final entry in decoded.entries) {
|
||||
_metricLabelsBySensor[entry.key] =
|
||||
(entry.value as Map<String, dynamic>).map(
|
||||
(key, value) => MapEntry(key, value as String),
|
||||
);
|
||||
}
|
||||
}
|
||||
if (storedOrderJson != null && storedOrderJson.isNotEmpty) {
|
||||
final decoded = jsonDecode(storedOrderJson) as Map<String, dynamic>;
|
||||
for (final entry in decoded.entries) {
|
||||
_metricOrderBySensor[entry.key] = (entry.value as List<dynamic>)
|
||||
.cast<String>()
|
||||
.toList();
|
||||
}
|
||||
}
|
||||
if (storedAutoRefreshJson != null && storedAutoRefreshJson.isNotEmpty) {
|
||||
final decoded =
|
||||
jsonDecode(storedAutoRefreshJson) as Map<String, dynamic>;
|
||||
for (final entry in decoded.entries) {
|
||||
final minutes = _normalizeAutoRefreshMinutes(
|
||||
(entry.value as num).toInt(),
|
||||
);
|
||||
if (minutes > 0) {
|
||||
_autoRefreshMinutesBySensor[entry.key] = minutes;
|
||||
}
|
||||
}
|
||||
}
|
||||
_autoRefreshMinutesBySensor.removeWhere(
|
||||
(key, _) => !_watchedSensorKeys.contains(key),
|
||||
);
|
||||
for (final key in _watchedSensorKeys) {
|
||||
_visibleFieldsBySensor.putIfAbsent(
|
||||
key,
|
||||
() => Set<String>.from(_defaultVisibleFields),
|
||||
);
|
||||
_fieldSpansBySensor.putIfAbsent(key, () => <String, int>{});
|
||||
_metricLabelsBySensor.putIfAbsent(key, () => <String, String>{});
|
||||
_metricOrderBySensor.putIfAbsent(
|
||||
key,
|
||||
() => List<String>.from(_defaultMetricOrder),
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
debugPrint('Error loading watched sensors: $e');
|
||||
@@ -87,10 +169,15 @@ class SensorsProvider with ChangeNotifier {
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> reloadProfileScopedState() async {
|
||||
_isLoaded = false;
|
||||
await _loadWatchedSensors();
|
||||
}
|
||||
|
||||
Future<void> _persistWatchedSensors() async {
|
||||
try {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
await prefs.setStringList(_watchedSensorsKey, _watchedSensorKeys);
|
||||
await prefs.setStringList(_key(_watchedSensorsKey), _watchedSensorKeys);
|
||||
} catch (e) {
|
||||
debugPrint('Error saving watched sensors: $e');
|
||||
}
|
||||
@@ -103,7 +190,10 @@ class SensorsProvider with ChangeNotifier {
|
||||
for (final entry in _visibleFieldsBySensor.entries) {
|
||||
encoded[entry.key] = entry.value.toList();
|
||||
}
|
||||
await prefs.setString(_visibleSensorMetricsKey, jsonEncode(encoded));
|
||||
await prefs.setString(
|
||||
_key(_visibleSensorMetricsKey),
|
||||
jsonEncode(encoded),
|
||||
);
|
||||
} catch (e) {
|
||||
debugPrint('Error saving visible sensor metrics: $e');
|
||||
}
|
||||
@@ -112,16 +202,71 @@ class SensorsProvider with ChangeNotifier {
|
||||
Future<void> _persistFieldSpans() async {
|
||||
try {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
await prefs.setString(_fieldSpanKey, jsonEncode(_fieldSpansBySensor));
|
||||
await prefs.setString(
|
||||
_key(_fieldSpanKey),
|
||||
jsonEncode(_fieldSpansBySensor),
|
||||
);
|
||||
} catch (e) {
|
||||
debugPrint('Error saving sensor field spans: $e');
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _persistMetricLabels() async {
|
||||
try {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
await prefs.setString(
|
||||
_key(_metricLabelKey),
|
||||
jsonEncode(_metricLabelsBySensor),
|
||||
);
|
||||
} catch (e) {
|
||||
debugPrint('Error saving sensor metric labels: $e');
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _persistMetricOrder() async {
|
||||
try {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
await prefs.setString(
|
||||
_key(_metricOrderKey),
|
||||
jsonEncode(_metricOrderBySensor),
|
||||
);
|
||||
} catch (e) {
|
||||
debugPrint('Error saving sensor metric order: $e');
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _persistAutoRefreshMinutes() async {
|
||||
try {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
await prefs.setString(
|
||||
_key(_autoRefreshMinutesKey),
|
||||
jsonEncode(_autoRefreshMinutesBySensor),
|
||||
);
|
||||
} catch (e) {
|
||||
debugPrint('Error saving sensor auto refresh minutes: $e');
|
||||
}
|
||||
}
|
||||
|
||||
Set<String> visibleFieldsFor(String publicKeyHex) => Set<String>.unmodifiable(
|
||||
_visibleFieldsBySensor[publicKeyHex] ?? _defaultVisibleFields,
|
||||
);
|
||||
|
||||
Set<String> effectiveVisibleFieldsFor(
|
||||
String publicKeyHex,
|
||||
Iterable<String> availableFieldKeys,
|
||||
) {
|
||||
final storedVisibleFields =
|
||||
_visibleFieldsBySensor[publicKeyHex] ?? _defaultVisibleFields;
|
||||
if (!_shouldAutoIncludeAvailableFields(publicKeyHex, storedVisibleFields)) {
|
||||
return Set<String>.unmodifiable(storedVisibleFields);
|
||||
}
|
||||
|
||||
return Set<String>.unmodifiable(<String>{
|
||||
...storedVisibleFields,
|
||||
...availableFieldKeys,
|
||||
});
|
||||
}
|
||||
|
||||
bool showsField(String publicKeyHex, String fieldKey) =>
|
||||
visibleFieldsFor(publicKeyHex).contains(fieldKey);
|
||||
|
||||
@@ -131,6 +276,118 @@ class SensorsProvider with ChangeNotifier {
|
||||
return span == 2 ? 2 : 1;
|
||||
}
|
||||
|
||||
List<String> metricOrderFor(
|
||||
String publicKeyHex,
|
||||
Iterable<String> availableFieldKeys,
|
||||
) {
|
||||
final available = availableFieldKeys.toList();
|
||||
final availableSet = available.toSet();
|
||||
final ordered = <String>[];
|
||||
final seen = <String>{};
|
||||
final stored =
|
||||
_metricOrderBySensor[publicKeyHex] ??
|
||||
List<String>.from(_defaultMetricOrder);
|
||||
|
||||
for (final fieldKey in stored) {
|
||||
if (availableSet.contains(fieldKey) && seen.add(fieldKey)) {
|
||||
ordered.add(fieldKey);
|
||||
}
|
||||
}
|
||||
for (final fieldKey in available) {
|
||||
if (seen.add(fieldKey)) {
|
||||
ordered.add(fieldKey);
|
||||
}
|
||||
}
|
||||
return List<String>.unmodifiable(ordered);
|
||||
}
|
||||
|
||||
Map<String, String> labelOverridesFor(String publicKeyHex) =>
|
||||
Map<String, String>.unmodifiable(
|
||||
_metricLabelsBySensor[publicKeyHex] ?? const <String, String>{},
|
||||
);
|
||||
|
||||
String? labelOverrideFor(String publicKeyHex, String fieldKey) =>
|
||||
_metricLabelsBySensor[publicKeyHex]?[fieldKey];
|
||||
|
||||
int autoRefreshMinutesFor(String publicKeyHex) =>
|
||||
_autoRefreshMinutesBySensor[publicKeyHex] ?? 0;
|
||||
|
||||
Future<void> setAutoRefreshMinutes(String publicKeyHex, int minutes) async {
|
||||
final normalizedMinutes = _normalizeAutoRefreshMinutes(minutes);
|
||||
final currentMinutes = autoRefreshMinutesFor(publicKeyHex);
|
||||
if (currentMinutes == normalizedMinutes) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (normalizedMinutes == 0) {
|
||||
_autoRefreshMinutesBySensor.remove(publicKeyHex);
|
||||
} else {
|
||||
_autoRefreshMinutesBySensor[publicKeyHex] = normalizedMinutes;
|
||||
}
|
||||
await _persistAutoRefreshMinutes();
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
static int _normalizeAutoRefreshMinutes(int minutes) {
|
||||
if (minutes <= 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (supportedAutoRefreshIntervals.contains(minutes)) {
|
||||
return minutes;
|
||||
}
|
||||
|
||||
final positiveIntervals = supportedAutoRefreshIntervals.where(
|
||||
(value) => value > 0,
|
||||
);
|
||||
var bestMatch = positiveIntervals.first;
|
||||
var bestDistance = (minutes - bestMatch).abs();
|
||||
|
||||
for (final interval in positiveIntervals.skip(1)) {
|
||||
final distance = (minutes - interval).abs();
|
||||
if (distance < bestDistance) {
|
||||
bestMatch = interval;
|
||||
bestDistance = distance;
|
||||
}
|
||||
}
|
||||
|
||||
return bestMatch;
|
||||
}
|
||||
|
||||
List<String> dueAutoRefreshSensorKeys({DateTime? now}) {
|
||||
final refreshTime = now ?? DateTime.now();
|
||||
final dueKeys = <String>[];
|
||||
|
||||
for (final key in _watchedSensorKeys) {
|
||||
final minutes = autoRefreshMinutesFor(key);
|
||||
if (minutes <= 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
final lastRefreshAt = _lastRefreshAttemptAt[key];
|
||||
if (lastRefreshAt == null ||
|
||||
refreshTime.difference(lastRefreshAt) >= Duration(minutes: minutes)) {
|
||||
dueKeys.add(key);
|
||||
}
|
||||
}
|
||||
|
||||
return List<String>.unmodifiable(dueKeys);
|
||||
}
|
||||
|
||||
bool _isRefreshDue(
|
||||
String publicKeyHex, {
|
||||
required int minutes,
|
||||
required DateTime refreshTime,
|
||||
}) {
|
||||
if (minutes <= 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final lastRefreshAt = _lastRefreshAttemptAt[publicKeyHex];
|
||||
return lastRefreshAt == null ||
|
||||
refreshTime.difference(lastRefreshAt) >= Duration(minutes: minutes);
|
||||
}
|
||||
|
||||
Future<void> toggleMetric(
|
||||
String publicKeyHex,
|
||||
String fieldKey,
|
||||
@@ -140,8 +397,17 @@ class SensorsProvider with ChangeNotifier {
|
||||
publicKeyHex,
|
||||
() => Set<String>.from(_defaultVisibleFields),
|
||||
);
|
||||
final metricOrder = _metricOrderBySensor.putIfAbsent(
|
||||
publicKeyHex,
|
||||
() => List<String>.from(_defaultMetricOrder),
|
||||
);
|
||||
var shouldPersistOrder = false;
|
||||
if (visible) {
|
||||
visibleFields.add(fieldKey);
|
||||
if (!metricOrder.contains(fieldKey)) {
|
||||
metricOrder.add(fieldKey);
|
||||
shouldPersistOrder = true;
|
||||
}
|
||||
} else {
|
||||
if (visibleFields.length == 1 && visibleFields.contains(fieldKey)) {
|
||||
return;
|
||||
@@ -149,6 +415,9 @@ class SensorsProvider with ChangeNotifier {
|
||||
visibleFields.remove(fieldKey);
|
||||
}
|
||||
await _persistVisibleMetrics();
|
||||
if (shouldPersistOrder) {
|
||||
await _persistMetricOrder();
|
||||
}
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
@@ -166,11 +435,84 @@ class SensorsProvider with ChangeNotifier {
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
Future<void> setMetricLabel(
|
||||
String publicKeyHex,
|
||||
String fieldKey,
|
||||
String? label,
|
||||
) async {
|
||||
final sensorLabels = _metricLabelsBySensor.putIfAbsent(
|
||||
publicKeyHex,
|
||||
() => <String, String>{},
|
||||
);
|
||||
final trimmed = label?.trim();
|
||||
if (trimmed == null || trimmed.isEmpty) {
|
||||
sensorLabels.remove(fieldKey);
|
||||
} else {
|
||||
sensorLabels[fieldKey] = trimmed;
|
||||
}
|
||||
if (sensorLabels.isEmpty) {
|
||||
_metricLabelsBySensor.remove(publicKeyHex);
|
||||
}
|
||||
await _persistMetricLabels();
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
Future<void> moveMetric(
|
||||
String publicKeyHex, {
|
||||
required List<String> availableFieldKeys,
|
||||
required int oldIndex,
|
||||
required int newIndex,
|
||||
}) async {
|
||||
if (oldIndex < 0 ||
|
||||
newIndex < 0 ||
|
||||
oldIndex >= availableFieldKeys.length ||
|
||||
newIndex >= availableFieldKeys.length ||
|
||||
oldIndex == newIndex) {
|
||||
return;
|
||||
}
|
||||
|
||||
final reordered = List<String>.from(
|
||||
metricOrderFor(publicKeyHex, availableFieldKeys),
|
||||
);
|
||||
final fieldKey = reordered.removeAt(oldIndex);
|
||||
reordered.insert(newIndex, fieldKey);
|
||||
|
||||
final storedTail =
|
||||
(_metricOrderBySensor[publicKeyHex] ?? _defaultMetricOrder).where(
|
||||
(key) => !reordered.contains(key),
|
||||
);
|
||||
_metricOrderBySensor[publicKeyHex] = <String>[...reordered, ...storedTail];
|
||||
await _persistMetricOrder();
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
bool isWatched(String publicKeyHex) =>
|
||||
_watchedSensorKeys.contains(publicKeyHex);
|
||||
|
||||
Contact? selfContact(
|
||||
ContactsProvider contactsProvider,
|
||||
ConnectionProvider connectionProvider,
|
||||
) {
|
||||
final selfKey = connectionProvider.deviceInfo.publicKey;
|
||||
if (selfKey == null || selfKey.isEmpty) {
|
||||
return null;
|
||||
}
|
||||
|
||||
final existing = contactsProvider.findContactByKey(
|
||||
Uint8List.fromList(selfKey),
|
||||
);
|
||||
final selfTelemetry = contactsProvider.selfTelemetry;
|
||||
if (existing != null) {
|
||||
return selfTelemetry == null
|
||||
? existing
|
||||
: existing.copyWith(telemetry: selfTelemetry);
|
||||
}
|
||||
|
||||
return _buildSelfCandidate(connectionProvider, telemetry: selfTelemetry);
|
||||
}
|
||||
|
||||
Future<void> addSensor(Contact contact) async {
|
||||
if (!contact.isChat && !contact.isRepeater) {
|
||||
if (!contact.isChat && !contact.isRepeater && !contact.isSensor) {
|
||||
return;
|
||||
}
|
||||
if (_watchedSensorKeys.contains(contact.publicKeyHex)) {
|
||||
@@ -178,43 +520,183 @@ class SensorsProvider with ChangeNotifier {
|
||||
}
|
||||
|
||||
_watchedSensorKeys.add(contact.publicKeyHex);
|
||||
final initialVisibleFields = _initialVisibleFieldsForContact(contact);
|
||||
await _persistWatchedSensors();
|
||||
_visibleFieldsBySensor[contact.publicKeyHex] = Set<String>.from(
|
||||
_defaultVisibleFields,
|
||||
);
|
||||
_visibleFieldsBySensor[contact.publicKeyHex] = initialVisibleFields;
|
||||
_fieldSpansBySensor[contact.publicKeyHex] = <String, int>{'gps': 2};
|
||||
_metricLabelsBySensor[contact.publicKeyHex] = <String, String>{};
|
||||
_metricOrderBySensor[contact.publicKeyHex] = metricOrderFor(
|
||||
contact.publicKeyHex,
|
||||
initialVisibleFields,
|
||||
);
|
||||
await _persistVisibleMetrics();
|
||||
await _persistFieldSpans();
|
||||
await _persistMetricLabels();
|
||||
await _persistMetricOrder();
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
bool _shouldAutoIncludeAvailableFields(
|
||||
String publicKeyHex,
|
||||
Set<String> storedVisibleFields,
|
||||
) {
|
||||
if (!setEquals(storedVisibleFields, _defaultVisibleFields)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final storedOrder = _metricOrderBySensor[publicKeyHex];
|
||||
if (storedOrder != null && !listEquals(storedOrder, _defaultMetricOrder)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final storedLabels = _metricLabelsBySensor[publicKeyHex];
|
||||
if (storedLabels != null && storedLabels.isNotEmpty) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final storedSpans = _fieldSpansBySensor[publicKeyHex];
|
||||
if (storedSpans != null &&
|
||||
!(storedSpans.length == 1 && storedSpans['gps'] == 2)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
Set<String> _initialVisibleFieldsForContact(Contact contact) {
|
||||
return <String>{..._defaultVisibleFields, ...sensorMetricKeysFor(contact)};
|
||||
}
|
||||
|
||||
Future<void> removeSensor(String publicKeyHex) async {
|
||||
_watchedSensorKeys.remove(publicKeyHex);
|
||||
_refreshStates.remove(publicKeyHex);
|
||||
_refreshStateUpdatedAt.remove(publicKeyHex);
|
||||
_visibleFieldsBySensor.remove(publicKeyHex);
|
||||
_fieldSpansBySensor.remove(publicKeyHex);
|
||||
_metricLabelsBySensor.remove(publicKeyHex);
|
||||
_metricOrderBySensor.remove(publicKeyHex);
|
||||
_autoRefreshMinutesBySensor.remove(publicKeyHex);
|
||||
_lastRefreshAttemptAt.remove(publicKeyHex);
|
||||
await _persistWatchedSensors();
|
||||
await _persistVisibleMetrics();
|
||||
await _persistFieldSpans();
|
||||
await _persistMetricLabels();
|
||||
await _persistMetricOrder();
|
||||
await _persistAutoRefreshMinutes();
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
List<Contact> availableCandidates(ContactsProvider contactsProvider) {
|
||||
Future<void> reorderSensors(int oldIndex, int newIndex) async {
|
||||
if (_watchedSensorKeys.length < 2) {
|
||||
return;
|
||||
}
|
||||
if (oldIndex < 0 ||
|
||||
oldIndex >= _watchedSensorKeys.length ||
|
||||
newIndex < 0 ||
|
||||
newIndex > _watchedSensorKeys.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
var targetIndex = newIndex;
|
||||
if (oldIndex < targetIndex) {
|
||||
targetIndex -= 1;
|
||||
}
|
||||
if (oldIndex == targetIndex) {
|
||||
return;
|
||||
}
|
||||
|
||||
final movedKey = _watchedSensorKeys.removeAt(oldIndex);
|
||||
_watchedSensorKeys.insert(targetIndex, movedKey);
|
||||
await _persistWatchedSensors();
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
List<Contact> availableCandidates(
|
||||
ContactsProvider contactsProvider, {
|
||||
ConnectionProvider? connectionProvider,
|
||||
}) {
|
||||
final candidates = <Contact>[
|
||||
...contactsProvider.chatContacts,
|
||||
...contactsProvider.repeaters,
|
||||
...contactsProvider.sensorContacts,
|
||||
];
|
||||
|
||||
// Add "myself" (own device) as a candidate if it has a public key
|
||||
if (connectionProvider != null) {
|
||||
final self = selfContact(contactsProvider, connectionProvider);
|
||||
if (self != null &&
|
||||
!candidates.any((c) => c.publicKeyHex == self.publicKeyHex)) {
|
||||
candidates.insert(0, self);
|
||||
}
|
||||
}
|
||||
|
||||
candidates.removeWhere((contact) => isWatched(contact.publicKeyHex));
|
||||
candidates.sort((a, b) => b.lastSeenTime.compareTo(a.lastSeenTime));
|
||||
return candidates;
|
||||
}
|
||||
|
||||
List<String> displaySensorKeys({
|
||||
required ContactsProvider contactsProvider,
|
||||
required ConnectionProvider connectionProvider,
|
||||
}) {
|
||||
if (_watchedSensorKeys.isNotEmpty) {
|
||||
return List<String>.unmodifiable(_watchedSensorKeys);
|
||||
}
|
||||
|
||||
final self = selfContact(contactsProvider, connectionProvider);
|
||||
if (self == null) {
|
||||
return const <String>[];
|
||||
}
|
||||
|
||||
return <String>[self.publicKeyHex];
|
||||
}
|
||||
|
||||
String? displaySelfKey({
|
||||
required ContactsProvider contactsProvider,
|
||||
required ConnectionProvider connectionProvider,
|
||||
}) {
|
||||
final self = selfContact(contactsProvider, connectionProvider);
|
||||
if (self == null || _watchedSensorKeys.contains(self.publicKeyHex)) {
|
||||
return null;
|
||||
}
|
||||
return self.publicKeyHex;
|
||||
}
|
||||
|
||||
Contact? contactForDisplay(
|
||||
String publicKeyHex, {
|
||||
required ContactsProvider contactsProvider,
|
||||
required ConnectionProvider connectionProvider,
|
||||
}) {
|
||||
for (final entry in contactsProvider.contacts) {
|
||||
if (entry.publicKeyHex == publicKeyHex) {
|
||||
return entry;
|
||||
}
|
||||
}
|
||||
|
||||
final self = selfContact(contactsProvider, connectionProvider);
|
||||
if (self?.publicKeyHex == publicKeyHex) {
|
||||
return self;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
Future<void> refreshAll({
|
||||
required ContactsProvider contactsProvider,
|
||||
required ConnectionProvider connectionProvider,
|
||||
}) async {
|
||||
if (_isRefreshingAll || _watchedSensorKeys.isEmpty) {
|
||||
if (_isRefreshingAll) {
|
||||
return;
|
||||
}
|
||||
|
||||
final self = selfContact(contactsProvider, connectionProvider);
|
||||
final keysToRefresh = <String>[
|
||||
if (self != null) self.publicKeyHex,
|
||||
..._watchedSensorKeys.where(
|
||||
(key) => self == null || key != self.publicKeyHex,
|
||||
),
|
||||
];
|
||||
if (keysToRefresh.isEmpty) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -222,7 +704,7 @@ class SensorsProvider with ChangeNotifier {
|
||||
notifyListeners();
|
||||
|
||||
try {
|
||||
for (final key in _watchedSensorKeys) {
|
||||
for (final key in keysToRefresh) {
|
||||
await refreshSensor(
|
||||
publicKeyHex: key,
|
||||
contactsProvider: contactsProvider,
|
||||
@@ -239,15 +721,20 @@ class SensorsProvider with ChangeNotifier {
|
||||
required String publicKeyHex,
|
||||
required ContactsProvider contactsProvider,
|
||||
required ConnectionProvider connectionProvider,
|
||||
DateTime? requestedAt,
|
||||
}) async {
|
||||
Contact? contact;
|
||||
for (final entry in contactsProvider.contacts) {
|
||||
if (entry.publicKeyHex == publicKeyHex) {
|
||||
contact = entry;
|
||||
break;
|
||||
}
|
||||
if (stateFor(publicKeyHex) == SensorRefreshState.refreshing) {
|
||||
return;
|
||||
}
|
||||
|
||||
_lastRefreshAttemptAt[publicKeyHex] = requestedAt ?? DateTime.now();
|
||||
|
||||
final contact = contactForDisplay(
|
||||
publicKeyHex,
|
||||
contactsProvider: contactsProvider,
|
||||
connectionProvider: connectionProvider,
|
||||
);
|
||||
|
||||
if (contact == null) {
|
||||
_setRefreshState(publicKeyHex, SensorRefreshState.unavailable);
|
||||
return;
|
||||
@@ -266,6 +753,48 @@ class SensorsProvider with ChangeNotifier {
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> refreshDueSensors({
|
||||
required ContactsProvider contactsProvider,
|
||||
required ConnectionProvider connectionProvider,
|
||||
DateTime? now,
|
||||
}) async {
|
||||
if (!connectionProvider.deviceInfo.isConnected ||
|
||||
_isRefreshingAll ||
|
||||
_isRunningAutoRefreshTick) {
|
||||
return;
|
||||
}
|
||||
|
||||
final refreshTime = now ?? DateTime.now();
|
||||
final dueKeys = dueAutoRefreshSensorKeys(now: refreshTime).toList();
|
||||
final self = selfContact(contactsProvider, connectionProvider);
|
||||
if (self != null &&
|
||||
!dueKeys.contains(self.publicKeyHex) &&
|
||||
_isRefreshDue(
|
||||
self.publicKeyHex,
|
||||
minutes: selfAutoRefreshMinutes,
|
||||
refreshTime: refreshTime,
|
||||
)) {
|
||||
dueKeys.insert(0, self.publicKeyHex);
|
||||
}
|
||||
if (dueKeys.isEmpty) {
|
||||
return;
|
||||
}
|
||||
|
||||
_isRunningAutoRefreshTick = true;
|
||||
try {
|
||||
for (final key in dueKeys) {
|
||||
await refreshSensor(
|
||||
publicKeyHex: key,
|
||||
contactsProvider: contactsProvider,
|
||||
connectionProvider: connectionProvider,
|
||||
requestedAt: refreshTime,
|
||||
);
|
||||
}
|
||||
} finally {
|
||||
_isRunningAutoRefreshTick = false;
|
||||
}
|
||||
}
|
||||
|
||||
void clearExpiredRefreshStates({DateTime? now}) {
|
||||
final cutoff = (now ?? DateTime.now()).subtract(_successStateRetention);
|
||||
final keysToClear = <String>[];
|
||||
@@ -297,4 +826,29 @@ class SensorsProvider with ChangeNotifier {
|
||||
_refreshStateUpdatedAt[publicKeyHex] = DateTime.now();
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
Contact? _buildSelfCandidate(
|
||||
ConnectionProvider connectionProvider, {
|
||||
ContactTelemetry? telemetry,
|
||||
}) {
|
||||
final deviceInfo = connectionProvider.deviceInfo;
|
||||
final selfKey = deviceInfo.publicKey;
|
||||
if (selfKey == null || selfKey.isEmpty) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return Contact(
|
||||
publicKey: Uint8List.fromList(selfKey),
|
||||
type: ContactType.chat,
|
||||
flags: 0,
|
||||
outPathLen: 0,
|
||||
outPath: Uint8List(64),
|
||||
advName: deviceInfo.selfName ?? 'My Device',
|
||||
lastAdvert: DateTime.now().millisecondsSinceEpoch ~/ 1000,
|
||||
advLat: deviceInfo.advLat ?? 0,
|
||||
advLon: deviceInfo.advLon ?? 0,
|
||||
lastMod: DateTime.now().millisecondsSinceEpoch ~/ 1000,
|
||||
telemetry: telemetry,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:typed_data';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import '../models/contact.dart';
|
||||
import '../services/profiles_feature_service.dart';
|
||||
import 'helpers/raw_session_retransmit.dart';
|
||||
import '../utils/voice_message_parser.dart';
|
||||
import '../services/voice_codec_service.dart';
|
||||
@@ -91,6 +93,9 @@ class VoiceProvider with ChangeNotifier {
|
||||
_restorePersistedVoiceData();
|
||||
}
|
||||
|
||||
String _storageKey() =>
|
||||
ProfileStorageScope.scopedKey(_voiceSessionsStorageKey);
|
||||
|
||||
// ── Session accessors ────────────────────────────────────────────────────
|
||||
|
||||
VoiceSession? session(String sessionId) => _sessions[sessionId];
|
||||
@@ -299,11 +304,15 @@ class VoiceProvider with ChangeNotifier {
|
||||
);
|
||||
|
||||
try {
|
||||
final pcm = await _codec.decodePackets(session.packets, session.mode);
|
||||
final decodedPcm = await _codec.decodePackets(
|
||||
session.packets,
|
||||
session.mode,
|
||||
);
|
||||
final pcm = _preparePlaybackPcm(decodedPcm, session.mode);
|
||||
debugPrint('🎙️ [VoiceProvider] decoded ${pcm.length} PCM samples');
|
||||
_playingSessionId = sessionId;
|
||||
notifyListeners();
|
||||
await _player.play(pcm);
|
||||
await _player.play(pcm, sampleRateHz: session.mode.sampleRateHz);
|
||||
} catch (e, st) {
|
||||
debugPrint('❌ [VoiceProvider] Playback error: $e\n$st');
|
||||
if (_playingSessionId == sessionId) {
|
||||
@@ -319,20 +328,27 @@ class VoiceProvider with ChangeNotifier {
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
Int16List _preparePlaybackPcm(Int16List pcm, VoicePacketMode mode) {
|
||||
return pcm;
|
||||
}
|
||||
|
||||
Future<void> clearStoredVoiceData() async {
|
||||
_sessions.clear();
|
||||
_outgoingSessions.clear();
|
||||
_ignoredIncomingSessions.clear();
|
||||
_playingSessionId = null;
|
||||
await _resetInMemoryState();
|
||||
notifyListeners();
|
||||
try {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
await prefs.remove(_voiceSessionsStorageKey);
|
||||
await prefs.remove(_storageKey());
|
||||
} catch (e) {
|
||||
debugPrint('❌ [VoiceProvider] Failed to clear stored voice data: $e');
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> reloadProfileScopedState() async {
|
||||
await _resetInMemoryState();
|
||||
await _restorePersistedVoiceData();
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
Future<void> _persistVoiceData() async {
|
||||
try {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
@@ -356,7 +372,7 @@ class VoiceProvider with ChangeNotifier {
|
||||
)
|
||||
.toList(),
|
||||
};
|
||||
await prefs.setString(_voiceSessionsStorageKey, jsonEncode(payload));
|
||||
await prefs.setString(_storageKey(), jsonEncode(payload));
|
||||
} catch (e) {
|
||||
debugPrint('❌ [VoiceProvider] Failed to persist voice data: $e');
|
||||
}
|
||||
@@ -365,7 +381,7 @@ class VoiceProvider with ChangeNotifier {
|
||||
Future<void> _restorePersistedVoiceData() async {
|
||||
try {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
final raw = prefs.getString(_voiceSessionsStorageKey);
|
||||
final raw = prefs.getString(_storageKey());
|
||||
if (raw == null || raw.isEmpty) return;
|
||||
|
||||
final parsed = jsonDecode(raw) as Map<String, dynamic>;
|
||||
@@ -428,6 +444,16 @@ class VoiceProvider with ChangeNotifier {
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _resetInMemoryState() async {
|
||||
if (_playingSessionId != null || _player.isPlaying) {
|
||||
await _player.stop();
|
||||
}
|
||||
_sessions.clear();
|
||||
_outgoingSessions.clear();
|
||||
_ignoredIncomingSessions.clear();
|
||||
_playingSessionId = null;
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_playerEventsSub.cancel();
|
||||
|
||||
394
lib/screens/add_contact_screen.dart
Normal file
394
lib/screens/add_contact_screen.dart
Normal file
@@ -0,0 +1,394 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
import '../providers/connection_provider.dart';
|
||||
import '../l10n/app_localizations.dart';
|
||||
|
||||
class AddContactScreen extends StatefulWidget {
|
||||
final String? initialAdvert;
|
||||
|
||||
const AddContactScreen({super.key, this.initialAdvert});
|
||||
|
||||
@override
|
||||
State<AddContactScreen> createState() => _AddContactScreenState();
|
||||
}
|
||||
|
||||
class _AddContactScreenState extends State<AddContactScreen> {
|
||||
final TextEditingController _advertController = TextEditingController();
|
||||
bool _isImporting = false;
|
||||
bool _importSucceeded = false;
|
||||
String? _validationError;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
if (widget.initialAdvert != null &&
|
||||
widget.initialAdvert!.trim().isNotEmpty) {
|
||||
_advertController.text = widget.initialAdvert!.trim();
|
||||
} else {
|
||||
_loadClipboardIfPresent();
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_advertController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
Future<void> _loadClipboardIfPresent() async {
|
||||
final clipboardData = await Clipboard.getData(Clipboard.kTextPlain);
|
||||
final text = clipboardData?.text?.trim();
|
||||
if (!mounted || text == null || text.isEmpty) {
|
||||
return;
|
||||
}
|
||||
if (_normalizeAdvertText(text) == null) {
|
||||
return;
|
||||
}
|
||||
_advertController.text = text;
|
||||
}
|
||||
|
||||
String? _normalizeAdvertText(String value) {
|
||||
final trimmed = value.trim();
|
||||
if (trimmed.isEmpty) {
|
||||
return null;
|
||||
}
|
||||
|
||||
var normalized = trimmed;
|
||||
if (normalized.startsWith('meshcore://')) {
|
||||
normalized = normalized.substring('meshcore://'.length);
|
||||
}
|
||||
|
||||
normalized = normalized.replaceAll(RegExp(r'\s+'), '');
|
||||
if (normalized.isEmpty) {
|
||||
return null;
|
||||
}
|
||||
|
||||
final isHex = RegExp(r'^[0-9a-fA-F]+$').hasMatch(normalized);
|
||||
if (!isHex || normalized.length.isOdd) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return normalized.toLowerCase();
|
||||
}
|
||||
|
||||
Uint8List _hexToBytes(String hex) {
|
||||
final bytes = <int>[];
|
||||
for (var i = 0; i < hex.length; i += 2) {
|
||||
bytes.add(int.parse(hex.substring(i, i + 2), radix: 16));
|
||||
}
|
||||
return Uint8List.fromList(bytes);
|
||||
}
|
||||
|
||||
Future<void> _pasteFromClipboard() async {
|
||||
final clipboardData = await Clipboard.getData(Clipboard.kTextPlain);
|
||||
final text = clipboardData?.text;
|
||||
if (text == null || text.trim().isEmpty) {
|
||||
if (!mounted) return;
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text(AppLocalizations.of(context)!.clipboardIsEmpty)),
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
setState(() {
|
||||
_advertController.text = text.trim();
|
||||
_importSucceeded = false;
|
||||
_validationError = null;
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> _importContact() async {
|
||||
final normalized = _normalizeAdvertText(_advertController.text);
|
||||
if (normalized == null) {
|
||||
setState(() {
|
||||
_validationError =
|
||||
'Enter a valid meshcore:// advert or raw hexadecimal contact advert.';
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
final advertBytes = _hexToBytes(normalized);
|
||||
if (advertBytes.length < 98) {
|
||||
setState(() {
|
||||
_validationError =
|
||||
'Advert is too short. Expected exported contact data.';
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
setState(() {
|
||||
_isImporting = true;
|
||||
_importSucceeded = false;
|
||||
_validationError = null;
|
||||
});
|
||||
|
||||
final connectionProvider = context.read<ConnectionProvider>();
|
||||
await connectionProvider.importContactAdvert(advertBytes);
|
||||
final importError = connectionProvider.error;
|
||||
|
||||
if (importError == null) {
|
||||
await connectionProvider.getContacts();
|
||||
}
|
||||
|
||||
if (!mounted) {
|
||||
return;
|
||||
}
|
||||
|
||||
setState(() {
|
||||
_isImporting = false;
|
||||
});
|
||||
|
||||
if (importError != null) {
|
||||
ScaffoldMessenger.of(
|
||||
context,
|
||||
).showSnackBar(SnackBar(content: Text(importError)));
|
||||
return;
|
||||
}
|
||||
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text(AppLocalizations.of(context)!.contactImported)),
|
||||
);
|
||||
setState(() {
|
||||
_importSucceeded = true;
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = Theme.of(context);
|
||||
final normalized = _normalizeAdvertText(_advertController.text);
|
||||
final colorScheme = theme.colorScheme;
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(title: Text(AppLocalizations.of(context)!.addContact)),
|
||||
body: ListView(
|
||||
padding: const EdgeInsets.all(16),
|
||||
children: [
|
||||
Container(
|
||||
padding: const EdgeInsets.all(18),
|
||||
decoration: BoxDecoration(
|
||||
color: colorScheme.surfaceContainerHighest.withValues(alpha: 0.5),
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
border: Border.all(
|
||||
color: colorScheme.outlineVariant.withValues(alpha: 0.6),
|
||||
),
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Container(
|
||||
padding: const EdgeInsets.all(10),
|
||||
decoration: BoxDecoration(
|
||||
color: colorScheme.primary.withValues(alpha: 0.12),
|
||||
borderRadius: BorderRadius.circular(14),
|
||||
),
|
||||
child: Icon(
|
||||
Icons.person_add_alt_1_outlined,
|
||||
color: colorScheme.primary,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: Text(
|
||||
'Import a shared contact advert',
|
||||
style: theme.textTheme.titleMedium?.copyWith(
|
||||
fontWeight: FontWeight.w700,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 14),
|
||||
Text(
|
||||
'Paste a `meshcore://...` link or raw hexadecimal advert. The app will validate it and import the contact into the connected device.',
|
||||
style: theme.textTheme.bodyMedium?.copyWith(
|
||||
color: colorScheme.onSurfaceVariant,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 14),
|
||||
Wrap(
|
||||
spacing: 8,
|
||||
runSpacing: 8,
|
||||
children: [
|
||||
_ImportHintChip(
|
||||
icon: Icons.link_outlined,
|
||||
label: AppLocalizations.of(context)!.acceptsShareLinks,
|
||||
),
|
||||
_ImportHintChip(
|
||||
icon: Icons.code_outlined,
|
||||
label: AppLocalizations.of(context)!.supportsRawHex,
|
||||
),
|
||||
_ImportHintChip(
|
||||
icon: Icons.content_paste_go_outlined,
|
||||
label: AppLocalizations.of(context)!.clipboardfriendly,
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Container(
|
||||
padding: const EdgeInsets.all(16),
|
||||
decoration: BoxDecoration(
|
||||
color: colorScheme.surface,
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
border: Border.all(
|
||||
color: colorScheme.outlineVariant.withValues(alpha: 0.7),
|
||||
),
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'Advert payload',
|
||||
style: theme.textTheme.titleSmall?.copyWith(
|
||||
fontWeight: FontWeight.w700,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 6),
|
||||
Text(
|
||||
'You can paste the full share link or only the hex payload.',
|
||||
style: theme.textTheme.bodySmall?.copyWith(
|
||||
color: colorScheme.onSurfaceVariant,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
TextField(
|
||||
controller: _advertController,
|
||||
minLines: 5,
|
||||
maxLines: 9,
|
||||
onChanged: (_) {
|
||||
if (_validationError != null || _importSucceeded) {
|
||||
setState(() {
|
||||
_importSucceeded = false;
|
||||
_validationError = null;
|
||||
});
|
||||
}
|
||||
},
|
||||
decoration: InputDecoration(
|
||||
labelText: 'Contact advert',
|
||||
hintText: 'Paste a share link or hex advert',
|
||||
alignLabelWithHint: true,
|
||||
border: const OutlineInputBorder(),
|
||||
errorText: _validationError,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Text(
|
||||
normalized == null
|
||||
? 'No valid advert detected yet'
|
||||
: 'Advert size: ${normalized.length ~/ 2} bytes',
|
||||
style: theme.textTheme.bodySmall?.copyWith(
|
||||
color: colorScheme.onSurfaceVariant,
|
||||
),
|
||||
),
|
||||
),
|
||||
TextButton.icon(
|
||||
onPressed: _isImporting ? null : _pasteFromClipboard,
|
||||
icon: Icon(Icons.content_paste_go_outlined),
|
||||
label: Text(AppLocalizations.of(context)!.paste),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
FilledButton.icon(
|
||||
onPressed: (_isImporting || _importSucceeded)
|
||||
? null
|
||||
: _importContact,
|
||||
style: FilledButton.styleFrom(
|
||||
padding: const EdgeInsets.symmetric(vertical: 14),
|
||||
),
|
||||
icon: _isImporting
|
||||
? const SizedBox(
|
||||
width: 16,
|
||||
height: 16,
|
||||
child: CircularProgressIndicator(strokeWidth: 2),
|
||||
)
|
||||
: _importSucceeded
|
||||
? const Icon(Icons.check_circle_outline)
|
||||
: const Icon(Icons.person_add_alt_1_outlined),
|
||||
label: Text(
|
||||
_isImporting
|
||||
? 'Importing...'
|
||||
: _importSucceeded
|
||||
? 'Imported'
|
||||
: 'Add Contact',
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
if (_importSucceeded)
|
||||
Container(
|
||||
padding: const EdgeInsets.all(14),
|
||||
decoration: BoxDecoration(
|
||||
color: colorScheme.primary.withValues(alpha: 0.08),
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
border: Border.all(
|
||||
color: colorScheme.primary.withValues(alpha: 0.18),
|
||||
),
|
||||
),
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Icon(
|
||||
Icons.check_circle,
|
||||
size: 18,
|
||||
color: colorScheme.primary,
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
Expanded(
|
||||
child: Text(
|
||||
'Contact imported. Paste another advert or edit the current one to import again.',
|
||||
style: theme.textTheme.bodySmall?.copyWith(
|
||||
color: colorScheme.onSurfaceVariant,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _ImportHintChip extends StatelessWidget {
|
||||
final IconData icon;
|
||||
final String label;
|
||||
|
||||
const _ImportHintChip({required this.icon, required this.label});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = Theme.of(context);
|
||||
return Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 8),
|
||||
decoration: BoxDecoration(
|
||||
color: theme.colorScheme.surface,
|
||||
borderRadius: BorderRadius.circular(999),
|
||||
border: Border.all(
|
||||
color: theme.colorScheme.outlineVariant.withValues(alpha: 0.7),
|
||||
),
|
||||
),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Icon(icon, size: 16, color: theme.colorScheme.onSurfaceVariant),
|
||||
const SizedBox(width: 6),
|
||||
Text(label, style: theme.textTheme.labelMedium),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
1028
lib/screens/discovery_screen.dart
Normal file
1028
lib/screens/discovery_screen.dart
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,3 +1,5 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
@@ -6,17 +8,21 @@ import 'package:shared_preferences/shared_preferences.dart';
|
||||
import 'package:vibration/vibration.dart';
|
||||
import '../providers/connection_provider.dart';
|
||||
import '../providers/app_provider.dart';
|
||||
import '../models/device_info.dart' show ConnectionMode;
|
||||
import '../models/device_info.dart' show ConnectionMode, DeviceInfo;
|
||||
import '../providers/messages_provider.dart';
|
||||
import '../providers/contacts_provider.dart';
|
||||
import '../theme/app_theme.dart';
|
||||
import 'messages_tab.dart';
|
||||
import 'contacts_tab.dart';
|
||||
import 'discovery_screen.dart';
|
||||
import 'sensors_tab.dart';
|
||||
import 'map_tab.dart';
|
||||
import 'repeaters_map_screen.dart';
|
||||
import 'settings_screen.dart';
|
||||
import 'device_config_screen.dart';
|
||||
import 'packet_log_screen.dart';
|
||||
import 'live_traffic_screen.dart';
|
||||
import 'profiles_screen.dart';
|
||||
import 'spectrum_scan_screen.dart';
|
||||
import '../utils/toast_logger.dart';
|
||||
import '../l10n/app_localizations.dart';
|
||||
@@ -24,6 +30,11 @@ import '../widgets/permission_request_dialog.dart';
|
||||
import '../widgets/connection_dialog.dart';
|
||||
import '../utils/battery_display_helper.dart';
|
||||
import '../services/developer_mode_service.dart';
|
||||
import '../services/mesh_map_nodes_service.dart';
|
||||
import '../services/profile_device_key_resolver.dart';
|
||||
import '../services/profile_manager.dart';
|
||||
import '../services/profile_workspace_coordinator.dart';
|
||||
import '../services/profiles_feature_service.dart';
|
||||
|
||||
enum _HomeTab { messages, contacts, sensors, map }
|
||||
|
||||
@@ -53,6 +64,7 @@ class _HomeScreenState extends State<HomeScreen>
|
||||
with TickerProviderStateMixin, WidgetsBindingObserver {
|
||||
late TabController _tabController;
|
||||
late final AppProvider _appProvider;
|
||||
late final ConnectionProvider _connectionProvider;
|
||||
int _currentIndex = 0;
|
||||
bool _isMapFullscreen = false;
|
||||
bool _showRxTxIndicators = true;
|
||||
@@ -61,6 +73,7 @@ class _HomeScreenState extends State<HomeScreen>
|
||||
bool _isContactsEnabled = true;
|
||||
bool _isSensorsEnabled = false;
|
||||
AppLifecycleState _lifecycleState = AppLifecycleState.resumed;
|
||||
String? _lastProfileDeviceKey;
|
||||
|
||||
List<_HomeTab> get _enabledTabs {
|
||||
return [
|
||||
@@ -84,6 +97,8 @@ class _HomeScreenState extends State<HomeScreen>
|
||||
super.initState();
|
||||
WidgetsBinding.instance.addObserver(this);
|
||||
_appProvider = context.read<AppProvider>();
|
||||
_connectionProvider = context.read<ConnectionProvider>();
|
||||
_connectionProvider.addListener(_handleConnectionProviderChanged);
|
||||
_isMapEnabled = _appProvider.isMapEnabled;
|
||||
_isContactsEnabled = _appProvider.isContactsEnabled;
|
||||
_isSensorsEnabled = _appProvider.isSensorsEnabled;
|
||||
@@ -93,6 +108,7 @@ class _HomeScreenState extends State<HomeScreen>
|
||||
_initTabController();
|
||||
_loadRxTxPreference();
|
||||
_loadDeveloperModePreference();
|
||||
MeshMapNodesService.syncInBackgroundIfStale();
|
||||
|
||||
// Show permission dialog after the first frame if needed
|
||||
if (widget.shouldShowPermissionDialog) {
|
||||
@@ -100,6 +116,10 @@ class _HomeScreenState extends State<HomeScreen>
|
||||
_showPermissionDialog();
|
||||
});
|
||||
}
|
||||
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
_handleConnectionProviderChanged();
|
||||
});
|
||||
}
|
||||
|
||||
void _initTabController() {
|
||||
@@ -195,7 +215,6 @@ class _HomeScreenState extends State<HomeScreen>
|
||||
|
||||
switch (tab) {
|
||||
case _HomeTab.messages:
|
||||
context.read<MessagesProvider>().markAllAsRead();
|
||||
break;
|
||||
case _HomeTab.contacts:
|
||||
context.read<ContactsProvider>().markAllAsViewed();
|
||||
@@ -216,17 +235,58 @@ class _HomeScreenState extends State<HomeScreen>
|
||||
);
|
||||
}
|
||||
|
||||
void _handleConnectionProviderChanged() {
|
||||
final deviceKey = ProfileDeviceKeyResolver.resolve(
|
||||
deviceInfo: _connectionProvider.deviceInfo,
|
||||
connectionMode: _connectionProvider.connectionMode,
|
||||
);
|
||||
if (_lastProfileDeviceKey == deviceKey) {
|
||||
return;
|
||||
}
|
||||
_lastProfileDeviceKey = deviceKey;
|
||||
if (deviceKey == null || !mounted) {
|
||||
return;
|
||||
}
|
||||
unawaited(
|
||||
context
|
||||
.read<ProfileWorkspaceCoordinator>()
|
||||
.syncActiveProfileForCurrentDevice(),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_connectionProvider.removeListener(_handleConnectionProviderChanged);
|
||||
WidgetsBinding.instance.removeObserver(this);
|
||||
_appProvider.setFastLocationUiActive(false);
|
||||
_appProvider.removeListener(_handleAppProviderChanged);
|
||||
_tabController.removeListener(_onTabChanged);
|
||||
_tabController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void _openLiveTraffic(ConnectionProvider provider) {
|
||||
openLiveTrafficScreen(context, provider);
|
||||
}
|
||||
|
||||
@override
|
||||
void didChangeAppLifecycleState(AppLifecycleState state) {
|
||||
_lifecycleState = state;
|
||||
_syncFastLocationUiState();
|
||||
if (state == AppLifecycleState.resumed) {
|
||||
MeshMapNodesService.syncInBackgroundIfStale();
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _loadRxTxPreference() async {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_showRxTxIndicators = prefs.getBool('show_rx_tx_indicators') ?? true;
|
||||
_showRxTxIndicators =
|
||||
prefs.getBool(
|
||||
ProfileStorageScope.scopedKey('show_rx_tx_indicators'),
|
||||
) ??
|
||||
true;
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -239,16 +299,6 @@ class _HomeScreenState extends State<HomeScreen>
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
WidgetsBinding.instance.removeObserver(this);
|
||||
_appProvider.setFastLocationUiActive(false);
|
||||
_appProvider.removeListener(_handleAppProviderChanged);
|
||||
_tabController.removeListener(_onTabChanged);
|
||||
_tabController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void _showPermissionDialog() {
|
||||
if (!mounted) return;
|
||||
|
||||
@@ -344,7 +394,7 @@ class _HomeScreenState extends State<HomeScreen>
|
||||
),
|
||||
),
|
||||
title: Text(l10n.flood),
|
||||
subtitle: const Text('Relay through repeaters across the mesh'),
|
||||
subtitle: Text(l10n.relayThroughRepeatersAcrossTheMesh),
|
||||
trailing: const Icon(Icons.chevron_right_rounded),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
@@ -370,7 +420,7 @@ class _HomeScreenState extends State<HomeScreen>
|
||||
),
|
||||
),
|
||||
title: Text(l10n.direct),
|
||||
subtitle: const Text('Nearby only, without repeater flooding'),
|
||||
subtitle: Text(l10n.nearbyOnlyWithoutRepeaterFlooding),
|
||||
trailing: const Icon(Icons.chevron_right_rounded),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
@@ -384,41 +434,184 @@ class _HomeScreenState extends State<HomeScreen>
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildActivityBadge({
|
||||
required String label,
|
||||
required int count,
|
||||
required bool isActive,
|
||||
required Color activeColor,
|
||||
}) {
|
||||
final color = isActive ? activeColor : Colors.grey;
|
||||
return Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 5),
|
||||
decoration: BoxDecoration(
|
||||
color: color.withValues(alpha: 0.12),
|
||||
borderRadius: BorderRadius.circular(999),
|
||||
void _showDeviceInfoSheet(BuildContext context, DeviceInfo deviceInfo) {
|
||||
// Request self telemetry so it's fresh
|
||||
context.read<ConnectionProvider>().requestSelfTelemetry();
|
||||
|
||||
showModalBottomSheet(
|
||||
context: context,
|
||||
shape: const RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.vertical(top: Radius.circular(16)),
|
||||
),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Container(
|
||||
width: 7,
|
||||
height: 7,
|
||||
decoration: BoxDecoration(shape: BoxShape.circle, color: color),
|
||||
),
|
||||
const SizedBox(width: 6),
|
||||
Text(
|
||||
'$label:$count',
|
||||
style: TextStyle(
|
||||
fontSize: 11,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: color,
|
||||
builder: (context) => Consumer<ContactsProvider>(
|
||||
builder: (context, contactsProvider, child) {
|
||||
final telemetry = contactsProvider.selfTelemetry;
|
||||
|
||||
return SafeArea(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(20),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Container(
|
||||
width: 40,
|
||||
height: 4,
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).dividerColor,
|
||||
borderRadius: BorderRadius.circular(2),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
deviceInfo.selfName ?? deviceInfo.deviceName ?? 'Device',
|
||||
style: Theme.of(context).textTheme.titleMedium,
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
_deviceInfoRow(
|
||||
context,
|
||||
Icons.bluetooth,
|
||||
'BLE Signal',
|
||||
deviceInfo.signalRssi != null
|
||||
? '${deviceInfo.signalRssi} dBm'
|
||||
: 'N/A',
|
||||
),
|
||||
if (deviceInfo.batteryPercent != null)
|
||||
_deviceInfoRow(
|
||||
context,
|
||||
BatteryDisplayHelper.getBatteryIcon(
|
||||
deviceInfo.batteryPercent!,
|
||||
),
|
||||
'Battery',
|
||||
'${deviceInfo.batteryPercent!.round()}%',
|
||||
),
|
||||
if (deviceInfo.batteryMilliVolts != null)
|
||||
_deviceInfoRow(
|
||||
context,
|
||||
Icons.bolt,
|
||||
'Voltage',
|
||||
'${(deviceInfo.batteryMilliVolts! / 1000).toStringAsFixed(2)}V',
|
||||
),
|
||||
if (deviceInfo.storageUsedKb != null &&
|
||||
deviceInfo.storageTotalKb != null)
|
||||
_deviceInfoRow(
|
||||
context,
|
||||
Icons.storage,
|
||||
'Storage',
|
||||
'${deviceInfo.storageUsedKb} / ${deviceInfo.storageTotalKb} KB',
|
||||
),
|
||||
if (deviceInfo.firmwareVersion != null)
|
||||
_deviceInfoRow(
|
||||
context,
|
||||
Icons.system_update,
|
||||
'Firmware',
|
||||
'v${deviceInfo.firmwareVersion}',
|
||||
),
|
||||
if (deviceInfo.radioFreq != null)
|
||||
_deviceInfoRow(
|
||||
context,
|
||||
Icons.radio,
|
||||
'Frequency',
|
||||
'${(deviceInfo.radioFreq! / 1000).toStringAsFixed(3)} MHz',
|
||||
),
|
||||
if (deviceInfo.txPower != null)
|
||||
_deviceInfoRow(
|
||||
context,
|
||||
Icons.power,
|
||||
'TX Power',
|
||||
'${deviceInfo.txPower} dBm',
|
||||
),
|
||||
if (telemetry != null) ...[
|
||||
const Divider(height: 24),
|
||||
Text(
|
||||
'Self Telemetry',
|
||||
style: Theme.of(context).textTheme.titleSmall,
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
if (telemetry.temperature != null)
|
||||
_deviceInfoRow(
|
||||
context,
|
||||
Icons.thermostat,
|
||||
'Temperature',
|
||||
'${telemetry.temperature!.toStringAsFixed(1)}°C',
|
||||
),
|
||||
if (telemetry.humidity != null)
|
||||
_deviceInfoRow(
|
||||
context,
|
||||
Icons.water_drop,
|
||||
'Humidity',
|
||||
'${telemetry.humidity!.toStringAsFixed(1)}%',
|
||||
),
|
||||
if (telemetry.pressure != null)
|
||||
_deviceInfoRow(
|
||||
context,
|
||||
Icons.compress,
|
||||
'Pressure',
|
||||
'${telemetry.pressure!.toStringAsFixed(1)} hPa',
|
||||
),
|
||||
if (telemetry.gpsLocation != null)
|
||||
_deviceInfoRow(
|
||||
context,
|
||||
Icons.gps_fixed,
|
||||
'GPS',
|
||||
'${telemetry.gpsLocation!.latitude.toStringAsFixed(5)}, ${telemetry.gpsLocation!.longitude.toStringAsFixed(5)}',
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _deviceInfoRow(
|
||||
BuildContext context,
|
||||
IconData icon,
|
||||
String label,
|
||||
String value,
|
||||
) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 6),
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(icon, size: 18, color: Theme.of(context).colorScheme.primary),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: Text(label, style: Theme.of(context).textTheme.bodyMedium),
|
||||
),
|
||||
Text(
|
||||
value,
|
||||
style: Theme.of(
|
||||
context,
|
||||
).textTheme.bodyMedium?.copyWith(fontWeight: FontWeight.w600),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildMiniSignalBars({required int activeBars, required Color color}) {
|
||||
final inactive = Colors.grey.withValues(alpha: 0.3);
|
||||
return Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.end,
|
||||
children: [
|
||||
for (var i = 0; i < 5; i++) ...[
|
||||
if (i > 0) const SizedBox(width: 1.5),
|
||||
Container(
|
||||
width: 2.5,
|
||||
height: 4.0 + (i * 2),
|
||||
decoration: BoxDecoration(
|
||||
color: i < activeBars ? color : inactive,
|
||||
borderRadius: BorderRadius.circular(1.5),
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildCompactActivityIndicator({
|
||||
required bool rxActive,
|
||||
required bool txActive,
|
||||
@@ -575,6 +768,9 @@ class _HomeScreenState extends State<HomeScreen>
|
||||
}
|
||||
|
||||
context.watch<AppProvider>();
|
||||
final activeProfileId = context.select<ProfileManager, String>(
|
||||
(manager) => manager.activeProfileId,
|
||||
);
|
||||
|
||||
final enabledTabs = _enabledTabs;
|
||||
final isMapTabActive = _currentTab == _HomeTab.map;
|
||||
@@ -599,7 +795,7 @@ class _HomeScreenState extends State<HomeScreen>
|
||||
onPressed: () async {
|
||||
await provider.disconnect();
|
||||
},
|
||||
icon: const Icon(Icons.power_settings_new),
|
||||
icon: Icon(Icons.power_settings_new),
|
||||
tooltip: AppLocalizations.of(context)!.disconnect,
|
||||
color: Colors.red.shade700,
|
||||
);
|
||||
@@ -611,6 +807,9 @@ class _HomeScreenState extends State<HomeScreen>
|
||||
icon: const Icon(Icons.more_vert),
|
||||
itemBuilder: (context) {
|
||||
final items = <PopupMenuEntry<void>>[];
|
||||
final profilesEnabled = context
|
||||
.read<ProfileManager>()
|
||||
.profilesEnabled;
|
||||
|
||||
if (_isDeveloperModeEnabled) {
|
||||
items.add(
|
||||
@@ -618,8 +817,8 @@ class _HomeScreenState extends State<HomeScreen>
|
||||
child: Row(
|
||||
children: [
|
||||
const Icon(Icons.radar),
|
||||
const SizedBox(width: 8),
|
||||
const Text('Spectrum Scan'),
|
||||
SizedBox(width: 8),
|
||||
Text(AppLocalizations.of(context)!.spectrumScan),
|
||||
],
|
||||
),
|
||||
onTap: () {
|
||||
@@ -642,8 +841,101 @@ class _HomeScreenState extends State<HomeScreen>
|
||||
PopupMenuItem(
|
||||
child: Row(
|
||||
children: [
|
||||
const Icon(Icons.settings),
|
||||
const Icon(Icons.radar_outlined),
|
||||
const SizedBox(width: 8),
|
||||
Text(AppLocalizations.of(context)!.liveTraffic),
|
||||
],
|
||||
),
|
||||
onTap: () {
|
||||
final navigator = Navigator.of(context);
|
||||
final provider = context.read<ConnectionProvider>();
|
||||
Future.delayed(Duration.zero, () {
|
||||
if (!mounted) return;
|
||||
navigator.push(
|
||||
MaterialPageRoute(
|
||||
builder: (_) => LiveTrafficScreen.fromProvider(
|
||||
provider,
|
||||
openPacketLogs: () {
|
||||
navigator.push(
|
||||
MaterialPageRoute(
|
||||
builder: (_) => PacketLogScreen(
|
||||
bleService: provider.bleService,
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
});
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
items.add(
|
||||
PopupMenuItem(
|
||||
child: Row(
|
||||
children: [
|
||||
const Icon(Icons.router_outlined),
|
||||
const SizedBox(width: 8),
|
||||
Text(AppLocalizations.of(context)!.repeatersMap),
|
||||
],
|
||||
),
|
||||
onTap: () {
|
||||
final navigator = Navigator.of(context);
|
||||
Future.delayed(Duration.zero, () {
|
||||
if (!mounted) return;
|
||||
navigator.push(
|
||||
MaterialPageRoute(
|
||||
builder: (context) =>
|
||||
const RepeatersMapScreen(),
|
||||
fullscreenDialog: true,
|
||||
),
|
||||
);
|
||||
});
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
items.add(
|
||||
PopupMenuItem(
|
||||
child: Row(
|
||||
children: [
|
||||
const Icon(Icons.person_search),
|
||||
const SizedBox(width: 8),
|
||||
Consumer<ContactsProvider>(
|
||||
builder: (context, contactsProvider, child) {
|
||||
final pendingCount =
|
||||
contactsProvider.pendingAdverts.length;
|
||||
return Text(
|
||||
pendingCount > 0
|
||||
? 'Discovery ($pendingCount)'
|
||||
: 'Discovery',
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
onTap: () {
|
||||
final navigator = Navigator.of(context);
|
||||
Future.delayed(Duration.zero, () {
|
||||
if (!mounted) return;
|
||||
navigator.push(
|
||||
MaterialPageRoute(
|
||||
builder: (context) => const DiscoveryScreen(),
|
||||
),
|
||||
);
|
||||
});
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
items.add(
|
||||
PopupMenuItem(
|
||||
child: Row(
|
||||
children: [
|
||||
const Icon(Icons.settings),
|
||||
SizedBox(width: 8),
|
||||
Text(AppLocalizations.of(context)!.settings),
|
||||
],
|
||||
),
|
||||
@@ -668,6 +960,31 @@ class _HomeScreenState extends State<HomeScreen>
|
||||
),
|
||||
);
|
||||
|
||||
if (profilesEnabled) {
|
||||
items.add(
|
||||
PopupMenuItem(
|
||||
child: Row(
|
||||
children: [
|
||||
const Icon(Icons.layers_outlined),
|
||||
const SizedBox(width: 8),
|
||||
Text(AppLocalizations.of(context)!.profiles),
|
||||
],
|
||||
),
|
||||
onTap: () {
|
||||
final navigator = Navigator.of(context);
|
||||
Future.delayed(Duration.zero, () {
|
||||
if (!mounted) return;
|
||||
navigator.push(
|
||||
MaterialPageRoute(
|
||||
builder: (context) => const ProfilesScreen(),
|
||||
),
|
||||
);
|
||||
});
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return items;
|
||||
},
|
||||
),
|
||||
@@ -679,6 +996,9 @@ class _HomeScreenState extends State<HomeScreen>
|
||||
switch (tab) {
|
||||
case _HomeTab.messages:
|
||||
return MessagesTab(
|
||||
isActive:
|
||||
_currentTab == _HomeTab.messages &&
|
||||
_lifecycleState == AppLifecycleState.resumed,
|
||||
onNavigateToMap: _isMapEnabled
|
||||
? () => _navigateToTab(_HomeTab.map)
|
||||
: null,
|
||||
@@ -691,9 +1011,14 @@ class _HomeScreenState extends State<HomeScreen>
|
||||
onNavigateToMessages: () => _navigateToTab(_HomeTab.messages),
|
||||
);
|
||||
case _HomeTab.sensors:
|
||||
return const SensorsTab();
|
||||
return SensorsTab(
|
||||
isActive:
|
||||
_currentTab == _HomeTab.sensors &&
|
||||
_lifecycleState == AppLifecycleState.resumed,
|
||||
);
|
||||
case _HomeTab.map:
|
||||
return MapTab(
|
||||
key: ValueKey<String>('map:$activeProfileId'),
|
||||
onFullscreenChanged: (isFullscreen) {
|
||||
setState(() {
|
||||
_isMapFullscreen = isFullscreen;
|
||||
@@ -738,7 +1063,7 @@ class _HomeScreenState extends State<HomeScreen>
|
||||
Icons.message,
|
||||
unreadCount,
|
||||
),
|
||||
text: AppLocalizations.of(context)!.messages,
|
||||
text: 'Chat',
|
||||
);
|
||||
case _HomeTab.contacts:
|
||||
return Tab(
|
||||
@@ -750,7 +1075,7 @@ class _HomeScreenState extends State<HomeScreen>
|
||||
);
|
||||
case _HomeTab.map:
|
||||
return Tab(
|
||||
icon: const Icon(Icons.map),
|
||||
icon: Icon(Icons.map),
|
||||
text: AppLocalizations.of(context)!.map,
|
||||
);
|
||||
case _HomeTab.sensors:
|
||||
@@ -775,19 +1100,35 @@ class _HomeScreenState extends State<HomeScreen>
|
||||
final isTcpConnected = provider.connectionMode == ConnectionMode.tcp;
|
||||
|
||||
if (!isConnected) {
|
||||
// Disconnected state: show connect button
|
||||
final buttonLabel = provider.isReconnecting
|
||||
? '${provider.reconnectionAttempt}/${provider.maxReconnectionAttempts}'
|
||||
: AppLocalizations.of(context)!.connect;
|
||||
return Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Text(
|
||||
AppLocalizations.of(context)!.appTitle,
|
||||
style: const TextStyle(
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Text(
|
||||
AppLocalizations.of(context)!.appTitle,
|
||||
style: const TextStyle(
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
provider.isReconnecting
|
||||
? 'Restoring previous link'
|
||||
: 'No device connected',
|
||||
style: Theme.of(context).textTheme.bodySmall?.copyWith(
|
||||
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
ElevatedButton.icon(
|
||||
FilledButton.icon(
|
||||
onPressed: provider.isReconnecting
|
||||
? null
|
||||
: () => _showConnectionDialog(context),
|
||||
@@ -798,22 +1139,19 @@ class _HomeScreenState extends State<HomeScreen>
|
||||
child: CircularProgressIndicator(
|
||||
strokeWidth: 2,
|
||||
valueColor: AlwaysStoppedAnimation<Color>(
|
||||
Colors.black54,
|
||||
Colors.white70,
|
||||
),
|
||||
),
|
||||
)
|
||||
: const Icon(Icons.bluetooth, size: 18),
|
||||
label: Text(
|
||||
provider.isReconnecting
|
||||
? '${provider.reconnectionAttempt}/${provider.maxReconnectionAttempts}'
|
||||
: AppLocalizations.of(context)!.connect,
|
||||
),
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: Colors.white,
|
||||
foregroundColor: Colors.black87,
|
||||
elevation: 0,
|
||||
: const Icon(Icons.add_link_rounded, size: 18),
|
||||
label: Text(buttonLabel),
|
||||
style: FilledButton.styleFrom(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 16,
|
||||
vertical: 12,
|
||||
),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -821,7 +1159,7 @@ class _HomeScreenState extends State<HomeScreen>
|
||||
const SizedBox(width: 8),
|
||||
IconButton(
|
||||
onPressed: () => provider.cancelReconnection(),
|
||||
icon: const Icon(Icons.close, size: 20),
|
||||
icon: Icon(Icons.close, size: 20),
|
||||
tooltip: AppLocalizations.of(context)!.cancelReconnection,
|
||||
style: IconButton.styleFrom(
|
||||
backgroundColor: Colors.red.shade700,
|
||||
@@ -880,50 +1218,45 @@ class _HomeScreenState extends State<HomeScreen>
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
const SizedBox(height: 2),
|
||||
Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Icon(
|
||||
isTcpConnected
|
||||
? Icons.wifi_rounded
|
||||
: Icons.bluetooth_connected_rounded,
|
||||
size: 13,
|
||||
color: signalColor,
|
||||
),
|
||||
if (!isTcpConnected &&
|
||||
deviceInfo.signalRssi != null) ...[
|
||||
const SizedBox(width: 4),
|
||||
SizedBox(
|
||||
width: 28,
|
||||
child: Text(
|
||||
'${deviceInfo.signalRssi}',
|
||||
style: TextStyle(
|
||||
fontSize: 10,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: signalColor,
|
||||
),
|
||||
maxLines: 1,
|
||||
),
|
||||
),
|
||||
],
|
||||
if (deviceInfo.batteryPercent != null) ...[
|
||||
const SizedBox(width: 8),
|
||||
GestureDetector(
|
||||
onTap: () =>
|
||||
_showDeviceInfoSheet(context, deviceInfo),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Icon(
|
||||
BatteryDisplayHelper.getBatteryIcon(
|
||||
deviceInfo.batteryPercent!,
|
||||
),
|
||||
isTcpConnected
|
||||
? Icons.wifi_rounded
|
||||
: Icons.bluetooth_connected_rounded,
|
||||
size: 13,
|
||||
color:
|
||||
BatteryDisplayHelper.getBatteryColor(
|
||||
deviceInfo.batteryPercent!,
|
||||
),
|
||||
color: signalColor,
|
||||
),
|
||||
const SizedBox(width: 4),
|
||||
SizedBox(
|
||||
width: 30,
|
||||
child: Text(
|
||||
if (!isTcpConnected &&
|
||||
deviceInfo.signalRssi != null) ...[
|
||||
const SizedBox(width: 4),
|
||||
_buildMiniSignalBars(
|
||||
activeBars:
|
||||
BatteryDisplayHelper.getSignalBars(
|
||||
deviceInfo.signalRssi!,
|
||||
),
|
||||
color: signalColor,
|
||||
),
|
||||
],
|
||||
if (deviceInfo.batteryPercent != null) ...[
|
||||
const SizedBox(width: 8),
|
||||
Icon(
|
||||
BatteryDisplayHelper.getBatteryIcon(
|
||||
deviceInfo.batteryPercent!,
|
||||
),
|
||||
size: 13,
|
||||
color:
|
||||
BatteryDisplayHelper.getBatteryColor(
|
||||
deviceInfo.batteryPercent!,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 2),
|
||||
Text(
|
||||
'${deviceInfo.batteryPercent!.round()}%',
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: TextStyle(
|
||||
fontSize: 10,
|
||||
fontWeight: FontWeight.w600,
|
||||
@@ -933,9 +1266,9 @@ class _HomeScreenState extends State<HomeScreen>
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
@@ -1022,6 +1355,7 @@ class _HomeScreenState extends State<HomeScreen>
|
||||
SizedBox(width: isTight ? 8 : 12),
|
||||
if (_showRxTxIndicators)
|
||||
GestureDetector(
|
||||
onTap: () => _openLiveTraffic(provider),
|
||||
onLongPress: () {
|
||||
Navigator.push(
|
||||
context,
|
||||
@@ -1031,44 +1365,13 @@ class _HomeScreenState extends State<HomeScreen>
|
||||
),
|
||||
);
|
||||
},
|
||||
child: isTight
|
||||
? _buildCompactActivityIndicator(
|
||||
rxActive: provider.rxActivity,
|
||||
txActive: provider.txActivity,
|
||||
)
|
||||
: Container(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 10,
|
||||
vertical: 8,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color: theme.colorScheme.surfaceContainerHigh
|
||||
.withValues(alpha: 0.85),
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.end,
|
||||
children: [
|
||||
_buildActivityBadge(
|
||||
label: 'RX',
|
||||
count: provider.rxPacketCount,
|
||||
isActive: provider.rxActivity,
|
||||
activeColor: Colors.green,
|
||||
),
|
||||
const SizedBox(height: 6),
|
||||
_buildActivityBadge(
|
||||
label: 'TX',
|
||||
count: provider.txPacketCount,
|
||||
isActive: provider.txActivity,
|
||||
activeColor: Colors.blue,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
child: _buildCompactActivityIndicator(
|
||||
rxActive: provider.rxActivity,
|
||||
txActive: provider.txActivity,
|
||||
),
|
||||
)
|
||||
else
|
||||
SizedBox(width: isTight ? 24 : 74),
|
||||
const SizedBox(width: 24),
|
||||
],
|
||||
);
|
||||
},
|
||||
|
||||
1154
lib/screens/live_traffic_screen.dart
Normal file
1154
lib/screens/live_traffic_screen.dart
Normal file
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -8,6 +8,7 @@ import 'package:meshcore_client/meshcore_client.dart';
|
||||
import '../l10n/app_localizations.dart';
|
||||
import '../providers/connection_provider.dart';
|
||||
import '../providers/contacts_provider.dart';
|
||||
import '../services/mesh_map_nodes_service.dart';
|
||||
import '../services/route_hash_preferences.dart';
|
||||
import '../utils/log_rx_route_decoder.dart';
|
||||
|
||||
@@ -60,7 +61,7 @@ class _PacketLogScreenState extends State<PacketLogScreen> {
|
||||
if (context.mounted) {
|
||||
ScaffoldMessenger.of(
|
||||
context,
|
||||
).showSnackBar(const SnackBar(content: Text('No logs to export')));
|
||||
).showSnackBar(SnackBar(content: Text(AppLocalizations.of(context)!.noLogsToExport)));
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -107,7 +108,7 @@ class _PacketLogScreenState extends State<PacketLogScreen> {
|
||||
if (context.mounted) {
|
||||
ScaffoldMessenger.of(
|
||||
context,
|
||||
).showSnackBar(const SnackBar(content: Text('No logs to export')));
|
||||
).showSnackBar(SnackBar(content: Text(AppLocalizations.of(context)!.noLogsToExport)));
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -154,8 +155,8 @@ class _PacketLogScreenState extends State<PacketLogScreen> {
|
||||
void _copyToClipboard(BuildContext context, BlePacketLog log) {
|
||||
Clipboard.setData(ClipboardData(text: log.hexData));
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(
|
||||
content: Text('Hex data copied to clipboard'),
|
||||
SnackBar(
|
||||
content: Text(AppLocalizations.of(context)!.hexDataCopiedToClipboard),
|
||||
duration: Duration(seconds: 1),
|
||||
),
|
||||
);
|
||||
@@ -183,7 +184,7 @@ class _PacketLogScreenState extends State<PacketLogScreen> {
|
||||
setState(() {});
|
||||
if (parentContext.mounted) {
|
||||
ScaffoldMessenger.of(parentContext).showSnackBar(
|
||||
const SnackBar(content: Text('Packet logs cleared')),
|
||||
SnackBar(content: Text(AppLocalizations.of(context)!.packetLogsCleared)),
|
||||
);
|
||||
}
|
||||
},
|
||||
@@ -204,7 +205,7 @@ class _PacketLogScreenState extends State<PacketLogScreen> {
|
||||
title: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const Text('BLE Packet Logs'),
|
||||
Text(AppLocalizations.of(context)!.blePacketLogs),
|
||||
Text(
|
||||
'${logs.length} packets',
|
||||
style: Theme.of(context).textTheme.bodySmall,
|
||||
@@ -315,23 +316,23 @@ class _PacketLogScreenState extends State<PacketLogScreen> {
|
||||
icon: const Icon(Icons.share),
|
||||
tooltip: 'Export logs',
|
||||
itemBuilder: (context) => [
|
||||
const PopupMenuItem(
|
||||
PopupMenuItem(
|
||||
value: 'csv',
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(Icons.table_chart),
|
||||
SizedBox(width: 8),
|
||||
Text('Export as CSV'),
|
||||
const Icon(Icons.table_chart),
|
||||
const SizedBox(width: 8),
|
||||
Text(AppLocalizations.of(context)!.exportAsCsv),
|
||||
],
|
||||
),
|
||||
),
|
||||
const PopupMenuItem(
|
||||
PopupMenuItem(
|
||||
value: 'txt',
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(Icons.text_snippet),
|
||||
SizedBox(width: 8),
|
||||
Text('Export as Text'),
|
||||
const Icon(Icons.text_snippet),
|
||||
const SizedBox(width: 8),
|
||||
Text(AppLocalizations.of(context)!.exportAsText),
|
||||
],
|
||||
),
|
||||
),
|
||||
@@ -410,8 +411,8 @@ class _PacketLogScreenState extends State<PacketLogScreen> {
|
||||
_filterDirection = null;
|
||||
});
|
||||
},
|
||||
icon: const Icon(Icons.clear_all),
|
||||
label: const Text('Clear filters'),
|
||||
icon: Icon(Icons.clear_all),
|
||||
label: Text(AppLocalizations.of(context)!.clearFilters),
|
||||
),
|
||||
],
|
||||
],
|
||||
@@ -518,24 +519,24 @@ class _PacketLogCard extends StatelessWidget {
|
||||
children: [
|
||||
_FactCard(
|
||||
icon: isRx ? Icons.call_received : Icons.call_made,
|
||||
label: 'Direction',
|
||||
label: AppLocalizations.of(context)!.direction,
|
||||
value: isRx ? 'RX' : 'TX',
|
||||
accent: directionColor,
|
||||
),
|
||||
_FactCard(
|
||||
icon: Icons.data_object,
|
||||
label: 'Size',
|
||||
label: AppLocalizations.of(context)!.size,
|
||||
value: '${log.rawData.length} bytes',
|
||||
),
|
||||
_FactCard(
|
||||
icon: Icons.schedule,
|
||||
label: 'Captured',
|
||||
label: AppLocalizations.of(context)!.captured,
|
||||
value: _formatTimestamp(log.timestamp),
|
||||
),
|
||||
if (log.responseCode != null)
|
||||
_FactCard(
|
||||
icon: Icons.sell,
|
||||
label: 'Opcode',
|
||||
label: AppLocalizations.of(context)!.opcode,
|
||||
value: log.opcodeName,
|
||||
),
|
||||
],
|
||||
@@ -564,7 +565,7 @@ class _PacketLogCard extends StatelessWidget {
|
||||
const SizedBox(height: 10),
|
||||
if (rxInfo?.rssiDbm != null)
|
||||
_SignalMeter(
|
||||
label: 'RSSI',
|
||||
label: AppLocalizations.of(context)!.rssi,
|
||||
valueLabel: '${rxInfo!.rssiDbm} dBm',
|
||||
normalized: _normalizeRssi(
|
||||
rxInfo.rssiDbm!.toDouble(),
|
||||
@@ -572,9 +573,9 @@ class _PacketLogCard extends StatelessWidget {
|
||||
color: _rssiColor(rxInfo.rssiDbm!.toDouble()),
|
||||
),
|
||||
if (rxInfo?.snrDb != null) ...[
|
||||
const SizedBox(height: 8),
|
||||
SizedBox(height: 8),
|
||||
_SignalMeter(
|
||||
label: 'SNR',
|
||||
label: AppLocalizations.of(context)!.snr,
|
||||
valueLabel:
|
||||
'${rxInfo!.snrDb!.toStringAsFixed(1)} dB',
|
||||
normalized: _normalizeSnr(rxInfo.snrDb!),
|
||||
@@ -746,49 +747,82 @@ class _DecodedRouteSection extends StatelessWidget {
|
||||
connectionProvider.deviceInfo.selfName ??
|
||||
connectionProvider.deviceInfo.displayName;
|
||||
|
||||
return FutureBuilder<int>(
|
||||
future: RouteHashPreferences.getHashSize(),
|
||||
return FutureBuilder<List<dynamic>>(
|
||||
future: Future.wait<dynamic>([
|
||||
RouteHashPreferences.getHashSize(),
|
||||
MeshMapNodesService.loadCachedNodes(
|
||||
cacheTtl: MeshMapNodesService.traceCacheTtl,
|
||||
),
|
||||
]),
|
||||
builder: (context, snapshot) {
|
||||
final decodedRoute = LogRxRouteDecoder.decode(
|
||||
log.rawData,
|
||||
preferredHashSize: snapshot.data,
|
||||
preferredHashSize: snapshot.data?.first as int?,
|
||||
);
|
||||
if (decodedRoute == null) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
|
||||
final cachedNodes = snapshot.hasData
|
||||
? snapshot.data![1] as List<MeshMapNode>
|
||||
: const <MeshMapNode>[];
|
||||
final resolvedPath = decodedRoute.hopHashes
|
||||
.map(
|
||||
(hashHex) => LogRxRouteDecoder.resolveHash(
|
||||
(hashHex) => _resolveHashWithFallback(
|
||||
hashHex,
|
||||
contacts: contacts,
|
||||
cachedNodes: cachedNodes,
|
||||
ownPublicKey: ownPublicKey,
|
||||
ownName: ownName,
|
||||
),
|
||||
)
|
||||
.toList();
|
||||
final originalSender = resolvedPath.isEmpty ? null : resolvedPath.first;
|
||||
|
||||
return _RouteSection(
|
||||
route: decodedRoute,
|
||||
path: resolvedPath,
|
||||
originalSender: originalSender,
|
||||
);
|
||||
return _RouteSection(route: decodedRoute, path: resolvedPath);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
ResolvedNodeHash _resolveHashWithFallback(
|
||||
String hashHex, {
|
||||
required List<Contact> contacts,
|
||||
required List<MeshMapNode> cachedNodes,
|
||||
required Uint8List? ownPublicKey,
|
||||
required String? ownName,
|
||||
}) {
|
||||
final localResolved = LogRxRouteDecoder.resolveHash(
|
||||
hashHex,
|
||||
contacts: contacts,
|
||||
ownPublicKey: ownPublicKey,
|
||||
ownName: ownName,
|
||||
);
|
||||
if (localResolved.matchCount > 0 || localResolved.isOwnNode) {
|
||||
return localResolved;
|
||||
}
|
||||
|
||||
final normalizedHashHex = hashHex.toLowerCase();
|
||||
final matches = cachedNodes
|
||||
.where((node) => node.publicKey.startsWith(normalizedHashHex))
|
||||
.toList();
|
||||
if (matches.isEmpty) {
|
||||
return localResolved;
|
||||
}
|
||||
|
||||
matches.sort((a, b) => b.updatedAtMs.compareTo(a.updatedAtMs));
|
||||
return ResolvedNodeHash(
|
||||
hashHex: normalizedHashHex,
|
||||
label: matches.first.name,
|
||||
isOwnNode: false,
|
||||
isUniqueMatch: matches.length == 1,
|
||||
matchCount: matches.length,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _RouteSection extends StatelessWidget {
|
||||
final DecodedLogRxRoute route;
|
||||
final List<ResolvedNodeHash> path;
|
||||
final ResolvedNodeHash? originalSender;
|
||||
|
||||
const _RouteSection({
|
||||
required this.route,
|
||||
required this.path,
|
||||
required this.originalSender,
|
||||
});
|
||||
const _RouteSection({required this.route, required this.path});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@@ -820,26 +854,20 @@ class _RouteSection extends StatelessWidget {
|
||||
children: [
|
||||
_FactCard(
|
||||
icon: Icons.route,
|
||||
label: 'Payload',
|
||||
label: AppLocalizations.of(context)!.payload,
|
||||
value: _payloadTypeLabel(route.payloadType),
|
||||
),
|
||||
_FactCard(
|
||||
icon: Icons.hub,
|
||||
label: 'Hops',
|
||||
label: AppLocalizations.of(context)!.hops,
|
||||
value: '${route.hopCount}',
|
||||
),
|
||||
_FactCard(
|
||||
icon: Icons.tag,
|
||||
label: 'Hash size',
|
||||
label: AppLocalizations.of(context)!.hashSize,
|
||||
value:
|
||||
'${route.hashSize} byte${route.hashSize == 1 ? '' : 's'}',
|
||||
),
|
||||
if (originalSender != null)
|
||||
_FactCard(
|
||||
icon: Icons.person_pin_circle,
|
||||
label: 'Original sender',
|
||||
value: _nodeLabel(originalSender!),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
|
||||
251
lib/screens/profiles_screen.dart
Normal file
251
lib/screens/profiles_screen.dart
Normal file
@@ -0,0 +1,251 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
import '../models/config_profile.dart';
|
||||
import '../services/profile_manager.dart';
|
||||
import '../services/profile_workspace_coordinator.dart';
|
||||
import '../l10n/app_localizations.dart';
|
||||
|
||||
class ProfilesScreen extends StatelessWidget {
|
||||
const ProfilesScreen({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Consumer<ProfileManager>(
|
||||
builder: (context, profileManager, child) {
|
||||
final profiles = profileManager.visibleProfiles;
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text(AppLocalizations.of(context)!.profiles),
|
||||
actions: [
|
||||
IconButton(
|
||||
onPressed: () async {
|
||||
await context
|
||||
.read<ProfileWorkspaceCoordinator>()
|
||||
.importProfileFromFile();
|
||||
},
|
||||
icon: const Icon(Icons.file_open),
|
||||
tooltip: 'Import profile',
|
||||
),
|
||||
],
|
||||
),
|
||||
floatingActionButton: FloatingActionButton.extended(
|
||||
onPressed: () => _createProfile(context),
|
||||
icon: Icon(Icons.add),
|
||||
label: Text(AppLocalizations.of(context)!.newProfile),
|
||||
),
|
||||
body: profiles.isEmpty
|
||||
? Center(
|
||||
child: Text(AppLocalizations.of(context)!.enableProfilesToStartManagingThem),
|
||||
)
|
||||
: ListView.builder(
|
||||
padding: const EdgeInsets.all(16),
|
||||
itemCount: profiles.length,
|
||||
itemBuilder: (context, index) {
|
||||
final profile = profiles[index];
|
||||
final isActive =
|
||||
profileManager.activeProfileId == profile.id;
|
||||
return Card(
|
||||
margin: const EdgeInsets.only(bottom: 12),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Text(
|
||||
profile.name,
|
||||
style: Theme.of(
|
||||
context,
|
||||
).textTheme.titleMedium,
|
||||
),
|
||||
),
|
||||
if (isActive)
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 8,
|
||||
vertical: 4,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(
|
||||
context,
|
||||
).colorScheme.primaryContainer,
|
||||
borderRadius: BorderRadius.circular(999),
|
||||
),
|
||||
child: Text(AppLocalizations.of(context)!.active),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Text(_summary(profile)),
|
||||
const SizedBox(height: 12),
|
||||
Wrap(
|
||||
spacing: 8,
|
||||
runSpacing: 8,
|
||||
children: [
|
||||
OutlinedButton(
|
||||
onPressed: () async {
|
||||
await context
|
||||
.read<ProfileWorkspaceCoordinator>()
|
||||
.openProfile(profile.id);
|
||||
},
|
||||
child: Text(AppLocalizations.of(context)!.open),
|
||||
),
|
||||
OutlinedButton(
|
||||
onPressed: () async {
|
||||
final resolved =
|
||||
profile.id ==
|
||||
ConfigProfile.defaultProfileId
|
||||
? await context
|
||||
.read<
|
||||
ProfileWorkspaceCoordinator
|
||||
>()
|
||||
.snapshotCurrentProfile(
|
||||
id: profile.id,
|
||||
name: profile.name,
|
||||
)
|
||||
: profile;
|
||||
if (!context.mounted) return;
|
||||
await context
|
||||
.read<ProfileWorkspaceCoordinator>()
|
||||
.exportProfile(resolved);
|
||||
},
|
||||
child: Text(AppLocalizations.of(context)!.share),
|
||||
),
|
||||
PopupMenuButton<String>(
|
||||
onSelected: (value) async {
|
||||
switch (value) {
|
||||
case 'duplicate':
|
||||
await context
|
||||
.read<ProfileWorkspaceCoordinator>()
|
||||
.duplicateProfile(profile);
|
||||
break;
|
||||
case 'rename':
|
||||
await _renameProfile(context, profile);
|
||||
break;
|
||||
case 'delete':
|
||||
await context
|
||||
.read<ProfileWorkspaceCoordinator>()
|
||||
.deleteProfile(profile);
|
||||
break;
|
||||
}
|
||||
},
|
||||
itemBuilder: (context) => [
|
||||
PopupMenuItem(
|
||||
value: 'duplicate',
|
||||
child: Text(AppLocalizations.of(context)!.duplicate),
|
||||
),
|
||||
if (!profile.isDefault)
|
||||
PopupMenuItem(
|
||||
value: 'rename',
|
||||
child: Text(AppLocalizations.of(context)!.rename),
|
||||
),
|
||||
if (!profile.isDefault)
|
||||
const PopupMenuItem(
|
||||
value: 'delete',
|
||||
child: Text('Delete'),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
String _summary(ConfigProfile profile) {
|
||||
if (profile.isDefault) {
|
||||
return 'Current app state and history.';
|
||||
}
|
||||
|
||||
final sections = <String>[];
|
||||
if (profile.sections.deviceConfig?.isEmpty == false) {
|
||||
sections.add('Device');
|
||||
}
|
||||
if (profile.sections.channels.isNotEmpty) {
|
||||
sections.add('${profile.sections.channels.length} channels');
|
||||
}
|
||||
if (profile.sections.appSettings?.isEmpty == false) {
|
||||
sections.add('App settings');
|
||||
}
|
||||
if (profile.sections.mapWorkspace?.isEmpty == false) {
|
||||
sections.add('Map workspace');
|
||||
}
|
||||
return sections.isEmpty ? 'Empty profile' : sections.join(' | ');
|
||||
}
|
||||
|
||||
Future<void> _createProfile(BuildContext context) async {
|
||||
final controller = TextEditingController();
|
||||
final name = await showDialog<String>(
|
||||
context: context,
|
||||
builder: (context) => AlertDialog(
|
||||
title: Text(AppLocalizations.of(context)!.createProfile),
|
||||
content: TextField(
|
||||
controller: controller,
|
||||
autofocus: true,
|
||||
decoration: const InputDecoration(labelText: 'Profile name'),
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.of(context).pop(),
|
||||
child: const Text('Cancel'),
|
||||
),
|
||||
FilledButton(
|
||||
onPressed: () => Navigator.of(context).pop(controller.text.trim()),
|
||||
child: Text(AppLocalizations.of(context)!.create),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
if (name == null || name.isEmpty || !context.mounted) {
|
||||
return;
|
||||
}
|
||||
await context.read<ProfileWorkspaceCoordinator>().createProfileFromCurrent(
|
||||
name: name,
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _renameProfile(
|
||||
BuildContext context,
|
||||
ConfigProfile profile,
|
||||
) async {
|
||||
final controller = TextEditingController(text: profile.name);
|
||||
final name = await showDialog<String>(
|
||||
context: context,
|
||||
builder: (context) => AlertDialog(
|
||||
title: Text(AppLocalizations.of(context)!.renameProfile),
|
||||
content: TextField(
|
||||
controller: controller,
|
||||
autofocus: true,
|
||||
decoration: const InputDecoration(labelText: 'Profile name'),
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.of(context).pop(),
|
||||
child: const Text('Cancel'),
|
||||
),
|
||||
FilledButton(
|
||||
onPressed: () => Navigator.of(context).pop(controller.text.trim()),
|
||||
child: const Text('Save'),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
if (name == null || name.isEmpty || !context.mounted) {
|
||||
return;
|
||||
}
|
||||
await context.read<ProfileWorkspaceCoordinator>().renameProfile(
|
||||
profile,
|
||||
name,
|
||||
);
|
||||
}
|
||||
}
|
||||
776
lib/screens/repeaters_map_screen.dart
Normal file
776
lib/screens/repeaters_map_screen.dart
Normal file
@@ -0,0 +1,776 @@
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_map/flutter_map.dart' as flutter_map;
|
||||
import 'package:geolocator/geolocator.dart';
|
||||
import 'package:latlong2/latlong.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
import '../models/contact.dart';
|
||||
import '../providers/connection_provider.dart';
|
||||
import '../providers/contacts_provider.dart';
|
||||
import '../services/mesh_map_nodes_service.dart';
|
||||
import '../l10n/app_localizations.dart';
|
||||
|
||||
class RepeatersMapScreen extends StatefulWidget {
|
||||
const RepeatersMapScreen({super.key});
|
||||
|
||||
@override
|
||||
State<RepeatersMapScreen> createState() => _RepeatersMapScreenState();
|
||||
}
|
||||
|
||||
class _RepeatersMapScreenState extends State<RepeatersMapScreen> {
|
||||
static const LatLng _fallbackCenter = LatLng(46.0569, 14.5058);
|
||||
static const double _fallbackZoom = 7;
|
||||
static const double _myLocationZoom = 14;
|
||||
|
||||
final flutter_map.MapController _mapController = flutter_map.MapController();
|
||||
|
||||
List<MeshMapNode> _onlineRepeaters = const [];
|
||||
bool _isLoading = true;
|
||||
bool _didFitCamera = false;
|
||||
String? _error;
|
||||
double _currentZoom = _fallbackZoom;
|
||||
LatLng? _myLocation;
|
||||
final Set<String> _addingRepeaters = <String>{};
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_loadRepeaters();
|
||||
}
|
||||
|
||||
Future<void> _loadRepeaters({bool forceRefresh = false}) async {
|
||||
setState(() {
|
||||
_isLoading = true;
|
||||
_error = null;
|
||||
if (forceRefresh) {
|
||||
_didFitCamera = false;
|
||||
}
|
||||
});
|
||||
|
||||
try {
|
||||
final nodes = await MeshMapNodesService.fetchNodes(
|
||||
forceRefresh: forceRefresh,
|
||||
);
|
||||
final repeaters = nodes.where(MeshMapNodesService.isRepeater).toList();
|
||||
|
||||
if (!mounted) return;
|
||||
setState(() {
|
||||
_onlineRepeaters = repeaters;
|
||||
_isLoading = false;
|
||||
});
|
||||
_refreshMyLocation();
|
||||
} catch (error) {
|
||||
final cached = await MeshMapNodesService.loadCachedNodes();
|
||||
final repeaters = cached.where(MeshMapNodesService.isRepeater).toList();
|
||||
|
||||
if (!mounted) return;
|
||||
setState(() {
|
||||
_onlineRepeaters = repeaters;
|
||||
_isLoading = false;
|
||||
_error = repeaters.isEmpty
|
||||
? 'Unable to load repeaters right now.'
|
||||
: 'Showing cached repeaters. Pull to retry.';
|
||||
});
|
||||
_refreshMyLocation();
|
||||
}
|
||||
}
|
||||
|
||||
List<_MapRepeater> _mergeRepeaters({
|
||||
required List<MeshMapNode> onlineNodes,
|
||||
required List<Contact> contactRepeaters,
|
||||
}) {
|
||||
final merged = <String, _MapRepeater>{};
|
||||
|
||||
for (final node in onlineNodes) {
|
||||
merged[node.publicKey] = _MapRepeater(
|
||||
publicKey: node.publicKey,
|
||||
name: node.name,
|
||||
latitude: node.latitude,
|
||||
longitude: node.longitude,
|
||||
isFromContacts: false,
|
||||
isFromOnline: true,
|
||||
);
|
||||
}
|
||||
|
||||
for (final contact in contactRepeaters) {
|
||||
final location = contact.displayLocation;
|
||||
if (location == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
final key = contact.publicKeyHex.toLowerCase();
|
||||
final existing = merged[key];
|
||||
merged[key] = _MapRepeater(
|
||||
publicKey: key,
|
||||
name: contact.displayName.isNotEmpty
|
||||
? contact.displayName
|
||||
: existing?.name ?? 'Unknown repeater',
|
||||
latitude: existing?.latitude ?? location.latitude,
|
||||
longitude: existing?.longitude ?? location.longitude,
|
||||
isFromContacts: true,
|
||||
isFromOnline: existing?.isFromOnline ?? false,
|
||||
);
|
||||
}
|
||||
|
||||
final repeaters = merged.values.toList()
|
||||
..sort((a, b) => a.name.toLowerCase().compareTo(b.name.toLowerCase()));
|
||||
return repeaters;
|
||||
}
|
||||
|
||||
Future<void> _refreshMyLocation() async {
|
||||
try {
|
||||
final position = await Geolocator.getCurrentPosition(
|
||||
locationSettings: const LocationSettings(
|
||||
accuracy: LocationAccuracy.best,
|
||||
distanceFilter: 0,
|
||||
),
|
||||
);
|
||||
if (!mounted) return;
|
||||
setState(() {
|
||||
_myLocation = LatLng(position.latitude, position.longitude);
|
||||
});
|
||||
} catch (_) {
|
||||
// Best effort only.
|
||||
}
|
||||
}
|
||||
|
||||
void _fitCameraIfNeeded(List<_MapRepeater> repeaters) {
|
||||
if (_didFitCamera || repeaters.isEmpty || !mounted) {
|
||||
return;
|
||||
}
|
||||
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
if (!mounted || _didFitCamera || repeaters.isEmpty) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (repeaters.length == 1) {
|
||||
final repeater = repeaters.first;
|
||||
_mapController.move(LatLng(repeater.latitude, repeater.longitude), 13);
|
||||
_currentZoom = 13;
|
||||
} else {
|
||||
final bounds = flutter_map.LatLngBounds.fromPoints(
|
||||
repeaters
|
||||
.map((node) => LatLng(node.latitude, node.longitude))
|
||||
.toList(),
|
||||
);
|
||||
_mapController.fitCamera(
|
||||
flutter_map.CameraFit.bounds(
|
||||
bounds: bounds,
|
||||
padding: const EdgeInsets.all(48),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
_didFitCamera = true;
|
||||
});
|
||||
}
|
||||
|
||||
void _showRepeaterDetails(_MapRepeater repeater) {
|
||||
final isAdding = _addingRepeaters.contains(repeater.publicKey);
|
||||
final canAdd = !repeater.isFromContacts;
|
||||
final isConnected = context
|
||||
.read<ConnectionProvider>()
|
||||
.deviceInfo
|
||||
.isConnected;
|
||||
|
||||
showModalBottomSheet<void>(
|
||||
context: context,
|
||||
showDragHandle: true,
|
||||
builder: (context) => SafeArea(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.fromLTRB(20, 0, 20, 24),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
repeater.name,
|
||||
style: Theme.of(
|
||||
context,
|
||||
).textTheme.titleLarge?.copyWith(fontWeight: FontWeight.w700),
|
||||
),
|
||||
SizedBox(height: 12),
|
||||
_InfoLine(
|
||||
label: AppLocalizations.of(context)!.publicKeyLabel,
|
||||
value: repeater.publicKey,
|
||||
monospace: true,
|
||||
),
|
||||
_InfoLine(
|
||||
label: AppLocalizations.of(context)!.coordinates,
|
||||
value:
|
||||
'${repeater.latitude.toStringAsFixed(5)}, ${repeater.longitude.toStringAsFixed(5)}',
|
||||
),
|
||||
_InfoLine(label: AppLocalizations.of(context)!.source, value: repeater.sourceLabel),
|
||||
const SizedBox(height: 12),
|
||||
if (canAdd)
|
||||
SizedBox(
|
||||
width: double.infinity,
|
||||
child: FilledButton.icon(
|
||||
onPressed: !isConnected || isAdding
|
||||
? null
|
||||
: () async {
|
||||
Navigator.of(context).pop();
|
||||
await _addRepeaterToContacts(repeater);
|
||||
},
|
||||
icon: isAdding
|
||||
? const SizedBox(
|
||||
width: 16,
|
||||
height: 16,
|
||||
child: CircularProgressIndicator(strokeWidth: 2),
|
||||
)
|
||||
: const Icon(Icons.person_add_alt_1),
|
||||
label: Text(
|
||||
!isConnected
|
||||
? 'Connect device to add'
|
||||
: 'Add to contacts',
|
||||
),
|
||||
),
|
||||
)
|
||||
else
|
||||
SizedBox(
|
||||
width: double.infinity,
|
||||
child: FilledButton.icon(
|
||||
onPressed: null,
|
||||
icon: Icon(Icons.check_circle),
|
||||
label: Text(AppLocalizations.of(context)!.alreadyInContacts),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _addRepeaterToContacts(_MapRepeater repeater) async {
|
||||
if (_addingRepeaters.contains(repeater.publicKey)) {
|
||||
return;
|
||||
}
|
||||
|
||||
final connectionProvider = context.read<ConnectionProvider>();
|
||||
if (!connectionProvider.deviceInfo.isConnected) {
|
||||
if (!mounted) return;
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(AppLocalizations.of(context)!.connectToADeviceBeforeAddingContacts),
|
||||
),
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
setState(() {
|
||||
_addingRepeaters.add(repeater.publicKey);
|
||||
});
|
||||
|
||||
try {
|
||||
await connectionProvider.getContact(_hexToBytes(repeater.publicKey));
|
||||
if (!mounted) return;
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text('${repeater.name} added to contacts')),
|
||||
);
|
||||
} catch (error) {
|
||||
if (!mounted) return;
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text('Failed to add ${repeater.name}: $error')),
|
||||
);
|
||||
} finally {
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_addingRepeaters.remove(repeater.publicKey);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Uint8List _hexToBytes(String hex) {
|
||||
final normalized = hex.replaceAll(':', '').trim().toLowerCase();
|
||||
return Uint8List.fromList(
|
||||
List<int>.generate(
|
||||
normalized.length ~/ 2,
|
||||
(index) => int.parse(
|
||||
normalized.substring(index * 2, index * 2 + 2),
|
||||
radix: 16,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final contactsProvider = context.watch<ContactsProvider>();
|
||||
final repeaters = _mergeRepeaters(
|
||||
onlineNodes: _onlineRepeaters,
|
||||
contactRepeaters: contactsProvider.repeaters,
|
||||
);
|
||||
_fitCameraIfNeeded(repeaters);
|
||||
final clusters = _buildClusters(repeaters);
|
||||
final markers = clusters.map((cluster) {
|
||||
final markerColor = switch (cluster.status) {
|
||||
_ClusterStatus.contactsOnly => const Color(0xFF7C3AED),
|
||||
_ClusterStatus.onlineOnly => const Color(0xFFE8681D),
|
||||
_ClusterStatus.both => const Color(0xFF1B8F4F),
|
||||
_ClusterStatus.mixed => const Color(0xFF2563EB),
|
||||
};
|
||||
|
||||
if (cluster.members.length == 1) {
|
||||
final repeater = cluster.members.first;
|
||||
return flutter_map.Marker(
|
||||
point: cluster.center,
|
||||
width: 80,
|
||||
height: 100,
|
||||
child: GestureDetector(
|
||||
onTap: () => _showRepeaterDetails(repeater),
|
||||
child: _RepeaterMarker(color: markerColor, label: repeater.name),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return flutter_map.Marker(
|
||||
point: cluster.center,
|
||||
width: 48,
|
||||
height: 48,
|
||||
child: GestureDetector(
|
||||
onTap: () => _zoomToCluster(cluster),
|
||||
child: _ClusterMarker(
|
||||
color: markerColor,
|
||||
count: cluster.members.length,
|
||||
),
|
||||
),
|
||||
);
|
||||
}).toList();
|
||||
final allMarkers = <flutter_map.Marker>[
|
||||
...markers,
|
||||
if (_myLocation != null)
|
||||
flutter_map.Marker(
|
||||
point: _myLocation!,
|
||||
width: 24,
|
||||
height: 24,
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: const Color(0xFF2563EB),
|
||||
shape: BoxShape.circle,
|
||||
border: Border.all(color: Colors.white, width: 3),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.black.withValues(alpha: 0.18),
|
||||
blurRadius: 12,
|
||||
offset: const Offset(0, 4),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
];
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text(AppLocalizations.of(context)!.repeatersMap),
|
||||
actions: [
|
||||
IconButton(
|
||||
onPressed: _isLoading
|
||||
? null
|
||||
: () => _loadRepeaters(forceRefresh: true),
|
||||
icon: const Icon(Icons.refresh),
|
||||
tooltip: 'Refresh',
|
||||
),
|
||||
],
|
||||
),
|
||||
body: Stack(
|
||||
children: [
|
||||
flutter_map.FlutterMap(
|
||||
mapController: _mapController,
|
||||
options: flutter_map.MapOptions(
|
||||
initialCenter: _fallbackCenter,
|
||||
initialZoom: _fallbackZoom,
|
||||
onPositionChanged: (camera, hasGesture) {
|
||||
final nextZoom = camera.zoom;
|
||||
if (nextZoom != _currentZoom && mounted) {
|
||||
setState(() {
|
||||
_currentZoom = nextZoom;
|
||||
});
|
||||
}
|
||||
},
|
||||
),
|
||||
children: [
|
||||
flutter_map.TileLayer(
|
||||
urlTemplate: 'https://tile.openstreetmap.org/{z}/{x}/{y}.png',
|
||||
userAgentPackageName: 'app.meshcore.sar',
|
||||
),
|
||||
flutter_map.MarkerLayer(markers: allMarkers),
|
||||
],
|
||||
),
|
||||
if (_error != null)
|
||||
Positioned(
|
||||
top: 16,
|
||||
left: 16,
|
||||
right: 16,
|
||||
child: Material(
|
||||
elevation: 2,
|
||||
borderRadius: BorderRadius.circular(14),
|
||||
color: Theme.of(context).colorScheme.surface,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 16,
|
||||
vertical: 12,
|
||||
),
|
||||
child: Text(_error!),
|
||||
),
|
||||
),
|
||||
),
|
||||
if (_isLoading) const Center(child: CircularProgressIndicator()),
|
||||
if (!_isLoading && repeaters.isEmpty)
|
||||
Center(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(24),
|
||||
child: Text(
|
||||
_error ?? 'No repeaters available.',
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
),
|
||||
),
|
||||
if (repeaters.isNotEmpty)
|
||||
Positioned(
|
||||
left: 16,
|
||||
top: _error == null ? 16 : 72,
|
||||
child: Material(
|
||||
elevation: 2,
|
||||
borderRadius: BorderRadius.circular(14),
|
||||
color: Theme.of(context).colorScheme.surface,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 14,
|
||||
vertical: 10,
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
_LegendRow(
|
||||
color: const Color(0xFF7C3AED),
|
||||
label: AppLocalizations.of(context)!.fromContacts,
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
_LegendRow(
|
||||
color: const Color(0xFFE8681D),
|
||||
label: AppLocalizations.of(context)!.onlineOnly,
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
_LegendRow(color: const Color(0xFF1B8F4F), label: AppLocalizations.of(context)!.inBoth),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
if (repeaters.isNotEmpty)
|
||||
Positioned(
|
||||
left: 16,
|
||||
right: 16,
|
||||
bottom: 16,
|
||||
child: Material(
|
||||
elevation: 3,
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
color: Theme.of(context).colorScheme.surface,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 16,
|
||||
vertical: 12,
|
||||
),
|
||||
child: Text(
|
||||
'${repeaters.length} repeaters',
|
||||
style: Theme.of(context).textTheme.titleMedium?.copyWith(
|
||||
fontWeight: FontWeight.w700,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
floatingActionButton: FloatingActionButton(
|
||||
onPressed: _jumpToMyLocation,
|
||||
tooltip: 'My location',
|
||||
child: const Icon(Icons.my_location),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _jumpToMyLocation() async {
|
||||
await _refreshMyLocation();
|
||||
final myLocation = _myLocation;
|
||||
if (!mounted || myLocation == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
_mapController.move(myLocation, _myLocationZoom);
|
||||
setState(() {
|
||||
_currentZoom = _myLocationZoom;
|
||||
});
|
||||
}
|
||||
|
||||
List<_RepeaterCluster> _buildClusters(List<_MapRepeater> repeaters) {
|
||||
if (repeaters.isEmpty) {
|
||||
return const [];
|
||||
}
|
||||
|
||||
if (_currentZoom >= 11.5) {
|
||||
return repeaters
|
||||
.map(
|
||||
(repeater) => _RepeaterCluster(
|
||||
members: [repeater],
|
||||
center: LatLng(repeater.latitude, repeater.longitude),
|
||||
status: repeater.clusterStatus,
|
||||
),
|
||||
)
|
||||
.toList();
|
||||
}
|
||||
|
||||
final cellSize = _gridSizeForZoom(_currentZoom);
|
||||
final buckets = <String, List<_MapRepeater>>{};
|
||||
|
||||
for (final repeater in repeaters) {
|
||||
final latBucket = (repeater.latitude / cellSize).floor();
|
||||
final lonBucket = (repeater.longitude / cellSize).floor();
|
||||
final key = '$latBucket:$lonBucket';
|
||||
buckets.putIfAbsent(key, () => <_MapRepeater>[]).add(repeater);
|
||||
}
|
||||
|
||||
return buckets.values.map((members) {
|
||||
final centerLat =
|
||||
members.map((node) => node.latitude).reduce((a, b) => a + b) /
|
||||
members.length;
|
||||
final centerLon =
|
||||
members.map((node) => node.longitude).reduce((a, b) => a + b) /
|
||||
members.length;
|
||||
final statuses = members.map((node) => node.clusterStatus).toSet();
|
||||
return _RepeaterCluster(
|
||||
members: members,
|
||||
center: LatLng(centerLat, centerLon),
|
||||
status: statuses.length == 1 ? statuses.first : _ClusterStatus.mixed,
|
||||
);
|
||||
}).toList();
|
||||
}
|
||||
|
||||
double _gridSizeForZoom(double zoom) {
|
||||
if (zoom < 5) return 3.0;
|
||||
if (zoom < 7) return 1.5;
|
||||
if (zoom < 9) return 0.7;
|
||||
if (zoom < 10.5) return 0.28;
|
||||
return 0.12;
|
||||
}
|
||||
|
||||
void _zoomToCluster(_RepeaterCluster cluster) {
|
||||
if (cluster.members.isEmpty) {
|
||||
return;
|
||||
}
|
||||
|
||||
final bounds = flutter_map.LatLngBounds.fromPoints(
|
||||
cluster.members
|
||||
.map((node) => LatLng(node.latitude, node.longitude))
|
||||
.toList(),
|
||||
);
|
||||
_mapController.fitCamera(
|
||||
flutter_map.CameraFit.bounds(
|
||||
bounds: bounds,
|
||||
padding: const EdgeInsets.all(64),
|
||||
maxZoom: (_currentZoom + 2).clamp(8, 14),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _InfoLine extends StatelessWidget {
|
||||
final String label;
|
||||
final String value;
|
||||
final bool monospace;
|
||||
|
||||
const _InfoLine({
|
||||
required this.label,
|
||||
required this.value,
|
||||
this.monospace = false,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(bottom: 8),
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
SizedBox(
|
||||
width: 92,
|
||||
child: Text(
|
||||
label,
|
||||
style: Theme.of(
|
||||
context,
|
||||
).textTheme.bodyMedium?.copyWith(fontWeight: FontWeight.w600),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: Text(
|
||||
value,
|
||||
style: monospace
|
||||
? const TextStyle(fontFamily: 'monospace')
|
||||
: null,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _LegendRow extends StatelessWidget {
|
||||
final Color color;
|
||||
final String label;
|
||||
|
||||
const _LegendRow({required this.color, required this.label});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Container(
|
||||
width: 12,
|
||||
height: 12,
|
||||
decoration: BoxDecoration(color: color, shape: BoxShape.circle),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Text(label),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _RepeaterMarker extends StatelessWidget {
|
||||
final Color color;
|
||||
final String label;
|
||||
|
||||
const _RepeaterMarker({required this.color, required this.label});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Container(
|
||||
decoration: BoxDecoration(
|
||||
color: color,
|
||||
shape: BoxShape.circle,
|
||||
border: Border.all(color: Colors.white, width: 2),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.black.withValues(alpha: 0.3),
|
||||
blurRadius: 4,
|
||||
offset: const Offset(0, 2),
|
||||
),
|
||||
],
|
||||
),
|
||||
padding: const EdgeInsets.all(6),
|
||||
child: const Icon(Icons.router, color: Colors.white, size: 18),
|
||||
),
|
||||
const SizedBox(height: 2),
|
||||
Container(
|
||||
constraints: const BoxConstraints(maxWidth: 80),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 4, vertical: 1),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.black.withValues(alpha: 0.7),
|
||||
borderRadius: BorderRadius.circular(3),
|
||||
),
|
||||
child: Text(
|
||||
label,
|
||||
style: const TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 9,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
maxLines: 1,
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _ClusterMarker extends StatelessWidget {
|
||||
final Color color;
|
||||
final int count;
|
||||
|
||||
const _ClusterMarker({required this.color, required this.count});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
decoration: BoxDecoration(
|
||||
color: color,
|
||||
shape: BoxShape.circle,
|
||||
border: Border.all(color: Colors.white, width: 2),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.black.withValues(alpha: 0.22),
|
||||
blurRadius: 10,
|
||||
offset: const Offset(0, 3),
|
||||
),
|
||||
],
|
||||
),
|
||||
alignment: Alignment.center,
|
||||
child: Text(
|
||||
'$count',
|
||||
style: const TextStyle(
|
||||
color: Colors.white,
|
||||
fontWeight: FontWeight.w800,
|
||||
fontSize: 14,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
enum _ClusterStatus { contactsOnly, onlineOnly, both, mixed }
|
||||
|
||||
class _RepeaterCluster {
|
||||
final List<_MapRepeater> members;
|
||||
final LatLng center;
|
||||
final _ClusterStatus status;
|
||||
|
||||
const _RepeaterCluster({
|
||||
required this.members,
|
||||
required this.center,
|
||||
required this.status,
|
||||
});
|
||||
}
|
||||
|
||||
class _MapRepeater {
|
||||
final String publicKey;
|
||||
final String name;
|
||||
final double latitude;
|
||||
final double longitude;
|
||||
final bool isFromContacts;
|
||||
final bool isFromOnline;
|
||||
|
||||
const _MapRepeater({
|
||||
required this.publicKey,
|
||||
required this.name,
|
||||
required this.latitude,
|
||||
required this.longitude,
|
||||
required this.isFromContacts,
|
||||
required this.isFromOnline,
|
||||
});
|
||||
|
||||
String get sourceLabel {
|
||||
if (isFromContacts && isFromOnline) return 'Contacts and online';
|
||||
if (isFromContacts) return 'Contacts';
|
||||
return 'Online';
|
||||
}
|
||||
|
||||
_ClusterStatus get clusterStatus {
|
||||
if (isFromContacts && isFromOnline) return _ClusterStatus.both;
|
||||
if (isFromContacts) return _ClusterStatus.contactsOnly;
|
||||
return _ClusterStatus.onlineOnly;
|
||||
}
|
||||
}
|
||||
@@ -240,7 +240,7 @@ class _SarTemplateManagementScreenState extends State<SarTemplateManagementScree
|
||||
PopupMenuItem(
|
||||
value: 'import',
|
||||
child: ListTile(
|
||||
leading: const Icon(Icons.download),
|
||||
leading: Icon(Icons.download),
|
||||
title: Text(l10n.importFromClipboard),
|
||||
contentPadding: EdgeInsets.zero,
|
||||
),
|
||||
@@ -248,7 +248,7 @@ class _SarTemplateManagementScreenState extends State<SarTemplateManagementScree
|
||||
PopupMenuItem(
|
||||
value: 'export',
|
||||
child: ListTile(
|
||||
leading: const Icon(Icons.upload),
|
||||
leading: Icon(Icons.upload),
|
||||
title: Text(l10n.exportToClipboard),
|
||||
contentPadding: EdgeInsets.zero,
|
||||
),
|
||||
@@ -257,7 +257,7 @@ class _SarTemplateManagementScreenState extends State<SarTemplateManagementScree
|
||||
PopupMenuItem(
|
||||
value: 'reset',
|
||||
child: ListTile(
|
||||
leading: const Icon(Icons.restart_alt),
|
||||
leading: Icon(Icons.restart_alt),
|
||||
title: Text(l10n.resetToDefaults),
|
||||
contentPadding: EdgeInsets.zero,
|
||||
),
|
||||
@@ -283,14 +283,14 @@ class _SarTemplateManagementScreenState extends State<SarTemplateManagementScree
|
||||
size: 64,
|
||||
color: colorScheme.onSurface.withValues(alpha: 0.3),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
SizedBox(height: 16),
|
||||
Text(
|
||||
l10n.noTemplates,
|
||||
style: theme.textTheme.titleMedium?.copyWith(
|
||||
color: colorScheme.onSurface.withValues(alpha: 0.6),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
SizedBox(height: 8),
|
||||
Text(
|
||||
l10n.tapAddToCreate,
|
||||
style: theme.textTheme.bodyMedium?.copyWith(
|
||||
@@ -317,7 +317,7 @@ class _SarTemplateManagementScreenState extends State<SarTemplateManagementScree
|
||||
),
|
||||
floatingActionButton: FloatingActionButton.extended(
|
||||
onPressed: _addTemplate,
|
||||
icon: const Icon(Icons.add),
|
||||
icon: Icon(Icons.add),
|
||||
label: Text(l10n.addTemplate),
|
||||
),
|
||||
);
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -4,6 +4,7 @@ import 'package:provider/provider.dart';
|
||||
import '../models/device_info.dart';
|
||||
import '../providers/connection_provider.dart';
|
||||
import '../widgets/device/spectrum_scan_panel.dart';
|
||||
import '../l10n/app_localizations.dart';
|
||||
|
||||
class SpectrumScanScreen extends StatefulWidget {
|
||||
const SpectrumScanScreen({super.key});
|
||||
@@ -323,8 +324,8 @@ class _SpectrumScanScreenState extends State<SpectrumScanScreen> {
|
||||
final mergedCandidates = _mergeSectorCandidates(sectorCandidates);
|
||||
if (mergedCandidates.isEmpty) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(
|
||||
content: Text('Spectrum scan returned no candidate frequencies'),
|
||||
SnackBar(
|
||||
content: Text(AppLocalizations.of(context)!.spectrumScanReturnedNoCandidateFrequencies),
|
||||
backgroundColor: Colors.red,
|
||||
),
|
||||
);
|
||||
@@ -383,7 +384,7 @@ class _SpectrumScanScreenState extends State<SpectrumScanScreen> {
|
||||
: possibleBracketFrequencies;
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(title: const Text('Spectrum Scan')),
|
||||
appBar: AppBar(title: Text(AppLocalizations.of(context)!.spectrumScan)),
|
||||
body: ListView(
|
||||
padding: const EdgeInsets.all(16),
|
||||
children: [
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import '../l10n/app_localizations.dart';
|
||||
import '../providers/connection_provider.dart';
|
||||
import '../services/wizard_preferences.dart';
|
||||
import '../widgets/connection_dialog.dart';
|
||||
|
||||
/// Welcome wizard screen to introduce new users to the app
|
||||
class WelcomeWizardScreen extends StatefulWidget {
|
||||
@@ -14,12 +17,171 @@ class WelcomeWizardScreen extends StatefulWidget {
|
||||
|
||||
class _WelcomeWizardScreenState extends State<WelcomeWizardScreen> {
|
||||
final PageController _pageController = PageController();
|
||||
final TextEditingController _deviceNameController = TextEditingController();
|
||||
int _currentPage = 0;
|
||||
static const int _totalPages = 5;
|
||||
bool _isApplyingDeviceSetup = false;
|
||||
static const int _totalPages = 6;
|
||||
static const List<_RadioPreset> _radioPresets = [
|
||||
// Official MeshCore config presets from https://api.meshcore.nz/api/v1/config
|
||||
_RadioPreset(
|
||||
id: 'australia',
|
||||
label: 'Australia',
|
||||
summary: '915.800 MHz, BW 250 kHz, SF10, CR5',
|
||||
frequencyKhz: 915800,
|
||||
bandwidth: 8,
|
||||
spreadingFactor: 10,
|
||||
codingRate: 5,
|
||||
),
|
||||
_RadioPreset(
|
||||
id: 'australia_narrow',
|
||||
label: 'Australia (Narrow)',
|
||||
summary: '916.575 MHz, BW 62.5 kHz, SF7, CR8',
|
||||
frequencyKhz: 916575,
|
||||
bandwidth: 6,
|
||||
spreadingFactor: 7,
|
||||
codingRate: 8,
|
||||
),
|
||||
_RadioPreset(
|
||||
id: 'australia_sa_wa',
|
||||
label: 'Australia: SA, WA',
|
||||
summary: '923.125 MHz, BW 62.5 kHz, SF8, CR8',
|
||||
frequencyKhz: 923125,
|
||||
bandwidth: 6,
|
||||
spreadingFactor: 8,
|
||||
codingRate: 8,
|
||||
),
|
||||
_RadioPreset(
|
||||
id: 'australia_qld',
|
||||
label: 'Australia: QLD',
|
||||
summary: '923.125 MHz, BW 62.5 kHz, SF8, CR5',
|
||||
frequencyKhz: 923125,
|
||||
bandwidth: 6,
|
||||
spreadingFactor: 8,
|
||||
codingRate: 5,
|
||||
),
|
||||
_RadioPreset(
|
||||
id: 'eu_uk_narrow',
|
||||
label: 'EU/UK (Narrow)',
|
||||
summary: '869.618 MHz, BW 62.5 kHz, SF8, CR8',
|
||||
frequencyKhz: 869618,
|
||||
bandwidth: 6,
|
||||
spreadingFactor: 8,
|
||||
codingRate: 8,
|
||||
),
|
||||
_RadioPreset(
|
||||
id: 'eu_uk_deprecated',
|
||||
label: 'EU/UK (Deprecated)',
|
||||
summary: '869.525 MHz, BW 250 kHz, SF11, CR5',
|
||||
frequencyKhz: 869525,
|
||||
bandwidth: 8,
|
||||
spreadingFactor: 11,
|
||||
codingRate: 5,
|
||||
),
|
||||
_RadioPreset(
|
||||
id: 'czech_republic_narrow',
|
||||
label: 'Czech Republic (Narrow)',
|
||||
summary: '869.432 MHz, BW 62.5 kHz, SF7, CR5',
|
||||
frequencyKhz: 869432,
|
||||
bandwidth: 6,
|
||||
spreadingFactor: 7,
|
||||
codingRate: 5,
|
||||
),
|
||||
_RadioPreset(
|
||||
id: 'eu_433_long_range',
|
||||
label: 'EU 433MHz (Long Range)',
|
||||
summary: '433.650 MHz, BW 250 kHz, SF11, CR5',
|
||||
frequencyKhz: 433650,
|
||||
bandwidth: 8,
|
||||
spreadingFactor: 11,
|
||||
codingRate: 5,
|
||||
),
|
||||
_RadioPreset(
|
||||
id: 'new_zealand',
|
||||
label: 'New Zealand',
|
||||
summary: '917.375 MHz, BW 250 kHz, SF11, CR5',
|
||||
frequencyKhz: 917375,
|
||||
bandwidth: 8,
|
||||
spreadingFactor: 11,
|
||||
codingRate: 5,
|
||||
),
|
||||
_RadioPreset(
|
||||
id: 'new_zealand_narrow',
|
||||
label: 'New Zealand (Narrow)',
|
||||
summary: '917.375 MHz, BW 62.5 kHz, SF7, CR5',
|
||||
frequencyKhz: 917375,
|
||||
bandwidth: 6,
|
||||
spreadingFactor: 7,
|
||||
codingRate: 5,
|
||||
),
|
||||
_RadioPreset(
|
||||
id: 'portugal_433',
|
||||
label: 'Portugal 433',
|
||||
summary: '433.375 MHz, BW 62.5 kHz, SF9, CR6',
|
||||
frequencyKhz: 433375,
|
||||
bandwidth: 6,
|
||||
spreadingFactor: 9,
|
||||
codingRate: 6,
|
||||
),
|
||||
_RadioPreset(
|
||||
id: 'portugal_868',
|
||||
label: 'Portugal 868',
|
||||
summary: '869.618 MHz, BW 62.5 kHz, SF7, CR6',
|
||||
frequencyKhz: 869618,
|
||||
bandwidth: 6,
|
||||
spreadingFactor: 7,
|
||||
codingRate: 6,
|
||||
),
|
||||
_RadioPreset(
|
||||
id: 'switzerland',
|
||||
label: 'Switzerland',
|
||||
summary: '869.618 MHz, BW 62.5 kHz, SF8, CR8',
|
||||
frequencyKhz: 869618,
|
||||
bandwidth: 6,
|
||||
spreadingFactor: 8,
|
||||
codingRate: 8,
|
||||
),
|
||||
_RadioPreset(
|
||||
id: 'usa_canada_recommended',
|
||||
label: 'USA/Canada (Recommended)',
|
||||
summary: '910.525 MHz, BW 62.5 kHz, SF7, CR5',
|
||||
frequencyKhz: 910525,
|
||||
bandwidth: 6,
|
||||
spreadingFactor: 7,
|
||||
codingRate: 5,
|
||||
),
|
||||
_RadioPreset(
|
||||
id: 'vietnam_narrow',
|
||||
label: 'Vietnam (Narrow)',
|
||||
summary: '920.250 MHz, BW 62.5 kHz, SF8, CR5',
|
||||
frequencyKhz: 920250,
|
||||
bandwidth: 6,
|
||||
spreadingFactor: 8,
|
||||
codingRate: 5,
|
||||
),
|
||||
_RadioPreset(
|
||||
id: 'vietnam_deprecated',
|
||||
label: 'Vietnam (Deprecated)',
|
||||
summary: '920.250 MHz, BW 250 kHz, SF11, CR5',
|
||||
frequencyKhz: 920250,
|
||||
bandwidth: 8,
|
||||
spreadingFactor: 11,
|
||||
codingRate: 5,
|
||||
),
|
||||
];
|
||||
_RadioPreset _selectedPreset = _radioPresets[4];
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
final deviceInfo = context.read<ConnectionProvider>().deviceInfo;
|
||||
_deviceNameController.text =
|
||||
deviceInfo.selfName ?? deviceInfo.deviceName ?? '';
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_pageController.dispose();
|
||||
_deviceNameController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@@ -30,7 +192,9 @@ class _WelcomeWizardScreenState extends State<WelcomeWizardScreen> {
|
||||
}
|
||||
|
||||
void _nextPage() {
|
||||
if (_currentPage < _totalPages - 1) {
|
||||
if (_currentPage == 1) {
|
||||
_handleDeviceSetupAction();
|
||||
} else if (_currentPage < _totalPages - 1) {
|
||||
_pageController.nextPage(
|
||||
duration: const Duration(milliseconds: 300),
|
||||
curve: Curves.easeInOut,
|
||||
@@ -56,6 +220,98 @@ class _WelcomeWizardScreenState extends State<WelcomeWizardScreen> {
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _openConnectionDialog() async {
|
||||
await showModalBottomSheet(
|
||||
context: context,
|
||||
isScrollControlled: true,
|
||||
builder: (context) => const ConnectionDialog(),
|
||||
);
|
||||
if (!mounted) return;
|
||||
final connectionProvider = context.read<ConnectionProvider>();
|
||||
if (connectionProvider.deviceInfo.isConnected) {
|
||||
await connectionProvider.refreshDeviceInfo();
|
||||
}
|
||||
if (!mounted) return;
|
||||
final deviceInfo = connectionProvider.deviceInfo;
|
||||
final fetchedName = deviceInfo.selfName ?? deviceInfo.deviceName ?? '';
|
||||
if (fetchedName.isNotEmpty) {
|
||||
setState(() {
|
||||
_deviceNameController.text = fetchedName;
|
||||
});
|
||||
} else {
|
||||
setState(() {});
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _handleDeviceSetupAction() async {
|
||||
final l10n = AppLocalizations.of(context)!;
|
||||
final connectionProvider = context.read<ConnectionProvider>();
|
||||
final deviceInfo = connectionProvider.deviceInfo;
|
||||
if (!deviceInfo.isConnected) {
|
||||
await _openConnectionDialog();
|
||||
return;
|
||||
}
|
||||
|
||||
final trimmedName = _deviceNameController.text.trim();
|
||||
if (trimmedName.isEmpty) {
|
||||
ScaffoldMessenger.of(
|
||||
context,
|
||||
).showSnackBar(SnackBar(content: Text(l10n.wizardEnterDeviceName)));
|
||||
return;
|
||||
}
|
||||
|
||||
setState(() {
|
||||
_isApplyingDeviceSetup = true;
|
||||
});
|
||||
|
||||
try {
|
||||
await connectionProvider.setAdvertName(trimmedName);
|
||||
await connectionProvider.setRadioParams(
|
||||
frequency: _selectedPreset.frequencyKhz,
|
||||
bandwidth: _selectedPreset.bandwidth,
|
||||
spreadingFactor: _selectedPreset.spreadingFactor,
|
||||
codingRate: _selectedPreset.codingRate,
|
||||
);
|
||||
await connectionProvider.refreshDeviceInfo();
|
||||
|
||||
if (!mounted) return;
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(
|
||||
l10n.wizardDeviceSetupSaved(trimmedName, _selectedPreset.label),
|
||||
),
|
||||
backgroundColor: Colors.green,
|
||||
),
|
||||
);
|
||||
_pageController.nextPage(
|
||||
duration: const Duration(milliseconds: 300),
|
||||
curve: Curves.easeInOut,
|
||||
);
|
||||
} catch (e) {
|
||||
if (!mounted) return;
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(l10n.failedToSave(e.toString())),
|
||||
backgroundColor: Colors.red,
|
||||
),
|
||||
);
|
||||
} finally {
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_isApplyingDeviceSetup = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void _skipDeviceSetupStep() {
|
||||
if (_isApplyingDeviceSetup) return;
|
||||
_pageController.nextPage(
|
||||
duration: const Duration(milliseconds: 300),
|
||||
curve: Curves.easeInOut,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final l10n = AppLocalizations.of(context)!;
|
||||
@@ -68,14 +324,14 @@ class _WelcomeWizardScreenState extends State<WelcomeWizardScreen> {
|
||||
children: [
|
||||
// Top bar with skip button
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
padding: const EdgeInsets.fromLTRB(16, 12, 16, 8),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
if (_currentPage > 0)
|
||||
TextButton.icon(
|
||||
onPressed: _previousPage,
|
||||
icon: const Icon(Icons.arrow_back),
|
||||
icon: Icon(Icons.arrow_back),
|
||||
label: Text(l10n.wizardBack),
|
||||
)
|
||||
else
|
||||
@@ -96,8 +352,10 @@ class _WelcomeWizardScreenState extends State<WelcomeWizardScreen> {
|
||||
child: PageView(
|
||||
controller: _pageController,
|
||||
onPageChanged: _onPageChanged,
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
children: [
|
||||
_buildWelcomePage(context, l10n, colorScheme),
|
||||
_buildConnectDevicePage(context, l10n, colorScheme),
|
||||
_buildConnectingPage(context, l10n, colorScheme),
|
||||
_buildChannelPage(context, l10n, colorScheme),
|
||||
_buildContactsPage(context, l10n, colorScheme),
|
||||
@@ -108,7 +366,7 @@ class _WelcomeWizardScreenState extends State<WelcomeWizardScreen> {
|
||||
|
||||
// Page indicators
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
padding: const EdgeInsets.fromLTRB(16, 8, 16, 8),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: List.generate(
|
||||
@@ -130,11 +388,11 @@ class _WelcomeWizardScreenState extends State<WelcomeWizardScreen> {
|
||||
|
||||
// Next/Get Started button
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
padding: const EdgeInsets.fromLTRB(16, 8, 16, 16),
|
||||
child: SizedBox(
|
||||
width: double.infinity,
|
||||
child: ElevatedButton(
|
||||
onPressed: _nextPage,
|
||||
onPressed: _isApplyingDeviceSetup ? null : _nextPage,
|
||||
style: ElevatedButton.styleFrom(
|
||||
padding: const EdgeInsets.symmetric(vertical: 16.0),
|
||||
shape: RoundedRectangleBorder(
|
||||
@@ -142,7 +400,9 @@ class _WelcomeWizardScreenState extends State<WelcomeWizardScreen> {
|
||||
),
|
||||
),
|
||||
child: Text(
|
||||
_currentPage < _totalPages - 1
|
||||
_currentPage == 1
|
||||
? _deviceSetupButtonLabel(context)
|
||||
: _currentPage < _totalPages - 1
|
||||
? l10n.wizardNext
|
||||
: l10n.wizardGetStarted,
|
||||
style: const TextStyle(fontSize: 16),
|
||||
@@ -165,7 +425,21 @@ class _WelcomeWizardScreenState extends State<WelcomeWizardScreen> {
|
||||
icon: Icons.waving_hand,
|
||||
iconColor: Colors.orange,
|
||||
title: l10n.wizardWelcomeTitle,
|
||||
description: l10n.wizardWelcomeDescription,
|
||||
description: l10n.wizardOverviewDescription,
|
||||
features: [
|
||||
_FeatureItem(
|
||||
icon: Icons.chat_bubble_outline,
|
||||
text: l10n.wizardOverviewFeature1,
|
||||
),
|
||||
_FeatureItem(
|
||||
icon: Icons.emergency_share,
|
||||
text: l10n.wizardOverviewFeature2,
|
||||
),
|
||||
_FeatureItem(
|
||||
icon: Icons.settings_input_antenna,
|
||||
text: l10n.wizardOverviewFeature3,
|
||||
),
|
||||
],
|
||||
colorScheme: colorScheme,
|
||||
);
|
||||
}
|
||||
@@ -176,33 +450,225 @@ class _WelcomeWizardScreenState extends State<WelcomeWizardScreen> {
|
||||
ColorScheme colorScheme,
|
||||
) {
|
||||
return _buildPage(
|
||||
icon: Icons.bluetooth_searching,
|
||||
icon: Icons.forum_rounded,
|
||||
iconColor: Colors.blue,
|
||||
title: l10n.wizardConnectingTitle,
|
||||
description: l10n.wizardConnectingDescription,
|
||||
title: l10n.wizardMessagingTitle,
|
||||
description: l10n.wizardMessagingDescription,
|
||||
features: [
|
||||
_FeatureItem(icon: Icons.radio, text: l10n.wizardConnectingFeature1),
|
||||
_FeatureItem(icon: Icons.link, text: l10n.wizardConnectingFeature2),
|
||||
_FeatureItem(icon: Icons.wifi_off, text: l10n.wizardConnectingFeature3),
|
||||
_FeatureItem(icon: Icons.chat, text: l10n.wizardMessagingFeature1),
|
||||
_FeatureItem(icon: Icons.campaign, text: l10n.wizardMessagingFeature2),
|
||||
_FeatureItem(icon: Icons.mic, text: l10n.wizardMessagingFeature3),
|
||||
],
|
||||
colorScheme: colorScheme,
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildConnectDevicePage(
|
||||
BuildContext context,
|
||||
AppLocalizations l10n,
|
||||
ColorScheme colorScheme,
|
||||
) {
|
||||
final connectionProvider = context.watch<ConnectionProvider>();
|
||||
final deviceInfo = connectionProvider.deviceInfo;
|
||||
final isConnected = deviceInfo.isConnected;
|
||||
final connectedName = deviceInfo.selfName ?? deviceInfo.deviceName;
|
||||
|
||||
return LayoutBuilder(
|
||||
builder: (context, constraints) => Center(
|
||||
child: ConstrainedBox(
|
||||
constraints: BoxConstraints(
|
||||
maxWidth: 600,
|
||||
maxHeight: constraints.maxHeight,
|
||||
),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.fromLTRB(20, 12, 20, 12),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
_buildPageHeader(
|
||||
context: context,
|
||||
colorScheme: colorScheme,
|
||||
icon: Icons.bluetooth_connected,
|
||||
iconColor: Colors.green,
|
||||
title: l10n.wizardConnectDeviceTitle,
|
||||
description: l10n.wizardConnectDeviceDescription,
|
||||
badge: l10n.wizardSetupBadge,
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
_buildContentCard(
|
||||
colorScheme: colorScheme,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Icon(
|
||||
isConnected
|
||||
? Icons.check_circle
|
||||
: Icons.bluetooth_searching,
|
||||
color: isConnected
|
||||
? colorScheme.primary
|
||||
: colorScheme.onSurfaceVariant,
|
||||
),
|
||||
SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: Text(
|
||||
isConnected
|
||||
? l10n.wizardConnectedToDevice(
|
||||
connectedName ?? l10n.device,
|
||||
)
|
||||
: l10n.wizardNoDeviceConnected,
|
||||
style: Theme.of(context).textTheme.bodyMedium
|
||||
?.copyWith(fontWeight: FontWeight.w600),
|
||||
),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: _openConnectionDialog,
|
||||
child: Text(
|
||||
isConnected ? l10n.change : l10n.connect,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
Align(
|
||||
alignment: Alignment.centerRight,
|
||||
child: TextButton(
|
||||
onPressed: _skipDeviceSetupStep,
|
||||
child: Text(l10n.wizardSkipForNow),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
_buildContentCard(
|
||||
colorScheme: colorScheme,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
TextField(
|
||||
controller: _deviceNameController,
|
||||
textInputAction: TextInputAction.done,
|
||||
decoration: InputDecoration(
|
||||
labelText: l10n.wizardDeviceNameLabel,
|
||||
border: OutlineInputBorder(),
|
||||
helperText: l10n.wizardDeviceNameHelp,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
DropdownButtonFormField<_RadioPreset>(
|
||||
initialValue: _selectedPreset,
|
||||
isExpanded: true,
|
||||
decoration: InputDecoration(
|
||||
labelText: l10n.wizardConfigRegionLabel,
|
||||
border: OutlineInputBorder(),
|
||||
helperText: l10n.wizardConfigRegionHelp,
|
||||
),
|
||||
items: _radioPresets
|
||||
.map(
|
||||
(preset) => DropdownMenuItem<_RadioPreset>(
|
||||
value: preset,
|
||||
child: Text(
|
||||
preset.label,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
onChanged: (preset) {
|
||||
if (preset == null) return;
|
||||
setState(() {
|
||||
_selectedPreset = preset;
|
||||
});
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
_buildContentCard(
|
||||
colorScheme: colorScheme,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
_selectedPreset.label,
|
||||
style: Theme.of(context).textTheme.titleMedium
|
||||
?.copyWith(fontWeight: FontWeight.w700),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
_selectedPreset.summary,
|
||||
style: Theme.of(context).textTheme.bodyMedium,
|
||||
),
|
||||
SizedBox(height: 14),
|
||||
...[
|
||||
_FeatureItem(
|
||||
icon: Icons.rule,
|
||||
text: l10n.wizardPresetNote1,
|
||||
),
|
||||
_FeatureItem(
|
||||
icon: Icons.settings_input_antenna,
|
||||
text: l10n.wizardPresetNote2,
|
||||
),
|
||||
_FeatureItem(
|
||||
icon: Icons.public,
|
||||
text: l10n.wizardPresetNote3,
|
||||
),
|
||||
].map(
|
||||
(feature) => Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 5),
|
||||
child: _buildFeatureRow(
|
||||
context,
|
||||
colorScheme,
|
||||
feature,
|
||||
iconSize: 20,
|
||||
gap: 12,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
String _deviceSetupButtonLabel(BuildContext context) {
|
||||
final isConnected = context
|
||||
.watch<ConnectionProvider>()
|
||||
.deviceInfo
|
||||
.isConnected;
|
||||
if (_isApplyingDeviceSetup) {
|
||||
return AppLocalizations.of(context)!.wizardSaving;
|
||||
}
|
||||
final l10n = AppLocalizations.of(context)!;
|
||||
return isConnected
|
||||
? l10n.wizardSaveAndContinue
|
||||
: l10n.wizardConnectDeviceTitle;
|
||||
}
|
||||
|
||||
Widget _buildChannelPage(
|
||||
BuildContext context,
|
||||
AppLocalizations l10n,
|
||||
ColorScheme colorScheme,
|
||||
) {
|
||||
return _buildPage(
|
||||
icon: Icons.campaign,
|
||||
icon: Icons.groups_rounded,
|
||||
iconColor: Colors.purple,
|
||||
title: l10n.wizardChannelTitle,
|
||||
description: l10n.wizardChannelDescription,
|
||||
title: l10n.wizardNetworkTitle,
|
||||
description: l10n.wizardNetworkDescription,
|
||||
features: [
|
||||
_FeatureItem(icon: Icons.public, text: l10n.wizardChannelFeature1),
|
||||
_FeatureItem(icon: Icons.groups, text: l10n.wizardChannelFeature2),
|
||||
_FeatureItem(icon: Icons.send, text: l10n.wizardChannelFeature3),
|
||||
_FeatureItem(
|
||||
icon: Icons.person_add_alt_1,
|
||||
text: l10n.wizardNetworkFeature1,
|
||||
),
|
||||
_FeatureItem(icon: Icons.route, text: l10n.wizardNetworkFeature2),
|
||||
_FeatureItem(icon: Icons.hub, text: l10n.wizardNetworkFeature3),
|
||||
],
|
||||
colorScheme: colorScheme,
|
||||
);
|
||||
@@ -214,17 +680,14 @@ class _WelcomeWizardScreenState extends State<WelcomeWizardScreen> {
|
||||
ColorScheme colorScheme,
|
||||
) {
|
||||
return _buildPage(
|
||||
icon: Icons.people,
|
||||
icon: Icons.map_rounded,
|
||||
iconColor: Colors.teal,
|
||||
title: l10n.wizardContactsTitle,
|
||||
description: l10n.wizardContactsDescription,
|
||||
title: l10n.wizardMapOpsTitle,
|
||||
description: l10n.wizardMapOpsDescription,
|
||||
features: [
|
||||
_FeatureItem(icon: Icons.person_add, text: l10n.wizardContactsFeature1),
|
||||
_FeatureItem(icon: Icons.chat, text: l10n.wizardContactsFeature2),
|
||||
_FeatureItem(
|
||||
icon: Icons.battery_std,
|
||||
text: l10n.wizardContactsFeature3,
|
||||
),
|
||||
_FeatureItem(icon: Icons.location_on, text: l10n.wizardMapOpsFeature1),
|
||||
_FeatureItem(icon: Icons.draw, text: l10n.wizardMapOpsFeature2),
|
||||
_FeatureItem(icon: Icons.router, text: l10n.wizardMapOpsFeature3),
|
||||
],
|
||||
colorScheme: colorScheme,
|
||||
);
|
||||
@@ -236,18 +699,17 @@ class _WelcomeWizardScreenState extends State<WelcomeWizardScreen> {
|
||||
ColorScheme colorScheme,
|
||||
) {
|
||||
return _buildPage(
|
||||
icon: Icons.map,
|
||||
icon: Icons.tune_rounded,
|
||||
iconColor: Colors.red,
|
||||
title: l10n.wizardMapTitle,
|
||||
description: l10n.wizardMapDescription,
|
||||
title: l10n.wizardToolsTitle,
|
||||
description: l10n.wizardToolsDescription,
|
||||
features: [
|
||||
_FeatureItem(icon: Icons.location_on, text: l10n.wizardMapFeature1),
|
||||
_FeatureItem(icon: Icons.settings, text: l10n.wizardToolsFeature1),
|
||||
_FeatureItem(icon: Icons.sensors, text: l10n.wizardToolsFeature2),
|
||||
_FeatureItem(
|
||||
icon: Icons.person_pin_circle,
|
||||
text: l10n.wizardMapFeature2,
|
||||
icon: Icons.analytics_outlined,
|
||||
text: l10n.wizardToolsFeature3,
|
||||
),
|
||||
_FeatureItem(icon: Icons.offline_pin, text: l10n.wizardMapFeature3),
|
||||
_FeatureItem(icon: Icons.draw, text: l10n.wizardMapFeature4),
|
||||
],
|
||||
colorScheme: colorScheme,
|
||||
);
|
||||
@@ -261,69 +723,149 @@ class _WelcomeWizardScreenState extends State<WelcomeWizardScreen> {
|
||||
List<_FeatureItem>? features,
|
||||
required ColorScheme colorScheme,
|
||||
}) {
|
||||
return SingleChildScrollView(
|
||||
padding: const EdgeInsets.all(24.0),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
const SizedBox(height: 20),
|
||||
// Icon
|
||||
Container(
|
||||
padding: const EdgeInsets.all(24.0),
|
||||
decoration: BoxDecoration(
|
||||
color: iconColor.withValues(alpha: 0.1),
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
child: Icon(icon, size: 80, color: iconColor),
|
||||
final l10n = AppLocalizations.of(context)!;
|
||||
return LayoutBuilder(
|
||||
builder: (context, constraints) => Center(
|
||||
child: ConstrainedBox(
|
||||
constraints: BoxConstraints(
|
||||
maxWidth: 560,
|
||||
maxHeight: constraints.maxHeight,
|
||||
),
|
||||
const SizedBox(height: 32),
|
||||
// Title
|
||||
Text(
|
||||
title,
|
||||
style: Theme.of(context).textTheme.headlineMedium?.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
color: colorScheme.onSurface,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
// Description
|
||||
Text(
|
||||
description,
|
||||
style: Theme.of(context).textTheme.bodyLarge?.copyWith(
|
||||
color: colorScheme.onSurface.withValues(alpha: 0.7),
|
||||
height: 1.5,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
if (features != null && features.isNotEmpty) ...[
|
||||
const SizedBox(height: 32),
|
||||
// Features list
|
||||
...features.map(
|
||||
(feature) => Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 8.0),
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(feature.icon, color: colorScheme.primary, size: 24),
|
||||
const SizedBox(width: 16),
|
||||
Expanded(
|
||||
child: Text(
|
||||
feature.text,
|
||||
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
|
||||
color: colorScheme.onSurface,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.fromLTRB(20, 12, 20, 12),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
_buildPageHeader(
|
||||
context: context,
|
||||
colorScheme: colorScheme,
|
||||
icon: icon,
|
||||
iconColor: iconColor,
|
||||
title: title,
|
||||
description: description,
|
||||
badge: l10n.wizardOverviewBadge,
|
||||
),
|
||||
),
|
||||
if (features != null && features.isNotEmpty) ...[
|
||||
const SizedBox(height: 18),
|
||||
_buildContentCard(
|
||||
colorScheme: colorScheme,
|
||||
child: Column(
|
||||
children: features
|
||||
.map(
|
||||
(feature) => Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 7),
|
||||
child: _buildFeatureRow(
|
||||
context,
|
||||
colorScheme,
|
||||
feature,
|
||||
),
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
],
|
||||
const SizedBox(height: 20),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildPageHeader({
|
||||
required BuildContext context,
|
||||
required ColorScheme colorScheme,
|
||||
required IconData icon,
|
||||
required Color iconColor,
|
||||
required String title,
|
||||
required String description,
|
||||
required String badge,
|
||||
}) {
|
||||
return Column(
|
||||
children: [
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 6),
|
||||
decoration: BoxDecoration(
|
||||
color: colorScheme.secondaryContainer,
|
||||
borderRadius: BorderRadius.circular(999),
|
||||
),
|
||||
child: Text(
|
||||
badge,
|
||||
style: Theme.of(context).textTheme.labelLarge?.copyWith(
|
||||
color: colorScheme.onSecondaryContainer,
|
||||
fontWeight: FontWeight.w700,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
Container(
|
||||
padding: const EdgeInsets.all(18),
|
||||
decoration: BoxDecoration(
|
||||
color: iconColor.withValues(alpha: 0.1),
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
child: Icon(icon, size: 56, color: iconColor),
|
||||
),
|
||||
const SizedBox(height: 18),
|
||||
Text(
|
||||
title,
|
||||
style: Theme.of(context).textTheme.headlineSmall?.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
color: colorScheme.onSurface,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
Text(
|
||||
description,
|
||||
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
|
||||
color: colorScheme.onSurface.withValues(alpha: 0.72),
|
||||
height: 1.4,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildContentCard({
|
||||
required ColorScheme colorScheme,
|
||||
required Widget child,
|
||||
}) {
|
||||
return Container(
|
||||
padding: const EdgeInsets.all(16),
|
||||
decoration: BoxDecoration(
|
||||
color: colorScheme.surfaceContainerHighest,
|
||||
borderRadius: BorderRadius.circular(18),
|
||||
),
|
||||
child: child,
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildFeatureRow(
|
||||
BuildContext context,
|
||||
ColorScheme colorScheme,
|
||||
_FeatureItem feature, {
|
||||
double iconSize = 22,
|
||||
double gap = 14,
|
||||
}) {
|
||||
return Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Icon(feature.icon, color: colorScheme.primary, size: iconSize),
|
||||
SizedBox(width: gap),
|
||||
Expanded(
|
||||
child: Text(
|
||||
feature.text,
|
||||
style: Theme.of(
|
||||
context,
|
||||
).textTheme.bodyMedium?.copyWith(color: colorScheme.onSurface),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _FeatureItem {
|
||||
@@ -332,3 +874,23 @@ class _FeatureItem {
|
||||
|
||||
_FeatureItem({required this.icon, required this.text});
|
||||
}
|
||||
|
||||
class _RadioPreset {
|
||||
final String id;
|
||||
final String label;
|
||||
final String summary;
|
||||
final int frequencyKhz;
|
||||
final int bandwidth;
|
||||
final int spreadingFactor;
|
||||
final int codingRate;
|
||||
|
||||
const _RadioPreset({
|
||||
required this.id,
|
||||
required this.label,
|
||||
required this.summary,
|
||||
required this.frequencyKhz,
|
||||
required this.bandwidth,
|
||||
required this.spreadingFactor,
|
||||
required this.codingRate,
|
||||
});
|
||||
}
|
||||
|
||||
156
lib/services/app_config_snapshot_service.dart
Normal file
156
lib/services/app_config_snapshot_service.dart
Normal file
@@ -0,0 +1,156 @@
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
|
||||
import '../models/config_profile.dart';
|
||||
import '../providers/app_provider.dart';
|
||||
import 'image_preferences.dart';
|
||||
import 'profiles_feature_service.dart';
|
||||
import 'route_hash_preferences.dart';
|
||||
import 'voice_bitrate_preferences.dart';
|
||||
|
||||
class AppConfigSnapshotService {
|
||||
Future<AppSettingsProfileSection> capture(AppProvider appProvider) async {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
final locationTracking = appProvider.locationTrackingService;
|
||||
return AppSettingsProfileSection(
|
||||
mapEnabled: appProvider.isMapEnabled,
|
||||
contactsEnabled: appProvider.isContactsEnabled,
|
||||
sensorsEnabled: appProvider.isSensorsEnabled,
|
||||
voiceSilenceTrimmingEnabled: appProvider.isVoiceSilenceTrimmingEnabled,
|
||||
voiceBandPassFilterEnabled: appProvider.isVoiceBandPassFilterEnabled,
|
||||
voiceCompressorEnabled: appProvider.isVoiceCompressorEnabled,
|
||||
voiceLimiterEnabled: appProvider.isVoiceLimiterEnabled,
|
||||
voiceAutoGainEnabled: appProvider.isVoiceAutoGainEnabled,
|
||||
voiceEchoCancellationEnabled: appProvider.isVoiceEchoCancellationEnabled,
|
||||
voiceNoiseSuppressionEnabled: appProvider.isVoiceNoiseSuppressionEnabled,
|
||||
messageFontScale: appProvider.messageFontScale,
|
||||
autoRouteRotationEnabled: appProvider.autoRouteRotationEnabled,
|
||||
clearPathOnMaxRetry: appProvider.clearPathOnMaxRetry,
|
||||
nearestRelayFallbackEnabled: appProvider.nearestRelayFallbackEnabled,
|
||||
voiceBitrate: await VoiceBitratePreferences.getBitrate(),
|
||||
routeHashSize: await RouteHashPreferences.getHashSize(),
|
||||
imageMaxSize: await ImagePreferences.getMaxSize(),
|
||||
imageCompression: await ImagePreferences.getCompression(),
|
||||
imageGrayscale: await ImagePreferences.getGrayscale(),
|
||||
imageUltraMode: await ImagePreferences.getUltraMode(),
|
||||
showRxTxIndicators:
|
||||
prefs.getBool(
|
||||
ProfileStorageScope.scopedKey('show_rx_tx_indicators'),
|
||||
) ??
|
||||
true,
|
||||
fastLocationUpdatesEnabled: locationTracking.fastLocationUpdatesEnabled,
|
||||
fastLocationMovementThresholdMeters:
|
||||
locationTracking.fastLocationMovementThresholdMeters,
|
||||
fastLocationActiveCadenceSeconds:
|
||||
locationTracking.fastLocationActiveCadenceSeconds,
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> apply(
|
||||
AppSettingsProfileSection? section,
|
||||
AppProvider appProvider,
|
||||
) async {
|
||||
if (section == null || section.isEmpty) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (section.mapEnabled != null) {
|
||||
await appProvider.toggleMapEnabled(section.mapEnabled!);
|
||||
}
|
||||
if (section.contactsEnabled != null) {
|
||||
await appProvider.toggleContactsEnabled(section.contactsEnabled!);
|
||||
}
|
||||
if (section.sensorsEnabled != null) {
|
||||
await appProvider.toggleSensorsEnabled(section.sensorsEnabled!);
|
||||
}
|
||||
if (section.voiceSilenceTrimmingEnabled != null) {
|
||||
await appProvider.toggleVoiceSilenceTrimmingEnabled(
|
||||
section.voiceSilenceTrimmingEnabled!,
|
||||
);
|
||||
}
|
||||
if (section.voiceBandPassFilterEnabled != null) {
|
||||
await appProvider.toggleVoiceBandPassFilterEnabled(
|
||||
section.voiceBandPassFilterEnabled!,
|
||||
);
|
||||
}
|
||||
if (section.voiceCompressorEnabled != null) {
|
||||
await appProvider.toggleVoiceCompressorEnabled(
|
||||
section.voiceCompressorEnabled!,
|
||||
);
|
||||
}
|
||||
if (section.voiceLimiterEnabled != null) {
|
||||
await appProvider.toggleVoiceLimiterEnabled(section.voiceLimiterEnabled!);
|
||||
}
|
||||
if (section.voiceAutoGainEnabled != null) {
|
||||
await appProvider.toggleVoiceAutoGainEnabled(
|
||||
section.voiceAutoGainEnabled!,
|
||||
);
|
||||
}
|
||||
if (section.voiceEchoCancellationEnabled != null) {
|
||||
await appProvider.toggleVoiceEchoCancellationEnabled(
|
||||
section.voiceEchoCancellationEnabled!,
|
||||
);
|
||||
}
|
||||
if (section.voiceNoiseSuppressionEnabled != null) {
|
||||
await appProvider.toggleVoiceNoiseSuppressionEnabled(
|
||||
section.voiceNoiseSuppressionEnabled!,
|
||||
);
|
||||
}
|
||||
if (section.messageFontScale != null) {
|
||||
await appProvider.setMessageFontScale(section.messageFontScale!);
|
||||
}
|
||||
if (section.autoRouteRotationEnabled != null) {
|
||||
await appProvider.toggleAutoRouteRotationEnabled(
|
||||
section.autoRouteRotationEnabled!,
|
||||
);
|
||||
}
|
||||
if (section.clearPathOnMaxRetry != null) {
|
||||
await appProvider.toggleClearPathOnMaxRetry(section.clearPathOnMaxRetry!);
|
||||
}
|
||||
if (section.nearestRelayFallbackEnabled != null) {
|
||||
await appProvider.toggleNearestRelayFallbackEnabled(
|
||||
section.nearestRelayFallbackEnabled!,
|
||||
);
|
||||
}
|
||||
if (section.voiceBitrate != null) {
|
||||
await VoiceBitratePreferences.setBitrate(section.voiceBitrate!);
|
||||
}
|
||||
if (section.routeHashSize != null) {
|
||||
await RouteHashPreferences.setHashSize(section.routeHashSize!);
|
||||
}
|
||||
if (section.imageMaxSize != null) {
|
||||
await ImagePreferences.setMaxSize(section.imageMaxSize!);
|
||||
}
|
||||
if (section.imageCompression != null) {
|
||||
await ImagePreferences.setCompression(section.imageCompression!);
|
||||
}
|
||||
if (section.imageGrayscale != null) {
|
||||
await ImagePreferences.setGrayscale(section.imageGrayscale!);
|
||||
}
|
||||
if (section.imageUltraMode != null) {
|
||||
await ImagePreferences.setUltraMode(section.imageUltraMode!);
|
||||
}
|
||||
if (section.showRxTxIndicators != null) {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
await prefs.setBool(
|
||||
ProfileStorageScope.scopedKey('show_rx_tx_indicators'),
|
||||
section.showRxTxIndicators!,
|
||||
);
|
||||
}
|
||||
|
||||
final locationTracking = appProvider.locationTrackingService;
|
||||
if (section.fastLocationUpdatesEnabled != null) {
|
||||
locationTracking.fastLocationUpdatesEnabled =
|
||||
section.fastLocationUpdatesEnabled!;
|
||||
}
|
||||
if (section.fastLocationMovementThresholdMeters != null) {
|
||||
locationTracking.fastLocationMovementThresholdMeters =
|
||||
section.fastLocationMovementThresholdMeters!;
|
||||
}
|
||||
if (section.fastLocationActiveCadenceSeconds != null) {
|
||||
locationTracking.fastLocationActiveCadenceSeconds =
|
||||
section.fastLocationActiveCadenceSeconds!;
|
||||
}
|
||||
await locationTracking.saveSettings();
|
||||
await appProvider.reloadProfileScopedSettings();
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,10 @@
|
||||
import 'dart:async';
|
||||
import 'dart:io';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:geolocator/geolocator.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import 'package:meshcore_client/meshcore_client.dart';
|
||||
import 'profiles_feature_service.dart';
|
||||
|
||||
/// Background location tracking service for SAR operations
|
||||
/// Tracks user location and sends periodic updates via MeshCore BLE
|
||||
@@ -14,6 +16,11 @@ class BackgroundLocationService {
|
||||
static const String _prefKeyLastLon = 'background_last_lon';
|
||||
|
||||
MeshCoreBleService? _bleService;
|
||||
|
||||
String _scopedKey(String baseKey) {
|
||||
return ProfileStorageScope.scopedKey(baseKey);
|
||||
}
|
||||
|
||||
bool _isInitialized = false;
|
||||
StreamSubscription<Position>? _positionSubscription;
|
||||
|
||||
@@ -58,18 +65,27 @@ class BackgroundLocationService {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (Platform.isIOS && permission != LocationPermission.always) {
|
||||
permission = await Geolocator.requestPermission();
|
||||
if (permission != LocationPermission.always) {
|
||||
debugPrint(
|
||||
'⚠️ [BackgroundLocation] iOS background tracking requires Always permission',
|
||||
);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Save settings
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
await prefs.setBool(_prefKeyEnabled, true);
|
||||
await prefs.setDouble(_prefKeyDistance, distanceThreshold);
|
||||
await prefs.setBool(_scopedKey(_prefKeyEnabled), true);
|
||||
await prefs.setDouble(_scopedKey(_prefKeyDistance), distanceThreshold);
|
||||
|
||||
// Start listening to position updates
|
||||
Position? lastPosition;
|
||||
try {
|
||||
_positionSubscription =
|
||||
Geolocator.getPositionStream(
|
||||
locationSettings: LocationSettings(
|
||||
accuracy: LocationAccuracy.best,
|
||||
locationSettings: _buildLocationSettings(
|
||||
distanceFilter: distanceThreshold.toInt(),
|
||||
),
|
||||
).listen((Position position) async {
|
||||
@@ -100,8 +116,14 @@ class BackgroundLocationService {
|
||||
lastPosition = position;
|
||||
|
||||
// Save to preferences
|
||||
await prefs.setDouble(_prefKeyLastLat, position.latitude);
|
||||
await prefs.setDouble(_prefKeyLastLon, position.longitude);
|
||||
await prefs.setDouble(
|
||||
_scopedKey(_prefKeyLastLat),
|
||||
position.latitude,
|
||||
);
|
||||
await prefs.setDouble(
|
||||
_scopedKey(_prefKeyLastLon),
|
||||
position.longitude,
|
||||
);
|
||||
|
||||
// Update device's advertised location
|
||||
if (_bleService != null && _bleService!.isConnected) {
|
||||
@@ -151,7 +173,7 @@ class BackgroundLocationService {
|
||||
_positionSubscription = null;
|
||||
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
await prefs.setBool(_prefKeyEnabled, false);
|
||||
await prefs.setBool(_scopedKey(_prefKeyEnabled), false);
|
||||
debugPrint('✅ [BackgroundLocation] Tracking stopped');
|
||||
}
|
||||
|
||||
@@ -159,16 +181,34 @@ class BackgroundLocationService {
|
||||
/// Note: This will restart tracking with the new threshold
|
||||
Future<void> updateDistanceThreshold(double distance) async {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
await prefs.setDouble(_prefKeyDistance, distance);
|
||||
await prefs.setDouble(_scopedKey(_prefKeyDistance), distance);
|
||||
debugPrint(
|
||||
'📏 [BackgroundLocation] Distance threshold updated to ${distance}m',
|
||||
);
|
||||
|
||||
// Restart tracking if currently enabled
|
||||
final isEnabled = prefs.getBool(_prefKeyEnabled) ?? false;
|
||||
final isEnabled = prefs.getBool(_scopedKey(_prefKeyEnabled)) ?? false;
|
||||
if (isEnabled && _bleService != null) {
|
||||
await stopTracking();
|
||||
await startTracking(distanceThreshold: distance);
|
||||
}
|
||||
}
|
||||
|
||||
LocationSettings _buildLocationSettings({int distanceFilter = 10}) {
|
||||
if (Platform.isIOS) {
|
||||
return AppleSettings(
|
||||
accuracy: LocationAccuracy.best,
|
||||
activityType: ActivityType.fitness,
|
||||
allowBackgroundLocationUpdates: true,
|
||||
distanceFilter: distanceFilter,
|
||||
pauseLocationUpdatesAutomatically: false,
|
||||
showBackgroundLocationIndicator: true,
|
||||
);
|
||||
}
|
||||
|
||||
return LocationSettings(
|
||||
accuracy: LocationAccuracy.best,
|
||||
distanceFilter: distanceFilter,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
178
lib/services/bthome_met_history.dart
Normal file
178
lib/services/bthome_met_history.dart
Normal file
@@ -0,0 +1,178 @@
|
||||
import '../models/contact.dart';
|
||||
|
||||
enum BTHomeMetMeasurement {
|
||||
temperature(1, 'Temperature', '°C'),
|
||||
humidity(2, 'Humidity', '%'),
|
||||
windSpeed(3, 'Wind speed', 'm/s'),
|
||||
gust(4, 'Wind gust', 'm/s'),
|
||||
rain(5, 'Rain', 'mm');
|
||||
|
||||
const BTHomeMetMeasurement(this.id, this.label, this.unit);
|
||||
|
||||
final int id;
|
||||
final String label;
|
||||
final String unit;
|
||||
|
||||
static BTHomeMetMeasurement? fromId(int id) {
|
||||
for (final value in BTHomeMetMeasurement.values) {
|
||||
if (value.id == id) {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
class BTHomeMetHistoryPage {
|
||||
const BTHomeMetHistoryPage({
|
||||
required this.measurement,
|
||||
required this.page,
|
||||
required this.values,
|
||||
});
|
||||
|
||||
final BTHomeMetMeasurement measurement;
|
||||
final int page;
|
||||
final List<double> values;
|
||||
|
||||
double? get latest => values.isEmpty ? null : values.last;
|
||||
double? get minimum => values.isEmpty
|
||||
? null
|
||||
: values.reduce((left, right) => left < right ? left : right);
|
||||
double? get maximum => values.isEmpty
|
||||
? null
|
||||
: values.reduce((left, right) => left > right ? left : right);
|
||||
}
|
||||
|
||||
class BTHomeMetHistoryFormatException implements Exception {
|
||||
const BTHomeMetHistoryFormatException(this.message);
|
||||
|
||||
final String message;
|
||||
|
||||
@override
|
||||
String toString() => message;
|
||||
}
|
||||
|
||||
class BTHomeMetHistoryParser {
|
||||
static BTHomeMetHistoryPage parse(String text) {
|
||||
final parts = text
|
||||
.trim()
|
||||
.split(',')
|
||||
.map((part) => part.trim())
|
||||
.toList(growable: false);
|
||||
if (parts.length < 3) {
|
||||
throw const BTHomeMetHistoryFormatException(
|
||||
'MET history response is too short.',
|
||||
);
|
||||
}
|
||||
|
||||
final measurementId = int.tryParse(parts[0]);
|
||||
final page = int.tryParse(parts[1]);
|
||||
final count = int.tryParse(parts[2]);
|
||||
if (measurementId == null || page == null || count == null) {
|
||||
throw const BTHomeMetHistoryFormatException(
|
||||
'MET history header is invalid.',
|
||||
);
|
||||
}
|
||||
|
||||
final measurement = BTHomeMetMeasurement.fromId(measurementId);
|
||||
if (measurement == null) {
|
||||
throw BTHomeMetHistoryFormatException(
|
||||
'Unsupported MET history measurement id: $measurementId',
|
||||
);
|
||||
}
|
||||
if (count < 0) {
|
||||
throw const BTHomeMetHistoryFormatException(
|
||||
'MET history sample count is invalid.',
|
||||
);
|
||||
}
|
||||
if (parts.length != count + 3) {
|
||||
throw BTHomeMetHistoryFormatException(
|
||||
'MET history sample count mismatch: expected $count values, got ${parts.length - 3}.',
|
||||
);
|
||||
}
|
||||
|
||||
final values = <double>[];
|
||||
for (final part in parts.skip(3)) {
|
||||
final value = double.tryParse(part);
|
||||
if (value == null) {
|
||||
throw BTHomeMetHistoryFormatException(
|
||||
'Invalid MET history sample value: $part',
|
||||
);
|
||||
}
|
||||
values.add(value);
|
||||
}
|
||||
|
||||
return BTHomeMetHistoryPage(
|
||||
measurement: measurement,
|
||||
page: page,
|
||||
values: List<double>.unmodifiable(values),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
bool _isTruthyCapabilityValue(Object? value) {
|
||||
if (value is num) {
|
||||
return value > 0;
|
||||
}
|
||||
if (value is bool) {
|
||||
return value;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool _hasBTHomeMetCapability(Contact? contact) {
|
||||
final extraSensorData = contact?.telemetry?.extraSensorData;
|
||||
if (extraSensorData == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// MET history is only enabled when the node advertises an explicit
|
||||
// capability marker on channel 1. Accept a small set of channel-1 marker
|
||||
// keys so the app remains tolerant while firmware-side encoding settles.
|
||||
const capabilityKeys = <String>[
|
||||
'met_capability',
|
||||
'met_capability_1',
|
||||
'generic_sensor_1',
|
||||
'light_level_1',
|
||||
];
|
||||
|
||||
for (final key in capabilityKeys) {
|
||||
if (_isTruthyCapabilityValue(extraSensorData[key])) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
List<BTHomeMetMeasurement> bTHomeMetMeasurementsForContact(Contact? contact) {
|
||||
final telemetry = contact?.telemetry;
|
||||
if (telemetry == null || !_hasBTHomeMetCapability(contact)) {
|
||||
return const <BTHomeMetMeasurement>[];
|
||||
}
|
||||
|
||||
final measurements = <BTHomeMetMeasurement>[
|
||||
if (telemetry.temperature != null) BTHomeMetMeasurement.temperature,
|
||||
if (telemetry.humidity != null) BTHomeMetMeasurement.humidity,
|
||||
];
|
||||
|
||||
final extraSensorData = telemetry.extraSensorData;
|
||||
if (extraSensorData != null) {
|
||||
if (extraSensorData.keys.any(
|
||||
(key) => key.startsWith('speed_') || key.startsWith('signed_speed_'),
|
||||
)) {
|
||||
measurements.add(BTHomeMetMeasurement.windSpeed);
|
||||
}
|
||||
if (extraSensorData.keys.any((key) => key.startsWith('gust_'))) {
|
||||
measurements.add(BTHomeMetMeasurement.gust);
|
||||
}
|
||||
if (extraSensorData.keys.any((key) => key.startsWith('rain_'))) {
|
||||
measurements.add(BTHomeMetMeasurement.rain);
|
||||
}
|
||||
}
|
||||
|
||||
return List<BTHomeMetMeasurement>.unmodifiable(measurements);
|
||||
}
|
||||
|
||||
bool supportsBTHomeMetHistory(Contact? contact) =>
|
||||
bTHomeMetMeasurementsForContact(contact).isNotEmpty;
|
||||
@@ -1,6 +1,5 @@
|
||||
import 'dart:io' show Platform;
|
||||
import 'package:flutter/foundation.dart' show TargetPlatform, defaultTargetPlatform, debugPrint, kIsWeb;
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
|
||||
/// Service for accessing build information from native platform code
|
||||
/// Currently supports Android only - returns "unknown" for other platforms
|
||||
@@ -25,7 +24,7 @@ class BuildInfoService {
|
||||
}
|
||||
|
||||
// Only Android has the platform channel implementation
|
||||
if (!Platform.isAndroid) {
|
||||
if (kIsWeb || defaultTargetPlatform != TargetPlatform.android) {
|
||||
debugPrint('[BuildInfoService] Not on Android platform, returning "unknown"');
|
||||
_cachedCommitHash = 'unknown';
|
||||
return _cachedCommitHash!;
|
||||
|
||||
@@ -5,6 +5,76 @@ import 'package:meshcore_client/meshcore_client.dart';
|
||||
/// Cayenne LPP (Low Power Payload) data parser
|
||||
/// Used for decoding telemetry sensor data from MeshCore devices
|
||||
class CayenneLppParser {
|
||||
static const int _selfTelemetryChannel = 1;
|
||||
static const int _lppGenericSensor = 100;
|
||||
static const int _lppCurrent = 117;
|
||||
static const int _lppFrequency = 118;
|
||||
static const int _lppPercentage = 120;
|
||||
static const int _lppAltitude = 121;
|
||||
static const int _lppConcentration = 125;
|
||||
static const int _lppPower = 128;
|
||||
static const int _lppSpeed = 129;
|
||||
static const int _lppDistance = 130;
|
||||
static const int _lppEnergy = 131;
|
||||
static const int _lppDirection = 132;
|
||||
static const int _lppUnixTime = 133;
|
||||
static const int _lppColour = 135;
|
||||
static const int _lppGust = 137;
|
||||
static const int _lppDewPoint = 138;
|
||||
static const int _lppRain = 139;
|
||||
static const int _lppSwitch = 142;
|
||||
static const int _lppBinaryBool = 143;
|
||||
static const int _lppBinaryPowerSwitch = 144;
|
||||
static const int _lppBinaryOpen = 145;
|
||||
static const int _lppBinaryBatteryLow = 146;
|
||||
static const int _lppBinaryCharging = 147;
|
||||
static const int _lppBinaryCarbonMonoxide = 148;
|
||||
static const int _lppBinaryCold = 149;
|
||||
static const int _lppBinaryConnectivity = 150;
|
||||
static const int _lppBinaryDoor = 151;
|
||||
static const int _lppBinaryGarageDoor = 152;
|
||||
static const int _lppBinaryGas = 153;
|
||||
static const int _lppBinaryHeat = 154;
|
||||
static const int _lppBinaryLight = 155;
|
||||
static const int _lppBinaryLock = 156;
|
||||
static const int _lppBinaryMoisture = 157;
|
||||
static const int _lppBinaryMotion = 158;
|
||||
static const int _lppBinaryMoving = 159;
|
||||
static const int _lppBinaryOccupancy = 160;
|
||||
static const int _lppBinaryPlug = 161;
|
||||
static const int _lppBinaryPresence = 162;
|
||||
static const int _lppBinaryProblem = 163;
|
||||
static const int _lppBinaryRunning = 164;
|
||||
static const int _lppBinarySafety = 165;
|
||||
static const int _lppBinarySmoke = 166;
|
||||
static const int _lppBinarySound = 167;
|
||||
static const int _lppBinaryTamper = 168;
|
||||
static const int _lppBinaryVibration = 169;
|
||||
static const int _lppBinaryWindow = 170;
|
||||
static const int _lppButtonEvent = 171;
|
||||
static const int _lppDimmer = 172;
|
||||
static const int _lppUv = 173;
|
||||
static const int _lppLightLevel = 174;
|
||||
static const int _lppPm25 = 175;
|
||||
static const int _lppPm10 = 176;
|
||||
static const int _lppCo2 = 177;
|
||||
static const int _lppTvoc = 178;
|
||||
static const int _lppRpm = 179;
|
||||
static const int _lppConductivity = 180;
|
||||
static const int _lppRotation = 181;
|
||||
static const int _lppDuration = 182;
|
||||
static const int _lppAcceleration = 183;
|
||||
static const int _lppGyroRate = 184;
|
||||
static const int _lppVolume = 185;
|
||||
static const int _lppFlowRate = 186;
|
||||
static const int _lppVolumeStorage = 187;
|
||||
static const int _lppWater = 188;
|
||||
static const int _lppGasVolume = 189;
|
||||
static const int _lppMass = 190;
|
||||
static const int _lppSignedSpeed = 191;
|
||||
static const int _lppSignedPower = 192;
|
||||
static const int _lppSignedCurrent = 193;
|
||||
|
||||
/// Parse Cayenne LPP data into ContactTelemetry
|
||||
static ContactTelemetry parse(Uint8List data) {
|
||||
debugPrint(' [CayenneLPP] Parsing LPP data...');
|
||||
@@ -18,14 +88,18 @@ class CayenneLppParser {
|
||||
LatLng? gpsLocation;
|
||||
double? batteryPercentage;
|
||||
double? batteryMilliVolts;
|
||||
double? deferredBatteryMilliVolts;
|
||||
double? deferredBatteryPercentage;
|
||||
double? temperature;
|
||||
double? humidity;
|
||||
double? pressure;
|
||||
final extraSensorData = <String, dynamic>{};
|
||||
bool sawNonSelfChannel = false;
|
||||
|
||||
int fieldCount = 0;
|
||||
while (reader.hasRemaining) {
|
||||
if (fieldCount > 0 && _isZeroPaddedTail(data, reader.remainingBytesCount)) {
|
||||
if (fieldCount > 0 &&
|
||||
_isZeroPaddedTail(data, reader.remainingBytesCount)) {
|
||||
debugPrint(
|
||||
' Detected zero-padded telemetry tail, stopping parse at position '
|
||||
'${data.length - reader.remainingBytesCount}',
|
||||
@@ -41,6 +115,9 @@ class CayenneLppParser {
|
||||
|
||||
final channel = reader.readByte();
|
||||
debugPrint(' Channel: $channel');
|
||||
if (channel != _selfTelemetryChannel) {
|
||||
sawNonSelfChannel = true;
|
||||
}
|
||||
|
||||
final type = reader.readByte();
|
||||
debugPrint(
|
||||
@@ -65,14 +142,20 @@ class CayenneLppParser {
|
||||
final value = rawValue / 100.0;
|
||||
debugPrint(' Analog Input (raw): $rawValue');
|
||||
debugPrint(' Analog Input (volts): ${value}V');
|
||||
extraSensorData['analog_input_$channel'] = value;
|
||||
// If this is a battery reading
|
||||
if (channel == 0 || channel == 1) {
|
||||
if (_isDedicatedBatteryChannel(channel)) {
|
||||
batteryMilliVolts = value * 1000;
|
||||
batteryPercentage = _calculateBatteryPercentage(value);
|
||||
extraSensorData[_sourceChannelKey('battery')] = channel;
|
||||
extraSensorData[_sourceChannelKey('voltage')] = channel;
|
||||
debugPrint(
|
||||
' → Battery: ${batteryPercentage.toStringAsFixed(1)}% (${batteryMilliVolts.toStringAsFixed(0)}mV)',
|
||||
);
|
||||
} else if (_isDeferredBatteryChannel(channel)) {
|
||||
deferredBatteryMilliVolts = value * 1000;
|
||||
deferredBatteryPercentage = _calculateBatteryPercentage(value);
|
||||
extraSensorData['analog_input_$channel'] = value;
|
||||
} else {
|
||||
extraSensorData['analog_input_$channel'] = value;
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -85,7 +168,7 @@ class CayenneLppParser {
|
||||
break;
|
||||
|
||||
case MeshCoreConstants.lppIlluminanceSensor:
|
||||
final value = reader.readUInt16BE();
|
||||
final value = reader.readUInt16BE().toDouble();
|
||||
debugPrint(' Illuminance: $value lux');
|
||||
extraSensorData['illuminance_$channel'] = value;
|
||||
break;
|
||||
@@ -98,18 +181,36 @@ class CayenneLppParser {
|
||||
|
||||
case MeshCoreConstants.lppTemperatureSensor:
|
||||
final rawValue = reader.readInt16BE();
|
||||
temperature = rawValue / 10.0;
|
||||
final value = rawValue / 10.0;
|
||||
debugPrint(' Temperature (raw): $rawValue');
|
||||
debugPrint(
|
||||
' Temperature: ${temperature.toStringAsFixed(1)}°C',
|
||||
);
|
||||
debugPrint(' Temperature: ${value.toStringAsFixed(1)}°C');
|
||||
if (channel == _selfTelemetryChannel) {
|
||||
temperature = value;
|
||||
extraSensorData[_sourceChannelKey('temperature')] = channel;
|
||||
} else {
|
||||
extraSensorData['temperature_$channel'] = value;
|
||||
if (temperature == null) {
|
||||
temperature = value;
|
||||
extraSensorData[_sourceChannelKey('temperature')] = channel;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case MeshCoreConstants.lppHumiditySensor:
|
||||
final rawValue = reader.readByte();
|
||||
humidity = rawValue / 2.0;
|
||||
final value = rawValue / 2.0;
|
||||
debugPrint(' Humidity (raw): $rawValue');
|
||||
debugPrint(' Humidity: ${humidity.toStringAsFixed(1)}%');
|
||||
debugPrint(' Humidity: ${value.toStringAsFixed(1)}%');
|
||||
if (channel == _selfTelemetryChannel) {
|
||||
humidity = value;
|
||||
extraSensorData[_sourceChannelKey('humidity')] = channel;
|
||||
} else {
|
||||
extraSensorData['humidity_$channel'] = value;
|
||||
if (humidity == null) {
|
||||
humidity = value;
|
||||
extraSensorData[_sourceChannelKey('humidity')] = channel;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case MeshCoreConstants.lppAccelerometer:
|
||||
@@ -126,9 +227,19 @@ class CayenneLppParser {
|
||||
|
||||
case MeshCoreConstants.lppBarometer:
|
||||
final rawValue = reader.readUInt16BE();
|
||||
pressure = rawValue / 10.0;
|
||||
final value = rawValue / 10.0;
|
||||
debugPrint(' Barometer (raw): $rawValue');
|
||||
debugPrint(' Barometer: ${pressure.toStringAsFixed(1)} hPa');
|
||||
debugPrint(' Barometer: ${value.toStringAsFixed(1)} hPa');
|
||||
if (channel == _selfTelemetryChannel) {
|
||||
pressure = value;
|
||||
extraSensorData[_sourceChannelKey('pressure')] = channel;
|
||||
} else {
|
||||
extraSensorData['pressure_$channel'] = value;
|
||||
if (pressure == null) {
|
||||
pressure = value;
|
||||
extraSensorData[_sourceChannelKey('pressure')] = channel;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case MeshCoreConstants.lppVoltageSensor:
|
||||
@@ -136,12 +247,21 @@ class CayenneLppParser {
|
||||
final value = rawValue / 100.0;
|
||||
debugPrint(' Voltage (raw): $rawValue');
|
||||
debugPrint(' Voltage: ${value}V');
|
||||
// Treat voltage sensor as battery reading
|
||||
batteryMilliVolts = value * 1000;
|
||||
batteryPercentage = _calculateBatteryPercentage(value);
|
||||
debugPrint(
|
||||
' → Battery: ${batteryPercentage.toStringAsFixed(1)}% (${batteryMilliVolts.toStringAsFixed(0)}mV)',
|
||||
);
|
||||
if (_isDedicatedBatteryChannel(channel)) {
|
||||
batteryMilliVolts = value * 1000;
|
||||
batteryPercentage = _calculateBatteryPercentage(value);
|
||||
extraSensorData[_sourceChannelKey('battery')] = channel;
|
||||
extraSensorData[_sourceChannelKey('voltage')] = channel;
|
||||
debugPrint(
|
||||
' → Battery: ${batteryPercentage.toStringAsFixed(1)}% (${batteryMilliVolts.toStringAsFixed(0)}mV)',
|
||||
);
|
||||
} else if (_isDeferredBatteryChannel(channel)) {
|
||||
deferredBatteryMilliVolts = value * 1000;
|
||||
deferredBatteryPercentage = _calculateBatteryPercentage(value);
|
||||
extraSensorData['voltage_$channel'] = value;
|
||||
} else {
|
||||
extraSensorData['voltage_$channel'] = value;
|
||||
}
|
||||
break;
|
||||
|
||||
case MeshCoreConstants.lppGyrometer:
|
||||
@@ -194,15 +314,328 @@ class CayenneLppParser {
|
||||
}
|
||||
|
||||
gpsLocation = LatLng(lat, lon);
|
||||
extraSensorData[_sourceChannelKey('gps')] = channel;
|
||||
extraSensorData['altitude_$channel'] = alt;
|
||||
break;
|
||||
|
||||
case _lppGenericSensor:
|
||||
final value = _readUInt32BE(reader).toDouble();
|
||||
debugPrint(' Generic Sensor: $value');
|
||||
extraSensorData['generic_sensor_$channel'] = value;
|
||||
break;
|
||||
|
||||
case _lppCurrent:
|
||||
final rawValue = reader.readInt16BE();
|
||||
final value = rawValue / 1000.0;
|
||||
debugPrint(' Current (raw): $rawValue');
|
||||
debugPrint(' Current: ${value}A');
|
||||
extraSensorData['current_$channel'] = value;
|
||||
break;
|
||||
|
||||
case _lppFrequency:
|
||||
final value = _readUInt32BE(reader).toDouble();
|
||||
debugPrint(' Frequency: ${value}Hz');
|
||||
extraSensorData['frequency_$channel'] = value;
|
||||
break;
|
||||
|
||||
case _lppPercentage:
|
||||
final value = reader.readByte().toDouble();
|
||||
debugPrint(' Percentage: $value%');
|
||||
if (_isDedicatedBatteryChannel(channel) || _isDeferredBatteryChannel(channel)) {
|
||||
batteryPercentage = value;
|
||||
extraSensorData[_sourceChannelKey('battery')] = channel;
|
||||
} else {
|
||||
extraSensorData['percentage_$channel'] = value;
|
||||
}
|
||||
break;
|
||||
|
||||
case _lppAltitude:
|
||||
final rawValue = reader.readInt16BE();
|
||||
final value = rawValue.toDouble();
|
||||
debugPrint(' Altitude: ${value}m');
|
||||
extraSensorData['altitude_$channel'] = value;
|
||||
break;
|
||||
|
||||
case _lppConcentration:
|
||||
final value = reader.readUInt16BE().toDouble();
|
||||
debugPrint(' Concentration: ${value}ppm');
|
||||
extraSensorData['concentration_$channel'] = value;
|
||||
break;
|
||||
|
||||
case _lppPower:
|
||||
final value = reader.readUInt16BE().toDouble();
|
||||
debugPrint(' Power: ${value}W');
|
||||
extraSensorData['power_$channel'] = value;
|
||||
break;
|
||||
|
||||
case _lppSpeed:
|
||||
final rawValue = reader.readUInt16BE();
|
||||
final value = rawValue / 100.0;
|
||||
debugPrint(' Speed: ${value}m/s');
|
||||
extraSensorData['speed_$channel'] = value;
|
||||
break;
|
||||
|
||||
case _lppDistance:
|
||||
final rawValue = _readUInt32BE(reader);
|
||||
final value = rawValue / 1000.0;
|
||||
debugPrint(' Distance: ${value}m');
|
||||
extraSensorData['distance_$channel'] = value;
|
||||
break;
|
||||
|
||||
case _lppEnergy:
|
||||
final rawValue = _readUInt32BE(reader);
|
||||
final value = rawValue / 1000.0;
|
||||
debugPrint(' Energy: ${value}kWh');
|
||||
extraSensorData['energy_$channel'] = value;
|
||||
break;
|
||||
|
||||
case _lppDirection:
|
||||
final value = reader.readUInt16BE().toDouble();
|
||||
debugPrint(' Direction: $value°');
|
||||
extraSensorData['direction_$channel'] = value;
|
||||
break;
|
||||
|
||||
case _lppUnixTime:
|
||||
final value = _readUInt32BE(reader);
|
||||
debugPrint(' Unix time: $value');
|
||||
extraSensorData['unixtime_$channel'] = value;
|
||||
break;
|
||||
|
||||
case _lppColour:
|
||||
final red = reader.readByte();
|
||||
final green = reader.readByte();
|
||||
final blue = reader.readByte();
|
||||
debugPrint(' Colour: r=$red, g=$green, b=$blue');
|
||||
extraSensorData['colour_$channel'] = {
|
||||
'r': red,
|
||||
'g': green,
|
||||
'b': blue,
|
||||
};
|
||||
break;
|
||||
|
||||
case _lppGust:
|
||||
final rawValue = reader.readUInt16BE();
|
||||
final value = rawValue / 100.0;
|
||||
debugPrint(' Gust: ${value}m/s');
|
||||
extraSensorData['gust_$channel'] = value;
|
||||
break;
|
||||
|
||||
case _lppDewPoint:
|
||||
final rawValue = reader.readInt16BE();
|
||||
final value = rawValue / 10.0;
|
||||
debugPrint(' Dew point: ${value.toStringAsFixed(1)}°C');
|
||||
extraSensorData['dew_$channel'] = value;
|
||||
break;
|
||||
|
||||
case _lppRain:
|
||||
final rawValue = reader.readUInt16BE();
|
||||
final value = rawValue / 10.0;
|
||||
debugPrint(' Rain: ${value}mm');
|
||||
extraSensorData['rain_$channel'] = value;
|
||||
break;
|
||||
|
||||
case _lppBinaryBool:
|
||||
case _lppBinaryPowerSwitch:
|
||||
case _lppBinaryOpen:
|
||||
case _lppBinaryBatteryLow:
|
||||
case _lppBinaryCharging:
|
||||
case _lppBinaryCarbonMonoxide:
|
||||
case _lppBinaryCold:
|
||||
case _lppBinaryConnectivity:
|
||||
case _lppBinaryDoor:
|
||||
case _lppBinaryGarageDoor:
|
||||
case _lppBinaryGas:
|
||||
case _lppBinaryHeat:
|
||||
case _lppBinaryLight:
|
||||
case _lppBinaryLock:
|
||||
case _lppBinaryMoisture:
|
||||
case _lppBinaryMotion:
|
||||
case _lppBinaryMoving:
|
||||
case _lppBinaryOccupancy:
|
||||
case _lppBinaryPlug:
|
||||
case _lppBinaryPresence:
|
||||
case _lppBinaryProblem:
|
||||
case _lppBinaryRunning:
|
||||
case _lppBinarySafety:
|
||||
case _lppBinarySmoke:
|
||||
case _lppBinarySound:
|
||||
case _lppBinaryTamper:
|
||||
case _lppBinaryVibration:
|
||||
case _lppBinaryWindow:
|
||||
final value = reader.readByte();
|
||||
debugPrint(' Binary state: $value');
|
||||
extraSensorData['${_binaryMetricKeyForType(type)}_$channel'] =
|
||||
value;
|
||||
break;
|
||||
|
||||
case _lppButtonEvent:
|
||||
final value = reader.readByte();
|
||||
debugPrint(' Button event: $value');
|
||||
extraSensorData['button_event_$channel'] = value;
|
||||
break;
|
||||
|
||||
case _lppDimmer:
|
||||
final value = _readInt8(reader);
|
||||
debugPrint(' Dimmer: $value');
|
||||
extraSensorData['dimmer_$channel'] = value;
|
||||
break;
|
||||
|
||||
case _lppUv:
|
||||
final value = reader.readByte() / 10.0;
|
||||
debugPrint(' UV index: $value');
|
||||
extraSensorData['uv_$channel'] = value;
|
||||
break;
|
||||
|
||||
case _lppLightLevel:
|
||||
final value = reader.readByte();
|
||||
debugPrint(' Light level: $value');
|
||||
extraSensorData['light_level_$channel'] = value;
|
||||
break;
|
||||
|
||||
case _lppPm25:
|
||||
final value = reader.readUInt16BE().toDouble();
|
||||
debugPrint(' PM2.5: $value');
|
||||
extraSensorData['pm25_$channel'] = value;
|
||||
break;
|
||||
|
||||
case _lppPm10:
|
||||
final value = reader.readUInt16BE().toDouble();
|
||||
debugPrint(' PM10: $value');
|
||||
extraSensorData['pm10_$channel'] = value;
|
||||
break;
|
||||
|
||||
case _lppCo2:
|
||||
final value = reader.readUInt16BE().toDouble();
|
||||
debugPrint(' CO2: $value');
|
||||
extraSensorData['co2_$channel'] = value;
|
||||
break;
|
||||
|
||||
case _lppTvoc:
|
||||
final value = reader.readUInt16BE().toDouble();
|
||||
debugPrint(' TVOC: $value');
|
||||
extraSensorData['tvoc_$channel'] = value;
|
||||
break;
|
||||
|
||||
case _lppRpm:
|
||||
final value = reader.readUInt16BE().toDouble();
|
||||
debugPrint(' RPM: $value');
|
||||
extraSensorData['rpm_$channel'] = value;
|
||||
break;
|
||||
|
||||
case _lppConductivity:
|
||||
final value = reader.readUInt16BE().toDouble();
|
||||
debugPrint(' Conductivity: $value');
|
||||
extraSensorData['conductivity_$channel'] = value;
|
||||
break;
|
||||
|
||||
case _lppRotation:
|
||||
final rawValue = reader.readInt16BE();
|
||||
final value = rawValue / 10.0;
|
||||
debugPrint(' Rotation: $value');
|
||||
extraSensorData['rotation_$channel'] = value;
|
||||
break;
|
||||
|
||||
case _lppDuration:
|
||||
final rawValue = _readUInt32BE(reader);
|
||||
final value = rawValue / 1000.0;
|
||||
debugPrint(' Duration: $value s');
|
||||
extraSensorData['duration_$channel'] = value;
|
||||
break;
|
||||
|
||||
case _lppAcceleration:
|
||||
final rawValue = _readInt32BE(reader);
|
||||
final value = rawValue / 1000000.0;
|
||||
debugPrint(' Acceleration: $value');
|
||||
extraSensorData['acceleration_$channel'] = value;
|
||||
break;
|
||||
|
||||
case _lppGyroRate:
|
||||
final rawValue = _readInt32BE(reader);
|
||||
final value = rawValue / 1000.0;
|
||||
debugPrint(' Gyro rate: $value');
|
||||
extraSensorData['gyro_rate_$channel'] = value;
|
||||
break;
|
||||
|
||||
case _lppVolume:
|
||||
final rawValue = _readUInt32BE(reader);
|
||||
final value = rawValue / 1000.0;
|
||||
debugPrint(' Volume: $value');
|
||||
extraSensorData['volume_$channel'] = value;
|
||||
break;
|
||||
|
||||
case _lppFlowRate:
|
||||
final rawValue = _readUInt32BE(reader);
|
||||
final value = rawValue / 1000.0;
|
||||
debugPrint(' Flow rate: $value');
|
||||
extraSensorData['flow_rate_$channel'] = value;
|
||||
break;
|
||||
|
||||
case _lppVolumeStorage:
|
||||
final rawValue = _readUInt32BE(reader);
|
||||
final value = rawValue / 1000.0;
|
||||
debugPrint(' Storage volume: $value');
|
||||
extraSensorData['volume_storage_$channel'] = value;
|
||||
break;
|
||||
|
||||
case _lppWater:
|
||||
final rawValue = _readUInt32BE(reader);
|
||||
final value = rawValue / 1000.0;
|
||||
debugPrint(' Water: $value');
|
||||
extraSensorData['water_$channel'] = value;
|
||||
break;
|
||||
|
||||
case _lppGasVolume:
|
||||
final rawValue = _readUInt32BE(reader);
|
||||
final value = rawValue / 1000.0;
|
||||
debugPrint(' Gas volume: $value');
|
||||
extraSensorData['gas_volume_$channel'] = value;
|
||||
break;
|
||||
|
||||
case _lppMass:
|
||||
final rawValue = _readUInt32BE(reader);
|
||||
final value = rawValue / 1000.0;
|
||||
debugPrint(' Mass: $value');
|
||||
extraSensorData['mass_$channel'] = value;
|
||||
break;
|
||||
|
||||
case _lppSignedSpeed:
|
||||
final rawValue = _readInt32BE(reader);
|
||||
final value = rawValue / 1000000.0;
|
||||
debugPrint(' Signed speed: $value');
|
||||
extraSensorData['signed_speed_$channel'] = value;
|
||||
break;
|
||||
|
||||
case _lppSignedPower:
|
||||
final rawValue = _readInt32BE(reader);
|
||||
final value = rawValue / 100.0;
|
||||
debugPrint(' Signed power: $value');
|
||||
extraSensorData['signed_power_$channel'] = value;
|
||||
break;
|
||||
|
||||
case _lppSignedCurrent:
|
||||
final rawValue = _readInt32BE(reader);
|
||||
final value = rawValue / 1000.0;
|
||||
debugPrint(' Signed current: $value');
|
||||
extraSensorData['signed_current_$channel'] = value;
|
||||
break;
|
||||
|
||||
case _lppSwitch:
|
||||
final value = reader.readByte();
|
||||
debugPrint(' Switch: $value');
|
||||
extraSensorData['switch_$channel'] = value;
|
||||
break;
|
||||
|
||||
default:
|
||||
debugPrint(
|
||||
' ⚠️ Unknown type, skipping remaining ${reader.remainingBytesCount} bytes',
|
||||
);
|
||||
// Unknown type, skip remaining to avoid parsing errors
|
||||
reader.skip(reader.remainingBytesCount);
|
||||
final size = _payloadSizeForType(type);
|
||||
if (size == null || reader.remainingBytesCount < size) {
|
||||
debugPrint(
|
||||
' ⚠️ Unknown type $type with unsupported size, stopping parse',
|
||||
);
|
||||
reader.skip(reader.remainingBytesCount);
|
||||
break;
|
||||
}
|
||||
debugPrint(' ⚠️ Unknown type $type, skipping $size bytes');
|
||||
reader.skip(size);
|
||||
break;
|
||||
}
|
||||
} catch (e) {
|
||||
@@ -212,6 +645,21 @@ class CayenneLppParser {
|
||||
}
|
||||
}
|
||||
|
||||
if (batteryMilliVolts == null &&
|
||||
batteryPercentage == null &&
|
||||
deferredBatteryMilliVolts != null &&
|
||||
deferredBatteryPercentage != null &&
|
||||
!sawNonSelfChannel) {
|
||||
batteryMilliVolts = deferredBatteryMilliVolts;
|
||||
batteryPercentage = deferredBatteryPercentage;
|
||||
extraSensorData[_sourceChannelKey('battery')] = _selfTelemetryChannel;
|
||||
extraSensorData[_sourceChannelKey('voltage')] = _selfTelemetryChannel;
|
||||
debugPrint(
|
||||
' Promoted self-channel voltage to battery: '
|
||||
'${batteryPercentage.toStringAsFixed(1)}% (${batteryMilliVolts.toStringAsFixed(0)}mV)',
|
||||
);
|
||||
}
|
||||
|
||||
debugPrint(' Parsed $fieldCount fields');
|
||||
debugPrint(' ✅ [CayenneLPP] Parsing complete');
|
||||
debugPrint(
|
||||
@@ -258,6 +706,165 @@ class CayenneLppParser {
|
||||
return ((voltage - 3.0) / 1.2) * 100.0;
|
||||
}
|
||||
|
||||
static bool _isDedicatedBatteryChannel(int channel) => channel == 0;
|
||||
|
||||
static bool _isDeferredBatteryChannel(int channel) =>
|
||||
channel == _selfTelemetryChannel;
|
||||
|
||||
static int _readUInt32BE(BufferReader reader) {
|
||||
final bytes = reader.readBytes(4);
|
||||
return (bytes[0] << 24) | (bytes[1] << 16) | (bytes[2] << 8) | bytes[3];
|
||||
}
|
||||
|
||||
static int _readInt32BE(BufferReader reader) {
|
||||
final value = _readUInt32BE(reader);
|
||||
if ((value & 0x80000000) != 0) {
|
||||
return value - 0x100000000;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
static int _readInt8(BufferReader reader) {
|
||||
final value = reader.readByte();
|
||||
if ((value & 0x80) != 0) {
|
||||
return value - 0x100;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
static String _sourceChannelKey(String fieldKey) =>
|
||||
'__source_channel:$fieldKey';
|
||||
|
||||
static String _binaryMetricKeyForType(int type) {
|
||||
switch (type) {
|
||||
case _lppBinaryBool:
|
||||
return 'binary_bool';
|
||||
case _lppBinaryPowerSwitch:
|
||||
return 'binary_power_switch';
|
||||
case _lppBinaryOpen:
|
||||
return 'binary_open';
|
||||
case _lppBinaryBatteryLow:
|
||||
return 'binary_battery_low';
|
||||
case _lppBinaryCharging:
|
||||
return 'binary_charging';
|
||||
case _lppBinaryCarbonMonoxide:
|
||||
return 'binary_carbon_monoxide';
|
||||
case _lppBinaryCold:
|
||||
return 'binary_cold';
|
||||
case _lppBinaryConnectivity:
|
||||
return 'binary_connectivity';
|
||||
case _lppBinaryDoor:
|
||||
return 'binary_door';
|
||||
case _lppBinaryGarageDoor:
|
||||
return 'binary_garage_door';
|
||||
case _lppBinaryGas:
|
||||
return 'binary_gas';
|
||||
case _lppBinaryHeat:
|
||||
return 'binary_heat';
|
||||
case _lppBinaryLight:
|
||||
return 'binary_light';
|
||||
case _lppBinaryLock:
|
||||
return 'binary_lock';
|
||||
case _lppBinaryMoisture:
|
||||
return 'binary_moisture';
|
||||
case _lppBinaryMotion:
|
||||
return 'binary_motion';
|
||||
case _lppBinaryMoving:
|
||||
return 'binary_moving';
|
||||
case _lppBinaryOccupancy:
|
||||
return 'binary_occupancy';
|
||||
case _lppBinaryPlug:
|
||||
return 'binary_plug';
|
||||
case _lppBinaryPresence:
|
||||
return 'binary_presence';
|
||||
case _lppBinaryProblem:
|
||||
return 'binary_problem';
|
||||
case _lppBinaryRunning:
|
||||
return 'binary_running';
|
||||
case _lppBinarySafety:
|
||||
return 'binary_safety';
|
||||
case _lppBinarySmoke:
|
||||
return 'binary_smoke';
|
||||
case _lppBinarySound:
|
||||
return 'binary_sound';
|
||||
case _lppBinaryTamper:
|
||||
return 'binary_tamper';
|
||||
case _lppBinaryVibration:
|
||||
return 'binary_vibration';
|
||||
case _lppBinaryWindow:
|
||||
return 'binary_window';
|
||||
}
|
||||
return 'binary_state';
|
||||
}
|
||||
|
||||
static int? _payloadSizeForType(int type) {
|
||||
if (type >= _lppBinaryBool && type <= _lppBinaryWindow) {
|
||||
return 1;
|
||||
}
|
||||
switch (type) {
|
||||
case MeshCoreConstants.lppDigitalInput:
|
||||
case MeshCoreConstants.lppDigitalOutput:
|
||||
case MeshCoreConstants.lppPresenceSensor:
|
||||
case MeshCoreConstants.lppHumiditySensor:
|
||||
case _lppPercentage:
|
||||
case _lppSwitch:
|
||||
case _lppButtonEvent:
|
||||
case _lppDimmer:
|
||||
case _lppUv:
|
||||
case _lppLightLevel:
|
||||
return 1;
|
||||
case MeshCoreConstants.lppAnalogInput:
|
||||
case MeshCoreConstants.lppAnalogOutput:
|
||||
case MeshCoreConstants.lppIlluminanceSensor:
|
||||
case MeshCoreConstants.lppTemperatureSensor:
|
||||
case MeshCoreConstants.lppBarometer:
|
||||
case MeshCoreConstants.lppVoltageSensor:
|
||||
case _lppCurrent:
|
||||
case _lppAltitude:
|
||||
case _lppConcentration:
|
||||
case _lppPower:
|
||||
case _lppSpeed:
|
||||
case _lppDirection:
|
||||
case _lppGust:
|
||||
case _lppDewPoint:
|
||||
case _lppRain:
|
||||
case _lppPm25:
|
||||
case _lppPm10:
|
||||
case _lppCo2:
|
||||
case _lppTvoc:
|
||||
case _lppRpm:
|
||||
case _lppConductivity:
|
||||
case _lppRotation:
|
||||
return 2;
|
||||
case MeshCoreConstants.lppAccelerometer:
|
||||
case MeshCoreConstants.lppGyrometer:
|
||||
return 6;
|
||||
case MeshCoreConstants.lppGps:
|
||||
return 9;
|
||||
case _lppGenericSensor:
|
||||
case _lppFrequency:
|
||||
case _lppDistance:
|
||||
case _lppEnergy:
|
||||
case _lppUnixTime:
|
||||
case _lppDuration:
|
||||
case _lppAcceleration:
|
||||
case _lppGyroRate:
|
||||
case _lppVolume:
|
||||
case _lppFlowRate:
|
||||
case _lppVolumeStorage:
|
||||
case _lppWater:
|
||||
case _lppGasVolume:
|
||||
case _lppMass:
|
||||
case _lppSignedSpeed:
|
||||
case _lppSignedPower:
|
||||
case _lppSignedCurrent:
|
||||
return 4;
|
||||
case _lppColour:
|
||||
return 3;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
static bool _isZeroPaddedTail(Uint8List data, int remainingBytes) {
|
||||
final start = data.length - remainingBytes;
|
||||
for (int i = start; i < data.length; i++) {
|
||||
|
||||
336
lib/services/contact_route_resolver.dart
Normal file
336
lib/services/contact_route_resolver.dart
Normal file
@@ -0,0 +1,336 @@
|
||||
import 'dart:math' as math;
|
||||
|
||||
import 'package:latlong2/latlong.dart';
|
||||
|
||||
import '../models/contact.dart';
|
||||
|
||||
class ResolvedContactRoutePlan {
|
||||
final List<String> tokens;
|
||||
final List<Contact> selectedContacts;
|
||||
final String summary;
|
||||
|
||||
const ResolvedContactRoutePlan({
|
||||
required this.tokens,
|
||||
required this.selectedContacts,
|
||||
required this.summary,
|
||||
});
|
||||
|
||||
String get canonicalText => tokens.join(',');
|
||||
}
|
||||
|
||||
class ContactRouteResolver {
|
||||
static const Distance _distance = Distance();
|
||||
|
||||
const ContactRouteResolver._();
|
||||
|
||||
static ResolvedContactRoutePlan? resolveAutomaticRoute({
|
||||
required LatLng senderLocation,
|
||||
required Contact recipient,
|
||||
required List<Contact> availableContacts,
|
||||
required int hashSize,
|
||||
}) {
|
||||
final recipientLocation = recipient.displayLocation;
|
||||
if (recipientLocation == null) return null;
|
||||
|
||||
final repeaters = availableContacts
|
||||
.where(
|
||||
(contact) =>
|
||||
contact.isRepeater &&
|
||||
contact.displayLocation != null &&
|
||||
contact.publicKeyHex != recipient.publicKeyHex,
|
||||
)
|
||||
.toList();
|
||||
if (repeaters.isEmpty) return null;
|
||||
|
||||
final recipientLatLng = LatLng(
|
||||
recipientLocation.latitude,
|
||||
recipientLocation.longitude,
|
||||
);
|
||||
|
||||
final routedCandidates =
|
||||
repeaters
|
||||
.where(
|
||||
(contact) =>
|
||||
contact.routeHasPath && contact.routeHashSize == hashSize,
|
||||
)
|
||||
.toList()
|
||||
..sort(
|
||||
(a, b) =>
|
||||
_scoreKnownRouteRepeater(
|
||||
senderLocation: senderLocation,
|
||||
recipientLocation: recipientLatLng,
|
||||
repeater: a,
|
||||
availableContacts: repeaters,
|
||||
hashSize: hashSize,
|
||||
).compareTo(
|
||||
_scoreKnownRouteRepeater(
|
||||
senderLocation: senderLocation,
|
||||
recipientLocation: recipientLatLng,
|
||||
repeater: b,
|
||||
availableContacts: repeaters,
|
||||
hashSize: hashSize,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
if (routedCandidates.isNotEmpty) {
|
||||
final anchor = routedCandidates.first;
|
||||
final tokens = <String>[
|
||||
...anchor.routeCanonicalText
|
||||
.split(',')
|
||||
.where((token) => token.isNotEmpty)
|
||||
.map((token) => token.toUpperCase()),
|
||||
_tokenFor(anchor, hashSize),
|
||||
];
|
||||
final selectedContacts = _matchContactsForTokens(
|
||||
tokens,
|
||||
availableContacts: repeaters,
|
||||
);
|
||||
return ResolvedContactRoutePlan(
|
||||
tokens: _dedupeTokens(tokens),
|
||||
selectedContacts: selectedContacts,
|
||||
summary: 'Resolved via known repeater route',
|
||||
);
|
||||
}
|
||||
|
||||
final corridorRepeaters = List<Contact>.from(repeaters)
|
||||
..sort((a, b) {
|
||||
final progressA = _progressAlongSegment(
|
||||
point: LatLng(
|
||||
a.displayLocation!.latitude,
|
||||
a.displayLocation!.longitude,
|
||||
),
|
||||
start: senderLocation,
|
||||
end: recipientLatLng,
|
||||
);
|
||||
final progressB = _progressAlongSegment(
|
||||
point: LatLng(
|
||||
b.displayLocation!.latitude,
|
||||
b.displayLocation!.longitude,
|
||||
),
|
||||
start: senderLocation,
|
||||
end: recipientLatLng,
|
||||
);
|
||||
final progressCompare = progressA.compareTo(progressB);
|
||||
if (progressCompare != 0) return progressCompare;
|
||||
return _scoreRepeater(
|
||||
senderLocation: senderLocation,
|
||||
recipientLocation: recipientLatLng,
|
||||
repeater: a,
|
||||
).compareTo(
|
||||
_scoreRepeater(
|
||||
senderLocation: senderLocation,
|
||||
recipientLocation: recipientLatLng,
|
||||
repeater: b,
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
final selected = <Contact>[];
|
||||
var currentPoint = senderLocation;
|
||||
var currentDistanceToRecipient = _distance.as(
|
||||
LengthUnit.Meter,
|
||||
senderLocation,
|
||||
recipientLatLng,
|
||||
);
|
||||
final maxCorridorDistance = math.max(
|
||||
1500.0,
|
||||
currentDistanceToRecipient * 0.22,
|
||||
);
|
||||
|
||||
for (final repeater in corridorRepeaters) {
|
||||
if (selected.length >= 4) break;
|
||||
final repeaterPoint = LatLng(
|
||||
repeater.displayLocation!.latitude,
|
||||
repeater.displayLocation!.longitude,
|
||||
);
|
||||
final distanceToSegment = _distanceToSegmentMeters(
|
||||
repeaterPoint,
|
||||
senderLocation,
|
||||
recipientLatLng,
|
||||
);
|
||||
if (distanceToSegment > maxCorridorDistance) {
|
||||
continue;
|
||||
}
|
||||
|
||||
final nextDistanceToRecipient = _distance.as(
|
||||
LengthUnit.Meter,
|
||||
repeaterPoint,
|
||||
recipientLatLng,
|
||||
);
|
||||
if (nextDistanceToRecipient >= currentDistanceToRecipient - 300) {
|
||||
continue;
|
||||
}
|
||||
|
||||
final distanceFromCurrent = _distance.as(
|
||||
LengthUnit.Meter,
|
||||
currentPoint,
|
||||
repeaterPoint,
|
||||
);
|
||||
if (distanceFromCurrent < 100) {
|
||||
continue;
|
||||
}
|
||||
|
||||
selected.add(repeater);
|
||||
currentPoint = repeaterPoint;
|
||||
currentDistanceToRecipient = nextDistanceToRecipient;
|
||||
|
||||
if (currentDistanceToRecipient < 2500) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (selected.isEmpty) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return ResolvedContactRoutePlan(
|
||||
tokens: selected.map((contact) => _tokenFor(contact, hashSize)).toList(),
|
||||
selectedContacts: selected,
|
||||
summary: 'Resolved from repeater locations',
|
||||
);
|
||||
}
|
||||
|
||||
static List<Contact> _matchContactsForTokens(
|
||||
List<String> tokens, {
|
||||
required List<Contact> availableContacts,
|
||||
}) {
|
||||
final matches = <Contact>[];
|
||||
final seen = <String>{};
|
||||
for (final token in tokens) {
|
||||
final match = availableContacts
|
||||
.where(
|
||||
(contact) => contact.publicKeyHex.toUpperCase().startsWith(token),
|
||||
)
|
||||
.firstOrNull;
|
||||
if (match != null && seen.add(match.publicKeyHex)) {
|
||||
matches.add(match);
|
||||
}
|
||||
}
|
||||
return matches;
|
||||
}
|
||||
|
||||
static List<String> _dedupeTokens(List<String> tokens) {
|
||||
final result = <String>[];
|
||||
for (final token in tokens) {
|
||||
if (result.isEmpty || result.last != token) {
|
||||
result.add(token);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
static String _tokenFor(Contact contact, int hashSize) {
|
||||
final hex = contact.publicKeyHex.toUpperCase();
|
||||
final length = hashSize * 2;
|
||||
return hex.length < length ? hex : hex.substring(0, length);
|
||||
}
|
||||
|
||||
static double _scoreRepeater({
|
||||
required LatLng senderLocation,
|
||||
required LatLng recipientLocation,
|
||||
required Contact repeater,
|
||||
}) {
|
||||
final point = LatLng(
|
||||
repeater.displayLocation!.latitude,
|
||||
repeater.displayLocation!.longitude,
|
||||
);
|
||||
final toRecipient = _distance.as(
|
||||
LengthUnit.Meter,
|
||||
point,
|
||||
recipientLocation,
|
||||
);
|
||||
final corridor = _distanceToSegmentMeters(
|
||||
point,
|
||||
senderLocation,
|
||||
recipientLocation,
|
||||
);
|
||||
return (toRecipient * 0.75) + (corridor * 0.25);
|
||||
}
|
||||
|
||||
static double _scoreKnownRouteRepeater({
|
||||
required LatLng senderLocation,
|
||||
required LatLng recipientLocation,
|
||||
required Contact repeater,
|
||||
required List<Contact> availableContacts,
|
||||
required int hashSize,
|
||||
}) {
|
||||
final baseScore = _scoreRepeater(
|
||||
senderLocation: senderLocation,
|
||||
recipientLocation: recipientLocation,
|
||||
repeater: repeater,
|
||||
);
|
||||
final repeaterPoint = LatLng(
|
||||
repeater.displayLocation!.latitude,
|
||||
repeater.displayLocation!.longitude,
|
||||
);
|
||||
|
||||
final chainContacts = repeater.routeCanonicalText
|
||||
.split(',')
|
||||
.where((token) => token.isNotEmpty)
|
||||
.map(
|
||||
(token) => availableContacts
|
||||
.where(
|
||||
(contact) =>
|
||||
contact.displayLocation != null &&
|
||||
contact.publicKeyHex.toUpperCase().startsWith(
|
||||
token.toUpperCase(),
|
||||
),
|
||||
)
|
||||
.firstOrNull,
|
||||
)
|
||||
.whereType<Contact>()
|
||||
.toList();
|
||||
|
||||
final chainEnd = chainContacts.isNotEmpty
|
||||
? LatLng(
|
||||
chainContacts.last.displayLocation!.latitude,
|
||||
chainContacts.last.displayLocation!.longitude,
|
||||
)
|
||||
: senderLocation;
|
||||
final chainGap = _distance.as(LengthUnit.Meter, chainEnd, repeaterPoint);
|
||||
final senderGap = _distance.as(
|
||||
LengthUnit.Meter,
|
||||
senderLocation,
|
||||
repeaterPoint,
|
||||
);
|
||||
|
||||
return (chainGap * 0.55) + (baseScore * 0.35) + (senderGap * 0.10);
|
||||
}
|
||||
|
||||
static double _progressAlongSegment({
|
||||
required LatLng point,
|
||||
required LatLng start,
|
||||
required LatLng end,
|
||||
}) {
|
||||
final dx = end.longitude - start.longitude;
|
||||
final dy = end.latitude - start.latitude;
|
||||
final lengthSquared = (dx * dx) + (dy * dy);
|
||||
if (lengthSquared == 0) return 0;
|
||||
return (((point.longitude - start.longitude) * dx) +
|
||||
((point.latitude - start.latitude) * dy)) /
|
||||
lengthSquared;
|
||||
}
|
||||
|
||||
static double _distanceToSegmentMeters(LatLng p, LatLng a, LatLng b) {
|
||||
final ax = a.longitude;
|
||||
final ay = a.latitude;
|
||||
final bx = b.longitude;
|
||||
final by = b.latitude;
|
||||
final px = p.longitude;
|
||||
final py = p.latitude;
|
||||
|
||||
final abx = bx - ax;
|
||||
final aby = by - ay;
|
||||
final apx = px - ax;
|
||||
final apy = py - ay;
|
||||
final ab2 = abx * abx + aby * aby;
|
||||
if (ab2 == 0) {
|
||||
return _distance.as(LengthUnit.Meter, a, p);
|
||||
}
|
||||
var t = (apx * abx + apy * aby) / ab2;
|
||||
t = t.clamp(0.0, 1.0);
|
||||
final closest = LatLng(ay + aby * t, ax + abx * t);
|
||||
return _distance.as(LengthUnit.Meter, closest, p);
|
||||
}
|
||||
}
|
||||
@@ -2,16 +2,25 @@ import 'dart:convert';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import '../models/contact.dart';
|
||||
import '../models/contact_group.dart';
|
||||
import 'profiles_feature_service.dart';
|
||||
import '../utils/key_comparison.dart';
|
||||
import 'package:latlong2/latlong.dart';
|
||||
|
||||
/// Service for persisting contacts to local storage
|
||||
class ContactStorageService {
|
||||
static const String _contactsKey = 'stored_contacts';
|
||||
static const String _contactGroupsKey = 'stored_contact_groups';
|
||||
static const String _pendingAdvertsKey = 'stored_pending_adverts';
|
||||
static const int _maxStoredContacts = 500; // Store up to 500 contacts
|
||||
static const int _maxStoredPendingAdverts = 500;
|
||||
|
||||
String _key(String baseKey, {String? namespace}) {
|
||||
return ProfileStorageScope.scopedKey(baseKey, namespace: namespace);
|
||||
}
|
||||
|
||||
/// Save contacts to persistent storage
|
||||
Future<void> saveContacts(List<Contact> contacts) async {
|
||||
Future<void> saveContacts(List<Contact> contacts, {String? namespace}) async {
|
||||
try {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
|
||||
@@ -26,7 +35,10 @@ class ContactStorageService {
|
||||
: jsonList;
|
||||
|
||||
final jsonString = jsonEncode(limitedList);
|
||||
await prefs.setString(_contactsKey, jsonString);
|
||||
await prefs.setString(
|
||||
_key(_contactsKey, namespace: namespace),
|
||||
jsonString,
|
||||
);
|
||||
|
||||
debugPrint(
|
||||
'✅ [ContactStorage] Saved ${limitedList.length} contacts to storage',
|
||||
@@ -38,10 +50,15 @@ class ContactStorageService {
|
||||
|
||||
/// Load contacts from persistent storage
|
||||
/// [excludePublicKey] - optional public key to exclude (e.g., device's own key)
|
||||
Future<List<Contact>> loadContacts({Uint8List? excludePublicKey}) async {
|
||||
Future<List<Contact>> loadContacts({
|
||||
Uint8List? excludePublicKey,
|
||||
String? namespace,
|
||||
}) async {
|
||||
try {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
final jsonString = prefs.getString(_contactsKey);
|
||||
final jsonString = prefs.getString(
|
||||
_key(_contactsKey, namespace: namespace),
|
||||
);
|
||||
|
||||
if (jsonString == null || jsonString.isEmpty) {
|
||||
debugPrint('ℹ️ [ContactStorage] No stored contacts found');
|
||||
@@ -80,21 +97,158 @@ class ContactStorageService {
|
||||
}
|
||||
|
||||
/// Clear all stored contacts
|
||||
Future<void> clearContacts() async {
|
||||
Future<void> clearContacts({String? namespace}) async {
|
||||
try {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
await prefs.remove(_contactsKey);
|
||||
await prefs.remove(_key(_contactsKey, namespace: namespace));
|
||||
debugPrint('✅ [ContactStorage] Cleared all stored contacts');
|
||||
} catch (e) {
|
||||
debugPrint('❌ [ContactStorage] Error clearing contacts: $e');
|
||||
}
|
||||
}
|
||||
|
||||
/// Get storage statistics
|
||||
Future<Map<String, dynamic>> getStorageStats() async {
|
||||
Future<void> saveContactGroups(
|
||||
List<SavedContactGroup> groups, {
|
||||
String? namespace,
|
||||
}) async {
|
||||
try {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
final jsonString = prefs.getString(_contactsKey);
|
||||
final jsonString = jsonEncode(
|
||||
groups.map((group) => _contactGroupToJson(group)).toList(),
|
||||
);
|
||||
await prefs.setString(
|
||||
_key(_contactGroupsKey, namespace: namespace),
|
||||
jsonString,
|
||||
);
|
||||
debugPrint(
|
||||
'✅ [ContactStorage] Saved ${groups.length} contact groups to storage',
|
||||
);
|
||||
} catch (e) {
|
||||
debugPrint('❌ [ContactStorage] Error saving contact groups: $e');
|
||||
}
|
||||
}
|
||||
|
||||
Future<List<SavedContactGroup>> loadContactGroups({String? namespace}) async {
|
||||
try {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
final jsonString = prefs.getString(
|
||||
_key(_contactGroupsKey, namespace: namespace),
|
||||
);
|
||||
if (jsonString == null || jsonString.isEmpty) {
|
||||
return [];
|
||||
}
|
||||
|
||||
final jsonList = jsonDecode(jsonString) as List<dynamic>;
|
||||
return jsonList
|
||||
.map((json) => _contactGroupFromJson(json as Map<String, dynamic>))
|
||||
.whereType<SavedContactGroup>()
|
||||
.toList();
|
||||
} catch (e) {
|
||||
debugPrint('❌ [ContactStorage] Error loading contact groups: $e');
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> clearContactGroups({String? namespace}) async {
|
||||
try {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
await prefs.remove(_key(_contactGroupsKey, namespace: namespace));
|
||||
debugPrint('✅ [ContactStorage] Cleared all contact groups');
|
||||
} catch (e) {
|
||||
debugPrint('❌ [ContactStorage] Error clearing contact groups: $e');
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> savePendingAdverts(
|
||||
List<Map<String, dynamic>> adverts, {
|
||||
String? namespace,
|
||||
}) async {
|
||||
try {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
final limitedList = adverts.length > _maxStoredPendingAdverts
|
||||
? adverts.sublist(adverts.length - _maxStoredPendingAdverts)
|
||||
: adverts;
|
||||
await prefs.setString(
|
||||
_key(_pendingAdvertsKey, namespace: namespace),
|
||||
jsonEncode(limitedList),
|
||||
);
|
||||
debugPrint(
|
||||
'✅ [ContactStorage] Saved ${limitedList.length} pending adverts to storage',
|
||||
);
|
||||
} catch (e) {
|
||||
debugPrint('❌ [ContactStorage] Error saving pending adverts: $e');
|
||||
}
|
||||
}
|
||||
|
||||
Future<List<Map<String, dynamic>>> loadPendingAdverts({
|
||||
String? namespace,
|
||||
}) async {
|
||||
try {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
final jsonString = prefs.getString(
|
||||
_key(_pendingAdvertsKey, namespace: namespace),
|
||||
);
|
||||
if (jsonString == null || jsonString.isEmpty) {
|
||||
return const [];
|
||||
}
|
||||
|
||||
final jsonList = jsonDecode(jsonString) as List<dynamic>;
|
||||
return jsonList.whereType<Map<String, dynamic>>().toList();
|
||||
} catch (e) {
|
||||
debugPrint('❌ [ContactStorage] Error loading pending adverts: $e');
|
||||
return const [];
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> clearPendingAdverts({String? namespace}) async {
|
||||
try {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
await prefs.remove(_key(_pendingAdvertsKey, namespace: namespace));
|
||||
debugPrint('✅ [ContactStorage] Cleared all stored pending adverts');
|
||||
} catch (e) {
|
||||
debugPrint('❌ [ContactStorage] Error clearing pending adverts: $e');
|
||||
}
|
||||
}
|
||||
|
||||
// ── Favorites ───────────────────────────────────────────────────────────────
|
||||
|
||||
static const String _favoritesKey = 'contact_favorites';
|
||||
|
||||
Future<void> saveFavorites(
|
||||
Set<String> publicKeyHexes, {
|
||||
String? namespace,
|
||||
}) async {
|
||||
try {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
await prefs.setStringList(
|
||||
_key(_favoritesKey, namespace: namespace),
|
||||
publicKeyHexes.toList(),
|
||||
);
|
||||
} catch (e) {
|
||||
debugPrint('❌ [ContactStorage] Error saving favorites: $e');
|
||||
}
|
||||
}
|
||||
|
||||
Future<Set<String>> loadFavorites({String? namespace}) async {
|
||||
try {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
final list = prefs.getStringList(
|
||||
_key(_favoritesKey, namespace: namespace),
|
||||
);
|
||||
return list?.toSet() ?? {};
|
||||
} catch (e) {
|
||||
debugPrint('❌ [ContactStorage] Error loading favorites: $e');
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
/// Get storage statistics
|
||||
Future<Map<String, dynamic>> getStorageStats({String? namespace}) async {
|
||||
try {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
final jsonString = prefs.getString(
|
||||
_key(_contactsKey, namespace: namespace),
|
||||
);
|
||||
|
||||
if (jsonString == null || jsonString.isEmpty) {
|
||||
return {'contactCount': 0, 'storageSizeBytes': 0, 'storageSizeKB': 0};
|
||||
@@ -123,6 +277,7 @@ class ContactStorageService {
|
||||
'outPathLen': contact.outPathLen,
|
||||
'outPath': base64Encode(contact.outPath),
|
||||
'advName': contact.advName,
|
||||
'nameOverride': contact.nameOverride,
|
||||
'lastAdvert': contact.lastAdvert,
|
||||
'advLat': contact.advLat,
|
||||
'advLon': contact.advLon,
|
||||
@@ -130,6 +285,15 @@ class ContactStorageService {
|
||||
'telemetry': contact.telemetry != null
|
||||
? _telemetryToJson(contact.telemetry!)
|
||||
: null,
|
||||
'advertHistory': contact.advertHistory
|
||||
.map(
|
||||
(point) => {
|
||||
'lat': point.location.latitude,
|
||||
'lon': point.location.longitude,
|
||||
'tsMillis': point.timestamp.millisecondsSinceEpoch,
|
||||
},
|
||||
)
|
||||
.toList(),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -145,6 +309,7 @@ class ContactStorageService {
|
||||
outPathLen: json['outPathLen'] as int,
|
||||
outPath: Uint8List.fromList(base64Decode(json['outPath'] as String)),
|
||||
advName: json['advName'] as String,
|
||||
nameOverride: json['nameOverride'] as String?,
|
||||
lastAdvert: json['lastAdvert'] as int,
|
||||
advLat: json['advLat'] as int,
|
||||
advLon: json['advLon'] as int,
|
||||
@@ -152,6 +317,7 @@ class ContactStorageService {
|
||||
telemetry: json['telemetry'] != null
|
||||
? _telemetryFromJson(json['telemetry'] as Map<String, dynamic>)
|
||||
: null,
|
||||
advertHistory: _advertHistoryFromJson(json['advertHistory']),
|
||||
);
|
||||
} catch (e) {
|
||||
debugPrint('❌ [ContactStorage] Error parsing contact from JSON: $e');
|
||||
@@ -203,4 +369,56 @@ class ContactStorageService {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
List<AdvertLocation> _advertHistoryFromJson(dynamic json) {
|
||||
if (json is! List) return [];
|
||||
final result = <AdvertLocation>[];
|
||||
for (final item in json) {
|
||||
if (item is! Map<String, dynamic>) continue;
|
||||
final lat = item['lat'];
|
||||
final lon = item['lon'];
|
||||
final tsMillis = item['tsMillis'];
|
||||
if (lat is! num || lon is! num || tsMillis is! int) continue;
|
||||
result.add(
|
||||
AdvertLocation(
|
||||
location: LatLng(lat.toDouble(), lon.toDouble()),
|
||||
timestamp: DateTime.fromMillisecondsSinceEpoch(tsMillis),
|
||||
),
|
||||
);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
Map<String, dynamic> _contactGroupToJson(SavedContactGroup group) {
|
||||
return {
|
||||
'id': group.id,
|
||||
'sectionKey': group.sectionKey,
|
||||
'label': group.label,
|
||||
'query': group.query,
|
||||
'createdAtMillis': group.createdAt.millisecondsSinceEpoch,
|
||||
'matchPrefixes': group.matchPrefixes,
|
||||
'isAutoGroup': group.isAutoGroup,
|
||||
};
|
||||
}
|
||||
|
||||
SavedContactGroup? _contactGroupFromJson(Map<String, dynamic> json) {
|
||||
try {
|
||||
return SavedContactGroup(
|
||||
id: json['id'] as String,
|
||||
sectionKey: json['sectionKey'] as String,
|
||||
label: json['label'] as String,
|
||||
query: json['query'] as String,
|
||||
createdAt: DateTime.fromMillisecondsSinceEpoch(
|
||||
json['createdAtMillis'] as int,
|
||||
),
|
||||
matchPrefixes: (json['matchPrefixes'] as List<dynamic>?)
|
||||
?.map((value) => value as String)
|
||||
.toList(),
|
||||
isAutoGroup: json['isAutoGroup'] as bool? ?? false,
|
||||
);
|
||||
} catch (e) {
|
||||
debugPrint('❌ [ContactStorage] Error parsing contact group: $e');
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
140
lib/services/device_config_applicator.dart
Normal file
140
lib/services/device_config_applicator.dart
Normal file
@@ -0,0 +1,140 @@
|
||||
import 'dart:typed_data';
|
||||
|
||||
import '../models/channel.dart';
|
||||
import '../models/config_profile.dart';
|
||||
import '../providers/channels_provider.dart';
|
||||
import '../providers/connection_provider.dart';
|
||||
|
||||
class DeviceConfigApplicator {
|
||||
ConfigProfileSections capture({
|
||||
required ConnectionProvider connectionProvider,
|
||||
required ChannelsProvider channelsProvider,
|
||||
}) {
|
||||
final deviceInfo = connectionProvider.deviceInfo;
|
||||
return ConfigProfileSections(
|
||||
deviceConfig: DeviceConfigProfileSection(
|
||||
frequencyKhz: deviceInfo.radioFreq,
|
||||
bandwidth: deviceInfo.radioBw,
|
||||
spreadingFactor: deviceInfo.radioSf,
|
||||
codingRate: deviceInfo.radioCr,
|
||||
repeatEnabled: deviceInfo.clientRepeat,
|
||||
txPower: deviceInfo.txPower,
|
||||
telemetryModes: deviceInfo.telemetryModes,
|
||||
advertLocationPolicy: deviceInfo.advertLocPolicy,
|
||||
multiAcks: deviceInfo.multiAcks,
|
||||
manualAddContacts: deviceInfo.manualAddContacts,
|
||||
autoAddUsers: deviceInfo.autoAddUsers,
|
||||
autoAddRepeaters: deviceInfo.autoAddRepeaters,
|
||||
autoAddRoomServers: deviceInfo.autoAddRoomServers,
|
||||
autoAddSensors: deviceInfo.autoAddSensors,
|
||||
autoAddOverwriteOldest: deviceInfo.autoAddOverwriteOldest,
|
||||
publicLatitude: _decodeAdvertCoordinate(deviceInfo.advLat),
|
||||
publicLongitude: _decodeAdvertCoordinate(deviceInfo.advLon),
|
||||
),
|
||||
channels: channelsProvider.channels,
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> apply(
|
||||
ConfigProfile profile, {
|
||||
required ConnectionProvider connectionProvider,
|
||||
required ChannelsProvider channelsProvider,
|
||||
}) async {
|
||||
if (!connectionProvider.deviceInfo.isConnected) {
|
||||
return;
|
||||
}
|
||||
|
||||
final deviceConfig = profile.sections.deviceConfig;
|
||||
if (deviceConfig != null && !deviceConfig.isEmpty) {
|
||||
if (deviceConfig.txPower != null) {
|
||||
await connectionProvider.setTxPower(deviceConfig.txPower!);
|
||||
}
|
||||
if (deviceConfig.telemetryModes != null ||
|
||||
deviceConfig.advertLocationPolicy != null ||
|
||||
deviceConfig.manualAddContacts != null ||
|
||||
deviceConfig.multiAcks != null) {
|
||||
await connectionProvider.setOtherParams(
|
||||
manualAddContacts: (deviceConfig.manualAddContacts ?? false) ? 1 : 0,
|
||||
telemetryModes: deviceConfig.telemetryModes ?? 0,
|
||||
advertLocationPolicy: deviceConfig.advertLocationPolicy ?? 0,
|
||||
multiAcks: deviceConfig.multiAcks ?? 0,
|
||||
);
|
||||
}
|
||||
if (deviceConfig.autoAddUsers != null &&
|
||||
deviceConfig.autoAddRepeaters != null &&
|
||||
deviceConfig.autoAddRoomServers != null &&
|
||||
deviceConfig.autoAddSensors != null &&
|
||||
deviceConfig.autoAddOverwriteOldest != null) {
|
||||
await connectionProvider.setAutoaddConfig(
|
||||
autoAddUsers: deviceConfig.autoAddUsers!,
|
||||
autoAddRepeaters: deviceConfig.autoAddRepeaters!,
|
||||
autoAddRoomServers: deviceConfig.autoAddRoomServers!,
|
||||
autoAddSensors: deviceConfig.autoAddSensors!,
|
||||
overwriteOldest: deviceConfig.autoAddOverwriteOldest!,
|
||||
);
|
||||
}
|
||||
if (deviceConfig.publicLatitude != null &&
|
||||
deviceConfig.publicLongitude != null) {
|
||||
await connectionProvider.setAdvertLatLon(
|
||||
latitude: deviceConfig.publicLatitude!,
|
||||
longitude: deviceConfig.publicLongitude!,
|
||||
);
|
||||
}
|
||||
if (deviceConfig.frequencyKhz != null &&
|
||||
deviceConfig.bandwidth != null &&
|
||||
deviceConfig.spreadingFactor != null &&
|
||||
deviceConfig.codingRate != null) {
|
||||
await connectionProvider.setRadioParams(
|
||||
frequency: deviceConfig.frequencyKhz!,
|
||||
bandwidth: deviceConfig.bandwidth!,
|
||||
spreadingFactor: deviceConfig.spreadingFactor!,
|
||||
codingRate: deviceConfig.codingRate!,
|
||||
repeat: deviceConfig.repeatEnabled,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
await _applyChannels(
|
||||
channels: profile.sections.channels,
|
||||
connectionProvider: connectionProvider,
|
||||
channelsProvider: channelsProvider,
|
||||
);
|
||||
await connectionProvider.refreshDeviceInfo();
|
||||
}
|
||||
|
||||
Future<void> _applyChannels({
|
||||
required List<Channel> channels,
|
||||
required ConnectionProvider connectionProvider,
|
||||
required ChannelsProvider channelsProvider,
|
||||
}) async {
|
||||
if (channels.isEmpty) {
|
||||
return;
|
||||
}
|
||||
|
||||
final desired = {for (final channel in channels) channel.index: channel};
|
||||
|
||||
for (final channel in channels) {
|
||||
await connectionProvider.setChannelSlot(
|
||||
channelIdx: channel.index,
|
||||
channelName: channel.name,
|
||||
secret: Uint8List.fromList(channel.secret),
|
||||
);
|
||||
channelsProvider.addOrUpdateChannelObject(channel);
|
||||
}
|
||||
|
||||
for (final existing in channelsProvider.channels) {
|
||||
if (existing.index == 0 || desired.containsKey(existing.index)) {
|
||||
continue;
|
||||
}
|
||||
await connectionProvider.deleteChannel(existing.index);
|
||||
channelsProvider.removeChannel(existing.index);
|
||||
}
|
||||
}
|
||||
|
||||
double? _decodeAdvertCoordinate(int? value) {
|
||||
if (value == null || value == 0) {
|
||||
return null;
|
||||
}
|
||||
return value / 1e6;
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import 'profiles_feature_service.dart';
|
||||
|
||||
/// Stores user-selected image compression settings.
|
||||
class ImagePreferences {
|
||||
@@ -17,44 +18,53 @@ class ImagePreferences {
|
||||
|
||||
static Future<int> getMaxSize() async {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
final value = prefs.getInt(_maxSizeKey) ?? defaultMaxSize;
|
||||
final value =
|
||||
prefs.getInt(ProfileStorageScope.scopedKey(_maxSizeKey)) ??
|
||||
defaultMaxSize;
|
||||
return supportedSizes.contains(value) ? value : defaultMaxSize;
|
||||
}
|
||||
|
||||
static Future<void> setMaxSize(int size) async {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
await prefs.setInt(_maxSizeKey, size);
|
||||
await prefs.setInt(ProfileStorageScope.scopedKey(_maxSizeKey), size);
|
||||
}
|
||||
|
||||
static Future<int> getCompression() async {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
final value = prefs.getInt(_qualityKey) ?? defaultQuality;
|
||||
final value =
|
||||
prefs.getInt(ProfileStorageScope.scopedKey(_qualityKey)) ??
|
||||
defaultQuality;
|
||||
return value.clamp(10, 90);
|
||||
}
|
||||
|
||||
static Future<void> setCompression(int compression) async {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
await prefs.setInt(_qualityKey, compression.clamp(10, 90));
|
||||
await prefs.setInt(
|
||||
ProfileStorageScope.scopedKey(_qualityKey),
|
||||
compression.clamp(10, 90),
|
||||
);
|
||||
}
|
||||
|
||||
static Future<bool> getGrayscale() async {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
return prefs.getBool(_grayscaleKey) ?? defaultGrayscale;
|
||||
return prefs.getBool(ProfileStorageScope.scopedKey(_grayscaleKey)) ??
|
||||
defaultGrayscale;
|
||||
}
|
||||
|
||||
static Future<void> setGrayscale(bool value) async {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
await prefs.setBool(_grayscaleKey, value);
|
||||
await prefs.setBool(ProfileStorageScope.scopedKey(_grayscaleKey), value);
|
||||
}
|
||||
|
||||
static Future<bool> getUltraMode() async {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
return prefs.getBool(_ultraModeKey) ?? defaultUltraMode;
|
||||
return prefs.getBool(ProfileStorageScope.scopedKey(_ultraModeKey)) ??
|
||||
defaultUltraMode;
|
||||
}
|
||||
|
||||
static Future<void> setUltraMode(bool value) async {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
await prefs.setBool(_ultraModeKey, value);
|
||||
await prefs.setBool(ProfileStorageScope.scopedKey(_ultraModeKey), value);
|
||||
}
|
||||
|
||||
static int effectiveMaxSize(
|
||||
|
||||
262
lib/services/live_traffic_summary.dart
Normal file
262
lib/services/live_traffic_summary.dart
Normal file
@@ -0,0 +1,262 @@
|
||||
import '../models/ble_packet_log.dart';
|
||||
import '../utils/log_rx_route_decoder.dart';
|
||||
|
||||
enum LiveTrafficBusyness { quiet, active, busy }
|
||||
|
||||
class LiveTrafficEntry {
|
||||
final BlePacketLog log;
|
||||
final DecodedLogRxRoute? route;
|
||||
|
||||
const LiveTrafficEntry({required this.log, required this.route});
|
||||
|
||||
bool get isMultiHop => (route?.hopCount ?? 0) > 1;
|
||||
|
||||
int? get hopCount => route?.hopCount;
|
||||
|
||||
String get payloadLabel {
|
||||
final decodedRoute = route;
|
||||
if (decodedRoute == null) {
|
||||
return log.responseCode != null ? log.opcodeName : 'Unknown';
|
||||
}
|
||||
return payloadTypeLabel(decodedRoute.payloadType);
|
||||
}
|
||||
|
||||
String? get payloadMeaning {
|
||||
final decodedRoute = route;
|
||||
if (decodedRoute == null) return null;
|
||||
return payloadTypeMeaning(decodedRoute.payloadType);
|
||||
}
|
||||
|
||||
String get routePreview {
|
||||
final decodedRoute = route;
|
||||
if (decodedRoute == null || decodedRoute.hopHashes.isEmpty) {
|
||||
return 'Direct packet';
|
||||
}
|
||||
return decodedRoute.hopHashes
|
||||
.map((hashHex) => '0x${hashHex.toUpperCase()}')
|
||||
.join(' -> ');
|
||||
}
|
||||
|
||||
static String payloadTypeLabel(int payloadType) {
|
||||
switch (payloadType) {
|
||||
case 0x00:
|
||||
return 'Request';
|
||||
case 0x01:
|
||||
return 'Response';
|
||||
case 0x02:
|
||||
return 'Text message';
|
||||
case 0x03:
|
||||
return 'Ack';
|
||||
case 0x04:
|
||||
return 'Advertisement';
|
||||
case 0x05:
|
||||
return 'Group text';
|
||||
case 0x06:
|
||||
return 'Group datagram';
|
||||
case 0x07:
|
||||
return 'Anonymous request';
|
||||
case 0x08:
|
||||
return 'Returned path';
|
||||
case 0x09:
|
||||
return 'Trace path';
|
||||
case 0x0A:
|
||||
return 'Multipart packet';
|
||||
case 0x0B:
|
||||
return 'Control packet';
|
||||
default:
|
||||
return '0x${payloadType.toRadixString(16).padLeft(2, '0')}';
|
||||
}
|
||||
}
|
||||
|
||||
static String payloadTypeMeaning(int payloadType) {
|
||||
switch (payloadType) {
|
||||
case 0x00:
|
||||
return 'Request (destination/source hashes + MAC)';
|
||||
case 0x01:
|
||||
return 'Response to Request or Anonymous request';
|
||||
case 0x02:
|
||||
return 'Plain text message';
|
||||
case 0x03:
|
||||
return 'Simple acknowledgement';
|
||||
case 0x04:
|
||||
return 'Node advertisement';
|
||||
case 0x05:
|
||||
return 'Unverified group text message';
|
||||
case 0x06:
|
||||
return 'Unverified group datagram';
|
||||
case 0x07:
|
||||
return 'Generic anonymous request';
|
||||
case 0x08:
|
||||
return 'Returned path payload';
|
||||
case 0x09:
|
||||
return 'Trace path collecting hop SNR';
|
||||
case 0x0A:
|
||||
return 'One packet from a multipart set';
|
||||
case 0x0B:
|
||||
return 'Control or discovery packet';
|
||||
default:
|
||||
return 'protocol payload';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class LiveTrafficSnapshot {
|
||||
final DateTime windowStart;
|
||||
final Duration windowDuration;
|
||||
final int packetsPerMinute;
|
||||
final int rxCount;
|
||||
final int txCount;
|
||||
final int totalCount;
|
||||
final double? avgSnrDb;
|
||||
final double? latestSnrDb;
|
||||
final double? avgRssiDbm;
|
||||
final int? latestRssiDbm;
|
||||
final int multiHopCount;
|
||||
final double? avgHopCount;
|
||||
final List<LiveTrafficEntry> visibleEntries;
|
||||
final LiveTrafficBusyness busyness;
|
||||
|
||||
const LiveTrafficSnapshot({
|
||||
required this.windowStart,
|
||||
required this.windowDuration,
|
||||
required this.packetsPerMinute,
|
||||
required this.rxCount,
|
||||
required this.txCount,
|
||||
required this.totalCount,
|
||||
required this.avgSnrDb,
|
||||
required this.latestSnrDb,
|
||||
required this.avgRssiDbm,
|
||||
required this.latestRssiDbm,
|
||||
required this.multiHopCount,
|
||||
required this.avgHopCount,
|
||||
required this.visibleEntries,
|
||||
required this.busyness,
|
||||
});
|
||||
}
|
||||
|
||||
class LiveTrafficSummary {
|
||||
static const Duration rollingWindow = Duration(seconds: 60);
|
||||
static const int maxVisibleEntries = 120;
|
||||
static const int logRxDataResponseCode = 0x88;
|
||||
|
||||
const LiveTrafficSummary._();
|
||||
|
||||
static bool isRxDataLog(BlePacketLog log) {
|
||||
return log.direction == PacketDirection.rx &&
|
||||
log.responseCode == logRxDataResponseCode;
|
||||
}
|
||||
|
||||
static int countRxDataLogs(Iterable<BlePacketLog> logs, {DateTime? since}) {
|
||||
return logs.where((log) {
|
||||
return isRxDataLog(log) &&
|
||||
(since == null || !log.timestamp.isBefore(since));
|
||||
}).length;
|
||||
}
|
||||
|
||||
static LiveTrafficSnapshot fromLogs(
|
||||
Iterable<BlePacketLog> logs, {
|
||||
required DateTime now,
|
||||
DateTime? clearedAt,
|
||||
int? preferredHashSize,
|
||||
Duration window = rollingWindow,
|
||||
String? packetTypeFilter,
|
||||
}) {
|
||||
final windowStart = now.subtract(window);
|
||||
final effectiveStart = clearedAt != null && clearedAt.isAfter(windowStart)
|
||||
? clearedAt
|
||||
: windowStart;
|
||||
|
||||
final recentLogs =
|
||||
logs
|
||||
.where(
|
||||
(log) =>
|
||||
isRxDataLog(log) && !log.timestamp.isBefore(effectiveStart),
|
||||
)
|
||||
.toList()
|
||||
..sort((a, b) => a.timestamp.compareTo(b.timestamp));
|
||||
|
||||
final entries = <LiveTrafficEntry>[];
|
||||
for (final log in recentLogs) {
|
||||
final route = LogRxRouteDecoder.decode(
|
||||
log.rawData,
|
||||
preferredHashSize: preferredHashSize,
|
||||
);
|
||||
entries.add(LiveTrafficEntry(log: log, route: route));
|
||||
}
|
||||
|
||||
final filteredEntries = packetTypeFilter == null
|
||||
? entries
|
||||
: entries
|
||||
.where((entry) => entry.payloadLabel == packetTypeFilter)
|
||||
.toList();
|
||||
|
||||
var rxCount = 0;
|
||||
var snrCount = 0;
|
||||
var snrSum = 0.0;
|
||||
var rssiCount = 0;
|
||||
var rssiSum = 0.0;
|
||||
double? latestSnrDb;
|
||||
int? latestRssiDbm;
|
||||
var multiHopCount = 0;
|
||||
var hopCountTotal = 0;
|
||||
var hopCountSamples = 0;
|
||||
|
||||
for (final entry in filteredEntries) {
|
||||
rxCount += 1;
|
||||
|
||||
final rxInfo = entry.log.logRxDataInfo;
|
||||
if (rxInfo?.snrDb != null) {
|
||||
snrCount += 1;
|
||||
snrSum += rxInfo!.snrDb!;
|
||||
latestSnrDb = rxInfo.snrDb!;
|
||||
}
|
||||
if (rxInfo?.rssiDbm != null) {
|
||||
rssiCount += 1;
|
||||
rssiSum += rxInfo!.rssiDbm!.toDouble();
|
||||
latestRssiDbm = rxInfo.rssiDbm!;
|
||||
}
|
||||
|
||||
final route = entry.route;
|
||||
if (route != null && route.hopCount > 0) {
|
||||
hopCountSamples += 1;
|
||||
hopCountTotal += route.hopCount;
|
||||
if (route.hopCount > 1) {
|
||||
multiHopCount += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
final visibleEntries = filteredEntries.reversed
|
||||
.take(maxVisibleEntries)
|
||||
.toList();
|
||||
const txCount = 0;
|
||||
final totalCount = rxCount;
|
||||
final packetsPerMinute =
|
||||
((totalCount * Duration.secondsPerMinute) / window.inSeconds).round();
|
||||
|
||||
return LiveTrafficSnapshot(
|
||||
windowStart: effectiveStart,
|
||||
windowDuration: window,
|
||||
packetsPerMinute: packetsPerMinute,
|
||||
rxCount: rxCount,
|
||||
txCount: txCount,
|
||||
totalCount: totalCount,
|
||||
avgSnrDb: snrCount == 0 ? null : snrSum / snrCount,
|
||||
latestSnrDb: latestSnrDb,
|
||||
avgRssiDbm: rssiCount == 0 ? null : rssiSum / rssiCount,
|
||||
latestRssiDbm: latestRssiDbm,
|
||||
multiHopCount: multiHopCount,
|
||||
avgHopCount: hopCountSamples == 0
|
||||
? null
|
||||
: hopCountTotal / hopCountSamples,
|
||||
visibleEntries: visibleEntries,
|
||||
busyness: _busynessForPacketsPerMinute(packetsPerMinute),
|
||||
);
|
||||
}
|
||||
|
||||
static LiveTrafficBusyness _busynessForPacketsPerMinute(int ppm) {
|
||||
if (ppm <= 5) return LiveTrafficBusyness.quiet;
|
||||
if (ppm <= 20) return LiveTrafficBusyness.active;
|
||||
return LiveTrafficBusyness.busy;
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,10 @@
|
||||
import 'dart:async';
|
||||
import 'dart:io';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:geolocator/geolocator.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import 'package:meshcore_client/meshcore_client.dart';
|
||||
import 'profiles_feature_service.dart';
|
||||
|
||||
/// Centralized location tracking service for MeshCore SAR
|
||||
///
|
||||
@@ -48,6 +50,10 @@ class LocationTrackingService {
|
||||
static const String _prefKeyFastActiveCadence =
|
||||
'fast_location_active_cadence_seconds';
|
||||
|
||||
String _scopedKey(String baseKey) {
|
||||
return ProfileStorageScope.scopedKey(baseKey);
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Configuration Properties
|
||||
// ============================================================================
|
||||
@@ -148,6 +154,10 @@ class LocationTrackingService {
|
||||
/// Check if location permissions are granted
|
||||
Future<bool> checkPermissions() async {
|
||||
final permission = await Geolocator.checkPermission();
|
||||
if (Platform.isIOS) {
|
||||
return permission == LocationPermission.always;
|
||||
}
|
||||
|
||||
return permission == LocationPermission.always ||
|
||||
permission == LocationPermission.whileInUse;
|
||||
}
|
||||
@@ -181,6 +191,16 @@ class LocationTrackingService {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (Platform.isIOS && permission != LocationPermission.always) {
|
||||
permission = await Geolocator.requestPermission();
|
||||
if (permission != LocationPermission.always) {
|
||||
onError?.call(
|
||||
'Background tracking on iOS requires "Always" location access. Enable it in Settings.',
|
||||
);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
debugPrint('✅ [LocationTracking] Location permissions granted');
|
||||
return true;
|
||||
}
|
||||
@@ -209,9 +229,9 @@ class LocationTrackingService {
|
||||
}
|
||||
|
||||
final position = await Geolocator.getCurrentPosition(
|
||||
locationSettings: LocationSettings(
|
||||
accuracy: LocationAccuracy.best,
|
||||
locationSettings: _buildLocationSettings(
|
||||
timeLimit: timeLimit,
|
||||
distanceFilter: 0,
|
||||
),
|
||||
);
|
||||
|
||||
@@ -255,13 +275,35 @@ class LocationTrackingService {
|
||||
/// [distanceFilter] - Minimum distance in meters between position updates
|
||||
Stream<Position> getPositionStream({double distanceFilter = 10.0}) {
|
||||
return Geolocator.getPositionStream(
|
||||
locationSettings: LocationSettings(
|
||||
accuracy: LocationAccuracy.best,
|
||||
locationSettings: _buildLocationSettings(
|
||||
distanceFilter: distanceFilter.toInt(),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
LocationSettings _buildLocationSettings({
|
||||
int distanceFilter = 10,
|
||||
Duration? timeLimit,
|
||||
}) {
|
||||
if (Platform.isIOS) {
|
||||
return AppleSettings(
|
||||
accuracy: LocationAccuracy.best,
|
||||
activityType: ActivityType.fitness,
|
||||
allowBackgroundLocationUpdates: true,
|
||||
distanceFilter: distanceFilter,
|
||||
pauseLocationUpdatesAutomatically: false,
|
||||
showBackgroundLocationIndicator: true,
|
||||
timeLimit: timeLimit,
|
||||
);
|
||||
}
|
||||
|
||||
return LocationSettings(
|
||||
accuracy: LocationAccuracy.best,
|
||||
distanceFilter: distanceFilter,
|
||||
timeLimit: timeLimit,
|
||||
);
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Tracking Control
|
||||
// ============================================================================
|
||||
@@ -362,7 +404,7 @@ class LocationTrackingService {
|
||||
|
||||
// Save disabled state
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
await prefs.setBool(_prefKeyEnabled, false);
|
||||
await prefs.setBool(_scopedKey(_prefKeyEnabled), false);
|
||||
|
||||
debugPrint('✅ [LocationTracking] Tracking stopped');
|
||||
}
|
||||
@@ -440,8 +482,8 @@ class LocationTrackingService {
|
||||
|
||||
// Save to preferences
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
await prefs.setDouble(_prefKeyLastLat, position.latitude);
|
||||
await prefs.setDouble(_prefKeyLastLon, position.longitude);
|
||||
await prefs.setDouble(_scopedKey(_prefKeyLastLat), position.latitude);
|
||||
await prefs.setDouble(_scopedKey(_prefKeyLastLon), position.longitude);
|
||||
|
||||
debugPrint('✅ [LocationTracking] Initial position set without broadcast');
|
||||
debugPrint(' Next broadcast allowed in ${minTimeIntervalSeconds}s');
|
||||
@@ -609,17 +651,24 @@ class LocationTrackingService {
|
||||
Future<void> loadSettings() async {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
|
||||
minDistanceMeters = prefs.getDouble(_prefKeyMinDistance) ?? 5.0;
|
||||
maxDistanceMeters = prefs.getDouble(_prefKeyMaxDistance) ?? 100.0;
|
||||
minTimeIntervalSeconds = prefs.getInt(_prefKeyMinTimeInterval) ?? 30;
|
||||
gpsUpdateDistance = prefs.getDouble(_prefKeyGpsUpdateDistance) ?? 10.0;
|
||||
minDistanceMeters = prefs.getDouble(_scopedKey(_prefKeyMinDistance)) ?? 5.0;
|
||||
maxDistanceMeters =
|
||||
prefs.getDouble(_scopedKey(_prefKeyMaxDistance)) ?? 100.0;
|
||||
minTimeIntervalSeconds =
|
||||
prefs.getInt(_scopedKey(_prefKeyMinTimeInterval)) ?? 30;
|
||||
gpsUpdateDistance =
|
||||
prefs.getDouble(_scopedKey(_prefKeyGpsUpdateDistance)) ?? 10.0;
|
||||
fastLocationUpdatesEnabled =
|
||||
prefs.getBool(_prefKeyFastLocationEnabled) ?? false;
|
||||
prefs.getBool(_scopedKey(_prefKeyFastLocationEnabled)) ?? false;
|
||||
fastLocationMovementThresholdMeters =
|
||||
(prefs.getDouble(_prefKeyFastMovementThreshold) ?? gpsUpdateDistance)
|
||||
(prefs.getDouble(_scopedKey(_prefKeyFastMovementThreshold)) ??
|
||||
gpsUpdateDistance)
|
||||
.clamp(1.0, 1000.0);
|
||||
fastLocationActiveCadenceSeconds =
|
||||
(prefs.getInt(_prefKeyFastActiveCadence) ?? 10).clamp(5, 60);
|
||||
(prefs.getInt(_scopedKey(_prefKeyFastActiveCadence)) ?? 10).clamp(
|
||||
5,
|
||||
60,
|
||||
);
|
||||
|
||||
debugPrint('✅ [LocationTracking] Settings loaded');
|
||||
debugPrint(' Min distance: ${minDistanceMeters}m');
|
||||
@@ -637,21 +686,27 @@ class LocationTrackingService {
|
||||
Future<void> saveSettings() async {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
|
||||
await prefs.setDouble(_prefKeyMinDistance, minDistanceMeters);
|
||||
await prefs.setDouble(_prefKeyMaxDistance, maxDistanceMeters);
|
||||
await prefs.setInt(_prefKeyMinTimeInterval, minTimeIntervalSeconds);
|
||||
await prefs.setDouble(_prefKeyGpsUpdateDistance, gpsUpdateDistance);
|
||||
await prefs.setBool(_prefKeyEnabled, isTracking);
|
||||
await prefs.setDouble(_scopedKey(_prefKeyMinDistance), minDistanceMeters);
|
||||
await prefs.setDouble(_scopedKey(_prefKeyMaxDistance), maxDistanceMeters);
|
||||
await prefs.setInt(
|
||||
_scopedKey(_prefKeyMinTimeInterval),
|
||||
minTimeIntervalSeconds,
|
||||
);
|
||||
await prefs.setDouble(
|
||||
_scopedKey(_prefKeyGpsUpdateDistance),
|
||||
gpsUpdateDistance,
|
||||
);
|
||||
await prefs.setBool(_scopedKey(_prefKeyEnabled), isTracking);
|
||||
await prefs.setBool(
|
||||
_prefKeyFastLocationEnabled,
|
||||
_scopedKey(_prefKeyFastLocationEnabled),
|
||||
fastLocationUpdatesEnabled,
|
||||
);
|
||||
await prefs.setDouble(
|
||||
_prefKeyFastMovementThreshold,
|
||||
_scopedKey(_prefKeyFastMovementThreshold),
|
||||
fastLocationMovementThresholdMeters,
|
||||
);
|
||||
await prefs.setInt(
|
||||
_prefKeyFastActiveCadence,
|
||||
_scopedKey(_prefKeyFastActiveCadence),
|
||||
fastLocationActiveCadenceSeconds,
|
||||
);
|
||||
|
||||
|
||||
@@ -41,90 +41,103 @@ class MapMarkerService {
|
||||
Function(Contact)? onTap,
|
||||
double mapRotation = 0,
|
||||
Position? userPosition,
|
||||
Map<String, LatLng>? estimatedLocations,
|
||||
}) {
|
||||
return contacts.map((contact) {
|
||||
final location = contact.displayLocation;
|
||||
if (location == null) return null;
|
||||
return contacts
|
||||
.map((contact) {
|
||||
final location = contact.displayLocation ??
|
||||
estimatedLocations?[contact.publicKeyHex];
|
||||
if (location == null) return null;
|
||||
|
||||
return Marker(
|
||||
point: location,
|
||||
width: 80,
|
||||
height: 100,
|
||||
rotate: false, // Don't rotate the entire marker with map
|
||||
child: Transform.rotate(
|
||||
angle: -mapRotation * pi / 180,
|
||||
child: GestureDetector(
|
||||
onTap: onTap != null ? () => onTap(contact) : null,
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
// Location update time indicator
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 4, vertical: 1),
|
||||
decoration: BoxDecoration(
|
||||
color: getLocationAgeColor(contact),
|
||||
borderRadius: BorderRadius.circular(3),
|
||||
),
|
||||
child: Text(
|
||||
contact.timeSinceLocationUpdate,
|
||||
style: const TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 9,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 2),
|
||||
// Marker icon
|
||||
Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
shape: contact.type == ContactType.channel ||
|
||||
contact.type == ContactType.room
|
||||
? BoxShape.rectangle
|
||||
: BoxShape.circle,
|
||||
borderRadius: contact.type == ContactType.channel ||
|
||||
contact.type == ContactType.room
|
||||
? BorderRadius.circular(14)
|
||||
: null,
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.black.withValues(alpha: 0.3),
|
||||
blurRadius: 4,
|
||||
offset: const Offset(0, 2),
|
||||
return Marker(
|
||||
point: location,
|
||||
width: 80,
|
||||
height: 100,
|
||||
rotate: false, // Don't rotate the entire marker with map
|
||||
child: Transform.rotate(
|
||||
angle: -mapRotation * pi / 180,
|
||||
child: GestureDetector(
|
||||
onTap: onTap != null ? () => onTap(contact) : null,
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
// Location update time indicator
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 4,
|
||||
vertical: 1,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color: getLocationAgeColor(contact),
|
||||
borderRadius: BorderRadius.circular(3),
|
||||
),
|
||||
child: Text(
|
||||
contact.timeSinceLocationUpdate,
|
||||
style: const TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 9,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
padding: const EdgeInsets.all(2),
|
||||
child: ContactAvatar(contact: contact, radius: 16),
|
||||
),
|
||||
const SizedBox(height: 2),
|
||||
// Name label (without emoji)
|
||||
Container(
|
||||
constraints: const BoxConstraints(maxWidth: 80),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 4, vertical: 1),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.black.withValues(alpha: 0.7),
|
||||
borderRadius: BorderRadius.circular(3),
|
||||
),
|
||||
child: Text(
|
||||
contact.displayName,
|
||||
style: const TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 9,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
maxLines: 1,
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
const SizedBox(height: 2),
|
||||
// Marker icon
|
||||
Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
shape:
|
||||
contact.type == ContactType.channel ||
|
||||
contact.type == ContactType.room
|
||||
? BoxShape.rectangle
|
||||
: BoxShape.circle,
|
||||
borderRadius:
|
||||
contact.type == ContactType.channel ||
|
||||
contact.type == ContactType.room
|
||||
? BorderRadius.circular(14)
|
||||
: null,
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.black.withValues(alpha: 0.3),
|
||||
blurRadius: 4,
|
||||
offset: const Offset(0, 2),
|
||||
),
|
||||
],
|
||||
),
|
||||
padding: const EdgeInsets.all(2),
|
||||
child: ContactAvatar(contact: contact, radius: 16),
|
||||
),
|
||||
const SizedBox(height: 2),
|
||||
// Name label (without emoji)
|
||||
Container(
|
||||
constraints: const BoxConstraints(maxWidth: 80),
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 4,
|
||||
vertical: 1,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.black.withValues(alpha: 0.7),
|
||||
borderRadius: BorderRadius.circular(3),
|
||||
),
|
||||
child: Text(
|
||||
contact.displayName,
|
||||
style: const TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 9,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
maxLines: 1,
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}).whereType<Marker>().toList();
|
||||
);
|
||||
})
|
||||
.whereType<Marker>()
|
||||
.toList();
|
||||
}
|
||||
|
||||
/// Generate markers for SAR events.
|
||||
@@ -141,10 +154,11 @@ class MapMarkerService {
|
||||
required BuildContext context,
|
||||
Function(SarMarker)? onTap,
|
||||
double mapRotation = 0,
|
||||
LatLng Function(LatLng point)? pointTransformer,
|
||||
}) {
|
||||
return sarMarkers.map((marker) {
|
||||
return Marker(
|
||||
point: marker.location,
|
||||
point: pointTransformer?.call(marker.location) ?? marker.location,
|
||||
width: 90,
|
||||
height: 100,
|
||||
rotate: false, // Don't rotate the entire marker with map
|
||||
@@ -157,7 +171,10 @@ class MapMarkerService {
|
||||
children: [
|
||||
// Time ago label
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 4, vertical: 1),
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 4,
|
||||
vertical: 1,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color: getSarMarkerColor(marker.type),
|
||||
borderRadius: BorderRadius.circular(3),
|
||||
@@ -188,7 +205,7 @@ class MapMarkerService {
|
||||
),
|
||||
padding: const EdgeInsets.all(6),
|
||||
child: Text(
|
||||
marker.emoji, // Use custom emoji if available
|
||||
marker.emoji, // Use custom emoji if available
|
||||
style: const TextStyle(fontSize: 18),
|
||||
),
|
||||
),
|
||||
@@ -196,13 +213,17 @@ class MapMarkerService {
|
||||
// Type label
|
||||
Container(
|
||||
constraints: const BoxConstraints(maxWidth: 90),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 4, vertical: 1),
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 4,
|
||||
vertical: 1,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.black.withValues(alpha: 0.7),
|
||||
borderRadius: BorderRadius.circular(3),
|
||||
),
|
||||
child: Text(
|
||||
marker.displayName, // Uses notes if available, otherwise type.displayName
|
||||
marker
|
||||
.displayName, // Uses notes if available, otherwise type.displayName
|
||||
style: const TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 9,
|
||||
@@ -269,7 +290,8 @@ class MapMarkerService {
|
||||
final dLat = (lat2 - lat1) * pi / 180;
|
||||
final dLon = (lon2 - lon1) * pi / 180;
|
||||
|
||||
final a = sin(dLat / 2) * sin(dLat / 2) +
|
||||
final a =
|
||||
sin(dLat / 2) * sin(dLat / 2) +
|
||||
cos(lat1 * pi / 180) *
|
||||
cos(lat2 * pi / 180) *
|
||||
sin(dLon / 2) *
|
||||
@@ -299,8 +321,8 @@ class MapMarkerService {
|
||||
final lat2Rad = lat2 * pi / 180;
|
||||
|
||||
final y = sin(dLon) * cos(lat2Rad);
|
||||
final x = cos(lat1Rad) * sin(lat2Rad) -
|
||||
sin(lat1Rad) * cos(lat2Rad) * cos(dLon);
|
||||
final x =
|
||||
cos(lat1Rad) * sin(lat2Rad) - sin(lat1Rad) * cos(lat2Rad) * cos(dLon);
|
||||
|
||||
final bearing = atan2(y, x) * 180 / pi;
|
||||
return (bearing + 360) % 360;
|
||||
@@ -368,6 +390,8 @@ class MapMarkerService {
|
||||
return Colors.deepPurple; // Purple for repeaters
|
||||
case ContactType.room:
|
||||
return Colors.teal; // Teal for rooms
|
||||
case ContactType.sensor:
|
||||
return Colors.green; // Green for sensors
|
||||
case ContactType.channel:
|
||||
return Colors.orange; // Orange for channels
|
||||
case ContactType.none:
|
||||
@@ -389,6 +413,8 @@ class MapMarkerService {
|
||||
return Icons.router; // Router icon for repeaters
|
||||
case ContactType.room:
|
||||
return Icons.forum; // Forum/chat icon for rooms
|
||||
case ContactType.sensor:
|
||||
return Icons.sensors; // Sensors icon for sensor nodes
|
||||
case ContactType.channel:
|
||||
return Icons.public; // Public icon for channels
|
||||
case ContactType.none:
|
||||
@@ -466,7 +492,8 @@ class MapMarkerService {
|
||||
}
|
||||
|
||||
if (allPoints.isEmpty) {
|
||||
return defaultCenter ?? const LatLng(46.0569, 14.5058); // Ljubljana, Slovenia
|
||||
return defaultCenter ??
|
||||
const LatLng(46.0569, 14.5058); // Ljubljana, Slovenia
|
||||
}
|
||||
|
||||
double lat = 0, lng = 0;
|
||||
|
||||
99
lib/services/map_workspace_snapshot_service.dart
Normal file
99
lib/services/map_workspace_snapshot_service.dart
Normal file
@@ -0,0 +1,99 @@
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
|
||||
import '../models/config_profile.dart';
|
||||
import '../providers/drawing_provider.dart';
|
||||
import '../providers/map_provider.dart';
|
||||
import 'profiles_feature_service.dart';
|
||||
|
||||
class MapWorkspaceSnapshotService {
|
||||
Future<MapWorkspaceProfileSection> capture({
|
||||
required MapProvider mapProvider,
|
||||
required DrawingProvider drawingProvider,
|
||||
}) async {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
return MapWorkspaceProfileSection(
|
||||
mapPrefs: {
|
||||
'map_rotate_with_heading':
|
||||
prefs.getBool(
|
||||
ProfileStorageScope.scopedKey('map_rotate_with_heading'),
|
||||
) ??
|
||||
false,
|
||||
'map_show_debug_info':
|
||||
prefs.getBool(
|
||||
ProfileStorageScope.scopedKey('map_show_debug_info'),
|
||||
) ??
|
||||
false,
|
||||
'map_fullscreen':
|
||||
prefs.getBool(ProfileStorageScope.scopedKey('map_fullscreen')) ??
|
||||
false,
|
||||
'map_last_latitude': prefs.getDouble(
|
||||
ProfileStorageScope.scopedKey('map_last_latitude'),
|
||||
),
|
||||
'map_last_longitude': prefs.getDouble(
|
||||
ProfileStorageScope.scopedKey('map_last_longitude'),
|
||||
),
|
||||
'map_last_zoom': prefs.getDouble(
|
||||
ProfileStorageScope.scopedKey('map_last_zoom'),
|
||||
),
|
||||
'map_last_layer_type': prefs.getInt(
|
||||
ProfileStorageScope.scopedKey('map_last_layer_type'),
|
||||
),
|
||||
'map_gps_update_distance': prefs.getDouble(
|
||||
ProfileStorageScope.scopedKey('map_gps_update_distance'),
|
||||
),
|
||||
'background_tracking_enabled': prefs.getBool(
|
||||
ProfileStorageScope.scopedKey('background_tracking_enabled'),
|
||||
),
|
||||
},
|
||||
drawings: drawingProvider.exportDrawingsJson(),
|
||||
currentTrail: mapProvider.currentTrail?.toJson(),
|
||||
trailHistory: mapProvider.trailHistory
|
||||
.map((trail) => trail.toJson())
|
||||
.toList(),
|
||||
importedTrail: mapProvider.importedTrail?.toJson(),
|
||||
isTrailVisible: mapProvider.isTrailVisible,
|
||||
showCadastralOverlay: mapProvider.showCadastralOverlay,
|
||||
showForestRoadsOverlay: mapProvider.showForestRoadsOverlay,
|
||||
showHikingTrailsOverlay: mapProvider.showHikingTrailsOverlay,
|
||||
showMainRoadsOverlay: mapProvider.showMainRoadsOverlay,
|
||||
showHouseNumbersOverlay: mapProvider.showHouseNumbersOverlay,
|
||||
showFireHazardZonesOverlay: mapProvider.showFireHazardZonesOverlay,
|
||||
showHistoricalFiresOverlay: mapProvider.showHistoricalFiresOverlay,
|
||||
showFirebreaksOverlay: mapProvider.showFirebreaksOverlay,
|
||||
showKrasFireZonesOverlay: mapProvider.showKrasFireZonesOverlay,
|
||||
showPlaceNamesOverlay: mapProvider.showPlaceNamesOverlay,
|
||||
showMunicipalityBordersOverlay:
|
||||
mapProvider.showMunicipalityBordersOverlay,
|
||||
hideRepeatersOnMap: mapProvider.hideRepeatersOnMap,
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> apply(
|
||||
MapWorkspaceProfileSection? section, {
|
||||
required MapProvider mapProvider,
|
||||
required DrawingProvider drawingProvider,
|
||||
}) async {
|
||||
if (section == null || section.isEmpty) {
|
||||
return;
|
||||
}
|
||||
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
final mapPrefs = section.mapPrefs ?? const <String, dynamic>{};
|
||||
for (final entry in mapPrefs.entries) {
|
||||
final key = ProfileStorageScope.scopedKey(entry.key);
|
||||
final value = entry.value;
|
||||
if (value == null) {
|
||||
await prefs.remove(key);
|
||||
} else if (value is bool) {
|
||||
await prefs.setBool(key, value);
|
||||
} else if (value is int) {
|
||||
await prefs.setInt(key, value);
|
||||
} else if (value is double) {
|
||||
await prefs.setDouble(key, value);
|
||||
}
|
||||
}
|
||||
|
||||
mapProvider.applyWorkspaceJson(section.toJson());
|
||||
await drawingProvider.replaceDrawingsFromJson(section.drawings);
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,7 @@
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
|
||||
class MeshMapNode {
|
||||
final int type;
|
||||
@@ -18,6 +20,14 @@ class MeshMapNode {
|
||||
required this.updatedAtMs,
|
||||
});
|
||||
|
||||
bool get hasValidCoordinates =>
|
||||
latitude >= -90 &&
|
||||
latitude <= 90 &&
|
||||
longitude >= -180 &&
|
||||
longitude <= 180 &&
|
||||
latitude != 0 &&
|
||||
longitude != 0;
|
||||
|
||||
factory MeshMapNode.fromJson(Map<String, dynamic> json) {
|
||||
return MeshMapNode(
|
||||
type: (json['type'] as num?)?.toInt() ?? 0,
|
||||
@@ -33,25 +43,36 @@ class MeshMapNode {
|
||||
class MeshMapNodesService {
|
||||
static const String _nodesEndpoint =
|
||||
'https://api.meshcore.nz/api/v1/map/nodes';
|
||||
static const Duration _cacheTtl = Duration(minutes: 2);
|
||||
static const Duration traceCacheTtl = Duration(minutes: 10);
|
||||
static const int repeaterType = 1;
|
||||
static const Duration _cacheTtl = Duration(hours: 24);
|
||||
static const Duration traceCacheTtl = _cacheTtl;
|
||||
static const Duration traceTimeout = Duration(seconds: 30);
|
||||
static const String _cacheKey = 'mesh_map_nodes_cache_v1';
|
||||
static const String _cacheTimestampKey = 'mesh_map_nodes_cache_timestamp_v1';
|
||||
static List<MeshMapNode>? _cachedNodes;
|
||||
static DateTime? _cachedAt;
|
||||
static Future<void>? _ongoingSync;
|
||||
|
||||
static Future<List<MeshMapNode>> fetchNodes({
|
||||
bool forceRefresh = false,
|
||||
Duration cacheTtl = _cacheTtl,
|
||||
bool allowNetwork = true,
|
||||
http.Client? client,
|
||||
}) async {
|
||||
final now = DateTime.now();
|
||||
final cached = await loadCachedNodes(cacheTtl: cacheTtl);
|
||||
if (!forceRefresh &&
|
||||
_cachedNodes != null &&
|
||||
cached.isNotEmpty &&
|
||||
_cachedAt != null &&
|
||||
now.difference(_cachedAt!) < cacheTtl) {
|
||||
return _cachedNodes!;
|
||||
return cached;
|
||||
}
|
||||
|
||||
final response = await http
|
||||
if (!allowNetwork) {
|
||||
return cached;
|
||||
}
|
||||
|
||||
final response = await (client ?? http.Client())
|
||||
.get(Uri.parse(_nodesEndpoint))
|
||||
.timeout(traceTimeout);
|
||||
if (response.statusCode < 200 || response.statusCode >= 300) {
|
||||
@@ -64,13 +85,134 @@ class MeshMapNodesService {
|
||||
final nodes = nodesRaw
|
||||
.whereType<Map<String, dynamic>>()
|
||||
.map(MeshMapNode.fromJson)
|
||||
.where(
|
||||
(n) => n.publicKey.isNotEmpty && n.latitude != 0 && n.longitude != 0,
|
||||
)
|
||||
.where((n) => n.publicKey.isNotEmpty && n.hasValidCoordinates)
|
||||
.toList();
|
||||
|
||||
_cachedNodes = nodes;
|
||||
_cachedAt = now;
|
||||
await _storeCache(nodes, cachedAt: now);
|
||||
return nodes;
|
||||
}
|
||||
|
||||
static Future<List<MeshMapNode>> loadCachedNodes({
|
||||
Duration cacheTtl = _cacheTtl,
|
||||
}) async {
|
||||
final now = DateTime.now();
|
||||
if (_cachedNodes != null &&
|
||||
_cachedAt != null &&
|
||||
now.difference(_cachedAt!) < cacheTtl) {
|
||||
return _cachedNodes!;
|
||||
}
|
||||
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
final cachedAtMs = prefs.getInt(_cacheTimestampKey);
|
||||
final cachedJson = prefs.getString(_cacheKey);
|
||||
if (cachedAtMs == null || cachedJson == null) {
|
||||
_cachedNodes = null;
|
||||
_cachedAt = null;
|
||||
return const [];
|
||||
}
|
||||
|
||||
final cachedAt = DateTime.fromMillisecondsSinceEpoch(cachedAtMs);
|
||||
if (now.difference(cachedAt) >= cacheTtl) {
|
||||
_cachedNodes = null;
|
||||
_cachedAt = cachedAt;
|
||||
return const [];
|
||||
}
|
||||
|
||||
final decoded = jsonDecode(cachedJson) as List<dynamic>;
|
||||
final nodes = decoded
|
||||
.whereType<Map<String, dynamic>>()
|
||||
.map(MeshMapNode.fromJson)
|
||||
.where((n) => n.publicKey.isNotEmpty && n.hasValidCoordinates)
|
||||
.toList();
|
||||
_cachedNodes = nodes;
|
||||
_cachedAt = cachedAt;
|
||||
return nodes;
|
||||
}
|
||||
|
||||
static Future<bool> hasFreshCache({Duration cacheTtl = _cacheTtl}) async {
|
||||
final nodes = await loadCachedNodes(cacheTtl: cacheTtl);
|
||||
return nodes.isNotEmpty;
|
||||
}
|
||||
|
||||
static Future<void> syncInBackgroundIfStale({
|
||||
Duration cacheTtl = _cacheTtl,
|
||||
http.Client? client,
|
||||
}) async {
|
||||
final now = DateTime.now();
|
||||
if (_cachedAt != null && now.difference(_cachedAt!) < cacheTtl) {
|
||||
return;
|
||||
}
|
||||
|
||||
final cached = await loadCachedNodes(cacheTtl: cacheTtl);
|
||||
if (cached.isNotEmpty && _cachedAt != null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (_ongoingSync != null) {
|
||||
return _ongoingSync!;
|
||||
}
|
||||
|
||||
_ongoingSync = () async {
|
||||
try {
|
||||
await fetchNodes(
|
||||
forceRefresh: true,
|
||||
cacheTtl: cacheTtl,
|
||||
allowNetwork: true,
|
||||
client: client,
|
||||
);
|
||||
} catch (_) {
|
||||
// Background refresh is best-effort only.
|
||||
} finally {
|
||||
_ongoingSync = null;
|
||||
}
|
||||
}();
|
||||
|
||||
return _ongoingSync!;
|
||||
}
|
||||
|
||||
static Future<void> clearCache() async {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
await prefs.remove(_cacheKey);
|
||||
await prefs.remove(_cacheTimestampKey);
|
||||
_cachedNodes = null;
|
||||
_cachedAt = null;
|
||||
}
|
||||
|
||||
static Future<DateTime?> cachedAt() async {
|
||||
if (_cachedAt != null) return _cachedAt;
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
final cachedAtMs = prefs.getInt(_cacheTimestampKey);
|
||||
if (cachedAtMs == null) return null;
|
||||
_cachedAt = DateTime.fromMillisecondsSinceEpoch(cachedAtMs);
|
||||
return _cachedAt;
|
||||
}
|
||||
|
||||
static bool isRepeater(MeshMapNode node) => node.type == repeaterType;
|
||||
|
||||
static Future<void> _storeCache(
|
||||
List<MeshMapNode> nodes, {
|
||||
required DateTime cachedAt,
|
||||
}) async {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
await prefs.setString(
|
||||
_cacheKey,
|
||||
jsonEncode(
|
||||
nodes
|
||||
.map(
|
||||
(node) => {
|
||||
'type': node.type,
|
||||
'name': node.name,
|
||||
'public_key': node.publicKey,
|
||||
'latitude': node.latitude,
|
||||
'longitude': node.longitude,
|
||||
'updated_at': node.updatedAtMs,
|
||||
},
|
||||
)
|
||||
.toList(),
|
||||
),
|
||||
);
|
||||
await prefs.setInt(_cacheTimestampKey, cachedAt.millisecondsSinceEpoch);
|
||||
_cachedNodes = nodes;
|
||||
_cachedAt = cachedAt;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,11 +6,13 @@ import '../models/message_contact_location.dart';
|
||||
import '../models/message_reception_details.dart';
|
||||
import '../models/message_transfer_details.dart';
|
||||
import '../models/message_route_metadata.dart';
|
||||
import 'profiles_feature_service.dart';
|
||||
import 'package:latlong2/latlong.dart';
|
||||
|
||||
/// Service for persisting messages to local storage
|
||||
class MessageStorageService {
|
||||
static const String _messagesKey = 'stored_messages';
|
||||
static const String _removedSarMarkerIdsKey = 'removed_sar_marker_ids';
|
||||
static const String _messageContactLocationsKey =
|
||||
'stored_message_contact_locations';
|
||||
static const String _messageReceptionDetailsKey =
|
||||
@@ -19,8 +21,14 @@ class MessageStorageService {
|
||||
'stored_message_transfer_details';
|
||||
static const String _messageRouteMetadataKey =
|
||||
'stored_message_route_metadata';
|
||||
static const String _embeddedReceptionDetailsKey = 'storedReceptionDetails';
|
||||
static const String _legacyPathBytesKey = 'storedPathBytes';
|
||||
static const int _maxStoredMessages = 1000; // Store up to 1000 messages
|
||||
|
||||
String _key(String baseKey, {String? namespace}) {
|
||||
return ProfileStorageScope.scopedKey(baseKey, namespace: namespace);
|
||||
}
|
||||
|
||||
/// Save messages to persistent storage
|
||||
Future<void> saveMessages(
|
||||
List<Message> messages, {
|
||||
@@ -28,12 +36,21 @@ class MessageStorageService {
|
||||
Map<String, MessageReceptionDetails> messageReceptionDetails = const {},
|
||||
Map<String, MessageTransferDetails> messageTransferDetails = const {},
|
||||
Map<String, MessageRouteMetadata> messageRouteMetadata = const {},
|
||||
String? namespace,
|
||||
}) async {
|
||||
try {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
|
||||
// Convert messages to JSON
|
||||
final jsonList = messages.map((msg) => _messageToJson(msg)).toList();
|
||||
// Convert messages to JSON and embed path bytes as a fallback so they
|
||||
// survive restore even if the sidecar reception-details entry is absent.
|
||||
final jsonList = messages
|
||||
.map(
|
||||
(msg) => _messageToJson(
|
||||
msg,
|
||||
receptionDetails: messageReceptionDetails[msg.id],
|
||||
),
|
||||
)
|
||||
.toList();
|
||||
|
||||
// Limit to max stored messages (keep most recent)
|
||||
final limitedList = jsonList.length > _maxStoredMessages
|
||||
@@ -41,7 +58,10 @@ class MessageStorageService {
|
||||
: jsonList;
|
||||
|
||||
final jsonString = jsonEncode(limitedList);
|
||||
await prefs.setString(_messagesKey, jsonString);
|
||||
await prefs.setString(
|
||||
_key(_messagesKey, namespace: namespace),
|
||||
jsonString,
|
||||
);
|
||||
final retainedMessageIds = limitedList
|
||||
.map((entry) => entry['id'] as String)
|
||||
.toSet();
|
||||
@@ -70,19 +90,19 @@ class MessageStorageService {
|
||||
}
|
||||
}
|
||||
await prefs.setString(
|
||||
_messageContactLocationsKey,
|
||||
_key(_messageContactLocationsKey, namespace: namespace),
|
||||
jsonEncode(locationJson),
|
||||
);
|
||||
await prefs.setString(
|
||||
_messageReceptionDetailsKey,
|
||||
_key(_messageReceptionDetailsKey, namespace: namespace),
|
||||
jsonEncode(receptionJson),
|
||||
);
|
||||
await prefs.setString(
|
||||
_messageTransferDetailsKey,
|
||||
_key(_messageTransferDetailsKey, namespace: namespace),
|
||||
jsonEncode(transferJson),
|
||||
);
|
||||
await prefs.setString(
|
||||
_messageRouteMetadataKey,
|
||||
_key(_messageRouteMetadataKey, namespace: namespace),
|
||||
jsonEncode(routeMetadataJson),
|
||||
);
|
||||
|
||||
@@ -94,11 +114,14 @@ class MessageStorageService {
|
||||
}
|
||||
}
|
||||
|
||||
Future<Map<String, MessageContactLocation>>
|
||||
loadMessageContactLocations() async {
|
||||
Future<Map<String, MessageContactLocation>> loadMessageContactLocations({
|
||||
String? namespace,
|
||||
}) async {
|
||||
try {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
final jsonString = prefs.getString(_messageContactLocationsKey);
|
||||
final jsonString = prefs.getString(
|
||||
_key(_messageContactLocationsKey, namespace: namespace),
|
||||
);
|
||||
if (jsonString == null || jsonString.isEmpty) {
|
||||
return const {};
|
||||
}
|
||||
@@ -124,29 +147,48 @@ class MessageStorageService {
|
||||
}
|
||||
}
|
||||
|
||||
Future<Map<String, MessageReceptionDetails>>
|
||||
loadMessageReceptionDetails() async {
|
||||
Future<Map<String, MessageReceptionDetails>> loadMessageReceptionDetails({
|
||||
String? namespace,
|
||||
}) async {
|
||||
try {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
final jsonString = prefs.getString(_messageReceptionDetailsKey);
|
||||
if (jsonString == null || jsonString.isEmpty) {
|
||||
return const {};
|
||||
}
|
||||
|
||||
final decoded = jsonDecode(jsonString);
|
||||
if (decoded is! Map<String, dynamic>) {
|
||||
return const {};
|
||||
}
|
||||
|
||||
final jsonString = prefs.getString(
|
||||
_key(_messageReceptionDetailsKey, namespace: namespace),
|
||||
);
|
||||
final result = <String, MessageReceptionDetails>{};
|
||||
for (final entry in decoded.entries) {
|
||||
final value = entry.value;
|
||||
if (value is! Map<String, dynamic>) continue;
|
||||
final snapshot = MessageReceptionDetails.fromJson(value);
|
||||
if (snapshot != null) {
|
||||
result[entry.key] = snapshot;
|
||||
if (jsonString != null && jsonString.isNotEmpty) {
|
||||
final decoded = jsonDecode(jsonString);
|
||||
if (decoded is Map<String, dynamic>) {
|
||||
for (final entry in decoded.entries) {
|
||||
final value = entry.value;
|
||||
if (value is! Map<String, dynamic>) continue;
|
||||
final snapshot = MessageReceptionDetails.fromJson(value);
|
||||
if (snapshot != null) {
|
||||
result[entry.key] = snapshot;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
final embeddedReceptionDetails = await _loadEmbeddedReceptionDetails(
|
||||
namespace: namespace,
|
||||
);
|
||||
embeddedReceptionDetails.forEach((messageId, snapshot) {
|
||||
result.putIfAbsent(messageId, () => snapshot);
|
||||
});
|
||||
|
||||
final fallbackPathBytes = await _loadLegacyPathBytesFromMessages(
|
||||
namespace: namespace,
|
||||
);
|
||||
fallbackPathBytes.forEach((messageId, pathBytes) {
|
||||
result.putIfAbsent(
|
||||
messageId,
|
||||
() => MessageReceptionDetails(
|
||||
capturedAt: DateTime.fromMillisecondsSinceEpoch(0),
|
||||
pathBytes: pathBytes,
|
||||
),
|
||||
);
|
||||
});
|
||||
return result;
|
||||
} catch (e) {
|
||||
debugPrint('❌ [MessageStorage] Error loading reception details: $e');
|
||||
@@ -154,11 +196,14 @@ class MessageStorageService {
|
||||
}
|
||||
}
|
||||
|
||||
Future<Map<String, MessageTransferDetails>>
|
||||
loadMessageTransferDetails() async {
|
||||
Future<Map<String, MessageTransferDetails>> loadMessageTransferDetails({
|
||||
String? namespace,
|
||||
}) async {
|
||||
try {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
final jsonString = prefs.getString(_messageTransferDetailsKey);
|
||||
final jsonString = prefs.getString(
|
||||
_key(_messageTransferDetailsKey, namespace: namespace),
|
||||
);
|
||||
if (jsonString == null || jsonString.isEmpty) {
|
||||
return const {};
|
||||
}
|
||||
@@ -184,10 +229,14 @@ class MessageStorageService {
|
||||
}
|
||||
}
|
||||
|
||||
Future<Map<String, MessageRouteMetadata>> loadMessageRouteMetadata() async {
|
||||
Future<Map<String, MessageRouteMetadata>> loadMessageRouteMetadata({
|
||||
String? namespace,
|
||||
}) async {
|
||||
try {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
final jsonString = prefs.getString(_messageRouteMetadataKey);
|
||||
final jsonString = prefs.getString(
|
||||
_key(_messageRouteMetadataKey, namespace: namespace),
|
||||
);
|
||||
if (jsonString == null || jsonString.isEmpty) {
|
||||
return const {};
|
||||
}
|
||||
@@ -211,10 +260,12 @@ class MessageStorageService {
|
||||
}
|
||||
|
||||
/// Load messages from persistent storage
|
||||
Future<List<Message>> loadMessages() async {
|
||||
Future<List<Message>> loadMessages({String? namespace}) async {
|
||||
try {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
final jsonString = prefs.getString(_messagesKey);
|
||||
final jsonString = prefs.getString(
|
||||
_key(_messagesKey, namespace: namespace),
|
||||
);
|
||||
|
||||
if (jsonString == null || jsonString.isEmpty) {
|
||||
debugPrint('ℹ️ [MessageStorage] No stored messages found');
|
||||
@@ -239,25 +290,67 @@ class MessageStorageService {
|
||||
}
|
||||
|
||||
/// Clear all stored messages
|
||||
Future<void> clearMessages() async {
|
||||
Future<void> clearMessages({String? namespace}) async {
|
||||
try {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
await prefs.remove(_messagesKey);
|
||||
await prefs.remove(_messageContactLocationsKey);
|
||||
await prefs.remove(_messageReceptionDetailsKey);
|
||||
await prefs.remove(_messageTransferDetailsKey);
|
||||
await prefs.remove(_messageRouteMetadataKey);
|
||||
await prefs.remove(_key(_messagesKey, namespace: namespace));
|
||||
await prefs.remove(
|
||||
_key(_messageContactLocationsKey, namespace: namespace),
|
||||
);
|
||||
await prefs.remove(
|
||||
_key(_messageReceptionDetailsKey, namespace: namespace),
|
||||
);
|
||||
await prefs.remove(
|
||||
_key(_messageTransferDetailsKey, namespace: namespace),
|
||||
);
|
||||
await prefs.remove(_key(_messageRouteMetadataKey, namespace: namespace));
|
||||
await prefs.remove(_key(_removedSarMarkerIdsKey, namespace: namespace));
|
||||
debugPrint('✅ [MessageStorage] Cleared all stored messages');
|
||||
} catch (e) {
|
||||
debugPrint('❌ [MessageStorage] Error clearing messages: $e');
|
||||
}
|
||||
}
|
||||
|
||||
/// Get storage statistics
|
||||
Future<Map<String, dynamic>> getStorageStats() async {
|
||||
Future<Set<String>> loadRemovedSarMarkerIds({String? namespace}) async {
|
||||
try {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
final jsonString = prefs.getString(_messagesKey);
|
||||
final ids =
|
||||
prefs.getStringList(
|
||||
_key(_removedSarMarkerIdsKey, namespace: namespace),
|
||||
) ??
|
||||
const [];
|
||||
return ids.toSet();
|
||||
} catch (e) {
|
||||
debugPrint('❌ [MessageStorage] Error loading removed SAR marker IDs: $e');
|
||||
return const <String>{};
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> saveRemovedSarMarkerIds(
|
||||
Set<String> ids, {
|
||||
String? namespace,
|
||||
}) async {
|
||||
try {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
await prefs.setStringList(
|
||||
_key(_removedSarMarkerIdsKey, namespace: namespace),
|
||||
ids.toList()..sort(),
|
||||
);
|
||||
debugPrint(
|
||||
'✅ [MessageStorage] Saved ${ids.length} removed SAR marker IDs',
|
||||
);
|
||||
} catch (e) {
|
||||
debugPrint('❌ [MessageStorage] Error saving removed SAR marker IDs: $e');
|
||||
}
|
||||
}
|
||||
|
||||
/// Get storage statistics
|
||||
Future<Map<String, dynamic>> getStorageStats({String? namespace}) async {
|
||||
try {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
final jsonString = prefs.getString(
|
||||
_key(_messagesKey, namespace: namespace),
|
||||
);
|
||||
|
||||
if (jsonString == null || jsonString.isEmpty) {
|
||||
return {'messageCount': 0, 'storageSizeBytes': 0, 'storageSizeKB': 0};
|
||||
@@ -278,7 +371,12 @@ class MessageStorageService {
|
||||
}
|
||||
|
||||
/// Convert Message to JSON
|
||||
Map<String, dynamic> _messageToJson(Message message) {
|
||||
Map<String, dynamic> _messageToJson(
|
||||
Message message, {
|
||||
MessageReceptionDetails? receptionDetails,
|
||||
}) {
|
||||
final persistedPathBytes =
|
||||
message.pathBytes ?? _pathBytesFromSnapshot(receptionDetails);
|
||||
return {
|
||||
'id': message.id,
|
||||
'messageType': message.messageType.name,
|
||||
@@ -287,12 +385,16 @@ class MessageStorageService {
|
||||
: null,
|
||||
'channelIdx': message.channelIdx,
|
||||
'pathLen': message.pathLen,
|
||||
'pathBytes': persistedPathBytes?.toList(),
|
||||
'textType': message.textType.value,
|
||||
'senderTimestamp': message.senderTimestamp,
|
||||
'text': message.text,
|
||||
'isSarMarker': message.isSarMarker,
|
||||
'sarGpsLat': message.sarGpsCoordinates?.latitude,
|
||||
'sarGpsLon': message.sarGpsCoordinates?.longitude,
|
||||
'sarCustomMapLat': message.sarCustomMapPoint?.latitude,
|
||||
'sarCustomMapLon': message.sarCustomMapPoint?.longitude,
|
||||
'sarCustomMapId': message.sarCustomMapId,
|
||||
'sarNotes': message.sarNotes,
|
||||
'sarCustomEmoji': message.sarCustomEmoji,
|
||||
'sarColorIndex': message.sarColorIndex,
|
||||
@@ -338,9 +440,74 @@ class MessageStorageService {
|
||||
},
|
||||
)
|
||||
.toList(),
|
||||
if (receptionDetails != null)
|
||||
_embeddedReceptionDetailsKey: receptionDetails.toJson(),
|
||||
if (receptionDetails?.pathBytes case final pathBytes?)
|
||||
_legacyPathBytesKey: List<int>.from(pathBytes),
|
||||
};
|
||||
}
|
||||
|
||||
Future<Map<String, MessageReceptionDetails>> _loadEmbeddedReceptionDetails({
|
||||
String? namespace,
|
||||
}) async {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
final jsonString = prefs.getString(
|
||||
_key(_messagesKey, namespace: namespace),
|
||||
);
|
||||
if (jsonString == null || jsonString.isEmpty) {
|
||||
return const {};
|
||||
}
|
||||
|
||||
final decoded = jsonDecode(jsonString);
|
||||
if (decoded is! List) {
|
||||
return const {};
|
||||
}
|
||||
|
||||
final result = <String, MessageReceptionDetails>{};
|
||||
for (final entry in decoded) {
|
||||
if (entry is! Map<String, dynamic>) continue;
|
||||
final messageId = entry['id'];
|
||||
final embedded = entry[_embeddedReceptionDetailsKey];
|
||||
if (messageId is! String || embedded is! Map<String, dynamic>) continue;
|
||||
final snapshot = MessageReceptionDetails.fromJson(embedded);
|
||||
if (snapshot == null) continue;
|
||||
result[messageId] = snapshot;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
Future<Map<String, List<int>>> _loadLegacyPathBytesFromMessages({
|
||||
String? namespace,
|
||||
}) async {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
final jsonString = prefs.getString(
|
||||
_key(_messagesKey, namespace: namespace),
|
||||
);
|
||||
if (jsonString == null || jsonString.isEmpty) {
|
||||
return const {};
|
||||
}
|
||||
|
||||
final decoded = jsonDecode(jsonString);
|
||||
if (decoded is! List) {
|
||||
return const {};
|
||||
}
|
||||
|
||||
final result = <String, List<int>>{};
|
||||
for (final entry in decoded) {
|
||||
if (entry is! Map<String, dynamic>) continue;
|
||||
final messageId = entry['id'];
|
||||
final pathBytes = entry[_legacyPathBytesKey];
|
||||
if (messageId is! String || pathBytes is! List) continue;
|
||||
final normalized = pathBytes
|
||||
.whereType<num>()
|
||||
.map((b) => b.toInt())
|
||||
.toList();
|
||||
if (normalized.isEmpty) continue;
|
||||
result[messageId] = normalized;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/// Convert JSON to Message
|
||||
Message? _messageFromJson(Map<String, dynamic> json) {
|
||||
try {
|
||||
@@ -357,14 +524,33 @@ class MessageStorageService {
|
||||
: null,
|
||||
channelIdx: json['channelIdx'] as int?,
|
||||
pathLen: json['pathLen'] as int,
|
||||
pathBytes: json['pathBytes'] is List
|
||||
? Uint8List.fromList(
|
||||
(json['pathBytes'] as List<dynamic>)
|
||||
.whereType<num>()
|
||||
.map((b) => b.toInt())
|
||||
.toList(),
|
||||
)
|
||||
: null,
|
||||
textType: MessageTextType.fromValue(json['textType'] as int),
|
||||
senderTimestamp: json['senderTimestamp'] as int,
|
||||
text: json['text'] as String,
|
||||
isSarMarker: json['isSarMarker'] as bool? ?? false,
|
||||
sarGpsCoordinates:
|
||||
json['sarGpsLat'] != null && json['sarGpsLon'] != null
|
||||
? LatLng(json['sarGpsLat'] as double, json['sarGpsLon'] as double)
|
||||
? LatLng(
|
||||
(json['sarGpsLat'] as num).toDouble(),
|
||||
(json['sarGpsLon'] as num).toDouble(),
|
||||
)
|
||||
: null,
|
||||
sarCustomMapPoint:
|
||||
json['sarCustomMapLat'] != null && json['sarCustomMapLon'] != null
|
||||
? LatLng(
|
||||
(json['sarCustomMapLat'] as num).toDouble(),
|
||||
(json['sarCustomMapLon'] as num).toDouble(),
|
||||
)
|
||||
: null,
|
||||
sarCustomMapId: json['sarCustomMapId'] as String?,
|
||||
sarNotes: json['sarNotes'] as String?,
|
||||
sarCustomEmoji: json['sarCustomEmoji'] as String?,
|
||||
sarColorIndex: json['sarColorIndex'] as int?,
|
||||
@@ -454,4 +640,12 @@ class MessageStorageService {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
Uint8List? _pathBytesFromSnapshot(MessageReceptionDetails? receptionDetails) {
|
||||
final pathBytes = receptionDetails?.pathBytes;
|
||||
if (pathBytes == null || pathBytes.isEmpty) {
|
||||
return null;
|
||||
}
|
||||
return Uint8List.fromList(pathBytes);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import 'profiles_feature_service.dart';
|
||||
|
||||
class MessagingRoutePreferences {
|
||||
static const bool defaultAutoRouteRotationEnabled = false;
|
||||
@@ -14,33 +15,49 @@ class MessagingRoutePreferences {
|
||||
|
||||
static Future<bool> getAutoRouteRotationEnabled() async {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
return prefs.getBool(_autoRouteRotationKey) ??
|
||||
return prefs.getBool(
|
||||
ProfileStorageScope.scopedKey(_autoRouteRotationKey),
|
||||
) ??
|
||||
defaultAutoRouteRotationEnabled;
|
||||
}
|
||||
|
||||
static Future<void> setAutoRouteRotationEnabled(bool enabled) async {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
await prefs.setBool(_autoRouteRotationKey, enabled);
|
||||
await prefs.setBool(
|
||||
ProfileStorageScope.scopedKey(_autoRouteRotationKey),
|
||||
enabled,
|
||||
);
|
||||
}
|
||||
|
||||
static Future<bool> getClearPathOnMaxRetry() async {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
return prefs.getBool(_clearPathOnMaxRetryKey) ?? defaultClearPathOnMaxRetry;
|
||||
return prefs.getBool(
|
||||
ProfileStorageScope.scopedKey(_clearPathOnMaxRetryKey),
|
||||
) ??
|
||||
defaultClearPathOnMaxRetry;
|
||||
}
|
||||
|
||||
static Future<void> setClearPathOnMaxRetry(bool enabled) async {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
await prefs.setBool(_clearPathOnMaxRetryKey, enabled);
|
||||
await prefs.setBool(
|
||||
ProfileStorageScope.scopedKey(_clearPathOnMaxRetryKey),
|
||||
enabled,
|
||||
);
|
||||
}
|
||||
|
||||
static Future<bool> getNearestRelayFallbackEnabled() async {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
return prefs.getBool(_nearestRelayFallbackKey) ??
|
||||
return prefs.getBool(
|
||||
ProfileStorageScope.scopedKey(_nearestRelayFallbackKey),
|
||||
) ??
|
||||
defaultNearestRelayFallbackEnabled;
|
||||
}
|
||||
|
||||
static Future<void> setNearestRelayFallbackEnabled(bool enabled) async {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
await prefs.setBool(_nearestRelayFallbackKey, enabled);
|
||||
await prefs.setBool(
|
||||
ProfileStorageScope.scopedKey(_nearestRelayFallbackKey),
|
||||
enabled,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import 'package:timezone/data/latest_all.dart' as tz;
|
||||
import '../models/sar_marker.dart';
|
||||
import '../l10n/app_localizations.dart';
|
||||
@@ -13,14 +14,28 @@ class NotificationService {
|
||||
|
||||
final FlutterLocalNotificationsPlugin _notificationsPlugin =
|
||||
FlutterLocalNotificationsPlugin();
|
||||
static const String _prefMessagesEnabled = 'notifications_messages_enabled';
|
||||
static const String _prefSarEnabled = 'notifications_sar_enabled';
|
||||
static const String _prefDiscoveryEnabled = 'notifications_discovery_enabled';
|
||||
static const String _prefUpdatesEnabled = 'notifications_updates_enabled';
|
||||
static const String _prefMuteForeground = 'notifications_mute_foreground';
|
||||
|
||||
bool _isInitialized = false;
|
||||
bool _permissionGranted = false;
|
||||
bool _messageNotificationsEnabled = true;
|
||||
bool _sarNotificationsEnabled = true;
|
||||
bool _discoveryNotificationsEnabled = true;
|
||||
bool _updateNotificationsEnabled = true;
|
||||
bool _muteForegroundNotifications = true;
|
||||
AppLifecycleState _lifecycleState = AppLifecycleState.resumed;
|
||||
String? _launchPayload;
|
||||
|
||||
// Notification IDs
|
||||
static const int _sarNotificationId = 1000;
|
||||
static const int _messageNotificationId = 2000;
|
||||
static const int _updateNotificationId = 3000;
|
||||
static const int _batteryNotificationId = 4000;
|
||||
static const int _discoveryNotificationId = 5000;
|
||||
|
||||
// Notification channels
|
||||
static const String _urgentChannelId = 'sar_urgent';
|
||||
@@ -38,12 +53,32 @@ class NotificationService {
|
||||
static const String _updateChannelDescription =
|
||||
'Notifications for available app updates';
|
||||
|
||||
static const String _batteryChannelId = 'battery_alerts';
|
||||
static const String _batteryChannelName = 'Battery Alerts';
|
||||
static const String _batteryChannelDescription =
|
||||
'Notifications when a device or contact battery is low';
|
||||
|
||||
static const String _discoveryChannelId = 'discovery_alerts';
|
||||
static const String _discoveryChannelName = 'Discovery Alerts';
|
||||
static const String _discoveryChannelDescription =
|
||||
'Notifications when new contacts are discovered';
|
||||
|
||||
bool get messageNotificationsEnabled => _messageNotificationsEnabled;
|
||||
bool get sarNotificationsEnabled => _sarNotificationsEnabled;
|
||||
bool get discoveryNotificationsEnabled => _discoveryNotificationsEnabled;
|
||||
bool get updateNotificationsEnabled => _updateNotificationsEnabled;
|
||||
bool get muteForegroundNotifications => _muteForegroundNotifications;
|
||||
bool get isAppInForeground => _lifecycleState == AppLifecycleState.resumed;
|
||||
|
||||
/// Initialize notification service
|
||||
Future<void> initialize() async {
|
||||
if (_isInitialized) return;
|
||||
|
||||
try {
|
||||
debugPrint('📬 [NotificationService] Initializing...');
|
||||
WidgetsBinding.instance.addObserver(_LifecycleObserver(this));
|
||||
_lifecycleState =
|
||||
WidgetsBinding.instance.lifecycleState ?? AppLifecycleState.resumed;
|
||||
|
||||
// Initialize timezone data
|
||||
tz.initializeTimeZones();
|
||||
@@ -72,8 +107,15 @@ class NotificationService {
|
||||
onDidReceiveNotificationResponse: _onNotificationResponse,
|
||||
);
|
||||
|
||||
final launchDetails = await _notificationsPlugin
|
||||
.getNotificationAppLaunchDetails();
|
||||
if (launchDetails?.didNotificationLaunchApp ?? false) {
|
||||
_launchPayload = launchDetails?.notificationResponse?.payload;
|
||||
}
|
||||
|
||||
// Request permissions
|
||||
await _requestPermissions();
|
||||
await _loadPreferences();
|
||||
|
||||
// Create notification channels (Android)
|
||||
await _createNotificationChannels();
|
||||
@@ -132,6 +174,54 @@ class NotificationService {
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _loadPreferences() async {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
_messageNotificationsEnabled = prefs.getBool(_prefMessagesEnabled) ?? true;
|
||||
_sarNotificationsEnabled = prefs.getBool(_prefSarEnabled) ?? true;
|
||||
_discoveryNotificationsEnabled =
|
||||
prefs.getBool(_prefDiscoveryEnabled) ?? true;
|
||||
_updateNotificationsEnabled = prefs.getBool(_prefUpdatesEnabled) ?? true;
|
||||
_muteForegroundNotifications = prefs.getBool(_prefMuteForeground) ?? true;
|
||||
}
|
||||
|
||||
Future<void> setMessageNotificationsEnabled(bool value) async {
|
||||
_messageNotificationsEnabled = value;
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
await prefs.setBool(_prefMessagesEnabled, value);
|
||||
}
|
||||
|
||||
Future<void> setSarNotificationsEnabled(bool value) async {
|
||||
_sarNotificationsEnabled = value;
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
await prefs.setBool(_prefSarEnabled, value);
|
||||
}
|
||||
|
||||
Future<void> setDiscoveryNotificationsEnabled(bool value) async {
|
||||
_discoveryNotificationsEnabled = value;
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
await prefs.setBool(_prefDiscoveryEnabled, value);
|
||||
}
|
||||
|
||||
Future<void> setUpdateNotificationsEnabled(bool value) async {
|
||||
_updateNotificationsEnabled = value;
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
await prefs.setBool(_prefUpdatesEnabled, value);
|
||||
}
|
||||
|
||||
Future<void> setMuteForegroundNotifications(bool value) async {
|
||||
_muteForegroundNotifications = value;
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
await prefs.setBool(_prefMuteForeground, value);
|
||||
}
|
||||
|
||||
void handleAppLifecycleStateChanged(AppLifecycleState state) {
|
||||
_lifecycleState = state;
|
||||
}
|
||||
|
||||
bool _shouldSuppressForegroundNotifications() {
|
||||
return _muteForegroundNotifications && isAppInForeground;
|
||||
}
|
||||
|
||||
/// Create notification channels for Android
|
||||
Future<void> _createNotificationChannels() async {
|
||||
try {
|
||||
@@ -177,9 +267,31 @@ class NotificationService {
|
||||
showBadge: true,
|
||||
);
|
||||
|
||||
const batteryChannel = AndroidNotificationChannel(
|
||||
_batteryChannelId,
|
||||
_batteryChannelName,
|
||||
description: _batteryChannelDescription,
|
||||
importance: Importance.high,
|
||||
playSound: true,
|
||||
enableVibration: true,
|
||||
showBadge: true,
|
||||
);
|
||||
|
||||
const discoveryChannel = AndroidNotificationChannel(
|
||||
_discoveryChannelId,
|
||||
_discoveryChannelName,
|
||||
description: _discoveryChannelDescription,
|
||||
importance: Importance.high,
|
||||
playSound: true,
|
||||
enableVibration: true,
|
||||
showBadge: true,
|
||||
);
|
||||
|
||||
await androidPlugin.createNotificationChannel(urgentChannel);
|
||||
await androidPlugin.createNotificationChannel(messagesChannel);
|
||||
await androidPlugin.createNotificationChannel(updateChannel);
|
||||
await androidPlugin.createNotificationChannel(batteryChannel);
|
||||
await androidPlugin.createNotificationChannel(discoveryChannel);
|
||||
debugPrint('✅ [NotificationService] Created notification channels');
|
||||
} catch (e) {
|
||||
debugPrint('⚠️ [NotificationService] Error creating channels: $e');
|
||||
@@ -201,6 +313,12 @@ class NotificationService {
|
||||
}
|
||||
}
|
||||
|
||||
String? consumeLaunchPayload() {
|
||||
final payload = _launchPayload;
|
||||
_launchPayload = null;
|
||||
return payload;
|
||||
}
|
||||
|
||||
/// Show urgent notification for SAR marker
|
||||
Future<void> showSarNotification({
|
||||
required SarMarkerType type,
|
||||
@@ -222,6 +340,16 @@ class NotificationService {
|
||||
);
|
||||
return;
|
||||
}
|
||||
if (!_sarNotificationsEnabled) {
|
||||
debugPrint('ℹ️ [NotificationService] SAR notifications disabled');
|
||||
return;
|
||||
}
|
||||
if (_shouldSuppressForegroundNotifications()) {
|
||||
debugPrint(
|
||||
'ℹ️ [NotificationService] App in foreground, skipping SAR notification',
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// Generate unique notification ID based on timestamp
|
||||
@@ -394,6 +522,16 @@ class NotificationService {
|
||||
);
|
||||
return;
|
||||
}
|
||||
if (!_messageNotificationsEnabled) {
|
||||
debugPrint('ℹ️ [NotificationService] Message notifications disabled');
|
||||
return;
|
||||
}
|
||||
if (_shouldSuppressForegroundNotifications()) {
|
||||
debugPrint(
|
||||
'ℹ️ [NotificationService] App in foreground, skipping message notification',
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// Generate unique notification ID based on timestamp
|
||||
@@ -402,10 +540,13 @@ class NotificationService {
|
||||
(DateTime.now().millisecondsSinceEpoch % 1000);
|
||||
|
||||
// Build notification title and body
|
||||
final resolvedChannelName = channelName?.trim().isNotEmpty == true
|
||||
? channelName!.trim()
|
||||
: (localizations?.publicChannel ?? 'Public');
|
||||
final title = isChannelMessage
|
||||
? (localizations != null
|
||||
? '${localizations.channel}: ${channelName ?? "Public"}'
|
||||
: 'Channel: ${channelName ?? "Public"}')
|
||||
? '${localizations.channel}: $resolvedChannelName'
|
||||
: 'Channel: $resolvedChannelName')
|
||||
: (localizations != null
|
||||
? '${localizations.newMessage} ${localizations.from} $senderName'
|
||||
: 'New message from $senderName');
|
||||
@@ -544,13 +685,23 @@ class NotificationService {
|
||||
);
|
||||
return;
|
||||
}
|
||||
if (!_updateNotificationsEnabled) {
|
||||
debugPrint('ℹ️ [NotificationService] Update notifications disabled');
|
||||
return;
|
||||
}
|
||||
if (_shouldSuppressForegroundNotifications()) {
|
||||
debugPrint(
|
||||
'ℹ️ [NotificationService] App in foreground, skipping update notification',
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// Build notification title and body
|
||||
final title = localizations?.updateAvailable ?? 'App Update Available';
|
||||
final body = localizations != null
|
||||
? '${localizations.currentVersion}: $currentVersion\n'
|
||||
'${localizations.latestVersion}: $latestVersion'
|
||||
'${localizations.latestVersion}: $latestVersion'
|
||||
: 'Current: $currentVersion\nLatest: $latestVersion';
|
||||
|
||||
// Android notification details
|
||||
@@ -614,4 +765,169 @@ class NotificationService {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Future<bool> showLowBatteryNotification({
|
||||
required String nodeId,
|
||||
required String nodeName,
|
||||
required double batteryPercent,
|
||||
required bool isCurrentDevice,
|
||||
}) async {
|
||||
if (!_isInitialized) {
|
||||
debugPrint(
|
||||
'⚠️ [NotificationService] Not initialized, skipping notification',
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!_permissionGranted) {
|
||||
debugPrint(
|
||||
'⚠️ [NotificationService] Permission not granted, skipping notification',
|
||||
);
|
||||
return false;
|
||||
}
|
||||
if (!_messageNotificationsEnabled) {
|
||||
debugPrint('ℹ️ [NotificationService] Message notifications disabled');
|
||||
return false;
|
||||
}
|
||||
if (_shouldSuppressForegroundNotifications()) {
|
||||
debugPrint(
|
||||
'ℹ️ [NotificationService] App in foreground, skipping low battery notification',
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
final roundedPercent = batteryPercent.round().clamp(0, 100);
|
||||
final title = isCurrentDevice
|
||||
? 'Device battery low'
|
||||
: 'Contact battery low';
|
||||
final body = isCurrentDevice
|
||||
? '$nodeName battery is at $roundedPercent%.'
|
||||
: '$nodeName is at $roundedPercent% battery.';
|
||||
final notificationId =
|
||||
_batteryNotificationId + ((nodeId.hashCode & 0x7fffffff) % 1000);
|
||||
|
||||
try {
|
||||
final androidDetails = AndroidNotificationDetails(
|
||||
_batteryChannelId,
|
||||
_batteryChannelName,
|
||||
channelDescription: _batteryChannelDescription,
|
||||
importance: Importance.high,
|
||||
priority: Priority.high,
|
||||
ticker: title,
|
||||
playSound: true,
|
||||
enableVibration: true,
|
||||
showWhen: true,
|
||||
when: DateTime.now().millisecondsSinceEpoch,
|
||||
styleInformation: BigTextStyleInformation(
|
||||
body,
|
||||
contentTitle: title,
|
||||
summaryText: '$roundedPercent%',
|
||||
),
|
||||
);
|
||||
|
||||
final darwinDetails = DarwinNotificationDetails(
|
||||
presentAlert: true,
|
||||
presentBadge: true,
|
||||
presentSound: true,
|
||||
sound: 'default',
|
||||
threadIdentifier: 'battery_alerts',
|
||||
subtitle: '$roundedPercent%',
|
||||
);
|
||||
|
||||
final notificationDetails = NotificationDetails(
|
||||
android: androidDetails,
|
||||
iOS: darwinDetails,
|
||||
);
|
||||
|
||||
await _notificationsPlugin.show(
|
||||
id: notificationId,
|
||||
title: title,
|
||||
body: body,
|
||||
notificationDetails: notificationDetails,
|
||||
payload: 'battery:$nodeId',
|
||||
);
|
||||
|
||||
debugPrint(
|
||||
'✅ [NotificationService] Showed low battery notification for $nodeName ($roundedPercent%)',
|
||||
);
|
||||
return true;
|
||||
} catch (e) {
|
||||
debugPrint(
|
||||
'❌ [NotificationService] Error showing low battery notification: $e',
|
||||
);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
Future<bool> showContactDiscoveredNotification({
|
||||
required String contactKey,
|
||||
String? contactName,
|
||||
}) async {
|
||||
if (!_isInitialized) return false;
|
||||
if (!_permissionGranted) return false;
|
||||
if (!_discoveryNotificationsEnabled) return false;
|
||||
if (_shouldSuppressForegroundNotifications()) return false;
|
||||
|
||||
final shortKey = contactKey.length > 12
|
||||
? contactKey.substring(0, 12).toUpperCase()
|
||||
: contactKey.toUpperCase();
|
||||
final title = 'New contact discovered';
|
||||
final resolvedName = contactName?.trim();
|
||||
final body = resolvedName != null && resolvedName.isNotEmpty
|
||||
? '$resolvedName is available in Discovery.'
|
||||
: 'New contact $shortKey is available in Discovery.';
|
||||
final notificationId =
|
||||
_discoveryNotificationId + ((contactKey.hashCode & 0x7fffffff) % 1000);
|
||||
|
||||
try {
|
||||
final androidDetails = AndroidNotificationDetails(
|
||||
_discoveryChannelId,
|
||||
_discoveryChannelName,
|
||||
channelDescription: _discoveryChannelDescription,
|
||||
importance: Importance.high,
|
||||
priority: Priority.high,
|
||||
ticker: title,
|
||||
playSound: true,
|
||||
enableVibration: true,
|
||||
showWhen: true,
|
||||
when: DateTime.now().millisecondsSinceEpoch,
|
||||
);
|
||||
|
||||
final darwinDetails = DarwinNotificationDetails(
|
||||
presentAlert: true,
|
||||
presentBadge: true,
|
||||
presentSound: true,
|
||||
sound: 'default',
|
||||
threadIdentifier: 'discovery_alerts',
|
||||
);
|
||||
|
||||
await _notificationsPlugin.show(
|
||||
id: notificationId,
|
||||
title: title,
|
||||
body: body,
|
||||
notificationDetails: NotificationDetails(
|
||||
android: androidDetails,
|
||||
iOS: darwinDetails,
|
||||
),
|
||||
payload: 'discovery:$contactKey',
|
||||
);
|
||||
return true;
|
||||
} catch (e) {
|
||||
debugPrint(
|
||||
'❌ [NotificationService] Error showing discovery notification: $e',
|
||||
);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class _LifecycleObserver with WidgetsBindingObserver {
|
||||
_LifecycleObserver(this._service);
|
||||
|
||||
final NotificationService _service;
|
||||
|
||||
@override
|
||||
void didChangeAppLifecycleState(AppLifecycleState state) {
|
||||
_service.handleAppLifecycleStateChanged(state);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -61,6 +61,9 @@ class PacketCaptureStorageService {
|
||||
File? _file;
|
||||
|
||||
Future<File> _resolveFile() async {
|
||||
if (kIsWeb) {
|
||||
throw UnsupportedError('Packet capture file storage is not supported on web');
|
||||
}
|
||||
if (_file != null) return _file!;
|
||||
final dir = await getApplicationSupportDirectory();
|
||||
final file = File('${dir.path}/$_fileName');
|
||||
@@ -73,6 +76,7 @@ class PacketCaptureStorageService {
|
||||
|
||||
Future<void> appendLogs(List<BlePacketLog> logs) async {
|
||||
if (logs.isEmpty) return;
|
||||
if (kIsWeb) return;
|
||||
try {
|
||||
final file = await _resolveFile();
|
||||
final sink = file.openWrite(mode: FileMode.append);
|
||||
@@ -98,6 +102,7 @@ class PacketCaptureStorageService {
|
||||
}
|
||||
|
||||
Future<List<StoredPacketCapture>> loadRecent({int limit = 500}) async {
|
||||
if (kIsWeb) return const [];
|
||||
try {
|
||||
final file = await _resolveFile();
|
||||
if (!await file.exists()) return const [];
|
||||
@@ -116,6 +121,7 @@ class PacketCaptureStorageService {
|
||||
}
|
||||
|
||||
Future<int> count() async {
|
||||
if (kIsWeb) return 0;
|
||||
try {
|
||||
final file = await _resolveFile();
|
||||
if (!await file.exists()) return 0;
|
||||
@@ -127,6 +133,7 @@ class PacketCaptureStorageService {
|
||||
}
|
||||
|
||||
Future<void> clear() async {
|
||||
if (kIsWeb) return;
|
||||
try {
|
||||
final file = await _resolveFile();
|
||||
if (await file.exists()) {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user