mirror of
https://github.com/zjs81/meshcore-open.git
synced 2026-06-14 22:55:12 +10:00
Merge pull request #413 from ericszimmermann/ez_marker_update_squashed
Improve SharedMarker handling
This commit is contained in:
@@ -682,6 +682,7 @@
|
||||
"map_showSharedMarkers": "Покажи споделени маркери",
|
||||
"map_lastSeenTime": "Последна видяна дата",
|
||||
"map_sharedPin": "Споделено копие",
|
||||
"map_sharedAt": "Споделено",
|
||||
"map_joinRoom": "Присъедини се към стаята",
|
||||
"map_manageRepeater": "Управление на Повтарящ се Елемент",
|
||||
"mapCache_title": "Кеш на офлайн карти",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -898,6 +898,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.",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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.",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -861,6 +861,7 @@
|
||||
"map_guessedLocation": "推測された場所",
|
||||
"map_lastSeenTime": "最後に確認された時間",
|
||||
"map_sharedPin": "共有パスワード",
|
||||
"map_sharedAt": "共有済み",
|
||||
"map_joinRoom": "部屋に参加する",
|
||||
"map_manageRepeater": "リピーターの管理",
|
||||
"map_tapToAdd": "ノードをクリックして、パスに追加します。",
|
||||
|
||||
@@ -861,6 +861,7 @@
|
||||
"map_guessedLocation": "추측된 위치",
|
||||
"map_lastSeenTime": "마지막으로 확인된 시간",
|
||||
"map_sharedPin": "공유 비밀번호",
|
||||
"map_sharedAt": "공유됨",
|
||||
"map_joinRoom": "방에 참여",
|
||||
"map_manageRepeater": "리피터 관리",
|
||||
"map_tapToAdd": "노드에 클릭하여 경로에 추가합니다.",
|
||||
|
||||
@@ -3142,6 +3142,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:
|
||||
|
||||
@@ -1730,6 +1730,9 @@ class AppLocalizationsBg extends AppLocalizations {
|
||||
@override
|
||||
String get map_sharedPin => 'Споделено копие';
|
||||
|
||||
@override
|
||||
String get map_sharedAt => 'Споделено';
|
||||
|
||||
@override
|
||||
String get map_joinRoom => 'Присъедини се към стаята';
|
||||
|
||||
|
||||
@@ -1727,6 +1727,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';
|
||||
|
||||
|
||||
@@ -1696,6 +1696,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';
|
||||
|
||||
|
||||
@@ -1726,6 +1726,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';
|
||||
|
||||
|
||||
@@ -1736,6 +1736,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';
|
||||
|
||||
|
||||
@@ -1739,6 +1739,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';
|
||||
|
||||
|
||||
@@ -1727,6 +1727,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';
|
||||
|
||||
|
||||
@@ -1654,6 +1654,9 @@ class AppLocalizationsJa extends AppLocalizations {
|
||||
@override
|
||||
String get map_sharedPin => '共有パスワード';
|
||||
|
||||
@override
|
||||
String get map_sharedAt => '共有済み';
|
||||
|
||||
@override
|
||||
String get map_joinRoom => '部屋に参加する';
|
||||
|
||||
|
||||
@@ -1650,6 +1650,9 @@ class AppLocalizationsKo extends AppLocalizations {
|
||||
@override
|
||||
String get map_sharedPin => '공유 비밀번호';
|
||||
|
||||
@override
|
||||
String get map_sharedAt => '공유됨';
|
||||
|
||||
@override
|
||||
String get map_joinRoom => '방에 참여';
|
||||
|
||||
|
||||
@@ -1715,6 +1715,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';
|
||||
|
||||
|
||||
@@ -1739,6 +1739,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';
|
||||
|
||||
|
||||
@@ -1727,6 +1727,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';
|
||||
|
||||
|
||||
@@ -1730,6 +1730,9 @@ class AppLocalizationsRu extends AppLocalizations {
|
||||
@override
|
||||
String get map_sharedPin => 'Общая метка';
|
||||
|
||||
@override
|
||||
String get map_sharedAt => 'Поделено';
|
||||
|
||||
@override
|
||||
String get map_joinRoom => 'Присоединиться к комнате';
|
||||
|
||||
|
||||
@@ -1716,6 +1716,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ť';
|
||||
|
||||
|
||||
@@ -1711,6 +1711,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';
|
||||
|
||||
|
||||
@@ -1705,6 +1705,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';
|
||||
|
||||
|
||||
@@ -1725,6 +1725,9 @@ class AppLocalizationsUk extends AppLocalizations {
|
||||
@override
|
||||
String get map_sharedPin => 'Спільний пін';
|
||||
|
||||
@override
|
||||
String get map_sharedAt => 'Поділено';
|
||||
|
||||
@override
|
||||
String get map_joinRoom => 'Приєднатися до кімнати';
|
||||
|
||||
|
||||
@@ -1622,6 +1622,9 @@ class AppLocalizationsZh extends AppLocalizations {
|
||||
@override
|
||||
String get map_sharedPin => '共享标记';
|
||||
|
||||
@override
|
||||
String get map_sharedAt => '已分享';
|
||||
|
||||
@override
|
||||
String get map_joinRoom => '加入房间';
|
||||
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -397,6 +397,7 @@
|
||||
"map_showSharedMarkers": "Показывать общие метки",
|
||||
"map_lastSeenTime": "Время последнего появления",
|
||||
"map_sharedPin": "Общая метка",
|
||||
"map_sharedAt": "Поделено",
|
||||
"map_joinRoom": "Присоединиться к комнате",
|
||||
"map_manageRepeater": "Управление репитером",
|
||||
"mapCache_title": "Кэш офлайн-карты",
|
||||
|
||||
@@ -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äť",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -683,6 +683,7 @@
|
||||
"map_showSharedMarkers": "Показувати спільні маркери",
|
||||
"map_lastSeenTime": "Час останньої активності",
|
||||
"map_sharedPin": "Спільний пін",
|
||||
"map_sharedAt": "Поділено",
|
||||
"map_joinRoom": "Приєднатися до кімнати",
|
||||
"map_manageRepeater": "Керувати ретранслятором",
|
||||
"mapCache_title": "Офлайн-кеш карти",
|
||||
|
||||
@@ -709,6 +709,7 @@
|
||||
"map_showSharedMarkers": "显示共享标记",
|
||||
"map_lastSeenTime": "最后在线时间",
|
||||
"map_sharedPin": "共享标记",
|
||||
"map_sharedAt": "已分享",
|
||||
"map_joinRoom": "加入房间",
|
||||
"map_manageRepeater": "管理转发节点",
|
||||
"mapCache_title": "离线地图缓存",
|
||||
|
||||
@@ -4,7 +4,6 @@ import 'dart:math' as math;
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/scheduler.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:latlong2/latlong.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
import '../connector/meshcore_connector.dart';
|
||||
@@ -403,7 +402,7 @@ class _ChannelChatScreenState extends State<ChannelChatScreen> {
|
||||
final enableTracing = settingsService.settings.enableMessageTracing;
|
||||
final isOutgoing = message.isOutgoing;
|
||||
final gifId = GifHelper.parseGif(message.text);
|
||||
final poi = _parsePoiMessage(message.text);
|
||||
final poi = parseMarkerText(message.text);
|
||||
final translatedDisplayText =
|
||||
message.translatedText != null &&
|
||||
message.translatedText!.trim().isNotEmpty
|
||||
@@ -491,6 +490,7 @@ class _ChannelChatScreenState extends State<ChannelChatScreen> {
|
||||
poi,
|
||||
isOutgoing,
|
||||
textScale,
|
||||
message.senderName,
|
||||
trailing: (!enableTracing && isOutgoing)
|
||||
? Padding(
|
||||
padding: const EdgeInsets.only(bottom: 2),
|
||||
@@ -747,7 +747,7 @@ class _ChannelChatScreenState extends State<ChannelChatScreen> {
|
||||
final previewTextColor = colorScheme.onSurface.withValues(alpha: 0.7);
|
||||
|
||||
final gifId = GifHelper.parseGif(replyText);
|
||||
final poi = _parsePoiMessage(replyText);
|
||||
final poi = parseMarkerText(replyText);
|
||||
|
||||
Widget contentPreview;
|
||||
if (gifId != null) {
|
||||
@@ -858,24 +858,12 @@ class _ChannelChatScreenState extends State<ChannelChatScreen> {
|
||||
);
|
||||
}
|
||||
|
||||
_PoiInfo? _parsePoiMessage(String text) {
|
||||
final trimmed = text.trim();
|
||||
final match = RegExp(
|
||||
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);
|
||||
}
|
||||
|
||||
Widget _buildPoiMessage(
|
||||
BuildContext context,
|
||||
_PoiInfo poi,
|
||||
MarkerPayload poi,
|
||||
bool isOutgoing,
|
||||
double textScale, {
|
||||
double textScale,
|
||||
String senderName, {
|
||||
Widget? trailing,
|
||||
}) {
|
||||
final colorScheme = Theme.of(context).colorScheme;
|
||||
@@ -895,12 +883,22 @@ class _ChannelChatScreenState extends State<ChannelChatScreen> {
|
||||
padding: EdgeInsets.zero,
|
||||
constraints: const BoxConstraints(minWidth: 32, minHeight: 32),
|
||||
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(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => MapScreen(
|
||||
highlightPosition: LatLng(poi.lat, poi.lon),
|
||||
highlightPosition: poi.position,
|
||||
highlightLabel: poi.label,
|
||||
highlightMarkerKey: key,
|
||||
),
|
||||
),
|
||||
);
|
||||
@@ -1562,11 +1560,3 @@ class _SwipeReplyBubbleState extends State<_SwipeReplyBubble> {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _PoiInfo {
|
||||
final double lat;
|
||||
final double lon;
|
||||
final String label;
|
||||
|
||||
const _PoiInfo({required this.lat, required this.lon, required this.label});
|
||||
}
|
||||
|
||||
@@ -9,7 +9,6 @@ import 'package:meshcore_open/screens/path_trace_map.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
import '../utils/platform_info.dart';
|
||||
import 'package:latlong2/latlong.dart';
|
||||
|
||||
import '../connector/meshcore_connector.dart';
|
||||
import '../connector/meshcore_protocol.dart';
|
||||
@@ -507,6 +506,7 @@ class _ChatScreenState extends State<ChatScreen> {
|
||||
senderName: resolvedContact.type == advTypeRoom
|
||||
? "${contact.name} [$fourByteHex]"
|
||||
: contact.name,
|
||||
sourceId: widget.contact.publicKeyHex,
|
||||
isRoomServer: resolvedContact.type == advTypeRoom,
|
||||
textScale: textScale,
|
||||
onTap: () => _openMessagePath(message, contact),
|
||||
@@ -1630,10 +1630,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,
|
||||
@@ -1648,7 +1650,7 @@ class _MessageBubble extends StatelessWidget {
|
||||
final isOutgoing = message.isOutgoing;
|
||||
final colorScheme = Theme.of(context).colorScheme;
|
||||
final gifId = GifHelper.parseGif(message.text);
|
||||
final poi = _parsePoiMessage(message.text);
|
||||
final poi = parseMarkerText(message.text);
|
||||
final isFailed = message.status == MessageStatus.failed;
|
||||
final bubbleColor = isFailed
|
||||
? colorScheme.errorContainer
|
||||
@@ -1740,6 +1742,7 @@ class _MessageBubble extends StatelessWidget {
|
||||
textColor,
|
||||
metaColor,
|
||||
textScale,
|
||||
senderName,
|
||||
trailing: (!enableTracing && isOutgoing)
|
||||
? Padding(
|
||||
padding: const EdgeInsets.only(bottom: 2),
|
||||
@@ -1921,25 +1924,13 @@ class _MessageBubble extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
|
||||
_PoiInfo? _parsePoiMessage(String text) {
|
||||
final trimmed = text.trim();
|
||||
final match = RegExp(
|
||||
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);
|
||||
}
|
||||
|
||||
Widget _buildPoiMessage(
|
||||
BuildContext context,
|
||||
_PoiInfo poi,
|
||||
MarkerPayload poi,
|
||||
Color textColor,
|
||||
Color metaColor,
|
||||
double textScale, {
|
||||
double textScale,
|
||||
String senderName, {
|
||||
Widget? trailing,
|
||||
}) {
|
||||
return Row(
|
||||
@@ -1949,13 +1940,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<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(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => MapScreen(
|
||||
highlightPosition: LatLng(poi.lat, poi.lon),
|
||||
highlightPosition: poi.position,
|
||||
highlightLabel: poi.label,
|
||||
highlightMarkerKey: key,
|
||||
),
|
||||
),
|
||||
);
|
||||
@@ -2136,11 +2137,3 @@ class _MessageBubble extends StatelessWidget {
|
||||
return '$hour:$minute';
|
||||
}
|
||||
}
|
||||
|
||||
class _PoiInfo {
|
||||
final double lat;
|
||||
final double lon;
|
||||
final String label;
|
||||
|
||||
const _PoiInfo({required this.lat, required this.lon, required this.label});
|
||||
}
|
||||
|
||||
+157
-48
@@ -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<MapScreen> {
|
||||
_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<String>.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<MapScreen> {
|
||||
: <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)
|
||||
LatLng center = const LatLng(0, 0);
|
||||
double initialZoom = 10.0;
|
||||
@@ -475,6 +508,8 @@ class _MapScreenState extends State<MapScreen> {
|
||||
),
|
||||
if (_polylines.isNotEmpty && _isBuildingPathTrace)
|
||||
PolylineLayer(polylines: _polylines),
|
||||
if (sharedMarkerPolylines.isNotEmpty)
|
||||
PolylineLayer(polylines: sharedMarkerPolylines),
|
||||
MarkerLayer(
|
||||
markers: [
|
||||
if (highlightPosition != null)
|
||||
@@ -1239,28 +1274,39 @@ class _MapScreenState extends State<MapScreen> {
|
||||
}
|
||||
|
||||
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';
|
||||
|
||||
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);
|
||||
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,
|
||||
label: payload.label.isEmpty
|
||||
? context.l10n.map_sharedPin
|
||||
: payload.label,
|
||||
flags: payload.flags,
|
||||
fromName: fromName,
|
||||
sourceLabel: contact.name,
|
||||
timestamp: message.timestamp,
|
||||
isChannel: false,
|
||||
isPublicChannel: false,
|
||||
),
|
||||
@@ -1272,23 +1318,28 @@ class _MapScreenState extends State<MapScreen> {
|
||||
final isPublic = _isPublicChannel(channel);
|
||||
final messages = connector.getChannelMessages(channel);
|
||||
for (final message in messages) {
|
||||
final payload = _parseMarkerText(message.text);
|
||||
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,
|
||||
label: payload.label.isEmpty
|
||||
? context.l10n.map_sharedPin
|
||||
: payload.label,
|
||||
flags: payload.flags,
|
||||
fromName: message.senderName,
|
||||
sourceLabel: channel.name.isEmpty
|
||||
? 'Channel ${channel.index}'
|
||||
: channel.name,
|
||||
timestamp: message.timestamp,
|
||||
isChannel: true,
|
||||
isPublicChannel: isPublic,
|
||||
),
|
||||
@@ -1296,38 +1347,27 @@ 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;
|
||||
}
|
||||
|
||||
_MarkerPayload? _parseMarkerText(String text) {
|
||||
final trimmed = text.trim();
|
||||
if (!trimmed.startsWith('m:')) return null;
|
||||
|
||||
final parts = trimmed.substring(2).split('|');
|
||||
if (parts.isEmpty) return null;
|
||||
final coords = parts[0].split(',');
|
||||
if (coords.length != 2) return null;
|
||||
final lat = double.tryParse(coords[0].trim());
|
||||
final lon = double.tryParse(coords[1].trim());
|
||||
if (lat == null || lon == null) return null;
|
||||
|
||||
final label = parts.length > 1 ? parts[1].trim() : '';
|
||||
final flags = parts.length > 2 ? parts[2].trim() : '';
|
||||
return _MarkerPayload(
|
||||
position: LatLng(lat, lon),
|
||||
label: label.isEmpty ? context.l10n.map_sharedPin : label,
|
||||
flags: flags,
|
||||
);
|
||||
}
|
||||
|
||||
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 +1377,15 @@ class _MapScreenState extends State<MapScreen> {
|
||||
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(
|
||||
@@ -1554,13 +1602,19 @@ class _MapScreenState extends State<MapScreen> {
|
||||
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)}',
|
||||
@@ -1727,6 +1781,10 @@ class _MapScreenState extends State<MapScreen> {
|
||||
String defaultLabel,
|
||||
) async {
|
||||
final controller = TextEditingController(text: defaultLabel);
|
||||
controller.selection = TextSelection(
|
||||
baseOffset: 0,
|
||||
extentOffset: controller.text.length,
|
||||
);
|
||||
return showDialog<String>(
|
||||
context: context,
|
||||
builder: (dialogContext) => AlertDialog(
|
||||
@@ -2322,18 +2380,50 @@ class _GuessedLocation {
|
||||
});
|
||||
}
|
||||
|
||||
class _MarkerPayload {
|
||||
class MarkerPayload {
|
||||
final LatLng position;
|
||||
final String label;
|
||||
final String flags;
|
||||
|
||||
_MarkerPayload({
|
||||
MarkerPayload({
|
||||
required this.position,
|
||||
required this.label,
|
||||
required this.flags,
|
||||
});
|
||||
}
|
||||
|
||||
/// Parse a shared marker text message of the form
|
||||
/// `m:<lat>,<lon>|<label>|<flags>` and return a [MarkerPayload].
|
||||
MarkerPayload? parseMarkerText(String text) {
|
||||
final trimmed = text.trim();
|
||||
final match = RegExp(
|
||||
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) ?? '').trim();
|
||||
final flags = (match.group(4) ?? '').trim();
|
||||
return MarkerPayload(position: LatLng(lat, lon), label: label, flags: flags);
|
||||
}
|
||||
|
||||
/// 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;
|
||||
@@ -2341,8 +2431,10 @@ class _SharedMarker {
|
||||
final String flags;
|
||||
final String fromName;
|
||||
final String sourceLabel;
|
||||
final DateTime timestamp;
|
||||
final bool isChannel;
|
||||
final bool isPublicChannel;
|
||||
final List<LatLng> history;
|
||||
|
||||
_SharedMarker({
|
||||
required this.id,
|
||||
@@ -2351,7 +2443,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<LatLng> newHistory) {
|
||||
return _SharedMarker(
|
||||
id: id,
|
||||
position: position,
|
||||
label: label,
|
||||
flags: flags,
|
||||
fromName: fromName,
|
||||
sourceLabel: sourceLabel,
|
||||
timestamp: timestamp,
|
||||
isChannel: isChannel,
|
||||
isPublicChannel: isPublicChannel,
|
||||
history: newHistory,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user