mirror of
https://github.com/dz0ny/meshcore-sar.git
synced 2026-08-11 16:30:28 +00:00
feat: Add drawing tools localization and update SAR message handling
This commit is contained in:
@@ -614,6 +614,11 @@
|
||||
"description": "Aktion zum Löschen einer Kartenzeichnung"
|
||||
},
|
||||
|
||||
"drawingTools": "Zeichenwerkzeuge",
|
||||
"@drawingTools": {
|
||||
"description": "Zeichenwerkzeuge Abschnitt oder Menütitel"
|
||||
},
|
||||
|
||||
"drawLine": "Linie zeichnen",
|
||||
"@drawLine": {
|
||||
"description": "Kartenzeichnungsmodus: Linie"
|
||||
|
||||
@@ -614,6 +614,11 @@
|
||||
"description": "Action to delete a map drawing"
|
||||
},
|
||||
|
||||
"drawingTools": "Drawing Tools",
|
||||
"@drawingTools": {
|
||||
"description": "Drawing tools section or menu title"
|
||||
},
|
||||
|
||||
"drawLine": "Draw Line",
|
||||
"@drawLine": {
|
||||
"description": "Map drawing mode: line"
|
||||
|
||||
@@ -614,6 +614,11 @@
|
||||
"description": "Acción para eliminar un dibujo del mapa"
|
||||
},
|
||||
|
||||
"drawingTools": "Herramientas de dibujo",
|
||||
"@drawingTools": {
|
||||
"description": "Sección de herramientas de dibujo o título del menú"
|
||||
},
|
||||
|
||||
"drawLine": "Dibujar línea",
|
||||
"@drawLine": {
|
||||
"description": "Modo de dibujo del mapa: línea"
|
||||
|
||||
@@ -614,6 +614,11 @@
|
||||
"description": "Action pour supprimer un dessin de carte"
|
||||
},
|
||||
|
||||
"drawingTools": "Outils de dessin",
|
||||
"@drawingTools": {
|
||||
"description": "Section des outils de dessin ou titre du menu"
|
||||
},
|
||||
|
||||
"drawLine": "Tracer une ligne",
|
||||
"@drawLine": {
|
||||
"description": "Mode de dessin de carte : ligne"
|
||||
|
||||
@@ -213,6 +213,8 @@
|
||||
|
||||
"deleteDrawing": "Izbriši crtež",
|
||||
|
||||
"drawingTools": "Alati za crtanje",
|
||||
|
||||
"drawLine": "Nacrtaj liniju",
|
||||
|
||||
"drawLineDesc": "Nacrtaj slobodnu liniju na karti",
|
||||
|
||||
@@ -614,6 +614,11 @@
|
||||
"description": "Azione per eliminare un disegno sulla mappa"
|
||||
},
|
||||
|
||||
"drawingTools": "Strumenti di disegno",
|
||||
"@drawingTools": {
|
||||
"description": "Sezione strumenti di disegno o titolo del menu"
|
||||
},
|
||||
|
||||
"drawLine": "Disegna Linea",
|
||||
"@drawLine": {
|
||||
"description": "Modalità disegno mappa: linea"
|
||||
|
||||
@@ -749,6 +749,12 @@ abstract class AppLocalizations {
|
||||
/// **'Delete Drawing'**
|
||||
String get deleteDrawing;
|
||||
|
||||
/// Drawing tools section or menu title
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Drawing Tools'**
|
||||
String get drawingTools;
|
||||
|
||||
/// Map drawing mode: line
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
|
||||
@@ -374,6 +374,9 @@ class AppLocalizationsDe extends AppLocalizations {
|
||||
@override
|
||||
String get deleteDrawing => 'Zeichnung löschen';
|
||||
|
||||
@override
|
||||
String get drawingTools => 'Zeichenwerkzeuge';
|
||||
|
||||
@override
|
||||
String get drawLine => 'Linie zeichnen';
|
||||
|
||||
|
||||
@@ -371,6 +371,9 @@ class AppLocalizationsEn extends AppLocalizations {
|
||||
@override
|
||||
String get deleteDrawing => 'Delete Drawing';
|
||||
|
||||
@override
|
||||
String get drawingTools => 'Drawing Tools';
|
||||
|
||||
@override
|
||||
String get drawLine => 'Draw Line';
|
||||
|
||||
|
||||
@@ -372,6 +372,9 @@ class AppLocalizationsEs extends AppLocalizations {
|
||||
@override
|
||||
String get deleteDrawing => 'Eliminar dibujo';
|
||||
|
||||
@override
|
||||
String get drawingTools => 'Herramientas de dibujo';
|
||||
|
||||
@override
|
||||
String get drawLine => 'Dibujar línea';
|
||||
|
||||
|
||||
@@ -374,6 +374,9 @@ class AppLocalizationsFr extends AppLocalizations {
|
||||
@override
|
||||
String get deleteDrawing => 'Supprimer le dessin';
|
||||
|
||||
@override
|
||||
String get drawingTools => 'Outils de dessin';
|
||||
|
||||
@override
|
||||
String get drawLine => 'Tracer une ligne';
|
||||
|
||||
|
||||
@@ -371,6 +371,9 @@ class AppLocalizationsHr extends AppLocalizations {
|
||||
@override
|
||||
String get deleteDrawing => 'Izbriši crtež';
|
||||
|
||||
@override
|
||||
String get drawingTools => 'Alati za crtanje';
|
||||
|
||||
@override
|
||||
String get drawLine => 'Nacrtaj liniju';
|
||||
|
||||
|
||||
@@ -373,6 +373,9 @@ class AppLocalizationsIt extends AppLocalizations {
|
||||
@override
|
||||
String get deleteDrawing => 'Elimina Disegno';
|
||||
|
||||
@override
|
||||
String get drawingTools => 'Strumenti di disegno';
|
||||
|
||||
@override
|
||||
String get drawLine => 'Disegna Linea';
|
||||
|
||||
|
||||
@@ -371,6 +371,9 @@ class AppLocalizationsSl extends AppLocalizations {
|
||||
@override
|
||||
String get deleteDrawing => 'Izbriši risbo';
|
||||
|
||||
@override
|
||||
String get drawingTools => 'Orodja za risanje';
|
||||
|
||||
@override
|
||||
String get drawLine => 'Nariši črto';
|
||||
|
||||
|
||||
@@ -213,6 +213,8 @@
|
||||
|
||||
"deleteDrawing": "Izbriši risbo",
|
||||
|
||||
"drawingTools": "Orodja za risanje",
|
||||
|
||||
"drawLine": "Nariši črto",
|
||||
|
||||
"drawLineDesc": "Nariši prosto črto na zemljevidu",
|
||||
|
||||
@@ -49,7 +49,6 @@ class Message {
|
||||
|
||||
// SAR marker data (if this is a SAR message)
|
||||
final bool isSarMarker;
|
||||
final SarMarkerType? sarMarkerType;
|
||||
final LatLng? sarGpsCoordinates;
|
||||
final String? sarNotes; // Optional message/notes for SAR marker
|
||||
final String? sarCustomEmoji; // Custom emoji for unknown SAR marker types
|
||||
@@ -91,7 +90,6 @@ class Message {
|
||||
required this.senderTimestamp,
|
||||
required this.text,
|
||||
this.isSarMarker = false,
|
||||
this.sarMarkerType,
|
||||
this.sarGpsCoordinates,
|
||||
this.sarNotes,
|
||||
this.sarCustomEmoji,
|
||||
@@ -112,6 +110,28 @@ class Message {
|
||||
this.firstEchoAt,
|
||||
});
|
||||
|
||||
/// Get SAR marker type by inferring from message content
|
||||
/// Returns the type inferred from sarCustomEmoji or by parsing the message text
|
||||
SarMarkerType? get sarMarkerType {
|
||||
if (!isSarMarker) return null;
|
||||
|
||||
// If we have a custom emoji stored, infer type from it
|
||||
if (sarCustomEmoji != null && sarCustomEmoji!.isNotEmpty) {
|
||||
return SarMarkerType.fromEmoji(sarCustomEmoji!);
|
||||
}
|
||||
|
||||
// Otherwise, parse the message text to extract the emoji
|
||||
final trimmed = text.trim();
|
||||
if (!trimmed.startsWith('S:')) return null;
|
||||
|
||||
// Extract emoji from format: S:<emoji>:... or S:<emoji>:<colorIndex>:...
|
||||
final parts = trimmed.split(':');
|
||||
if (parts.length < 3) return null;
|
||||
|
||||
final emoji = parts[1];
|
||||
return SarMarkerType.fromEmoji(emoji);
|
||||
}
|
||||
|
||||
/// Get sender public key as hex string
|
||||
String? get senderKeyShort {
|
||||
if (senderPublicKeyPrefix == null) return null;
|
||||
@@ -288,7 +308,6 @@ class Message {
|
||||
int? senderTimestamp,
|
||||
String? text,
|
||||
bool? isSarMarker,
|
||||
SarMarkerType? sarMarkerType,
|
||||
LatLng? sarGpsCoordinates,
|
||||
String? sarNotes,
|
||||
String? sarCustomEmoji,
|
||||
@@ -319,7 +338,6 @@ class Message {
|
||||
senderTimestamp: senderTimestamp ?? this.senderTimestamp,
|
||||
text: text ?? this.text,
|
||||
isSarMarker: isSarMarker ?? this.isSarMarker,
|
||||
sarMarkerType: sarMarkerType ?? this.sarMarkerType,
|
||||
sarGpsCoordinates: sarGpsCoordinates ?? this.sarGpsCoordinates,
|
||||
sarNotes: sarNotes ?? this.sarNotes,
|
||||
sarCustomEmoji: sarCustomEmoji ?? this.sarCustomEmoji,
|
||||
|
||||
@@ -2,7 +2,6 @@ import 'dart:convert';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import '../models/message.dart';
|
||||
import '../models/sar_marker.dart';
|
||||
import 'package:latlong2/latlong.dart';
|
||||
|
||||
/// Service for persisting messages to local storage
|
||||
@@ -111,7 +110,6 @@ class MessageStorageService {
|
||||
'senderTimestamp': message.senderTimestamp,
|
||||
'text': message.text,
|
||||
'isSarMarker': message.isSarMarker,
|
||||
'sarMarkerType': message.sarMarkerType?.name,
|
||||
'sarGpsLat': message.sarGpsCoordinates?.latitude,
|
||||
'sarGpsLon': message.sarGpsCoordinates?.longitude,
|
||||
'receivedAtMillis': message.receivedAt.millisecondsSinceEpoch,
|
||||
@@ -148,12 +146,6 @@ class MessageStorageService {
|
||||
senderTimestamp: json['senderTimestamp'] as int,
|
||||
text: json['text'] as String,
|
||||
isSarMarker: json['isSarMarker'] as bool? ?? false,
|
||||
sarMarkerType: json['sarMarkerType'] != null
|
||||
? SarMarkerType.values.firstWhere(
|
||||
(e) => e.name == json['sarMarkerType'],
|
||||
orElse: () => SarMarkerType.unknown,
|
||||
)
|
||||
: null,
|
||||
sarGpsCoordinates:
|
||||
json['sarGpsLat'] != null && json['sarGpsLon'] != null
|
||||
? LatLng(json['sarGpsLat'] as double, json['sarGpsLon'] as double)
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import 'dart:typed_data';
|
||||
import 'dart:math';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:latlong2/latlong.dart';
|
||||
import '../models/contact.dart';
|
||||
import '../models/contact_telemetry.dart';
|
||||
import '../models/message.dart';
|
||||
import '../models/sar_marker.dart';
|
||||
import '../l10n/app_localizations.dart';
|
||||
|
||||
/// Generates sample data for testing/demo purposes
|
||||
@@ -148,8 +148,8 @@ class SampleDataGenerator {
|
||||
text: 'S:🧑:${lat.toStringAsFixed(5)},${lon.toStringAsFixed(5)}',
|
||||
receivedAt: timestamp,
|
||||
isSarMarker: true,
|
||||
sarMarkerType: SarMarkerType.foundPerson,
|
||||
sarGpsCoordinates: LatLng(lat, lon),
|
||||
sarCustomEmoji: '🧑',
|
||||
senderName: l10n.sampleTeamMember,
|
||||
));
|
||||
messageId++;
|
||||
@@ -177,8 +177,8 @@ class SampleDataGenerator {
|
||||
text: 'S:🔥:${lat.toStringAsFixed(5)},${lon.toStringAsFixed(5)}',
|
||||
receivedAt: timestamp,
|
||||
isSarMarker: true,
|
||||
sarMarkerType: SarMarkerType.fire,
|
||||
sarGpsCoordinates: LatLng(lat, lon),
|
||||
sarCustomEmoji: '🔥',
|
||||
senderName: l10n.sampleScout,
|
||||
));
|
||||
messageId++;
|
||||
@@ -206,8 +206,8 @@ class SampleDataGenerator {
|
||||
text: 'S:🏕️:${lat.toStringAsFixed(5)},${lon.toStringAsFixed(5)}',
|
||||
receivedAt: timestamp,
|
||||
isSarMarker: true,
|
||||
sarMarkerType: SarMarkerType.stagingArea,
|
||||
sarGpsCoordinates: LatLng(lat, lon),
|
||||
sarCustomEmoji: '🏕️',
|
||||
senderName: l10n.sampleBase,
|
||||
));
|
||||
messageId++;
|
||||
@@ -242,8 +242,8 @@ class SampleDataGenerator {
|
||||
text: 'S:📦:${lat.toStringAsFixed(5)},${lon.toStringAsFixed(5)}${notes[i % notes.length]}',
|
||||
receivedAt: timestamp,
|
||||
isSarMarker: true,
|
||||
sarMarkerType: SarMarkerType.object,
|
||||
sarGpsCoordinates: LatLng(lat, lon),
|
||||
sarCustomEmoji: '📦',
|
||||
senderName: l10n.sampleSearcher,
|
||||
));
|
||||
messageId++;
|
||||
@@ -252,6 +252,51 @@ class SampleDataGenerator {
|
||||
return messages;
|
||||
}
|
||||
|
||||
/// Generate sample map drawings
|
||||
static List<dynamic> generateDrawings({
|
||||
required LatLng centerLocation,
|
||||
required AppLocalizations l10n,
|
||||
}) {
|
||||
final drawings = <dynamic>[];
|
||||
final now = DateTime.now();
|
||||
|
||||
// Generate a line drawing (e.g., search path)
|
||||
final linePoints = <LatLng>[
|
||||
LatLng(centerLocation.latitude + 0.002, centerLocation.longitude - 0.003),
|
||||
LatLng(centerLocation.latitude + 0.004, centerLocation.longitude - 0.002),
|
||||
LatLng(centerLocation.latitude + 0.005, centerLocation.longitude + 0.001),
|
||||
LatLng(centerLocation.latitude + 0.003, centerLocation.longitude + 0.003),
|
||||
];
|
||||
|
||||
drawings.add({
|
||||
'type': 'line',
|
||||
'id': 'sample_line_${now.millisecondsSinceEpoch}',
|
||||
'color': Colors.blue.toARGB32(),
|
||||
'createdAt': now.subtract(const Duration(minutes: 15)).toIso8601String(),
|
||||
'points': linePoints.map((p) => {'lat': p.latitude, 'lon': p.longitude}).toList(),
|
||||
'sender': l10n.sampleTeamMember,
|
||||
});
|
||||
|
||||
// Generate a rectangle drawing (e.g., search area)
|
||||
drawings.add({
|
||||
'type': 'rectangle',
|
||||
'id': 'sample_rect_${now.millisecondsSinceEpoch + 1}',
|
||||
'color': Colors.red.toARGB32(),
|
||||
'createdAt': now.subtract(const Duration(minutes: 10)).toIso8601String(),
|
||||
'topLeft': {
|
||||
'lat': centerLocation.latitude - 0.003,
|
||||
'lon': centerLocation.longitude - 0.004,
|
||||
},
|
||||
'bottomRight': {
|
||||
'lat': centerLocation.latitude - 0.001,
|
||||
'lon': centerLocation.longitude - 0.001,
|
||||
},
|
||||
'sender': l10n.sampleScout,
|
||||
});
|
||||
|
||||
return drawings;
|
||||
}
|
||||
|
||||
/// Generate sample channel messages for public channels
|
||||
static List<Message> generateChannelMessages({
|
||||
LatLng? centerLocation,
|
||||
|
||||
@@ -117,12 +117,9 @@ class SarMessageParser {
|
||||
|
||||
return message.copyWith(
|
||||
isSarMarker: true,
|
||||
sarMarkerType: sarInfo.type,
|
||||
sarGpsCoordinates: sarInfo.location,
|
||||
sarNotes: sarInfo.notes, // Extract and store notes
|
||||
sarCustomEmoji: sarInfo.type == SarMarkerType.unknown
|
||||
? sarInfo.emoji // Preserve custom emoji for unknown types
|
||||
: null,
|
||||
sarCustomEmoji: sarInfo.emoji, // Always store emoji for type inference
|
||||
sarColorIndex: sarInfo.colorIndex, // Store color index
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user