mirror of
https://github.com/dz0ny/meshcore-sar.git
synced 2026-08-12 00:40:28 +00:00
Compare commits
14 Commits
v2026.0310
...
v2026.0311
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
99bb446ee9 | ||
|
|
088f3d37ef | ||
|
|
7f200e5334 | ||
|
|
ae16417886 | ||
|
|
467371946d | ||
|
|
2d23328ad0 | ||
|
|
0dfb84bf4e | ||
|
|
193a193e8a | ||
|
|
c1d1ec7d0c | ||
|
|
b415b63b4b | ||
|
|
818b9dbfb2 | ||
|
|
8085b89c07 | ||
|
|
86771a3c55 | ||
|
|
4b668ac2e9 |
@@ -489,7 +489,7 @@
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CURRENT_PROJECT_VERSION = 107;
|
||||
CURRENT_PROJECT_VERSION = 111;
|
||||
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 = 107;
|
||||
CURRENT_PROJECT_VERSION = 111;
|
||||
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 = 107;
|
||||
CURRENT_PROJECT_VERSION = 111;
|
||||
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 = 107;
|
||||
CURRENT_PROJECT_VERSION = 111;
|
||||
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 = 107;
|
||||
CURRENT_PROJECT_VERSION = 111;
|
||||
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 = 107;
|
||||
CURRENT_PROJECT_VERSION = 111;
|
||||
DEVELOPMENT_TEAM = JND55328G8;
|
||||
ENABLE_BITCODE = NO;
|
||||
INFOPLIST_FILE = Runner/Info.plist;
|
||||
|
||||
@@ -43,7 +43,7 @@
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>107</string>
|
||||
<string>111</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.000256">
|
||||
|
||||
</testcase>
|
||||
|
||||
|
||||
<testcase classname="fastlane.lanes" name="1: increment_build_number" time="0.511584">
|
||||
<testcase classname="fastlane.lanes" name="1: increment_build_number" time="0.924905">
|
||||
|
||||
</testcase>
|
||||
|
||||
|
||||
<testcase classname="fastlane.lanes" name="2: build_app" time="95.467725">
|
||||
<testcase classname="fastlane.lanes" name="2: build_app" time="113.366384">
|
||||
|
||||
</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="755.542625">
|
||||
|
||||
</testcase>
|
||||
|
||||
|
||||
@@ -1586,5 +1586,57 @@
|
||||
"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}"
|
||||
}
|
||||
|
||||
@@ -2251,5 +2251,57 @@
|
||||
"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}"
|
||||
}
|
||||
|
||||
@@ -2301,5 +2301,246 @@
|
||||
},
|
||||
"requestingVoice": "Requesting voice",
|
||||
"@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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1586,5 +1586,57 @@
|
||||
"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}"
|
||||
}
|
||||
|
||||
@@ -1589,5 +1589,57 @@
|
||||
"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}"
|
||||
}
|
||||
|
||||
@@ -511,5 +511,57 @@
|
||||
"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}"
|
||||
}
|
||||
|
||||
@@ -1589,5 +1589,57 @@
|
||||
"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}"
|
||||
}
|
||||
|
||||
@@ -3203,6 +3203,318 @@ abstract class AppLocalizations {
|
||||
/// In en, this message translates to:
|
||||
/// **'Requesting voice'**
|
||||
String get requestingVoice;
|
||||
|
||||
/// Generic device label
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'device'**
|
||||
String get device;
|
||||
|
||||
/// Generic change action
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Change'**
|
||||
String get change;
|
||||
|
||||
/// Updated onboarding overview description
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'This app combines MeshCore messaging, SAR field updates, mapping, and device tools in one place.'**
|
||||
String get wizardOverviewDescription;
|
||||
|
||||
/// Updated onboarding overview feature 1
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Send direct, room, and channel messages from the main Messages tab.'**
|
||||
String get wizardOverviewFeature1;
|
||||
|
||||
/// Updated onboarding overview feature 2
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Share SAR markers, map drawings, voice clips, and images over the mesh.'**
|
||||
String get wizardOverviewFeature2;
|
||||
|
||||
/// Updated onboarding overview feature 3
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Connect over BLE or TCP, then manage the companion radio from inside the app.'**
|
||||
String get wizardOverviewFeature3;
|
||||
|
||||
/// Updated onboarding messaging page title
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Messaging and Field Reports'**
|
||||
String get wizardMessagingTitle;
|
||||
|
||||
/// Updated onboarding messaging page description
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Messages are more than plain text here. The app already supports several operational payloads and transfer workflows.'**
|
||||
String get wizardMessagingDescription;
|
||||
|
||||
/// Updated onboarding messaging feature 1
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Send direct messages, room posts, and channel traffic from one composer.'**
|
||||
String get wizardMessagingFeature1;
|
||||
|
||||
/// Updated onboarding messaging feature 2
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Create SAR updates and reusable SAR templates for common field reports.'**
|
||||
String get wizardMessagingFeature2;
|
||||
|
||||
/// Updated onboarding messaging feature 3
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Transfer voice sessions and images, with progress and airtime estimates in the UI.'**
|
||||
String get wizardMessagingFeature3;
|
||||
|
||||
/// Onboarding connect device page title
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Connect device'**
|
||||
String get wizardConnectDeviceTitle;
|
||||
|
||||
/// Onboarding connect device page description
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Connect your MeshCore radio, choose a name, and apply a radio preset before continuing.'**
|
||||
String get wizardConnectDeviceDescription;
|
||||
|
||||
/// Onboarding setup badge label
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Setup'**
|
||||
String get wizardSetupBadge;
|
||||
|
||||
/// Onboarding overview badge label
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Overview'**
|
||||
String get wizardOverviewBadge;
|
||||
|
||||
/// Onboarding connection status when connected
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Connected to {deviceName}'**
|
||||
String wizardConnectedToDevice(String deviceName);
|
||||
|
||||
/// Onboarding connection status when disconnected
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'No device connected yet'**
|
||||
String get wizardNoDeviceConnected;
|
||||
|
||||
/// Onboarding skip current setup step action
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Skip for now'**
|
||||
String get wizardSkipForNow;
|
||||
|
||||
/// Onboarding device name field label
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Device name'**
|
||||
String get wizardDeviceNameLabel;
|
||||
|
||||
/// Onboarding device name field helper text
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'This name is advertised to other MeshCore users.'**
|
||||
String get wizardDeviceNameHelp;
|
||||
|
||||
/// Onboarding config region field label
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Config region'**
|
||||
String get wizardConfigRegionLabel;
|
||||
|
||||
/// Onboarding config region helper text
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Uses the full official MeshCore preset list. Default is EU/UK (Narrow).'**
|
||||
String get wizardConfigRegionHelp;
|
||||
|
||||
/// Onboarding preset note 1
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Make sure the selected preset matches your local radio regulations.'**
|
||||
String get wizardPresetNote1;
|
||||
|
||||
/// Onboarding preset note 2
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'The list matches the official MeshCore config tool preset feed.'**
|
||||
String get wizardPresetNote2;
|
||||
|
||||
/// Onboarding preset note 3
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'EU/UK (Narrow) stays selected by default for onboarding.'**
|
||||
String get wizardPresetNote3;
|
||||
|
||||
/// Onboarding saving button label
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Saving...'**
|
||||
String get wizardSaving;
|
||||
|
||||
/// Onboarding save and continue button label
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Save and continue'**
|
||||
String get wizardSaveAndContinue;
|
||||
|
||||
/// Onboarding validation when device name is missing
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Enter a device name before continuing.'**
|
||||
String get wizardEnterDeviceName;
|
||||
|
||||
/// Onboarding success message after saving device setup
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Saved {deviceName} with {presetName}.'**
|
||||
String wizardDeviceSetupSaved(String deviceName, String presetName);
|
||||
|
||||
/// Updated onboarding network page title
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Contacts, Rooms, and Repeaters'**
|
||||
String get wizardNetworkTitle;
|
||||
|
||||
/// Updated onboarding network page description
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'The Contacts tab organizes the network you discover and the routes you learn over time.'**
|
||||
String get wizardNetworkDescription;
|
||||
|
||||
/// Updated onboarding network feature 1
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Review team members, repeaters, rooms, channels, and pending adverts in one list.'**
|
||||
String get wizardNetworkFeature1;
|
||||
|
||||
/// Updated onboarding network feature 2
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Use smart ping, room login, learned paths, and route reset tools when connectivity gets messy.'**
|
||||
String get wizardNetworkFeature2;
|
||||
|
||||
/// Updated onboarding network feature 3
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Create channels and manage network destinations without leaving the app.'**
|
||||
String get wizardNetworkFeature3;
|
||||
|
||||
/// Updated onboarding map page title
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Map, Trails, and Shared Geometry'**
|
||||
String get wizardMapOpsTitle;
|
||||
|
||||
/// Updated onboarding map page description
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'The app map is tied directly into messaging, tracking, and SAR overlays instead of being a separate viewer.'**
|
||||
String get wizardMapOpsDescription;
|
||||
|
||||
/// Updated onboarding map feature 1
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Track your own position, teammate locations, and movement trails on the map.'**
|
||||
String get wizardMapOpsFeature1;
|
||||
|
||||
/// Updated onboarding map feature 2
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Open drawings from messages, preview them inline, and remove them from the map when needed.'**
|
||||
String get wizardMapOpsFeature2;
|
||||
|
||||
/// Updated onboarding map feature 3
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Use repeater map views and shared overlays to understand network reach in the field.'**
|
||||
String get wizardMapOpsFeature3;
|
||||
|
||||
/// Updated onboarding tools page title
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Tools Beyond Messaging'**
|
||||
String get wizardToolsTitle;
|
||||
|
||||
/// Updated onboarding tools page description
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'There is more here than the four main tabs. The app also includes configuration, diagnostics, and optional sensor workflows.'**
|
||||
String get wizardToolsDescription;
|
||||
|
||||
/// Updated onboarding tools feature 1
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Open device config to change radio settings, telemetry, TX power, and companion details.'**
|
||||
String get wizardToolsFeature1;
|
||||
|
||||
/// Updated onboarding tools feature 2
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Enable the Sensors tab when you want watched sensor dashboards and quick refresh actions.'**
|
||||
String get wizardToolsFeature2;
|
||||
|
||||
/// Updated onboarding tools feature 3
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Use packet logs, spectrum scan, and developer diagnostics when troubleshooting the mesh.'**
|
||||
String get wizardToolsFeature3;
|
||||
|
||||
/// Contact actions label when already in sensors
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'In Sensors'**
|
||||
String get contactInSensors;
|
||||
|
||||
/// Contact actions label to add to sensors
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Add to Sensors'**
|
||||
String get contactAddToSensors;
|
||||
|
||||
/// Contact actions label to set route path
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Set path'**
|
||||
String get contactSetPath;
|
||||
|
||||
/// Snackbar after adding contact to sensors
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'{contactName} added to Sensors'**
|
||||
String contactAddedToSensors(String contactName);
|
||||
|
||||
/// Error when clearing a contact route fails
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Failed to clear route: {error}'**
|
||||
String contactFailedToClearRoute(String error);
|
||||
|
||||
/// Snackbar after route is cleared
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Route cleared'**
|
||||
String get contactRouteCleared;
|
||||
|
||||
/// Snackbar after route is set
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Route set: {route}'**
|
||||
String contactRouteSet(String route);
|
||||
|
||||
/// Error when setting a contact route fails
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Failed to set route: {error}'**
|
||||
String contactFailedToSetRoute(String error);
|
||||
}
|
||||
|
||||
class _AppLocalizationsDelegate
|
||||
|
||||
@@ -1734,4 +1734,199 @@ 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';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1741,4 +1741,199 @@ 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';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1708,4 +1708,198 @@ 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';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1736,4 +1736,200 @@ 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';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1740,4 +1740,200 @@ 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';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1718,4 +1718,198 @@ 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';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1732,4 +1732,199 @@ 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';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1729,4 +1729,199 @@ 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';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1736,4 +1736,199 @@ 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';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1726,4 +1726,199 @@ 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';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1718,4 +1718,198 @@ 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';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1720,4 +1720,198 @@ 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';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1732,4 +1732,199 @@ 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';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1656,4 +1656,175 @@ 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';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2276,5 +2276,57 @@
|
||||
"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}"
|
||||
}
|
||||
|
||||
@@ -2276,5 +2276,57 @@
|
||||
"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}"
|
||||
}
|
||||
|
||||
@@ -511,5 +511,57 @@
|
||||
"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}"
|
||||
}
|
||||
|
||||
@@ -512,4 +512,57 @@
|
||||
"cannotRetryMissingRecipient": "Ponovnega pošiljanja ni mogoče: informacije o prejemniku manjkajo",
|
||||
"voiceUnavailable": "Glas trenutno ni na voljo",
|
||||
"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}"
|
||||
}
|
||||
|
||||
@@ -2276,5 +2276,57 @@
|
||||
"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}"
|
||||
}
|
||||
|
||||
@@ -2276,5 +2276,57 @@
|
||||
"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}"
|
||||
}
|
||||
|
||||
@@ -2301,5 +2301,57 @@
|
||||
},
|
||||
"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}"
|
||||
}
|
||||
|
||||
@@ -128,6 +128,9 @@ class AppProvider with ChangeNotifier {
|
||||
Timer? _packetCaptureFlushTimer;
|
||||
String? _lastPersistedPacketSignature;
|
||||
bool _isPersistingPacketCapture = false;
|
||||
bool _wasDeviceConnected = false;
|
||||
bool _hasCompletedConnectionBootstrap = false;
|
||||
bool _isReconnectSyncInProgress = false;
|
||||
|
||||
AppProvider({
|
||||
required this.connectionProvider,
|
||||
@@ -139,6 +142,7 @@ class AppProvider with ChangeNotifier {
|
||||
required this.imageProvider,
|
||||
}) {
|
||||
_setupCallbacks();
|
||||
_wasDeviceConnected = connectionProvider.deviceInfo.isConnected;
|
||||
_initializeLocationTracking();
|
||||
_loadMapEnabled();
|
||||
_loadContactsEnabled();
|
||||
@@ -570,8 +574,20 @@ class AppProvider with ChangeNotifier {
|
||||
connectionProvider.addListener(_handleConnectionStateChange);
|
||||
messagesProvider.resolveContactNameCallback =
|
||||
_resolveContactNameForNotification;
|
||||
messagesProvider.resolveChannelNameCallback =
|
||||
channelsProvider.getChannelDisplayName;
|
||||
messagesProvider.resolveChannelNameCallback = (channelIdx) {
|
||||
if (channelIdx == 0) {
|
||||
return 'Public';
|
||||
}
|
||||
|
||||
for (final channel in contactsProvider.channels) {
|
||||
if (channel.publicKey.length > 1 &&
|
||||
channel.publicKey[1] == channelIdx) {
|
||||
return channel.displayName;
|
||||
}
|
||||
}
|
||||
|
||||
return channelsProvider.getChannelDisplayName(channelIdx);
|
||||
};
|
||||
|
||||
voiceProvider.sendRawPacketCallback =
|
||||
({
|
||||
@@ -1659,12 +1675,42 @@ class AppProvider with ChangeNotifier {
|
||||
);
|
||||
messagesProvider.syncDrawingsWithProvider(drawingProvider);
|
||||
|
||||
_hasCompletedConnectionBootstrap = true;
|
||||
_wasDeviceConnected = connectionProvider.deviceInfo.isConnected;
|
||||
notifyListeners();
|
||||
} catch (e) {
|
||||
debugPrint('Initialization error: $e');
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _syncAfterReconnect() async {
|
||||
if (_isReconnectSyncInProgress ||
|
||||
!connectionProvider.deviceInfo.isConnected) {
|
||||
return;
|
||||
}
|
||||
|
||||
_isReconnectSyncInProgress = true;
|
||||
try {
|
||||
debugPrint(
|
||||
'🔄 [AppProvider] Device reconnected - syncing contacts and missed messages',
|
||||
);
|
||||
|
||||
await contactsProvider.initialize(
|
||||
devicePublicKey: connectionProvider.deviceInfo.publicKey,
|
||||
);
|
||||
await connectionProvider.getContacts();
|
||||
|
||||
final messageCount = await connectionProvider.syncAllMessages();
|
||||
debugPrint(
|
||||
'📥 [AppProvider] Reconnect sync retrieved $messageCount message(s)',
|
||||
);
|
||||
} catch (e) {
|
||||
debugPrint('❌ [AppProvider] Reconnect sync error: $e');
|
||||
} finally {
|
||||
_isReconnectSyncInProgress = false;
|
||||
}
|
||||
}
|
||||
|
||||
/// Automatically login to all rooms with saved passwords on cold connect
|
||||
Future<void> _autoLoginToRooms() async {
|
||||
if (!connectionProvider.deviceInfo.isConnected) return;
|
||||
@@ -2637,6 +2683,8 @@ class AppProvider with ChangeNotifier {
|
||||
/// Handle connection state changes to manage location tracking
|
||||
void _handleConnectionStateChange() {
|
||||
final isConnected = connectionProvider.deviceInfo.isConnected;
|
||||
final wasConnected = _wasDeviceConnected;
|
||||
_wasDeviceConnected = isConnected;
|
||||
final wasTracking = locationTrackingService.isTracking;
|
||||
|
||||
// Only stop tracking on disconnect - DON'T start on connect
|
||||
@@ -2648,6 +2696,10 @@ class AppProvider with ChangeNotifier {
|
||||
);
|
||||
_stopLocationTracking();
|
||||
}
|
||||
|
||||
if (isConnected && !wasConnected && _hasCompletedConnectionBootstrap) {
|
||||
unawaited(_syncAfterReconnect());
|
||||
}
|
||||
}
|
||||
|
||||
/// Start location tracking
|
||||
|
||||
@@ -848,10 +848,26 @@ class ConnectionProvider with ChangeNotifier {
|
||||
return true;
|
||||
} catch (e) {
|
||||
debugPrint('❌ [Provider] Failed to check slot $channelIdx: $e');
|
||||
if (_isEmptyChannelQueryError(e)) {
|
||||
debugPrint(
|
||||
' ℹ️ Treating slot $channelIdx as empty because the device reported it was not found',
|
||||
);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool _isEmptyChannelQueryError(Object error) {
|
||||
final message = error.toString().toLowerCase();
|
||||
return message.contains('not found');
|
||||
}
|
||||
|
||||
bool _isChannelRefreshTimeoutError(Object error) {
|
||||
final message = error.toString().toLowerCase();
|
||||
return message.contains('0x1f') && message.contains('timed out');
|
||||
}
|
||||
|
||||
Future<int?> findNextEmptyChannelSlot() async {
|
||||
if (!_activeService.isConnected) {
|
||||
throw Exception('Not connected to device');
|
||||
@@ -862,6 +878,35 @@ class ConnectionProvider with ChangeNotifier {
|
||||
|
||||
// maxChannels from device info, or default to 40
|
||||
final maxChannels = _deviceInfo.maxChannels ?? 40;
|
||||
final maxCustomChannels = maxChannels > 0 ? maxChannels - 1 : 0;
|
||||
|
||||
// Match meshcore-open: trust the current synced channel list first and
|
||||
// pick the first missing slot rather than probing every slot on-device.
|
||||
if (getChannelInfo != null) {
|
||||
final usedIndices = <int>{};
|
||||
for (int i = 1; i < maxChannels; i++) {
|
||||
final channel = getChannelInfo!(i);
|
||||
if (channel == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
final channelName = (channel as dynamic).name as String?;
|
||||
if (channelName != null && channelName.isNotEmpty) {
|
||||
usedIndices.add(i);
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 1; i < maxChannels; i++) {
|
||||
if (!usedIndices.contains(i)) {
|
||||
debugPrint(' ✅ Found empty slot from synced channels: $i');
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
debugPrint(
|
||||
' ⚠️ Synced channels report all custom slots occupied ($maxCustomChannels total)',
|
||||
);
|
||||
}
|
||||
|
||||
// Check each slot starting from 1 (skip 0 = public channel)
|
||||
for (int i = 1; i < maxChannels; i++) {
|
||||
@@ -958,10 +1003,12 @@ class ConnectionProvider with ChangeNotifier {
|
||||
debugPrint(' Using existing slot: $slotIdx (overwrite mode)');
|
||||
} else {
|
||||
// Find next empty slot for new channel
|
||||
final maxChannels = _deviceInfo.maxChannels ?? 40;
|
||||
final maxCustomChannels = maxChannels > 0 ? maxChannels - 1 : 0;
|
||||
final emptySlot = await findNextEmptyChannelSlot();
|
||||
if (emptySlot == null) {
|
||||
throw Exception(
|
||||
'All channel slots are in use (maximum 39 custom channels)',
|
||||
'All channel slots are in use (maximum $maxCustomChannels custom channels)',
|
||||
);
|
||||
}
|
||||
slotIdx = emptySlot;
|
||||
@@ -982,23 +1029,33 @@ class ConnectionProvider with ChangeNotifier {
|
||||
debugPrint(' Secret converted to 16-byte key using MD5');
|
||||
}
|
||||
|
||||
// Send CMD_SET_CHANNEL to radio
|
||||
await _activeService.setChannel(
|
||||
channelIdx: slotIdx,
|
||||
channelName: channelName,
|
||||
secret: secretBytes,
|
||||
);
|
||||
// Send CMD_SET_CHANNEL to radio. Some devices accept the write but do not
|
||||
// reliably answer the follow-up CMD_GET_CHANNEL verification (0x1F).
|
||||
try {
|
||||
await _activeService.setChannel(
|
||||
channelIdx: slotIdx,
|
||||
channelName: channelName,
|
||||
secret: secretBytes,
|
||||
);
|
||||
} catch (e) {
|
||||
if (_isChannelRefreshTimeoutError(e)) {
|
||||
debugPrint(
|
||||
'⚠️ [Provider] CMD_GET_CHANNEL verification timed out after SET_CHANNEL; assuming the channel write succeeded',
|
||||
);
|
||||
onChannelInfoReceived?.call(
|
||||
slotIdx,
|
||||
channelName,
|
||||
Uint8List.fromList(secretBytes),
|
||||
null,
|
||||
);
|
||||
} else {
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
debugPrint(
|
||||
'✅ [Provider] Channel ${existingSlot != null ? 'updated' : 'created'} successfully in slot $slotIdx',
|
||||
);
|
||||
|
||||
// Small delay to allow the response to propagate
|
||||
await Future.delayed(const Duration(milliseconds: 100));
|
||||
|
||||
// Refresh channels to update UI
|
||||
// The channel info will be received via onChannelInfoReceived callback
|
||||
await _activeService.getChannel(slotIdx);
|
||||
} catch (e) {
|
||||
_error = 'Failed to create channel: $e';
|
||||
debugPrint('❌ [Provider] Channel creation failed: $e');
|
||||
|
||||
@@ -213,6 +213,22 @@ class MessagesProvider with ChangeNotifier {
|
||||
.where((message) => _isMessageForDestination(message, contact))
|
||||
.length;
|
||||
|
||||
DateTime? getLastActivityForDestination(Contact contact) {
|
||||
DateTime? latest;
|
||||
|
||||
for (final message in _messages) {
|
||||
if (!_isMessageForDestination(message, contact)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (latest == null || message.sentAt.isAfter(latest)) {
|
||||
latest = message.sentAt;
|
||||
}
|
||||
}
|
||||
|
||||
return latest;
|
||||
}
|
||||
|
||||
int getUnreadCountForDestination(Contact contact) => _messages
|
||||
.where(
|
||||
(message) =>
|
||||
@@ -749,9 +765,17 @@ class MessagesProvider with ChangeNotifier {
|
||||
final idx = channelIdx ?? 0;
|
||||
final resolved = resolveChannelNameCallback?.call(idx).trim();
|
||||
if (resolved != null && resolved.isNotEmpty) {
|
||||
if (idx == 0 && resolved == 'Public') {
|
||||
return _localizations?.publicChannel ?? resolved;
|
||||
}
|
||||
return resolved;
|
||||
}
|
||||
return idx == 0 ? 'Public' : 'Channel $idx';
|
||||
if (idx == 0) {
|
||||
return _localizations?.publicChannel ?? 'Public';
|
||||
}
|
||||
return _localizations != null
|
||||
? '${_localizations!.channel} $idx'
|
||||
: 'Channel $idx';
|
||||
}
|
||||
|
||||
String _buildNotificationMessageText(
|
||||
@@ -904,7 +928,8 @@ class MessagesProvider with ChangeNotifier {
|
||||
|
||||
final matchesDestination = switch (destinationType) {
|
||||
'channel' => _isChannelMessageForContact(message, contact),
|
||||
'contact' || 'room' => contact != null && _isMessageForDestination(message, contact),
|
||||
'contact' ||
|
||||
'room' => contact != null && _isMessageForDestination(message, contact),
|
||||
_ => false,
|
||||
};
|
||||
|
||||
|
||||
@@ -665,12 +665,32 @@ class _ChannelActivityCard extends StatelessWidget {
|
||||
required this.onNavigateToMessages,
|
||||
});
|
||||
|
||||
String _formatRelativeTime(BuildContext context, DateTime when) {
|
||||
final l10n = AppLocalizations.of(context)!;
|
||||
final diff = DateTime.now().difference(when);
|
||||
|
||||
if (diff.inMinutes < 1) return l10n.justNow;
|
||||
if (diff.inMinutes < 60) return l10n.minutesAgo(diff.inMinutes);
|
||||
if (diff.inHours < 24) return l10n.hoursAgo(diff.inHours);
|
||||
return l10n.daysAgo(diff.inDays);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final colorScheme = Theme.of(context).colorScheme;
|
||||
final titleStyle = Theme.of(context).textTheme.titleMedium?.copyWith(
|
||||
fontWeight: FontWeight.w800,
|
||||
letterSpacing: -0.3,
|
||||
);
|
||||
final channelIdx = channel.publicKey.length > 1 ? channel.publicKey[1] : 0;
|
||||
final channelMessages = messagesProvider.getMessagesForChannel(channelIdx)
|
||||
..sort((a, b) => b.sentAt.compareTo(a.sentAt));
|
||||
final lastActivityAt = messagesProvider.getLastActivityForDestination(
|
||||
channel,
|
||||
);
|
||||
final activityLabel = lastActivityAt == null
|
||||
? null
|
||||
: _formatRelativeTime(context, lastActivityAt);
|
||||
final participantNames = <String>[];
|
||||
for (final message in channelMessages) {
|
||||
final senderName = message.senderName?.trim();
|
||||
@@ -688,12 +708,19 @@ class _ChannelActivityCard extends StatelessWidget {
|
||||
end: Alignment.bottomRight,
|
||||
colors: [
|
||||
colorScheme.surfaceContainerLow,
|
||||
colorScheme.tertiaryContainer.withValues(alpha: 0.45),
|
||||
colorScheme.surfaceContainerHighest.withValues(alpha: 0.9),
|
||||
],
|
||||
),
|
||||
border: Border.all(
|
||||
color: colorScheme.outlineVariant.withValues(alpha: 0.35),
|
||||
),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.black.withValues(alpha: 0.045),
|
||||
blurRadius: 12,
|
||||
offset: const Offset(0, 4),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Material(
|
||||
color: Colors.transparent,
|
||||
@@ -715,22 +742,34 @@ class _ChannelActivityCard extends StatelessWidget {
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
ContactAvatar(contact: channel, radius: 20),
|
||||
ContactAvatar(contact: channel, radius: 24),
|
||||
const SizedBox(width: 10),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
channel.displayName,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: Theme.of(context).textTheme.titleMedium
|
||||
?.copyWith(
|
||||
fontWeight: FontWeight.w800,
|
||||
fontSize: 15,
|
||||
height: 1.05,
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Expanded(
|
||||
child: Text(
|
||||
channel.displayName,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: titleStyle,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
if (activityLabel != null)
|
||||
Text(
|
||||
activityLabel,
|
||||
style: Theme.of(context).textTheme.labelSmall
|
||||
?.copyWith(
|
||||
color: colorScheme.onSurfaceVariant,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
if (participantNames.isNotEmpty)
|
||||
|
||||
@@ -14,6 +14,7 @@ import 'messages_tab.dart';
|
||||
import 'contacts_tab.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';
|
||||
@@ -646,6 +647,31 @@ class _HomeScreenState extends State<HomeScreen>
|
||||
);
|
||||
}
|
||||
|
||||
items.add(
|
||||
PopupMenuItem(
|
||||
child: const Row(
|
||||
children: [
|
||||
Icon(Icons.router_outlined),
|
||||
SizedBox(width: 8),
|
||||
Text('Repeaters Map'),
|
||||
],
|
||||
),
|
||||
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(
|
||||
@@ -746,7 +772,7 @@ class _HomeScreenState extends State<HomeScreen>
|
||||
Icons.message,
|
||||
unreadCount,
|
||||
),
|
||||
text: AppLocalizations.of(context)!.messages,
|
||||
text: 'Chat',
|
||||
);
|
||||
case _HomeTab.contacts:
|
||||
return Tab(
|
||||
|
||||
@@ -27,6 +27,7 @@ import '../widgets/map_debug_info.dart';
|
||||
import '../widgets/map/compass_widget.dart';
|
||||
import '../widgets/map/detailed_compass_dialog.dart';
|
||||
import '../widgets/map/drawing_layer.dart';
|
||||
import '../widgets/map/drawing_toolbar.dart';
|
||||
import '../widgets/map/location_trail_layer.dart';
|
||||
import '../widgets/map/trail_controls.dart';
|
||||
import '../widgets/map/map_message_overlay.dart';
|
||||
@@ -2131,6 +2132,8 @@ class _MapTabState extends State<MapTab> with AutomaticKeepAliveClientMixin {
|
||||
right: 16,
|
||||
child: Column(
|
||||
children: [
|
||||
const DrawingToolbar(),
|
||||
const SizedBox(height: 8),
|
||||
// Hide other buttons when in drawing mode
|
||||
if (!drawingProvider.isDrawing) ...[
|
||||
// Current Location - always center to GPS
|
||||
|
||||
@@ -338,9 +338,35 @@ class _MessagesTabState extends State<MessagesTab> {
|
||||
_focusNode.requestFocus();
|
||||
}
|
||||
|
||||
void _insertReplyMention(String displayName) {
|
||||
final trimmedName = displayName.trim();
|
||||
if (trimmedName.isEmpty) return;
|
||||
|
||||
final mention = '@[$trimmedName] ';
|
||||
final value = _textController.value;
|
||||
final selection = value.selection;
|
||||
final hasSelection =
|
||||
selection.isValid &&
|
||||
selection.start >= 0 &&
|
||||
selection.end >= selection.start;
|
||||
|
||||
final start = hasSelection ? selection.start : value.text.length;
|
||||
final end = hasSelection ? selection.end : value.text.length;
|
||||
final nextText = value.text.replaceRange(start, end, mention);
|
||||
final nextOffset = start + mention.length;
|
||||
|
||||
_textController.value = value.copyWith(
|
||||
text: nextText,
|
||||
selection: TextSelection.collapsed(offset: nextOffset),
|
||||
composing: TextRange.empty,
|
||||
);
|
||||
_enforceMessageByteLimit();
|
||||
}
|
||||
|
||||
Future<void> _replyToMessage(Message message) async {
|
||||
final l10n = AppLocalizations.of(context)!;
|
||||
final contactsProvider = context.read<ContactsProvider>();
|
||||
Contact? senderContact;
|
||||
|
||||
String destinationType;
|
||||
Contact? recipient;
|
||||
@@ -360,6 +386,11 @@ class _MessagesTabState extends State<MessagesTab> {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
final senderPrefix = message.senderPublicKeyPrefix;
|
||||
if (senderPrefix != null && senderPrefix.length >= 6) {
|
||||
senderContact = contactsProvider.findContactByPrefix(senderPrefix);
|
||||
}
|
||||
} else {
|
||||
final senderPrefix = message.senderPublicKeyPrefix;
|
||||
if (senderPrefix == null || senderPrefix.length < 6) {
|
||||
@@ -380,6 +411,9 @@ class _MessagesTabState extends State<MessagesTab> {
|
||||
|
||||
await _onRecipientSelected(destinationType, recipient);
|
||||
if (!mounted) return;
|
||||
if (message.isChannelMessage && senderContact != null) {
|
||||
_insertReplyMention(senderContact.displayName);
|
||||
}
|
||||
_focusNode.requestFocus();
|
||||
}
|
||||
|
||||
|
||||
775
lib/screens/repeaters_map_screen.dart
Normal file
775
lib/screens/repeaters_map_screen.dart
Normal file
@@ -0,0 +1,775 @@
|
||||
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';
|
||||
|
||||
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),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
_InfoLine(
|
||||
label: 'Public key',
|
||||
value: repeater.publicKey,
|
||||
monospace: true,
|
||||
),
|
||||
_InfoLine(
|
||||
label: 'Coordinates',
|
||||
value:
|
||||
'${repeater.latitude.toStringAsFixed(5)}, ${repeater.longitude.toStringAsFixed(5)}',
|
||||
),
|
||||
_InfoLine(label: '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: const Icon(Icons.check_circle),
|
||||
label: const Text('Already in contacts'),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
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(
|
||||
const SnackBar(
|
||||
content: Text('Connect to a device before adding contacts'),
|
||||
),
|
||||
);
|
||||
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: const Text('Repeaters Map'),
|
||||
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: const [
|
||||
_LegendRow(
|
||||
color: Color(0xFF7C3AED),
|
||||
label: 'From contacts',
|
||||
),
|
||||
SizedBox(height: 8),
|
||||
_LegendRow(
|
||||
color: Color(0xFFE8681D),
|
||||
label: 'Online only',
|
||||
),
|
||||
SizedBox(height: 8),
|
||||
_LegendRow(color: Color(0xFF1B8F4F), label: 'In both'),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -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,7 +324,7 @@ 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: [
|
||||
@@ -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,
|
||||
),
|
||||
const 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,
|
||||
),
|
||||
const 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,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import 'dart:async';
|
||||
import 'dart:io';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:geolocator/geolocator.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
@@ -58,6 +59,16 @@ 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);
|
||||
@@ -68,8 +79,7 @@ class BackgroundLocationService {
|
||||
try {
|
||||
_positionSubscription =
|
||||
Geolocator.getPositionStream(
|
||||
locationSettings: LocationSettings(
|
||||
accuracy: LocationAccuracy.best,
|
||||
locationSettings: _buildLocationSettings(
|
||||
distanceFilter: distanceThreshold.toInt(),
|
||||
),
|
||||
).listen((Position position) async {
|
||||
@@ -171,4 +181,22 @@ class BackgroundLocationService {
|
||||
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,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import 'dart:async';
|
||||
import 'dart:io';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:geolocator/geolocator.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
@@ -148,6 +149,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 +186,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 +224,9 @@ class LocationTrackingService {
|
||||
}
|
||||
|
||||
final position = await Geolocator.getCurrentPosition(
|
||||
locationSettings: LocationSettings(
|
||||
accuracy: LocationAccuracy.best,
|
||||
locationSettings: _buildLocationSettings(
|
||||
timeLimit: timeLimit,
|
||||
distanceFilter: 0,
|
||||
),
|
||||
);
|
||||
|
||||
@@ -255,13 +270,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
|
||||
// ============================================================================
|
||||
|
||||
@@ -35,6 +35,7 @@ class MeshMapNode {
|
||||
class MeshMapNodesService {
|
||||
static const String _nodesEndpoint =
|
||||
'https://api.meshcore.nz/api/v1/map/nodes';
|
||||
static const int repeaterType = 1;
|
||||
static const Duration _cacheTtl = Duration(hours: 24);
|
||||
static const Duration traceCacheTtl = _cacheTtl;
|
||||
static const Duration traceTimeout = Duration(seconds: 30);
|
||||
@@ -182,6 +183,8 @@ class MeshMapNodesService {
|
||||
return _cachedAt;
|
||||
}
|
||||
|
||||
static bool isRepeater(MeshMapNode node) => node.type == repeaterType;
|
||||
|
||||
static Future<void> _storeCache(
|
||||
List<MeshMapNode> nodes, {
|
||||
required DateTime cachedAt,
|
||||
|
||||
@@ -402,10 +402,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');
|
||||
@@ -550,7 +553,7 @@ class NotificationService {
|
||||
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
|
||||
|
||||
@@ -35,16 +35,17 @@ class _AddChannelDialogState extends State<AddChannelDialog> {
|
||||
/// Validate channel name
|
||||
String? _validateName(String? value) {
|
||||
final l10n = AppLocalizations.of(context)!;
|
||||
final trimmedValue = value?.trim() ?? '';
|
||||
|
||||
if (value == null || value.trim().isEmpty) {
|
||||
if (trimmedValue.isEmpty) {
|
||||
return l10n.channelNameRequired;
|
||||
}
|
||||
|
||||
if (value.length > 31) {
|
||||
if (trimmedValue.length > 31) {
|
||||
return l10n.channelNameTooLong;
|
||||
}
|
||||
|
||||
if (!_isAscii(value)) {
|
||||
if (!_isAscii(trimmedValue)) {
|
||||
return l10n.invalidAsciiCharacters;
|
||||
}
|
||||
|
||||
@@ -105,7 +106,8 @@ class _AddChannelDialogState extends State<AddChannelDialog> {
|
||||
Widget build(BuildContext context) {
|
||||
final l10n = AppLocalizations.of(context)!;
|
||||
final theme = Theme.of(context);
|
||||
final isHashChannel = _nameController.text.startsWith('#');
|
||||
final normalizedName = _nameController.text.trimLeft();
|
||||
final isHashChannel = normalizedName.startsWith('#');
|
||||
|
||||
return AlertDialog(
|
||||
title: Text(l10n.addChannel),
|
||||
@@ -163,8 +165,15 @@ class _AddChannelDialogState extends State<AddChannelDialog> {
|
||||
enabled: !_isCreating,
|
||||
maxLength: 31,
|
||||
validator: _validateName,
|
||||
textInputAction: TextInputAction.next,
|
||||
textInputAction: isHashChannel
|
||||
? TextInputAction.done
|
||||
: TextInputAction.next,
|
||||
onChanged: (_) => setState(() {}), // Rebuild to update icon
|
||||
onFieldSubmitted: (_) {
|
||||
if (isHashChannel) {
|
||||
_handleCreate();
|
||||
}
|
||||
},
|
||||
),
|
||||
// Channel Secret Field (only show for private channels)
|
||||
if (!isHashChannel) ...[
|
||||
|
||||
@@ -36,10 +36,9 @@ class ContactTile extends StatelessWidget {
|
||||
this.onNavigateToMessages,
|
||||
});
|
||||
|
||||
/// Get localized time since last seen
|
||||
String _getLocalizedTimeSinceLastSeen(BuildContext context) {
|
||||
String _getLocalizedRelativeTime(BuildContext context, DateTime when) {
|
||||
final l10n = AppLocalizations.of(context)!;
|
||||
final diff = DateTime.now().difference(contact.lastSeenTime);
|
||||
final diff = DateTime.now().difference(when);
|
||||
|
||||
if (diff.inMinutes < 1) return l10n.justNow;
|
||||
if (diff.inMinutes < 60) return l10n.minutesAgo(diff.inMinutes);
|
||||
@@ -50,6 +49,8 @@ class ContactTile extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final isChannel = contact.type == ContactType.channel;
|
||||
final isRoom = contact.type == ContactType.room;
|
||||
final isRoomOrChannel = isRoom || isChannel;
|
||||
final location = contact.displayLocation;
|
||||
// Calculate distance if both positions are available
|
||||
String? distanceText;
|
||||
@@ -68,6 +69,7 @@ class ContactTile extends StatelessWidget {
|
||||
|
||||
// Get room login state if this is a room
|
||||
final connectionProvider = context.watch<ConnectionProvider>();
|
||||
final messagesProvider = context.watch<MessagesProvider>();
|
||||
final isPingInProgress = connectionProvider.isPingInProgress(
|
||||
contact.publicKey,
|
||||
);
|
||||
@@ -109,40 +111,44 @@ class ContactTile extends StatelessWidget {
|
||||
fontWeight: FontWeight.w800,
|
||||
letterSpacing: -0.3,
|
||||
);
|
||||
final timeAgoText = _getLocalizedTimeSinceLastSeen(context);
|
||||
final lastActivityAt = isRoomOrChannel
|
||||
? messagesProvider.getLastActivityForDestination(contact)
|
||||
: null;
|
||||
final timeAgoText = _getLocalizedRelativeTime(
|
||||
context,
|
||||
lastActivityAt ?? contact.lastSeenTime,
|
||||
);
|
||||
final timeAgoStyle = Theme.of(context).textTheme.labelSmall?.copyWith(
|
||||
color: contact.isRecentlySeen
|
||||
? colorScheme.primary
|
||||
: colorScheme.onSurfaceVariant,
|
||||
fontWeight: FontWeight.w600,
|
||||
);
|
||||
final Widget? subtitleWidget = isChannel
|
||||
? null
|
||||
: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
if (location != null) ...[
|
||||
const SizedBox(height: 2),
|
||||
_buildLocationLine(
|
||||
context,
|
||||
latitude: location.latitude,
|
||||
longitude: location.longitude,
|
||||
distanceText: distanceText,
|
||||
),
|
||||
const SizedBox(height: 6),
|
||||
Row(children: [_buildRoutePill(context, contact)]),
|
||||
] else
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 4),
|
||||
child: Text(
|
||||
AppLocalizations.of(context)!.noGpsData,
|
||||
style: Theme.of(
|
||||
context,
|
||||
).textTheme.labelSmall?.copyWith(color: Colors.grey),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
final Widget subtitleWidget = Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
if (location != null) ...[
|
||||
const SizedBox(height: 2),
|
||||
_buildLocationLine(
|
||||
context,
|
||||
latitude: location.latitude,
|
||||
longitude: location.longitude,
|
||||
distanceText: distanceText,
|
||||
),
|
||||
const SizedBox(height: 6),
|
||||
Row(children: [_buildRoutePill(context, contact)]),
|
||||
] else
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 4),
|
||||
child: Text(
|
||||
AppLocalizations.of(context)!.noGpsData,
|
||||
style: Theme.of(
|
||||
context,
|
||||
).textTheme.labelSmall?.copyWith(color: Colors.grey),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
|
||||
return Container(
|
||||
margin: const EdgeInsets.only(bottom: 8),
|
||||
@@ -239,7 +245,7 @@ class ContactTile extends StatelessWidget {
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
if (!isChannel)
|
||||
if (!isRoomOrChannel || lastActivityAt != null)
|
||||
Text(timeAgoText, style: timeAgoStyle),
|
||||
if (isPingInProgress) ...[
|
||||
const SizedBox(width: 6),
|
||||
@@ -254,7 +260,7 @@ class ContactTile extends StatelessWidget {
|
||||
],
|
||||
],
|
||||
),
|
||||
?subtitleWidget,
|
||||
subtitleWidget,
|
||||
],
|
||||
),
|
||||
),
|
||||
@@ -335,7 +341,11 @@ class ContactTile extends StatelessWidget {
|
||||
leading: Icon(
|
||||
isInSensors ? Icons.sensors : Icons.sensors_outlined,
|
||||
),
|
||||
title: Text(isInSensors ? 'In Sensors' : 'Add to Sensors'),
|
||||
title: Text(
|
||||
isInSensors
|
||||
? l10n.contactInSensors
|
||||
: l10n.contactAddToSensors,
|
||||
),
|
||||
enabled: !isInSensors,
|
||||
onTap: isInSensors
|
||||
? null
|
||||
@@ -347,7 +357,7 @@ class ContactTile extends StatelessWidget {
|
||||
if (canSetPath)
|
||||
ListTile(
|
||||
leading: const Icon(Icons.alt_route),
|
||||
title: const Text('Set path'),
|
||||
title: Text(l10n.contactSetPath),
|
||||
onTap: () {
|
||||
Navigator.pop(sheetContext);
|
||||
_showSetRouteDialog(context, contact);
|
||||
@@ -360,13 +370,18 @@ class ContactTile extends StatelessWidget {
|
||||
contact.isChannel ? l10n.deleteChannel : l10n.deleteContact,
|
||||
style: const TextStyle(color: Colors.red),
|
||||
),
|
||||
onTap: () {
|
||||
onTap: () async {
|
||||
Navigator.pop(sheetContext);
|
||||
if (contact.isChannel) {
|
||||
_showDeleteChannelDialog(context, contact);
|
||||
} else {
|
||||
_showDeleteConfirmation(context, contact);
|
||||
}
|
||||
await Future<void>.delayed(Duration.zero);
|
||||
if (!context.mounted) return;
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
if (!context.mounted) return;
|
||||
if (contact.isChannel) {
|
||||
_showDeleteChannelDialog(context, contact);
|
||||
} else {
|
||||
_showDeleteConfirmation(context, contact);
|
||||
}
|
||||
});
|
||||
},
|
||||
),
|
||||
],
|
||||
@@ -397,7 +412,13 @@ class ContactTile extends StatelessWidget {
|
||||
}
|
||||
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text('${contact.displayName} added to Sensors')),
|
||||
SnackBar(
|
||||
content: Text(
|
||||
AppLocalizations.of(
|
||||
context,
|
||||
)!.contactAddedToSensors(contact.displayName),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -523,15 +544,22 @@ class ContactTile extends StatelessWidget {
|
||||
paddedPathBytes: previousPathBytes,
|
||||
);
|
||||
if (context.mounted) {
|
||||
ToastLogger.error(context, 'Failed to clear route: $error');
|
||||
ToastLogger.error(
|
||||
context,
|
||||
AppLocalizations.of(
|
||||
context,
|
||||
)!.contactFailedToClearRoute(error.toString()),
|
||||
);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (context.mounted) {
|
||||
ScaffoldMessenger.of(
|
||||
context,
|
||||
).showSnackBar(const SnackBar(content: Text('Route cleared')));
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(AppLocalizations.of(context)!.contactRouteCleared),
|
||||
),
|
||||
);
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -551,7 +579,13 @@ class ContactTile extends StatelessWidget {
|
||||
);
|
||||
if (context.mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text('Route set: ${parsedRoute.canonicalText}')),
|
||||
SnackBar(
|
||||
content: Text(
|
||||
AppLocalizations.of(
|
||||
context,
|
||||
)!.contactRouteSet(parsedRoute.canonicalText),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
@@ -561,7 +595,12 @@ class ContactTile extends StatelessWidget {
|
||||
paddedPathBytes: previousPathBytes,
|
||||
);
|
||||
if (context.mounted) {
|
||||
ToastLogger.error(context, 'Failed to set route: $error');
|
||||
ToastLogger.error(
|
||||
context,
|
||||
AppLocalizations.of(
|
||||
context,
|
||||
)!.contactFailedToSetRoute(error.toString()),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import '../../models/contact.dart';
|
||||
import '../../models/location_trail.dart';
|
||||
import '../../providers/map_provider.dart';
|
||||
import '../../providers/contacts_provider.dart';
|
||||
import '../../services/gpx_service.dart';
|
||||
import '../../services/trail_color_service.dart';
|
||||
import '../../l10n/app_localizations.dart';
|
||||
|
||||
@@ -66,6 +69,31 @@ class TrailControls extends StatelessWidget {
|
||||
const Divider(),
|
||||
const SizedBox(height: 8),
|
||||
|
||||
Wrap(
|
||||
spacing: 12,
|
||||
runSpacing: 12,
|
||||
children: [
|
||||
OutlinedButton.icon(
|
||||
onPressed: () => _importTrail(context, mapProvider),
|
||||
icon: const Icon(Icons.file_upload_outlined),
|
||||
label: Text(l10n.importFromClipboard),
|
||||
),
|
||||
if (mapProvider.currentTrail != null &&
|
||||
mapProvider.currentTrail!.points.length >= 2)
|
||||
OutlinedButton.icon(
|
||||
onPressed: () => _exportTrail(
|
||||
context,
|
||||
mapProvider.currentTrail!,
|
||||
customName: 'My Trail',
|
||||
),
|
||||
icon: const Icon(Icons.file_download_outlined),
|
||||
label: Text(l10n.exportToClipboard),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
const SizedBox(height: 16),
|
||||
|
||||
// Trail stats
|
||||
if (mapProvider.currentTrail != null &&
|
||||
mapProvider.currentTrail!.points.isNotEmpty)
|
||||
@@ -104,8 +132,6 @@ class TrailControls extends StatelessWidget {
|
||||
),
|
||||
),
|
||||
|
||||
const SizedBox(height: 16),
|
||||
|
||||
// Clear trail button
|
||||
if (mapProvider.currentTrail != null &&
|
||||
mapProvider.currentTrail!.points.isNotEmpty)
|
||||
@@ -238,7 +264,17 @@ class TrailControls extends StatelessWidget {
|
||||
),
|
||||
],
|
||||
),
|
||||
title: Text(contact.displayName),
|
||||
title: Row(
|
||||
children: [
|
||||
Expanded(child: Text(contact.displayName)),
|
||||
IconButton(
|
||||
tooltip: l10n.exportToClipboard,
|
||||
icon: const Icon(Icons.file_download_outlined),
|
||||
onPressed: () =>
|
||||
_exportContactTrail(context, contact),
|
||||
),
|
||||
],
|
||||
),
|
||||
subtitle: Text(
|
||||
'${contact.advertHistory.length} points',
|
||||
),
|
||||
@@ -266,6 +302,92 @@ class TrailControls extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _importTrail(
|
||||
BuildContext context,
|
||||
MapProvider mapProvider,
|
||||
) async {
|
||||
final l10n = AppLocalizations.of(context)!;
|
||||
|
||||
try {
|
||||
final importedTrail = await GpxService.importTrailFromFile();
|
||||
if (importedTrail == null || !context.mounted) {
|
||||
return;
|
||||
}
|
||||
|
||||
mapProvider.replaceCurrentTrailWithImport(importedTrail);
|
||||
mapProvider.clearImportedTrail();
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(
|
||||
'${l10n.locationTrail}: ${importedTrail.points.length} ${l10n.points.toLowerCase()}',
|
||||
),
|
||||
),
|
||||
);
|
||||
} catch (error) {
|
||||
if (!context.mounted) {
|
||||
return;
|
||||
}
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text(l10n.importFailed(error.toString()))),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _exportTrail(
|
||||
BuildContext context,
|
||||
LocationTrail trail, {
|
||||
String? customName,
|
||||
}) async {
|
||||
final l10n = AppLocalizations.of(context)!;
|
||||
|
||||
try {
|
||||
final success = await GpxService.exportTrailToFile(
|
||||
trail,
|
||||
customName: customName,
|
||||
);
|
||||
if (!success && context.mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text(l10n.exportFailed('unable to share GPX'))),
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
if (!context.mounted) {
|
||||
return;
|
||||
}
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text(l10n.exportFailed(error.toString()))),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _exportContactTrail(BuildContext context, Contact contact) async {
|
||||
await _exportTrail(
|
||||
context,
|
||||
_buildContactTrail(contact),
|
||||
customName: '${contact.displayName} Trail',
|
||||
);
|
||||
}
|
||||
|
||||
LocationTrail _buildContactTrail(Contact contact) {
|
||||
final sortedHistory = [...contact.advertHistory]
|
||||
..sort((a, b) => a.timestamp.compareTo(b.timestamp));
|
||||
|
||||
return LocationTrail(
|
||||
id: 'contact_${contact.publicKeyHex}',
|
||||
isActive: false,
|
||||
startTime: sortedHistory.first.timestamp,
|
||||
endTime: sortedHistory.last.timestamp,
|
||||
points: sortedHistory
|
||||
.map(
|
||||
(advert) => TrailPoint(
|
||||
position: advert.location,
|
||||
timestamp: advert.timestamp,
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
);
|
||||
}
|
||||
|
||||
void _showClearConfirmation(
|
||||
BuildContext context,
|
||||
MapProvider mapProvider,
|
||||
|
||||
@@ -68,6 +68,7 @@ class MessageBubble extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _MessageBubbleState extends State<MessageBubble> {
|
||||
static final RegExp _mentionPattern = RegExp(r'@\[(.+?)\]');
|
||||
bool _isExpanded = false;
|
||||
bool _showReceivedStats = false;
|
||||
|
||||
@@ -95,6 +96,72 @@ class _MessageBubbleState extends State<MessageBubble> {
|
||||
});
|
||||
}
|
||||
|
||||
Widget _buildMessageTextContent(String text, TextStyle? baseBodyStyle) {
|
||||
final matches = _mentionPattern.allMatches(text).toList();
|
||||
if (matches.isEmpty) {
|
||||
return Text(text, style: baseBodyStyle);
|
||||
}
|
||||
|
||||
final textColor =
|
||||
baseBodyStyle?.color ?? Theme.of(context).colorScheme.onSurface;
|
||||
final backgroundColor = Theme.of(
|
||||
context,
|
||||
).colorScheme.primary.withValues(alpha: 0.12);
|
||||
final borderColor = Theme.of(
|
||||
context,
|
||||
).colorScheme.primary.withValues(alpha: 0.25);
|
||||
|
||||
final spans = <InlineSpan>[];
|
||||
var cursor = 0;
|
||||
|
||||
for (final match in matches) {
|
||||
if (match.start > cursor) {
|
||||
spans.add(
|
||||
TextSpan(
|
||||
text: text.substring(cursor, match.start),
|
||||
style: baseBodyStyle,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
final mentionName = match.group(1)?.trim() ?? '';
|
||||
if (mentionName.isNotEmpty) {
|
||||
spans.add(
|
||||
WidgetSpan(
|
||||
alignment: PlaceholderAlignment.middle,
|
||||
child: Container(
|
||||
margin: const EdgeInsets.symmetric(horizontal: 1, vertical: 1),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 3),
|
||||
decoration: BoxDecoration(
|
||||
color: backgroundColor,
|
||||
borderRadius: BorderRadius.circular(999),
|
||||
border: Border.all(color: borderColor),
|
||||
),
|
||||
child: Text(
|
||||
'@$mentionName',
|
||||
style: baseBodyStyle?.copyWith(
|
||||
color: textColor,
|
||||
fontWeight: FontWeight.w700,
|
||||
height: 1.1,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
} else {
|
||||
spans.add(TextSpan(text: match.group(0), style: baseBodyStyle));
|
||||
}
|
||||
|
||||
cursor = match.end;
|
||||
}
|
||||
|
||||
if (cursor < text.length) {
|
||||
spans.add(TextSpan(text: text.substring(cursor), style: baseBodyStyle));
|
||||
}
|
||||
|
||||
return Text.rich(TextSpan(children: spans, style: baseBodyStyle));
|
||||
}
|
||||
|
||||
void _handleBubbleTap({required bool isSarMarker, required bool isDrawing}) {
|
||||
if (!widget.message.isRead &&
|
||||
!widget.message.isSentMessage &&
|
||||
@@ -2229,7 +2296,10 @@ class _MessageBubbleState extends State<MessageBubble> {
|
||||
);
|
||||
|
||||
if (drawing == null) {
|
||||
return Text(message.text, style: baseBodyStyle);
|
||||
return _buildMessageTextContent(
|
||||
message.text,
|
||||
baseBodyStyle,
|
||||
);
|
||||
}
|
||||
|
||||
final String drawingTypeLabel;
|
||||
@@ -2315,7 +2385,7 @@ class _MessageBubbleState extends State<MessageBubble> {
|
||||
)
|
||||
// Regular message content
|
||||
else if (!message.isDrawing || widget.isCompact)
|
||||
Text(message.text, style: baseBodyStyle),
|
||||
_buildMessageTextContent(message.text, baseBodyStyle),
|
||||
|
||||
if (!widget.isCompact &&
|
||||
!isSarMarker &&
|
||||
|
||||
@@ -16,7 +16,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev
|
||||
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
|
||||
# In Windows, build-name is used as the major, minor, and patch parts
|
||||
# of the product and file versions while build-number is used as the build suffix.
|
||||
version: 2026.0308.3+23
|
||||
version: 2026.0311.1+26
|
||||
|
||||
environment:
|
||||
sdk: ^3.9.2
|
||||
|
||||
@@ -12,7 +12,7 @@ import 'package:shared_preferences/shared_preferences.dart';
|
||||
void main() {
|
||||
TestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
Contact _buildContact({
|
||||
Contact buildContact({
|
||||
required List<int> prefix,
|
||||
required ContactType type,
|
||||
String name = 'Test Contact',
|
||||
@@ -218,12 +218,12 @@ void main() {
|
||||
|
||||
test('marks only the selected contact destination as read', () {
|
||||
final provider = MessagesProvider();
|
||||
final alice = _buildContact(
|
||||
final alice = buildContact(
|
||||
prefix: [0x10, 0x11, 0x12, 0x13, 0x14, 0x15],
|
||||
type: ContactType.chat,
|
||||
name: 'Alice',
|
||||
);
|
||||
final bob = _buildContact(
|
||||
final bob = buildContact(
|
||||
prefix: [0x20, 0x21, 0x22, 0x23, 0x24, 0x25],
|
||||
type: ContactType.chat,
|
||||
name: 'Bob',
|
||||
|
||||
64
test/widgets/add_channel_dialog_test.dart
Normal file
64
test/widgets/add_channel_dialog_test.dart
Normal file
@@ -0,0 +1,64 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:meshcore_sar_app/l10n/app_localizations.dart';
|
||||
import 'package:meshcore_sar_app/widgets/contacts/add_channel_dialog.dart';
|
||||
|
||||
void main() {
|
||||
Future<void> pumpDialog(
|
||||
WidgetTester tester, {
|
||||
required Future<void> Function(String name, String secret) onCreateChannel,
|
||||
}) async {
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
localizationsDelegates: AppLocalizations.localizationsDelegates,
|
||||
supportedLocales: AppLocalizations.supportedLocales,
|
||||
home: Scaffold(
|
||||
body: Builder(
|
||||
builder: (context) => Center(
|
||||
child: AddChannelDialog(onCreateChannel: onCreateChannel),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
testWidgets('submits hash channels with an empty secret', (tester) async {
|
||||
String? submittedName;
|
||||
String? submittedSecret;
|
||||
|
||||
await pumpDialog(
|
||||
tester,
|
||||
onCreateChannel: (name, secret) async {
|
||||
submittedName = name;
|
||||
submittedSecret = secret;
|
||||
},
|
||||
);
|
||||
|
||||
await tester.enterText(find.byType(TextFormField).first, '#slovenia');
|
||||
await tester.pump();
|
||||
await tester.tap(find.text('Create Channel'));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(submittedName, '#slovenia');
|
||||
expect(submittedSecret, '');
|
||||
});
|
||||
|
||||
testWidgets('uses done action for hash channels', (tester) async {
|
||||
String? submittedName;
|
||||
|
||||
await pumpDialog(
|
||||
tester,
|
||||
onCreateChannel: (name, secret) async {
|
||||
submittedName = name;
|
||||
},
|
||||
);
|
||||
|
||||
await tester.enterText(find.byType(TextFormField).first, '#slovenia');
|
||||
await tester.pump();
|
||||
await tester.testTextInput.receiveAction(TextInputAction.done);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(submittedName, '#slovenia');
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user