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.

chore/offband-rebrand
ericz 3 months ago
parent 40d3941aab
commit 7db3a12723

@ -682,6 +682,7 @@
"map_showSharedMarkers": "Покажи споделени маркери", "map_showSharedMarkers": "Покажи споделени маркери",
"map_lastSeenTime": "Последна видяна дата", "map_lastSeenTime": "Последна видяна дата",
"map_sharedPin": "Споделено копие", "map_sharedPin": "Споделено копие",
"map_sharedAt": "Споделено",
"map_joinRoom": "Присъедини се към стаята", "map_joinRoom": "Присъедини се към стаята",
"map_manageRepeater": "Управление на Повтарящ се Елемент", "map_manageRepeater": "Управление на Повтарящ се Елемент",
"mapCache_title": "Кеш на офлайн карти", "mapCache_title": "Кеш на офлайн карти",

@ -682,6 +682,7 @@
"map_showSharedMarkers": "Zeige gemeinsam genutzte Marker", "map_showSharedMarkers": "Zeige gemeinsam genutzte Marker",
"map_lastSeenTime": "Letzte Sichtung", "map_lastSeenTime": "Letzte Sichtung",
"map_sharedPin": "Gemeinsames Passwort", "map_sharedPin": "Gemeinsames Passwort",
"map_sharedAt": "Geteilt",
"map_joinRoom": "Beitreten Sie dem Raum", "map_joinRoom": "Beitreten Sie dem Raum",
"map_manageRepeater": "Repeater verwalten", "map_manageRepeater": "Repeater verwalten",
"mapCache_title": "Offline-Karten-Cache", "mapCache_title": "Offline-Karten-Cache",

@ -896,6 +896,7 @@
"map_guessedLocation": "Guessed location", "map_guessedLocation": "Guessed location",
"map_lastSeenTime": "Last Seen Time", "map_lastSeenTime": "Last Seen Time",
"map_sharedPin": "Shared pin", "map_sharedPin": "Shared pin",
"map_sharedAt": "Shared",
"map_joinRoom": "Join Room", "map_joinRoom": "Join Room",
"map_manageRepeater": "Manage Repeater", "map_manageRepeater": "Manage Repeater",
"map_tapToAdd": "Tap on nodes to add them to the path.", "map_tapToAdd": "Tap on nodes to add them to the path.",

@ -682,6 +682,7 @@
"map_showSharedMarkers": "Mostrar marcadores compartidos", "map_showSharedMarkers": "Mostrar marcadores compartidos",
"map_lastSeenTime": "Última vez que se vio", "map_lastSeenTime": "Última vez que se vio",
"map_sharedPin": "Pin compartido", "map_sharedPin": "Pin compartido",
"map_sharedAt": "Compartido",
"map_joinRoom": "Únete a la sala", "map_joinRoom": "Únete a la sala",
"map_manageRepeater": "Gestionar Repetidor", "map_manageRepeater": "Gestionar Repetidor",
"mapCache_title": "Caché de Mapa Offline", "mapCache_title": "Caché de Mapa Offline",

@ -682,6 +682,7 @@
"map_showSharedMarkers": "Afficher les marqueurs partagés", "map_showSharedMarkers": "Afficher les marqueurs partagés",
"map_lastSeenTime": "Dernière fois vu", "map_lastSeenTime": "Dernière fois vu",
"map_sharedPin": "Clé partagée", "map_sharedPin": "Clé partagée",
"map_sharedAt": "Partagé",
"map_joinRoom": "Rejoindre le room server", "map_joinRoom": "Rejoindre le room server",
"map_manageRepeater": "Gérer le répéteur", "map_manageRepeater": "Gérer le répéteur",
"mapCache_title": "Cache de Carte Hors Ligne", "mapCache_title": "Cache de Carte Hors Ligne",

@ -861,6 +861,7 @@
"map_guessedLocation": "Tippolt hely", "map_guessedLocation": "Tippolt hely",
"map_lastSeenTime": "Utoljára megjelent idő", "map_lastSeenTime": "Utoljára megjelent idő",
"map_sharedPin": "Gemeinsames PIN-kód", "map_sharedPin": "Gemeinsames PIN-kód",
"map_sharedAt": "Megosztva",
"map_joinRoom": "Csatlakozás a szobához", "map_joinRoom": "Csatlakozás a szobához",
"map_manageRepeater": "Ellenőriző eszköz kezelése", "map_manageRepeater": "Ellenőriző eszköz kezelése",
"map_tapToAdd": "Nyomj meg a csomópontokhoz, hogy hozzáadd őket az útvonalhoz.", "map_tapToAdd": "Nyomj meg a csomópontokhoz, hogy hozzáadd őket az útvonalhoz.",

