From 7db3a127230391cf21b1455f8e170f3d9121ea84 Mon Sep 17 00:00:00 2001 From: ericz Date: Sun, 26 Apr 2026 00:13:26 +0200 Subject: [PATCH] squashed commit for: deduplicate markers, allow for updates on position with same label with drawing line, get marker back after deletion in map through tabbing on icon in poi-message. --- lib/l10n/app_bg.arb | 1 + lib/l10n/app_de.arb | 1 + lib/l10n/app_en.arb | 1 + lib/l10n/app_es.arb | 1 + lib/l10n/app_fr.arb | 1 + lib/l10n/app_hu.arb | 1 + lib/l10n/app_it.arb | 1 + lib/l10n/app_ja.arb | 1 + lib/l10n/app_ko.arb | 1 + lib/l10n/app_localizations.dart | 6 ++ lib/l10n/app_localizations_bg.dart | 3 + lib/l10n/app_localizations_de.dart | 3 + lib/l10n/app_localizations_en.dart | 3 + lib/l10n/app_localizations_es.dart | 3 + lib/l10n/app_localizations_fr.dart | 3 + lib/l10n/app_localizations_hu.dart | 3 + lib/l10n/app_localizations_it.dart | 3 + lib/l10n/app_localizations_ja.dart | 3 + lib/l10n/app_localizations_ko.dart | 3 + lib/l10n/app_localizations_nl.dart | 3 + lib/l10n/app_localizations_pl.dart | 3 + lib/l10n/app_localizations_pt.dart | 3 + lib/l10n/app_localizations_ru.dart | 3 + lib/l10n/app_localizations_sk.dart | 3 + lib/l10n/app_localizations_sl.dart | 3 + lib/l10n/app_localizations_sv.dart | 3 + lib/l10n/app_localizations_uk.dart | 3 + lib/l10n/app_localizations_zh.dart | 3 + lib/l10n/app_nl.arb | 1 + lib/l10n/app_pl.arb | 1 + lib/l10n/app_pt.arb | 1 + lib/l10n/app_ru.arb | 1 + lib/l10n/app_sk.arb | 1 + lib/l10n/app_sl.arb | 1 + lib/l10n/app_sv.arb | 1 + lib/l10n/app_uk.arb | 1 + lib/l10n/app_zh.arb | 1 + lib/screens/channel_chat_screen.dart | 27 ++++- lib/screens/chat_screen.dart | 32 +++++- lib/screens/map_screen.dart | 152 +++++++++++++++++++++++---- 40 files changed, 259 insertions(+), 30 deletions(-) diff --git a/lib/l10n/app_bg.arb b/lib/l10n/app_bg.arb index 7260c253..c4af0584 100644 --- a/lib/l10n/app_bg.arb +++ b/lib/l10n/app_bg.arb @@ -682,6 +682,7 @@ "map_showSharedMarkers": "Покажи споделени маркери", "map_lastSeenTime": "Последна видяна дата", "map_sharedPin": "Споделено копие", + "map_sharedAt": "Споделено", "map_joinRoom": "Присъедини се към стаята", "map_manageRepeater": "Управление на Повтарящ се Елемент", "mapCache_title": "Кеш на офлайн карти", diff --git a/lib/l10n/app_de.arb b/lib/l10n/app_de.arb index bf8ad2eb..7d9edd58 100644 --- a/lib/l10n/app_de.arb +++ b/lib/l10n/app_de.arb @@ -682,6 +682,7 @@ "map_showSharedMarkers": "Zeige gemeinsam genutzte Marker", "map_lastSeenTime": "Letzte Sichtung", "map_sharedPin": "Gemeinsames Passwort", + "map_sharedAt": "Geteilt", "map_joinRoom": "Beitreten Sie dem Raum", "map_manageRepeater": "Repeater verwalten", "mapCache_title": "Offline-Karten-Cache", diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 606410cb..9fd22e91 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -896,6 +896,7 @@ "map_guessedLocation": "Guessed location", "map_lastSeenTime": "Last Seen Time", "map_sharedPin": "Shared pin", + "map_sharedAt": "Shared", "map_joinRoom": "Join Room", "map_manageRepeater": "Manage Repeater", "map_tapToAdd": "Tap on nodes to add them to the path.", diff --git a/lib/l10n/app_es.arb b/lib/l10n/app_es.arb index b0585680..859b13b8 100644 --- a/lib/l10n/app_es.arb +++ b/lib/l10n/app_es.arb @@ -682,6 +682,7 @@ "map_showSharedMarkers": "Mostrar marcadores compartidos", "map_lastSeenTime": "Última vez que se vio", "map_sharedPin": "Pin compartido", + "map_sharedAt": "Compartido", "map_joinRoom": "Únete a la sala", "map_manageRepeater": "Gestionar Repetidor", "mapCache_title": "Caché de Mapa Offline", diff --git a/lib/l10n/app_fr.arb b/lib/l10n/app_fr.arb index fa375a40..9fc51c81 100644 --- a/lib/l10n/app_fr.arb +++ b/lib/l10n/app_fr.arb @@ -682,6 +682,7 @@ "map_showSharedMarkers": "Afficher les marqueurs partagés", "map_lastSeenTime": "Dernière fois vu", "map_sharedPin": "Clé partagée", + "map_sharedAt": "Partagé", "map_joinRoom": "Rejoindre le room server", "map_manageRepeater": "Gérer le répéteur", "mapCache_title": "Cache de Carte Hors Ligne", diff --git a/lib/l10n/app_hu.arb b/lib/l10n/app_hu.arb index ae6acfb2..de00ac49 100644 --- a/lib/l10n/app_hu.arb +++ b/lib/l10n/app_hu.arb @@ -861,6 +861,7 @@ "map_guessedLocation": "Tippolt hely", "map_lastSeenTime": "Utoljára megjelent idő", "map_sharedPin": "Gemeinsames PIN-kód", + "map_sharedAt": "Megosztva", "map_joinRoom": "Csatlakozás a szobához", "map_manageRepeater": "Ellenőriző eszköz kezelése", "map_tapToAdd": "Nyomj meg a csomópontokhoz, hogy hozzáadd őket az útvonalhoz.", diff --git a/lib/l10n/app_it.arb b/lib/l10n/app_it.arb index 35001e61..0ea9ab03 100644 --- a/lib/l10n/app_it.arb +++ b/lib/l10n/app_it.arb @@ -682,6 +682,7 @@ "map_showSharedMarkers": "Mostra i segnaposto condivisi", "map_lastSeenTime": "Ultimo Tempo di Visualizzazione", "map_sharedPin": "Condividi PIN", + "map_sharedAt": "Condiviso", "map_joinRoom": "Unisciti alla stanza", "map_manageRepeater": "Gestisci Ripetitore", "mapCache_title": "Cache Mappa Offline", diff --git a/lib/l10n/app_ja.arb b/lib/l10n/app_ja.arb index 7ac449db..517ef5f3 100644 --- a/lib/l10n/app_ja.arb +++ b/lib/l10n/app_ja.arb @@ -861,6 +861,7 @@ "map_guessedLocation": "推測された場所", "map_lastSeenTime": "最後に確認された時間", "map_sharedPin": "共有パスワード", + "map_sharedAt": "共有済み", "map_joinRoom": "部屋に参加する", "map_manageRepeater": "リピーターの管理", "map_tapToAdd": "ノードをクリックして、パスに追加します。", diff --git a/lib/l10n/app_ko.arb b/lib/l10n/app_ko.arb index 54073db7..81b37f78 100644 --- a/lib/l10n/app_ko.arb +++ b/lib/l10n/app_ko.arb @@ -861,6 +861,7 @@ "map_guessedLocation": "추측된 위치", "map_lastSeenTime": "마지막으로 확인된 시간", "map_sharedPin": "공유 비밀번호", + "map_sharedAt": "공유됨", "map_joinRoom": "방에 참여", "map_manageRepeater": "리피터 관리", "map_tapToAdd": "노드에 클릭하여 경로에 추가합니다.", diff --git a/lib/l10n/app_localizations.dart b/lib/l10n/app_localizations.dart index 09075570..b8f35eab 100644 --- a/lib/l10n/app_localizations.dart +++ b/lib/l10n/app_localizations.dart @@ -3130,6 +3130,12 @@ abstract class AppLocalizations { /// **'Shared pin'** String get map_sharedPin; + /// No description provided for @map_sharedAt. + /// + /// In en, this message translates to: + /// **'Shared'** + String get map_sharedAt; + /// No description provided for @map_joinRoom. /// /// In en, this message translates to: diff --git a/lib/l10n/app_localizations_bg.dart b/lib/l10n/app_localizations_bg.dart index 01973dff..1ac0074d 100644 --- a/lib/l10n/app_localizations_bg.dart +++ b/lib/l10n/app_localizations_bg.dart @@ -1724,6 +1724,9 @@ class AppLocalizationsBg extends AppLocalizations { @override String get map_sharedPin => 'Споделено копие'; + @override + String get map_sharedAt => 'Споделено'; + @override String get map_joinRoom => 'Присъедини се към стаята'; diff --git a/lib/l10n/app_localizations_de.dart b/lib/l10n/app_localizations_de.dart index 60cd3252..59542752 100644 --- a/lib/l10n/app_localizations_de.dart +++ b/lib/l10n/app_localizations_de.dart @@ -1721,6 +1721,9 @@ class AppLocalizationsDe extends AppLocalizations { @override String get map_sharedPin => 'Gemeinsames Passwort'; + @override + String get map_sharedAt => 'Geteilt'; + @override String get map_joinRoom => 'Beitreten Sie dem Raum'; diff --git a/lib/l10n/app_localizations_en.dart b/lib/l10n/app_localizations_en.dart index 353e5bcb..6611bd0c 100644 --- a/lib/l10n/app_localizations_en.dart +++ b/lib/l10n/app_localizations_en.dart @@ -1690,6 +1690,9 @@ class AppLocalizationsEn extends AppLocalizations { @override String get map_sharedPin => 'Shared pin'; + @override + String get map_sharedAt => 'Shared'; + @override String get map_joinRoom => 'Join Room'; diff --git a/lib/l10n/app_localizations_es.dart b/lib/l10n/app_localizations_es.dart index d656ac49..c5029a76 100644 --- a/lib/l10n/app_localizations_es.dart +++ b/lib/l10n/app_localizations_es.dart @@ -1720,6 +1720,9 @@ class AppLocalizationsEs extends AppLocalizations { @override String get map_sharedPin => 'Pin compartido'; + @override + String get map_sharedAt => 'Compartido'; + @override String get map_joinRoom => 'Únete a la sala'; diff --git a/lib/l10n/app_localizations_fr.dart b/lib/l10n/app_localizations_fr.dart index acdc9454..91d6836c 100644 --- a/lib/l10n/app_localizations_fr.dart +++ b/lib/l10n/app_localizations_fr.dart @@ -1730,6 +1730,9 @@ class AppLocalizationsFr extends AppLocalizations { @override String get map_sharedPin => 'Clé partagée'; + @override + String get map_sharedAt => 'Partagé'; + @override String get map_joinRoom => 'Rejoindre le room server'; diff --git a/lib/l10n/app_localizations_hu.dart b/lib/l10n/app_localizations_hu.dart index 24c9ac66..609d4e18 100644 --- a/lib/l10n/app_localizations_hu.dart +++ b/lib/l10n/app_localizations_hu.dart @@ -1733,6 +1733,9 @@ class AppLocalizationsHu extends AppLocalizations { @override String get map_sharedPin => 'Gemeinsames PIN-kód'; + @override + String get map_sharedAt => 'Megosztva'; + @override String get map_joinRoom => 'Csatlakozás a szobához'; diff --git a/lib/l10n/app_localizations_it.dart b/lib/l10n/app_localizations_it.dart index abb1427d..a906b7ff 100644 --- a/lib/l10n/app_localizations_it.dart +++ b/lib/l10n/app_localizations_it.dart @@ -1721,6 +1721,9 @@ class AppLocalizationsIt extends AppLocalizations { @override String get map_sharedPin => 'Condividi PIN'; + @override + String get map_sharedAt => 'Condiviso'; + @override String get map_joinRoom => 'Unisciti alla stanza'; diff --git a/lib/l10n/app_localizations_ja.dart b/lib/l10n/app_localizations_ja.dart index 60018262..a222cd5f 100644 --- a/lib/l10n/app_localizations_ja.dart +++ b/lib/l10n/app_localizations_ja.dart @@ -1648,6 +1648,9 @@ class AppLocalizationsJa extends AppLocalizations { @override String get map_sharedPin => '共有パスワード'; + @override + String get map_sharedAt => '共有済み'; + @override String get map_joinRoom => '部屋に参加する'; diff --git a/lib/l10n/app_localizations_ko.dart b/lib/l10n/app_localizations_ko.dart index 8f74af8f..3a23f2c8 100644 --- a/lib/l10n/app_localizations_ko.dart +++ b/lib/l10n/app_localizations_ko.dart @@ -1644,6 +1644,9 @@ class AppLocalizationsKo extends AppLocalizations { @override String get map_sharedPin => '공유 비밀번호'; + @override + String get map_sharedAt => '공유됨'; + @override String get map_joinRoom => '방에 참여'; diff --git a/lib/l10n/app_localizations_nl.dart b/lib/l10n/app_localizations_nl.dart index d1ebea4a..9470ad92 100644 --- a/lib/l10n/app_localizations_nl.dart +++ b/lib/l10n/app_localizations_nl.dart @@ -1709,6 +1709,9 @@ class AppLocalizationsNl extends AppLocalizations { @override String get map_sharedPin => 'Gedeelde pin'; + @override + String get map_sharedAt => 'Gedeeld'; + @override String get map_joinRoom => 'Kamer Toetreden'; diff --git a/lib/l10n/app_localizations_pl.dart b/lib/l10n/app_localizations_pl.dart index 918860ed..223a2685 100644 --- a/lib/l10n/app_localizations_pl.dart +++ b/lib/l10n/app_localizations_pl.dart @@ -1733,6 +1733,9 @@ class AppLocalizationsPl extends AppLocalizations { @override String get map_sharedPin => 'Udostępniona pinezka'; + @override + String get map_sharedAt => 'Udostępnione'; + @override String get map_joinRoom => 'Dołącz do pokoju'; diff --git a/lib/l10n/app_localizations_pt.dart b/lib/l10n/app_localizations_pt.dart index 5584da93..18a6c694 100644 --- a/lib/l10n/app_localizations_pt.dart +++ b/lib/l10n/app_localizations_pt.dart @@ -1721,6 +1721,9 @@ class AppLocalizationsPt extends AppLocalizations { @override String get map_sharedPin => 'Pin compartilhado'; + @override + String get map_sharedAt => 'Compartilhado'; + @override String get map_joinRoom => 'Junte-se à Sala'; diff --git a/lib/l10n/app_localizations_ru.dart b/lib/l10n/app_localizations_ru.dart index 4b7e3d44..ba4970ff 100644 --- a/lib/l10n/app_localizations_ru.dart +++ b/lib/l10n/app_localizations_ru.dart @@ -1724,6 +1724,9 @@ class AppLocalizationsRu extends AppLocalizations { @override String get map_sharedPin => 'Общая метка'; + @override + String get map_sharedAt => 'Поделено'; + @override String get map_joinRoom => 'Присоединиться к комнате'; diff --git a/lib/l10n/app_localizations_sk.dart b/lib/l10n/app_localizations_sk.dart index f855652d..f135fdd6 100644 --- a/lib/l10n/app_localizations_sk.dart +++ b/lib/l10n/app_localizations_sk.dart @@ -1710,6 +1710,9 @@ class AppLocalizationsSk extends AppLocalizations { @override String get map_sharedPin => 'Zdieľaný PIN'; + @override + String get map_sharedAt => 'Zdieľané'; + @override String get map_joinRoom => 'Pripojiť miestnosť'; diff --git a/lib/l10n/app_localizations_sl.dart b/lib/l10n/app_localizations_sl.dart index 2f3b30c0..311946a9 100644 --- a/lib/l10n/app_localizations_sl.dart +++ b/lib/l10n/app_localizations_sl.dart @@ -1705,6 +1705,9 @@ class AppLocalizationsSl extends AppLocalizations { @override String get map_sharedPin => 'Deljeno naslovno geslo'; + @override + String get map_sharedAt => 'Deljeno'; + @override String get map_joinRoom => 'Pridružiti sobo'; diff --git a/lib/l10n/app_localizations_sv.dart b/lib/l10n/app_localizations_sv.dart index 00816f2a..bb1cc1b0 100644 --- a/lib/l10n/app_localizations_sv.dart +++ b/lib/l10n/app_localizations_sv.dart @@ -1699,6 +1699,9 @@ class AppLocalizationsSv extends AppLocalizations { @override String get map_sharedPin => 'Delad PIN'; + @override + String get map_sharedAt => 'Delad'; + @override String get map_joinRoom => 'Gå med i rum'; diff --git a/lib/l10n/app_localizations_uk.dart b/lib/l10n/app_localizations_uk.dart index f43fd53e..c27b1d05 100644 --- a/lib/l10n/app_localizations_uk.dart +++ b/lib/l10n/app_localizations_uk.dart @@ -1719,6 +1719,9 @@ class AppLocalizationsUk extends AppLocalizations { @override String get map_sharedPin => 'Спільний пін'; + @override + String get map_sharedAt => 'Поділено'; + @override String get map_joinRoom => 'Приєднатися до кімнати'; diff --git a/lib/l10n/app_localizations_zh.dart b/lib/l10n/app_localizations_zh.dart index 6181e8af..e415081e 100644 --- a/lib/l10n/app_localizations_zh.dart +++ b/lib/l10n/app_localizations_zh.dart @@ -1616,6 +1616,9 @@ class AppLocalizationsZh extends AppLocalizations { @override String get map_sharedPin => '共享标记'; + @override + String get map_sharedAt => '已分享'; + @override String get map_joinRoom => '加入房间'; diff --git a/lib/l10n/app_nl.arb b/lib/l10n/app_nl.arb index fa3eb37b..932f9995 100644 --- a/lib/l10n/app_nl.arb +++ b/lib/l10n/app_nl.arb @@ -682,6 +682,7 @@ "map_showSharedMarkers": "Toon gedeelde markeringen", "map_lastSeenTime": "Laatste Bekeken Tijd", "map_sharedPin": "Gedeelde pin", + "map_sharedAt": "Gedeeld", "map_joinRoom": "Kamer Toetreden", "map_manageRepeater": "Beheer Repeater", "mapCache_title": "Offline Kaarten Cache", diff --git a/lib/l10n/app_pl.arb b/lib/l10n/app_pl.arb index 870be9e6..1509340a 100644 --- a/lib/l10n/app_pl.arb +++ b/lib/l10n/app_pl.arb @@ -692,6 +692,7 @@ "map_showSharedMarkers": "Pokaż udostępnione znaczniki.", "map_lastSeenTime": "Ostatni raz widziany", "map_sharedPin": "Udostępniona pinezka", + "map_sharedAt": "Udostępnione", "map_joinRoom": "Dołącz do pokoju", "map_manageRepeater": "Zarządzaj przekaźnikiem", "mapCache_title": "Pamięć podręczna map offline", diff --git a/lib/l10n/app_pt.arb b/lib/l10n/app_pt.arb index 634cf1c0..189f5b9b 100644 --- a/lib/l10n/app_pt.arb +++ b/lib/l10n/app_pt.arb @@ -682,6 +682,7 @@ "map_showSharedMarkers": "Mostrar marcadores compartilhados", "map_lastSeenTime": "Último Tempo de Visualização", "map_sharedPin": "Pin compartilhado", + "map_sharedAt": "Compartilhado", "map_joinRoom": "Junte-se à Sala", "map_manageRepeater": "Gerenciar Repetidor", "mapCache_title": "Cache de Mapa Offline", diff --git a/lib/l10n/app_ru.arb b/lib/l10n/app_ru.arb index 1ad0f1b9..fc7830b8 100644 --- a/lib/l10n/app_ru.arb +++ b/lib/l10n/app_ru.arb @@ -397,6 +397,7 @@ "map_showSharedMarkers": "Показывать общие метки", "map_lastSeenTime": "Время последнего появления", "map_sharedPin": "Общая метка", + "map_sharedAt": "Поделено", "map_joinRoom": "Присоединиться к комнате", "map_manageRepeater": "Управление репитером", "mapCache_title": "Кэш офлайн-карты", diff --git a/lib/l10n/app_sk.arb b/lib/l10n/app_sk.arb index 9cd6072c..c6002ff5 100644 --- a/lib/l10n/app_sk.arb +++ b/lib/l10n/app_sk.arb @@ -682,6 +682,7 @@ "map_showSharedMarkers": "Zobraziť zdieľané značky", "map_lastSeenTime": "Posledný čas sledovania", "map_sharedPin": "Zdieľaný PIN", + "map_sharedAt": "Zdieľané", "map_joinRoom": "Pripojiť miestnosť", "map_manageRepeater": "Spravovať Opakovanie", "mapCache_title": "Offline Mapa Pamäť", diff --git a/lib/l10n/app_sl.arb b/lib/l10n/app_sl.arb index cace8c6b..4ad9d9e6 100644 --- a/lib/l10n/app_sl.arb +++ b/lib/l10n/app_sl.arb @@ -682,6 +682,7 @@ "map_showSharedMarkers": "Pokaži skupno označenja", "map_lastSeenTime": "Datum zadnjega vpogleda", "map_sharedPin": "Deljeno naslovno geslo", + "map_sharedAt": "Deljeno", "map_joinRoom": "Pridružiti sobo", "map_manageRepeater": "Upravljajte Ponovitve", "mapCache_title": "Omrezni predpomnilnik zemljeških zemljejevskih slik", diff --git a/lib/l10n/app_sv.arb b/lib/l10n/app_sv.arb index aaa6ea75..25a6dc26 100644 --- a/lib/l10n/app_sv.arb +++ b/lib/l10n/app_sv.arb @@ -682,6 +682,7 @@ "map_showSharedMarkers": "Visa delade markörer", "map_lastSeenTime": "Senaste Visats Tid", "map_sharedPin": "Delad PIN", + "map_sharedAt": "Delad", "map_joinRoom": "Gå med i rum", "map_manageRepeater": "Hantera Upprepare", "mapCache_title": "Offline Kartcache", diff --git a/lib/l10n/app_uk.arb b/lib/l10n/app_uk.arb index 489b8a2e..1ac0b48a 100644 --- a/lib/l10n/app_uk.arb +++ b/lib/l10n/app_uk.arb @@ -683,6 +683,7 @@ "map_showSharedMarkers": "Показувати спільні маркери", "map_lastSeenTime": "Час останньої активності", "map_sharedPin": "Спільний пін", + "map_sharedAt": "Поділено", "map_joinRoom": "Приєднатися до кімнати", "map_manageRepeater": "Керувати ретранслятором", "mapCache_title": "Офлайн-кеш карти", diff --git a/lib/l10n/app_zh.arb b/lib/l10n/app_zh.arb index 9d6a8bfd..4eb539d6 100644 --- a/lib/l10n/app_zh.arb +++ b/lib/l10n/app_zh.arb @@ -709,6 +709,7 @@ "map_showSharedMarkers": "显示共享标记", "map_lastSeenTime": "最后在线时间", "map_sharedPin": "共享标记", + "map_sharedAt": "已分享", "map_joinRoom": "加入房间", "map_manageRepeater": "管理转发节点", "mapCache_title": "离线地图缓存", diff --git a/lib/screens/channel_chat_screen.dart b/lib/screens/channel_chat_screen.dart index b203cbbe..e89eb158 100644 --- a/lib/screens/channel_chat_screen.dart +++ b/lib/screens/channel_chat_screen.dart @@ -445,6 +445,7 @@ class _ChannelChatScreenState extends State { poi, isOutgoing, textScale, + message.senderName, trailing: (!enableTracing && isOutgoing) ? Padding( padding: const EdgeInsets.only(bottom: 2), @@ -815,21 +816,23 @@ class _ChannelChatScreenState extends State { _PoiInfo? _parsePoiMessage(String text) { final trimmed = text.trim(); final match = RegExp( - r'm:([\-0-9.]+),([\-0-9.]+)\|([^|]*)\|', + r'm:([\-0-9.]+),([\-0-9.]+)\|([^|]*)\|(.*)', ).firstMatch(trimmed); if (match == null) return null; final lat = double.tryParse(match.group(1) ?? ''); final lon = double.tryParse(match.group(2) ?? ''); if (lat == null || lon == null) return null; final label = match.group(3) ?? ''; - return _PoiInfo(lat: lat, lon: lon, label: label); + final flags = match.group(4) ?? ''; + return _PoiInfo(lat: lat, lon: lon, label: label, flags: flags); } Widget _buildPoiMessage( BuildContext context, _PoiInfo poi, bool isOutgoing, - double textScale, { + double textScale, + String senderName, { Widget? trailing, }) { final colorScheme = Theme.of(context).colorScheme; @@ -849,12 +852,22 @@ class _ChannelChatScreenState extends State { padding: EdgeInsets.zero, constraints: const BoxConstraints(minWidth: 32, minHeight: 32), onPressed: () { + final selfName = context.read().selfName ?? 'Me'; + final fromName = isOutgoing ? selfName : senderName; + final key = buildSharedMarkerKey( + sourceId: 'channel:${widget.channel.index}', + label: poi.label, + fromName: fromName, + flags: poi.flags, + isChannel: true, + ); Navigator.push( context, MaterialPageRoute( builder: (context) => MapScreen( highlightPosition: LatLng(poi.lat, poi.lon), highlightLabel: poi.label, + highlightMarkerKey: key, ), ), ); @@ -1512,6 +1525,12 @@ class _PoiInfo { final double lat; final double lon; final String label; + final String flags; - const _PoiInfo({required this.lat, required this.lon, required this.label}); + const _PoiInfo({ + required this.lat, + required this.lon, + required this.label, + required this.flags, + }); } diff --git a/lib/screens/chat_screen.dart b/lib/screens/chat_screen.dart index ffa8344b..129f6925 100644 --- a/lib/screens/chat_screen.dart +++ b/lib/screens/chat_screen.dart @@ -479,6 +479,7 @@ class _ChatScreenState extends State { senderName: resolvedContact.type == advTypeRoom ? "${contact.name} [$fourByteHex]" : contact.name, + sourceId: widget.contact.publicKeyHex, isRoomServer: resolvedContact.type == advTypeRoom, textScale: textScale, onTap: () => _openMessagePath(message, contact), @@ -1568,10 +1569,12 @@ class _MessageBubble extends StatelessWidget { final VoidCallback? onLongPress; final void Function(Message message, String emoji)? onRetryReaction; final double textScale; + final String sourceId; const _MessageBubble({ required this.message, required this.senderName, + required this.sourceId, required this.isRoomServer, required this.textScale, this.onTap, @@ -1678,6 +1681,7 @@ class _MessageBubble extends StatelessWidget { textColor, metaColor, textScale, + senderName, trailing: (!enableTracing && isOutgoing) ? Padding( padding: const EdgeInsets.only(bottom: 2), @@ -1862,14 +1866,15 @@ class _MessageBubble extends StatelessWidget { _PoiInfo? _parsePoiMessage(String text) { final trimmed = text.trim(); final match = RegExp( - r'^m:([\-0-9.]+),([\-0-9.]+)\|([^|]*)\|.*$', + r'^m:([\-0-9.]+),([\-0-9.]+)\|([^|]*)\|(.*)$', ).firstMatch(trimmed); if (match == null) return null; final lat = double.tryParse(match.group(1) ?? ''); final lon = double.tryParse(match.group(2) ?? ''); if (lat == null || lon == null) return null; final label = match.group(3) ?? ''; - return _PoiInfo(lat: lat, lon: lon, label: label); + final flags = match.group(4) ?? ''; + return _PoiInfo(lat: lat, lon: lon, label: label, flags: flags); } Widget _buildPoiMessage( @@ -1877,7 +1882,8 @@ class _MessageBubble extends StatelessWidget { _PoiInfo poi, Color textColor, Color metaColor, - double textScale, { + double textScale, + String senderName, { Widget? trailing, }) { return Row( @@ -1887,13 +1893,23 @@ class _MessageBubble extends StatelessWidget { icon: Icon(Icons.location_on_outlined, color: textColor), padding: EdgeInsets.zero, constraints: const BoxConstraints(minWidth: 32, minHeight: 32), - onPressed: () { + onPressed: () async { + final selfName = context.read().selfName ?? 'Me'; + final fromName = message.isOutgoing ? selfName : senderName; + final key = buildSharedMarkerKey( + sourceId: sourceId, + label: poi.label, + fromName: fromName, + flags: poi.flags, + isChannel: false, + ); Navigator.push( context, MaterialPageRoute( builder: (context) => MapScreen( highlightPosition: LatLng(poi.lat, poi.lon), highlightLabel: poi.label, + highlightMarkerKey: key, ), ), ); @@ -2079,6 +2095,12 @@ class _PoiInfo { final double lat; final double lon; final String label; + final String flags; - const _PoiInfo({required this.lat, required this.lon, required this.label}); + const _PoiInfo({ + required this.lat, + required this.lon, + required this.label, + required this.flags, + }); } diff --git a/lib/screens/map_screen.dart b/lib/screens/map_screen.dart index 6a8acda7..1f8bce26 100644 --- a/lib/screens/map_screen.dart +++ b/lib/screens/map_screen.dart @@ -37,6 +37,7 @@ import 'line_of_sight_map_screen.dart'; class MapScreen extends StatefulWidget { final LatLng? highlightPosition; final String? highlightLabel; + final String? highlightMarkerKey; final double highlightZoom; final bool hideBackButton; @@ -44,6 +45,7 @@ class MapScreen extends StatefulWidget { super.key, this.highlightPosition, this.highlightLabel, + this.highlightMarkerKey, this.highlightZoom = 15.0, this.hideBackButton = false, }); @@ -94,6 +96,19 @@ class _MapScreenState extends State { _removedMarkerIds = ids; _removedMarkersLoaded = true; }); + // If this screen was opened to highlight a marker, and that marker + // was previously removed, re-enable it now that we've loaded the saved + // removed IDs. + if (widget.highlightMarkerKey != null && + _removedMarkerIds.contains(widget.highlightMarkerKey)) { + final updated = Set.from(_removedMarkerIds); + updated.remove(widget.highlightMarkerKey); + if (!mounted) return; + setState(() { + _removedMarkerIds = updated; + }); + await _markerService.saveRemovedIds(updated); + } } bool _checkLocationPlausibility(double lat, double lon) { @@ -229,6 +244,24 @@ class _MapScreenState extends State { : [], ); + // Collect polylines for shared markers' history with dashed lines + final List sharedMarkerPolylines = []; + for (final marker in sharedMarkers) { + if (marker.history.isNotEmpty) { + final points = List.from(marker.history); + points.add(marker.position); + sharedMarkerPolylines.add( + Polyline( + points: points, + color: marker.isChannel + ? (marker.isPublicChannel ? Colors.orange : Colors.purple) + : Colors.blue, + strokeWidth: 3, + ), + ); + } + } + // Calculate center and zoom of all nodes, or default to (0, 0) LatLng center = const LatLng(0, 0); double initialZoom = 10.0; @@ -475,6 +508,8 @@ class _MapScreenState extends State { ), if (_polylines.isNotEmpty && _isBuildingPathTrace) PolylineLayer(polylines: _polylines), + if (sharedMarkerPolylines.isNotEmpty) + PolylineLayer(polylines: sharedMarkerPolylines), MarkerLayer( markers: [ if (highlightPosition != null) @@ -1239,28 +1274,37 @@ class _MapScreenState extends State { } List<_SharedMarker> _collectSharedMarkers(MeshCoreConnector connector) { - final markers = <_SharedMarker>[]; + // Build a _SharedMarker per message (history empty), grouped by dedupe key. + // Afterwards pick the latest per key and fill its history from older ones. + final updatesByKey = >{}; final selfName = connector.selfName ?? 'Me'; + void addUpdate(_SharedMarker update) { + (updatesByKey[update.id] ??= <_SharedMarker>[]).add(update); + } + for (final contact in connector.contacts) { final messages = connector.getMessages(contact); for (final message in messages) { final payload = _parseMarkerText(message.text); if (payload == null) continue; final fromName = message.isOutgoing ? selfName : contact.name; - final id = _buildMarkerId( + final key = buildSharedMarkerKey( sourceId: contact.publicKeyHex, - timestamp: message.timestamp, - text: message.text, + label: payload.label, + fromName: fromName, + flags: payload.flags, + isChannel: false, ); - markers.add( + addUpdate( _SharedMarker( - id: id, + id: key, position: payload.position, label: payload.label, flags: payload.flags, fromName: fromName, sourceLabel: contact.name, + timestamp: message.timestamp, isChannel: false, isPublicChannel: false, ), @@ -1274,14 +1318,16 @@ class _MapScreenState extends State { for (final message in messages) { final payload = _parseMarkerText(message.text); if (payload == null) continue; - final id = _buildMarkerId( + final key = buildSharedMarkerKey( sourceId: 'channel:${channel.index}', - timestamp: message.timestamp, - text: message.text, + label: payload.label, + fromName: message.senderName, + flags: payload.flags, + isChannel: true, ); - markers.add( + addUpdate( _SharedMarker( - id: id, + id: key, position: payload.position, label: payload.label, flags: payload.flags, @@ -1289,6 +1335,7 @@ class _MapScreenState extends State { sourceLabel: channel.name.isEmpty ? 'Channel ${channel.index}' : channel.name, + timestamp: message.timestamp, isChannel: true, isPublicChannel: isPublic, ), @@ -1296,6 +1343,24 @@ class _MapScreenState extends State { } } + final markers = <_SharedMarker>[]; + updatesByKey.forEach((_, updates) { + updates.sort((a, b) => a.timestamp.compareTo(b.timestamp)); + final latest = updates.last; + // History: older positions, drop consecutive duplicates at same position. + final history = []; + for (var i = 0; i < updates.length - 1; i++) { + final p = updates[i].position; + if (history.isEmpty || + history.last.latitude != p.latitude || + history.last.longitude != p.longitude) { + history.add(p); + } + } + markers.add(latest.copyWithHistory(history)); + }); + + markers.sort((a, b) => b.timestamp.compareTo(a.timestamp)); return markers; } @@ -1320,14 +1385,6 @@ class _MapScreenState extends State { ); } - String _buildMarkerId({ - required String sourceId, - required DateTime timestamp, - required String text, - }) { - return '$sourceId|${timestamp.millisecondsSinceEpoch}|$text'; - } - Marker _buildSharedMarker(_SharedMarker marker) { final markerColor = marker.isChannel ? (marker.isPublicChannel ? Colors.orange : Colors.purple) @@ -1337,7 +1394,15 @@ class _MapScreenState extends State { width: 60, height: 60, child: GestureDetector( - onTap: () => _showMarkerInfo(marker), + onTap: () async { + if (_removedMarkerIds.contains(marker.id)) { + setState(() { + _removedMarkerIds.remove(marker.id); + }); + await _markerService.saveRemovedIds(_removedMarkerIds); + } + _showMarkerInfo(marker); + }, child: Column( children: [ Container( @@ -1542,13 +1607,19 @@ class _MapScreenState extends State { showDialog( context: context, builder: (dialogContext) => AlertDialog( - title: Text(marker.label), + title: Text( + marker.label.isEmpty ? context.l10n.map_sharedPin : marker.label, + ), content: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ _buildInfoRow(context.l10n.map_from, marker.fromName), _buildInfoRow(context.l10n.map_source, marker.sourceLabel), + _buildInfoRow( + context.l10n.map_sharedAt, + _formatLastSeen(marker.timestamp), + ), _buildInfoRow( 'Location', '${marker.position.latitude.toStringAsFixed(6)}, ${marker.position.longitude.toStringAsFixed(6)}', @@ -1715,6 +1786,10 @@ class _MapScreenState extends State { String defaultLabel, ) async { final controller = TextEditingController(text: defaultLabel); + controller.selection = TextSelection( + baseOffset: 0, + extentOffset: controller.text.length, + ); return showDialog( context: context, builder: (dialogContext) => AlertDialog( @@ -2322,6 +2397,22 @@ class _MarkerPayload { }); } +/// Build a normalized dedupe key for shared markers. +/// Keeps the same algorithm previously present in both chat and map screens. +String buildSharedMarkerKey({ + required String sourceId, + required String label, + required String fromName, + required String flags, + required bool isChannel, +}) { + final normalizedLabel = label.trim().toLowerCase(); + final normalizedFrom = fromName.trim().toLowerCase(); + final normalizedFlags = flags.trim().toLowerCase(); + final scope = isChannel ? 'ch' : 'dm'; + return '$scope|$sourceId|$normalizedFrom|$normalizedLabel|$normalizedFlags'; +} + class _SharedMarker { final String id; final LatLng position; @@ -2329,8 +2420,10 @@ class _SharedMarker { final String flags; final String fromName; final String sourceLabel; + final DateTime timestamp; final bool isChannel; final bool isPublicChannel; + final List history; _SharedMarker({ required this.id, @@ -2339,7 +2432,24 @@ class _SharedMarker { required this.flags, required this.fromName, required this.sourceLabel, + required this.timestamp, required this.isChannel, required this.isPublicChannel, + this.history = const [], }); + + _SharedMarker copyWithHistory(List newHistory) { + return _SharedMarker( + id: id, + position: position, + label: label, + flags: flags, + fromName: fromName, + sourceLabel: sourceLabel, + timestamp: timestamp, + isChannel: isChannel, + isPublicChannel: isPublicChannel, + history: newHistory, + ); + } }