From 4e368d562d3868fda7cd539d2609377ed2748f97 Mon Sep 17 00:00:00 2001 From: just-stuff-tm Date: Sat, 25 Apr 2026 08:56:28 -0400 Subject: [PATCH 1/4] add selectable LOS obstruction pinning for repeater placement --- .contextstream/config.json | 7 + lib/l10n/app_bg.arb | 42 +- lib/l10n/app_de.arb | 42 +- lib/l10n/app_en.arb | 40 ++ lib/l10n/app_es.arb | 42 +- lib/l10n/app_fr.arb | 42 +- lib/l10n/app_hu.arb | 42 +- lib/l10n/app_it.arb | 42 +- lib/l10n/app_ja.arb | 42 +- lib/l10n/app_ko.arb | 42 +- lib/l10n/app_localizations.dart | 41 ++ lib/l10n/app_localizations_bg.dart | 31 ++ lib/l10n/app_localizations_de.dart | 31 ++ lib/l10n/app_localizations_en.dart | 31 ++ lib/l10n/app_localizations_es.dart | 31 ++ lib/l10n/app_localizations_fr.dart | 31 ++ lib/l10n/app_localizations_hu.dart | 31 ++ lib/l10n/app_localizations_it.dart | 31 ++ lib/l10n/app_localizations_ja.dart | 30 ++ lib/l10n/app_localizations_ko.dart | 30 ++ lib/l10n/app_localizations_nl.dart | 31 ++ lib/l10n/app_localizations_pl.dart | 31 ++ lib/l10n/app_localizations_pt.dart | 31 ++ lib/l10n/app_localizations_ru.dart | 32 ++ lib/l10n/app_localizations_sk.dart | 31 ++ lib/l10n/app_localizations_sl.dart | 31 ++ lib/l10n/app_localizations_sv.dart | 31 ++ lib/l10n/app_localizations_uk.dart | 31 ++ lib/l10n/app_localizations_zh.dart | 30 ++ lib/l10n/app_nl.arb | 42 +- lib/l10n/app_pl.arb | 42 +- lib/l10n/app_pt.arb | 42 +- lib/l10n/app_ru.arb | 42 +- lib/l10n/app_sk.arb | 42 +- lib/l10n/app_sl.arb | 42 +- lib/l10n/app_sv.arb | 42 +- lib/l10n/app_uk.arb | 42 +- lib/l10n/app_zh.arb | 42 +- lib/screens/line_of_sight_map_screen.dart | 426 ++++++++++++++++-- lib/services/line_of_sight_service.dart | 49 +- test/services/line_of_sight_service_test.dart | 24 + 41 files changed, 1811 insertions(+), 46 deletions(-) create mode 100644 .contextstream/config.json diff --git a/.contextstream/config.json b/.contextstream/config.json new file mode 100644 index 00000000..412e2fb3 --- /dev/null +++ b/.contextstream/config.json @@ -0,0 +1,7 @@ +{ + "workspace_id": "56872cc9-7375-4423-92d5-6cdebf6564dc", + "workspace_name": ".contextstream-global", + "project_id": "5b1fe101-ea6b-4fe3-9de5-c87722c38084", + "project_name": "meshcore-open", + "associated_at": "2026-04-25T12:13:54.086Z" +} \ No newline at end of file diff --git a/lib/l10n/app_bg.arb b/lib/l10n/app_bg.arb index 7260c253..b37529ba 100644 --- a/lib/l10n/app_bg.arb +++ b/lib/l10n/app_bg.arb @@ -2066,5 +2066,45 @@ "room_guest": "Информация за сървъра на стаята", "repeater_guest": "Информация за ретранслаторите", "repeater_guestTools": "Инструменти за гости", - "settings_multiAck": "Множество потвърждения" + "settings_multiAck": "Множество потвърждения", + "@losBlockedSpotChip": { + "placeholders": { + "distance": { + "type": "String" + }, + "distanceUnit": { + "type": "String" + }, + "obstruction": { + "type": "String" + }, + "heightUnit": { + "type": "String" + } + } + }, + "@losSelectedObstructionDetails": { + "placeholders": { + "obstruction": { + "type": "String" + }, + "heightUnit": { + "type": "String" + }, + "distanceFromA": { + "type": "String" + }, + "distanceUnit": { + "type": "String" + }, + "distanceFromB": { + "type": "String" + } + } + }, + "losSelectedObstructionTitle": "Избрано препятствие", + "losBlockedSpotChip": "{distance} {distanceUnit} • {obstruction} {heightUnit}", + "losBlockedSpotsHint": "Кликнете върху блокираната точка, за да я отбележите на картата.", + "losBlockedSpotsTitle": "Ограничени места", + "losSelectedObstructionDetails": "Blocked by {obstruction} {heightUnit}, {distanceFromA} from A and {distanceFromB} from B ({distanceUnit})." } diff --git a/lib/l10n/app_de.arb b/lib/l10n/app_de.arb index bf8ad2eb..86846e98 100644 --- a/lib/l10n/app_de.arb +++ b/lib/l10n/app_de.arb @@ -2094,5 +2094,45 @@ "repeater_guestTools": "Gastwerkzeuge", "chat_sendMessage": "Nachricht senden", "room_guest": "Informationen zum Room Server", - "settings_multiAck": "Mehrere Bestätigungen" + "settings_multiAck": "Mehrere Bestätigungen", + "@losBlockedSpotChip": { + "placeholders": { + "distance": { + "type": "String" + }, + "distanceUnit": { + "type": "String" + }, + "obstruction": { + "type": "String" + }, + "heightUnit": { + "type": "String" + } + } + }, + "@losSelectedObstructionDetails": { + "placeholders": { + "obstruction": { + "type": "String" + }, + "heightUnit": { + "type": "String" + }, + "distanceFromA": { + "type": "String" + }, + "distanceUnit": { + "type": "String" + }, + "distanceFromB": { + "type": "String" + } + } + }, + "losBlockedSpotsTitle": "Reservierte Plätze", + "losSelectedObstructionTitle": "Ausgewählte Behinderung", + "losBlockedSpotChip": "{distance} • {distanceUnit} • {obstruction} {heightUnit}", + "losBlockedSpotsHint": "Klicken Sie auf einen blockierten Bereich, um ihn auf der Karte hervorzuheben.", + "losSelectedObstructionDetails": "Blockiert durch {obstruction} in einer Höhe von {heightUnit}, {distanceFromA} von A und {distanceFromB} von B ({distanceUnit})." } diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 606410cb..dfe81683 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -1875,6 +1875,46 @@ "losLegendRadioHorizon": "Radio horizon", "losLegendLosBeam": "LOS beam", "losLegendTerrain": "Terrain", + "losBlockedSpotsTitle": "Blocked spots", + "losBlockedSpotsHint": "Tap a blocked spot to highlight it on the map.", + "losBlockedSpotChip": "{distance} {distanceUnit} • {obstruction} {heightUnit}", + "@losBlockedSpotChip": { + "placeholders": { + "distance": { + "type": "String" + }, + "distanceUnit": { + "type": "String" + }, + "obstruction": { + "type": "String" + }, + "heightUnit": { + "type": "String" + } + } + }, + "losSelectedObstructionTitle": "Selected obstruction", + "losSelectedObstructionDetails": "Blocked by {obstruction} {heightUnit}, {distanceFromA} from A and {distanceFromB} from B ({distanceUnit}).", + "@losSelectedObstructionDetails": { + "placeholders": { + "obstruction": { + "type": "String" + }, + "heightUnit": { + "type": "String" + }, + "distanceFromA": { + "type": "String" + }, + "distanceUnit": { + "type": "String" + }, + "distanceFromB": { + "type": "String" + } + } + }, "losFrequencyLabel": "Frequency", "losFrequencyInfoTooltip": "View calculation details", "losFrequencyDialogTitle": "Radio horizon calculation", diff --git a/lib/l10n/app_es.arb b/lib/l10n/app_es.arb index b0585680..04497cbf 100644 --- a/lib/l10n/app_es.arb +++ b/lib/l10n/app_es.arb @@ -2094,5 +2094,45 @@ "chat_sendMessage": "Enviar mensaje", "repeater_guestTools": "Herramientas para invitados", "room_guest": "Información del servidor", - "settings_multiAck": "Múltiples respuestas de confirmación" + "settings_multiAck": "Múltiples respuestas de confirmación", + "@losBlockedSpotChip": { + "placeholders": { + "distance": { + "type": "String" + }, + "distanceUnit": { + "type": "String" + }, + "obstruction": { + "type": "String" + }, + "heightUnit": { + "type": "String" + } + } + }, + "@losSelectedObstructionDetails": { + "placeholders": { + "obstruction": { + "type": "String" + }, + "heightUnit": { + "type": "String" + }, + "distanceFromA": { + "type": "String" + }, + "distanceUnit": { + "type": "String" + }, + "distanceFromB": { + "type": "String" + } + } + }, + "losBlockedSpotsTitle": "Espacios ocupados", + "losBlockedSpotsHint": "Seleccione un punto bloqueado para resaltarlo en el mapa.", + "losSelectedObstructionTitle": "Obstrucción seleccionada", + "losSelectedObstructionDetails": "Bloqueado por {obstruction} a una altura de {heightUnit}, a {distanceFromA} metros de A y a {distanceFromB} metros de B ({distanceUnit}).", + "losBlockedSpotChip": "{distance} {distanceUnit} • {obstruction} {heightUnit}" } diff --git a/lib/l10n/app_fr.arb b/lib/l10n/app_fr.arb index fa375a40..59ca46ac 100644 --- a/lib/l10n/app_fr.arb +++ b/lib/l10n/app_fr.arb @@ -2066,5 +2066,45 @@ "chat_sendMessage": "Envoyer un message", "room_guest": "Informations sur le serveur", "repeater_guest": "Informations sur les répéteurs", - "settings_multiAck": "Plusieurs accusés de réception" + "settings_multiAck": "Plusieurs accusés de réception", + "@losBlockedSpotChip": { + "placeholders": { + "distance": { + "type": "String" + }, + "distanceUnit": { + "type": "String" + }, + "obstruction": { + "type": "String" + }, + "heightUnit": { + "type": "String" + } + } + }, + "@losSelectedObstructionDetails": { + "placeholders": { + "obstruction": { + "type": "String" + }, + "heightUnit": { + "type": "String" + }, + "distanceFromA": { + "type": "String" + }, + "distanceUnit": { + "type": "String" + }, + "distanceFromB": { + "type": "String" + } + } + }, + "losSelectedObstructionTitle": "Obstruction sélectionnée", + "losBlockedSpotsTitle": "Places occupés", + "losBlockedSpotsHint": "Sélectionnez un emplacement bloqué pour le mettre en évidence sur la carte.", + "losSelectedObstructionDetails": "Bloqué par {obstruction}, à une hauteur de {heightUnit}, à une distance de {distanceFromA} par rapport à A et à une distance de {distanceFromB} par rapport à B ({distanceUnit}).", + "losBlockedSpotChip": "{distance} {distanceUnit} • {obstruction} {heightUnit}" } diff --git a/lib/l10n/app_hu.arb b/lib/l10n/app_hu.arb index ae6acfb2..597faf62 100644 --- a/lib/l10n/app_hu.arb +++ b/lib/l10n/app_hu.arb @@ -2104,5 +2104,45 @@ "room_guest": "Szoba szerver információk", "chat_sendMessage": "Üzenet küldése", "repeater_guest": "Adatok a repeaterről", - "settings_multiAck": "Többszörös visszaigazolások" + "settings_multiAck": "Többszörös visszaigazolások", + "@losBlockedSpotChip": { + "placeholders": { + "distance": { + "type": "String" + }, + "distanceUnit": { + "type": "String" + }, + "obstruction": { + "type": "String" + }, + "heightUnit": { + "type": "String" + } + } + }, + "@losSelectedObstructionDetails": { + "placeholders": { + "obstruction": { + "type": "String" + }, + "heightUnit": { + "type": "String" + }, + "distanceFromA": { + "type": "String" + }, + "distanceUnit": { + "type": "String" + }, + "distanceFromB": { + "type": "String" + } + } + }, + "losSelectedObstructionTitle": "Kiválasztott akadály", + "losBlockedSpotChip": "{distance} {distanceUnit} • {obstruction} {heightUnit}", + "losBlockedSpotsHint": "A blokkolt területet megjelölve, hogy a térképen kiemeljük.", + "losBlockedSpotsTitle": "Foglalhatatlan területek", + "losSelectedObstructionDetails": "Elakadt a {obstruction} miatt, {heightUnit} magasságban, {distanceFromA} méterrel A-tól és {distanceFromB} méterrel B-től ({distanceUnit})." } diff --git a/lib/l10n/app_it.arb b/lib/l10n/app_it.arb index 35001e61..9db535dd 100644 --- a/lib/l10n/app_it.arb +++ b/lib/l10n/app_it.arb @@ -2066,5 +2066,45 @@ "repeater_guestTools": "Strumenti per gli ospiti", "chat_sendMessage": "Invia messaggio", "room_guest": "Informazioni sul server", - "settings_multiAck": "ACK multipli" + "settings_multiAck": "ACK multipli", + "@losBlockedSpotChip": { + "placeholders": { + "distance": { + "type": "String" + }, + "distanceUnit": { + "type": "String" + }, + "obstruction": { + "type": "String" + }, + "heightUnit": { + "type": "String" + } + } + }, + "@losSelectedObstructionDetails": { + "placeholders": { + "obstruction": { + "type": "String" + }, + "heightUnit": { + "type": "String" + }, + "distanceFromA": { + "type": "String" + }, + "distanceUnit": { + "type": "String" + }, + "distanceFromB": { + "type": "String" + } + } + }, + "losSelectedObstructionTitle": "Ostacolo selezionato", + "losBlockedSpotsHint": "Tocca un punto bloccato sulla mappa per evidenziarlo.", + "losBlockedSpotsTitle": "Posti occupati", + "losBlockedSpotChip": "{distance} {distanceUnit} • {obstruction} {heightUnit}", + "losSelectedObstructionDetails": "Blocked by {obstruction} {heightUnit}, {distanceFromA} from A and {distanceFromB} from B ({distanceUnit})." } diff --git a/lib/l10n/app_ja.arb b/lib/l10n/app_ja.arb index 7ac449db..4a45f4cd 100644 --- a/lib/l10n/app_ja.arb +++ b/lib/l10n/app_ja.arb @@ -2104,5 +2104,45 @@ "chat_sendMessage": "メッセージを送信する", "repeater_guest": "繰り返し送信に関する情報", "repeater_guestTools": "ゲスト向けツール", - "settings_multiAck": "複数のACK(応答)" + "settings_multiAck": "複数のACK(応答)", + "@losBlockedSpotChip": { + "placeholders": { + "distance": { + "type": "String" + }, + "distanceUnit": { + "type": "String" + }, + "obstruction": { + "type": "String" + }, + "heightUnit": { + "type": "String" + } + } + }, + "@losSelectedObstructionDetails": { + "placeholders": { + "obstruction": { + "type": "String" + }, + "heightUnit": { + "type": "String" + }, + "distanceFromA": { + "type": "String" + }, + "distanceUnit": { + "type": "String" + }, + "distanceFromB": { + "type": "String" + } + } + }, + "losBlockedSpotsHint": "地図上で、特定された場所を強調するために、該当する場所をタップしてください。", + "losSelectedObstructionTitle": "選択された障害", + "losBlockedSpotsTitle": "利用できない場所", + "losSelectedObstructionDetails": "{obstruction} によって {heightUnit} の高さで、A地点から {distanceFromA}、B地点から {distanceFromB} ({distanceUnit}) の距離で塞がれています。", + "losBlockedSpotChip": "{distance} {distanceUnit} • {obstruction} {heightUnit}" } diff --git a/lib/l10n/app_ko.arb b/lib/l10n/app_ko.arb index 54073db7..69fd99f9 100644 --- a/lib/l10n/app_ko.arb +++ b/lib/l10n/app_ko.arb @@ -2104,5 +2104,45 @@ "chat_sendMessage": "메시지를 보내기", "repeater_guest": "반복 장비 정보", "room_guest": "서버 정보", - "settings_multiAck": "다중 ACK" + "settings_multiAck": "다중 ACK", + "@losBlockedSpotChip": { + "placeholders": { + "distance": { + "type": "String" + }, + "distanceUnit": { + "type": "String" + }, + "obstruction": { + "type": "String" + }, + "heightUnit": { + "type": "String" + } + } + }, + "@losSelectedObstructionDetails": { + "placeholders": { + "obstruction": { + "type": "String" + }, + "heightUnit": { + "type": "String" + }, + "distanceFromA": { + "type": "String" + }, + "distanceUnit": { + "type": "String" + }, + "distanceFromB": { + "type": "String" + } + } + }, + "losBlockedSpotsHint": "지도에서 특정 위치를 강조하려면 해당 위치를 클릭하세요.", + "losBlockedSpotsTitle": "차단된 공간", + "losSelectedObstructionTitle": "선택된 장애물", + "losBlockedSpotChip": "{distance} {distanceUnit} • {obstruction} {heightUnit}", + "losSelectedObstructionDetails": "Blocked by {obstruction} {heightUnit}, {distanceFromA} from A and {distanceFromB} from B ({distanceUnit})." } diff --git a/lib/l10n/app_localizations.dart b/lib/l10n/app_localizations.dart index 09075570..d090bc13 100644 --- a/lib/l10n/app_localizations.dart +++ b/lib/l10n/app_localizations.dart @@ -5640,6 +5640,47 @@ abstract class AppLocalizations { /// **'Terrain'** String get losLegendTerrain; + /// No description provided for @losBlockedSpotsTitle. + /// + /// In en, this message translates to: + /// **'Blocked spots'** + String get losBlockedSpotsTitle; + + /// No description provided for @losBlockedSpotsHint. + /// + /// In en, this message translates to: + /// **'Tap a blocked spot to highlight it on the map.'** + String get losBlockedSpotsHint; + + /// No description provided for @losBlockedSpotChip. + /// + /// In en, this message translates to: + /// **'{distance} {distanceUnit} • {obstruction} {heightUnit}'** + String losBlockedSpotChip( + String distance, + String distanceUnit, + String obstruction, + String heightUnit, + ); + + /// No description provided for @losSelectedObstructionTitle. + /// + /// In en, this message translates to: + /// **'Selected obstruction'** + String get losSelectedObstructionTitle; + + /// No description provided for @losSelectedObstructionDetails. + /// + /// In en, this message translates to: + /// **'Blocked by {obstruction} {heightUnit}, {distanceFromA} from A and {distanceFromB} from B ({distanceUnit}).'** + String losSelectedObstructionDetails( + String obstruction, + String heightUnit, + String distanceFromA, + String distanceUnit, + String distanceFromB, + ); + /// No description provided for @losFrequencyLabel. /// /// In en, this message translates to: diff --git a/lib/l10n/app_localizations_bg.dart b/lib/l10n/app_localizations_bg.dart index 01973dff..8ee18b67 100644 --- a/lib/l10n/app_localizations_bg.dart +++ b/lib/l10n/app_localizations_bg.dart @@ -3236,6 +3236,37 @@ class AppLocalizationsBg extends AppLocalizations { @override String get losLegendTerrain => 'Терен'; + @override + String get losBlockedSpotsTitle => 'Ограничени места'; + + @override + String get losBlockedSpotsHint => + 'Кликнете върху блокираната точка, за да я отбележите на картата.'; + + @override + String losBlockedSpotChip( + String distance, + String distanceUnit, + String obstruction, + String heightUnit, + ) { + return '$distance $distanceUnit • $obstruction $heightUnit'; + } + + @override + String get losSelectedObstructionTitle => 'Избрано препятствие'; + + @override + String losSelectedObstructionDetails( + String obstruction, + String heightUnit, + String distanceFromA, + String distanceUnit, + String distanceFromB, + ) { + return 'Blocked by $obstruction $heightUnit, $distanceFromA from A and $distanceFromB from B ($distanceUnit).'; + } + @override String get losFrequencyLabel => 'Честота'; diff --git a/lib/l10n/app_localizations_de.dart b/lib/l10n/app_localizations_de.dart index 60cd3252..70d09b31 100644 --- a/lib/l10n/app_localizations_de.dart +++ b/lib/l10n/app_localizations_de.dart @@ -3241,6 +3241,37 @@ class AppLocalizationsDe extends AppLocalizations { @override String get losLegendTerrain => 'Gelände'; + @override + String get losBlockedSpotsTitle => 'Reservierte Plätze'; + + @override + String get losBlockedSpotsHint => + 'Klicken Sie auf einen blockierten Bereich, um ihn auf der Karte hervorzuheben.'; + + @override + String losBlockedSpotChip( + String distance, + String distanceUnit, + String obstruction, + String heightUnit, + ) { + return '$distance • $distanceUnit • $obstruction $heightUnit'; + } + + @override + String get losSelectedObstructionTitle => 'Ausgewählte Behinderung'; + + @override + String losSelectedObstructionDetails( + String obstruction, + String heightUnit, + String distanceFromA, + String distanceUnit, + String distanceFromB, + ) { + return 'Blockiert durch $obstruction in einer Höhe von $heightUnit, $distanceFromA von A und $distanceFromB von B ($distanceUnit).'; + } + @override String get losFrequencyLabel => 'Frequenz'; diff --git a/lib/l10n/app_localizations_en.dart b/lib/l10n/app_localizations_en.dart index 353e5bcb..eeb9091b 100644 --- a/lib/l10n/app_localizations_en.dart +++ b/lib/l10n/app_localizations_en.dart @@ -3180,6 +3180,37 @@ class AppLocalizationsEn extends AppLocalizations { @override String get losLegendTerrain => 'Terrain'; + @override + String get losBlockedSpotsTitle => 'Blocked spots'; + + @override + String get losBlockedSpotsHint => + 'Tap a blocked spot to highlight it on the map.'; + + @override + String losBlockedSpotChip( + String distance, + String distanceUnit, + String obstruction, + String heightUnit, + ) { + return '$distance $distanceUnit • $obstruction $heightUnit'; + } + + @override + String get losSelectedObstructionTitle => 'Selected obstruction'; + + @override + String losSelectedObstructionDetails( + String obstruction, + String heightUnit, + String distanceFromA, + String distanceUnit, + String distanceFromB, + ) { + return 'Blocked by $obstruction $heightUnit, $distanceFromA from A and $distanceFromB from B ($distanceUnit).'; + } + @override String get losFrequencyLabel => 'Frequency'; diff --git a/lib/l10n/app_localizations_es.dart b/lib/l10n/app_localizations_es.dart index d656ac49..824daaba 100644 --- a/lib/l10n/app_localizations_es.dart +++ b/lib/l10n/app_localizations_es.dart @@ -3235,6 +3235,37 @@ class AppLocalizationsEs extends AppLocalizations { @override String get losLegendTerrain => 'Terreno'; + @override + String get losBlockedSpotsTitle => 'Espacios ocupados'; + + @override + String get losBlockedSpotsHint => + 'Seleccione un punto bloqueado para resaltarlo en el mapa.'; + + @override + String losBlockedSpotChip( + String distance, + String distanceUnit, + String obstruction, + String heightUnit, + ) { + return '$distance $distanceUnit • $obstruction $heightUnit'; + } + + @override + String get losSelectedObstructionTitle => 'Obstrucción seleccionada'; + + @override + String losSelectedObstructionDetails( + String obstruction, + String heightUnit, + String distanceFromA, + String distanceUnit, + String distanceFromB, + ) { + return 'Bloqueado por $obstruction a una altura de $heightUnit, a $distanceFromA metros de A y a $distanceFromB metros de B ($distanceUnit).'; + } + @override String get losFrequencyLabel => 'Frecuencia'; diff --git a/lib/l10n/app_localizations_fr.dart b/lib/l10n/app_localizations_fr.dart index acdc9454..1ad69397 100644 --- a/lib/l10n/app_localizations_fr.dart +++ b/lib/l10n/app_localizations_fr.dart @@ -3253,6 +3253,37 @@ class AppLocalizationsFr extends AppLocalizations { @override String get losLegendTerrain => 'Terrain'; + @override + String get losBlockedSpotsTitle => 'Places occupés'; + + @override + String get losBlockedSpotsHint => + 'Sélectionnez un emplacement bloqué pour le mettre en évidence sur la carte.'; + + @override + String losBlockedSpotChip( + String distance, + String distanceUnit, + String obstruction, + String heightUnit, + ) { + return '$distance $distanceUnit • $obstruction $heightUnit'; + } + + @override + String get losSelectedObstructionTitle => 'Obstruction sélectionnée'; + + @override + String losSelectedObstructionDetails( + String obstruction, + String heightUnit, + String distanceFromA, + String distanceUnit, + String distanceFromB, + ) { + return 'Bloqué par $obstruction, à une hauteur de $heightUnit, à une distance de $distanceFromA par rapport à A et à une distance de $distanceFromB par rapport à B ($distanceUnit).'; + } + @override String get losFrequencyLabel => 'Fréquence'; diff --git a/lib/l10n/app_localizations_hu.dart b/lib/l10n/app_localizations_hu.dart index 24c9ac66..34c25bd5 100644 --- a/lib/l10n/app_localizations_hu.dart +++ b/lib/l10n/app_localizations_hu.dart @@ -3249,6 +3249,37 @@ class AppLocalizationsHu extends AppLocalizations { @override String get losLegendTerrain => 'Terület'; + @override + String get losBlockedSpotsTitle => 'Foglalhatatlan területek'; + + @override + String get losBlockedSpotsHint => + 'A blokkolt területet megjelölve, hogy a térképen kiemeljük.'; + + @override + String losBlockedSpotChip( + String distance, + String distanceUnit, + String obstruction, + String heightUnit, + ) { + return '$distance $distanceUnit • $obstruction $heightUnit'; + } + + @override + String get losSelectedObstructionTitle => 'Kiválasztott akadály'; + + @override + String losSelectedObstructionDetails( + String obstruction, + String heightUnit, + String distanceFromA, + String distanceUnit, + String distanceFromB, + ) { + return 'Elakadt a $obstruction miatt, $heightUnit magasságban, $distanceFromA méterrel A-tól és $distanceFromB méterrel B-től ($distanceUnit).'; + } + @override String get losFrequencyLabel => 'Hatósság'; diff --git a/lib/l10n/app_localizations_it.dart b/lib/l10n/app_localizations_it.dart index abb1427d..21f9e52b 100644 --- a/lib/l10n/app_localizations_it.dart +++ b/lib/l10n/app_localizations_it.dart @@ -3238,6 +3238,37 @@ class AppLocalizationsIt extends AppLocalizations { @override String get losLegendTerrain => 'Terreno'; + @override + String get losBlockedSpotsTitle => 'Posti occupati'; + + @override + String get losBlockedSpotsHint => + 'Tocca un punto bloccato sulla mappa per evidenziarlo.'; + + @override + String losBlockedSpotChip( + String distance, + String distanceUnit, + String obstruction, + String heightUnit, + ) { + return '$distance $distanceUnit • $obstruction $heightUnit'; + } + + @override + String get losSelectedObstructionTitle => 'Ostacolo selezionato'; + + @override + String losSelectedObstructionDetails( + String obstruction, + String heightUnit, + String distanceFromA, + String distanceUnit, + String distanceFromB, + ) { + return 'Blocked by $obstruction $heightUnit, $distanceFromA from A and $distanceFromB from B ($distanceUnit).'; + } + @override String get losFrequencyLabel => 'Frequenza'; diff --git a/lib/l10n/app_localizations_ja.dart b/lib/l10n/app_localizations_ja.dart index 60018262..d2d0511d 100644 --- a/lib/l10n/app_localizations_ja.dart +++ b/lib/l10n/app_localizations_ja.dart @@ -3090,6 +3090,36 @@ class AppLocalizationsJa extends AppLocalizations { @override String get losLegendTerrain => '地形'; + @override + String get losBlockedSpotsTitle => '利用できない場所'; + + @override + String get losBlockedSpotsHint => '地図上で、特定された場所を強調するために、該当する場所をタップしてください。'; + + @override + String losBlockedSpotChip( + String distance, + String distanceUnit, + String obstruction, + String heightUnit, + ) { + return '$distance $distanceUnit • $obstruction $heightUnit'; + } + + @override + String get losSelectedObstructionTitle => '選択された障害'; + + @override + String losSelectedObstructionDetails( + String obstruction, + String heightUnit, + String distanceFromA, + String distanceUnit, + String distanceFromB, + ) { + return '$obstruction によって $heightUnit の高さで、A地点から $distanceFromA、B地点から $distanceFromB ($distanceUnit) の距離で塞がれています。'; + } + @override String get losFrequencyLabel => '周波数'; diff --git a/lib/l10n/app_localizations_ko.dart b/lib/l10n/app_localizations_ko.dart index 8f74af8f..b2bd9131 100644 --- a/lib/l10n/app_localizations_ko.dart +++ b/lib/l10n/app_localizations_ko.dart @@ -3090,6 +3090,36 @@ class AppLocalizationsKo extends AppLocalizations { @override String get losLegendTerrain => '지형'; + @override + String get losBlockedSpotsTitle => '차단된 공간'; + + @override + String get losBlockedSpotsHint => '지도에서 특정 위치를 강조하려면 해당 위치를 클릭하세요.'; + + @override + String losBlockedSpotChip( + String distance, + String distanceUnit, + String obstruction, + String heightUnit, + ) { + return '$distance $distanceUnit • $obstruction $heightUnit'; + } + + @override + String get losSelectedObstructionTitle => '선택된 장애물'; + + @override + String losSelectedObstructionDetails( + String obstruction, + String heightUnit, + String distanceFromA, + String distanceUnit, + String distanceFromB, + ) { + return 'Blocked by $obstruction $heightUnit, $distanceFromA from A and $distanceFromB from B ($distanceUnit).'; + } + @override String get losFrequencyLabel => '빈도'; diff --git a/lib/l10n/app_localizations_nl.dart b/lib/l10n/app_localizations_nl.dart index d1ebea4a..fadc7b1e 100644 --- a/lib/l10n/app_localizations_nl.dart +++ b/lib/l10n/app_localizations_nl.dart @@ -3220,6 +3220,37 @@ class AppLocalizationsNl extends AppLocalizations { @override String get losLegendTerrain => 'Terrein'; + @override + String get losBlockedSpotsTitle => 'Geplande plaatsen'; + + @override + String get losBlockedSpotsHint => + 'Tik op een geblokkeerd gebied om het op de kaart te markeren.'; + + @override + String losBlockedSpotChip( + String distance, + String distanceUnit, + String obstruction, + String heightUnit, + ) { + return '$distance $distanceUnit • $obstruction $heightUnit'; + } + + @override + String get losSelectedObstructionTitle => 'Geselecteerde obstakel'; + + @override + String losSelectedObstructionDetails( + String obstruction, + String heightUnit, + String distanceFromA, + String distanceUnit, + String distanceFromB, + ) { + return 'Blocked by $obstruction $heightUnit, $distanceFromA from A and $distanceFromB from B ($distanceUnit).'; + } + @override String get losFrequencyLabel => 'Frequentie'; diff --git a/lib/l10n/app_localizations_pl.dart b/lib/l10n/app_localizations_pl.dart index 918860ed..cdfef437 100644 --- a/lib/l10n/app_localizations_pl.dart +++ b/lib/l10n/app_localizations_pl.dart @@ -3245,6 +3245,37 @@ class AppLocalizationsPl extends AppLocalizations { @override String get losLegendTerrain => 'Teren'; + @override + String get losBlockedSpotsTitle => 'Zablokowane miejsca'; + + @override + String get losBlockedSpotsHint => + 'Kliknij zablokowane miejsce, aby je zaznaczyć na mapie.'; + + @override + String losBlockedSpotChip( + String distance, + String distanceUnit, + String obstruction, + String heightUnit, + ) { + return '$distance $distanceUnit • $obstruction $heightUnit'; + } + + @override + String get losSelectedObstructionTitle => 'Wybór przeszkody'; + + @override + String losSelectedObstructionDetails( + String obstruction, + String heightUnit, + String distanceFromA, + String distanceUnit, + String distanceFromB, + ) { + return 'Blocked by $obstruction $heightUnit, $distanceFromA from A and $distanceFromB from B ($distanceUnit).'; + } + @override String get losFrequencyLabel => 'Częstotliwość'; diff --git a/lib/l10n/app_localizations_pt.dart b/lib/l10n/app_localizations_pt.dart index 5584da93..2b7d1109 100644 --- a/lib/l10n/app_localizations_pt.dart +++ b/lib/l10n/app_localizations_pt.dart @@ -3234,6 +3234,37 @@ class AppLocalizationsPt extends AppLocalizations { @override String get losLegendTerrain => 'Terreno'; + @override + String get losBlockedSpotsTitle => 'Locais ocupados'; + + @override + String get losBlockedSpotsHint => + 'Toque em um ponto bloqueado para destacá-lo no mapa.'; + + @override + String losBlockedSpotChip( + String distance, + String distanceUnit, + String obstruction, + String heightUnit, + ) { + return '$distance $distanceUnit • $obstruction $heightUnit'; + } + + @override + String get losSelectedObstructionTitle => 'Obstrução selecionada'; + + @override + String losSelectedObstructionDetails( + String obstruction, + String heightUnit, + String distanceFromA, + String distanceUnit, + String distanceFromB, + ) { + return 'Blocked by $obstruction $heightUnit, $distanceFromA from A and $distanceFromB from B ($distanceUnit).'; + } + @override String get losFrequencyLabel => 'Frequência'; diff --git a/lib/l10n/app_localizations_ru.dart b/lib/l10n/app_localizations_ru.dart index 4b7e3d44..76cf8e34 100644 --- a/lib/l10n/app_localizations_ru.dart +++ b/lib/l10n/app_localizations_ru.dart @@ -3239,6 +3239,38 @@ class AppLocalizationsRu extends AppLocalizations { @override String get losLegendTerrain => 'Рельеф'; + @override + String get losBlockedSpotsTitle => 'Зарезервированные места'; + + @override + String get losBlockedSpotsHint => + 'Щелкните по заблокированной области, чтобы выделить ее на карте.'; + + @override + String losBlockedSpotChip( + String distance, + String distanceUnit, + String obstruction, + String heightUnit, + ) { + return '$distance $distanceUnit • $obstruction $heightUnit'; + } + + @override + String get losSelectedObstructionTitle => + 'Выбранный объект, препятствующий движению'; + + @override + String losSelectedObstructionDetails( + String obstruction, + String heightUnit, + String distanceFromA, + String distanceUnit, + String distanceFromB, + ) { + return 'Blocked by $obstruction $heightUnit, $distanceFromA from A and $distanceFromB from B ($distanceUnit).'; + } + @override String get losFrequencyLabel => 'Частота'; diff --git a/lib/l10n/app_localizations_sk.dart b/lib/l10n/app_localizations_sk.dart index f855652d..10407978 100644 --- a/lib/l10n/app_localizations_sk.dart +++ b/lib/l10n/app_localizations_sk.dart @@ -3214,6 +3214,37 @@ class AppLocalizationsSk extends AppLocalizations { @override String get losLegendTerrain => 'Terén'; + @override + String get losBlockedSpotsTitle => 'Zablokované miesta'; + + @override + String get losBlockedSpotsHint => + 'Kliknite na zablokované miesto, aby ste ho zvýraznili na mape.'; + + @override + String losBlockedSpotChip( + String distance, + String distanceUnit, + String obstruction, + String heightUnit, + ) { + return '$distance $distanceUnit • $obstruction $heightUnit'; + } + + @override + String get losSelectedObstructionTitle => 'Vybraná prekážka'; + + @override + String losSelectedObstructionDetails( + String obstruction, + String heightUnit, + String distanceFromA, + String distanceUnit, + String distanceFromB, + ) { + return 'Blocked by $obstruction $heightUnit, $distanceFromA from A and $distanceFromB from B ($distanceUnit).'; + } + @override String get losFrequencyLabel => 'Frekvencia'; diff --git a/lib/l10n/app_localizations_sl.dart b/lib/l10n/app_localizations_sl.dart index 2f3b30c0..157dce80 100644 --- a/lib/l10n/app_localizations_sl.dart +++ b/lib/l10n/app_localizations_sl.dart @@ -3215,6 +3215,37 @@ class AppLocalizationsSl extends AppLocalizations { @override String get losLegendTerrain => 'Teren'; + @override + String get losBlockedSpotsTitle => 'Zasedena parkirišča'; + + @override + String get losBlockedSpotsHint => + 'Dotaknite blokirano točko, da jo označite na zemljeplati.'; + + @override + String losBlockedSpotChip( + String distance, + String distanceUnit, + String obstruction, + String heightUnit, + ) { + return '$distance $distanceUnit • $obstruction $heightUnit'; + } + + @override + String get losSelectedObstructionTitle => 'Izbrano ovire'; + + @override + String losSelectedObstructionDetails( + String obstruction, + String heightUnit, + String distanceFromA, + String distanceUnit, + String distanceFromB, + ) { + return 'Blocked by $obstruction $heightUnit, $distanceFromA from A and $distanceFromB from B ($distanceUnit).'; + } + @override String get losFrequencyLabel => 'Frekvenca'; diff --git a/lib/l10n/app_localizations_sv.dart b/lib/l10n/app_localizations_sv.dart index 00816f2a..f3259c5f 100644 --- a/lib/l10n/app_localizations_sv.dart +++ b/lib/l10n/app_localizations_sv.dart @@ -3197,6 +3197,37 @@ class AppLocalizationsSv extends AppLocalizations { @override String get losLegendTerrain => 'Terräng'; + @override + String get losBlockedSpotsTitle => 'Reserverade platser'; + + @override + String get losBlockedSpotsHint => + 'Klicka på en markerad plats för att framhäva den på kartan.'; + + @override + String losBlockedSpotChip( + String distance, + String distanceUnit, + String obstruction, + String heightUnit, + ) { + return '$distance $distanceUnit • $obstruction $heightUnit'; + } + + @override + String get losSelectedObstructionTitle => 'Vald hinder'; + + @override + String losSelectedObstructionDetails( + String obstruction, + String heightUnit, + String distanceFromA, + String distanceUnit, + String distanceFromB, + ) { + return 'Blocked by $obstruction $heightUnit, $distanceFromA from A and $distanceFromB from B ($distanceUnit).'; + } + @override String get losFrequencyLabel => 'Frekvens'; diff --git a/lib/l10n/app_localizations_uk.dart b/lib/l10n/app_localizations_uk.dart index f43fd53e..8f83263d 100644 --- a/lib/l10n/app_localizations_uk.dart +++ b/lib/l10n/app_localizations_uk.dart @@ -3242,6 +3242,37 @@ class AppLocalizationsUk extends AppLocalizations { @override String get losLegendTerrain => 'Рельєф'; + @override + String get losBlockedSpotsTitle => 'Заблоковані місця'; + + @override + String get losBlockedSpotsHint => + 'Натисніть на заблоковане місце, щоб виділити його на карті.'; + + @override + String losBlockedSpotChip( + String distance, + String distanceUnit, + String obstruction, + String heightUnit, + ) { + return '$distance $distanceUnit • $obstruction $heightUnit'; + } + + @override + String get losSelectedObstructionTitle => 'Вибраний об\'єкт перешкоди'; + + @override + String losSelectedObstructionDetails( + String obstruction, + String heightUnit, + String distanceFromA, + String distanceUnit, + String distanceFromB, + ) { + return 'Blocked by $obstruction $heightUnit, $distanceFromA from A and $distanceFromB from B ($distanceUnit).'; + } + @override String get losFrequencyLabel => 'Частота'; diff --git a/lib/l10n/app_localizations_zh.dart b/lib/l10n/app_localizations_zh.dart index 6181e8af..4cef6064 100644 --- a/lib/l10n/app_localizations_zh.dart +++ b/lib/l10n/app_localizations_zh.dart @@ -3014,6 +3014,36 @@ class AppLocalizationsZh extends AppLocalizations { @override String get losLegendTerrain => '地形'; + @override + String get losBlockedSpotsTitle => '被占用区域'; + + @override + String get losBlockedSpotsHint => '点击地图上的某个被遮盖的区域,以突出显示该区域。'; + + @override + String losBlockedSpotChip( + String distance, + String distanceUnit, + String obstruction, + String heightUnit, + ) { + return '$distance $distanceUnit • $obstruction $heightUnit'; + } + + @override + String get losSelectedObstructionTitle => '选择性阻碍'; + + @override + String losSelectedObstructionDetails( + String obstruction, + String heightUnit, + String distanceFromA, + String distanceUnit, + String distanceFromB, + ) { + return 'Blocked by $obstruction $heightUnit, $distanceFromA from A and $distanceFromB from B ($distanceUnit).'; + } + @override String get losFrequencyLabel => '频率'; diff --git a/lib/l10n/app_nl.arb b/lib/l10n/app_nl.arb index fa3eb37b..fa8bc465 100644 --- a/lib/l10n/app_nl.arb +++ b/lib/l10n/app_nl.arb @@ -2066,5 +2066,45 @@ "room_guest": "Informatie over de server", "chat_sendMessage": "Verzend bericht", "repeater_guest": "Informatie over herhalingsapparatuur", - "settings_multiAck": "Meerdere bevestigingen" + "settings_multiAck": "Meerdere bevestigingen", + "@losBlockedSpotChip": { + "placeholders": { + "distance": { + "type": "String" + }, + "distanceUnit": { + "type": "String" + }, + "obstruction": { + "type": "String" + }, + "heightUnit": { + "type": "String" + } + } + }, + "@losSelectedObstructionDetails": { + "placeholders": { + "obstruction": { + "type": "String" + }, + "heightUnit": { + "type": "String" + }, + "distanceFromA": { + "type": "String" + }, + "distanceUnit": { + "type": "String" + }, + "distanceFromB": { + "type": "String" + } + } + }, + "losSelectedObstructionTitle": "Geselecteerde obstakel", + "losBlockedSpotsHint": "Tik op een geblokkeerd gebied om het op de kaart te markeren.", + "losBlockedSpotsTitle": "Geplande plaatsen", + "losBlockedSpotChip": "{distance} {distanceUnit} • {obstruction} {heightUnit}", + "losSelectedObstructionDetails": "Blocked by {obstruction} {heightUnit}, {distanceFromA} from A and {distanceFromB} from B ({distanceUnit})." } diff --git a/lib/l10n/app_pl.arb b/lib/l10n/app_pl.arb index 870be9e6..2c971bed 100644 --- a/lib/l10n/app_pl.arb +++ b/lib/l10n/app_pl.arb @@ -2104,5 +2104,45 @@ "repeater_guestTools": "Narzędzia dla gości", "repeater_guest": "Informacje dotyczące urządzenia powtarzającego", "room_guest": "Informacje o serwerze", - "settings_multiAck": "Wielokrotne potwierdzenia odbioru" + "settings_multiAck": "Wielokrotne potwierdzenia odbioru", + "@losBlockedSpotChip": { + "placeholders": { + "distance": { + "type": "String" + }, + "distanceUnit": { + "type": "String" + }, + "obstruction": { + "type": "String" + }, + "heightUnit": { + "type": "String" + } + } + }, + "@losSelectedObstructionDetails": { + "placeholders": { + "obstruction": { + "type": "String" + }, + "heightUnit": { + "type": "String" + }, + "distanceFromA": { + "type": "String" + }, + "distanceUnit": { + "type": "String" + }, + "distanceFromB": { + "type": "String" + } + } + }, + "losSelectedObstructionTitle": "Wybór przeszkody", + "losBlockedSpotsTitle": "Zablokowane miejsca", + "losBlockedSpotsHint": "Kliknij zablokowane miejsce, aby je zaznaczyć na mapie.", + "losBlockedSpotChip": "{distance} {distanceUnit} • {obstruction} {heightUnit}", + "losSelectedObstructionDetails": "Blocked by {obstruction} {heightUnit}, {distanceFromA} from A and {distanceFromB} from B ({distanceUnit})." } diff --git a/lib/l10n/app_pt.arb b/lib/l10n/app_pt.arb index 634cf1c0..2b8efabe 100644 --- a/lib/l10n/app_pt.arb +++ b/lib/l10n/app_pt.arb @@ -2066,5 +2066,45 @@ "room_guest": "Informações do Servidor", "chat_sendMessage": "Enviar mensagem", "repeater_guest": "Informações sobre repetidores", - "repeater_guestTools": "Ferramentas para hóspedes" + "repeater_guestTools": "Ferramentas para hóspedes", + "@losBlockedSpotChip": { + "placeholders": { + "distance": { + "type": "String" + }, + "distanceUnit": { + "type": "String" + }, + "obstruction": { + "type": "String" + }, + "heightUnit": { + "type": "String" + } + } + }, + "@losSelectedObstructionDetails": { + "placeholders": { + "obstruction": { + "type": "String" + }, + "heightUnit": { + "type": "String" + }, + "distanceFromA": { + "type": "String" + }, + "distanceUnit": { + "type": "String" + }, + "distanceFromB": { + "type": "String" + } + } + }, + "losBlockedSpotsTitle": "Locais ocupados", + "losBlockedSpotsHint": "Toque em um ponto bloqueado para destacá-lo no mapa.", + "losSelectedObstructionTitle": "Obstrução selecionada", + "losBlockedSpotChip": "{distance} {distanceUnit} • {obstruction} {heightUnit}", + "losSelectedObstructionDetails": "Blocked by {obstruction} {heightUnit}, {distanceFromA} from A and {distanceFromB} from B ({distanceUnit})." } diff --git a/lib/l10n/app_ru.arb b/lib/l10n/app_ru.arb index 1ad0f1b9..9ab27fec 100644 --- a/lib/l10n/app_ru.arb +++ b/lib/l10n/app_ru.arb @@ -1306,5 +1306,45 @@ "repeater_guest": "Информация о ретрансляторе", "room_guest": "Информация о сервере", "repeater_guestTools": "Инструменты для гостей", - "settings_multiAck": "Несколько подтверждений" + "settings_multiAck": "Несколько подтверждений", + "@losBlockedSpotChip": { + "placeholders": { + "distance": { + "type": "String" + }, + "distanceUnit": { + "type": "String" + }, + "obstruction": { + "type": "String" + }, + "heightUnit": { + "type": "String" + } + } + }, + "@losSelectedObstructionDetails": { + "placeholders": { + "obstruction": { + "type": "String" + }, + "heightUnit": { + "type": "String" + }, + "distanceFromA": { + "type": "String" + }, + "distanceUnit": { + "type": "String" + }, + "distanceFromB": { + "type": "String" + } + } + }, + "losBlockedSpotsHint": "Щелкните по заблокированной области, чтобы выделить ее на карте.", + "losBlockedSpotsTitle": "Зарезервированные места", + "losSelectedObstructionTitle": "Выбранный объект, препятствующий движению", + "losBlockedSpotChip": "{distance} {distanceUnit} • {obstruction} {heightUnit}", + "losSelectedObstructionDetails": "Blocked by {obstruction} {heightUnit}, {distanceFromA} from A and {distanceFromB} from B ({distanceUnit})." } diff --git a/lib/l10n/app_sk.arb b/lib/l10n/app_sk.arb index 9cd6072c..daddb249 100644 --- a/lib/l10n/app_sk.arb +++ b/lib/l10n/app_sk.arb @@ -2066,5 +2066,45 @@ "chat_sendMessage": "Odoslať správu", "repeater_guest": "Informácie o opakovači", "room_guest": "Informácie o serveri", - "repeater_guestTools": "Nástroje pre hostí" + "repeater_guestTools": "Nástroje pre hostí", + "@losBlockedSpotChip": { + "placeholders": { + "distance": { + "type": "String" + }, + "distanceUnit": { + "type": "String" + }, + "obstruction": { + "type": "String" + }, + "heightUnit": { + "type": "String" + } + } + }, + "@losSelectedObstructionDetails": { + "placeholders": { + "obstruction": { + "type": "String" + }, + "heightUnit": { + "type": "String" + }, + "distanceFromA": { + "type": "String" + }, + "distanceUnit": { + "type": "String" + }, + "distanceFromB": { + "type": "String" + } + } + }, + "losBlockedSpotsTitle": "Zablokované miesta", + "losBlockedSpotsHint": "Kliknite na zablokované miesto, aby ste ho zvýraznili na mape.", + "losSelectedObstructionTitle": "Vybraná prekážka", + "losBlockedSpotChip": "{distance} {distanceUnit} • {obstruction} {heightUnit}", + "losSelectedObstructionDetails": "Blocked by {obstruction} {heightUnit}, {distanceFromA} from A and {distanceFromB} from B ({distanceUnit})." } diff --git a/lib/l10n/app_sl.arb b/lib/l10n/app_sl.arb index cace8c6b..5969b918 100644 --- a/lib/l10n/app_sl.arb +++ b/lib/l10n/app_sl.arb @@ -2066,5 +2066,45 @@ "chat_sendMessage": "Pošlji sporočilo", "room_guest": "Informacije o strežniku", "repeater_guestTools": "Naložila za goste", - "settings_multiAck": "Več potrdil" + "settings_multiAck": "Več potrdil", + "@losBlockedSpotChip": { + "placeholders": { + "distance": { + "type": "String" + }, + "distanceUnit": { + "type": "String" + }, + "obstruction": { + "type": "String" + }, + "heightUnit": { + "type": "String" + } + } + }, + "@losSelectedObstructionDetails": { + "placeholders": { + "obstruction": { + "type": "String" + }, + "heightUnit": { + "type": "String" + }, + "distanceFromA": { + "type": "String" + }, + "distanceUnit": { + "type": "String" + }, + "distanceFromB": { + "type": "String" + } + } + }, + "losBlockedSpotsHint": "Dotaknite blokirano točko, da jo označite na zemljeplati.", + "losSelectedObstructionTitle": "Izbrano ovire", + "losBlockedSpotsTitle": "Zasedena parkirišča", + "losBlockedSpotChip": "{distance} {distanceUnit} • {obstruction} {heightUnit}", + "losSelectedObstructionDetails": "Blocked by {obstruction} {heightUnit}, {distanceFromA} from A and {distanceFromB} from B ({distanceUnit})." } diff --git a/lib/l10n/app_sv.arb b/lib/l10n/app_sv.arb index aaa6ea75..5ed2561a 100644 --- a/lib/l10n/app_sv.arb +++ b/lib/l10n/app_sv.arb @@ -2066,5 +2066,45 @@ "chat_sendMessage": "Skicka meddelande", "repeater_guestTools": "Gästverktyg", "room_guest": "Information om servern", - "settings_multiAck": "Flera bekräftelser" + "settings_multiAck": "Flera bekräftelser", + "@losBlockedSpotChip": { + "placeholders": { + "distance": { + "type": "String" + }, + "distanceUnit": { + "type": "String" + }, + "obstruction": { + "type": "String" + }, + "heightUnit": { + "type": "String" + } + } + }, + "@losSelectedObstructionDetails": { + "placeholders": { + "obstruction": { + "type": "String" + }, + "heightUnit": { + "type": "String" + }, + "distanceFromA": { + "type": "String" + }, + "distanceUnit": { + "type": "String" + }, + "distanceFromB": { + "type": "String" + } + } + }, + "losBlockedSpotChip": "{distance} {distanceUnit} • {obstruction} {heightUnit}", + "losBlockedSpotsTitle": "Reserverade platser", + "losSelectedObstructionTitle": "Vald hinder", + "losBlockedSpotsHint": "Klicka på en markerad plats för att framhäva den på kartan.", + "losSelectedObstructionDetails": "Blocked by {obstruction} {heightUnit}, {distanceFromA} from A and {distanceFromB} from B ({distanceUnit})." } diff --git a/lib/l10n/app_uk.arb b/lib/l10n/app_uk.arb index 489b8a2e..1bf2b584 100644 --- a/lib/l10n/app_uk.arb +++ b/lib/l10n/app_uk.arb @@ -2066,5 +2066,45 @@ "repeater_guest": "Інформація про ретранслятор", "room_guest": "Інформація про сервер кімнати", "chat_sendMessage": "Надіслати повідомлення", - "settings_multiAck": "Багато підтверджень" + "settings_multiAck": "Багато підтверджень", + "@losBlockedSpotChip": { + "placeholders": { + "distance": { + "type": "String" + }, + "distanceUnit": { + "type": "String" + }, + "obstruction": { + "type": "String" + }, + "heightUnit": { + "type": "String" + } + } + }, + "@losSelectedObstructionDetails": { + "placeholders": { + "obstruction": { + "type": "String" + }, + "heightUnit": { + "type": "String" + }, + "distanceFromA": { + "type": "String" + }, + "distanceUnit": { + "type": "String" + }, + "distanceFromB": { + "type": "String" + } + } + }, + "losBlockedSpotsHint": "Натисніть на заблоковане місце, щоб виділити його на карті.", + "losBlockedSpotsTitle": "Заблоковані місця", + "losSelectedObstructionTitle": "Вибраний об'єкт перешкоди", + "losBlockedSpotChip": "{distance} {distanceUnit} • {obstruction} {heightUnit}", + "losSelectedObstructionDetails": "Blocked by {obstruction} {heightUnit}, {distanceFromA} from A and {distanceFromB} from B ({distanceUnit})." } diff --git a/lib/l10n/app_zh.arb b/lib/l10n/app_zh.arb index 9d6a8bfd..ee8285c0 100644 --- a/lib/l10n/app_zh.arb +++ b/lib/l10n/app_zh.arb @@ -2071,5 +2071,45 @@ "repeater_guestTools": "访客工具", "repeater_guest": "重复器信息", "chat_sendMessage": "发送消息", - "room_guest": "服务器信息" + "room_guest": "服务器信息", + "@losBlockedSpotChip": { + "placeholders": { + "distance": { + "type": "String" + }, + "distanceUnit": { + "type": "String" + }, + "obstruction": { + "type": "String" + }, + "heightUnit": { + "type": "String" + } + } + }, + "@losSelectedObstructionDetails": { + "placeholders": { + "obstruction": { + "type": "String" + }, + "heightUnit": { + "type": "String" + }, + "distanceFromA": { + "type": "String" + }, + "distanceUnit": { + "type": "String" + }, + "distanceFromB": { + "type": "String" + } + } + }, + "losBlockedSpotsTitle": "被占用区域", + "losBlockedSpotsHint": "点击地图上的某个被遮盖的区域,以突出显示该区域。", + "losSelectedObstructionTitle": "选择性阻碍", + "losBlockedSpotChip": "{distance} {distanceUnit} • {obstruction} {heightUnit}", + "losSelectedObstructionDetails": "Blocked by {obstruction} {heightUnit}, {distanceFromA} from A and {distanceFromB} from B ({distanceUnit})." } diff --git a/lib/screens/line_of_sight_map_screen.dart b/lib/screens/line_of_sight_map_screen.dart index ec8a391f..e88f4b9e 100644 --- a/lib/screens/line_of_sight_map_screen.dart +++ b/lib/screens/line_of_sight_map_screen.dart @@ -62,6 +62,7 @@ class _LineOfSightMapScreenState extends State { bool _loading = false; String? _error; LineOfSightPathResult? _result; + LineOfSightObstruction? _selectedObstruction; LineOfSightEndpoint? _start; LineOfSightEndpoint? _end; final List _customEndpoints = []; @@ -111,6 +112,7 @@ class _LineOfSightMapScreenState extends State { if (start == null || end == null) { setState(() { _result = null; + _selectedObstruction = null; _error = _errorSelectStartEnd; }); return; @@ -142,6 +144,7 @@ class _LineOfSightMapScreenState extends State { } setState(() { _result = result; + _selectedObstruction = _defaultObstructionFor(result); }); } catch (e) { if (!mounted) return; @@ -156,6 +159,7 @@ class _LineOfSightMapScreenState extends State { } setState(() { _result = null; + _selectedObstruction = null; _error = context.l10n.losRunFailed(e.toString()); }); } finally { @@ -184,6 +188,7 @@ class _LineOfSightMapScreenState extends State { void _selectFromMap(LineOfSightEndpoint endpoint) { setState(() { _result = null; + _selectedObstruction = null; _error = null; if (_start == null || (_start != null && _end != null)) { _start = endpoint; @@ -241,6 +246,7 @@ class _LineOfSightMapScreenState extends State { _start = null; _end = null; _result = null; + _selectedObstruction = null; _error = _errorSelectStartEnd; }); } @@ -251,6 +257,7 @@ class _LineOfSightMapScreenState extends State { if (identical(_start, endpoint)) _start = null; if (identical(_end, endpoint)) _end = null; _result = null; + _selectedObstruction = null; }); } @@ -377,7 +384,9 @@ class _LineOfSightMapScreenState extends State { ), if (_result != null && _result!.segments.isNotEmpty) PolylineLayer(polylines: _buildSegmentPolylines(_result!)), - MarkerLayer(markers: _buildMarkers(endpoints)), + MarkerLayer( + markers: _buildMarkers(endpoints, _primaryObstructions()), + ), ], ), if (_showHud) @@ -445,6 +454,8 @@ class _LineOfSightMapScreenState extends State { ); final displayFrequencyMHz = segment?.frequencyMHz ?? reportedFrequencyMHz; final kFactorUsed = segment?.usedKFactor; + final obstructions = + segment?.obstructions ?? const []; final endpoints = _visibleEndpoints(); final distanceUnit = isImperial ? 'mi' : 'km'; final heightUnit = isImperial ? 'ft' : 'm'; @@ -463,31 +474,7 @@ class _LineOfSightMapScreenState extends State { crossAxisAlignment: CrossAxisAlignment.start, children: [ if (segment != null) - SizedBox( - height: 160, - width: double.infinity, - child: CustomPaint( - painter: _LosProfilePainter( - samples: segment.samples, - distanceUnit: distanceUnit, - heightUnit: heightUnit, - badgeTextStyle: - Theme.of(context).textTheme.labelSmall?.copyWith( - color: Colors.white70, - fontSize: 10, - fontWeight: FontWeight.w600, - ) ?? - const TextStyle( - color: Colors.white70, - fontSize: 10, - fontWeight: FontWeight.w600, - ), - terrainLabel: context.l10n.losLegendTerrain, - losBeamLabel: context.l10n.losLegendLosBeam, - radioHorizonLabel: context.l10n.losLegendRadioHorizon, - ), - ), - ) + _buildProfileView(segment, distanceUnit, heightUnit, isImperial) else SizedBox( height: 44, @@ -519,6 +506,96 @@ class _LineOfSightMapScreenState extends State { fontWeight: FontWeight.w600, ), ), + if (obstructions.isNotEmpty) ...[ + const SizedBox(height: 8), + Text( + context.l10n.losBlockedSpotsTitle, + style: const TextStyle( + fontSize: 12, + fontWeight: FontWeight.w700, + ), + ), + const SizedBox(height: 4), + Text( + context.l10n.losBlockedSpotsHint, + style: TextStyle(fontSize: 11, color: Colors.grey[700]), + ), + const SizedBox(height: 6), + Wrap( + spacing: 6, + runSpacing: 6, + children: [ + for (final obstruction in obstructions) + ChoiceChip( + label: Text( + _obstructionChipLabel(obstruction, isImperial), + style: const TextStyle(fontSize: 11), + ), + selected: + _selectedObstruction?.sampleIndex == + obstruction.sampleIndex, + onSelected: (_) => _selectObstruction(obstruction), + ), + ], + ), + if (_selectedObstruction != null) ...[ + const SizedBox(height: 8), + DecoratedBox( + decoration: BoxDecoration( + color: Colors.orange.withValues(alpha: 0.1), + borderRadius: BorderRadius.circular(10), + border: Border.all( + color: Colors.deepOrangeAccent.withValues(alpha: 0.45), + ), + ), + child: Padding( + padding: const EdgeInsets.all(10), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + context.l10n.losSelectedObstructionTitle, + style: const TextStyle( + fontSize: 12, + fontWeight: FontWeight.w700, + ), + ), + const SizedBox(height: 4), + Text( + context.l10n.losSelectedObstructionDetails( + _formatHeightValue( + _selectedObstruction!.obstructionMeters, + isImperial, + ), + heightUnit, + _formatDistanceValue( + _selectedObstruction!.distanceMeters, + isImperial, + ), + distanceUnit, + _formatDistanceValue( + segment!.totalDistanceMeters - + _selectedObstruction!.distanceMeters, + isImperial, + ), + ), + style: const TextStyle(fontSize: 11), + ), + const SizedBox(height: 4), + Text( + '${_selectedObstruction!.point.latitude.toStringAsFixed(5)}, ' + '${_selectedObstruction!.point.longitude.toStringAsFixed(5)}', + style: TextStyle( + fontSize: 11, + color: Colors.grey[700], + ), + ), + ], + ), + ), + ), + ], + ], const SizedBox(height: 4), if (displayFrequencyMHz != null) Padding( @@ -605,6 +682,7 @@ class _LineOfSightMapScreenState extends State { _showDisplayNodes = value; _sanitizeSelection(); _result = null; + _selectedObstruction = null; }); }, ), @@ -655,6 +733,7 @@ class _LineOfSightMapScreenState extends State { setState(() { _start = value; _result = null; + _selectedObstruction = null; }); if (_start != null && _end != null) { _runLos(); @@ -670,6 +749,7 @@ class _LineOfSightMapScreenState extends State { setState(() { _end = value; _result = null; + _selectedObstruction = null; }); if (_start != null && _end != null) { _runLos(); @@ -769,6 +849,179 @@ class _LineOfSightMapScreenState extends State { return _result!.segments.first.result; } + List _primaryObstructions() { + return _primarySegmentResult()?.obstructions ?? const []; + } + + LineOfSightObstruction? _defaultObstructionFor( + LineOfSightPathResult? result, + ) { + if (result == null || result.segments.isEmpty) return null; + final obstructions = result.segments.first.result.obstructions; + if (obstructions.isEmpty) return null; + return obstructions.reduce( + (current, next) => + next.obstructionMeters > current.obstructionMeters ? next : current, + ); + } + + void _selectObstruction(LineOfSightObstruction obstruction) { + setState(() { + _selectedObstruction = obstruction; + }); + } + + String _formatDistanceValue(double meters, bool isImperial) { + final value = isImperial ? (meters / 1000.0) * _kmToMiles : meters / 1000.0; + return value.toStringAsFixed(2); + } + + String _formatHeightValue(double meters, bool isImperial) { + final value = isImperial ? meters * _metersToFeet : meters; + return value.toStringAsFixed(1); + } + + String _obstructionChipLabel( + LineOfSightObstruction obstruction, + bool isImperial, + ) { + final distanceUnit = isImperial ? 'mi' : 'km'; + final heightUnit = isImperial ? 'ft' : 'm'; + return context.l10n.losBlockedSpotChip( + _formatDistanceValue(obstruction.distanceMeters, isImperial), + distanceUnit, + _formatHeightValue(obstruction.obstructionMeters, isImperial), + heightUnit, + ); + } + + Widget _buildProfileView( + LineOfSightResult segment, + String distanceUnit, + String heightUnit, + bool isImperial, + ) { + if (segment.samples.length < 2) { + return SizedBox( + height: 160, + width: double.infinity, + child: CustomPaint( + painter: _LosProfilePainter( + samples: segment.samples, + distanceUnit: distanceUnit, + heightUnit: heightUnit, + badgeTextStyle: + Theme.of(context).textTheme.labelSmall?.copyWith( + color: Colors.white70, + fontSize: 10, + fontWeight: FontWeight.w600, + ) ?? + const TextStyle( + color: Colors.white70, + fontSize: 10, + fontWeight: FontWeight.w600, + ), + terrainLabel: context.l10n.losLegendTerrain, + losBeamLabel: context.l10n.losLegendLosBeam, + radioHorizonLabel: context.l10n.losLegendRadioHorizon, + selectedSampleIndex: _selectedObstruction?.sampleIndex, + ), + ), + ); + } + return SizedBox( + height: 160, + width: double.infinity, + child: LayoutBuilder( + builder: (context, constraints) { + final size = Size(constraints.maxWidth, 160); + final geometry = _LosProfileGeometry( + samples: segment.samples, + size: size, + ); + return Stack( + clipBehavior: Clip.none, + children: [ + Positioned.fill( + child: CustomPaint( + painter: _LosProfilePainter( + samples: segment.samples, + distanceUnit: distanceUnit, + heightUnit: heightUnit, + badgeTextStyle: + Theme.of(context).textTheme.labelSmall?.copyWith( + color: Colors.white70, + fontSize: 10, + fontWeight: FontWeight.w600, + ) ?? + const TextStyle( + color: Colors.white70, + fontSize: 10, + fontWeight: FontWeight.w600, + ), + terrainLabel: context.l10n.losLegendTerrain, + losBeamLabel: context.l10n.losLegendLosBeam, + radioHorizonLabel: context.l10n.losLegendRadioHorizon, + selectedSampleIndex: _selectedObstruction?.sampleIndex, + ), + ), + ), + for (final obstruction in segment.obstructions) + Builder( + builder: (context) { + final sample = segment.samples[obstruction.sampleIndex]; + final position = geometry.mapPoint( + sample.distanceMeters, + sample.terrainMeters, + ); + final isSelected = + _selectedObstruction?.sampleIndex == + obstruction.sampleIndex; + final markerSize = isSelected ? 18.0 : 14.0; + final left = (position.dx - markerSize / 2) + .clamp(0.0, math.max(0.0, size.width - markerSize)) + .toDouble(); + final top = (position.dy - markerSize / 2) + .clamp(0.0, math.max(0.0, size.height - markerSize)) + .toDouble(); + return Positioned( + left: left, + top: top, + child: Tooltip( + message: _obstructionChipLabel(obstruction, isImperial), + child: GestureDetector( + onTap: () => _selectObstruction(obstruction), + child: Container( + width: markerSize, + height: markerSize, + decoration: BoxDecoration( + color: isSelected + ? Colors.amberAccent + : Colors.deepOrangeAccent, + shape: BoxShape.circle, + border: Border.all( + color: isSelected + ? Colors.white + : Colors.black87, + width: isSelected ? 2 : 1.5, + ), + boxShadow: const [ + BoxShadow(color: Colors.black45, blurRadius: 4), + ], + ), + ), + ), + ), + ); + }, + ), + ], + ); + }, + ), + ); + } + String _profileStats(LineOfSightResult result, bool isImperial) { final distance = isImperial ? (result.totalDistanceMeters / 1000.0) * _kmToMiles @@ -820,8 +1073,51 @@ class _LineOfSightMapScreenState extends State { return polylines; } - List _buildMarkers(List endpoints) { + List _buildMarkers( + List endpoints, + List obstructions, + ) { return [ + for (final obstruction in obstructions) + Marker( + point: obstruction.point, + width: 52, + height: 52, + child: GestureDetector( + onTap: () => _selectObstruction(obstruction), + child: Center( + child: Container( + width: + _selectedObstruction?.sampleIndex == obstruction.sampleIndex + ? 36 + : 24, + height: + _selectedObstruction?.sampleIndex == obstruction.sampleIndex + ? 36 + : 24, + decoration: BoxDecoration( + shape: BoxShape.circle, + color: Colors.transparent, + border: Border.all( + color: + _selectedObstruction?.sampleIndex == + obstruction.sampleIndex + ? Colors.amberAccent + : Colors.deepOrangeAccent, + width: + _selectedObstruction?.sampleIndex == + obstruction.sampleIndex + ? 4 + : 3, + ), + boxShadow: const [ + BoxShadow(color: Colors.black26, blurRadius: 6), + ], + ), + ), + ), + ), + ), for (final endpoint in endpoints) Marker( point: endpoint.point, @@ -1010,6 +1306,51 @@ class _LineOfSightMapScreenState extends State { } } +class _LosProfileGeometry { + static const horizontalPadding = 12.0; + static const verticalPadding = 12.0; + + final List samples; + final Size size; + late final double minY = samples + .map( + (s) => math.min( + math.min(s.terrainMeters, s.lineHeightMeters), + s.refractedHeightMeters, + ), + ) + .reduce(math.min); + late final double maxY = samples + .map( + (s) => math.max( + math.max(s.terrainMeters, s.lineHeightMeters), + s.refractedHeightMeters, + ), + ) + .reduce(math.max); + late final double ySpan = math.max(1.0, maxY - minY); + late final double maxDist = math.max(1.0, samples.last.distanceMeters); + late final double chartWidth = math.max( + 1.0, + size.width - horizontalPadding * 2, + ); + late final double chartHeight = math.max( + 1.0, + size.height - verticalPadding * 2, + ); + + _LosProfileGeometry({required this.samples, required this.size}); + + Offset mapPoint(double distanceMeters, double elevationMeters) { + final px = horizontalPadding + (distanceMeters / maxDist) * chartWidth; + final py = + size.height - + verticalPadding - + ((elevationMeters - minY) / ySpan) * chartHeight; + return Offset(px, py); + } +} + class _LosProfilePainter extends CustomPainter { final List samples; final String distanceUnit; @@ -1018,6 +1359,7 @@ class _LosProfilePainter extends CustomPainter { final String terrainLabel; final String losBeamLabel; final String radioHorizonLabel; + final int? selectedSampleIndex; const _LosProfilePainter({ required this.samples, @@ -1027,6 +1369,7 @@ class _LosProfilePainter extends CustomPainter { required this.terrainLabel, required this.losBeamLabel, required this.radioHorizonLabel, + this.selectedSampleIndex, }); @override @@ -1212,6 +1555,32 @@ class _LosProfilePainter extends CustomPainter { ..color = horizonFillColor ..style = PaintingStyle.fill, ); + + if (selectedSampleIndex != null && + selectedSampleIndex! >= 0 && + selectedSampleIndex! < samples.length) { + final selectedSample = samples[selectedSampleIndex!]; + final selectedPoint = mapPoint( + selectedSample.distanceMeters, + selectedSample.terrainMeters, + ); + canvas.drawLine( + Offset(selectedPoint.dx, verticalPadding), + Offset(selectedPoint.dx, size.height - verticalPadding), + Paint() + ..color = Colors.amberAccent.withValues(alpha: 0.7) + ..strokeWidth = 1.5, + ); + canvas.drawCircle(selectedPoint, 7, Paint()..color = Colors.amberAccent); + canvas.drawCircle( + selectedPoint, + 8.5, + Paint() + ..color = Colors.white + ..style = PaintingStyle.stroke + ..strokeWidth = 1.5, + ); + } } @override @@ -1222,7 +1591,8 @@ class _LosProfilePainter extends CustomPainter { oldDelegate.badgeTextStyle != badgeTextStyle || oldDelegate.terrainLabel != terrainLabel || oldDelegate.losBeamLabel != losBeamLabel || - oldDelegate.radioHorizonLabel != radioHorizonLabel; + oldDelegate.radioHorizonLabel != radioHorizonLabel || + oldDelegate.selectedSampleIndex != selectedSampleIndex; } void _drawUnitBadge(Canvas canvas, Size size) { diff --git a/lib/services/line_of_sight_service.dart b/lib/services/line_of_sight_service.dart index 7f056c81..e8d149ad 100644 --- a/lib/services/line_of_sight_service.dart +++ b/lib/services/line_of_sight_service.dart @@ -24,6 +24,26 @@ class LineOfSightSample { }); } +class LineOfSightObstruction { + final int sampleIndex; + final LatLng point; + final double distanceMeters; + final double clearanceMeters; + final double obstructionMeters; + final double terrainMeters; + final double lineHeightMeters; + + const LineOfSightObstruction({ + required this.sampleIndex, + required this.point, + required this.distanceMeters, + required this.clearanceMeters, + required this.obstructionMeters, + required this.terrainMeters, + required this.lineHeightMeters, + }); +} + class LineOfSightResult { final bool hasData; final bool isClear; @@ -31,6 +51,7 @@ class LineOfSightResult { final double maxObstructionMeters; final double? firstObstructionDistanceMeters; final List samples; + final List obstructions; final String? errorMessage; final double usedKFactor; final double? frequencyMHz; @@ -42,6 +63,7 @@ class LineOfSightResult { required this.maxObstructionMeters, required this.firstObstructionDistanceMeters, required this.samples, + required this.obstructions, required this.usedKFactor, this.frequencyMHz, this.errorMessage, @@ -56,7 +78,8 @@ class LineOfSightResult { isClear = false, maxObstructionMeters = 0, firstObstructionDistanceMeters = null, - samples = const []; + samples = const [], + obstructions = const []; } class LineOfSightPathSegment { @@ -191,6 +214,7 @@ class LineOfSightService { maxObstructionMeters: 0, firstObstructionDistanceMeters: null, samples: const [], + obstructions: const [], usedKFactor: kFactor, frequencyMHz: frequencyMHz, ); @@ -249,7 +273,9 @@ class LineOfSightService { var maxObstructionMeters = 0.0; double? firstObstructionDistanceMeters; final samples = []; + final obstructions = []; var isClear = true; + LineOfSightObstruction? clusterWorstObstruction; for (int i = 0; i < points.length; i++) { final fraction = points.length == 1 ? 0.0 : i / (points.length - 1); @@ -274,6 +300,23 @@ class LineOfSightService { maxObstructionMeters = obstruction; } firstObstructionDistanceMeters ??= distanceFromStart; + final candidate = LineOfSightObstruction( + sampleIndex: i, + point: points[i], + distanceMeters: distanceFromStart, + clearanceMeters: clearance, + obstructionMeters: obstruction, + terrainMeters: terrainHeight, + lineHeightMeters: lineHeight, + ); + if (clusterWorstObstruction == null || + candidate.obstructionMeters > + clusterWorstObstruction.obstructionMeters) { + clusterWorstObstruction = candidate; + } + } else if (clusterWorstObstruction != null) { + obstructions.add(clusterWorstObstruction); + clusterWorstObstruction = null; } samples.add( @@ -286,6 +329,9 @@ class LineOfSightService { ), ); } + if (clusterWorstObstruction != null) { + obstructions.add(clusterWorstObstruction); + } return LineOfSightResult( hasData: true, @@ -294,6 +340,7 @@ class LineOfSightService { maxObstructionMeters: maxObstructionMeters, firstObstructionDistanceMeters: firstObstructionDistanceMeters, samples: samples, + obstructions: obstructions, usedKFactor: kFactor, frequencyMHz: frequencyMHz, ); diff --git a/test/services/line_of_sight_service_test.dart b/test/services/line_of_sight_service_test.dart index 267a70ba..bd649f2a 100644 --- a/test/services/line_of_sight_service_test.dart +++ b/test/services/line_of_sight_service_test.dart @@ -23,6 +23,7 @@ void main() { expect(result.isClear, isTrue); expect(result.maxObstructionMeters, equals(0)); expect(result.firstObstructionDistanceMeters, isNull); + expect(result.obstructions, isEmpty); }); test( @@ -44,9 +45,32 @@ void main() { expect(result.isClear, isFalse); expect(result.maxObstructionMeters, greaterThan(0)); expect(result.firstObstructionDistanceMeters, isNotNull); + expect(result.obstructions, hasLength(1)); + expect(result.obstructions.single.sampleIndex, equals(10)); + expect(result.obstructions.single.point, equals(points[10])); }, ); + test('computeFromElevations groups contiguous blocked samples', () { + final points = makePoints(21); + final elevations = List.filled(points.length, 100); + elevations[9] = 220; + elevations[10] = 320; + elevations[11] = 240; + + final result = LineOfSightService.computeFromElevations( + points: points, + elevations: elevations, + startAntennaHeightMeters: 1.5, + endAntennaHeightMeters: 1.5, + kFactor: 4.0 / 3.0, + ); + + expect(result.obstructions, hasLength(1)); + expect(result.obstructions.single.sampleIndex, equals(10)); + expect(result.obstructions.single.obstructionMeters, greaterThan(0)); + }); + test('analyzePath summarizes clear and blocked segments', () async { final service = LineOfSightService( elevationDataSource: (points) async { From 46683e0ec297cc63459bd168577de3aafa5307a5 Mon Sep 17 00:00:00 2001 From: just_stuff_tm <133525672+just-stuff-tm@users.noreply.github.com> Date: Sat, 25 Apr 2026 09:04:00 -0400 Subject: [PATCH 2/4] Delete .contextstream/config.json --- .contextstream/config.json | 7 ------- 1 file changed, 7 deletions(-) delete mode 100644 .contextstream/config.json diff --git a/.contextstream/config.json b/.contextstream/config.json deleted file mode 100644 index 412e2fb3..00000000 --- a/.contextstream/config.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "workspace_id": "56872cc9-7375-4423-92d5-6cdebf6564dc", - "workspace_name": ".contextstream-global", - "project_id": "5b1fe101-ea6b-4fe3-9de5-c87722c38084", - "project_name": "meshcore-open", - "associated_at": "2026-04-25T12:13:54.086Z" -} \ No newline at end of file From 7f353490cf720b082ca386b62fe28a39c69b6096 Mon Sep 17 00:00:00 2001 From: just-stuff-tm Date: Sat, 25 Apr 2026 09:07:28 -0400 Subject: [PATCH 3/4] contextstream/ is added to .gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 88295e7c..fa4d28d6 100644 --- a/.gitignore +++ b/.gitignore @@ -87,6 +87,7 @@ keystore.properties # IDE .vscode/launch.json .vscode/settings.json +.contextstream/ # Cloudflare Wrangler .wrangler From fcf10b4a7317d638337d8f1d6004e27ff038ecae Mon Sep 17 00:00:00 2001 From: just-stuff-tm Date: Sat, 25 Apr 2026 09:11:49 -0400 Subject: [PATCH 4/4] added strings translategemma didnt translate to proper locallization --- untranslated.json | 39 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 38 insertions(+), 1 deletion(-) diff --git a/untranslated.json b/untranslated.json index 9e26dfee..6c2e251d 100644 --- a/untranslated.json +++ b/untranslated.json @@ -1 +1,38 @@ -{} \ No newline at end of file +{ + "bg": [ + "losSelectedObstructionDetails" + ], + "it": [ + "losSelectedObstructionDetails" + ], + "ko": [ + "losSelectedObstructionDetails" + ], + "nl": [ + "losSelectedObstructionDetails" + ], + "pl": [ + "losSelectedObstructionDetails" + ], + "pt": [ + "losSelectedObstructionDetails" + ], + "ru": [ + "losSelectedObstructionDetails" + ], + "sk": [ + "losSelectedObstructionDetails" + ], + "sl": [ + "losSelectedObstructionDetails" + ], + "sv": [ + "losSelectedObstructionDetails" + ], + "uk": [ + "losSelectedObstructionDetails" + ], + "zh": [ + "losSelectedObstructionDetails" + ] +}