@ -682,6 +682,7 @@
"map_showSharedMarkers": "Mostra i segnaposto condivisi", "map_showSharedMarkers": "Mostra i segnaposto condivisi",
"map_lastSeenTime": "Ultimo Tempo di Visualizzazione", "map_lastSeenTime": "Ultimo Tempo di Visualizzazione",
"map_sharedPin": "Condividi PIN", "map_sharedPin": "Condividi PIN",
"map_sharedAt": "Condiviso",
"map_joinRoom": "Unisciti alla stanza", "map_joinRoom": "Unisciti alla stanza",
"map_manageRepeater": "Gestisci Ripetitore", "map_manageRepeater": "Gestisci Ripetitore",
"mapCache_title": "Cache Mappa Offline", "mapCache_title": "Cache Mappa Offline",

@ -861,6 +861,7 @@
"map_guessedLocation": "推測された場所", "map_guessedLocation": "推測された場所",
"map_lastSeenTime": "最後に確認された時間", "map_lastSeenTime": "最後に確認された時間",
"map_sharedPin": "共有パスワード", "map_sharedPin": "共有パスワード",
"map_sharedAt": "共有済み",
"map_joinRoom": "部屋に参加する", "map_joinRoom": "部屋に参加する",
"map_manageRepeater": "リピーターの管理", "map_manageRepeater": "リピーターの管理",
"map_tapToAdd": "ノードをクリックして、パスに追加します。", "map_tapToAdd": "ノードをクリックして、パスに追加します。",

@ -861,6 +861,7 @@
"map_guessedLocation": "추측된 위치", "map_guessedLocation": "추측된 위치",
"map_lastSeenTime": "마지막으로 확인된 시간", "map_lastSeenTime": "마지막으로 확인된 시간",
"map_sharedPin": "공유 비밀번호", "map_sharedPin": "공유 비밀번호",
"map_sharedAt": "공유됨",
"map_joinRoom": "방에 참여", "map_joinRoom": "방에 참여",
"map_manageRepeater": "리피터 관리", "map_manageRepeater": "리피터 관리",
"map_tapToAdd": "노드에 클릭하여 경로에 추가합니다.", "map_tapToAdd": "노드에 클릭하여 경로에 추가합니다.",

@ -3130,6 +3130,12 @@ abstract class AppLocalizations {
/// **'Shared pin'** /// **'Shared pin'**
String get map_sharedPin; 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. /// No description provided for @map_joinRoom.
/// ///
/// In en, this message translates to: /// In en, this message translates to:

@ -1724,6 +1724,9 @@ class AppLocalizationsBg extends AppLocalizations {
@override @override
String get map_sharedPin => 'Споделено копие'; String get map_sharedPin => 'Споделено копие';
@override
String get map_sharedAt => 'Споделено';
@override @override
String get map_joinRoom => 'Присъедини се към стаята'; String get map_joinRoom => 'Присъедини се към стаята';

@ -1721,6 +1721,9 @@ class AppLocalizationsDe extends AppLocalizations {
@override @override
String get map_sharedPin => 'Gemeinsames Passwort'; String get map_sharedPin => 'Gemeinsames Passwort';
@override
String get map_sharedAt => 'Geteilt';
@override @override
String get map_joinRoom => 'Beitreten Sie dem Raum'; String get map_joinRoom => 'Beitreten Sie dem Raum';

@ -1690,6 +1690,9 @@ class AppLocalizationsEn extends AppLocalizations {
@override @override
String get map_sharedPin => 'Shared pin'; String get map_sharedPin => 'Shared pin';
@override
String get map_sharedAt => 'Shared';
@override @override
String get map_joinRoom => 'Join Room'; String get map_joinRoom => 'Join Room';

@ -1720,6 +1720,9 @@ class AppLocalizationsEs extends AppLocalizations {
@override @override
String get map_sharedPin => 'Pin compartido'; String get map_sharedPin => 'Pin compartido';
@override
String get map_sharedAt => 'Compartido';
@override @override
String get map_joinRoom => 'Únete a la sala'; String get map_joinRoom => 'Únete a la sala';

@ -1730,6 +1730,9 @@ class AppLocalizationsFr extends AppLocalizations {
@override @override
String get map_sharedPin => 'Clé partagée'; String get map_sharedPin => 'Clé partagée';
@override
String get map_sharedAt => 'Partagé';
@override @override
String get map_joinRoom => 'Rejoindre le room server'; String get map_joinRoom => 'Rejoindre le room server';

@ -1733,6 +1733,9 @@ class AppLocalizationsHu extends AppLocalizations {
@override @override
String get map_sharedPin => 'Gemeinsames PIN-kód'; String get map_sharedPin => 'Gemeinsames PIN-kód';
@override
String get map_sharedAt => 'Megosztva';
@override @override
String get map_joinRoom => 'Csatlakozás a szobához'; String get map_joinRoom => 'Csatlakozás a szobához';

@ -1721,6 +1721,9 @@ class AppLocalizationsIt extends AppLocalizations {
@override @override
String get map_sharedPin => 'Condividi PIN'; String get map_sharedPin => 'Condividi PIN';
@override
String get map_sharedAt => 'Condiviso';
@override @override
String get map_joinRoom => 'Unisciti alla stanza'; String get map_joinRoom => 'Unisciti alla stanza';

@ -1648,6 +1648,9 @@ class AppLocalizationsJa extends AppLocalizations {
@override @override
String get map_sharedPin => '共有パスワード'; String get map_sharedPin => '共有パスワード';
@override
String get map_sharedAt => '共有済み';
@override @override
String get map_joinRoom => '部屋に参加する'; String get map_joinRoom => '部屋に参加する';

@ -1644,6 +1644,9 @@ class AppLocalizationsKo extends AppLocalizations {
@override @override
String get map_sharedPin => '공유 비밀번호'; String get map_sharedPin => '공유 비밀번호';
@override
String get map_sharedAt => '공유됨';
@override @override
String get map_joinRoom => '방에 참여'; String get map_joinRoom => '방에 참여';

@ -1709,6 +1709,9 @@ class AppLocalizationsNl extends AppLocalizations {
@override @override
String get map_sharedPin => 'Gedeelde pin'; String get map_sharedPin => 'Gedeelde pin';
@override
String get map_sharedAt => 'Gedeeld';
@override @override
String get map_joinRoom => 'Kamer Toetreden'; String get map_joinRoom => 'Kamer Toetreden';

@ -1733,6 +1733,9 @@ class AppLocalizationsPl extends AppLocalizations {
@override @override
String get map_sharedPin => 'Udostępniona pinezka'; String get map_sharedPin => 'Udostępniona pinezka';
@override
String get map_sharedAt => 'Udostępnione';
@override @override
String get map_joinRoom => 'Dołącz do pokoju'; String get map_joinRoom => 'Dołącz do pokoju';

@ -1721,6 +1721,9 @@ class AppLocalizationsPt extends AppLocalizations {
@override @override
String get map_sharedPin => 'Pin compartilhado'; String get map_sharedPin => 'Pin compartilhado';
@override
String get map_sharedAt => 'Compartilhado';
@override @override
String get map_joinRoom => 'Junte-se à Sala'; String get map_joinRoom => 'Junte-se à Sala';

@ -1724,6 +1724,9 @@ class AppLocalizationsRu extends AppLocalizations {
@override @override
String get map_sharedPin => 'Общая метка'; String get map_sharedPin => 'Общая метка';
@override
String get map_sharedAt => 'Поделено';
@override @override
String get map_joinRoom => 'Присоединиться к комнате'; String get map_joinRoom => 'Присоединиться к комнате';

@ -1710,6 +1710,9 @@ class AppLocalizationsSk extends AppLocalizations {
@override @override
String get map_sharedPin => 'Zdieľaný PIN'; String get map_sharedPin => 'Zdieľaný PIN';
@override
String get map_sharedAt => 'Zdieľané';
@override @override
String get map_joinRoom => 'Pripojiť miestnosť'; String get map_joinRoom => 'Pripojiť miestnosť';

@ -1705,6 +1705,9 @@ class AppLocalizationsSl extends AppLocalizations {
@override @override
String get map_sharedPin => 'Deljeno naslovno geslo'; String get map_sharedPin => 'Deljeno naslovno geslo';
@override
String get map_sharedAt => 'Deljeno';
@override @override
String get map_joinRoom => 'Pridružiti sobo'; String get map_joinRoom => 'Pridružiti sobo';

@ -1699,6 +1699,9 @@ class AppLocalizationsSv extends AppLocalizations {
@override @override
String get map_sharedPin => 'Delad PIN'; String get map_sharedPin => 'Delad PIN';
@override
String get map_sharedAt => 'Delad';
@override @override
String get map_joinRoom => 'Gå med i rum'; String get map_joinRoom => 'Gå med i rum';

@ -1719,6 +1719,9 @@ class AppLocalizationsUk extends AppLocalizations {
@override @override
String get map_sharedPin => 'Спільний пін'; String get map_sharedPin => 'Спільний пін';
@override
String get map_sharedAt => 'Поділено';
@override @override
String get map_joinRoom => 'Приєднатися до кімнати'; String get map_joinRoom => 'Приєднатися до кімнати';

@ -1616,6 +1616,9 @@ class AppLocalizationsZh extends AppLocalizations {
@override @override
String get map_sharedPin => '共享标记'; String get map_sharedPin => '共享标记';
@override
String get map_sharedAt => '已分享';
@override @override
String get map_joinRoom => '加入房间'; String get map_joinRoom => '加入房间';

@ -682,6 +682,7 @@
"map_showSharedMarkers": "Toon gedeelde markeringen", "map_showSharedMarkers": "Toon gedeelde markeringen",
"map_lastSeenTime": "Laatste Bekeken Tijd", "map_lastSeenTime": "Laatste Bekeken Tijd",
"map_sharedPin": "Gedeelde pin", "map_sharedPin": "Gedeelde pin",
"map_sharedAt": "Gedeeld",
"map_joinRoom": "Kamer Toetreden", "map_joinRoom": "Kamer Toetreden",
"map_manageRepeater": "Beheer Repeater", "map_manageRepeater": "Beheer Repeater",
"mapCache_title": "Offline Kaarten Cache", "mapCache_title": "Offline Kaarten Cache",

@ -692,6 +692,7 @@
"map_showSharedMarkers": "Pokaż udostępnione znaczniki.", "map_showSharedMarkers": "Pokaż udostępnione znaczniki.",
"map_lastSeenTime": "Ostatni raz widziany", "map_lastSeenTime": "Ostatni raz widziany",
"map_sharedPin": "Udostępniona pinezka", "map_sharedPin": "Udostępniona pinezka",
"map_sharedAt": "Udostępnione",
"map_joinRoom": "Dołącz do pokoju", "map_joinRoom": "Dołącz do pokoju",
"map_manageRepeater": "Zarządzaj przekaźnikiem", "map_manageRepeater": "Zarządzaj przekaźnikiem",
"mapCache_title": "Pamięć podręczna map offline", "mapCache_title": "Pamięć podręczna map offline",

@ -682,6 +682,7 @@
"map_showSharedMarkers": "Mostrar marcadores compartilhados", "map_showSharedMarkers": "Mostrar marcadores compartilhados",
"map_lastSeenTime": "Último Tempo de Visualização", "map_lastSeenTime": "Último Tempo de Visualização",
"map_sharedPin": "Pin compartilhado", "map_sharedPin": "Pin compartilhado",
"map_sharedAt": "Compartilhado",
"map_joinRoom": "Junte-se à Sala", "map_joinRoom": "Junte-se à Sala",
"map_manageRepeater": "Gerenciar Repetidor", "map_manageRepeater": "Gerenciar Repetidor",
"mapCache_title": "Cache de Mapa Offline", "mapCache_title": "Cache de Mapa Offline",

@ -397,6 +397,7 @@
"map_showSharedMarkers": "Показывать общие метки", "map_showSharedMarkers": "Показывать общие метки",
"map_lastSeenTime": "Время последнего появления", "map_lastSeenTime": "Время последнего появления",
"map_sharedPin": "Общая метка", "map_sharedPin": "Общая метка",
"map_sharedAt": "Поделено",
"map_joinRoom": "Присоединиться к комнате", "map_joinRoom": "Присоединиться к комнате",
"map_manageRepeater": "Управление репитером", "map_manageRepeater": "Управление репитером",
"mapCache_title": "Кэш офлайн-карты", "mapCache_title": "Кэш офлайн-карты",

@ -682,6 +682,7 @@
"map_showSharedMarkers": "Zobraziť zdieľané značky", "map_showSharedMarkers": "Zobraziť zdieľané značky",
"map_lastSeenTime": "Posledný čas sledovania", "map_lastSeenTime": "Posledný čas sledovania",
"map_sharedPin": "Zdieľaný PIN", "map_sharedPin": "Zdieľaný PIN",
"map_sharedAt": "Zdieľané",
"map_joinRoom": "Pripojiť miestnosť", "map_joinRoom": "Pripojiť miestnosť",
"map_manageRepeater": "Spravovať Opakovanie", "map_manageRepeater": "Spravovať Opakovanie",
"mapCache_title": "Offline Mapa Pamäť", "mapCache_title": "Offline Mapa Pamäť",

@ -682,6 +682,7 @@
"map_showSharedMarkers": "Pokaži skupno označenja", "map_showSharedMarkers": "Pokaži skupno označenja",
"map_lastSeenTime": "Datum zadnjega vpogleda", "map_lastSeenTime": "Datum zadnjega vpogleda",
"map_sharedPin": "Deljeno naslovno geslo", "map_sharedPin": "Deljeno naslovno geslo",
"map_sharedAt": "Deljeno",
"map_joinRoom": "Pridružiti sobo", "map_joinRoom": "Pridružiti sobo",
"map_manageRepeater": "Upravljajte Ponovitve", "map_manageRepeater": "Upravljajte Ponovitve",
"mapCache_title": "Omrezni predpomnilnik zemljeških zemljejevskih slik", "mapCache_title": "Omrezni predpomnilnik zemljeških zemljejevskih slik",

@ -682,6 +682,7 @@
"map_showSharedMarkers": "Visa delade markörer", "map_showSharedMarkers": "Visa delade markörer",
"map_lastSeenTime": "Senaste Visats Tid", "map_lastSeenTime": "Senaste Visats Tid",
"map_sharedPin": "Delad PIN", "map_sharedPin": "Delad PIN",
"map_sharedAt": "Delad",
"map_joinRoom": "Gå med i rum", "map_joinRoom": "Gå med i rum",
"map_manageRepeater": "Hantera Upprepare", "map_manageRepeater": "Hantera Upprepare",
"mapCache_title": "Offline Kartcache", "mapCache_title": "Offline Kartcache",

@ -683,6 +683,7 @@
"map_showSharedMarkers": "Показувати спільні маркери", "map_showSharedMarkers": "Показувати спільні маркери",
"map_lastSeenTime": "Час останньої активності", "map_lastSeenTime": "Час останньої активності",
"map_sharedPin": "Спільний пін", "map_sharedPin": "Спільний пін",
"map_sharedAt": "Поділено",
"map_joinRoom": "Приєднатися до кімнати", "map_joinRoom": "Приєднатися до кімнати",
"map_manageRepeater": "Керувати ретранслятором", "map_manageRepeater": "Керувати ретранслятором",
"mapCache_title": "Офлайн-кеш карти", "mapCache_title": "Офлайн-кеш карти",

@ -709,6 +709,7 @@
"map_showSharedMarkers": "显示共享标记", "map_showSharedMarkers": "显示共享标记",
"map_lastSeenTime": "最后在线时间", "map_lastSeenTime": "最后在线时间",
"map_sharedPin": "共享标记", "map_sharedPin": "共享标记",
"map_sharedAt": "已分享",
"map_joinRoom": "加入房间", "map_joinRoom": "加入房间",
"map_manageRepeater": "管理转发节点", "map_manageRepeater": "管理转发节点",
"mapCache_title": "离线地图缓存", "mapCache_title": "离线地图缓存",

@ -445,6 +445,7 @@ class _ChannelChatScreenState extends State<ChannelChatScreen> {
poi, poi,
isOutgoing, isOutgoing,
textScale, textScale,
message.senderName,
trailing: (!enableTracing && isOutgoing) trailing: (!enableTracing && isOutgoing)
? Padding( ? Padding(
padding: const EdgeInsets.only(bottom: 2), padding: const EdgeInsets.only(bottom: 2),
@ -815,21 +816,23 @@ class _ChannelChatScreenState extends State<ChannelChatScreen> {
_PoiInfo? _parsePoiMessage(String text) { _PoiInfo? _parsePoiMessage(String text) {
final trimmed = text.trim(); final trimmed = text.trim();
final match = RegExp( final match = RegExp(
r'm:([\-0-9.]+),([\-0-9.]+)\|([^|]*)\|', r'm:([\-0-9.]+),([\-0-9.]+)\|([^|]*)\|(.*)',
).firstMatch(trimmed); ).firstMatch(trimmed);
if (match == null) return null; if (match == null) return null;
final lat = double.tryParse(match.group(1) ?? ''); final lat = double.tryParse(match.group(1) ?? '');
final lon = double.tryParse(match.group(2) ?? ''); final lon = double.tryParse(match.group(2) ?? '');
if (lat == null || lon == null) return null; if (lat == null || lon == null) return null;
final label = match.group(3) ?? ''; 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( Widget _buildPoiMessage(
BuildContext context, BuildContext context,
_PoiInfo poi, _PoiInfo poi,
bool isOutgoing, bool isOutgoing,
double textScale, { double textScale,
String senderName, {
Widget? trailing, Widget? trailing,
}) { }) {
final colorScheme = Theme.of(context).colorScheme; final colorScheme = Theme.of(context).colorScheme;
@ -849,12 +852,22 @@ class _ChannelChatScreenState extends State<ChannelChatScreen> {
padding: EdgeInsets.zero, padding: EdgeInsets.zero,
constraints: const BoxConstraints(minWidth: 32, minHeight: 32), constraints: const BoxConstraints(minWidth: 32, minHeight: 32),
onPressed: () { onPressed: () {
final selfName = context.read<MeshCoreConnector>().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( Navigator.push(
context, context,
MaterialPageRoute( MaterialPageRoute(
builder: (context) => MapScreen( builder: (context) => MapScreen(
highlightPosition: LatLng(poi.lat, poi.lon), highlightPosition: LatLng(poi.lat, poi.lon),
highlightLabel: poi.label, highlightLabel: poi.label,
highlightMarkerKey: key,
), ),
), ),
); );
@ -1512,6 +1525,12 @@ class _PoiInfo {
final double lat; final double lat;
final double lon; final double lon;
final String label; 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,
});
} }

@ -479,6 +479,7 @@ class _ChatScreenState extends State<ChatScreen> {
senderName: resolvedContact.type == advTypeRoom senderName: resolvedContact.type == advTypeRoom
? "${contact.name} [$fourByteHex]" ? "${contact.name} [$fourByteHex]"
: contact.name, : contact.name,
sourceId: widget.contact.publicKeyHex,
isRoomServer: resolvedContact.type == advTypeRoom, isRoomServer: resolvedContact.type == advTypeRoom,
textScale: textScale, textScale: textScale,
onTap: () => _openMessagePath(message, contact), onTap: () => _openMessagePath(message, contact),
@ -1568,10 +1569,12 @@ class _MessageBubble extends StatelessWidget {
final VoidCallback? onLongPress; final VoidCallback? onLongPress;
final void Function(Message message, String emoji)? onRetryReaction; final void Function(Message message, String emoji)? onRetryReaction;
final double textScale; final double textScale;
final String sourceId;
const _MessageBubble({ const _MessageBubble({
required this.message, required this.message,
required this.senderName, required this.senderName,
required this.sourceId,
required this.isRoomServer, required this.isRoomServer,
required this.textScale, required this.textScale,
this.onTap, this.onTap,
@ -1678,6 +1681,7 @@ class _MessageBubble extends StatelessWidget {
textColor, textColor,
metaColor, metaColor,
textScale, textScale,
senderName,
trailing: (!enableTracing && isOutgoing) trailing: (!enableTracing && isOutgoing)
? Padding( ? Padding(
padding: const EdgeInsets.only(bottom: 2), padding: const EdgeInsets.only(bottom: 2),
@ -1862,14 +1866,15 @@ class _MessageBubble extends StatelessWidget {
_PoiInfo? _parsePoiMessage(String text) { _PoiInfo? _parsePoiMessage(String text) {
final trimmed = text.trim(); final trimmed = text.trim();
final match = RegExp( final match = RegExp(
r'^m:([\-0-9.]+),([\-0-9.]+)\|([^|]*)\|.*$', r'^m:([\-0-9.]+),([\-0-9.]+)\|([^|]*)\|(.*)$',
).firstMatch(trimmed); ).firstMatch(trimmed);
if (match == null) return null; if (match == null) return null;
final lat = double.tryParse(match.group(1) ?? ''); final lat = double.tryParse(match.group(1) ?? '');
final lon = double.tryParse(match.group(2) ?? ''); final lon = double.tryParse(match.group(2) ?? '');
if (lat == null || lon == null) return null; if (lat == null || lon == null) return null;
final label = match.group(3) ?? ''; 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( Widget _buildPoiMessage(
@ -1877,7 +1882,8 @@ class _MessageBubble extends StatelessWidget {
_PoiInfo poi, _PoiInfo poi,
Color textColor, Color textColor,
Color metaColor, Color metaColor,
double textScale, { double textScale,
String senderName, {
Widget? trailing, Widget? trailing,
}) { }) {
return Row( return Row(
@ -1887,13 +1893,23 @@ class _MessageBubble extends StatelessWidget {
icon: Icon(Icons.location_on_outlined, color: textColor), icon: Icon(Icons.location_on_outlined, color: textColor),
padding: EdgeInsets.zero, padding: EdgeInsets.zero,
constraints: const BoxConstraints(minWidth: 32, minHeight: 32), constraints: const BoxConstraints(minWidth: 32, minHeight: 32),
onPressed: () { onPressed: () async {
final selfName = context.read<MeshCoreConnector>().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( Navigator.push(
context, context,
MaterialPageRoute( MaterialPageRoute(
builder: (context) => MapScreen( builder: (context) => MapScreen(
highlightPosition: LatLng(poi.lat, poi.lon), highlightPosition: LatLng(poi.lat, poi.lon),
highlightLabel: poi.label, highlightLabel: poi.label,
highlightMarkerKey: key,
), ),
), ),
); );
@ -2079,6 +2095,12 @@ class _PoiInfo {
final double lat; final double lat;
final double lon; final double lon;
final String label; 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,
});
} }

@ -37,6 +37,7 @@ import 'line_of_sight_map_screen.dart';
class MapScreen extends StatefulWidget { class MapScreen extends StatefulWidget {
final LatLng? highlightPosition; final LatLng? highlightPosition;
final String? highlightLabel; final String? highlightLabel;
final String? highlightMarkerKey;
final double highlightZoom; final double highlightZoom;
final bool hideBackButton; final bool hideBackButton;
@ -44,6 +45,7 @@ class MapScreen extends StatefulWidget {
super.key, super.key,
this.highlightPosition, this.highlightPosition,
this.highlightLabel, this.highlightLabel,
this.highlightMarkerKey,
this.highlightZoom = 15.0, this.highlightZoom = 15.0,
this.hideBackButton = false, this.hideBackButton = false,
}); });
@ -94,6 +96,19 @@ class _MapScreenState extends State<MapScreen> {
_removedMarkerIds = ids; _removedMarkerIds = ids;
_removedMarkersLoaded = true; _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<String>.from(_removedMarkerIds);
updated.remove(widget.highlightMarkerKey);
if (!mounted) return;
setState(() {
_removedMarkerIds = updated;
});
await _markerService.saveRemovedIds(updated);
}
} }
bool _checkLocationPlausibility(double lat, double lon) { bool _checkLocationPlausibility(double lat, double lon) {
@ -229,6 +244,24 @@ class _MapScreenState extends State<MapScreen> {
: <Polyline>[], : <Polyline>[],
); );
// Collect polylines for shared markers' history with dashed lines
final List<Polyline> sharedMarkerPolylines = [];
for (final marker in sharedMarkers) {
if (marker.history.isNotEmpty) {
final points = List<LatLng>.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) // Calculate center and zoom of all nodes, or default to (0, 0)
LatLng center = const LatLng(0, 0); LatLng center = const LatLng(0, 0);
double initialZoom = 10.0; double initialZoom = 10.0;
@ -475,6 +508,8 @@ class _MapScreenState extends State<MapScreen> {
), ),
if (_polylines.isNotEmpty && _isBuildingPathTrace) if (_polylines.isNotEmpty && _isBuildingPathTrace)
PolylineLayer(polylines: _polylines), PolylineLayer(polylines: _polylines),
if (sharedMarkerPolylines.isNotEmpty)
PolylineLayer(polylines: sharedMarkerPolylines),
MarkerLayer( MarkerLayer(
markers: [ markers: [
if (highlightPosition != null) if (highlightPosition != null)
@ -1239,28 +1274,37 @@ class _MapScreenState extends State<MapScreen> {
} }
List<_SharedMarker> _collectSharedMarkers(MeshCoreConnector connector) { 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 = <String, List<_SharedMarker>>{};
final selfName = connector.selfName ?? 'Me'; final selfName = connector.selfName ?? 'Me';
void addUpdate(_SharedMarker update) {
(updatesByKey[update.id] ??= <_SharedMarker>[]).add(update);
}
for (final contact in connector.contacts) { for (final contact in connector.contacts) {
final messages = connector.getMessages(contact); final messages = connector.getMessages(contact);
for (final message in messages) { for (final message in messages) {
final payload = _parseMarkerText(message.text); final payload = _parseMarkerText(message.text);
if (payload == null) continue; if (payload == null) continue;
final fromName = message.isOutgoing ? selfName : contact.name; final fromName = message.isOutgoing ? selfName : contact.name;
final id = _buildMarkerId( final key = buildSharedMarkerKey(
sourceId: contact.publicKeyHex, sourceId: contact.publicKeyHex,
timestamp: message.timestamp, label: payload.label,
text: message.text, fromName: fromName,
flags: payload.flags,
isChannel: false,
); );
markers.add( addUpdate(
_SharedMarker( _SharedMarker(
id: id, id: key,
position: payload.position, position: payload.position,
label: payload.label, label: payload.label,
flags: payload.flags, flags: payload.flags,
fromName: fromName, fromName: fromName,
sourceLabel: contact.name, sourceLabel: contact.name,
timestamp: message.timestamp,
isChannel: false, isChannel: false,
isPublicChannel: false, isPublicChannel: false,
), ),
@ -1274,14 +1318,16 @@ class _MapScreenState extends State<MapScreen> {
for (final message in messages) { for (final message in messages) {
final payload = _parseMarkerText(message.text); final payload = _parseMarkerText(message.text);
if (payload == null) continue; if (payload == null) continue;
final id = _buildMarkerId( final key = buildSharedMarkerKey(
sourceId: 'channel:${channel.index}', sourceId: 'channel:${channel.index}',
timestamp: message.timestamp, label: payload.label,
text: message.text, fromName: message.senderName,
flags: payload.flags,
isChannel: true,
); );
markers.add( addUpdate(
_SharedMarker( _SharedMarker(
id: id, id: key,
position: payload.position, position: payload.position,
label: payload.label, label: payload.label,
flags: payload.flags, flags: payload.flags,
@ -1289,6 +1335,7 @@ class _MapScreenState extends State<MapScreen> {
sourceLabel: channel.name.isEmpty sourceLabel: channel.name.isEmpty
? 'Channel ${channel.index}' ? 'Channel ${channel.index}'
: channel.name, : channel.name,
timestamp: message.timestamp,
isChannel: true, isChannel: true,
isPublicChannel: isPublic, isPublicChannel: isPublic,
), ),
@ -1296,6 +1343,24 @@ class _MapScreenState extends State<MapScreen> {
} }
} }
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 = <LatLng>[];
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; return markers;
} }
@ -1320,14 +1385,6 @@ class _MapScreenState extends State<MapScreen> {
); );
} }
String _buildMarkerId({
required String sourceId,
required DateTime timestamp,
required String text,
}) {
return '$sourceId|${timestamp.millisecondsSinceEpoch}|$text';
}
Marker _buildSharedMarker(_SharedMarker marker) { Marker _buildSharedMarker(_SharedMarker marker) {
final markerColor = marker.isChannel final markerColor = marker.isChannel
? (marker.isPublicChannel ? Colors.orange : Colors.purple) ? (marker.isPublicChannel ? Colors.orange : Colors.purple)
@ -1337,7 +1394,15 @@ class _MapScreenState extends State<MapScreen> {
width: 60, width: 60,
height: 60, height: 60,
child: GestureDetector( 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( child: Column(
children: [ children: [
Container( Container(
@ -1542,13 +1607,19 @@ class _MapScreenState extends State<MapScreen> {
showDialog( showDialog(
context: context, context: context,
builder: (dialogContext) => AlertDialog( builder: (dialogContext) => AlertDialog(
title: Text(marker.label), title: Text(
marker.label.isEmpty ? context.l10n.map_sharedPin : marker.label,
),
content: Column( content: Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
_buildInfoRow(context.l10n.map_from, marker.fromName), _buildInfoRow(context.l10n.map_from, marker.fromName),
_buildInfoRow(context.l10n.map_source, marker.sourceLabel), _buildInfoRow(context.l10n.map_source, marker.sourceLabel),
_buildInfoRow(
context.l10n.map_sharedAt,
_formatLastSeen(marker.timestamp),
),
_buildInfoRow( _buildInfoRow(
'Location', 'Location',
'${marker.position.latitude.toStringAsFixed(6)}, ${marker.position.longitude.toStringAsFixed(6)}', '${marker.position.latitude.toStringAsFixed(6)}, ${marker.position.longitude.toStringAsFixed(6)}',
@ -1715,6 +1786,10 @@ class _MapScreenState extends State<MapScreen> {
String defaultLabel, String defaultLabel,
) async { ) async {
final controller = TextEditingController(text: defaultLabel); final controller = TextEditingController(text: defaultLabel);
controller.selection = TextSelection(
baseOffset: 0,
extentOffset: controller.text.length,
);
return showDialog<String>( return showDialog<String>(
context: context, context: context,
builder: (dialogContext) => AlertDialog( 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 { class _SharedMarker {
final String id; final String id;
final LatLng position; final LatLng position;
@ -2329,8 +2420,10 @@ class _SharedMarker {
final String flags; final String flags;
final String fromName; final String fromName;
final String sourceLabel; final String sourceLabel;
final DateTime timestamp;
final bool isChannel; final bool isChannel;
final bool isPublicChannel; final bool isPublicChannel;
final List<LatLng> history;
_SharedMarker({ _SharedMarker({
required this.id, required this.id,
@ -2339,7 +2432,24 @@ class _SharedMarker {
required this.flags, required this.flags,
required this.fromName, required this.fromName,
required this.sourceLabel, required this.sourceLabel,
required this.timestamp,
required this.isChannel, required this.isChannel,
required this.isPublicChannel, required this.isPublicChannel,
this.history = const [],
}); });
_SharedMarker copyWithHistory(List<LatLng> newHistory) {
return _SharedMarker(
id: id,
position: position,
label: label,
flags: flags,
fromName: fromName,
sourceLabel: sourceLabel,
timestamp: timestamp,
isChannel: isChannel,
isPublicChannel: isPublicChannel,
history: newHistory,
);
}
} }

Loading…
Cancel
Save

Powered by TurnKey Linux.