mirror of
https://github.com/zjs81/meshcore-open.git
synced 2026-04-20 22:13:48 +00:00
Added Line Of Sight Feature for repeater placement, Added app wide Units Setting (#198)
* feat: add LOS workflow, global units, l10n cleanup, and mobile UI overflow fixes Squashes prior PR commits into one changeset including: LOS map/service/tests, global metric/imperial unit system adoption, notification/BLE safety fixes, app-wide localization backfill/mojibake cleanup, and responsive UI title/overflow hardening. * l10n: revert unrelated locale churn for LOS feature * feat: keep LOS with app-wide unit settings * fix: resolve post-merge app bar/import analyzer errors * style: format screen files for CI
This commit is contained in:
parent
d2b693e5ce
commit
f4b18d97a1
52 changed files with 6078 additions and 214 deletions
|
|
@ -1599,5 +1599,120 @@
|
|||
"chat_ShowAllPaths": "Покажи всички пътища",
|
||||
"settings_clientRepeatSubtitle": "Позволете на това устройство да предава пакети към мрежата за други устройства.",
|
||||
"settings_clientRepeatFreqWarning": "За повторение извън мрежата са необходими честоти от 433, 869 или 918 MHz.",
|
||||
"settings_clientRepeat": "Без електричество – повторение"
|
||||
"settings_clientRepeat": "Без електричество – повторение",
|
||||
"settings_aboutOpenMeteoAttribution": "Данни за надморска височина на LOS: Open-Meteo (CC BY 4.0)",
|
||||
"appSettings_unitsTitle": "единици",
|
||||
"appSettings_unitsMetric": "Метрика (m / km)",
|
||||
"appSettings_unitsImperial": "Имперска (ft / mi)",
|
||||
"map_lineOfSight": "Линия на видимост",
|
||||
"map_losScreenTitle": "Линия на видимост",
|
||||
"losSelectStartEnd": "Изберете начални и крайни възли за LOS.",
|
||||
"losRunFailed": "Проверката на пряката видимост е неуспешна: {error}",
|
||||
"@losRunFailed": {
|
||||
"placeholders": {
|
||||
"error": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"losClearAllPoints": "Изчистете всички точки",
|
||||
"losRunToViewElevationProfile": "Стартирайте LOS, за да видите профила на надморската височина",
|
||||
"losMenuTitle": "LOS меню",
|
||||
"losMenuSubtitle": "Докоснете възли или натиснете продължително карта за персонализирани точки",
|
||||
"losShowDisplayNodes": "Показване на възли на дисплея",
|
||||
"losCustomPoints": "Персонализирани точки",
|
||||
"losCustomPointLabel": "Персонализирано {index}",
|
||||
"@losCustomPointLabel": {
|
||||
"placeholders": {
|
||||
"index": {
|
||||
"type": "int"
|
||||
}
|
||||
}
|
||||
},
|
||||
"losPointA": "Точка А",
|
||||
"losPointB": "Точка Б",
|
||||
"losAntennaA": "Антена A: {value} {unit}",
|
||||
"@losAntennaA": {
|
||||
"placeholders": {
|
||||
"value": {
|
||||
"type": "String"
|
||||
},
|
||||
"unit": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"losAntennaB": "Антена B: {value} {unit}",
|
||||
"@losAntennaB": {
|
||||
"placeholders": {
|
||||
"value": {
|
||||
"type": "String"
|
||||
},
|
||||
"unit": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"losRun": "Стартирайте LOS",
|
||||
"losNoElevationData": "Няма данни за надморска височина",
|
||||
"losProfileClear": "{distance} {distanceUnit}, чист LOS, минимално разстояние {clearance} {heightUnit}",
|
||||
"@losProfileClear": {
|
||||
"placeholders": {
|
||||
"distance": {
|
||||
"type": "String"
|
||||
},
|
||||
"distanceUnit": {
|
||||
"type": "String"
|
||||
},
|
||||
"clearance": {
|
||||
"type": "String"
|
||||
},
|
||||
"heightUnit": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"losProfileBlocked": "{distance} {distanceUnit}, блокиран от {obstruction} {heightUnit}",
|
||||
"@losProfileBlocked": {
|
||||
"placeholders": {
|
||||
"distance": {
|
||||
"type": "String"
|
||||
},
|
||||
"distanceUnit": {
|
||||
"type": "String"
|
||||
},
|
||||
"obstruction": {
|
||||
"type": "String"
|
||||
},
|
||||
"heightUnit": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"losStatusChecking": "LOS: проверка...",
|
||||
"losStatusNoData": "LOS: няма данни",
|
||||
"losStatusSummary": "LOS: {clear}/{total} ясно, {blocked} блокирано, {unknown} неизвестно",
|
||||
"@losStatusSummary": {
|
||||
"placeholders": {
|
||||
"clear": {
|
||||
"type": "int"
|
||||
},
|
||||
"total": {
|
||||
"type": "int"
|
||||
},
|
||||
"blocked": {
|
||||
"type": "int"
|
||||
},
|
||||
"unknown": {
|
||||
"type": "int"
|
||||
}
|
||||
}
|
||||
},
|
||||
"losErrorElevationUnavailable": "Няма налични данни за надморска височина за една или повече проби.",
|
||||
"losErrorInvalidInput": "Невалидни данни за точки/надморска височина за изчисляване на LOS.",
|
||||
"losRenameCustomPoint": "Преименувайте персонализирана точка",
|
||||
"losPointName": "Име на точката",
|
||||
"losShowPanelTooltip": "Показване на LOS панел",
|
||||
"losHidePanelTooltip": "Скриване на LOS панела",
|
||||
"losElevationAttribution": "Данни за надморска височина: Open-Meteo (CC BY 4.0)"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1627,5 +1627,120 @@
|
|||
"chat_ShowAllPaths": "Alle Pfade anzeigen",
|
||||
"settings_clientRepeat": "Wiederholung, ohne Stromanschluss",
|
||||
"settings_clientRepeatFreqWarning": "Die Kommunikation ohne Stromversorgung erfordert Frequenzen von 433, 869 oder 918 MHz.",
|
||||
"settings_clientRepeatSubtitle": "Ermöglichen Sie diesem Gerät, Mesh-Pakete für andere zu wiederholen."
|
||||
"settings_clientRepeatSubtitle": "Ermöglichen Sie diesem Gerät, Mesh-Pakete für andere zu wiederholen.",
|
||||
"settings_aboutOpenMeteoAttribution": "LOS-Höhendaten: Open-Meteo (CC BY 4.0)",
|
||||
"appSettings_unitsTitle": "Einheiten",
|
||||
"appSettings_unitsMetric": "Metrisch (m/km)",
|
||||
"appSettings_unitsImperial": "Imperial (ft/mi)",
|
||||
"map_lineOfSight": "Sichtlinie",
|
||||
"map_losScreenTitle": "Sichtlinie",
|
||||
"losSelectStartEnd": "Wählen Sie Start- und Endknoten für LOS aus.",
|
||||
"losRunFailed": "Sichtlinienprüfung fehlgeschlagen: {error}",
|
||||
"@losRunFailed": {
|
||||
"placeholders": {
|
||||
"error": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"losClearAllPoints": "Löschen Sie alle Punkte",
|
||||
"losRunToViewElevationProfile": "Führen Sie LOS aus, um das Höhenprofil anzuzeigen",
|
||||
"losMenuTitle": "LOS-Menü",
|
||||
"losMenuSubtitle": "Tippen Sie auf Knoten oder drücken Sie lange auf die Karte, um benutzerdefinierte Punkte anzuzeigen",
|
||||
"losShowDisplayNodes": "Anzeigeknoten anzeigen",
|
||||
"losCustomPoints": "Benutzerdefinierte Punkte",
|
||||
"losCustomPointLabel": "Benutzerdefiniert {index}",
|
||||
"@losCustomPointLabel": {
|
||||
"placeholders": {
|
||||
"index": {
|
||||
"type": "int"
|
||||
}
|
||||
}
|
||||
},
|
||||
"losPointA": "Punkt A",
|
||||
"losPointB": "Punkt B",
|
||||
"losAntennaA": "Antenne A: {value} {unit}",
|
||||
"@losAntennaA": {
|
||||
"placeholders": {
|
||||
"value": {
|
||||
"type": "String"
|
||||
},
|
||||
"unit": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"losAntennaB": "Antenne B: {value} {unit}",
|
||||
"@losAntennaB": {
|
||||
"placeholders": {
|
||||
"value": {
|
||||
"type": "String"
|
||||
},
|
||||
"unit": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"losRun": "Führen Sie LOS aus",
|
||||
"losNoElevationData": "Keine Höhendaten",
|
||||
"losProfileClear": "{distance} {distanceUnit}, freie Sichtlinie, Mindestabstand {clearance} {heightUnit}",
|
||||
"@losProfileClear": {
|
||||
"placeholders": {
|
||||
"distance": {
|
||||
"type": "String"
|
||||
},
|
||||
"distanceUnit": {
|
||||
"type": "String"
|
||||
},
|
||||
"clearance": {
|
||||
"type": "String"
|
||||
},
|
||||
"heightUnit": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"losProfileBlocked": "{distance} {distanceUnit}, blockiert durch {obstruction} {heightUnit}",
|
||||
"@losProfileBlocked": {
|
||||
"placeholders": {
|
||||
"distance": {
|
||||
"type": "String"
|
||||
},
|
||||
"distanceUnit": {
|
||||
"type": "String"
|
||||
},
|
||||
"obstruction": {
|
||||
"type": "String"
|
||||
},
|
||||
"heightUnit": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"losStatusChecking": "LOS: Überprüfen...",
|
||||
"losStatusNoData": "LOS: keine Daten",
|
||||
"losStatusSummary": "Sichtlinie: {clear}/{total} frei, {blocked} blockiert, {unknown} unbekannt",
|
||||
"@losStatusSummary": {
|
||||
"placeholders": {
|
||||
"clear": {
|
||||
"type": "int"
|
||||
},
|
||||
"total": {
|
||||
"type": "int"
|
||||
},
|
||||
"blocked": {
|
||||
"type": "int"
|
||||
},
|
||||
"unknown": {
|
||||
"type": "int"
|
||||
}
|
||||
}
|
||||
},
|
||||
"losErrorElevationUnavailable": "Für eine oder mehrere Proben sind keine Höhendaten verfügbar.",
|
||||
"losErrorInvalidInput": "Ungültige Punkte/Höhendaten für die LOS-Berechnung.",
|
||||
"losRenameCustomPoint": "Benennen Sie den benutzerdefinierten Punkt um",
|
||||
"losPointName": "Punktname",
|
||||
"losShowPanelTooltip": "LOS-Panel anzeigen",
|
||||
"losHidePanelTooltip": "LOS-Panel ausblenden",
|
||||
"losElevationAttribution": "Höhendaten: Open-Meteo (CC BY 4.0)"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -131,6 +131,7 @@
|
|||
},
|
||||
"settings_aboutLegalese": "2026 MeshCore Open Source Project",
|
||||
"settings_aboutDescription": "An open-source Flutter client for MeshCore LoRa mesh networking devices.",
|
||||
"settings_aboutOpenMeteoAttribution": "LOS elevation data: Open-Meteo (CC BY 4.0)",
|
||||
"settings_infoName": "Name",
|
||||
"settings_infoId": "ID",
|
||||
"settings_infoStatus": "Status",
|
||||
|
|
@ -242,6 +243,9 @@
|
|||
"appSettings_last24Hours": "Last 24 hours",
|
||||
"appSettings_lastWeek": "Last week",
|
||||
"appSettings_offlineMapCache": "Offline Map Cache",
|
||||
"appSettings_unitsTitle": "Units",
|
||||
"appSettings_unitsMetric": "Metric (m / km)",
|
||||
"appSettings_unitsImperial": "Imperial (ft / mi)",
|
||||
"appSettings_noAreaSelected": "No area selected",
|
||||
"appSettings_areaSelectedZoom": "Area selected (zoom {minZoom}-{maxZoom})",
|
||||
"@appSettings_areaSelectedZoom": {
|
||||
|
|
@ -639,6 +643,8 @@
|
|||
},
|
||||
"chat_invalidLink": "Invalid link format",
|
||||
"map_title": "Node Map",
|
||||
"map_lineOfSight": "Line of Sight",
|
||||
"map_losScreenTitle": "Line of Sight",
|
||||
"map_noNodesWithLocation": "No nodes with location data",
|
||||
"map_nodesNeedGps": "Nodes need to share their GPS coordinates\nto appear on the map",
|
||||
"map_nodesCount": "Nodes: {count}",
|
||||
|
|
@ -1548,6 +1554,115 @@
|
|||
"pathTrace_refreshTooltip": "Refresh Path Trace.",
|
||||
"pathTrace_someHopsNoLocation": "One or more of the hops is missing a location!",
|
||||
"pathTrace_clearTooltip": "Clear path.",
|
||||
"losSelectStartEnd": "Select start and end nodes for LOS.",
|
||||
"losRunFailed": "Line-of-sight check failed: {error}",
|
||||
"@losRunFailed": {
|
||||
"placeholders": {
|
||||
"error": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"losClearAllPoints": "Clear all points",
|
||||
"losRunToViewElevationProfile": "Run LOS to view elevation profile",
|
||||
"losMenuTitle": "LOS Menu",
|
||||
"losMenuSubtitle": "Tap nodes or long-press map for custom points",
|
||||
"losShowDisplayNodes": "Show display nodes",
|
||||
"losCustomPoints": "Custom points",
|
||||
"losCustomPointLabel": "Custom {index}",
|
||||
"@losCustomPointLabel": {
|
||||
"placeholders": {
|
||||
"index": {
|
||||
"type": "int"
|
||||
}
|
||||
}
|
||||
},
|
||||
"losPointA": "Point A",
|
||||
"losPointB": "Point B",
|
||||
"losAntennaA": "Antenna A: {value} {unit}",
|
||||
"@losAntennaA": {
|
||||
"placeholders": {
|
||||
"value": {
|
||||
"type": "String"
|
||||
},
|
||||
"unit": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"losAntennaB": "Antenna B: {value} {unit}",
|
||||
"@losAntennaB": {
|
||||
"placeholders": {
|
||||
"value": {
|
||||
"type": "String"
|
||||
},
|
||||
"unit": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"losRun": "Run LOS",
|
||||
"losNoElevationData": "No elevation data",
|
||||
"losProfileClear": "{distance} {distanceUnit}, clear LOS, min clearance {clearance} {heightUnit}",
|
||||
"@losProfileClear": {
|
||||
"placeholders": {
|
||||
"distance": {
|
||||
"type": "String"
|
||||
},
|
||||
"distanceUnit": {
|
||||
"type": "String"
|
||||
},
|
||||
"clearance": {
|
||||
"type": "String"
|
||||
},
|
||||
"heightUnit": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"losProfileBlocked": "{distance} {distanceUnit}, blocked by {obstruction} {heightUnit}",
|
||||
"@losProfileBlocked": {
|
||||
"placeholders": {
|
||||
"distance": {
|
||||
"type": "String"
|
||||
},
|
||||
"distanceUnit": {
|
||||
"type": "String"
|
||||
},
|
||||
"obstruction": {
|
||||
"type": "String"
|
||||
},
|
||||
"heightUnit": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"losStatusChecking": "LOS: checking...",
|
||||
"losStatusNoData": "LOS: no data",
|
||||
"losStatusSummary": "LOS: {clear}/{total} clear, {blocked} blocked, {unknown} unknown",
|
||||
"@losStatusSummary": {
|
||||
"placeholders": {
|
||||
"clear": {
|
||||
"type": "int"
|
||||
},
|
||||
"total": {
|
||||
"type": "int"
|
||||
},
|
||||
"blocked": {
|
||||
"type": "int"
|
||||
},
|
||||
"unknown": {
|
||||
"type": "int"
|
||||
}
|
||||
}
|
||||
},
|
||||
"losErrorElevationUnavailable": "Elevation data unavailable for one or more samples.",
|
||||
"losErrorInvalidInput": "Invalid points/elevation data for LOS calculation.",
|
||||
"losRenameCustomPoint": "Rename custom point",
|
||||
"losPointName": "Point name",
|
||||
"losShowPanelTooltip": "Show LOS panel",
|
||||
"losHidePanelTooltip": "Hide LOS panel",
|
||||
"losElevationAttribution": "Elevation data: Open-Meteo (CC BY 4.0)",
|
||||
"contacts_pathTrace": "Path Trace",
|
||||
"contacts_ping": "Ping",
|
||||
"contacts_repeaterPathTrace": "Path trace to repeater",
|
||||
|
|
|
|||
|
|
@ -1627,5 +1627,120 @@
|
|||
"chat_ShowAllPaths": "Mostrar todos los caminos",
|
||||
"settings_clientRepeatFreqWarning": "Para la comunicación fuera de la red, se requiere una frecuencia de 433, 869 o 918 MHz.",
|
||||
"settings_clientRepeat": "Repetir sin conexión",
|
||||
"settings_clientRepeatSubtitle": "Permita que este dispositivo repita los paquetes de red para otros usuarios."
|
||||
"settings_clientRepeatSubtitle": "Permita que este dispositivo repita los paquetes de red para otros usuarios.",
|
||||
"settings_aboutOpenMeteoAttribution": "Datos de elevación LOS: Open-Meteo (CC BY 4.0)",
|
||||
"appSettings_unitsTitle": "Unidades",
|
||||
"appSettings_unitsMetric": "Métrico (m/km)",
|
||||
"appSettings_unitsImperial": "Imperial (pies/millas)",
|
||||
"map_lineOfSight": "Línea de visión",
|
||||
"map_losScreenTitle": "Línea de visión",
|
||||
"losSelectStartEnd": "Seleccione los nodos de inicio y fin para LOS.",
|
||||
"losRunFailed": "Error en la comprobación de la línea de visión: {error}",
|
||||
"@losRunFailed": {
|
||||
"placeholders": {
|
||||
"error": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"losClearAllPoints": "Borrar todos los puntos",
|
||||
"losRunToViewElevationProfile": "Ejecute LOS para ver el perfil de elevación",
|
||||
"losMenuTitle": "Menú LOS",
|
||||
"losMenuSubtitle": "Toque nodos o mantenga presionado el mapa para puntos personalizados",
|
||||
"losShowDisplayNodes": "Mostrar nodos de visualización",
|
||||
"losCustomPoints": "Puntos personalizados",
|
||||
"losCustomPointLabel": "Personalizado {index}",
|
||||
"@losCustomPointLabel": {
|
||||
"placeholders": {
|
||||
"index": {
|
||||
"type": "int"
|
||||
}
|
||||
}
|
||||
},
|
||||
"losPointA": "Punto A",
|
||||
"losPointB": "Punto B",
|
||||
"losAntennaA": "Antena A: {value} {unit}",
|
||||
"@losAntennaA": {
|
||||
"placeholders": {
|
||||
"value": {
|
||||
"type": "String"
|
||||
},
|
||||
"unit": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"losAntennaB": "Antena B: {value} {unit}",
|
||||
"@losAntennaB": {
|
||||
"placeholders": {
|
||||
"value": {
|
||||
"type": "String"
|
||||
},
|
||||
"unit": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"losRun": "Ejecutar LOS",
|
||||
"losNoElevationData": "Sin datos de elevación",
|
||||
"losProfileClear": "{distance} {distanceUnit}, despejar LOS, autorización mínima {clearance} {heightUnit}",
|
||||
"@losProfileClear": {
|
||||
"placeholders": {
|
||||
"distance": {
|
||||
"type": "String"
|
||||
},
|
||||
"distanceUnit": {
|
||||
"type": "String"
|
||||
},
|
||||
"clearance": {
|
||||
"type": "String"
|
||||
},
|
||||
"heightUnit": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"losProfileBlocked": "{distance} {distanceUnit}, bloqueado por {obstruction} {heightUnit}",
|
||||
"@losProfileBlocked": {
|
||||
"placeholders": {
|
||||
"distance": {
|
||||
"type": "String"
|
||||
},
|
||||
"distanceUnit": {
|
||||
"type": "String"
|
||||
},
|
||||
"obstruction": {
|
||||
"type": "String"
|
||||
},
|
||||
"heightUnit": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"losStatusChecking": "LOS: comprobando...",
|
||||
"losStatusNoData": "LOS: sin datos",
|
||||
"losStatusSummary": "LOS: {clear}/{total} claro, {blocked} bloqueado, {unknown} desconocido",
|
||||
"@losStatusSummary": {
|
||||
"placeholders": {
|
||||
"clear": {
|
||||
"type": "int"
|
||||
},
|
||||
"total": {
|
||||
"type": "int"
|
||||
},
|
||||
"blocked": {
|
||||
"type": "int"
|
||||
},
|
||||
"unknown": {
|
||||
"type": "int"
|
||||
}
|
||||
}
|
||||
},
|
||||
"losErrorElevationUnavailable": "Datos de elevación no disponibles para una o más muestras.",
|
||||
"losErrorInvalidInput": "Datos de puntos/elevación no válidos para el cálculo de LOS.",
|
||||
"losRenameCustomPoint": "Cambiar el nombre del punto personalizado",
|
||||
"losPointName": "Nombre del punto",
|
||||
"losShowPanelTooltip": "Mostrar panel LOS",
|
||||
"losHidePanelTooltip": "Ocultar panel LOS",
|
||||
"losElevationAttribution": "Datos de elevación: Open-Meteo (CC BY 4.0)"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1599,5 +1599,120 @@
|
|||
"chat_ShowAllPaths": "Afficher tous les chemins",
|
||||
"settings_clientRepeatFreqWarning": "Pour les transmissions hors réseau, il est nécessaire d'utiliser les fréquences de 433, 869 ou 918 MHz.",
|
||||
"settings_clientRepeatSubtitle": "Permettez à cet appareil de répéter les paquets de données pour les autres.",
|
||||
"settings_clientRepeat": "Répétition hors réseau"
|
||||
"settings_clientRepeat": "Répétition hors réseau",
|
||||
"settings_aboutOpenMeteoAttribution": "Données d'élévation LOS : Open-Meteo (CC BY 4.0)",
|
||||
"appSettings_unitsTitle": "Unités",
|
||||
"appSettings_unitsMetric": "Métrique (m/km)",
|
||||
"appSettings_unitsImperial": "Impérial (ft / mi)",
|
||||
"map_lineOfSight": "Ligne de vue",
|
||||
"map_losScreenTitle": "Ligne de vue",
|
||||
"losSelectStartEnd": "Sélectionnez les nœuds de début et de fin pour LOS.",
|
||||
"losRunFailed": "Échec de la vérification de la ligne de vue : {error}",
|
||||
"@losRunFailed": {
|
||||
"placeholders": {
|
||||
"error": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"losClearAllPoints": "Effacer tous les points",
|
||||
"losRunToViewElevationProfile": "Exécutez LOS pour afficher le profil d'altitude",
|
||||
"losMenuTitle": "Menu LOS",
|
||||
"losMenuSubtitle": "Appuyez sur les nœuds ou appuyez longuement sur la carte pour des points personnalisés",
|
||||
"losShowDisplayNodes": "Afficher les nœuds d'affichage",
|
||||
"losCustomPoints": "Points personnalisés",
|
||||
"losCustomPointLabel": "Personnalisé {index}",
|
||||
"@losCustomPointLabel": {
|
||||
"placeholders": {
|
||||
"index": {
|
||||
"type": "int"
|
||||
}
|
||||
}
|
||||
},
|
||||
"losPointA": "Point A",
|
||||
"losPointB": "Point B",
|
||||
"losAntennaA": "Antenne A : {value} {unit}",
|
||||
"@losAntennaA": {
|
||||
"placeholders": {
|
||||
"value": {
|
||||
"type": "String"
|
||||
},
|
||||
"unit": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"losAntennaB": "Antenne B : {value} {unit}",
|
||||
"@losAntennaB": {
|
||||
"placeholders": {
|
||||
"value": {
|
||||
"type": "String"
|
||||
},
|
||||
"unit": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"losRun": "Exécuter la LOS",
|
||||
"losNoElevationData": "Aucune donnée d'altitude",
|
||||
"losProfileClear": "{distance} {distanceUnit}, LOS clair, clairance minimale {clearance} {heightUnit}",
|
||||
"@losProfileClear": {
|
||||
"placeholders": {
|
||||
"distance": {
|
||||
"type": "String"
|
||||
},
|
||||
"distanceUnit": {
|
||||
"type": "String"
|
||||
},
|
||||
"clearance": {
|
||||
"type": "String"
|
||||
},
|
||||
"heightUnit": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"losProfileBlocked": "{distance} {distanceUnit}, bloqué par {obstruction} {heightUnit}",
|
||||
"@losProfileBlocked": {
|
||||
"placeholders": {
|
||||
"distance": {
|
||||
"type": "String"
|
||||
},
|
||||
"distanceUnit": {
|
||||
"type": "String"
|
||||
},
|
||||
"obstruction": {
|
||||
"type": "String"
|
||||
},
|
||||
"heightUnit": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"losStatusChecking": "LOS : vérification...",
|
||||
"losStatusNoData": "LOS : aucune donnée",
|
||||
"losStatusSummary": "LOS : {clear}/{total} clair, {blocked} bloqué, {unknown} inconnu",
|
||||
"@losStatusSummary": {
|
||||
"placeholders": {
|
||||
"clear": {
|
||||
"type": "int"
|
||||
},
|
||||
"total": {
|
||||
"type": "int"
|
||||
},
|
||||
"blocked": {
|
||||
"type": "int"
|
||||
},
|
||||
"unknown": {
|
||||
"type": "int"
|
||||
}
|
||||
}
|
||||
},
|
||||
"losErrorElevationUnavailable": "Données d'altitude indisponibles pour un ou plusieurs échantillons.",
|
||||
"losErrorInvalidInput": "Données de points/d'altitude non valides pour le calcul de la LOS.",
|
||||
"losRenameCustomPoint": "Renommer le point personnalisé",
|
||||
"losPointName": "Nom du point",
|
||||
"losShowPanelTooltip": "Afficher le panneau LOS",
|
||||
"losHidePanelTooltip": "Masquer le panneau LOS",
|
||||
"losElevationAttribution": "Données d'altitude : Open-Meteo (CC BY 4.0)"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1599,5 +1599,120 @@
|
|||
"chat_ShowAllPaths": "Mostra tutti i percorsi",
|
||||
"settings_clientRepeat": "Ripetizione \"fuori dalla rete\"",
|
||||
"settings_clientRepeatFreqWarning": "Per la comunicazione fuori rete, è necessario utilizzare frequenze di 433, 869 o 918 MHz.",
|
||||
"settings_clientRepeatSubtitle": "Permetti a questo dispositivo di ripetere i pacchetti di rete per gli altri."
|
||||
"settings_clientRepeatSubtitle": "Permetti a questo dispositivo di ripetere i pacchetti di rete per gli altri.",
|
||||
"settings_aboutOpenMeteoAttribution": "Dati di elevazione LOS: Open-Meteo (CC BY 4.0)",
|
||||
"appSettings_unitsTitle": "Unità",
|
||||
"appSettings_unitsMetric": "Metrico (m/km)",
|
||||
"appSettings_unitsImperial": "Imperiale (ft / mi)",
|
||||
"map_lineOfSight": "Linea di vista",
|
||||
"map_losScreenTitle": "Linea di vista",
|
||||
"losSelectStartEnd": "Seleziona i nodi iniziali e finali per la LOS.",
|
||||
"losRunFailed": "Controllo della linea di vista fallito: {error}",
|
||||
"@losRunFailed": {
|
||||
"placeholders": {
|
||||
"error": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"losClearAllPoints": "Cancella tutti i punti",
|
||||
"losRunToViewElevationProfile": "Eseguire LOS per visualizzare il profilo altimetrico",
|
||||
"losMenuTitle": "Menù LOS",
|
||||
"losMenuSubtitle": "Tocca i nodi o premi a lungo la mappa per punti personalizzati",
|
||||
"losShowDisplayNodes": "Mostra i nodi di visualizzazione",
|
||||
"losCustomPoints": "Punti personalizzati",
|
||||
"losCustomPointLabel": "Personalizzato {index}",
|
||||
"@losCustomPointLabel": {
|
||||
"placeholders": {
|
||||
"index": {
|
||||
"type": "int"
|
||||
}
|
||||
}
|
||||
},
|
||||
"losPointA": "Punto A",
|
||||
"losPointB": "Punto B",
|
||||
"losAntennaA": "Antenna A: {value} {unit}",
|
||||
"@losAntennaA": {
|
||||
"placeholders": {
|
||||
"value": {
|
||||
"type": "String"
|
||||
},
|
||||
"unit": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"losAntennaB": "Antenna B: {value} {unit}",
|
||||
"@losAntennaB": {
|
||||
"placeholders": {
|
||||
"value": {
|
||||
"type": "String"
|
||||
},
|
||||
"unit": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"losRun": "Esegui LOS",
|
||||
"losNoElevationData": "Nessun dato di elevazione",
|
||||
"losProfileClear": "{distance} {distanceUnit}, libera LOS, distanza minima {clearance} {heightUnit}",
|
||||
"@losProfileClear": {
|
||||
"placeholders": {
|
||||
"distance": {
|
||||
"type": "String"
|
||||
},
|
||||
"distanceUnit": {
|
||||
"type": "String"
|
||||
},
|
||||
"clearance": {
|
||||
"type": "String"
|
||||
},
|
||||
"heightUnit": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"losProfileBlocked": "{distance} {distanceUnit}, bloccato da {obstruction} {heightUnit}",
|
||||
"@losProfileBlocked": {
|
||||
"placeholders": {
|
||||
"distance": {
|
||||
"type": "String"
|
||||
},
|
||||
"distanceUnit": {
|
||||
"type": "String"
|
||||
},
|
||||
"obstruction": {
|
||||
"type": "String"
|
||||
},
|
||||
"heightUnit": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"losStatusChecking": "LOS: controllo...",
|
||||
"losStatusNoData": "LOS: nessun dato",
|
||||
"losStatusSummary": "LOS: {clear}/{total} libera, {blocked} bloccato, {unknown} sconosciuto",
|
||||
"@losStatusSummary": {
|
||||
"placeholders": {
|
||||
"clear": {
|
||||
"type": "int"
|
||||
},
|
||||
"total": {
|
||||
"type": "int"
|
||||
},
|
||||
"blocked": {
|
||||
"type": "int"
|
||||
},
|
||||
"unknown": {
|
||||
"type": "int"
|
||||
}
|
||||
}
|
||||
},
|
||||
"losErrorElevationUnavailable": "Dati di elevazione non disponibili per uno o più campioni.",
|
||||
"losErrorInvalidInput": "Dati punti/elevazione non validi per il calcolo della LOS.",
|
||||
"losRenameCustomPoint": "Rinomina punto personalizzato",
|
||||
"losPointName": "Nome del punto",
|
||||
"losShowPanelTooltip": "Mostra il pannello LOS",
|
||||
"losHidePanelTooltip": "Nascondi il pannello LOS",
|
||||
"losElevationAttribution": "Dati di elevazione: Open-Meteo (CC BY 4.0)"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -700,6 +700,12 @@ abstract class AppLocalizations {
|
|||
/// **'An open-source Flutter client for MeshCore LoRa mesh networking devices.'**
|
||||
String get settings_aboutDescription;
|
||||
|
||||
/// No description provided for @settings_aboutOpenMeteoAttribution.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'LOS elevation data: Open-Meteo (CC BY 4.0)'**
|
||||
String get settings_aboutOpenMeteoAttribution;
|
||||
|
||||
/// No description provided for @settings_infoName.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
|
|
@ -1240,6 +1246,24 @@ abstract class AppLocalizations {
|
|||
/// **'Offline Map Cache'**
|
||||
String get appSettings_offlineMapCache;
|
||||
|
||||
/// No description provided for @appSettings_unitsTitle.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Units'**
|
||||
String get appSettings_unitsTitle;
|
||||
|
||||
/// No description provided for @appSettings_unitsMetric.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Metric (m / km)'**
|
||||
String get appSettings_unitsMetric;
|
||||
|
||||
/// No description provided for @appSettings_unitsImperial.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Imperial (ft / mi)'**
|
||||
String get appSettings_unitsImperial;
|
||||
|
||||
/// No description provided for @appSettings_noAreaSelected.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
|
|
@ -2290,6 +2314,18 @@ abstract class AppLocalizations {
|
|||
/// **'Node Map'**
|
||||
String get map_title;
|
||||
|
||||
/// No description provided for @map_lineOfSight.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Line of Sight'**
|
||||
String get map_lineOfSight;
|
||||
|
||||
/// No description provided for @map_losScreenTitle.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Line of Sight'**
|
||||
String get map_losScreenTitle;
|
||||
|
||||
/// No description provided for @map_noNodesWithLocation.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
|
|
@ -4772,6 +4808,178 @@ abstract class AppLocalizations {
|
|||
/// **'Clear path.'**
|
||||
String get pathTrace_clearTooltip;
|
||||
|
||||
/// No description provided for @losSelectStartEnd.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Select start and end nodes for LOS.'**
|
||||
String get losSelectStartEnd;
|
||||
|
||||
/// No description provided for @losRunFailed.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Line-of-sight check failed: {error}'**
|
||||
String losRunFailed(String error);
|
||||
|
||||
/// No description provided for @losClearAllPoints.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Clear all points'**
|
||||
String get losClearAllPoints;
|
||||
|
||||
/// No description provided for @losRunToViewElevationProfile.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Run LOS to view elevation profile'**
|
||||
String get losRunToViewElevationProfile;
|
||||
|
||||
/// No description provided for @losMenuTitle.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'LOS Menu'**
|
||||
String get losMenuTitle;
|
||||
|
||||
/// No description provided for @losMenuSubtitle.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Tap nodes or long-press map for custom points'**
|
||||
String get losMenuSubtitle;
|
||||
|
||||
/// No description provided for @losShowDisplayNodes.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Show display nodes'**
|
||||
String get losShowDisplayNodes;
|
||||
|
||||
/// No description provided for @losCustomPoints.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Custom points'**
|
||||
String get losCustomPoints;
|
||||
|
||||
/// No description provided for @losCustomPointLabel.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Custom {index}'**
|
||||
String losCustomPointLabel(int index);
|
||||
|
||||
/// No description provided for @losPointA.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Point A'**
|
||||
String get losPointA;
|
||||
|
||||
/// No description provided for @losPointB.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Point B'**
|
||||
String get losPointB;
|
||||
|
||||
/// No description provided for @losAntennaA.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Antenna A: {value} {unit}'**
|
||||
String losAntennaA(String value, String unit);
|
||||
|
||||
/// No description provided for @losAntennaB.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Antenna B: {value} {unit}'**
|
||||
String losAntennaB(String value, String unit);
|
||||
|
||||
/// No description provided for @losRun.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Run LOS'**
|
||||
String get losRun;
|
||||
|
||||
/// No description provided for @losNoElevationData.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'No elevation data'**
|
||||
String get losNoElevationData;
|
||||
|
||||
/// No description provided for @losProfileClear.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'{distance} {distanceUnit}, clear LOS, min clearance {clearance} {heightUnit}'**
|
||||
String losProfileClear(
|
||||
String distance,
|
||||
String distanceUnit,
|
||||
String clearance,
|
||||
String heightUnit,
|
||||
);
|
||||
|
||||
/// No description provided for @losProfileBlocked.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'{distance} {distanceUnit}, blocked by {obstruction} {heightUnit}'**
|
||||
String losProfileBlocked(
|
||||
String distance,
|
||||
String distanceUnit,
|
||||
String obstruction,
|
||||
String heightUnit,
|
||||
);
|
||||
|
||||
/// No description provided for @losStatusChecking.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'LOS: checking...'**
|
||||
String get losStatusChecking;
|
||||
|
||||
/// No description provided for @losStatusNoData.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'LOS: no data'**
|
||||
String get losStatusNoData;
|
||||
|
||||
/// No description provided for @losStatusSummary.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'LOS: {clear}/{total} clear, {blocked} blocked, {unknown} unknown'**
|
||||
String losStatusSummary(int clear, int total, int blocked, int unknown);
|
||||
|
||||
/// No description provided for @losErrorElevationUnavailable.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Elevation data unavailable for one or more samples.'**
|
||||
String get losErrorElevationUnavailable;
|
||||
|
||||
/// No description provided for @losErrorInvalidInput.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Invalid points/elevation data for LOS calculation.'**
|
||||
String get losErrorInvalidInput;
|
||||
|
||||
/// No description provided for @losRenameCustomPoint.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Rename custom point'**
|
||||
String get losRenameCustomPoint;
|
||||
|
||||
/// No description provided for @losPointName.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Point name'**
|
||||
String get losPointName;
|
||||
|
||||
/// No description provided for @losShowPanelTooltip.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Show LOS panel'**
|
||||
String get losShowPanelTooltip;
|
||||
|
||||
/// No description provided for @losHidePanelTooltip.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Hide LOS panel'**
|
||||
String get losHidePanelTooltip;
|
||||
|
||||
/// No description provided for @losElevationAttribution.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Elevation data: Open-Meteo (CC BY 4.0)'**
|
||||
String get losElevationAttribution;
|
||||
|
||||
/// No description provided for @contacts_pathTrace.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
|
|
|
|||
|
|
@ -326,6 +326,10 @@ class AppLocalizationsBg extends AppLocalizations {
|
|||
String get settings_aboutDescription =>
|
||||
'Отворен софтуер за Flutter клиент за MeshCore LoRa мрежови устройства.';
|
||||
|
||||
@override
|
||||
String get settings_aboutOpenMeteoAttribution =>
|
||||
'Данни за надморска височина на LOS: Open-Meteo (CC BY 4.0)';
|
||||
|
||||
@override
|
||||
String get settings_infoName => 'Име';
|
||||
|
||||
|
|
@ -622,6 +626,15 @@ class AppLocalizationsBg extends AppLocalizations {
|
|||
@override
|
||||
String get appSettings_offlineMapCache => 'Кеш на офлайн карти';
|
||||
|
||||
@override
|
||||
String get appSettings_unitsTitle => 'единици';
|
||||
|
||||
@override
|
||||
String get appSettings_unitsMetric => 'Метрика (m / km)';
|
||||
|
||||
@override
|
||||
String get appSettings_unitsImperial => 'Имперска (ft / mi)';
|
||||
|
||||
@override
|
||||
String get appSettings_noAreaSelected => 'Няма избрана област';
|
||||
|
||||
|
|
@ -1243,6 +1256,12 @@ class AppLocalizationsBg extends AppLocalizations {
|
|||
@override
|
||||
String get map_title => 'Карта на възлите';
|
||||
|
||||
@override
|
||||
String get map_lineOfSight => 'Линия на видимост';
|
||||
|
||||
@override
|
||||
String get map_losScreenTitle => 'Линия на видимост';
|
||||
|
||||
@override
|
||||
String get map_noNodesWithLocation => 'Няма възли с данни за местоположение.';
|
||||
|
||||
|
|
@ -2724,6 +2743,116 @@ class AppLocalizationsBg extends AppLocalizations {
|
|||
@override
|
||||
String get pathTrace_clearTooltip => 'Изчисти пътя';
|
||||
|
||||
@override
|
||||
String get losSelectStartEnd => 'Изберете начални и крайни възли за LOS.';
|
||||
|
||||
@override
|
||||
String losRunFailed(String error) {
|
||||
return 'Проверката на пряката видимост е неуспешна: $error';
|
||||
}
|
||||
|
||||
@override
|
||||
String get losClearAllPoints => 'Изчистете всички точки';
|
||||
|
||||
@override
|
||||
String get losRunToViewElevationProfile =>
|
||||
'Стартирайте LOS, за да видите профила на надморската височина';
|
||||
|
||||
@override
|
||||
String get losMenuTitle => 'LOS меню';
|
||||
|
||||
@override
|
||||
String get losMenuSubtitle =>
|
||||
'Докоснете възли или натиснете продължително карта за персонализирани точки';
|
||||
|
||||
@override
|
||||
String get losShowDisplayNodes => 'Показване на възли на дисплея';
|
||||
|
||||
@override
|
||||
String get losCustomPoints => 'Персонализирани точки';
|
||||
|
||||
@override
|
||||
String losCustomPointLabel(int index) {
|
||||
return 'Персонализирано $index';
|
||||
}
|
||||
|
||||
@override
|
||||
String get losPointA => 'Точка А';
|
||||
|
||||
@override
|
||||
String get losPointB => 'Точка Б';
|
||||
|
||||
@override
|
||||
String losAntennaA(String value, String unit) {
|
||||
return 'Антена A: $value $unit';
|
||||
}
|
||||
|
||||
@override
|
||||
String losAntennaB(String value, String unit) {
|
||||
return 'Антена B: $value $unit';
|
||||
}
|
||||
|
||||
@override
|
||||
String get losRun => 'Стартирайте LOS';
|
||||
|
||||
@override
|
||||
String get losNoElevationData => 'Няма данни за надморска височина';
|
||||
|
||||
@override
|
||||
String losProfileClear(
|
||||
String distance,
|
||||
String distanceUnit,
|
||||
String clearance,
|
||||
String heightUnit,
|
||||
) {
|
||||
return '$distance $distanceUnit, чист LOS, минимално разстояние $clearance $heightUnit';
|
||||
}
|
||||
|
||||
@override
|
||||
String losProfileBlocked(
|
||||
String distance,
|
||||
String distanceUnit,
|
||||
String obstruction,
|
||||
String heightUnit,
|
||||
) {
|
||||
return '$distance $distanceUnit, блокиран от $obstruction $heightUnit';
|
||||
}
|
||||
|
||||
@override
|
||||
String get losStatusChecking => 'LOS: проверка...';
|
||||
|
||||
@override
|
||||
String get losStatusNoData => 'LOS: няма данни';
|
||||
|
||||
@override
|
||||
String losStatusSummary(int clear, int total, int blocked, int unknown) {
|
||||
return 'LOS: $clear/$total ясно, $blocked блокирано, $unknown неизвестно';
|
||||
}
|
||||
|
||||
@override
|
||||
String get losErrorElevationUnavailable =>
|
||||
'Няма налични данни за надморска височина за една или повече проби.';
|
||||
|
||||
@override
|
||||
String get losErrorInvalidInput =>
|
||||
'Невалидни данни за точки/надморска височина за изчисляване на LOS.';
|
||||
|
||||
@override
|
||||
String get losRenameCustomPoint => 'Преименувайте персонализирана точка';
|
||||
|
||||
@override
|
||||
String get losPointName => 'Име на точката';
|
||||
|
||||
@override
|
||||
String get losShowPanelTooltip => 'Показване на LOS панел';
|
||||
|
||||
@override
|
||||
String get losHidePanelTooltip => 'Скриване на LOS панела';
|
||||
|
||||
@override
|
||||
String get losElevationAttribution =>
|
||||
'Данни за надморска височина: Open-Meteo (CC BY 4.0)';
|
||||
|
||||
@override
|
||||
String get contacts_pathTrace => 'Пътен проследяване';
|
||||
|
||||
|
|
|
|||
|
|
@ -320,6 +320,10 @@ class AppLocalizationsDe extends AppLocalizations {
|
|||
String get settings_aboutDescription =>
|
||||
'Ein Open-Source-Flutter-Client für MeshCore LoRa-Meshnetzwerkgeräte.';
|
||||
|
||||
@override
|
||||
String get settings_aboutOpenMeteoAttribution =>
|
||||
'LOS-Höhendaten: Open-Meteo (CC BY 4.0)';
|
||||
|
||||
@override
|
||||
String get settings_infoName => 'Name';
|
||||
|
||||
|
|
@ -619,6 +623,15 @@ class AppLocalizationsDe extends AppLocalizations {
|
|||
@override
|
||||
String get appSettings_offlineMapCache => 'Offline-Karten-Cache';
|
||||
|
||||
@override
|
||||
String get appSettings_unitsTitle => 'Einheiten';
|
||||
|
||||
@override
|
||||
String get appSettings_unitsMetric => 'Metrisch (m/km)';
|
||||
|
||||
@override
|
||||
String get appSettings_unitsImperial => 'Imperial (ft/mi)';
|
||||
|
||||
@override
|
||||
String get appSettings_noAreaSelected => 'Kein Bereich ausgewählt';
|
||||
|
||||
|
|
@ -1242,6 +1255,12 @@ class AppLocalizationsDe extends AppLocalizations {
|
|||
@override
|
||||
String get map_title => 'Karte';
|
||||
|
||||
@override
|
||||
String get map_lineOfSight => 'Sichtlinie';
|
||||
|
||||
@override
|
||||
String get map_losScreenTitle => 'Sichtlinie';
|
||||
|
||||
@override
|
||||
String get map_noNodesWithLocation => 'Keine Knoten mit Standortdaten';
|
||||
|
||||
|
|
@ -2729,6 +2748,117 @@ class AppLocalizationsDe extends AppLocalizations {
|
|||
@override
|
||||
String get pathTrace_clearTooltip => 'Pfad löschen';
|
||||
|
||||
@override
|
||||
String get losSelectStartEnd =>
|
||||
'Wählen Sie Start- und Endknoten für LOS aus.';
|
||||
|
||||
@override
|
||||
String losRunFailed(String error) {
|
||||
return 'Sichtlinienprüfung fehlgeschlagen: $error';
|
||||
}
|
||||
|
||||
@override
|
||||
String get losClearAllPoints => 'Löschen Sie alle Punkte';
|
||||
|
||||
@override
|
||||
String get losRunToViewElevationProfile =>
|
||||
'Führen Sie LOS aus, um das Höhenprofil anzuzeigen';
|
||||
|
||||
@override
|
||||
String get losMenuTitle => 'LOS-Menü';
|
||||
|
||||
@override
|
||||
String get losMenuSubtitle =>
|
||||
'Tippen Sie auf Knoten oder drücken Sie lange auf die Karte, um benutzerdefinierte Punkte anzuzeigen';
|
||||
|
||||
@override
|
||||
String get losShowDisplayNodes => 'Anzeigeknoten anzeigen';
|
||||
|
||||
@override
|
||||
String get losCustomPoints => 'Benutzerdefinierte Punkte';
|
||||
|
||||
@override
|
||||
String losCustomPointLabel(int index) {
|
||||
return 'Benutzerdefiniert $index';
|
||||
}
|
||||
|
||||
@override
|
||||
String get losPointA => 'Punkt A';
|
||||
|
||||
@override
|
||||
String get losPointB => 'Punkt B';
|
||||
|
||||
@override
|
||||
String losAntennaA(String value, String unit) {
|
||||
return 'Antenne A: $value $unit';
|
||||
}
|
||||
|
||||
@override
|
||||
String losAntennaB(String value, String unit) {
|
||||
return 'Antenne B: $value $unit';
|
||||
}
|
||||
|
||||
@override
|
||||
String get losRun => 'Führen Sie LOS aus';
|
||||
|
||||
@override
|
||||
String get losNoElevationData => 'Keine Höhendaten';
|
||||
|
||||
@override
|
||||
String losProfileClear(
|
||||
String distance,
|
||||
String distanceUnit,
|
||||
String clearance,
|
||||
String heightUnit,
|
||||
) {
|
||||
return '$distance $distanceUnit, freie Sichtlinie, Mindestabstand $clearance $heightUnit';
|
||||
}
|
||||
|
||||
@override
|
||||
String losProfileBlocked(
|
||||
String distance,
|
||||
String distanceUnit,
|
||||
String obstruction,
|
||||
String heightUnit,
|
||||
) {
|
||||
return '$distance $distanceUnit, blockiert durch $obstruction $heightUnit';
|
||||
}
|
||||
|
||||
@override
|
||||
String get losStatusChecking => 'LOS: Überprüfen...';
|
||||
|
||||
@override
|
||||
String get losStatusNoData => 'LOS: keine Daten';
|
||||
|
||||
@override
|
||||
String losStatusSummary(int clear, int total, int blocked, int unknown) {
|
||||
return 'Sichtlinie: $clear/$total frei, $blocked blockiert, $unknown unbekannt';
|
||||
}
|
||||
|
||||
@override
|
||||
String get losErrorElevationUnavailable =>
|
||||
'Für eine oder mehrere Proben sind keine Höhendaten verfügbar.';
|
||||
|
||||
@override
|
||||
String get losErrorInvalidInput =>
|
||||
'Ungültige Punkte/Höhendaten für die LOS-Berechnung.';
|
||||
|
||||
@override
|
||||
String get losRenameCustomPoint =>
|
||||
'Benennen Sie den benutzerdefinierten Punkt um';
|
||||
|
||||
@override
|
||||
String get losPointName => 'Punktname';
|
||||
|
||||
@override
|
||||
String get losShowPanelTooltip => 'LOS-Panel anzeigen';
|
||||
|
||||
@override
|
||||
String get losHidePanelTooltip => 'LOS-Panel ausblenden';
|
||||
|
||||
@override
|
||||
String get losElevationAttribution => 'Höhendaten: Open-Meteo (CC BY 4.0)';
|
||||
|
||||
@override
|
||||
String get contacts_pathTrace => 'Pfadverfolgung';
|
||||
|
||||
|
|
|
|||
|
|
@ -318,6 +318,10 @@ class AppLocalizationsEn extends AppLocalizations {
|
|||
String get settings_aboutDescription =>
|
||||
'An open-source Flutter client for MeshCore LoRa mesh networking devices.';
|
||||
|
||||
@override
|
||||
String get settings_aboutOpenMeteoAttribution =>
|
||||
'LOS elevation data: Open-Meteo (CC BY 4.0)';
|
||||
|
||||
@override
|
||||
String get settings_infoName => 'Name';
|
||||
|
||||
|
|
@ -614,6 +618,15 @@ class AppLocalizationsEn extends AppLocalizations {
|
|||
@override
|
||||
String get appSettings_offlineMapCache => 'Offline Map Cache';
|
||||
|
||||
@override
|
||||
String get appSettings_unitsTitle => 'Units';
|
||||
|
||||
@override
|
||||
String get appSettings_unitsMetric => 'Metric (m / km)';
|
||||
|
||||
@override
|
||||
String get appSettings_unitsImperial => 'Imperial (ft / mi)';
|
||||
|
||||
@override
|
||||
String get appSettings_noAreaSelected => 'No area selected';
|
||||
|
||||
|
|
@ -1222,6 +1235,12 @@ class AppLocalizationsEn extends AppLocalizations {
|
|||
@override
|
||||
String get map_title => 'Node Map';
|
||||
|
||||
@override
|
||||
String get map_lineOfSight => 'Line of Sight';
|
||||
|
||||
@override
|
||||
String get map_losScreenTitle => 'Line of Sight';
|
||||
|
||||
@override
|
||||
String get map_noNodesWithLocation => 'No nodes with location data';
|
||||
|
||||
|
|
@ -2683,6 +2702,115 @@ class AppLocalizationsEn extends AppLocalizations {
|
|||
@override
|
||||
String get pathTrace_clearTooltip => 'Clear path.';
|
||||
|
||||
@override
|
||||
String get losSelectStartEnd => 'Select start and end nodes for LOS.';
|
||||
|
||||
@override
|
||||
String losRunFailed(String error) {
|
||||
return 'Line-of-sight check failed: $error';
|
||||
}
|
||||
|
||||
@override
|
||||
String get losClearAllPoints => 'Clear all points';
|
||||
|
||||
@override
|
||||
String get losRunToViewElevationProfile =>
|
||||
'Run LOS to view elevation profile';
|
||||
|
||||
@override
|
||||
String get losMenuTitle => 'LOS Menu';
|
||||
|
||||
@override
|
||||
String get losMenuSubtitle => 'Tap nodes or long-press map for custom points';
|
||||
|
||||
@override
|
||||
String get losShowDisplayNodes => 'Show display nodes';
|
||||
|
||||
@override
|
||||
String get losCustomPoints => 'Custom points';
|
||||
|
||||
@override
|
||||
String losCustomPointLabel(int index) {
|
||||
return 'Custom $index';
|
||||
}
|
||||
|
||||
@override
|
||||
String get losPointA => 'Point A';
|
||||
|
||||
@override
|
||||
String get losPointB => 'Point B';
|
||||
|
||||
@override
|
||||
String losAntennaA(String value, String unit) {
|
||||
return 'Antenna A: $value $unit';
|
||||
}
|
||||
|
||||
@override
|
||||
String losAntennaB(String value, String unit) {
|
||||
return 'Antenna B: $value $unit';
|
||||
}
|
||||
|
||||
@override
|
||||
String get losRun => 'Run LOS';
|
||||
|
||||
@override
|
||||
String get losNoElevationData => 'No elevation data';
|
||||
|
||||
@override
|
||||
String losProfileClear(
|
||||
String distance,
|
||||
String distanceUnit,
|
||||
String clearance,
|
||||
String heightUnit,
|
||||
) {
|
||||
return '$distance $distanceUnit, clear LOS, min clearance $clearance $heightUnit';
|
||||
}
|
||||
|
||||
@override
|
||||
String losProfileBlocked(
|
||||
String distance,
|
||||
String distanceUnit,
|
||||
String obstruction,
|
||||
String heightUnit,
|
||||
) {
|
||||
return '$distance $distanceUnit, blocked by $obstruction $heightUnit';
|
||||
}
|
||||
|
||||
@override
|
||||
String get losStatusChecking => 'LOS: checking...';
|
||||
|
||||
@override
|
||||
String get losStatusNoData => 'LOS: no data';
|
||||
|
||||
@override
|
||||
String losStatusSummary(int clear, int total, int blocked, int unknown) {
|
||||
return 'LOS: $clear/$total clear, $blocked blocked, $unknown unknown';
|
||||
}
|
||||
|
||||
@override
|
||||
String get losErrorElevationUnavailable =>
|
||||
'Elevation data unavailable for one or more samples.';
|
||||
|
||||
@override
|
||||
String get losErrorInvalidInput =>
|
||||
'Invalid points/elevation data for LOS calculation.';
|
||||
|
||||
@override
|
||||
String get losRenameCustomPoint => 'Rename custom point';
|
||||
|
||||
@override
|
||||
String get losPointName => 'Point name';
|
||||
|
||||
@override
|
||||
String get losShowPanelTooltip => 'Show LOS panel';
|
||||
|
||||
@override
|
||||
String get losHidePanelTooltip => 'Hide LOS panel';
|
||||
|
||||
@override
|
||||
String get losElevationAttribution =>
|
||||
'Elevation data: Open-Meteo (CC BY 4.0)';
|
||||
|
||||
@override
|
||||
String get contacts_pathTrace => 'Path Trace';
|
||||
|
||||
|
|
|
|||
|
|
@ -323,6 +323,10 @@ class AppLocalizationsEs extends AppLocalizations {
|
|||
String get settings_aboutDescription =>
|
||||
'Un cliente de código abierto de Flutter para dispositivos de red mesh LoRa de MeshCore.';
|
||||
|
||||
@override
|
||||
String get settings_aboutOpenMeteoAttribution =>
|
||||
'Datos de elevación LOS: Open-Meteo (CC BY 4.0)';
|
||||
|
||||
@override
|
||||
String get settings_infoName => 'Nombre';
|
||||
|
||||
|
|
@ -620,6 +624,15 @@ class AppLocalizationsEs extends AppLocalizations {
|
|||
@override
|
||||
String get appSettings_offlineMapCache => 'Caché de Mapa Offline';
|
||||
|
||||
@override
|
||||
String get appSettings_unitsTitle => 'Unidades';
|
||||
|
||||
@override
|
||||
String get appSettings_unitsMetric => 'Métrico (m/km)';
|
||||
|
||||
@override
|
||||
String get appSettings_unitsImperial => 'Imperial (pies/millas)';
|
||||
|
||||
@override
|
||||
String get appSettings_noAreaSelected => 'No se ha seleccionado ningún área';
|
||||
|
||||
|
|
@ -1240,6 +1253,12 @@ class AppLocalizationsEs extends AppLocalizations {
|
|||
@override
|
||||
String get map_title => 'Mapa de Nodos';
|
||||
|
||||
@override
|
||||
String get map_lineOfSight => 'Línea de visión';
|
||||
|
||||
@override
|
||||
String get map_losScreenTitle => 'Línea de visión';
|
||||
|
||||
@override
|
||||
String get map_noNodesWithLocation => 'No hay nodos con datos de ubicación';
|
||||
|
||||
|
|
@ -2722,6 +2741,118 @@ class AppLocalizationsEs extends AppLocalizations {
|
|||
@override
|
||||
String get pathTrace_clearTooltip => 'Borrar ruta';
|
||||
|
||||
@override
|
||||
String get losSelectStartEnd =>
|
||||
'Seleccione los nodos de inicio y fin para LOS.';
|
||||
|
||||
@override
|
||||
String losRunFailed(String error) {
|
||||
return 'Error en la comprobación de la línea de visión: $error';
|
||||
}
|
||||
|
||||
@override
|
||||
String get losClearAllPoints => 'Borrar todos los puntos';
|
||||
|
||||
@override
|
||||
String get losRunToViewElevationProfile =>
|
||||
'Ejecute LOS para ver el perfil de elevación';
|
||||
|
||||
@override
|
||||
String get losMenuTitle => 'Menú LOS';
|
||||
|
||||
@override
|
||||
String get losMenuSubtitle =>
|
||||
'Toque nodos o mantenga presionado el mapa para puntos personalizados';
|
||||
|
||||
@override
|
||||
String get losShowDisplayNodes => 'Mostrar nodos de visualización';
|
||||
|
||||
@override
|
||||
String get losCustomPoints => 'Puntos personalizados';
|
||||
|
||||
@override
|
||||
String losCustomPointLabel(int index) {
|
||||
return 'Personalizado $index';
|
||||
}
|
||||
|
||||
@override
|
||||
String get losPointA => 'Punto A';
|
||||
|
||||
@override
|
||||
String get losPointB => 'Punto B';
|
||||
|
||||
@override
|
||||
String losAntennaA(String value, String unit) {
|
||||
return 'Antena A: $value $unit';
|
||||
}
|
||||
|
||||
@override
|
||||
String losAntennaB(String value, String unit) {
|
||||
return 'Antena B: $value $unit';
|
||||
}
|
||||
|
||||
@override
|
||||
String get losRun => 'Ejecutar LOS';
|
||||
|
||||
@override
|
||||
String get losNoElevationData => 'Sin datos de elevación';
|
||||
|
||||
@override
|
||||
String losProfileClear(
|
||||
String distance,
|
||||
String distanceUnit,
|
||||
String clearance,
|
||||
String heightUnit,
|
||||
) {
|
||||
return '$distance $distanceUnit, despejar LOS, autorización mínima $clearance $heightUnit';
|
||||
}
|
||||
|
||||
@override
|
||||
String losProfileBlocked(
|
||||
String distance,
|
||||
String distanceUnit,
|
||||
String obstruction,
|
||||
String heightUnit,
|
||||
) {
|
||||
return '$distance $distanceUnit, bloqueado por $obstruction $heightUnit';
|
||||
}
|
||||
|
||||
@override
|
||||
String get losStatusChecking => 'LOS: comprobando...';
|
||||
|
||||
@override
|
||||
String get losStatusNoData => 'LOS: sin datos';
|
||||
|
||||
@override
|
||||
String losStatusSummary(int clear, int total, int blocked, int unknown) {
|
||||
return 'LOS: $clear/$total claro, $blocked bloqueado, $unknown desconocido';
|
||||
}
|
||||
|
||||
@override
|
||||
String get losErrorElevationUnavailable =>
|
||||
'Datos de elevación no disponibles para una o más muestras.';
|
||||
|
||||
@override
|
||||
String get losErrorInvalidInput =>
|
||||
'Datos de puntos/elevación no válidos para el cálculo de LOS.';
|
||||
|
||||
@override
|
||||
String get losRenameCustomPoint =>
|
||||
'Cambiar el nombre del punto personalizado';
|
||||
|
||||
@override
|
||||
String get losPointName => 'Nombre del punto';
|
||||
|
||||
@override
|
||||
String get losShowPanelTooltip => 'Mostrar panel LOS';
|
||||
|
||||
@override
|
||||
String get losHidePanelTooltip => 'Ocultar panel LOS';
|
||||
|
||||
@override
|
||||
String get losElevationAttribution =>
|
||||
'Datos de elevación: Open-Meteo (CC BY 4.0)';
|
||||
|
||||
@override
|
||||
String get contacts_pathTrace => 'Rastreo de caminos';
|
||||
|
||||
|
|
|
|||
|
|
@ -324,6 +324,10 @@ class AppLocalizationsFr extends AppLocalizations {
|
|||
String get settings_aboutDescription =>
|
||||
'Un client Flutter open source pour les appareils de réseau mesh MeshCore LoRa.';
|
||||
|
||||
@override
|
||||
String get settings_aboutOpenMeteoAttribution =>
|
||||
'Données d\'élévation LOS : Open-Meteo (CC BY 4.0)';
|
||||
|
||||
@override
|
||||
String get settings_infoName => 'Nom';
|
||||
|
||||
|
|
@ -622,6 +626,15 @@ class AppLocalizationsFr extends AppLocalizations {
|
|||
@override
|
||||
String get appSettings_offlineMapCache => 'Cache de Carte Hors Ligne';
|
||||
|
||||
@override
|
||||
String get appSettings_unitsTitle => 'Unités';
|
||||
|
||||
@override
|
||||
String get appSettings_unitsMetric => 'Métrique (m/km)';
|
||||
|
||||
@override
|
||||
String get appSettings_unitsImperial => 'Impérial (ft / mi)';
|
||||
|
||||
@override
|
||||
String get appSettings_noAreaSelected => 'Aucune zone sélectionnée';
|
||||
|
||||
|
|
@ -1246,6 +1259,12 @@ class AppLocalizationsFr extends AppLocalizations {
|
|||
@override
|
||||
String get map_title => 'Carte des nœuds';
|
||||
|
||||
@override
|
||||
String get map_lineOfSight => 'Ligne de vue';
|
||||
|
||||
@override
|
||||
String get map_losScreenTitle => 'Ligne de vue';
|
||||
|
||||
@override
|
||||
String get map_noNodesWithLocation =>
|
||||
'Aucun nœud avec des données de localisation';
|
||||
|
|
@ -2738,6 +2757,117 @@ class AppLocalizationsFr extends AppLocalizations {
|
|||
@override
|
||||
String get pathTrace_clearTooltip => 'Effacer le chemin';
|
||||
|
||||
@override
|
||||
String get losSelectStartEnd =>
|
||||
'Sélectionnez les nœuds de début et de fin pour LOS.';
|
||||
|
||||
@override
|
||||
String losRunFailed(String error) {
|
||||
return 'Échec de la vérification de la ligne de vue : $error';
|
||||
}
|
||||
|
||||
@override
|
||||
String get losClearAllPoints => 'Effacer tous les points';
|
||||
|
||||
@override
|
||||
String get losRunToViewElevationProfile =>
|
||||
'Exécutez LOS pour afficher le profil d\'altitude';
|
||||
|
||||
@override
|
||||
String get losMenuTitle => 'Menu LOS';
|
||||
|
||||
@override
|
||||
String get losMenuSubtitle =>
|
||||
'Appuyez sur les nœuds ou appuyez longuement sur la carte pour des points personnalisés';
|
||||
|
||||
@override
|
||||
String get losShowDisplayNodes => 'Afficher les nœuds d\'affichage';
|
||||
|
||||
@override
|
||||
String get losCustomPoints => 'Points personnalisés';
|
||||
|
||||
@override
|
||||
String losCustomPointLabel(int index) {
|
||||
return 'Personnalisé $index';
|
||||
}
|
||||
|
||||
@override
|
||||
String get losPointA => 'Point A';
|
||||
|
||||
@override
|
||||
String get losPointB => 'Point B';
|
||||
|
||||
@override
|
||||
String losAntennaA(String value, String unit) {
|
||||
return 'Antenne A : $value $unit';
|
||||
}
|
||||
|
||||
@override
|
||||
String losAntennaB(String value, String unit) {
|
||||
return 'Antenne B : $value $unit';
|
||||
}
|
||||
|
||||
@override
|
||||
String get losRun => 'Exécuter la LOS';
|
||||
|
||||
@override
|
||||
String get losNoElevationData => 'Aucune donnée d\'altitude';
|
||||
|
||||
@override
|
||||
String losProfileClear(
|
||||
String distance,
|
||||
String distanceUnit,
|
||||
String clearance,
|
||||
String heightUnit,
|
||||
) {
|
||||
return '$distance $distanceUnit, LOS clair, clairance minimale $clearance $heightUnit';
|
||||
}
|
||||
|
||||
@override
|
||||
String losProfileBlocked(
|
||||
String distance,
|
||||
String distanceUnit,
|
||||
String obstruction,
|
||||
String heightUnit,
|
||||
) {
|
||||
return '$distance $distanceUnit, bloqué par $obstruction $heightUnit';
|
||||
}
|
||||
|
||||
@override
|
||||
String get losStatusChecking => 'LOS : vérification...';
|
||||
|
||||
@override
|
||||
String get losStatusNoData => 'LOS : aucune donnée';
|
||||
|
||||
@override
|
||||
String losStatusSummary(int clear, int total, int blocked, int unknown) {
|
||||
return 'LOS : $clear/$total clair, $blocked bloqué, $unknown inconnu';
|
||||
}
|
||||
|
||||
@override
|
||||
String get losErrorElevationUnavailable =>
|
||||
'Données d\'altitude indisponibles pour un ou plusieurs échantillons.';
|
||||
|
||||
@override
|
||||
String get losErrorInvalidInput =>
|
||||
'Données de points/d\'altitude non valides pour le calcul de la LOS.';
|
||||
|
||||
@override
|
||||
String get losRenameCustomPoint => 'Renommer le point personnalisé';
|
||||
|
||||
@override
|
||||
String get losPointName => 'Nom du point';
|
||||
|
||||
@override
|
||||
String get losShowPanelTooltip => 'Afficher le panneau LOS';
|
||||
|
||||
@override
|
||||
String get losHidePanelTooltip => 'Masquer le panneau LOS';
|
||||
|
||||
@override
|
||||
String get losElevationAttribution =>
|
||||
'Données d\'altitude : Open-Meteo (CC BY 4.0)';
|
||||
|
||||
@override
|
||||
String get contacts_pathTrace => 'Traçage de chemin';
|
||||
|
||||
|
|
|
|||
|
|
@ -322,6 +322,10 @@ class AppLocalizationsIt extends AppLocalizations {
|
|||
String get settings_aboutDescription =>
|
||||
'Un client Flutter open-source per i dispositivi di rete mesh LoRa Core di MeshCore.';
|
||||
|
||||
@override
|
||||
String get settings_aboutOpenMeteoAttribution =>
|
||||
'Dati di elevazione LOS: Open-Meteo (CC BY 4.0)';
|
||||
|
||||
@override
|
||||
String get settings_infoName => 'Nome';
|
||||
|
||||
|
|
@ -619,6 +623,15 @@ class AppLocalizationsIt extends AppLocalizations {
|
|||
@override
|
||||
String get appSettings_offlineMapCache => 'Cache Mappa Offline';
|
||||
|
||||
@override
|
||||
String get appSettings_unitsTitle => 'Unità';
|
||||
|
||||
@override
|
||||
String get appSettings_unitsMetric => 'Metrico (m/km)';
|
||||
|
||||
@override
|
||||
String get appSettings_unitsImperial => 'Imperiale (ft / mi)';
|
||||
|
||||
@override
|
||||
String get appSettings_noAreaSelected => 'Nessun\'area selezionata';
|
||||
|
||||
|
|
@ -1239,6 +1252,12 @@ class AppLocalizationsIt extends AppLocalizations {
|
|||
@override
|
||||
String get map_title => 'Mappa Nodi';
|
||||
|
||||
@override
|
||||
String get map_lineOfSight => 'Linea di vista';
|
||||
|
||||
@override
|
||||
String get map_losScreenTitle => 'Linea di vista';
|
||||
|
||||
@override
|
||||
String get map_noNodesWithLocation => 'Nessun nodo con dati di posizione';
|
||||
|
||||
|
|
@ -2723,6 +2742,117 @@ class AppLocalizationsIt extends AppLocalizations {
|
|||
@override
|
||||
String get pathTrace_clearTooltip => 'Pulisci percorso';
|
||||
|
||||
@override
|
||||
String get losSelectStartEnd =>
|
||||
'Seleziona i nodi iniziali e finali per la LOS.';
|
||||
|
||||
@override
|
||||
String losRunFailed(String error) {
|
||||
return 'Controllo della linea di vista fallito: $error';
|
||||
}
|
||||
|
||||
@override
|
||||
String get losClearAllPoints => 'Cancella tutti i punti';
|
||||
|
||||
@override
|
||||
String get losRunToViewElevationProfile =>
|
||||
'Eseguire LOS per visualizzare il profilo altimetrico';
|
||||
|
||||
@override
|
||||
String get losMenuTitle => 'Menù LOS';
|
||||
|
||||
@override
|
||||
String get losMenuSubtitle =>
|
||||
'Tocca i nodi o premi a lungo la mappa per punti personalizzati';
|
||||
|
||||
@override
|
||||
String get losShowDisplayNodes => 'Mostra i nodi di visualizzazione';
|
||||
|
||||
@override
|
||||
String get losCustomPoints => 'Punti personalizzati';
|
||||
|
||||
@override
|
||||
String losCustomPointLabel(int index) {
|
||||
return 'Personalizzato $index';
|
||||
}
|
||||
|
||||
@override
|
||||
String get losPointA => 'Punto A';
|
||||
|
||||
@override
|
||||
String get losPointB => 'Punto B';
|
||||
|
||||
@override
|
||||
String losAntennaA(String value, String unit) {
|
||||
return 'Antenna A: $value $unit';
|
||||
}
|
||||
|
||||
@override
|
||||
String losAntennaB(String value, String unit) {
|
||||
return 'Antenna B: $value $unit';
|
||||
}
|
||||
|
||||
@override
|
||||
String get losRun => 'Esegui LOS';
|
||||
|
||||
@override
|
||||
String get losNoElevationData => 'Nessun dato di elevazione';
|
||||
|
||||
@override
|
||||
String losProfileClear(
|
||||
String distance,
|
||||
String distanceUnit,
|
||||
String clearance,
|
||||
String heightUnit,
|
||||
) {
|
||||
return '$distance $distanceUnit, libera LOS, distanza minima $clearance $heightUnit';
|
||||
}
|
||||
|
||||
@override
|
||||
String losProfileBlocked(
|
||||
String distance,
|
||||
String distanceUnit,
|
||||
String obstruction,
|
||||
String heightUnit,
|
||||
) {
|
||||
return '$distance $distanceUnit, bloccato da $obstruction $heightUnit';
|
||||
}
|
||||
|
||||
@override
|
||||
String get losStatusChecking => 'LOS: controllo...';
|
||||
|
||||
@override
|
||||
String get losStatusNoData => 'LOS: nessun dato';
|
||||
|
||||
@override
|
||||
String losStatusSummary(int clear, int total, int blocked, int unknown) {
|
||||
return 'LOS: $clear/$total libera, $blocked bloccato, $unknown sconosciuto';
|
||||
}
|
||||
|
||||
@override
|
||||
String get losErrorElevationUnavailable =>
|
||||
'Dati di elevazione non disponibili per uno o più campioni.';
|
||||
|
||||
@override
|
||||
String get losErrorInvalidInput =>
|
||||
'Dati punti/elevazione non validi per il calcolo della LOS.';
|
||||
|
||||
@override
|
||||
String get losRenameCustomPoint => 'Rinomina punto personalizzato';
|
||||
|
||||
@override
|
||||
String get losPointName => 'Nome del punto';
|
||||
|
||||
@override
|
||||
String get losShowPanelTooltip => 'Mostra il pannello LOS';
|
||||
|
||||
@override
|
||||
String get losHidePanelTooltip => 'Nascondi il pannello LOS';
|
||||
|
||||
@override
|
||||
String get losElevationAttribution =>
|
||||
'Dati di elevazione: Open-Meteo (CC BY 4.0)';
|
||||
|
||||
@override
|
||||
String get contacts_pathTrace => 'Traccia Percorso';
|
||||
|
||||
|
|
|
|||
|
|
@ -320,6 +320,10 @@ class AppLocalizationsNl extends AppLocalizations {
|
|||
String get settings_aboutDescription =>
|
||||
'Een open-source Flutter client voor MeshCore LoRa mesh netwerkapparaten.';
|
||||
|
||||
@override
|
||||
String get settings_aboutOpenMeteoAttribution =>
|
||||
'LOS-hoogtegegevens: Open-Meteo (CC BY 4.0)';
|
||||
|
||||
@override
|
||||
String get settings_infoName => 'Naam';
|
||||
|
||||
|
|
@ -617,6 +621,15 @@ class AppLocalizationsNl extends AppLocalizations {
|
|||
@override
|
||||
String get appSettings_offlineMapCache => 'Offline Kaarten Cache';
|
||||
|
||||
@override
|
||||
String get appSettings_unitsTitle => 'Eenheden';
|
||||
|
||||
@override
|
||||
String get appSettings_unitsMetric => 'Metrisch (m / km)';
|
||||
|
||||
@override
|
||||
String get appSettings_unitsImperial => 'Imperiaal (ft / mi)';
|
||||
|
||||
@override
|
||||
String get appSettings_noAreaSelected => 'Geen gebied geselecteerd';
|
||||
|
||||
|
|
@ -1235,6 +1248,12 @@ class AppLocalizationsNl extends AppLocalizations {
|
|||
@override
|
||||
String get map_title => 'Node Map';
|
||||
|
||||
@override
|
||||
String get map_lineOfSight => 'Zichtlijn';
|
||||
|
||||
@override
|
||||
String get map_losScreenTitle => 'Zichtlijn';
|
||||
|
||||
@override
|
||||
String get map_noNodesWithLocation => 'Geen nodes met locatiegegevens';
|
||||
|
||||
|
|
@ -2714,6 +2733,117 @@ class AppLocalizationsNl extends AppLocalizations {
|
|||
@override
|
||||
String get pathTrace_clearTooltip => 'Weg wissen';
|
||||
|
||||
@override
|
||||
String get losSelectStartEnd =>
|
||||
'Selecteer begin- en eindknooppunten voor LOS.';
|
||||
|
||||
@override
|
||||
String losRunFailed(String error) {
|
||||
return 'Zichtlijncontrole mislukt: $error';
|
||||
}
|
||||
|
||||
@override
|
||||
String get losClearAllPoints => 'Wis alle punten';
|
||||
|
||||
@override
|
||||
String get losRunToViewElevationProfile =>
|
||||
'Voer LOS uit om het hoogteprofiel te bekijken';
|
||||
|
||||
@override
|
||||
String get losMenuTitle => 'LOS-menu';
|
||||
|
||||
@override
|
||||
String get losMenuSubtitle =>
|
||||
'Tik op knooppunten of druk lang op de kaart voor aangepaste punten';
|
||||
|
||||
@override
|
||||
String get losShowDisplayNodes => 'Toon weergaveknooppunten';
|
||||
|
||||
@override
|
||||
String get losCustomPoints => 'Aangepaste punten';
|
||||
|
||||
@override
|
||||
String losCustomPointLabel(int index) {
|
||||
return 'Aangepast $index';
|
||||
}
|
||||
|
||||
@override
|
||||
String get losPointA => 'Punt A';
|
||||
|
||||
@override
|
||||
String get losPointB => 'Punt B';
|
||||
|
||||
@override
|
||||
String losAntennaA(String value, String unit) {
|
||||
return 'Antenne A: $value $unit';
|
||||
}
|
||||
|
||||
@override
|
||||
String losAntennaB(String value, String unit) {
|
||||
return 'Antenne B: $value $unit';
|
||||
}
|
||||
|
||||
@override
|
||||
String get losRun => 'Voer LOS uit';
|
||||
|
||||
@override
|
||||
String get losNoElevationData => 'Geen hoogtegegevens';
|
||||
|
||||
@override
|
||||
String losProfileClear(
|
||||
String distance,
|
||||
String distanceUnit,
|
||||
String clearance,
|
||||
String heightUnit,
|
||||
) {
|
||||
return '$distance $distanceUnit, vrije LOS, min. vrije ruimte $clearance $heightUnit';
|
||||
}
|
||||
|
||||
@override
|
||||
String losProfileBlocked(
|
||||
String distance,
|
||||
String distanceUnit,
|
||||
String obstruction,
|
||||
String heightUnit,
|
||||
) {
|
||||
return '$distance $distanceUnit, geblokkeerd door $obstruction $heightUnit';
|
||||
}
|
||||
|
||||
@override
|
||||
String get losStatusChecking => 'LOS: controleren...';
|
||||
|
||||
@override
|
||||
String get losStatusNoData => 'LOS: geen gegevens';
|
||||
|
||||
@override
|
||||
String losStatusSummary(int clear, int total, int blocked, int unknown) {
|
||||
return 'LOS: $clear/$total gewist, $blocked geblokkeerd, $unknown onbekend';
|
||||
}
|
||||
|
||||
@override
|
||||
String get losErrorElevationUnavailable =>
|
||||
'Hoogtegegevens niet beschikbaar voor een of meer monsters.';
|
||||
|
||||
@override
|
||||
String get losErrorInvalidInput =>
|
||||
'Ongeldige punten/hoogtegegevens voor LOS-berekening.';
|
||||
|
||||
@override
|
||||
String get losRenameCustomPoint => 'Hernoem aangepast punt';
|
||||
|
||||
@override
|
||||
String get losPointName => 'Puntnaam';
|
||||
|
||||
@override
|
||||
String get losShowPanelTooltip => 'Toon LOS-paneel';
|
||||
|
||||
@override
|
||||
String get losHidePanelTooltip => 'LOS-paneel verbergen';
|
||||
|
||||
@override
|
||||
String get losElevationAttribution =>
|
||||
'Hoogtegegevens: Open-Meteo (CC BY 4.0)';
|
||||
|
||||
@override
|
||||
String get contacts_pathTrace => 'Pad Traceren';
|
||||
|
||||
|
|
|
|||
|
|
@ -323,6 +323,10 @@ class AppLocalizationsPl extends AppLocalizations {
|
|||
String get settings_aboutDescription =>
|
||||
'Otwarty kod źródłowy klient Flutter dla urządzeń do sieci mesh LoRa MeshCore.';
|
||||
|
||||
@override
|
||||
String get settings_aboutOpenMeteoAttribution =>
|
||||
'Dane wysokościowe LOS: Open-Meteo (CC BY 4.0)';
|
||||
|
||||
@override
|
||||
String get settings_infoName => 'Imię';
|
||||
|
||||
|
|
@ -621,6 +625,15 @@ class AppLocalizationsPl extends AppLocalizations {
|
|||
@override
|
||||
String get appSettings_offlineMapCache => 'Bufor Map Offline';
|
||||
|
||||
@override
|
||||
String get appSettings_unitsTitle => 'Jednostki';
|
||||
|
||||
@override
|
||||
String get appSettings_unitsMetric => 'Metryczne (m / km)';
|
||||
|
||||
@override
|
||||
String get appSettings_unitsImperial => 'Imperialne (ft / mi)';
|
||||
|
||||
@override
|
||||
String get appSettings_noAreaSelected => 'Nie zaznaczono żadnej powierzchni.';
|
||||
|
||||
|
|
@ -1241,6 +1254,12 @@ class AppLocalizationsPl extends AppLocalizations {
|
|||
@override
|
||||
String get map_title => 'Mapa węzłów';
|
||||
|
||||
@override
|
||||
String get map_lineOfSight => 'Linia wzroku';
|
||||
|
||||
@override
|
||||
String get map_losScreenTitle => 'Linia wzroku';
|
||||
|
||||
@override
|
||||
String get map_noNodesWithLocation => 'Brak węzłów z danymi lokalizacyjnymi';
|
||||
|
||||
|
|
@ -2721,6 +2740,116 @@ class AppLocalizationsPl extends AppLocalizations {
|
|||
@override
|
||||
String get pathTrace_clearTooltip => 'Wyczyść ścieżkę';
|
||||
|
||||
@override
|
||||
String get losSelectStartEnd => 'Wybierz węzły początkowe i końcowe dla LOS.';
|
||||
|
||||
@override
|
||||
String losRunFailed(String error) {
|
||||
return 'Sprawdzenie pola widzenia nie powiodło się: $error';
|
||||
}
|
||||
|
||||
@override
|
||||
String get losClearAllPoints => 'Wyczyść wszystkie punkty';
|
||||
|
||||
@override
|
||||
String get losRunToViewElevationProfile =>
|
||||
'Uruchom LOS, aby wyświetlić profil wysokości';
|
||||
|
||||
@override
|
||||
String get losMenuTitle => 'Menu LOS';
|
||||
|
||||
@override
|
||||
String get losMenuSubtitle =>
|
||||
'Stuknij węzły lub naciśnij i przytrzymaj mapę, aby uzyskać niestandardowe punkty';
|
||||
|
||||
@override
|
||||
String get losShowDisplayNodes => 'Pokaż węzły wyświetlające';
|
||||
|
||||
@override
|
||||
String get losCustomPoints => 'Punkty niestandardowe';
|
||||
|
||||
@override
|
||||
String losCustomPointLabel(int index) {
|
||||
return 'Niestandardowe $index';
|
||||
}
|
||||
|
||||
@override
|
||||
String get losPointA => 'Punkt A';
|
||||
|
||||
@override
|
||||
String get losPointB => 'Punkt B';
|
||||
|
||||
@override
|
||||
String losAntennaA(String value, String unit) {
|
||||
return 'Antena A: $value $unit';
|
||||
}
|
||||
|
||||
@override
|
||||
String losAntennaB(String value, String unit) {
|
||||
return 'Antena B: $value $unit';
|
||||
}
|
||||
|
||||
@override
|
||||
String get losRun => 'Uruchom LOS-a';
|
||||
|
||||
@override
|
||||
String get losNoElevationData => 'Brak danych o wysokości';
|
||||
|
||||
@override
|
||||
String losProfileClear(
|
||||
String distance,
|
||||
String distanceUnit,
|
||||
String clearance,
|
||||
String heightUnit,
|
||||
) {
|
||||
return '$distance $distanceUnit, czysty LOS, minimalny prześwit $clearance $heightUnit';
|
||||
}
|
||||
|
||||
@override
|
||||
String losProfileBlocked(
|
||||
String distance,
|
||||
String distanceUnit,
|
||||
String obstruction,
|
||||
String heightUnit,
|
||||
) {
|
||||
return '$distance $distanceUnit, zablokowane przez $obstruction $heightUnit';
|
||||
}
|
||||
|
||||
@override
|
||||
String get losStatusChecking => 'LOS: sprawdzam...';
|
||||
|
||||
@override
|
||||
String get losStatusNoData => 'LOS: brak danych';
|
||||
|
||||
@override
|
||||
String losStatusSummary(int clear, int total, int blocked, int unknown) {
|
||||
return 'LOS: $clear/$total jasne, $blocked zablokowane, $unknown nieznane';
|
||||
}
|
||||
|
||||
@override
|
||||
String get losErrorElevationUnavailable =>
|
||||
'Dane dotyczące wysokości są niedostępne dla jednej lub większej liczby próbek.';
|
||||
|
||||
@override
|
||||
String get losErrorInvalidInput =>
|
||||
'Nieprawidłowe dane punktów/wysokości do obliczenia LOS.';
|
||||
|
||||
@override
|
||||
String get losRenameCustomPoint => 'Zmień nazwę punktu niestandardowego';
|
||||
|
||||
@override
|
||||
String get losPointName => 'Nazwa punktu';
|
||||
|
||||
@override
|
||||
String get losShowPanelTooltip => 'Pokaż panel LOS';
|
||||
|
||||
@override
|
||||
String get losHidePanelTooltip => 'Ukryj panel LOS';
|
||||
|
||||
@override
|
||||
String get losElevationAttribution =>
|
||||
'Dane dotyczące wysokości: Open-Meteo (CC BY 4.0)';
|
||||
|
||||
@override
|
||||
String get contacts_pathTrace => 'Śledzenie Ścieżek';
|
||||
|
||||
|
|
|
|||
|
|
@ -324,6 +324,10 @@ class AppLocalizationsPt extends AppLocalizations {
|
|||
String get settings_aboutDescription =>
|
||||
'Um cliente Flutter de código aberto para dispositivos de rede mesh LoRa Core da MeshCore.';
|
||||
|
||||
@override
|
||||
String get settings_aboutOpenMeteoAttribution =>
|
||||
'Dados de elevação LOS: Open-Meteo (CC BY 4.0)';
|
||||
|
||||
@override
|
||||
String get settings_infoName => 'Nome';
|
||||
|
||||
|
|
@ -620,6 +624,15 @@ class AppLocalizationsPt extends AppLocalizations {
|
|||
@override
|
||||
String get appSettings_offlineMapCache => 'Cache de Mapa Offline';
|
||||
|
||||
@override
|
||||
String get appSettings_unitsTitle => 'Unidades';
|
||||
|
||||
@override
|
||||
String get appSettings_unitsMetric => 'Métrico (m/km)';
|
||||
|
||||
@override
|
||||
String get appSettings_unitsImperial => 'Imperial (ft/mi)';
|
||||
|
||||
@override
|
||||
String get appSettings_noAreaSelected => 'Nenhuma área selecionada';
|
||||
|
||||
|
|
@ -1240,6 +1253,12 @@ class AppLocalizationsPt extends AppLocalizations {
|
|||
@override
|
||||
String get map_title => 'Mapa de Nós';
|
||||
|
||||
@override
|
||||
String get map_lineOfSight => 'Linha de visão';
|
||||
|
||||
@override
|
||||
String get map_losScreenTitle => 'Linha de visão';
|
||||
|
||||
@override
|
||||
String get map_noNodesWithLocation =>
|
||||
'Não existem nós com dados de localização.';
|
||||
|
|
@ -2723,6 +2742,116 @@ class AppLocalizationsPt extends AppLocalizations {
|
|||
@override
|
||||
String get pathTrace_clearTooltip => 'Limpar caminho';
|
||||
|
||||
@override
|
||||
String get losSelectStartEnd => 'Selecione nós iniciais e finais para LOS.';
|
||||
|
||||
@override
|
||||
String losRunFailed(String error) {
|
||||
return 'Falha na verificação da linha de visão: $error';
|
||||
}
|
||||
|
||||
@override
|
||||
String get losClearAllPoints => 'Limpe todos os pontos';
|
||||
|
||||
@override
|
||||
String get losRunToViewElevationProfile =>
|
||||
'Execute o LOS para visualizar o perfil de elevação';
|
||||
|
||||
@override
|
||||
String get losMenuTitle => 'Menu LOS';
|
||||
|
||||
@override
|
||||
String get losMenuSubtitle =>
|
||||
'Toque nos nós ou mantenha pressionado o mapa para obter pontos personalizados';
|
||||
|
||||
@override
|
||||
String get losShowDisplayNodes => 'Mostrar nós de exibição';
|
||||
|
||||
@override
|
||||
String get losCustomPoints => 'Pontos personalizados';
|
||||
|
||||
@override
|
||||
String losCustomPointLabel(int index) {
|
||||
return '$index personalizado';
|
||||
}
|
||||
|
||||
@override
|
||||
String get losPointA => 'Ponto A';
|
||||
|
||||
@override
|
||||
String get losPointB => 'Ponto B';
|
||||
|
||||
@override
|
||||
String losAntennaA(String value, String unit) {
|
||||
return 'Antena A: $value $unit';
|
||||
}
|
||||
|
||||
@override
|
||||
String losAntennaB(String value, String unit) {
|
||||
return 'Antena B: $value $unit';
|
||||
}
|
||||
|
||||
@override
|
||||
String get losRun => 'Executar LOS';
|
||||
|
||||
@override
|
||||
String get losNoElevationData => 'Sem dados de elevação';
|
||||
|
||||
@override
|
||||
String losProfileClear(
|
||||
String distance,
|
||||
String distanceUnit,
|
||||
String clearance,
|
||||
String heightUnit,
|
||||
) {
|
||||
return '$distance $distanceUnit, limpar LOS, liberação mínima $clearance $heightUnit';
|
||||
}
|
||||
|
||||
@override
|
||||
String losProfileBlocked(
|
||||
String distance,
|
||||
String distanceUnit,
|
||||
String obstruction,
|
||||
String heightUnit,
|
||||
) {
|
||||
return '$distance $distanceUnit, bloqueado por $obstruction $heightUnit';
|
||||
}
|
||||
|
||||
@override
|
||||
String get losStatusChecking => 'LOS: verificando...';
|
||||
|
||||
@override
|
||||
String get losStatusNoData => 'LOS: sem dados';
|
||||
|
||||
@override
|
||||
String losStatusSummary(int clear, int total, int blocked, int unknown) {
|
||||
return 'LOS: $clear/$total limpo, $blocked bloqueado, $unknown desconhecido';
|
||||
}
|
||||
|
||||
@override
|
||||
String get losErrorElevationUnavailable =>
|
||||
'Dados de elevação indisponíveis para uma ou mais amostras.';
|
||||
|
||||
@override
|
||||
String get losErrorInvalidInput =>
|
||||
'Dados de pontos/elevação inválidos para cálculo de LOS.';
|
||||
|
||||
@override
|
||||
String get losRenameCustomPoint => 'Renomear ponto personalizado';
|
||||
|
||||
@override
|
||||
String get losPointName => 'Nome do ponto';
|
||||
|
||||
@override
|
||||
String get losShowPanelTooltip => 'Mostrar painel LOS';
|
||||
|
||||
@override
|
||||
String get losHidePanelTooltip => 'Ocultar painel LOS';
|
||||
|
||||
@override
|
||||
String get losElevationAttribution =>
|
||||
'Dados de elevação: Open-Meteo (CC BY 4.0)';
|
||||
|
||||
@override
|
||||
String get contacts_pathTrace => 'Traçado de Caminho';
|
||||
|
||||
|
|
|
|||
|
|
@ -321,6 +321,10 @@ class AppLocalizationsRu extends AppLocalizations {
|
|||
String get settings_aboutDescription =>
|
||||
'Открытое клиентское приложение на Flutter для устройств MeshCore с LoRa-сетями.';
|
||||
|
||||
@override
|
||||
String get settings_aboutOpenMeteoAttribution =>
|
||||
'Данные о высоте LOS: Open-Meteo (CC BY 4.0)';
|
||||
|
||||
@override
|
||||
String get settings_infoName => 'Имя';
|
||||
|
||||
|
|
@ -620,6 +624,15 @@ class AppLocalizationsRu extends AppLocalizations {
|
|||
@override
|
||||
String get appSettings_offlineMapCache => 'Кэш офлайн-карты';
|
||||
|
||||
@override
|
||||
String get appSettings_unitsTitle => 'Единицы';
|
||||
|
||||
@override
|
||||
String get appSettings_unitsMetric => 'Метрическая (м/км)';
|
||||
|
||||
@override
|
||||
String get appSettings_unitsImperial => 'Имперская (ft / mi)';
|
||||
|
||||
@override
|
||||
String get appSettings_noAreaSelected => 'Область не выбрана';
|
||||
|
||||
|
|
@ -1242,6 +1255,12 @@ class AppLocalizationsRu extends AppLocalizations {
|
|||
@override
|
||||
String get map_title => 'Карта нод';
|
||||
|
||||
@override
|
||||
String get map_lineOfSight => 'Линия видимости';
|
||||
|
||||
@override
|
||||
String get map_losScreenTitle => 'Линия видимости';
|
||||
|
||||
@override
|
||||
String get map_noNodesWithLocation => 'Нет нод с данными о местоположении';
|
||||
|
||||
|
|
@ -2726,6 +2745,116 @@ class AppLocalizationsRu extends AppLocalizations {
|
|||
@override
|
||||
String get pathTrace_clearTooltip => 'Очистить путь';
|
||||
|
||||
@override
|
||||
String get losSelectStartEnd => 'Выберите начальный и конечный узлы для LOS.';
|
||||
|
||||
@override
|
||||
String losRunFailed(String error) {
|
||||
return 'Проверка прямой видимости не удалась: $error';
|
||||
}
|
||||
|
||||
@override
|
||||
String get losClearAllPoints => 'Очистить все точки';
|
||||
|
||||
@override
|
||||
String get losRunToViewElevationProfile =>
|
||||
'Запустите LOS, чтобы просмотреть профиль высот.';
|
||||
|
||||
@override
|
||||
String get losMenuTitle => 'ЛОС Меню';
|
||||
|
||||
@override
|
||||
String get losMenuSubtitle =>
|
||||
'Коснитесь узлов или нажмите и удерживайте карту для выбора пользовательских точек.';
|
||||
|
||||
@override
|
||||
String get losShowDisplayNodes => 'Показать узлы отображения';
|
||||
|
||||
@override
|
||||
String get losCustomPoints => 'Пользовательские точки';
|
||||
|
||||
@override
|
||||
String losCustomPointLabel(int index) {
|
||||
return 'Пользовательский $index';
|
||||
}
|
||||
|
||||
@override
|
||||
String get losPointA => 'Точка А';
|
||||
|
||||
@override
|
||||
String get losPointB => 'Точка Б';
|
||||
|
||||
@override
|
||||
String losAntennaA(String value, String unit) {
|
||||
return 'Антенна А: $value $unit';
|
||||
}
|
||||
|
||||
@override
|
||||
String losAntennaB(String value, String unit) {
|
||||
return 'Антенна Б: $value $unit';
|
||||
}
|
||||
|
||||
@override
|
||||
String get losRun => 'Запустить ЛОС';
|
||||
|
||||
@override
|
||||
String get losNoElevationData => 'Нет данных о высоте';
|
||||
|
||||
@override
|
||||
String losProfileClear(
|
||||
String distance,
|
||||
String distanceUnit,
|
||||
String clearance,
|
||||
String heightUnit,
|
||||
) {
|
||||
return '$distance $distanceUnit, свободная зона видимости, минимальный зазор $clearance $heightUnit';
|
||||
}
|
||||
|
||||
@override
|
||||
String losProfileBlocked(
|
||||
String distance,
|
||||
String distanceUnit,
|
||||
String obstruction,
|
||||
String heightUnit,
|
||||
) {
|
||||
return '$distance $distanceUnit, заблокирован $obstruction $heightUnit';
|
||||
}
|
||||
|
||||
@override
|
||||
String get losStatusChecking => 'ЛОС: проверяю...';
|
||||
|
||||
@override
|
||||
String get losStatusNoData => 'ЛОС: нет данных';
|
||||
|
||||
@override
|
||||
String losStatusSummary(int clear, int total, int blocked, int unknown) {
|
||||
return 'LOS: $clear/$total очищено, $blocked заблокировано, $unknown неизвестно.';
|
||||
}
|
||||
|
||||
@override
|
||||
String get losErrorElevationUnavailable =>
|
||||
'Данные о высоте недоступны для одного или нескольких образцов.';
|
||||
|
||||
@override
|
||||
String get losErrorInvalidInput =>
|
||||
'Неверные данные о точках/высоте для расчета LOS.';
|
||||
|
||||
@override
|
||||
String get losRenameCustomPoint => 'Переименовать пользовательскую точку';
|
||||
|
||||
@override
|
||||
String get losPointName => 'Имя точки';
|
||||
|
||||
@override
|
||||
String get losShowPanelTooltip => 'Показать панель LOS';
|
||||
|
||||
@override
|
||||
String get losHidePanelTooltip => 'Скрыть панель LOS';
|
||||
|
||||
@override
|
||||
String get losElevationAttribution =>
|
||||
'Данные о высоте: Open-Meteo (CC BY 4.0)';
|
||||
|
||||
@override
|
||||
String get contacts_pathTrace => 'Трассировка пути';
|
||||
|
||||
|
|
|
|||
|
|
@ -320,6 +320,10 @@ class AppLocalizationsSk extends AppLocalizations {
|
|||
String get settings_aboutDescription =>
|
||||
'Otvorený zdrojový Flutter klient pre MeshCore LoRa sieťové zariadenia.';
|
||||
|
||||
@override
|
||||
String get settings_aboutOpenMeteoAttribution =>
|
||||
'Údaje o nadmorskej výške LOS: Open-Meteo (CC BY 4.0)';
|
||||
|
||||
@override
|
||||
String get settings_infoName => 'Meno';
|
||||
|
||||
|
|
@ -614,6 +618,15 @@ class AppLocalizationsSk extends AppLocalizations {
|
|||
@override
|
||||
String get appSettings_offlineMapCache => 'Offline Mapa Pamäť';
|
||||
|
||||
@override
|
||||
String get appSettings_unitsTitle => 'Jednotky';
|
||||
|
||||
@override
|
||||
String get appSettings_unitsMetric => 'Metrické (m / km)';
|
||||
|
||||
@override
|
||||
String get appSettings_unitsImperial => 'Imperiálne (ft / mi)';
|
||||
|
||||
@override
|
||||
String get appSettings_noAreaSelected => 'Neoznačila sa žiadna oblasť';
|
||||
|
||||
|
|
@ -1236,6 +1249,12 @@ class AppLocalizationsSk extends AppLocalizations {
|
|||
@override
|
||||
String get map_title => 'Mapa uzlov';
|
||||
|
||||
@override
|
||||
String get map_lineOfSight => 'Line of Sight';
|
||||
|
||||
@override
|
||||
String get map_losScreenTitle => 'Line of Sight';
|
||||
|
||||
@override
|
||||
String get map_noNodesWithLocation => 'Žiadne uzly s údajmi o polohe';
|
||||
|
||||
|
|
@ -2709,6 +2728,116 @@ class AppLocalizationsSk extends AppLocalizations {
|
|||
@override
|
||||
String get pathTrace_clearTooltip => 'Zmazať cestu';
|
||||
|
||||
@override
|
||||
String get losSelectStartEnd => 'Vyberte počiatočný a koncový uzol pre LOS.';
|
||||
|
||||
@override
|
||||
String losRunFailed(String error) {
|
||||
return 'Kontrola priamej viditeľnosti zlyhala: $error';
|
||||
}
|
||||
|
||||
@override
|
||||
String get losClearAllPoints => 'Vymazať všetky body';
|
||||
|
||||
@override
|
||||
String get losRunToViewElevationProfile =>
|
||||
'Ak chcete zobraziť výškový profil, spustite LOS';
|
||||
|
||||
@override
|
||||
String get losMenuTitle => 'Menu LOS';
|
||||
|
||||
@override
|
||||
String get losMenuSubtitle =>
|
||||
'Klepnutím na uzly alebo dlhým stlačením mapy získate vlastné body';
|
||||
|
||||
@override
|
||||
String get losShowDisplayNodes => 'Zobraziť uzly zobrazenia';
|
||||
|
||||
@override
|
||||
String get losCustomPoints => 'Vlastné body';
|
||||
|
||||
@override
|
||||
String losCustomPointLabel(int index) {
|
||||
return 'Vlastné $index';
|
||||
}
|
||||
|
||||
@override
|
||||
String get losPointA => 'Bod A';
|
||||
|
||||
@override
|
||||
String get losPointB => 'Bod B';
|
||||
|
||||
@override
|
||||
String losAntennaA(String value, String unit) {
|
||||
return 'Anténa A: $value $unit';
|
||||
}
|
||||
|
||||
@override
|
||||
String losAntennaB(String value, String unit) {
|
||||
return 'Anténa B: $value $unit';
|
||||
}
|
||||
|
||||
@override
|
||||
String get losRun => 'Spustite LOS';
|
||||
|
||||
@override
|
||||
String get losNoElevationData => 'Žiadne údaje o nadmorskej výške';
|
||||
|
||||
@override
|
||||
String losProfileClear(
|
||||
String distance,
|
||||
String distanceUnit,
|
||||
String clearance,
|
||||
String heightUnit,
|
||||
) {
|
||||
return '$distance $distanceUnit, vymazať LOS, min. vôľa $clearance $heightUnit';
|
||||
}
|
||||
|
||||
@override
|
||||
String losProfileBlocked(
|
||||
String distance,
|
||||
String distanceUnit,
|
||||
String obstruction,
|
||||
String heightUnit,
|
||||
) {
|
||||
return '$distance $distanceUnit, blokovaný $obstruction $heightUnit';
|
||||
}
|
||||
|
||||
@override
|
||||
String get losStatusChecking => 'LOS: kontrolujem...';
|
||||
|
||||
@override
|
||||
String get losStatusNoData => 'LOS: žiadne údaje';
|
||||
|
||||
@override
|
||||
String losStatusSummary(int clear, int total, int blocked, int unknown) {
|
||||
return 'LOS: $clear/$total vymazané, $blocked blokované, $unknown neznáme';
|
||||
}
|
||||
|
||||
@override
|
||||
String get losErrorElevationUnavailable =>
|
||||
'Údaje o nadmorskej výške nie sú k dispozícii pre jednu alebo viacero vzoriek.';
|
||||
|
||||
@override
|
||||
String get losErrorInvalidInput =>
|
||||
'Neplatné body/údaje o nadmorskej výške pre výpočet LOS.';
|
||||
|
||||
@override
|
||||
String get losRenameCustomPoint => 'Premenovať vlastný bod';
|
||||
|
||||
@override
|
||||
String get losPointName => 'Názov bodu';
|
||||
|
||||
@override
|
||||
String get losShowPanelTooltip => 'Zobraziť panel LOS';
|
||||
|
||||
@override
|
||||
String get losHidePanelTooltip => 'Skryť panel LOS';
|
||||
|
||||
@override
|
||||
String get losElevationAttribution =>
|
||||
'Údaje o nadmorskej výške: Open-Meteo (CC BY 4.0)';
|
||||
|
||||
@override
|
||||
String get contacts_pathTrace => 'Sledovanie lúčov';
|
||||
|
||||
|
|
|
|||
|
|
@ -319,6 +319,10 @@ class AppLocalizationsSl extends AppLocalizations {
|
|||
String get settings_aboutDescription =>
|
||||
'Odprtokodni Flutter klient za naprave za LoRa omrežje MeshCore.';
|
||||
|
||||
@override
|
||||
String get settings_aboutOpenMeteoAttribution =>
|
||||
'Podatki o višini LOS: Open-Meteo (CC BY 4.0)';
|
||||
|
||||
@override
|
||||
String get settings_infoName => 'Ime';
|
||||
|
||||
|
|
@ -615,6 +619,15 @@ class AppLocalizationsSl extends AppLocalizations {
|
|||
@override
|
||||
String get appSettings_offlineMapCache => 'Shramba zemljevidov brez povezave';
|
||||
|
||||
@override
|
||||
String get appSettings_unitsTitle => 'Enote';
|
||||
|
||||
@override
|
||||
String get appSettings_unitsMetric => 'Metrična (m/km)';
|
||||
|
||||
@override
|
||||
String get appSettings_unitsImperial => 'Imperialno (ft / mi)';
|
||||
|
||||
@override
|
||||
String get appSettings_noAreaSelected => 'Območje ni izbrano';
|
||||
|
||||
|
|
@ -1231,6 +1244,12 @@ class AppLocalizationsSl extends AppLocalizations {
|
|||
@override
|
||||
String get map_title => 'Mapa omrežja';
|
||||
|
||||
@override
|
||||
String get map_lineOfSight => 'Linija vida';
|
||||
|
||||
@override
|
||||
String get map_losScreenTitle => 'Linija vida';
|
||||
|
||||
@override
|
||||
String get map_noNodesWithLocation =>
|
||||
'Nihče od notranjih elementov nima podatkov o lokaciji.';
|
||||
|
|
@ -2712,6 +2731,116 @@ class AppLocalizationsSl extends AppLocalizations {
|
|||
@override
|
||||
String get pathTrace_clearTooltip => 'Počisti pot';
|
||||
|
||||
@override
|
||||
String get losSelectStartEnd => 'Izberite začetno in končno vozlišče za LOS.';
|
||||
|
||||
@override
|
||||
String losRunFailed(String error) {
|
||||
return 'Preverjanje vidnega polja ni uspelo: $error';
|
||||
}
|
||||
|
||||
@override
|
||||
String get losClearAllPoints => 'Počisti vse točke';
|
||||
|
||||
@override
|
||||
String get losRunToViewElevationProfile =>
|
||||
'Zaženite LOS za ogled višinskega profila';
|
||||
|
||||
@override
|
||||
String get losMenuTitle => 'LOS meni';
|
||||
|
||||
@override
|
||||
String get losMenuSubtitle =>
|
||||
'Tapnite vozlišča ali dolgo pritisnite na zemljevid za točke po meri';
|
||||
|
||||
@override
|
||||
String get losShowDisplayNodes => 'Pokaži prikazna vozlišča';
|
||||
|
||||
@override
|
||||
String get losCustomPoints => 'Točke po meri';
|
||||
|
||||
@override
|
||||
String losCustomPointLabel(int index) {
|
||||
return 'Po meri $index';
|
||||
}
|
||||
|
||||
@override
|
||||
String get losPointA => 'Točka A';
|
||||
|
||||
@override
|
||||
String get losPointB => 'Točka B';
|
||||
|
||||
@override
|
||||
String losAntennaA(String value, String unit) {
|
||||
return 'Antena A: $value $unit';
|
||||
}
|
||||
|
||||
@override
|
||||
String losAntennaB(String value, String unit) {
|
||||
return 'Antena B: $value $unit';
|
||||
}
|
||||
|
||||
@override
|
||||
String get losRun => 'Zaženi LOS';
|
||||
|
||||
@override
|
||||
String get losNoElevationData => 'Ni podatkov o višini';
|
||||
|
||||
@override
|
||||
String losProfileClear(
|
||||
String distance,
|
||||
String distanceUnit,
|
||||
String clearance,
|
||||
String heightUnit,
|
||||
) {
|
||||
return '$distance $distanceUnit, čisti LOS, najmanjša razdalja $clearance $heightUnit';
|
||||
}
|
||||
|
||||
@override
|
||||
String losProfileBlocked(
|
||||
String distance,
|
||||
String distanceUnit,
|
||||
String obstruction,
|
||||
String heightUnit,
|
||||
) {
|
||||
return '$distance $distanceUnit, blokiral $obstruction $heightUnit';
|
||||
}
|
||||
|
||||
@override
|
||||
String get losStatusChecking => 'LOS: preverjam ...';
|
||||
|
||||
@override
|
||||
String get losStatusNoData => 'LOS: ni podatkov';
|
||||
|
||||
@override
|
||||
String losStatusSummary(int clear, int total, int blocked, int unknown) {
|
||||
return 'LOS: $clear/$total jasno, $blocked blokirano, $unknown neznano';
|
||||
}
|
||||
|
||||
@override
|
||||
String get losErrorElevationUnavailable =>
|
||||
'Podatki o nadmorski višini niso na voljo za enega ali več vzorcev.';
|
||||
|
||||
@override
|
||||
String get losErrorInvalidInput =>
|
||||
'Neveljavni podatki o točkah/višini za izračun LOS.';
|
||||
|
||||
@override
|
||||
String get losRenameCustomPoint => 'Preimenujte točko po meri';
|
||||
|
||||
@override
|
||||
String get losPointName => 'Ime točke';
|
||||
|
||||
@override
|
||||
String get losShowPanelTooltip => 'Pokaži ploščo LOS';
|
||||
|
||||
@override
|
||||
String get losHidePanelTooltip => 'Skrij ploščo LOS';
|
||||
|
||||
@override
|
||||
String get losElevationAttribution =>
|
||||
'Podatki o višini: Open-Meteo (CC BY 4.0)';
|
||||
|
||||
@override
|
||||
String get contacts_pathTrace => 'Sledenje poti';
|
||||
|
||||
|
|
|
|||
|
|
@ -317,6 +317,10 @@ class AppLocalizationsSv extends AppLocalizations {
|
|||
String get settings_aboutDescription =>
|
||||
'En öppen källkods Flutter-klient för MeshCore LoRa meshnätverksenheter.';
|
||||
|
||||
@override
|
||||
String get settings_aboutOpenMeteoAttribution =>
|
||||
'LOS-höjddata: Open-Meteo (CC BY 4.0)';
|
||||
|
||||
@override
|
||||
String get settings_infoName => 'Namn';
|
||||
|
||||
|
|
@ -610,6 +614,15 @@ class AppLocalizationsSv extends AppLocalizations {
|
|||
@override
|
||||
String get appSettings_offlineMapCache => 'Offline Kartcache';
|
||||
|
||||
@override
|
||||
String get appSettings_unitsTitle => 'Enheter';
|
||||
|
||||
@override
|
||||
String get appSettings_unitsMetric => 'Metriskt (m/km)';
|
||||
|
||||
@override
|
||||
String get appSettings_unitsImperial => 'Imperialt (ft / mi)';
|
||||
|
||||
@override
|
||||
String get appSettings_noAreaSelected => 'Ingen area markerad';
|
||||
|
||||
|
|
@ -1228,6 +1241,12 @@ class AppLocalizationsSv extends AppLocalizations {
|
|||
@override
|
||||
String get map_title => 'Nodkarta';
|
||||
|
||||
@override
|
||||
String get map_lineOfSight => 'Synlinje';
|
||||
|
||||
@override
|
||||
String get map_losScreenTitle => 'Synlinje';
|
||||
|
||||
@override
|
||||
String get map_noNodesWithLocation => 'Inga noder med platsinformation';
|
||||
|
||||
|
|
@ -2697,6 +2716,114 @@ class AppLocalizationsSv extends AppLocalizations {
|
|||
@override
|
||||
String get pathTrace_clearTooltip => 'Rensa väg';
|
||||
|
||||
@override
|
||||
String get losSelectStartEnd => 'Välj start- och slutnoder för LOS.';
|
||||
|
||||
@override
|
||||
String losRunFailed(String error) {
|
||||
return 'Synlinjekontroll misslyckades: $error';
|
||||
}
|
||||
|
||||
@override
|
||||
String get losClearAllPoints => 'Rensa alla punkter';
|
||||
|
||||
@override
|
||||
String get losRunToViewElevationProfile => 'Kör LOS för att se höjdprofil';
|
||||
|
||||
@override
|
||||
String get losMenuTitle => 'LOS-menyn';
|
||||
|
||||
@override
|
||||
String get losMenuSubtitle =>
|
||||
'Tryck på noder eller tryck länge på kartan för anpassade punkter';
|
||||
|
||||
@override
|
||||
String get losShowDisplayNodes => 'Visa displaynoder';
|
||||
|
||||
@override
|
||||
String get losCustomPoints => 'Anpassade poäng';
|
||||
|
||||
@override
|
||||
String losCustomPointLabel(int index) {
|
||||
return 'Anpassad $index';
|
||||
}
|
||||
|
||||
@override
|
||||
String get losPointA => 'Punkt A';
|
||||
|
||||
@override
|
||||
String get losPointB => 'Punkt B';
|
||||
|
||||
@override
|
||||
String losAntennaA(String value, String unit) {
|
||||
return 'Antenn A: $value $unit';
|
||||
}
|
||||
|
||||
@override
|
||||
String losAntennaB(String value, String unit) {
|
||||
return 'Antenn B: $value $unit';
|
||||
}
|
||||
|
||||
@override
|
||||
String get losRun => 'Kör LOS';
|
||||
|
||||
@override
|
||||
String get losNoElevationData => 'Inga höjddata';
|
||||
|
||||
@override
|
||||
String losProfileClear(
|
||||
String distance,
|
||||
String distanceUnit,
|
||||
String clearance,
|
||||
String heightUnit,
|
||||
) {
|
||||
return '$distance $distanceUnit, rensa LOS, min clearance $clearance $heightUnit';
|
||||
}
|
||||
|
||||
@override
|
||||
String losProfileBlocked(
|
||||
String distance,
|
||||
String distanceUnit,
|
||||
String obstruction,
|
||||
String heightUnit,
|
||||
) {
|
||||
return '$distance $distanceUnit, blockerad av $obstruction $heightUnit';
|
||||
}
|
||||
|
||||
@override
|
||||
String get losStatusChecking => 'LOS: kollar...';
|
||||
|
||||
@override
|
||||
String get losStatusNoData => 'LOS: inga data';
|
||||
|
||||
@override
|
||||
String losStatusSummary(int clear, int total, int blocked, int unknown) {
|
||||
return 'LOS: $clear/$total rensa, $blocked blockerad, $unknown okänd';
|
||||
}
|
||||
|
||||
@override
|
||||
String get losErrorElevationUnavailable =>
|
||||
'Höjddata är inte tillgänglig för ett eller flera prover.';
|
||||
|
||||
@override
|
||||
String get losErrorInvalidInput =>
|
||||
'Ogiltiga poäng/höjddata för LOS-beräkning.';
|
||||
|
||||
@override
|
||||
String get losRenameCustomPoint => 'Byt namn på anpassad punkt';
|
||||
|
||||
@override
|
||||
String get losPointName => 'Punktnamn';
|
||||
|
||||
@override
|
||||
String get losShowPanelTooltip => 'Visa LOS-panelen';
|
||||
|
||||
@override
|
||||
String get losHidePanelTooltip => 'Dölj LOS-panelen';
|
||||
|
||||
@override
|
||||
String get losElevationAttribution => 'Höjddata: Open-Meteo (CC BY 4.0)';
|
||||
|
||||
@override
|
||||
String get contacts_pathTrace => 'Path Trace';
|
||||
|
||||
|
|
|
|||
|
|
@ -322,6 +322,10 @@ class AppLocalizationsUk extends AppLocalizations {
|
|||
String get settings_aboutDescription =>
|
||||
'Клієнт Flutter з відкритим вихідним кодом для пристроїв мережі MeshCore LoRa.';
|
||||
|
||||
@override
|
||||
String get settings_aboutOpenMeteoAttribution =>
|
||||
'Дані про висоту LOS: Open-Meteo (CC BY 4.0)';
|
||||
|
||||
@override
|
||||
String get settings_infoName => 'Ім\'я';
|
||||
|
||||
|
|
@ -618,6 +622,15 @@ class AppLocalizationsUk extends AppLocalizations {
|
|||
@override
|
||||
String get appSettings_offlineMapCache => 'Офлайн-кеш карти';
|
||||
|
||||
@override
|
||||
String get appSettings_unitsTitle => 'одиниці';
|
||||
|
||||
@override
|
||||
String get appSettings_unitsMetric => 'Метричний (м / км)';
|
||||
|
||||
@override
|
||||
String get appSettings_unitsImperial => 'Імперська (ft / mi)';
|
||||
|
||||
@override
|
||||
String get appSettings_noAreaSelected => 'Область не вибрано';
|
||||
|
||||
|
|
@ -1240,6 +1253,12 @@ class AppLocalizationsUk extends AppLocalizations {
|
|||
@override
|
||||
String get map_title => 'Карта вузлів';
|
||||
|
||||
@override
|
||||
String get map_lineOfSight => 'Пряма видимість';
|
||||
|
||||
@override
|
||||
String get map_losScreenTitle => 'Пряма видимість';
|
||||
|
||||
@override
|
||||
String get map_noNodesWithLocation =>
|
||||
'Немає вузлів з даними про розташування';
|
||||
|
|
@ -2733,6 +2752,117 @@ class AppLocalizationsUk extends AppLocalizations {
|
|||
@override
|
||||
String get pathTrace_clearTooltip => 'Очистити шлях';
|
||||
|
||||
@override
|
||||
String get losSelectStartEnd =>
|
||||
'Виберіть початковий і кінцевий вузли для LOS.';
|
||||
|
||||
@override
|
||||
String losRunFailed(String error) {
|
||||
return 'Помилка перевірки прямої видимості: $error';
|
||||
}
|
||||
|
||||
@override
|
||||
String get losClearAllPoints => 'Очистити всі пункти';
|
||||
|
||||
@override
|
||||
String get losRunToViewElevationProfile =>
|
||||
'Запустіть LOS, щоб переглянути профіль висоти';
|
||||
|
||||
@override
|
||||
String get losMenuTitle => 'Меню LOS';
|
||||
|
||||
@override
|
||||
String get losMenuSubtitle =>
|
||||
'Торкніться вузлів або утримуйте карту, щоб отримати власні точки';
|
||||
|
||||
@override
|
||||
String get losShowDisplayNodes => 'Показати вузли відображення';
|
||||
|
||||
@override
|
||||
String get losCustomPoints => 'Користувальницькі точки';
|
||||
|
||||
@override
|
||||
String losCustomPointLabel(int index) {
|
||||
return 'Спеціальний $index';
|
||||
}
|
||||
|
||||
@override
|
||||
String get losPointA => 'Точка А';
|
||||
|
||||
@override
|
||||
String get losPointB => 'Точка Б';
|
||||
|
||||
@override
|
||||
String losAntennaA(String value, String unit) {
|
||||
return 'Антена A: $value $unit';
|
||||
}
|
||||
|
||||
@override
|
||||
String losAntennaB(String value, String unit) {
|
||||
return 'Антена B: $value $unit';
|
||||
}
|
||||
|
||||
@override
|
||||
String get losRun => 'Запустіть LOS';
|
||||
|
||||
@override
|
||||
String get losNoElevationData => 'Немає даних про висоту';
|
||||
|
||||
@override
|
||||
String losProfileClear(
|
||||
String distance,
|
||||
String distanceUnit,
|
||||
String clearance,
|
||||
String heightUnit,
|
||||
) {
|
||||
return '$distance $distanceUnit, чистий LOS, мінімальний зазор $clearance $heightUnit';
|
||||
}
|
||||
|
||||
@override
|
||||
String losProfileBlocked(
|
||||
String distance,
|
||||
String distanceUnit,
|
||||
String obstruction,
|
||||
String heightUnit,
|
||||
) {
|
||||
return '$distance $distanceUnit, заблоковано $obstruction $heightUnit';
|
||||
}
|
||||
|
||||
@override
|
||||
String get losStatusChecking => 'LOS: перевірка...';
|
||||
|
||||
@override
|
||||
String get losStatusNoData => 'LOS: немає даних';
|
||||
|
||||
@override
|
||||
String losStatusSummary(int clear, int total, int blocked, int unknown) {
|
||||
return 'LOS: $clear/$total очищено, $blocked заблоковано, $unknown невідомо';
|
||||
}
|
||||
|
||||
@override
|
||||
String get losErrorElevationUnavailable =>
|
||||
'Дані про висоту недоступні для одного чи кількох зразків.';
|
||||
|
||||
@override
|
||||
String get losErrorInvalidInput =>
|
||||
'Недійсні дані про точки/висоту для розрахунку LOS.';
|
||||
|
||||
@override
|
||||
String get losRenameCustomPoint => 'Перейменуйте спеціальну точку';
|
||||
|
||||
@override
|
||||
String get losPointName => 'Назва точки';
|
||||
|
||||
@override
|
||||
String get losShowPanelTooltip => 'Показати панель LOS';
|
||||
|
||||
@override
|
||||
String get losHidePanelTooltip => 'Приховати панель LOS';
|
||||
|
||||
@override
|
||||
String get losElevationAttribution =>
|
||||
'Дані про висоту: Open-Meteo (CC BY 4.0)';
|
||||
|
||||
@override
|
||||
String get contacts_pathTrace => 'Трасування шляхів';
|
||||
|
||||
|
|
|
|||
|
|
@ -307,6 +307,10 @@ class AppLocalizationsZh extends AppLocalizations {
|
|||
String get settings_aboutDescription =>
|
||||
'一个开源的 Flutter 客户端,用于 MeshCore LoRa 无线网络设备。';
|
||||
|
||||
@override
|
||||
String get settings_aboutOpenMeteoAttribution =>
|
||||
'LOS 高程数据:Open-Meteo (CC BY 4.0)';
|
||||
|
||||
@override
|
||||
String get settings_infoName => '姓名';
|
||||
|
||||
|
|
@ -585,6 +589,15 @@ class AppLocalizationsZh extends AppLocalizations {
|
|||
@override
|
||||
String get appSettings_offlineMapCache => '离线地图缓存';
|
||||
|
||||
@override
|
||||
String get appSettings_unitsTitle => '单位';
|
||||
|
||||
@override
|
||||
String get appSettings_unitsMetric => '公制(米/公里)';
|
||||
|
||||
@override
|
||||
String get appSettings_unitsImperial => '英制 (ft / mi)';
|
||||
|
||||
@override
|
||||
String get appSettings_noAreaSelected => '未选择任何区域';
|
||||
|
||||
|
|
@ -1182,6 +1195,12 @@ class AppLocalizationsZh extends AppLocalizations {
|
|||
@override
|
||||
String get map_title => '节点图';
|
||||
|
||||
@override
|
||||
String get map_lineOfSight => '视线';
|
||||
|
||||
@override
|
||||
String get map_losScreenTitle => '视线';
|
||||
|
||||
@override
|
||||
String get map_noNodesWithLocation => '没有包含位置信息的节点';
|
||||
|
||||
|
|
@ -2579,6 +2598,111 @@ class AppLocalizationsZh extends AppLocalizations {
|
|||
@override
|
||||
String get pathTrace_clearTooltip => '清除路径';
|
||||
|
||||
@override
|
||||
String get losSelectStartEnd => '选择 LOS 的起始节点和结束节点。';
|
||||
|
||||
@override
|
||||
String losRunFailed(String error) {
|
||||
return '视线检查失败:$error';
|
||||
}
|
||||
|
||||
@override
|
||||
String get losClearAllPoints => '清除所有点';
|
||||
|
||||
@override
|
||||
String get losRunToViewElevationProfile => '运行 LOS 查看高程剖面';
|
||||
|
||||
@override
|
||||
String get losMenuTitle => '服务水平菜单';
|
||||
|
||||
@override
|
||||
String get losMenuSubtitle => '点击节点或长按地图以获取自定义点';
|
||||
|
||||
@override
|
||||
String get losShowDisplayNodes => '显示显示节点';
|
||||
|
||||
@override
|
||||
String get losCustomPoints => '自定义积分';
|
||||
|
||||
@override
|
||||
String losCustomPointLabel(int index) {
|
||||
return '自定义 $index';
|
||||
}
|
||||
|
||||
@override
|
||||
String get losPointA => 'A点';
|
||||
|
||||
@override
|
||||
String get losPointB => 'B点';
|
||||
|
||||
@override
|
||||
String losAntennaA(String value, String unit) {
|
||||
return '天线 A: $value $unit';
|
||||
}
|
||||
|
||||
@override
|
||||
String losAntennaB(String value, String unit) {
|
||||
return '天线 B:$value $unit';
|
||||
}
|
||||
|
||||
@override
|
||||
String get losRun => '运行视距';
|
||||
|
||||
@override
|
||||
String get losNoElevationData => '无海拔数据';
|
||||
|
||||
@override
|
||||
String losProfileClear(
|
||||
String distance,
|
||||
String distanceUnit,
|
||||
String clearance,
|
||||
String heightUnit,
|
||||
) {
|
||||
return '$distance $distanceUnit,清除 LOS,最小间隙 $clearance $heightUnit';
|
||||
}
|
||||
|
||||
@override
|
||||
String losProfileBlocked(
|
||||
String distance,
|
||||
String distanceUnit,
|
||||
String obstruction,
|
||||
String heightUnit,
|
||||
) {
|
||||
return '$distance $distanceUnit,被 $obstruction $heightUnit 阻止';
|
||||
}
|
||||
|
||||
@override
|
||||
String get losStatusChecking => '洛斯:正在检查...';
|
||||
|
||||
@override
|
||||
String get losStatusNoData => 'LOS:无数据';
|
||||
|
||||
@override
|
||||
String losStatusSummary(int clear, int total, int blocked, int unknown) {
|
||||
return 'LOS:$clear/$total 清除,$blocked 阻塞,$unknown 未知';
|
||||
}
|
||||
|
||||
@override
|
||||
String get losErrorElevationUnavailable => '一个或多个样本的海拔数据不可用。';
|
||||
|
||||
@override
|
||||
String get losErrorInvalidInput => '用于 LOS 计算的点/高程数据无效。';
|
||||
|
||||
@override
|
||||
String get losRenameCustomPoint => '重命名自定义点';
|
||||
|
||||
@override
|
||||
String get losPointName => '点名称';
|
||||
|
||||
@override
|
||||
String get losShowPanelTooltip => '显示 LOS 面板';
|
||||
|
||||
@override
|
||||
String get losHidePanelTooltip => '隐藏 LOS 面板';
|
||||
|
||||
@override
|
||||
String get losElevationAttribution => '高程数据:Open-Meteo (CC BY 4.0)';
|
||||
|
||||
@override
|
||||
String get contacts_pathTrace => '路径追踪';
|
||||
|
||||
|
|
|
|||
|
|
@ -1599,5 +1599,120 @@
|
|||
"chat_ShowAllPaths": "Toon alle paden",
|
||||
"settings_clientRepeat": "Herhalen: Afgekoppeld",
|
||||
"settings_clientRepeatSubtitle": "Laat dit apparaat de mesh-pakketten opnieuw verzenden voor andere apparaten.",
|
||||
"settings_clientRepeatFreqWarning": "Om een signaal buiten het netwerk te versturen, zijn frequenties van 433, 869 of 918 MHz vereist."
|
||||
"settings_clientRepeatFreqWarning": "Om een signaal buiten het netwerk te versturen, zijn frequenties van 433, 869 of 918 MHz vereist.",
|
||||
"settings_aboutOpenMeteoAttribution": "LOS-hoogtegegevens: Open-Meteo (CC BY 4.0)",
|
||||
"appSettings_unitsTitle": "Eenheden",
|
||||
"appSettings_unitsMetric": "Metrisch (m / km)",
|
||||
"appSettings_unitsImperial": "Imperiaal (ft / mi)",
|
||||
"map_lineOfSight": "Zichtlijn",
|
||||
"map_losScreenTitle": "Zichtlijn",
|
||||
"losSelectStartEnd": "Selecteer begin- en eindknooppunten voor LOS.",
|
||||
"losRunFailed": "Zichtlijncontrole mislukt: {error}",
|
||||
"@losRunFailed": {
|
||||
"placeholders": {
|
||||
"error": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"losClearAllPoints": "Wis alle punten",
|
||||
"losRunToViewElevationProfile": "Voer LOS uit om het hoogteprofiel te bekijken",
|
||||
"losMenuTitle": "LOS-menu",
|
||||
"losMenuSubtitle": "Tik op knooppunten of druk lang op de kaart voor aangepaste punten",
|
||||
"losShowDisplayNodes": "Toon weergaveknooppunten",
|
||||
"losCustomPoints": "Aangepaste punten",
|
||||
"losCustomPointLabel": "Aangepast {index}",
|
||||
"@losCustomPointLabel": {
|
||||
"placeholders": {
|
||||
"index": {
|
||||
"type": "int"
|
||||
}
|
||||
}
|
||||
},
|
||||
"losPointA": "Punt A",
|
||||
"losPointB": "Punt B",
|
||||
"losAntennaA": "Antenne A: {value} {unit}",
|
||||
"@losAntennaA": {
|
||||
"placeholders": {
|
||||
"value": {
|
||||
"type": "String"
|
||||
},
|
||||
"unit": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"losAntennaB": "Antenne B: {value} {unit}",
|
||||
"@losAntennaB": {
|
||||
"placeholders": {
|
||||
"value": {
|
||||
"type": "String"
|
||||
},
|
||||
"unit": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"losRun": "Voer LOS uit",
|
||||
"losNoElevationData": "Geen hoogtegegevens",
|
||||
"losProfileClear": "{distance} {distanceUnit}, vrije LOS, min. vrije ruimte {clearance} {heightUnit}",
|
||||
"@losProfileClear": {
|
||||
"placeholders": {
|
||||
"distance": {
|
||||
"type": "String"
|
||||
},
|
||||
"distanceUnit": {
|
||||
"type": "String"
|
||||
},
|
||||
"clearance": {
|
||||
"type": "String"
|
||||
},
|
||||
"heightUnit": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"losProfileBlocked": "{distance} {distanceUnit}, geblokkeerd door {obstruction} {heightUnit}",
|
||||
"@losProfileBlocked": {
|
||||
"placeholders": {
|
||||
"distance": {
|
||||
"type": "String"
|
||||
},
|
||||
"distanceUnit": {
|
||||
"type": "String"
|
||||
},
|
||||
"obstruction": {
|
||||
"type": "String"
|
||||
},
|
||||
"heightUnit": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"losStatusChecking": "LOS: controleren...",
|
||||
"losStatusNoData": "LOS: geen gegevens",
|
||||
"losStatusSummary": "LOS: {clear}/{total} gewist, {blocked} geblokkeerd, {unknown} onbekend",
|
||||
"@losStatusSummary": {
|
||||
"placeholders": {
|
||||
"clear": {
|
||||
"type": "int"
|
||||
},
|
||||
"total": {
|
||||
"type": "int"
|
||||
},
|
||||
"blocked": {
|
||||
"type": "int"
|
||||
},
|
||||
"unknown": {
|
||||
"type": "int"
|
||||
}
|
||||
}
|
||||
},
|
||||
"losErrorElevationUnavailable": "Hoogtegegevens niet beschikbaar voor een of meer monsters.",
|
||||
"losErrorInvalidInput": "Ongeldige punten/hoogtegegevens voor LOS-berekening.",
|
||||
"losRenameCustomPoint": "Hernoem aangepast punt",
|
||||
"losPointName": "Puntnaam",
|
||||
"losShowPanelTooltip": "Toon LOS-paneel",
|
||||
"losHidePanelTooltip": "LOS-paneel verbergen",
|
||||
"losElevationAttribution": "Hoogtegegevens: Open-Meteo (CC BY 4.0)"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1599,5 +1599,120 @@
|
|||
"chat_ShowAllPaths": "Pokaż wszystkie ścieżki",
|
||||
"settings_clientRepeatSubtitle": "Pozwól temu urządzeniu powtarzać pakiety danych dla innych urządzeń.",
|
||||
"settings_clientRepeat": "Powtórzenie: Niezależne od sieci",
|
||||
"settings_clientRepeatFreqWarning": "Powtórka poza siecią wymaga częstotliwości 433, 869 lub 918 MHz."
|
||||
"settings_clientRepeatFreqWarning": "Powtórka poza siecią wymaga częstotliwości 433, 869 lub 918 MHz.",
|
||||
"settings_aboutOpenMeteoAttribution": "Dane wysokościowe LOS: Open-Meteo (CC BY 4.0)",
|
||||
"appSettings_unitsTitle": "Jednostki",
|
||||
"appSettings_unitsMetric": "Metryczne (m / km)",
|
||||
"appSettings_unitsImperial": "Imperialne (ft / mi)",
|
||||
"map_lineOfSight": "Linia wzroku",
|
||||
"map_losScreenTitle": "Linia wzroku",
|
||||
"losSelectStartEnd": "Wybierz węzły początkowe i końcowe dla LOS.",
|
||||
"losRunFailed": "Sprawdzenie pola widzenia nie powiodło się: {error}",
|
||||
"@losRunFailed": {
|
||||
"placeholders": {
|
||||
"error": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"losClearAllPoints": "Wyczyść wszystkie punkty",
|
||||
"losRunToViewElevationProfile": "Uruchom LOS, aby wyświetlić profil wysokości",
|
||||
"losMenuTitle": "Menu LOS",
|
||||
"losMenuSubtitle": "Stuknij węzły lub naciśnij i przytrzymaj mapę, aby uzyskać niestandardowe punkty",
|
||||
"losShowDisplayNodes": "Pokaż węzły wyświetlające",
|
||||
"losCustomPoints": "Punkty niestandardowe",
|
||||
"losCustomPointLabel": "Niestandardowe {index}",
|
||||
"@losCustomPointLabel": {
|
||||
"placeholders": {
|
||||
"index": {
|
||||
"type": "int"
|
||||
}
|
||||
}
|
||||
},
|
||||
"losPointA": "Punkt A",
|
||||
"losPointB": "Punkt B",
|
||||
"losAntennaA": "Antena A: {value} {unit}",
|
||||
"@losAntennaA": {
|
||||
"placeholders": {
|
||||
"value": {
|
||||
"type": "String"
|
||||
},
|
||||
"unit": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"losAntennaB": "Antena B: {value} {unit}",
|
||||
"@losAntennaB": {
|
||||
"placeholders": {
|
||||
"value": {
|
||||
"type": "String"
|
||||
},
|
||||
"unit": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"losRun": "Uruchom LOS-a",
|
||||
"losNoElevationData": "Brak danych o wysokości",
|
||||
"losProfileClear": "{distance} {distanceUnit}, czysty LOS, minimalny prześwit {clearance} {heightUnit}",
|
||||
"@losProfileClear": {
|
||||
"placeholders": {
|
||||
"distance": {
|
||||
"type": "String"
|
||||
},
|
||||
"distanceUnit": {
|
||||
"type": "String"
|
||||
},
|
||||
"clearance": {
|
||||
"type": "String"
|
||||
},
|
||||
"heightUnit": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"losProfileBlocked": "{distance} {distanceUnit}, zablokowane przez {obstruction} {heightUnit}",
|
||||
"@losProfileBlocked": {
|
||||
"placeholders": {
|
||||
"distance": {
|
||||
"type": "String"
|
||||
},
|
||||
"distanceUnit": {
|
||||
"type": "String"
|
||||
},
|
||||
"obstruction": {
|
||||
"type": "String"
|
||||
},
|
||||
"heightUnit": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"losStatusChecking": "LOS: sprawdzam...",
|
||||
"losStatusNoData": "LOS: brak danych",
|
||||
"losStatusSummary": "LOS: {clear}/{total} jasne, {blocked} zablokowane, {unknown} nieznane",
|
||||
"@losStatusSummary": {
|
||||
"placeholders": {
|
||||
"clear": {
|
||||
"type": "int"
|
||||
},
|
||||
"total": {
|
||||
"type": "int"
|
||||
},
|
||||
"blocked": {
|
||||
"type": "int"
|
||||
},
|
||||
"unknown": {
|
||||
"type": "int"
|
||||
}
|
||||
}
|
||||
},
|
||||
"losErrorElevationUnavailable": "Dane dotyczące wysokości są niedostępne dla jednej lub większej liczby próbek.",
|
||||
"losErrorInvalidInput": "Nieprawidłowe dane punktów/wysokości do obliczenia LOS.",
|
||||
"losRenameCustomPoint": "Zmień nazwę punktu niestandardowego",
|
||||
"losPointName": "Nazwa punktu",
|
||||
"losShowPanelTooltip": "Pokaż panel LOS",
|
||||
"losHidePanelTooltip": "Ukryj panel LOS",
|
||||
"losElevationAttribution": "Dane dotyczące wysokości: Open-Meteo (CC BY 4.0)"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1599,5 +1599,120 @@
|
|||
"chat_ShowAllPaths": "Mostrar todos os caminhos",
|
||||
"settings_clientRepeatFreqWarning": "A repetição fora da rede requer frequências de 433, 869 ou 918 MHz.",
|
||||
"settings_clientRepeat": "Repetição sem rede",
|
||||
"settings_clientRepeatSubtitle": "Permita que este dispositivo repita pacotes de rede para outros dispositivos."
|
||||
"settings_clientRepeatSubtitle": "Permita que este dispositivo repita pacotes de rede para outros dispositivos.",
|
||||
"settings_aboutOpenMeteoAttribution": "Dados de elevação LOS: Open-Meteo (CC BY 4.0)",
|
||||
"appSettings_unitsTitle": "Unidades",
|
||||
"appSettings_unitsMetric": "Métrico (m/km)",
|
||||
"appSettings_unitsImperial": "Imperial (ft/mi)",
|
||||
"map_lineOfSight": "Linha de visão",
|
||||
"map_losScreenTitle": "Linha de visão",
|
||||
"losSelectStartEnd": "Selecione nós iniciais e finais para LOS.",
|
||||
"losRunFailed": "Falha na verificação da linha de visão: {error}",
|
||||
"@losRunFailed": {
|
||||
"placeholders": {
|
||||
"error": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"losClearAllPoints": "Limpe todos os pontos",
|
||||
"losRunToViewElevationProfile": "Execute o LOS para visualizar o perfil de elevação",
|
||||
"losMenuTitle": "Menu LOS",
|
||||
"losMenuSubtitle": "Toque nos nós ou mantenha pressionado o mapa para obter pontos personalizados",
|
||||
"losShowDisplayNodes": "Mostrar nós de exibição",
|
||||
"losCustomPoints": "Pontos personalizados",
|
||||
"losCustomPointLabel": "{index} personalizado",
|
||||
"@losCustomPointLabel": {
|
||||
"placeholders": {
|
||||
"index": {
|
||||
"type": "int"
|
||||
}
|
||||
}
|
||||
},
|
||||
"losPointA": "Ponto A",
|
||||
"losPointB": "Ponto B",
|
||||
"losAntennaA": "Antena A: {value} {unit}",
|
||||
"@losAntennaA": {
|
||||
"placeholders": {
|
||||
"value": {
|
||||
"type": "String"
|
||||
},
|
||||
"unit": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"losAntennaB": "Antena B: {value} {unit}",
|
||||
"@losAntennaB": {
|
||||
"placeholders": {
|
||||
"value": {
|
||||
"type": "String"
|
||||
},
|
||||
"unit": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"losRun": "Executar LOS",
|
||||
"losNoElevationData": "Sem dados de elevação",
|
||||
"losProfileClear": "{distance} {distanceUnit}, limpar LOS, liberação mínima {clearance} {heightUnit}",
|
||||
"@losProfileClear": {
|
||||
"placeholders": {
|
||||
"distance": {
|
||||
"type": "String"
|
||||
},
|
||||
"distanceUnit": {
|
||||
"type": "String"
|
||||
},
|
||||
"clearance": {
|
||||
"type": "String"
|
||||
},
|
||||
"heightUnit": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"losProfileBlocked": "{distance} {distanceUnit}, bloqueado por {obstruction} {heightUnit}",
|
||||
"@losProfileBlocked": {
|
||||
"placeholders": {
|
||||
"distance": {
|
||||
"type": "String"
|
||||
},
|
||||
"distanceUnit": {
|
||||
"type": "String"
|
||||
},
|
||||
"obstruction": {
|
||||
"type": "String"
|
||||
},
|
||||
"heightUnit": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"losStatusChecking": "LOS: verificando...",
|
||||
"losStatusNoData": "LOS: sem dados",
|
||||
"losStatusSummary": "LOS: {clear}/{total} limpo, {blocked} bloqueado, {unknown} desconhecido",
|
||||
"@losStatusSummary": {
|
||||
"placeholders": {
|
||||
"clear": {
|
||||
"type": "int"
|
||||
},
|
||||
"total": {
|
||||
"type": "int"
|
||||
},
|
||||
"blocked": {
|
||||
"type": "int"
|
||||
},
|
||||
"unknown": {
|
||||
"type": "int"
|
||||
}
|
||||
}
|
||||
},
|
||||
"losErrorElevationUnavailable": "Dados de elevação indisponíveis para uma ou mais amostras.",
|
||||
"losErrorInvalidInput": "Dados de pontos/elevação inválidos para cálculo de LOS.",
|
||||
"losRenameCustomPoint": "Renomear ponto personalizado",
|
||||
"losPointName": "Nome do ponto",
|
||||
"losShowPanelTooltip": "Mostrar painel LOS",
|
||||
"losHidePanelTooltip": "Ocultar painel LOS",
|
||||
"losElevationAttribution": "Dados de elevação: Open-Meteo (CC BY 4.0)"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -839,5 +839,120 @@
|
|||
"chat_ShowAllPaths": "Показать все пути",
|
||||
"settings_clientRepeatFreqWarning": "Для работы в режиме \"без подключения к сети\" требуется частота 433, 869 или 918 МГц.",
|
||||
"settings_clientRepeatSubtitle": "Позвольте этому устройству повторять пакеты данных для других устройств.",
|
||||
"settings_clientRepeat": "Повторение \"вне сети\""
|
||||
"settings_clientRepeat": "Повторение \"вне сети\"",
|
||||
"settings_aboutOpenMeteoAttribution": "Данные о высоте LOS: Open-Meteo (CC BY 4.0)",
|
||||
"appSettings_unitsTitle": "Единицы",
|
||||
"appSettings_unitsMetric": "Метрическая (м/км)",
|
||||
"appSettings_unitsImperial": "Имперская (ft / mi)",
|
||||
"map_lineOfSight": "Линия видимости",
|
||||
"map_losScreenTitle": "Линия видимости",
|
||||
"losSelectStartEnd": "Выберите начальный и конечный узлы для LOS.",
|
||||
"losRunFailed": "Проверка прямой видимости не удалась: {error}",
|
||||
"@losRunFailed": {
|
||||
"placeholders": {
|
||||
"error": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"losClearAllPoints": "Очистить все точки",
|
||||
"losRunToViewElevationProfile": "Запустите LOS, чтобы просмотреть профиль высот.",
|
||||
"losMenuTitle": "ЛОС Меню",
|
||||
"losMenuSubtitle": "Коснитесь узлов или нажмите и удерживайте карту для выбора пользовательских точек.",
|
||||
"losShowDisplayNodes": "Показать узлы отображения",
|
||||
"losCustomPoints": "Пользовательские точки",
|
||||
"losCustomPointLabel": "Пользовательский {index}",
|
||||
"@losCustomPointLabel": {
|
||||
"placeholders": {
|
||||
"index": {
|
||||
"type": "int"
|
||||
}
|
||||
}
|
||||
},
|
||||
"losPointA": "Точка А",
|
||||
"losPointB": "Точка Б",
|
||||
"losAntennaA": "Антенна А: {value} {unit}",
|
||||
"@losAntennaA": {
|
||||
"placeholders": {
|
||||
"value": {
|
||||
"type": "String"
|
||||
},
|
||||
"unit": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"losAntennaB": "Антенна Б: {value} {unit}",
|
||||
"@losAntennaB": {
|
||||
"placeholders": {
|
||||
"value": {
|
||||
"type": "String"
|
||||
},
|
||||
"unit": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"losRun": "Запустить ЛОС",
|
||||
"losNoElevationData": "Нет данных о высоте",
|
||||
"losProfileClear": "{distance} {distanceUnit}, свободная зона видимости, минимальный зазор {clearance} {heightUnit}",
|
||||
"@losProfileClear": {
|
||||
"placeholders": {
|
||||
"distance": {
|
||||
"type": "String"
|
||||
},
|
||||
"distanceUnit": {
|
||||
"type": "String"
|
||||
},
|
||||
"clearance": {
|
||||
"type": "String"
|
||||
},
|
||||
"heightUnit": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"losProfileBlocked": "{distance} {distanceUnit}, заблокирован {obstruction} {heightUnit}",
|
||||
"@losProfileBlocked": {
|
||||
"placeholders": {
|
||||
"distance": {
|
||||
"type": "String"
|
||||
},
|
||||
"distanceUnit": {
|
||||
"type": "String"
|
||||
},
|
||||
"obstruction": {
|
||||
"type": "String"
|
||||
},
|
||||
"heightUnit": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"losStatusChecking": "ЛОС: проверяю...",
|
||||
"losStatusNoData": "ЛОС: нет данных",
|
||||
"losStatusSummary": "LOS: {clear}/{total} очищено, {blocked} заблокировано, {unknown} неизвестно.",
|
||||
"@losStatusSummary": {
|
||||
"placeholders": {
|
||||
"clear": {
|
||||
"type": "int"
|
||||
},
|
||||
"total": {
|
||||
"type": "int"
|
||||
},
|
||||
"blocked": {
|
||||
"type": "int"
|
||||
},
|
||||
"unknown": {
|
||||
"type": "int"
|
||||
}
|
||||
}
|
||||
},
|
||||
"losErrorElevationUnavailable": "Данные о высоте недоступны для одного или нескольких образцов.",
|
||||
"losErrorInvalidInput": "Неверные данные о точках/высоте для расчета LOS.",
|
||||
"losRenameCustomPoint": "Переименовать пользовательскую точку",
|
||||
"losPointName": "Имя точки",
|
||||
"losShowPanelTooltip": "Показать панель LOS",
|
||||
"losHidePanelTooltip": "Скрыть панель LOS",
|
||||
"losElevationAttribution": "Данные о высоте: Open-Meteo (CC BY 4.0)"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1599,5 +1599,120 @@
|
|||
"chat_ShowAllPaths": "Zobraziť všetky cesty",
|
||||
"settings_clientRepeat": "Opätovné použitie bez elektrickej siete",
|
||||
"settings_clientRepeatFreqWarning": "Použitie off-grid systému vyžaduje frekvencie 433, 869 alebo 918 MHz.",
|
||||
"settings_clientRepeatSubtitle": "Umožnite, aby toto zariadenie opakovávalo siete pre ostatných."
|
||||
"settings_clientRepeatSubtitle": "Umožnite, aby toto zariadenie opakovávalo siete pre ostatných.",
|
||||
"settings_aboutOpenMeteoAttribution": "Údaje o nadmorskej výške LOS: Open-Meteo (CC BY 4.0)",
|
||||
"appSettings_unitsTitle": "Jednotky",
|
||||
"appSettings_unitsMetric": "Metrické (m / km)",
|
||||
"appSettings_unitsImperial": "Imperiálne (ft / mi)",
|
||||
"map_lineOfSight": "Line of Sight",
|
||||
"map_losScreenTitle": "Line of Sight",
|
||||
"losSelectStartEnd": "Vyberte počiatočný a koncový uzol pre LOS.",
|
||||
"losRunFailed": "Kontrola priamej viditeľnosti zlyhala: {error}",
|
||||
"@losRunFailed": {
|
||||
"placeholders": {
|
||||
"error": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"losClearAllPoints": "Vymazať všetky body",
|
||||
"losRunToViewElevationProfile": "Ak chcete zobraziť výškový profil, spustite LOS",
|
||||
"losMenuTitle": "Menu LOS",
|
||||
"losMenuSubtitle": "Klepnutím na uzly alebo dlhým stlačením mapy získate vlastné body",
|
||||
"losShowDisplayNodes": "Zobraziť uzly zobrazenia",
|
||||
"losCustomPoints": "Vlastné body",
|
||||
"losCustomPointLabel": "Vlastné {index}",
|
||||
"@losCustomPointLabel": {
|
||||
"placeholders": {
|
||||
"index": {
|
||||
"type": "int"
|
||||
}
|
||||
}
|
||||
},
|
||||
"losPointA": "Bod A",
|
||||
"losPointB": "Bod B",
|
||||
"losAntennaA": "Anténa A: {value} {unit}",
|
||||
"@losAntennaA": {
|
||||
"placeholders": {
|
||||
"value": {
|
||||
"type": "String"
|
||||
},
|
||||
"unit": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"losAntennaB": "Anténa B: {value} {unit}",
|
||||
"@losAntennaB": {
|
||||
"placeholders": {
|
||||
"value": {
|
||||
"type": "String"
|
||||
},
|
||||
"unit": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"losRun": "Spustite LOS",
|
||||
"losNoElevationData": "Žiadne údaje o nadmorskej výške",
|
||||
"losProfileClear": "{distance} {distanceUnit}, vymazať LOS, min. vôľa {clearance} {heightUnit}",
|
||||
"@losProfileClear": {
|
||||
"placeholders": {
|
||||
"distance": {
|
||||
"type": "String"
|
||||
},
|
||||
"distanceUnit": {
|
||||
"type": "String"
|
||||
},
|
||||
"clearance": {
|
||||
"type": "String"
|
||||
},
|
||||
"heightUnit": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"losProfileBlocked": "{distance} {distanceUnit}, blokovaný {obstruction} {heightUnit}",
|
||||
"@losProfileBlocked": {
|
||||
"placeholders": {
|
||||
"distance": {
|
||||
"type": "String"
|
||||
},
|
||||
"distanceUnit": {
|
||||
"type": "String"
|
||||
},
|
||||
"obstruction": {
|
||||
"type": "String"
|
||||
},
|
||||
"heightUnit": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"losStatusChecking": "LOS: kontrolujem...",
|
||||
"losStatusNoData": "LOS: žiadne údaje",
|
||||
"losStatusSummary": "LOS: {clear}/{total} vymazané, {blocked} blokované, {unknown} neznáme",
|
||||
"@losStatusSummary": {
|
||||
"placeholders": {
|
||||
"clear": {
|
||||
"type": "int"
|
||||
},
|
||||
"total": {
|
||||
"type": "int"
|
||||
},
|
||||
"blocked": {
|
||||
"type": "int"
|
||||
},
|
||||
"unknown": {
|
||||
"type": "int"
|
||||
}
|
||||
}
|
||||
},
|
||||
"losErrorElevationUnavailable": "Údaje o nadmorskej výške nie sú k dispozícii pre jednu alebo viacero vzoriek.",
|
||||
"losErrorInvalidInput": "Neplatné body/údaje o nadmorskej výške pre výpočet LOS.",
|
||||
"losRenameCustomPoint": "Premenovať vlastný bod",
|
||||
"losPointName": "Názov bodu",
|
||||
"losShowPanelTooltip": "Zobraziť panel LOS",
|
||||
"losHidePanelTooltip": "Skryť panel LOS",
|
||||
"losElevationAttribution": "Údaje o nadmorskej výške: Open-Meteo (CC BY 4.0)"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1599,5 +1599,120 @@
|
|||
"chat_ShowAllPaths": "Prikaži vse poti",
|
||||
"settings_clientRepeatFreqWarning": "Za ponovni prenos na brezžični način so potrebne frekvence 433, 869 ali 918 MHz.",
|
||||
"settings_clientRepeatSubtitle": "Omogočite temu naprave, da ponavlja paketne sporočila za druge.",
|
||||
"settings_clientRepeat": "Neovadno ponavljanje"
|
||||
"settings_clientRepeat": "Neovadno ponavljanje",
|
||||
"settings_aboutOpenMeteoAttribution": "Podatki o višini LOS: Open-Meteo (CC BY 4.0)",
|
||||
"appSettings_unitsTitle": "Enote",
|
||||
"appSettings_unitsMetric": "Metrična (m/km)",
|
||||
"appSettings_unitsImperial": "Imperialno (ft / mi)",
|
||||
"map_lineOfSight": "Linija vida",
|
||||
"map_losScreenTitle": "Linija vida",
|
||||
"losSelectStartEnd": "Izberite začetno in končno vozlišče za LOS.",
|
||||
"losRunFailed": "Preverjanje vidnega polja ni uspelo: {error}",
|
||||
"@losRunFailed": {
|
||||
"placeholders": {
|
||||
"error": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"losClearAllPoints": "Počisti vse točke",
|
||||
"losRunToViewElevationProfile": "Zaženite LOS za ogled višinskega profila",
|
||||
"losMenuTitle": "LOS meni",
|
||||
"losMenuSubtitle": "Tapnite vozlišča ali dolgo pritisnite na zemljevid za točke po meri",
|
||||
"losShowDisplayNodes": "Pokaži prikazna vozlišča",
|
||||
"losCustomPoints": "Točke po meri",
|
||||
"losCustomPointLabel": "Po meri {index}",
|
||||
"@losCustomPointLabel": {
|
||||
"placeholders": {
|
||||
"index": {
|
||||
"type": "int"
|
||||
}
|
||||
}
|
||||
},
|
||||
"losPointA": "Točka A",
|
||||
"losPointB": "Točka B",
|
||||
"losAntennaA": "Antena A: {value} {unit}",
|
||||
"@losAntennaA": {
|
||||
"placeholders": {
|
||||
"value": {
|
||||
"type": "String"
|
||||
},
|
||||
"unit": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"losAntennaB": "Antena B: {value} {unit}",
|
||||
"@losAntennaB": {
|
||||
"placeholders": {
|
||||
"value": {
|
||||
"type": "String"
|
||||
},
|
||||
"unit": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"losRun": "Zaženi LOS",
|
||||
"losNoElevationData": "Ni podatkov o višini",
|
||||
"losProfileClear": "{distance} {distanceUnit}, čisti LOS, najmanjša razdalja {clearance} {heightUnit}",
|
||||
"@losProfileClear": {
|
||||
"placeholders": {
|
||||
"distance": {
|
||||
"type": "String"
|
||||
},
|
||||
"distanceUnit": {
|
||||
"type": "String"
|
||||
},
|
||||
"clearance": {
|
||||
"type": "String"
|
||||
},
|
||||
"heightUnit": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"losProfileBlocked": "{distance} {distanceUnit}, blokiral {obstruction} {heightUnit}",
|
||||
"@losProfileBlocked": {
|
||||
"placeholders": {
|
||||
"distance": {
|
||||
"type": "String"
|
||||
},
|
||||
"distanceUnit": {
|
||||
"type": "String"
|
||||
},
|
||||
"obstruction": {
|
||||
"type": "String"
|
||||
},
|
||||
"heightUnit": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"losStatusChecking": "LOS: preverjam ...",
|
||||
"losStatusNoData": "LOS: ni podatkov",
|
||||
"losStatusSummary": "LOS: {clear}/{total} jasno, {blocked} blokirano, {unknown} neznano",
|
||||
"@losStatusSummary": {
|
||||
"placeholders": {
|
||||
"clear": {
|
||||
"type": "int"
|
||||
},
|
||||
"total": {
|
||||
"type": "int"
|
||||
},
|
||||
"blocked": {
|
||||
"type": "int"
|
||||
},
|
||||
"unknown": {
|
||||
"type": "int"
|
||||
}
|
||||
}
|
||||
},
|
||||
"losErrorElevationUnavailable": "Podatki o nadmorski višini niso na voljo za enega ali več vzorcev.",
|
||||
"losErrorInvalidInput": "Neveljavni podatki o točkah/višini za izračun LOS.",
|
||||
"losRenameCustomPoint": "Preimenujte točko po meri",
|
||||
"losPointName": "Ime točke",
|
||||
"losShowPanelTooltip": "Pokaži ploščo LOS",
|
||||
"losHidePanelTooltip": "Skrij ploščo LOS",
|
||||
"losElevationAttribution": "Podatki o višini: Open-Meteo (CC BY 4.0)"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1599,5 +1599,120 @@
|
|||
"chat_ShowAllPaths": "Visa alla vägar",
|
||||
"settings_clientRepeatSubtitle": "Låt enheten repetera nätpaket för andra användare.",
|
||||
"settings_clientRepeat": "Upprepa utan elnät",
|
||||
"settings_clientRepeatFreqWarning": "För att kunna kommunicera utanför elnätet krävs frekvenserna 433, 869 eller 918 MHz."
|
||||
"settings_clientRepeatFreqWarning": "För att kunna kommunicera utanför elnätet krävs frekvenserna 433, 869 eller 918 MHz.",
|
||||
"settings_aboutOpenMeteoAttribution": "LOS-höjddata: Open-Meteo (CC BY 4.0)",
|
||||
"appSettings_unitsTitle": "Enheter",
|
||||
"appSettings_unitsMetric": "Metriskt (m/km)",
|
||||
"appSettings_unitsImperial": "Imperialt (ft / mi)",
|
||||
"map_lineOfSight": "Synlinje",
|
||||
"map_losScreenTitle": "Synlinje",
|
||||
"losSelectStartEnd": "Välj start- och slutnoder för LOS.",
|
||||
"losRunFailed": "Synlinjekontroll misslyckades: {error}",
|
||||
"@losRunFailed": {
|
||||
"placeholders": {
|
||||
"error": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"losClearAllPoints": "Rensa alla punkter",
|
||||
"losRunToViewElevationProfile": "Kör LOS för att se höjdprofil",
|
||||
"losMenuTitle": "LOS-menyn",
|
||||
"losMenuSubtitle": "Tryck på noder eller tryck länge på kartan för anpassade punkter",
|
||||
"losShowDisplayNodes": "Visa displaynoder",
|
||||
"losCustomPoints": "Anpassade poäng",
|
||||
"losCustomPointLabel": "Anpassad {index}",
|
||||
"@losCustomPointLabel": {
|
||||
"placeholders": {
|
||||
"index": {
|
||||
"type": "int"
|
||||
}
|
||||
}
|
||||
},
|
||||
"losPointA": "Punkt A",
|
||||
"losPointB": "Punkt B",
|
||||
"losAntennaA": "Antenn A: {value} {unit}",
|
||||
"@losAntennaA": {
|
||||
"placeholders": {
|
||||
"value": {
|
||||
"type": "String"
|
||||
},
|
||||
"unit": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"losAntennaB": "Antenn B: {value} {unit}",
|
||||
"@losAntennaB": {
|
||||
"placeholders": {
|
||||
"value": {
|
||||
"type": "String"
|
||||
},
|
||||
"unit": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"losRun": "Kör LOS",
|
||||
"losNoElevationData": "Inga höjddata",
|
||||
"losProfileClear": "{distance} {distanceUnit}, rensa LOS, min clearance {clearance} {heightUnit}",
|
||||
"@losProfileClear": {
|
||||
"placeholders": {
|
||||
"distance": {
|
||||
"type": "String"
|
||||
},
|
||||
"distanceUnit": {
|
||||
"type": "String"
|
||||
},
|
||||
"clearance": {
|
||||
"type": "String"
|
||||
},
|
||||
"heightUnit": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"losProfileBlocked": "{distance} {distanceUnit}, blockerad av {obstruction} {heightUnit}",
|
||||
"@losProfileBlocked": {
|
||||
"placeholders": {
|
||||
"distance": {
|
||||
"type": "String"
|
||||
},
|
||||
"distanceUnit": {
|
||||
"type": "String"
|
||||
},
|
||||
"obstruction": {
|
||||
"type": "String"
|
||||
},
|
||||
"heightUnit": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"losStatusChecking": "LOS: kollar...",
|
||||
"losStatusNoData": "LOS: inga data",
|
||||
"losStatusSummary": "LOS: {clear}/{total} rensa, {blocked} blockerad, {unknown} okänd",
|
||||
"@losStatusSummary": {
|
||||
"placeholders": {
|
||||
"clear": {
|
||||
"type": "int"
|
||||
},
|
||||
"total": {
|
||||
"type": "int"
|
||||
},
|
||||
"blocked": {
|
||||
"type": "int"
|
||||
},
|
||||
"unknown": {
|
||||
"type": "int"
|
||||
}
|
||||
}
|
||||
},
|
||||
"losErrorElevationUnavailable": "Höjddata är inte tillgänglig för ett eller flera prover.",
|
||||
"losErrorInvalidInput": "Ogiltiga poäng/höjddata för LOS-beräkning.",
|
||||
"losRenameCustomPoint": "Byt namn på anpassad punkt",
|
||||
"losPointName": "Punktnamn",
|
||||
"losShowPanelTooltip": "Visa LOS-panelen",
|
||||
"losHidePanelTooltip": "Dölj LOS-panelen",
|
||||
"losElevationAttribution": "Höjddata: Open-Meteo (CC BY 4.0)"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1599,5 +1599,120 @@
|
|||
"chat_ShowAllPaths": "Показати всі шляхи",
|
||||
"settings_clientRepeatFreqWarning": "Повтор без підключення до мережі вимагає частоти 433, 869 або 918 МГц.",
|
||||
"settings_clientRepeatSubtitle": "Дозвольте цьому пристрою повторювати пакети даних для інших пристроїв.",
|
||||
"settings_clientRepeat": "Автономна система"
|
||||
"settings_clientRepeat": "Автономна система",
|
||||
"settings_aboutOpenMeteoAttribution": "Дані про висоту LOS: Open-Meteo (CC BY 4.0)",
|
||||
"appSettings_unitsTitle": "одиниці",
|
||||
"appSettings_unitsMetric": "Метричний (м / км)",
|
||||
"appSettings_unitsImperial": "Імперська (ft / mi)",
|
||||
"map_lineOfSight": "Пряма видимість",
|
||||
"map_losScreenTitle": "Пряма видимість",
|
||||
"losSelectStartEnd": "Виберіть початковий і кінцевий вузли для LOS.",
|
||||
"losRunFailed": "Помилка перевірки прямої видимості: {error}",
|
||||
"@losRunFailed": {
|
||||
"placeholders": {
|
||||
"error": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"losClearAllPoints": "Очистити всі пункти",
|
||||
"losRunToViewElevationProfile": "Запустіть LOS, щоб переглянути профіль висоти",
|
||||
"losMenuTitle": "Меню LOS",
|
||||
"losMenuSubtitle": "Торкніться вузлів або утримуйте карту, щоб отримати власні точки",
|
||||
"losShowDisplayNodes": "Показати вузли відображення",
|
||||
"losCustomPoints": "Користувальницькі точки",
|
||||
"losCustomPointLabel": "Спеціальний {index}",
|
||||
"@losCustomPointLabel": {
|
||||
"placeholders": {
|
||||
"index": {
|
||||
"type": "int"
|
||||
}
|
||||
}
|
||||
},
|
||||
"losPointA": "Точка А",
|
||||
"losPointB": "Точка Б",
|
||||
"losAntennaA": "Антена A: {value} {unit}",
|
||||
"@losAntennaA": {
|
||||
"placeholders": {
|
||||
"value": {
|
||||
"type": "String"
|
||||
},
|
||||
"unit": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"losAntennaB": "Антена B: {value} {unit}",
|
||||
"@losAntennaB": {
|
||||
"placeholders": {
|
||||
"value": {
|
||||
"type": "String"
|
||||
},
|
||||
"unit": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"losRun": "Запустіть LOS",
|
||||
"losNoElevationData": "Немає даних про висоту",
|
||||
"losProfileClear": "{distance} {distanceUnit}, чистий LOS, мінімальний зазор {clearance} {heightUnit}",
|
||||
"@losProfileClear": {
|
||||
"placeholders": {
|
||||
"distance": {
|
||||
"type": "String"
|
||||
},
|
||||
"distanceUnit": {
|
||||
"type": "String"
|
||||
},
|
||||
"clearance": {
|
||||
"type": "String"
|
||||
},
|
||||
"heightUnit": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"losProfileBlocked": "{distance} {distanceUnit}, заблоковано {obstruction} {heightUnit}",
|
||||
"@losProfileBlocked": {
|
||||
"placeholders": {
|
||||
"distance": {
|
||||
"type": "String"
|
||||
},
|
||||
"distanceUnit": {
|
||||
"type": "String"
|
||||
},
|
||||
"obstruction": {
|
||||
"type": "String"
|
||||
},
|
||||
"heightUnit": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"losStatusChecking": "LOS: перевірка...",
|
||||
"losStatusNoData": "LOS: немає даних",
|
||||
"losStatusSummary": "LOS: {clear}/{total} очищено, {blocked} заблоковано, {unknown} невідомо",
|
||||
"@losStatusSummary": {
|
||||
"placeholders": {
|
||||
"clear": {
|
||||
"type": "int"
|
||||
},
|
||||
"total": {
|
||||
"type": "int"
|
||||
},
|
||||
"blocked": {
|
||||
"type": "int"
|
||||
},
|
||||
"unknown": {
|
||||
"type": "int"
|
||||
}
|
||||
}
|
||||
},
|
||||
"losErrorElevationUnavailable": "Дані про висоту недоступні для одного чи кількох зразків.",
|
||||
"losErrorInvalidInput": "Недійсні дані про точки/висоту для розрахунку LOS.",
|
||||
"losRenameCustomPoint": "Перейменуйте спеціальну точку",
|
||||
"losPointName": "Назва точки",
|
||||
"losShowPanelTooltip": "Показати панель LOS",
|
||||
"losHidePanelTooltip": "Приховати панель LOS",
|
||||
"losElevationAttribution": "Дані про висоту: Open-Meteo (CC BY 4.0)"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1599,5 +1599,120 @@
|
|||
"chat_ShowAllPaths": "显示所有路径",
|
||||
"settings_clientRepeat": "离网重复",
|
||||
"settings_clientRepeatSubtitle": "允许此设备重复发送网状数据包给其他设备",
|
||||
"settings_clientRepeatFreqWarning": "离网重复通信需要使用 433、869 或 918 兆赫兹的频率。"
|
||||
"settings_clientRepeatFreqWarning": "离网重复通信需要使用 433、869 或 918 兆赫兹的频率。",
|
||||
"settings_aboutOpenMeteoAttribution": "LOS 高程数据:Open-Meteo (CC BY 4.0)",
|
||||
"appSettings_unitsTitle": "单位",
|
||||
"appSettings_unitsMetric": "公制(米/公里)",
|
||||
"appSettings_unitsImperial": "英制 (ft / mi)",
|
||||
"map_lineOfSight": "视线",
|
||||
"map_losScreenTitle": "视线",
|
||||
"losSelectStartEnd": "选择 LOS 的起始节点和结束节点。",
|
||||
"losRunFailed": "视线检查失败:{error}",
|
||||
"@losRunFailed": {
|
||||
"placeholders": {
|
||||
"error": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"losClearAllPoints": "清除所有点",
|
||||
"losRunToViewElevationProfile": "运行 LOS 查看高程剖面",
|
||||
"losMenuTitle": "服务水平菜单",
|
||||
"losMenuSubtitle": "点击节点或长按地图以获取自定义点",
|
||||
"losShowDisplayNodes": "显示显示节点",
|
||||
"losCustomPoints": "自定义积分",
|
||||
"losCustomPointLabel": "自定义 {index}",
|
||||
"@losCustomPointLabel": {
|
||||
"placeholders": {
|
||||
"index": {
|
||||
"type": "int"
|
||||
}
|
||||
}
|
||||
},
|
||||
"losPointA": "A点",
|
||||
"losPointB": "B点",
|
||||
"losAntennaA": "天线 A: {value} {unit}",
|
||||
"@losAntennaA": {
|
||||
"placeholders": {
|
||||
"value": {
|
||||
"type": "String"
|
||||
},
|
||||
"unit": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"losAntennaB": "天线 B:{value} {unit}",
|
||||
"@losAntennaB": {
|
||||
"placeholders": {
|
||||
"value": {
|
||||
"type": "String"
|
||||
},
|
||||
"unit": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"losRun": "运行视距",
|
||||
"losNoElevationData": "无海拔数据",
|
||||
"losProfileClear": "{distance} {distanceUnit},清除 LOS,最小间隙 {clearance} {heightUnit}",
|
||||
"@losProfileClear": {
|
||||
"placeholders": {
|
||||
"distance": {
|
||||
"type": "String"
|
||||
},
|
||||
"distanceUnit": {
|
||||
"type": "String"
|
||||
},
|
||||
"clearance": {
|
||||
"type": "String"
|
||||
},
|
||||
"heightUnit": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"losProfileBlocked": "{distance} {distanceUnit},被 {obstruction} {heightUnit} 阻止",
|
||||
"@losProfileBlocked": {
|
||||
"placeholders": {
|
||||
"distance": {
|
||||
"type": "String"
|
||||
},
|
||||
"distanceUnit": {
|
||||
"type": "String"
|
||||
},
|
||||
"obstruction": {
|
||||
"type": "String"
|
||||
},
|
||||
"heightUnit": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"losStatusChecking": "洛斯:正在检查...",
|
||||
"losStatusNoData": "LOS:无数据",
|
||||
"losStatusSummary": "LOS:{clear}/{total} 清除,{blocked} 阻塞,{unknown} 未知",
|
||||
"@losStatusSummary": {
|
||||
"placeholders": {
|
||||
"clear": {
|
||||
"type": "int"
|
||||
},
|
||||
"total": {
|
||||
"type": "int"
|
||||
},
|
||||
"blocked": {
|
||||
"type": "int"
|
||||
},
|
||||
"unknown": {
|
||||
"type": "int"
|
||||
}
|
||||
}
|
||||
},
|
||||
"losErrorElevationUnavailable": "一个或多个样本的海拔数据不可用。",
|
||||
"losErrorInvalidInput": "用于 LOS 计算的点/高程数据无效。",
|
||||
"losRenameCustomPoint": "重命名自定义点",
|
||||
"losPointName": "点名称",
|
||||
"losShowPanelTooltip": "显示 LOS 面板",
|
||||
"losHidePanelTooltip": "隐藏 LOS 面板",
|
||||
"losElevationAttribution": "高程数据:Open-Meteo (CC BY 4.0)"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_localizations/flutter_localizations.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'l10n/app_localizations.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
|
|
@ -47,6 +48,7 @@ void main() async {
|
|||
final notificationService = NotificationService();
|
||||
await notificationService.initialize();
|
||||
await backgroundService.initialize();
|
||||
_registerThirdPartyLicenses();
|
||||
|
||||
// Wire up connector with services
|
||||
connector.initialize(
|
||||
|
|
@ -80,6 +82,27 @@ void main() async {
|
|||
);
|
||||
}
|
||||
|
||||
void _registerThirdPartyLicenses() {
|
||||
LicenseRegistry.addLicense(() async* {
|
||||
yield const LicenseEntryWithLineBreaks(
|
||||
<String>['Open-Meteo Elevation API Data'],
|
||||
'''
|
||||
Data used by LOS elevation lookups is provided by Open-Meteo.
|
||||
|
||||
Open-Meteo terms and attribution:
|
||||
https://open-meteo.com/en/terms
|
||||
|
||||
Elevation API:
|
||||
https://open-meteo.com/en/docs/elevation-api
|
||||
|
||||
Attribution license reference:
|
||||
Creative Commons Attribution 4.0 International (CC BY 4.0)
|
||||
https://creativecommons.org/licenses/by/4.0/
|
||||
''',
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
class MeshCoreApp extends StatelessWidget {
|
||||
final MeshCoreConnector connector;
|
||||
final MessageRetryService retryService;
|
||||
|
|
|
|||
|
|
@ -1,3 +1,16 @@
|
|||
enum UnitSystem { metric, imperial }
|
||||
|
||||
extension UnitSystemValue on UnitSystem {
|
||||
String get value {
|
||||
switch (this) {
|
||||
case UnitSystem.imperial:
|
||||
return 'imperial';
|
||||
case UnitSystem.metric:
|
||||
return 'metric';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class AppSettings {
|
||||
static const Object _unset = Object();
|
||||
|
||||
|
|
@ -21,6 +34,7 @@ class AppSettings {
|
|||
final String? languageOverride; // null = system default
|
||||
final bool appDebugLogEnabled;
|
||||
final Map<String, String> batteryChemistryByDeviceId;
|
||||
final UnitSystem unitSystem;
|
||||
|
||||
AppSettings({
|
||||
this.clearPathOnMaxRetry = false,
|
||||
|
|
@ -43,6 +57,7 @@ class AppSettings {
|
|||
this.languageOverride,
|
||||
this.appDebugLogEnabled = false,
|
||||
Map<String, String>? batteryChemistryByDeviceId,
|
||||
this.unitSystem = UnitSystem.metric,
|
||||
}) : batteryChemistryByDeviceId = batteryChemistryByDeviceId ?? {};
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
|
|
@ -67,10 +82,18 @@ class AppSettings {
|
|||
'language_override': languageOverride,
|
||||
'app_debug_log_enabled': appDebugLogEnabled,
|
||||
'battery_chemistry_by_device_id': batteryChemistryByDeviceId,
|
||||
'unit_system': unitSystem.value,
|
||||
};
|
||||
}
|
||||
|
||||
factory AppSettings.fromJson(Map<String, dynamic> json) {
|
||||
UnitSystem parseUnitSystem(dynamic value) {
|
||||
if (value is String && value.toLowerCase() == 'imperial') {
|
||||
return UnitSystem.imperial;
|
||||
}
|
||||
return UnitSystem.metric;
|
||||
}
|
||||
|
||||
return AppSettings(
|
||||
clearPathOnMaxRetry: json['clear_path_on_max_retry'] as bool? ?? false,
|
||||
mapShowRepeaters: json['map_show_repeaters'] as bool? ?? true,
|
||||
|
|
@ -101,6 +124,9 @@ class AppSettings {
|
|||
(key, value) => MapEntry(key.toString(), value.toString()),
|
||||
) ??
|
||||
{},
|
||||
unitSystem: parseUnitSystem(
|
||||
json['unit_system'] ?? json['los_unit_system'],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -125,6 +151,7 @@ class AppSettings {
|
|||
Object? languageOverride = _unset,
|
||||
bool? appDebugLogEnabled,
|
||||
Map<String, String>? batteryChemistryByDeviceId,
|
||||
UnitSystem? unitSystem,
|
||||
}) {
|
||||
return AppSettings(
|
||||
clearPathOnMaxRetry: clearPathOnMaxRetry ?? this.clearPathOnMaxRetry,
|
||||
|
|
@ -154,6 +181,7 @@ class AppSettings {
|
|||
appDebugLogEnabled: appDebugLogEnabled ?? this.appDebugLogEnabled,
|
||||
batteryChemistryByDeviceId:
|
||||
batteryChemistryByDeviceId ?? this.batteryChemistryByDeviceId,
|
||||
unitSystem: unitSystem ?? this.unitSystem,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import 'package:provider/provider.dart';
|
|||
|
||||
import '../l10n/l10n.dart';
|
||||
import '../services/app_debug_log_service.dart';
|
||||
import '../widgets/adaptive_app_bar_title.dart';
|
||||
|
||||
class AppDebugLogScreen extends StatelessWidget {
|
||||
const AppDebugLogScreen({super.key});
|
||||
|
|
@ -17,7 +18,7 @@ class AppDebugLogScreen extends StatelessWidget {
|
|||
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text(context.l10n.debugLog_appTitle),
|
||||
title: AdaptiveAppBarTitle(context.l10n.debugLog_appTitle),
|
||||
centerTitle: true,
|
||||
actions: [
|
||||
IconButton(
|
||||
|
|
|
|||
|
|
@ -3,8 +3,10 @@ import 'package:provider/provider.dart';
|
|||
|
||||
import '../connector/meshcore_connector.dart';
|
||||
import '../l10n/l10n.dart';
|
||||
import '../models/app_settings.dart';
|
||||
import '../services/app_settings_service.dart';
|
||||
import '../services/notification_service.dart';
|
||||
import '../widgets/adaptive_app_bar_title.dart';
|
||||
import 'map_cache_screen.dart';
|
||||
|
||||
class AppSettingsScreen extends StatelessWidget {
|
||||
|
|
@ -14,7 +16,7 @@ class AppSettingsScreen extends StatelessWidget {
|
|||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text(context.l10n.appSettings_title),
|
||||
title: AdaptiveAppBarTitle(context.l10n.appSettings_title),
|
||||
centerTitle: true,
|
||||
),
|
||||
body: SafeArea(
|
||||
|
|
@ -360,6 +362,18 @@ class AppSettingsScreen extends StatelessWidget {
|
|||
onTap: () => _showTimeFilterDialog(context, settingsService),
|
||||
),
|
||||
const Divider(height: 1),
|
||||
ListTile(
|
||||
leading: const Icon(Icons.straighten),
|
||||
title: Text(context.l10n.appSettings_unitsTitle),
|
||||
subtitle: Text(
|
||||
settingsService.settings.unitSystem == UnitSystem.imperial
|
||||
? context.l10n.appSettings_unitsImperial
|
||||
: context.l10n.appSettings_unitsMetric,
|
||||
),
|
||||
trailing: const Icon(Icons.chevron_right),
|
||||
onTap: () => _showUnitsDialog(context, settingsService),
|
||||
),
|
||||
const Divider(height: 1),
|
||||
ListTile(
|
||||
leading: const Icon(Icons.download_outlined),
|
||||
title: Text(context.l10n.appSettings_offlineMapCache),
|
||||
|
|
@ -706,6 +720,46 @@ class AppSettingsScreen extends StatelessWidget {
|
|||
);
|
||||
}
|
||||
|
||||
void _showUnitsDialog(
|
||||
BuildContext context,
|
||||
AppSettingsService settingsService,
|
||||
) {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) => AlertDialog(
|
||||
title: Text(context.l10n.appSettings_unitsTitle),
|
||||
content: RadioGroup<UnitSystem>(
|
||||
groupValue: settingsService.settings.unitSystem,
|
||||
onChanged: (value) {
|
||||
if (value != null) {
|
||||
settingsService.setUnitSystem(value);
|
||||
Navigator.pop(context);
|
||||
}
|
||||
},
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
ListTile(
|
||||
title: Text(context.l10n.appSettings_unitsMetric),
|
||||
leading: const Radio<UnitSystem>(value: UnitSystem.metric),
|
||||
),
|
||||
ListTile(
|
||||
title: Text(context.l10n.appSettings_unitsImperial),
|
||||
leading: const Radio<UnitSystem>(value: UnitSystem.imperial),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(context),
|
||||
child: Text(context.l10n.common_close),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildDebugCard(
|
||||
BuildContext context,
|
||||
AppSettingsService settingsService,
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import 'package:flutter/services.dart';
|
|||
import '../l10n/l10n.dart';
|
||||
import '../services/ble_debug_log_service.dart';
|
||||
import '../connector/meshcore_protocol.dart';
|
||||
import '../widgets/adaptive_app_bar_title.dart';
|
||||
|
||||
enum _BleLogView { frames, rawLogRx }
|
||||
|
||||
|
|
@ -29,7 +30,7 @@ class _BleDebugLogScreenState extends State<BleDebugLogScreen> {
|
|||
: rawEntries.isNotEmpty;
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text(context.l10n.debugLog_bleTitle),
|
||||
title: AdaptiveAppBarTitle(context.l10n.debugLog_bleTitle),
|
||||
actions: [
|
||||
IconButton(
|
||||
tooltip: context.l10n.debugLog_copyLog,
|
||||
|
|
|
|||
|
|
@ -9,11 +9,14 @@ import 'package:provider/provider.dart';
|
|||
|
||||
import '../connector/meshcore_connector.dart';
|
||||
import '../services/map_tile_cache_service.dart';
|
||||
import '../services/app_settings_service.dart';
|
||||
import '../connector/meshcore_protocol.dart';
|
||||
import '../l10n/app_localizations.dart';
|
||||
import '../l10n/l10n.dart';
|
||||
import '../models/channel_message.dart';
|
||||
import '../models/app_settings.dart';
|
||||
import '../models/contact.dart';
|
||||
import '../widgets/adaptive_app_bar_title.dart';
|
||||
|
||||
class ChannelMessagePathScreen extends StatelessWidget {
|
||||
final ChannelMessage message;
|
||||
|
|
@ -48,7 +51,7 @@ class ChannelMessagePathScreen extends StatelessWidget {
|
|||
final extraPaths = _otherPaths(primaryPath, message.pathVariants);
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text(l10n.channelPath_title),
|
||||
title: AdaptiveAppBarTitle(l10n.channelPath_title),
|
||||
actions: [
|
||||
IconButton(
|
||||
icon: const Icon(Icons.radar_outlined),
|
||||
|
|
@ -297,8 +300,12 @@ class ChannelMessagePathMapScreen extends StatefulWidget {
|
|||
|
||||
class _ChannelMessagePathMapScreenState
|
||||
extends State<ChannelMessagePathMapScreen> {
|
||||
static const double _labelZoomThreshold = 8.5;
|
||||
|
||||
Uint8List? _selectedPath;
|
||||
double _pathDistance = 0.0;
|
||||
bool _showNodeLabels = true;
|
||||
bool _didReceivePositionUpdate = false;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
|
|
@ -333,6 +340,8 @@ class _ChannelMessagePathMapScreenState
|
|||
Widget build(BuildContext context) {
|
||||
return Consumer<MeshCoreConnector>(
|
||||
builder: (context, connector, _) {
|
||||
final settings = context.watch<AppSettingsService>().settings;
|
||||
final isImperial = settings.unitSystem == UnitSystem.imperial;
|
||||
final tileCache = context.read<MapTileCacheService>();
|
||||
final primaryPath = _selectPrimaryPath(
|
||||
widget.message.pathBytes,
|
||||
|
|
@ -393,6 +402,9 @@ class _ChannelMessagePathMapScreenState
|
|||
? points.first
|
||||
: const LatLng(0, 0);
|
||||
final initialZoom = points.isNotEmpty ? 13.0 : 2.0;
|
||||
if (!_didReceivePositionUpdate) {
|
||||
_showNodeLabels = initialZoom >= _labelZoomThreshold;
|
||||
}
|
||||
final bounds = points.length > 1
|
||||
? LatLngBounds.fromPoints(points)
|
||||
: null;
|
||||
|
|
@ -402,7 +414,9 @@ class _ChannelMessagePathMapScreenState
|
|||
_pathDistance = _getPathDistance(points);
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(title: Text(context.l10n.channelPath_mapTitle)),
|
||||
appBar: AppBar(
|
||||
title: AdaptiveAppBarTitle(context.l10n.channelPath_mapTitle),
|
||||
),
|
||||
body: SafeArea(
|
||||
top: false,
|
||||
child: Stack(
|
||||
|
|
@ -424,6 +438,17 @@ class _ChannelMessagePathMapScreenState
|
|||
interactionOptions: InteractionOptions(
|
||||
flags: ~InteractiveFlag.rotate,
|
||||
),
|
||||
onPositionChanged: (camera, hasGesture) {
|
||||
final shouldShow = camera.zoom >= _labelZoomThreshold;
|
||||
if (!_didReceivePositionUpdate ||
|
||||
shouldShow != _showNodeLabels) {
|
||||
if (!mounted) return;
|
||||
setState(() {
|
||||
_didReceivePositionUpdate = true;
|
||||
_showNodeLabels = shouldShow;
|
||||
});
|
||||
}
|
||||
},
|
||||
),
|
||||
children: [
|
||||
TileLayer(
|
||||
|
|
@ -435,7 +460,12 @@ class _ChannelMessagePathMapScreenState
|
|||
),
|
||||
if (polylines.isNotEmpty)
|
||||
PolylineLayer(polylines: polylines),
|
||||
MarkerLayer(markers: _buildHopMarkers(hops)),
|
||||
MarkerLayer(
|
||||
markers: _buildHopMarkers(
|
||||
hops,
|
||||
showLabels: _showNodeLabels,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
if (observedPaths.length > 1)
|
||||
|
|
@ -458,7 +488,7 @@ class _ChannelMessagePathMapScreenState
|
|||
),
|
||||
),
|
||||
),
|
||||
_buildLegendCard(context, hops),
|
||||
_buildLegendCard(context, hops, isImperial),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
|
@ -530,45 +560,61 @@ class _ChannelMessagePathMapScreenState
|
|||
);
|
||||
}
|
||||
|
||||
List<Marker> _buildHopMarkers(List<_PathHop> hops) {
|
||||
return [
|
||||
for (final hop in hops)
|
||||
if (hop.hasLocation)
|
||||
Marker(
|
||||
point: hop.position!,
|
||||
width: 35,
|
||||
height: 35,
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.green,
|
||||
shape: BoxShape.circle,
|
||||
border: Border.all(color: Colors.white, width: 2),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.black.withValues(alpha: 0.3),
|
||||
blurRadius: 4,
|
||||
offset: const Offset(0, 2),
|
||||
),
|
||||
],
|
||||
),
|
||||
alignment: Alignment.center,
|
||||
child: Text(
|
||||
hop.index.toString(),
|
||||
style: const TextStyle(
|
||||
color: Colors.white,
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 12,
|
||||
List<Marker> _buildHopMarkers(
|
||||
List<_PathHop> hops, {
|
||||
required bool showLabels,
|
||||
}) {
|
||||
final markers = <Marker>[];
|
||||
for (final hop in hops) {
|
||||
if (!hop.hasLocation) continue;
|
||||
final point = hop.position!;
|
||||
markers.add(
|
||||
Marker(
|
||||
point: point,
|
||||
width: 35,
|
||||
height: 35,
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.green,
|
||||
shape: BoxShape.circle,
|
||||
border: Border.all(color: Colors.white, width: 2),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.black.withValues(alpha: 0.3),
|
||||
blurRadius: 4,
|
||||
offset: const Offset(0, 2),
|
||||
),
|
||||
],
|
||||
),
|
||||
alignment: Alignment.center,
|
||||
child: Text(
|
||||
hop.index.toString(),
|
||||
style: const TextStyle(
|
||||
color: Colors.white,
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 12,
|
||||
),
|
||||
),
|
||||
),
|
||||
if (context.read<MeshCoreConnector>().selfLatitude != null &&
|
||||
context.read<MeshCoreConnector>().selfLongitude != null)
|
||||
Marker(
|
||||
point: LatLng(
|
||||
context.read<MeshCoreConnector>().selfLatitude!,
|
||||
context.read<MeshCoreConnector>().selfLongitude!,
|
||||
),
|
||||
);
|
||||
if (showLabels) {
|
||||
markers.add(
|
||||
_buildNodeLabelMarker(
|
||||
point: point,
|
||||
label: hop.contact?.name ?? _formatPrefix(hop.prefix),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
final selfLat = context.read<MeshCoreConnector>().selfLatitude;
|
||||
final selfLon = context.read<MeshCoreConnector>().selfLongitude;
|
||||
if (selfLat != null && selfLon != null) {
|
||||
final selfPoint = LatLng(selfLat, selfLon);
|
||||
markers.add(
|
||||
Marker(
|
||||
point: selfPoint,
|
||||
width: 35,
|
||||
height: 35,
|
||||
child: Container(
|
||||
|
|
@ -595,10 +641,63 @@ class _ChannelMessagePathMapScreenState
|
|||
),
|
||||
),
|
||||
),
|
||||
];
|
||||
);
|
||||
if (showLabels) {
|
||||
markers.add(
|
||||
_buildNodeLabelMarker(
|
||||
point: selfPoint,
|
||||
label: context.l10n.pathTrace_you,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return markers;
|
||||
}
|
||||
|
||||
Widget _buildLegendCard(BuildContext context, List<_PathHop> hops) {
|
||||
Marker _buildNodeLabelMarker({required LatLng point, required String label}) {
|
||||
return Marker(
|
||||
point: point,
|
||||
width: 120,
|
||||
height: 24,
|
||||
alignment: Alignment.topCenter,
|
||||
child: IgnorePointer(
|
||||
child: Transform.translate(
|
||||
offset: const Offset(0, -26),
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.black54,
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
child: SizedBox(
|
||||
width: 96,
|
||||
child: FittedBox(
|
||||
fit: BoxFit.scaleDown,
|
||||
alignment: Alignment.centerLeft,
|
||||
child: Text(
|
||||
label,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: const TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 11,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildLegendCard(
|
||||
BuildContext context,
|
||||
List<_PathHop> hops,
|
||||
bool isImperial,
|
||||
) {
|
||||
final l10n = context.l10n;
|
||||
final maxHeight = MediaQuery.of(context).size.height * 0.35;
|
||||
final estimatedHeight = 72.0 + (hops.length * 56.0);
|
||||
|
|
@ -617,7 +716,7 @@ class _ChannelMessagePathMapScreenState
|
|||
Padding(
|
||||
padding: const EdgeInsets.all(12),
|
||||
child: Text(
|
||||
'${l10n.channelPath_repeaterHops} (${(_pathDistance / 1609.34).toStringAsFixed(2)} Miles / ${(_pathDistance / 1000).toStringAsFixed(2)} Km)',
|
||||
'${l10n.channelPath_repeaterHops} ${formatDistance(_pathDistance, isImperial: isImperial)}',
|
||||
style: const TextStyle(fontWeight: FontWeight.w600),
|
||||
),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import '../connector/meshcore_connector.dart';
|
|||
import '../l10n/l10n.dart';
|
||||
import '../models/community.dart';
|
||||
import '../storage/community_store.dart';
|
||||
import '../widgets/adaptive_app_bar_title.dart';
|
||||
import '../widgets/qr_scanner_widget.dart';
|
||||
|
||||
/// Screen for scanning community QR codes to join communities.
|
||||
|
|
@ -29,7 +30,7 @@ class _CommunityQrScannerScreenState extends State<CommunityQrScannerScreen> {
|
|||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text(context.l10n.community_scanQr),
|
||||
title: AdaptiveAppBarTitle(context.l10n.community_scanQr),
|
||||
centerTitle: true,
|
||||
),
|
||||
body: _isProcessing
|
||||
|
|
|
|||
|
|
@ -1170,12 +1170,17 @@ class _ContactTile extends StatelessWidget {
|
|||
backgroundColor: _getTypeColor(contact.type),
|
||||
child: _buildContactAvatar(contact),
|
||||
),
|
||||
title: Text(contact.name),
|
||||
title: Text(contact.name, maxLines: 1, overflow: TextOverflow.ellipsis),
|
||||
subtitle: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(contact.pathLabel),
|
||||
Text(contact.shortPubKeyHex, style: TextStyle(fontSize: 12)),
|
||||
Text(contact.pathLabel, maxLines: 1, overflow: TextOverflow.ellipsis),
|
||||
Text(
|
||||
contact.shortPubKeyHex,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: const TextStyle(fontSize: 12),
|
||||
),
|
||||
],
|
||||
),
|
||||
// Clamp text scaling in trailing section to prevent overflow while
|
||||
|
|
@ -1186,26 +1191,32 @@ class _ContactTile extends StatelessWidget {
|
|||
MediaQuery.textScalerOf(context).scale(1.0).clamp(1.0, 1.3),
|
||||
),
|
||||
),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.end,
|
||||
children: [
|
||||
if (unreadCount > 0) ...[
|
||||
UnreadBadge(count: unreadCount),
|
||||
const SizedBox(height: 4),
|
||||
],
|
||||
Text(
|
||||
_formatLastSeen(context, lastSeen),
|
||||
style: TextStyle(fontSize: 12, color: Colors.grey[600]),
|
||||
),
|
||||
Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
if (contact.hasLocation)
|
||||
Icon(Icons.location_on, size: 14, color: Colors.grey[400]),
|
||||
child: SizedBox(
|
||||
width: 120,
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.end,
|
||||
children: [
|
||||
if (unreadCount > 0) ...[
|
||||
UnreadBadge(count: unreadCount),
|
||||
const SizedBox(height: 4),
|
||||
],
|
||||
),
|
||||
],
|
||||
Text(
|
||||
_formatLastSeen(context, lastSeen),
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
textAlign: TextAlign.right,
|
||||
style: TextStyle(fontSize: 12, color: Colors.grey[600]),
|
||||
),
|
||||
Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
if (contact.hasLocation)
|
||||
Icon(Icons.location_on, size: 14, color: Colors.grey[400]),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
onTap: onTap,
|
||||
|
|
|
|||
1005
lib/screens/line_of_sight_map_screen.dart
Normal file
1005
lib/screens/line_of_sight_map_screen.dart
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -7,6 +7,7 @@ import '../l10n/app_localizations.dart';
|
|||
import '../l10n/l10n.dart';
|
||||
import '../services/app_settings_service.dart';
|
||||
import '../services/map_tile_cache_service.dart';
|
||||
import '../widgets/adaptive_app_bar_title.dart';
|
||||
|
||||
class MapCacheScreen extends StatefulWidget {
|
||||
const MapCacheScreen({super.key});
|
||||
|
|
@ -224,7 +225,10 @@ class _MapCacheScreenState extends State<MapCacheScreen> {
|
|||
: (_completedTiles / _estimatedTiles).clamp(0.0, 1.0).toDouble();
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(title: Text(l10n.mapCache_title), centerTitle: true),
|
||||
appBar: AppBar(
|
||||
title: AdaptiveAppBarTitle(l10n.mapCache_title),
|
||||
centerTitle: true,
|
||||
),
|
||||
body: Column(
|
||||
children: [
|
||||
Expanded(
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import 'package:provider/provider.dart';
|
|||
import '../connector/meshcore_connector.dart';
|
||||
import '../l10n/l10n.dart';
|
||||
import '../connector/meshcore_protocol.dart';
|
||||
import '../models/app_settings.dart';
|
||||
import '../models/channel.dart';
|
||||
import '../models/contact.dart';
|
||||
import '../services/app_settings_service.dart';
|
||||
|
|
@ -26,6 +27,7 @@ import '../widgets/repeater_login_dialog.dart';
|
|||
import '../widgets/room_login_dialog.dart';
|
||||
import 'repeater_hub_screen.dart';
|
||||
import 'settings_screen.dart';
|
||||
import 'line_of_sight_map_screen.dart';
|
||||
|
||||
class MapScreen extends StatefulWidget {
|
||||
final LatLng? highlightPosition;
|
||||
|
|
@ -46,6 +48,8 @@ class MapScreen extends StatefulWidget {
|
|||
}
|
||||
|
||||
class _MapScreenState extends State<MapScreen> {
|
||||
static const double _labelZoomThreshold = 8.5;
|
||||
|
||||
final MapController _mapController = MapController();
|
||||
final MapMarkerService _markerService = MapMarkerService();
|
||||
final Set<String> _hiddenMarkerIds = {};
|
||||
|
|
@ -58,6 +62,7 @@ class _MapScreenState extends State<MapScreen> {
|
|||
final List<LatLng> _points = [];
|
||||
final List<Polyline> _polylines = [];
|
||||
bool _legendExpanded = false;
|
||||
bool _showNodeLabels = true;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
|
|
@ -247,6 +252,7 @@ class _MapScreenState extends State<MapScreen> {
|
|||
// Re center map after removed markers have loaded
|
||||
if (!_hasInitializedMap && _removedMarkersLoaded) {
|
||||
_hasInitializedMap = true;
|
||||
_showNodeLabels = initialZoom >= _labelZoomThreshold;
|
||||
if (hasMapContent) {
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
if (mounted) {
|
||||
|
|
@ -272,6 +278,47 @@ class _MapScreenState extends State<MapScreen> {
|
|||
onPressed: () => _startPath(),
|
||||
tooltip: context.l10n.contacts_pathTrace,
|
||||
),
|
||||
if (!_isBuildingPathTrace)
|
||||
IconButton(
|
||||
icon: const Icon(Icons.visibility),
|
||||
onPressed: () {
|
||||
final candidates = <LineOfSightEndpoint>[];
|
||||
if (connector.selfLatitude != null &&
|
||||
connector.selfLongitude != null) {
|
||||
candidates.add(
|
||||
LineOfSightEndpoint(
|
||||
label: context.l10n.pathTrace_you,
|
||||
point: LatLng(
|
||||
connector.selfLatitude!,
|
||||
connector.selfLongitude!,
|
||||
),
|
||||
color: Colors.teal,
|
||||
icon: Icons.person_pin_circle,
|
||||
),
|
||||
);
|
||||
}
|
||||
for (final c in contactsWithLocation) {
|
||||
candidates.add(
|
||||
LineOfSightEndpoint(
|
||||
label: c.name,
|
||||
point: LatLng(c.latitude!, c.longitude!),
|
||||
color: _getNodeColor(c.type),
|
||||
icon: _getNodeIcon(c.type),
|
||||
),
|
||||
);
|
||||
}
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => LineOfSightMapScreen(
|
||||
title: context.l10n.map_losScreenTitle,
|
||||
candidates: candidates,
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
tooltip: context.l10n.map_lineOfSight,
|
||||
),
|
||||
PopupMenuButton(
|
||||
itemBuilder: (context) => [
|
||||
PopupMenuItem(
|
||||
|
|
@ -350,6 +397,14 @@ class _MapScreenState extends State<MapScreen> {
|
|||
position: latLng,
|
||||
);
|
||||
},
|
||||
onPositionChanged: (camera, hasGesture) {
|
||||
final shouldShow = camera.zoom >= _labelZoomThreshold;
|
||||
if (shouldShow != _showNodeLabels && mounted) {
|
||||
setState(() {
|
||||
_showNodeLabels = shouldShow;
|
||||
});
|
||||
}
|
||||
},
|
||||
),
|
||||
children: [
|
||||
TileLayer(
|
||||
|
|
@ -374,7 +429,11 @@ class _MapScreenState extends State<MapScreen> {
|
|||
size: 34,
|
||||
),
|
||||
),
|
||||
..._buildMarkers(contactsWithLocation, settings),
|
||||
..._buildMarkers(
|
||||
contactsWithLocation,
|
||||
settings,
|
||||
showLabels: _showNodeLabels,
|
||||
),
|
||||
...sharedMarkers.map(_buildSharedMarker),
|
||||
if (connector.selfLatitude != null &&
|
||||
connector.selfLongitude != null)
|
||||
|
|
@ -413,6 +472,16 @@ class _MapScreenState extends State<MapScreen> {
|
|||
),
|
||||
),
|
||||
),
|
||||
if (_showNodeLabels &&
|
||||
connector.selfLatitude != null &&
|
||||
connector.selfLongitude != null)
|
||||
_buildNodeLabelMarker(
|
||||
point: LatLng(
|
||||
connector.selfLatitude!,
|
||||
connector.selfLongitude!,
|
||||
),
|
||||
label: connector.deviceDisplayName,
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
|
|
@ -444,7 +513,11 @@ class _MapScreenState extends State<MapScreen> {
|
|||
);
|
||||
}
|
||||
|
||||
List<Marker> _buildMarkers(List<Contact> contacts, settings) {
|
||||
List<Marker> _buildMarkers(
|
||||
List<Contact> contacts,
|
||||
settings, {
|
||||
required bool showLabels,
|
||||
}) {
|
||||
final markers = <Marker>[];
|
||||
|
||||
for (final contact in contacts) {
|
||||
|
|
@ -499,11 +572,57 @@ class _MapScreenState extends State<MapScreen> {
|
|||
);
|
||||
|
||||
markers.add(marker);
|
||||
if (showLabels) {
|
||||
markers.add(
|
||||
_buildNodeLabelMarker(
|
||||
point: LatLng(contact.latitude!, contact.longitude!),
|
||||
label: contact.name,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return markers;
|
||||
}
|
||||
|
||||
Marker _buildNodeLabelMarker({required LatLng point, required String label}) {
|
||||
return Marker(
|
||||
point: point,
|
||||
width: 120,
|
||||
height: 24,
|
||||
alignment: Alignment.topCenter,
|
||||
child: IgnorePointer(
|
||||
child: Transform.translate(
|
||||
offset: const Offset(0, -26),
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.black54,
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
child: SizedBox(
|
||||
width: 96,
|
||||
child: FittedBox(
|
||||
fit: BoxFit.scaleDown,
|
||||
alignment: Alignment.centerLeft,
|
||||
child: Text(
|
||||
label,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: const TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 11,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Color _getNodeColor(int type) {
|
||||
switch (type) {
|
||||
case advTypeChat:
|
||||
|
|
@ -1519,6 +1638,9 @@ class _MapScreenState extends State<MapScreen> {
|
|||
|
||||
Widget _buildPathTraceOverlay() {
|
||||
final l10n = context.l10n;
|
||||
final isImperial =
|
||||
context.read<AppSettingsService>().settings.unitSystem ==
|
||||
UnitSystem.imperial;
|
||||
return Positioned(
|
||||
top: 16,
|
||||
left: 16,
|
||||
|
|
@ -1539,7 +1661,7 @@ class _MapScreenState extends State<MapScreen> {
|
|||
const SizedBox(height: 6),
|
||||
if (_pathTrace.isNotEmpty)
|
||||
Text(
|
||||
"${l10n.path_currentPathLabel} ${formatDistance(getPathDistanceMeters(_points))}",
|
||||
"${l10n.path_currentPathLabel} ${formatDistance(getPathDistanceMeters(_points), isImperial: isImperial)}",
|
||||
style: TextStyle(fontSize: 12, color: Colors.grey[700]),
|
||||
),
|
||||
SelectableText(
|
||||
|
|
@ -1549,8 +1671,10 @@ class _MapScreenState extends State<MapScreen> {
|
|||
style: TextStyle(fontSize: 18),
|
||||
),
|
||||
const SizedBox(height: 6),
|
||||
Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
Wrap(
|
||||
alignment: WrapAlignment.center,
|
||||
spacing: 8,
|
||||
runSpacing: 8,
|
||||
children: [
|
||||
if (_pathTrace.isNotEmpty)
|
||||
ElevatedButton(
|
||||
|
|
|
|||
|
|
@ -8,7 +8,9 @@ import 'package:latlong2/latlong.dart';
|
|||
import 'package:meshcore_open/connector/meshcore_connector.dart';
|
||||
import 'package:meshcore_open/connector/meshcore_protocol.dart';
|
||||
import 'package:meshcore_open/l10n/l10n.dart';
|
||||
import 'package:meshcore_open/models/app_settings.dart';
|
||||
import 'package:meshcore_open/models/contact.dart';
|
||||
import 'package:meshcore_open/services/app_settings_service.dart';
|
||||
import 'package:meshcore_open/services/map_tile_cache_service.dart';
|
||||
import 'package:meshcore_open/utils/app_logger.dart';
|
||||
import 'package:meshcore_open/widgets/snr_indicator.dart';
|
||||
|
|
@ -27,8 +29,11 @@ double getPathDistanceMeters(List<LatLng> points) {
|
|||
return distanceMeters;
|
||||
}
|
||||
|
||||
String formatDistance(double distanceMeters) {
|
||||
return '(${(distanceMeters / 1609.34).toStringAsFixed(2)} Miles / ${(distanceMeters / 1000).toStringAsFixed(2)} Km)';
|
||||
String formatDistance(double distanceMeters, {required bool isImperial}) {
|
||||
if (isImperial) {
|
||||
return '(${(distanceMeters / 1609.34).toStringAsFixed(2)} mi)';
|
||||
}
|
||||
return '(${(distanceMeters / 1000).toStringAsFixed(2)} km)';
|
||||
}
|
||||
|
||||
class PathTraceData {
|
||||
|
|
@ -64,6 +69,8 @@ class PathTraceMapScreen extends StatefulWidget {
|
|||
}
|
||||
|
||||
class _PathTraceMapScreenState extends State<PathTraceMapScreen> {
|
||||
static const double _labelZoomThreshold = 8.5;
|
||||
|
||||
StreamSubscription<Uint8List>? _frameSubscription;
|
||||
Timer? _timeoutTimer;
|
||||
|
||||
|
|
@ -78,6 +85,7 @@ class _PathTraceMapScreenState extends State<PathTraceMapScreen> {
|
|||
LatLngBounds? _bounds;
|
||||
ValueKey<String> _mapKey = const ValueKey('initial');
|
||||
double _pathDistanceMeters = 0.0;
|
||||
bool _showNodeLabels = true;
|
||||
|
||||
String _formatPathPrefixes(Uint8List pathBytes) {
|
||||
return pathBytes
|
||||
|
|
@ -291,6 +299,8 @@ class _PathTraceMapScreenState extends State<PathTraceMapScreen> {
|
|||
Widget build(BuildContext context) {
|
||||
return Consumer<MeshCoreConnector>(
|
||||
builder: (context, connector, _) {
|
||||
final settings = context.watch<AppSettingsService>().settings;
|
||||
final isImperial = settings.unitSystem == UnitSystem.imperial;
|
||||
final tileCache = context.read<MapTileCacheService>();
|
||||
|
||||
return Scaffold(
|
||||
|
|
@ -355,7 +365,8 @@ class _PathTraceMapScreenState extends State<PathTraceMapScreen> {
|
|||
),
|
||||
),
|
||||
),
|
||||
if (_hasData) _buildLegendCard(context, _traceData!),
|
||||
if (_hasData)
|
||||
_buildLegendCard(context, _traceData!, isImperial),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
|
@ -364,55 +375,61 @@ class _PathTraceMapScreenState extends State<PathTraceMapScreen> {
|
|||
);
|
||||
}
|
||||
|
||||
List<Marker> _buildHopMarkers(List<int> pathData) {
|
||||
return [
|
||||
for (final hop in pathData)
|
||||
if (_traceData!.pathContacts[hop] != null &&
|
||||
_traceData!.pathContacts[hop]!.hasLocation)
|
||||
Marker(
|
||||
point: LatLng(
|
||||
_traceData!.pathContacts[hop]!.latitude!,
|
||||
_traceData!.pathContacts[hop]!.longitude!,
|
||||
),
|
||||
width: 35,
|
||||
height: 35,
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(4),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.green,
|
||||
shape: BoxShape.circle,
|
||||
border: Border.all(color: Colors.white, width: 2),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.black.withValues(alpha: 0.3),
|
||||
blurRadius: 4,
|
||||
offset: const Offset(0, 2),
|
||||
),
|
||||
],
|
||||
),
|
||||
alignment: Alignment.center,
|
||||
child: Text(
|
||||
_traceData!.pathContacts[hop]!.publicKey
|
||||
.sublist(0, 1)
|
||||
.map(
|
||||
(b) => b.toRadixString(16).padLeft(2, '0').toUpperCase(),
|
||||
)
|
||||
.join(),
|
||||
style: const TextStyle(
|
||||
color: Colors.white,
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 12,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
if (context.read<MeshCoreConnector>().selfLatitude != null &&
|
||||
context.read<MeshCoreConnector>().selfLongitude != null)
|
||||
List<Marker> _buildHopMarkers(
|
||||
List<int> pathData, {
|
||||
required bool showLabels,
|
||||
}) {
|
||||
final markers = <Marker>[];
|
||||
for (final hop in pathData) {
|
||||
final contact = _traceData!.pathContacts[hop];
|
||||
if (contact == null || !contact.hasLocation) continue;
|
||||
final point = LatLng(contact.latitude!, contact.longitude!);
|
||||
markers.add(
|
||||
Marker(
|
||||
point: LatLng(
|
||||
context.read<MeshCoreConnector>().selfLatitude!,
|
||||
context.read<MeshCoreConnector>().selfLongitude!,
|
||||
point: point,
|
||||
width: 35,
|
||||
height: 35,
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(4),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.green,
|
||||
shape: BoxShape.circle,
|
||||
border: Border.all(color: Colors.white, width: 2),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.black.withValues(alpha: 0.3),
|
||||
blurRadius: 4,
|
||||
offset: const Offset(0, 2),
|
||||
),
|
||||
],
|
||||
),
|
||||
alignment: Alignment.center,
|
||||
child: Text(
|
||||
contact.publicKey
|
||||
.sublist(0, 1)
|
||||
.map((b) => b.toRadixString(16).padLeft(2, '0').toUpperCase())
|
||||
.join(),
|
||||
style: const TextStyle(
|
||||
color: Colors.white,
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 12,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
if (showLabels) {
|
||||
markers.add(_buildNodeLabelMarker(point: point, label: contact.name));
|
||||
}
|
||||
}
|
||||
|
||||
final selfLat = context.read<MeshCoreConnector>().selfLatitude;
|
||||
final selfLon = context.read<MeshCoreConnector>().selfLongitude;
|
||||
if (selfLat != null && selfLon != null) {
|
||||
final selfPoint = LatLng(selfLat, selfLon);
|
||||
markers.add(
|
||||
Marker(
|
||||
point: selfPoint,
|
||||
width: 35,
|
||||
height: 35,
|
||||
child: Container(
|
||||
|
|
@ -440,7 +457,56 @@ class _PathTraceMapScreenState extends State<PathTraceMapScreen> {
|
|||
),
|
||||
),
|
||||
),
|
||||
];
|
||||
);
|
||||
if (showLabels) {
|
||||
markers.add(
|
||||
_buildNodeLabelMarker(
|
||||
point: selfPoint,
|
||||
label: context.l10n.pathTrace_you,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return markers;
|
||||
}
|
||||
|
||||
Marker _buildNodeLabelMarker({required LatLng point, required String label}) {
|
||||
return Marker(
|
||||
point: point,
|
||||
width: 120,
|
||||
height: 24,
|
||||
alignment: Alignment.topCenter,
|
||||
child: IgnorePointer(
|
||||
child: Transform.translate(
|
||||
offset: const Offset(0, -26),
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.black54,
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
child: SizedBox(
|
||||
width: 96,
|
||||
child: FittedBox(
|
||||
fit: BoxFit.scaleDown,
|
||||
alignment: Alignment.centerLeft,
|
||||
child: Text(
|
||||
label,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: const TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 11,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
String formatDirectionText(PathTraceData pathTraceData, int index) {
|
||||
|
|
@ -520,6 +586,14 @@ class _PathTraceMapScreenState extends State<PathTraceMapScreen> {
|
|||
),
|
||||
minZoom: 2.0,
|
||||
maxZoom: 18.0,
|
||||
onPositionChanged: (camera, hasGesture) {
|
||||
final shouldShow = camera.zoom >= _labelZoomThreshold;
|
||||
if (shouldShow != _showNodeLabels && mounted) {
|
||||
setState(() {
|
||||
_showNodeLabels = shouldShow;
|
||||
});
|
||||
}
|
||||
},
|
||||
),
|
||||
children: [
|
||||
TileLayer(
|
||||
|
|
@ -530,12 +604,21 @@ class _PathTraceMapScreenState extends State<PathTraceMapScreen> {
|
|||
),
|
||||
if (_polylines.isNotEmpty) PolylineLayer(polylines: _polylines),
|
||||
if (_traceData!.pathData.isNotEmpty)
|
||||
MarkerLayer(markers: _buildHopMarkers(_traceData!.pathData)),
|
||||
MarkerLayer(
|
||||
markers: _buildHopMarkers(
|
||||
_traceData!.pathData,
|
||||
showLabels: _showNodeLabels,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildLegendCard(BuildContext context, PathTraceData pathTraceData) {
|
||||
Widget _buildLegendCard(
|
||||
BuildContext context,
|
||||
PathTraceData pathTraceData,
|
||||
bool isImperial,
|
||||
) {
|
||||
final l10n = context.l10n;
|
||||
final maxHeight = MediaQuery.of(context).size.height * 0.35;
|
||||
final estimatedHeight = 72.0 + (pathTraceData.pathData.length * 56.0);
|
||||
|
|
@ -554,7 +637,7 @@ class _PathTraceMapScreenState extends State<PathTraceMapScreen> {
|
|||
Padding(
|
||||
padding: const EdgeInsets.all(12),
|
||||
child: Text(
|
||||
'${l10n.channelPath_repeaterHops} ${formatDistance(_pathDistanceMeters)}',
|
||||
'${l10n.channelPath_repeaterHops} ${formatDistance(_pathDistanceMeters, isImperial: isImperial)}',
|
||||
style: const TextStyle(fontWeight: FontWeight.w600),
|
||||
),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import 'package:provider/provider.dart';
|
|||
|
||||
import '../connector/meshcore_connector.dart';
|
||||
import '../l10n/l10n.dart';
|
||||
import '../widgets/adaptive_app_bar_title.dart';
|
||||
import '../widgets/device_tile.dart';
|
||||
import 'contacts_screen.dart';
|
||||
|
||||
|
|
@ -70,7 +71,7 @@ class _ScannerScreenState extends State<ScannerScreen> {
|
|||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text(context.l10n.scanner_title),
|
||||
title: AdaptiveAppBarTitle(context.l10n.scanner_title),
|
||||
centerTitle: true,
|
||||
automaticallyImplyLeading: false,
|
||||
),
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import '../connector/meshcore_connector.dart';
|
|||
import '../connector/meshcore_protocol.dart';
|
||||
import '../l10n/l10n.dart';
|
||||
import '../models/radio_settings.dart';
|
||||
import '../widgets/adaptive_app_bar_title.dart';
|
||||
import 'app_settings_screen.dart';
|
||||
import 'app_debug_log_screen.dart';
|
||||
import 'ble_debug_log_screen.dart';
|
||||
|
|
@ -41,7 +42,10 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
|||
Widget build(BuildContext context) {
|
||||
final l10n = context.l10n;
|
||||
return Scaffold(
|
||||
appBar: AppBar(title: Text(l10n.settings_title), centerTitle: true),
|
||||
appBar: AppBar(
|
||||
title: AdaptiveAppBarTitle(l10n.settings_title),
|
||||
centerTitle: true,
|
||||
),
|
||||
body: SafeArea(
|
||||
top: false,
|
||||
child: Consumer<MeshCoreConnector>(
|
||||
|
|
|
|||
|
|
@ -5,8 +5,10 @@ import 'package:provider/provider.dart';
|
|||
import '../l10n/l10n.dart';
|
||||
import '../models/contact.dart';
|
||||
import '../models/path_selection.dart';
|
||||
import '../models/app_settings.dart';
|
||||
import '../connector/meshcore_connector.dart';
|
||||
import '../connector/meshcore_protocol.dart';
|
||||
import '../services/app_settings_service.dart';
|
||||
import '../services/repeater_command_service.dart';
|
||||
import '../widgets/path_management_dialog.dart';
|
||||
import '../helpers/cayenne_lpp.dart';
|
||||
|
|
@ -181,6 +183,8 @@ class _TelemetryScreenState extends State<TelemetryScreen> {
|
|||
Widget build(BuildContext context) {
|
||||
final l10n = context.l10n;
|
||||
final connector = context.watch<MeshCoreConnector>();
|
||||
final settings = context.watch<AppSettingsService>().settings;
|
||||
final isImperialUnits = settings.unitSystem == UnitSystem.imperial;
|
||||
final repeater = _resolveRepeater(connector);
|
||||
final isFloodMode = repeater.pathOverride == -1;
|
||||
|
||||
|
|
@ -307,6 +311,7 @@ class _TelemetryScreenState extends State<TelemetryScreen> {
|
|||
entry['values'],
|
||||
l10n.telemetry_channelTitle(entry['channel']),
|
||||
entry['channel'],
|
||||
isImperialUnits,
|
||||
),
|
||||
],
|
||||
),
|
||||
|
|
@ -319,6 +324,7 @@ class _TelemetryScreenState extends State<TelemetryScreen> {
|
|||
Map<String, dynamic> channelData,
|
||||
String title,
|
||||
int channel,
|
||||
bool isImperialUnits,
|
||||
) {
|
||||
final l10n = context.l10n;
|
||||
return Card(
|
||||
|
|
@ -358,12 +364,12 @@ class _TelemetryScreenState extends State<TelemetryScreen> {
|
|||
else if (entry.key == 'temperature' && channel == 1)
|
||||
_buildInfoRow(
|
||||
l10n.telemetry_mcuTemperatureLabel,
|
||||
_temperatureText(entry.value),
|
||||
_temperatureText(entry.value, isImperialUnits),
|
||||
)
|
||||
else if (entry.key == 'temperature')
|
||||
_buildInfoRow(
|
||||
l10n.telemetry_temperatureLabel,
|
||||
_temperatureText(entry.value),
|
||||
_temperatureText(entry.value, isImperialUnits),
|
||||
)
|
||||
else if (entry.key == 'current' && channel == 1)
|
||||
_buildInfoRow(
|
||||
|
|
@ -421,13 +427,13 @@ class _TelemetryScreenState extends State<TelemetryScreen> {
|
|||
return (((millivolts - minMv) * 100) / (maxMv - minMv)).round();
|
||||
}
|
||||
|
||||
String _temperatureText(double? tempC) {
|
||||
String _temperatureText(double? tempC, bool isImperialUnits) {
|
||||
final l10n = context.l10n;
|
||||
if (tempC == null) return l10n.common_notAvailable;
|
||||
final tempF = (tempC * 9 / 5) + 32;
|
||||
return l10n.telemetry_temperatureValue(
|
||||
tempC.toStringAsFixed(1),
|
||||
tempF.toStringAsFixed(1),
|
||||
);
|
||||
if (isImperialUnits) {
|
||||
return '${tempF.toStringAsFixed(1)}°F';
|
||||
}
|
||||
return '${tempC.toStringAsFixed(1)}°C';
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -132,4 +132,14 @@ class AppSettingsService extends ChangeNotifier {
|
|||
_settings.copyWith(batteryChemistryByDeviceId: updated),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> setUnitSystem(UnitSystem value) async {
|
||||
await updateSettings(_settings.copyWith(unitSystem: value));
|
||||
}
|
||||
|
||||
Future<void> setLosUnitSystem(String value) async {
|
||||
await setUnitSystem(
|
||||
value == 'imperial' ? UnitSystem.imperial : UnitSystem.metric,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/scheduler.dart';
|
||||
import '../connector/meshcore_protocol.dart';
|
||||
|
||||
class BleDebugLogEntry {
|
||||
|
|
@ -44,6 +45,7 @@ class BleDebugLogService extends ChangeNotifier {
|
|||
static const int maxEntries = 500;
|
||||
final List<BleDebugLogEntry> _entries = [];
|
||||
final List<BleRawLogRxEntry> _rawLogRxEntries = [];
|
||||
bool _notifyScheduled = false;
|
||||
|
||||
List<BleDebugLogEntry> get entries => List.unmodifiable(_entries);
|
||||
List<BleRawLogRxEntry> get rawLogRxEntries =>
|
||||
|
|
@ -78,13 +80,31 @@ class BleDebugLogService extends ChangeNotifier {
|
|||
}
|
||||
}
|
||||
|
||||
notifyListeners();
|
||||
_notifyListenersSafely();
|
||||
}
|
||||
|
||||
void clear() {
|
||||
_entries.clear();
|
||||
_rawLogRxEntries.clear();
|
||||
notifyListeners();
|
||||
_notifyListenersSafely();
|
||||
}
|
||||
|
||||
void _notifyListenersSafely() {
|
||||
final phase = SchedulerBinding.instance.schedulerPhase;
|
||||
final canNotifyNow =
|
||||
phase == SchedulerPhase.idle ||
|
||||
phase == SchedulerPhase.postFrameCallbacks;
|
||||
if (canNotifyNow) {
|
||||
notifyListeners();
|
||||
return;
|
||||
}
|
||||
|
||||
if (_notifyScheduled) return;
|
||||
_notifyScheduled = true;
|
||||
SchedulerBinding.instance.addPostFrameCallback((_) {
|
||||
_notifyScheduled = false;
|
||||
notifyListeners();
|
||||
});
|
||||
}
|
||||
|
||||
String _describeFrame(
|
||||
|
|
|
|||
406
lib/services/line_of_sight_service.dart
Normal file
406
lib/services/line_of_sight_service.dart
Normal file
|
|
@ -0,0 +1,406 @@
|
|||
import 'dart:convert';
|
||||
import 'dart:async';
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'package:latlong2/latlong.dart';
|
||||
|
||||
typedef ElevationDataSource =
|
||||
Future<List<double?>> Function(List<LatLng> points);
|
||||
|
||||
class LineOfSightSample {
|
||||
final double distanceMeters;
|
||||
final double terrainMeters;
|
||||
final double lineHeightMeters;
|
||||
final double clearanceMeters;
|
||||
|
||||
const LineOfSightSample({
|
||||
required this.distanceMeters,
|
||||
required this.terrainMeters,
|
||||
required this.lineHeightMeters,
|
||||
required this.clearanceMeters,
|
||||
});
|
||||
}
|
||||
|
||||
class LineOfSightResult {
|
||||
final bool hasData;
|
||||
final bool isClear;
|
||||
final double totalDistanceMeters;
|
||||
final double maxObstructionMeters;
|
||||
final double? firstObstructionDistanceMeters;
|
||||
final List<LineOfSightSample> samples;
|
||||
final String? errorMessage;
|
||||
|
||||
const LineOfSightResult({
|
||||
required this.hasData,
|
||||
required this.isClear,
|
||||
required this.totalDistanceMeters,
|
||||
required this.maxObstructionMeters,
|
||||
required this.firstObstructionDistanceMeters,
|
||||
required this.samples,
|
||||
this.errorMessage,
|
||||
});
|
||||
|
||||
const LineOfSightResult.error({
|
||||
required this.totalDistanceMeters,
|
||||
required this.errorMessage,
|
||||
}) : hasData = false,
|
||||
isClear = false,
|
||||
maxObstructionMeters = 0,
|
||||
firstObstructionDistanceMeters = null,
|
||||
samples = const [];
|
||||
}
|
||||
|
||||
class LineOfSightPathSegment {
|
||||
final int index;
|
||||
final LatLng start;
|
||||
final LatLng end;
|
||||
final LineOfSightResult result;
|
||||
|
||||
const LineOfSightPathSegment({
|
||||
required this.index,
|
||||
required this.start,
|
||||
required this.end,
|
||||
required this.result,
|
||||
});
|
||||
}
|
||||
|
||||
class LineOfSightPathResult {
|
||||
final List<LineOfSightPathSegment> segments;
|
||||
final int clearSegments;
|
||||
final int blockedSegments;
|
||||
final int unknownSegments;
|
||||
|
||||
const LineOfSightPathResult({
|
||||
required this.segments,
|
||||
required this.clearSegments,
|
||||
required this.blockedSegments,
|
||||
required this.unknownSegments,
|
||||
});
|
||||
}
|
||||
|
||||
class LineOfSightService {
|
||||
static const String errorElevationUnavailable =
|
||||
'los_error_elevation_unavailable';
|
||||
static const String errorInvalidInput = 'los_error_invalid_input';
|
||||
|
||||
static const double _earthRadiusMeters = 6371000.0;
|
||||
static const Distance _distance = Distance();
|
||||
static const Duration _cacheTtl = Duration(hours: 24);
|
||||
static const int _maxFetchAttempts = 4; // initial try + 3 retries
|
||||
static const Duration _initialBackoff = Duration(milliseconds: 300);
|
||||
|
||||
final http.Client _httpClient;
|
||||
final bool _ownsHttpClient;
|
||||
final ElevationDataSource? _elevationDataSource;
|
||||
final Map<String, _CachedElevation> _elevationCache = {};
|
||||
|
||||
LineOfSightService({
|
||||
http.Client? httpClient,
|
||||
ElevationDataSource? elevationDataSource,
|
||||
}) : _httpClient = httpClient ?? http.Client(),
|
||||
_ownsHttpClient = httpClient == null,
|
||||
_elevationDataSource = elevationDataSource;
|
||||
|
||||
Future<LineOfSightPathResult> analyzePath(
|
||||
List<LatLng> points, {
|
||||
double startAntennaHeightMeters = 1.5,
|
||||
double endAntennaHeightMeters = 1.5,
|
||||
double kFactor = 4.0 / 3.0,
|
||||
double obstructionToleranceMeters = 0.0,
|
||||
}) async {
|
||||
if (points.length < 2) {
|
||||
return const LineOfSightPathResult(
|
||||
segments: [],
|
||||
clearSegments: 0,
|
||||
blockedSegments: 0,
|
||||
unknownSegments: 0,
|
||||
);
|
||||
}
|
||||
|
||||
final segments = <LineOfSightPathSegment>[];
|
||||
var clearSegments = 0;
|
||||
var blockedSegments = 0;
|
||||
var unknownSegments = 0;
|
||||
|
||||
for (int i = 0; i < points.length - 1; i++) {
|
||||
final result = await analyzeLink(
|
||||
points[i],
|
||||
points[i + 1],
|
||||
startAntennaHeightMeters: startAntennaHeightMeters,
|
||||
endAntennaHeightMeters: endAntennaHeightMeters,
|
||||
kFactor: kFactor,
|
||||
obstructionToleranceMeters: obstructionToleranceMeters,
|
||||
);
|
||||
segments.add(
|
||||
LineOfSightPathSegment(
|
||||
index: i,
|
||||
start: points[i],
|
||||
end: points[i + 1],
|
||||
result: result,
|
||||
),
|
||||
);
|
||||
|
||||
if (!result.hasData) {
|
||||
unknownSegments++;
|
||||
} else if (result.isClear) {
|
||||
clearSegments++;
|
||||
} else {
|
||||
blockedSegments++;
|
||||
}
|
||||
}
|
||||
|
||||
return LineOfSightPathResult(
|
||||
segments: segments,
|
||||
clearSegments: clearSegments,
|
||||
blockedSegments: blockedSegments,
|
||||
unknownSegments: unknownSegments,
|
||||
);
|
||||
}
|
||||
|
||||
Future<LineOfSightResult> analyzeLink(
|
||||
LatLng start,
|
||||
LatLng end, {
|
||||
double startAntennaHeightMeters = 1.5,
|
||||
double endAntennaHeightMeters = 1.5,
|
||||
double kFactor = 4.0 / 3.0,
|
||||
double obstructionToleranceMeters = 0.0,
|
||||
}) async {
|
||||
final totalDistanceMeters = _distance.as(LengthUnit.Meter, start, end);
|
||||
if (totalDistanceMeters <= 1) {
|
||||
return LineOfSightResult(
|
||||
hasData: true,
|
||||
isClear: true,
|
||||
totalDistanceMeters: totalDistanceMeters,
|
||||
maxObstructionMeters: 0,
|
||||
firstObstructionDistanceMeters: null,
|
||||
samples: const [],
|
||||
);
|
||||
}
|
||||
|
||||
final samplePoints = _buildSamplePoints(start, end, totalDistanceMeters);
|
||||
final elevations = await _getElevations(samplePoints);
|
||||
|
||||
if (elevations.any((e) => e == null)) {
|
||||
return LineOfSightResult.error(
|
||||
totalDistanceMeters: totalDistanceMeters,
|
||||
errorMessage: errorElevationUnavailable,
|
||||
);
|
||||
}
|
||||
|
||||
return computeFromElevations(
|
||||
points: samplePoints,
|
||||
elevations: elevations.cast<double>(),
|
||||
startAntennaHeightMeters: startAntennaHeightMeters,
|
||||
endAntennaHeightMeters: endAntennaHeightMeters,
|
||||
kFactor: kFactor,
|
||||
obstructionToleranceMeters: obstructionToleranceMeters,
|
||||
);
|
||||
}
|
||||
|
||||
static LineOfSightResult computeFromElevations({
|
||||
required List<LatLng> points,
|
||||
required List<double> elevations,
|
||||
double startAntennaHeightMeters = 1.5,
|
||||
double endAntennaHeightMeters = 1.5,
|
||||
double kFactor = 4.0 / 3.0,
|
||||
double obstructionToleranceMeters = 0.0,
|
||||
}) {
|
||||
if (points.length < 2 || elevations.length != points.length) {
|
||||
return const LineOfSightResult.error(
|
||||
totalDistanceMeters: 0,
|
||||
errorMessage: errorInvalidInput,
|
||||
);
|
||||
}
|
||||
|
||||
final totalDistanceMeters = _distance.as(
|
||||
LengthUnit.Meter,
|
||||
points.first,
|
||||
points.last,
|
||||
);
|
||||
final effectiveEarthRadius = _earthRadiusMeters * kFactor;
|
||||
final startLineHeight = elevations.first + startAntennaHeightMeters;
|
||||
final endLineHeight = elevations.last + endAntennaHeightMeters;
|
||||
|
||||
var maxObstructionMeters = 0.0;
|
||||
double? firstObstructionDistanceMeters;
|
||||
final samples = <LineOfSightSample>[];
|
||||
var isClear = true;
|
||||
|
||||
for (int i = 0; i < points.length; i++) {
|
||||
final fraction = points.length == 1 ? 0.0 : i / (points.length - 1);
|
||||
final distanceFromStart = totalDistanceMeters * fraction;
|
||||
final lineHeight =
|
||||
startLineHeight + (endLineHeight - startLineHeight) * fraction;
|
||||
|
||||
final earthBulge =
|
||||
(distanceFromStart * (totalDistanceMeters - distanceFromStart)) /
|
||||
(2 * effectiveEarthRadius);
|
||||
final terrainHeight = elevations[i] + earthBulge;
|
||||
final clearance = lineHeight - terrainHeight;
|
||||
|
||||
if (clearance < -obstructionToleranceMeters) {
|
||||
isClear = false;
|
||||
final obstruction = -clearance;
|
||||
if (obstruction > maxObstructionMeters) {
|
||||
maxObstructionMeters = obstruction;
|
||||
}
|
||||
firstObstructionDistanceMeters ??= distanceFromStart;
|
||||
}
|
||||
|
||||
samples.add(
|
||||
LineOfSightSample(
|
||||
distanceMeters: distanceFromStart,
|
||||
terrainMeters: terrainHeight,
|
||||
lineHeightMeters: lineHeight,
|
||||
clearanceMeters: clearance,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return LineOfSightResult(
|
||||
hasData: true,
|
||||
isClear: isClear,
|
||||
totalDistanceMeters: totalDistanceMeters,
|
||||
maxObstructionMeters: maxObstructionMeters,
|
||||
firstObstructionDistanceMeters: firstObstructionDistanceMeters,
|
||||
samples: samples,
|
||||
);
|
||||
}
|
||||
|
||||
List<LatLng> _buildSamplePoints(
|
||||
LatLng start,
|
||||
LatLng end,
|
||||
double distanceMeters,
|
||||
) {
|
||||
final sampleCount = distanceMeters < 2000
|
||||
? 21
|
||||
: distanceMeters < 10000
|
||||
? 41
|
||||
: 81;
|
||||
|
||||
final points = <LatLng>[];
|
||||
for (int i = 0; i < sampleCount; i++) {
|
||||
final t = i / (sampleCount - 1);
|
||||
points.add(
|
||||
LatLng(
|
||||
start.latitude + (end.latitude - start.latitude) * t,
|
||||
start.longitude + (end.longitude - start.longitude) * t,
|
||||
),
|
||||
);
|
||||
}
|
||||
return points;
|
||||
}
|
||||
|
||||
Future<List<double?>> _getElevations(List<LatLng> points) async {
|
||||
final dataSource = _elevationDataSource;
|
||||
if (dataSource != null) {
|
||||
return dataSource(points);
|
||||
}
|
||||
|
||||
final uncached = <int, LatLng>{};
|
||||
final values = List<double?>.filled(points.length, null);
|
||||
for (int i = 0; i < points.length; i++) {
|
||||
final key = _cacheKey(points[i]);
|
||||
final cached = _readCachedValue(key);
|
||||
if (cached != null) {
|
||||
values[i] = cached;
|
||||
} else {
|
||||
uncached[i] = points[i];
|
||||
}
|
||||
}
|
||||
|
||||
if (uncached.isEmpty) return values;
|
||||
|
||||
final latCsv = uncached.values
|
||||
.map((p) => p.latitude.toStringAsFixed(6))
|
||||
.join(',');
|
||||
final lonCsv = uncached.values
|
||||
.map((p) => p.longitude.toStringAsFixed(6))
|
||||
.join(',');
|
||||
|
||||
final uri = Uri.parse(
|
||||
'https://api.open-meteo.com/v1/elevation?latitude=$latCsv&longitude=$lonCsv',
|
||||
);
|
||||
|
||||
final response = await _getWithBackoff(uri);
|
||||
if (response.statusCode != 200) {
|
||||
return values;
|
||||
}
|
||||
|
||||
final decoded = jsonDecode(response.body);
|
||||
if (decoded is! Map<String, dynamic>) {
|
||||
return values;
|
||||
}
|
||||
final elevations = decoded['elevation'];
|
||||
if (elevations is! List) {
|
||||
return values;
|
||||
}
|
||||
|
||||
final indices = uncached.keys.toList();
|
||||
for (int i = 0; i < min(indices.length, elevations.length); i++) {
|
||||
final value = elevations[i];
|
||||
if (value is! num) continue;
|
||||
final index = indices[i];
|
||||
final elevation = value.toDouble();
|
||||
values[index] = elevation;
|
||||
_elevationCache[_cacheKey(points[index])] = _CachedElevation(
|
||||
value: elevation,
|
||||
expiresAt: DateTime.now().add(_cacheTtl),
|
||||
);
|
||||
}
|
||||
return values;
|
||||
}
|
||||
|
||||
Future<http.Response> _getWithBackoff(Uri uri) async {
|
||||
var attempt = 0;
|
||||
Duration backoff = _initialBackoff;
|
||||
|
||||
while (true) {
|
||||
attempt++;
|
||||
try {
|
||||
final response = await _httpClient.get(uri);
|
||||
if (!_shouldRetryStatus(response.statusCode) ||
|
||||
attempt >= _maxFetchAttempts) {
|
||||
return response;
|
||||
}
|
||||
} catch (_) {
|
||||
if (attempt >= _maxFetchAttempts) rethrow;
|
||||
}
|
||||
|
||||
await Future.delayed(backoff);
|
||||
backoff *= 2;
|
||||
}
|
||||
}
|
||||
|
||||
bool _shouldRetryStatus(int statusCode) {
|
||||
return statusCode == 429 || statusCode >= 500;
|
||||
}
|
||||
|
||||
double? _readCachedValue(String key) {
|
||||
final cached = _elevationCache[key];
|
||||
if (cached == null) return null;
|
||||
if (DateTime.now().isAfter(cached.expiresAt)) {
|
||||
_elevationCache.remove(key);
|
||||
return null;
|
||||
}
|
||||
return cached.value;
|
||||
}
|
||||
|
||||
String _cacheKey(LatLng point) {
|
||||
return '${point.latitude.toStringAsFixed(5)},${point.longitude.toStringAsFixed(5)}';
|
||||
}
|
||||
|
||||
void dispose() {
|
||||
if (_ownsHttpClient) {
|
||||
_httpClient.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class _CachedElevation {
|
||||
final double value;
|
||||
final DateTime expiresAt;
|
||||
|
||||
const _CachedElevation({required this.value, required this.expiresAt});
|
||||
}
|
||||
|
|
@ -58,11 +58,17 @@ class NotificationService {
|
|||
requestBadgePermission: true,
|
||||
requestSoundPermission: true,
|
||||
);
|
||||
const windowsSettings = WindowsInitializationSettings(
|
||||
appName: 'MeshCore Open',
|
||||
appUserModelId: 'org.meshcore.open.app',
|
||||
guid: 'e7ea8f85-72f5-4f36-91f6-038f740ccf86',
|
||||
);
|
||||
|
||||
const initSettings = InitializationSettings(
|
||||
android: androidSettings,
|
||||
iOS: iosSettings,
|
||||
macOS: macSettings,
|
||||
windows: windowsSettings,
|
||||
);
|
||||
|
||||
try {
|
||||
|
|
@ -76,6 +82,13 @@ class NotificationService {
|
|||
}
|
||||
}
|
||||
|
||||
Future<bool> _ensureInitialized() async {
|
||||
if (!_isInitialized) {
|
||||
await initialize();
|
||||
}
|
||||
return _isInitialized;
|
||||
}
|
||||
|
||||
Future<bool> requestPermissions() async {
|
||||
if (!_isInitialized) {
|
||||
await initialize();
|
||||
|
|
@ -114,9 +127,7 @@ class NotificationService {
|
|||
String? contactId,
|
||||
int? badgeCount,
|
||||
}) async {
|
||||
if (!_isInitialized) {
|
||||
await initialize();
|
||||
}
|
||||
if (!await _ensureInitialized()) return;
|
||||
|
||||
final androidDetails = AndroidNotificationDetails(
|
||||
'messages',
|
||||
|
|
@ -148,13 +159,17 @@ class NotificationService {
|
|||
macOS: macDetails,
|
||||
);
|
||||
|
||||
await _notifications.show(
|
||||
id: contactId?.hashCode ?? 0,
|
||||
title: contactName,
|
||||
body: message,
|
||||
notificationDetails: notificationDetails,
|
||||
payload: 'message:$contactId',
|
||||
);
|
||||
try {
|
||||
await _notifications.show(
|
||||
id: contactId?.hashCode ?? 0,
|
||||
title: contactName,
|
||||
body: message,
|
||||
notificationDetails: notificationDetails,
|
||||
payload: 'message:$contactId',
|
||||
);
|
||||
} catch (e) {
|
||||
debugPrint('Failed to show message notification: $e');
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _showAdvertNotificationImpl({
|
||||
|
|
@ -162,9 +177,7 @@ class NotificationService {
|
|||
required String contactType,
|
||||
String? contactId,
|
||||
}) async {
|
||||
if (!_isInitialized) {
|
||||
await initialize();
|
||||
}
|
||||
if (!await _ensureInitialized()) return;
|
||||
|
||||
const androidDetails = AndroidNotificationDetails(
|
||||
'adverts',
|
||||
|
|
@ -193,13 +206,17 @@ class NotificationService {
|
|||
macOS: macDetails,
|
||||
);
|
||||
|
||||
await _notifications.show(
|
||||
id: contactId?.hashCode ?? DateTime.now().millisecondsSinceEpoch,
|
||||
title: _l10n.notification_newTypeDiscovered(contactType),
|
||||
body: contactName,
|
||||
notificationDetails: notificationDetails,
|
||||
payload: 'advert:$contactId',
|
||||
);
|
||||
try {
|
||||
await _notifications.show(
|
||||
id: contactId?.hashCode ?? DateTime.now().millisecondsSinceEpoch,
|
||||
title: _l10n.notification_newTypeDiscovered(contactType),
|
||||
body: contactName,
|
||||
notificationDetails: notificationDetails,
|
||||
payload: 'advert:$contactId',
|
||||
);
|
||||
} catch (e) {
|
||||
debugPrint('Failed to show advert notification: $e');
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _showChannelMessageNotificationImpl({
|
||||
|
|
@ -208,9 +225,7 @@ class NotificationService {
|
|||
int? channelIndex,
|
||||
int? badgeCount,
|
||||
}) async {
|
||||
if (!_isInitialized) {
|
||||
await initialize();
|
||||
}
|
||||
if (!await _ensureInitialized()) return;
|
||||
|
||||
final androidDetails = AndroidNotificationDetails(
|
||||
'channel_messages',
|
||||
|
|
@ -247,13 +262,17 @@ class NotificationService {
|
|||
? _l10n.notification_receivedNewMessage
|
||||
: preview;
|
||||
|
||||
await _notifications.show(
|
||||
id: channelIndex?.hashCode ?? DateTime.now().millisecondsSinceEpoch,
|
||||
title: channelName,
|
||||
body: body,
|
||||
notificationDetails: notificationDetails,
|
||||
payload: 'channel:$channelIndex',
|
||||
);
|
||||
try {
|
||||
await _notifications.show(
|
||||
id: channelIndex?.hashCode ?? DateTime.now().millisecondsSinceEpoch,
|
||||
title: channelName,
|
||||
body: body,
|
||||
notificationDetails: notificationDetails,
|
||||
payload: 'channel:$channelIndex',
|
||||
);
|
||||
} catch (e) {
|
||||
debugPrint('Failed to show channel notification: $e');
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a privacy-safe identifier for debug logging.
|
||||
|
|
@ -396,35 +415,39 @@ class NotificationService {
|
|||
Future<void> _showNotificationImmediately(
|
||||
_PendingNotification notification,
|
||||
) async {
|
||||
switch (notification.type) {
|
||||
case _NotificationType.message:
|
||||
await _showMessageNotificationImpl(
|
||||
contactName: notification.title,
|
||||
message: notification.body,
|
||||
contactId: notification.id,
|
||||
badgeCount: notification.badgeCount,
|
||||
);
|
||||
break;
|
||||
case _NotificationType.advert:
|
||||
await _showAdvertNotificationImpl(
|
||||
contactName: notification.body,
|
||||
contactType: notification.title,
|
||||
contactId: notification.id,
|
||||
);
|
||||
break;
|
||||
case _NotificationType.channelMessage:
|
||||
await _showChannelMessageNotificationImpl(
|
||||
channelName: notification.title,
|
||||
message: notification.body,
|
||||
channelIndex: int.tryParse(notification.id ?? ''),
|
||||
badgeCount: notification.badgeCount,
|
||||
);
|
||||
break;
|
||||
try {
|
||||
switch (notification.type) {
|
||||
case _NotificationType.message:
|
||||
await _showMessageNotificationImpl(
|
||||
contactName: notification.title,
|
||||
message: notification.body,
|
||||
contactId: notification.id,
|
||||
badgeCount: notification.badgeCount,
|
||||
);
|
||||
break;
|
||||
case _NotificationType.advert:
|
||||
await _showAdvertNotificationImpl(
|
||||
contactName: notification.body,
|
||||
contactType: notification.title,
|
||||
contactId: notification.id,
|
||||
);
|
||||
break;
|
||||
case _NotificationType.channelMessage:
|
||||
await _showChannelMessageNotificationImpl(
|
||||
channelName: notification.title,
|
||||
message: notification.body,
|
||||
channelIndex: int.tryParse(notification.id ?? ''),
|
||||
badgeCount: notification.badgeCount,
|
||||
);
|
||||
break;
|
||||
}
|
||||
} catch (e) {
|
||||
debugPrint('Failed to show immediate notification: $e');
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _showBatchSummary(List<_PendingNotification> batch) async {
|
||||
if (!_isInitialized) await initialize();
|
||||
if (!await _ensureInitialized()) return;
|
||||
|
||||
// Group by type
|
||||
final messages = batch
|
||||
|
|
@ -468,13 +491,17 @@ class NotificationService {
|
|||
|
||||
const notificationDetails = NotificationDetails(android: androidDetails);
|
||||
|
||||
await _notifications.show(
|
||||
id: 'batch_summary'.hashCode,
|
||||
title: _l10n.notification_activityTitle,
|
||||
body: parts.join(', '),
|
||||
notificationDetails: notificationDetails,
|
||||
payload: 'batch',
|
||||
);
|
||||
try {
|
||||
await _notifications.show(
|
||||
id: 'batch_summary'.hashCode,
|
||||
title: _l10n.notification_activityTitle,
|
||||
body: parts.join(', '),
|
||||
notificationDetails: notificationDetails,
|
||||
payload: 'batch',
|
||||
);
|
||||
} catch (e) {
|
||||
debugPrint('Failed to show batch summary notification: $e');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
17
lib/widgets/adaptive_app_bar_title.dart
Normal file
17
lib/widgets/adaptive_app_bar_title.dart
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
class AdaptiveAppBarTitle extends StatelessWidget {
|
||||
final String text;
|
||||
|
||||
const AdaptiveAppBarTitle(this.text, {super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return LayoutBuilder(
|
||||
builder: (context, constraints) => SizedBox(
|
||||
width: constraints.maxWidth,
|
||||
child: FittedBox(fit: BoxFit.scaleDown, child: Text(text, maxLines: 1)),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
72
test/services/line_of_sight_service_test.dart
Normal file
72
test/services/line_of_sight_service_test.dart
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:latlong2/latlong.dart';
|
||||
import 'package:meshcore_open/services/line_of_sight_service.dart';
|
||||
|
||||
void main() {
|
||||
List<LatLng> makePoints(int count) {
|
||||
return List<LatLng>.generate(count, (i) => LatLng(0, i * 0.00001));
|
||||
}
|
||||
|
||||
test('computeFromElevations reports clear LOS on flat terrain', () {
|
||||
final points = makePoints(21);
|
||||
final elevations = List<double>.filled(points.length, 100);
|
||||
|
||||
final result = LineOfSightService.computeFromElevations(
|
||||
points: points,
|
||||
elevations: elevations,
|
||||
startAntennaHeightMeters: 2,
|
||||
endAntennaHeightMeters: 2,
|
||||
);
|
||||
|
||||
expect(result.hasData, isTrue);
|
||||
expect(result.isClear, isTrue);
|
||||
expect(result.maxObstructionMeters, equals(0));
|
||||
expect(result.firstObstructionDistanceMeters, isNull);
|
||||
});
|
||||
|
||||
test(
|
||||
'computeFromElevations reports blocked LOS with central obstruction',
|
||||
() {
|
||||
final points = makePoints(21);
|
||||
final elevations = List<double>.filled(points.length, 100);
|
||||
elevations[10] = 300;
|
||||
|
||||
final result = LineOfSightService.computeFromElevations(
|
||||
points: points,
|
||||
elevations: elevations,
|
||||
startAntennaHeightMeters: 1.5,
|
||||
endAntennaHeightMeters: 1.5,
|
||||
);
|
||||
|
||||
expect(result.hasData, isTrue);
|
||||
expect(result.isClear, isFalse);
|
||||
expect(result.maxObstructionMeters, greaterThan(0));
|
||||
expect(result.firstObstructionDistanceMeters, isNotNull);
|
||||
},
|
||||
);
|
||||
|
||||
test('analyzePath summarizes clear and blocked segments', () async {
|
||||
final service = LineOfSightService(
|
||||
elevationDataSource: (points) async {
|
||||
final elevations = List<double?>.filled(points.length, 100);
|
||||
if (points.first.longitude > 0.00005) {
|
||||
elevations[elevations.length ~/ 2] = 300;
|
||||
}
|
||||
return elevations;
|
||||
},
|
||||
);
|
||||
|
||||
final path = [
|
||||
const LatLng(0, 0),
|
||||
const LatLng(0, 0.0001),
|
||||
const LatLng(0, 0.0002),
|
||||
];
|
||||
|
||||
final result = await service.analyzePath(path);
|
||||
|
||||
expect(result.segments.length, 2);
|
||||
expect(result.clearSegments, 1);
|
||||
expect(result.blockedSegments, 1);
|
||||
expect(result.unknownSegments, 0);
|
||||
});
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue