feat: Enhance message handling for drawings

- Added properties to the Message model to track drawing status and associated drawing ID.
- Updated AppProvider to mark messages as drawings and link them to their respective drawing IDs.
- Enhanced DrawingProvider to manage received drawings and their visibility.
- Implemented navigation to drawings in MapProvider, including dynamic zoom levels based on drawing size.
- Updated MessagesProvider to parse drawing messages and manage linked drawings.
- Enhanced MessagesTab to support drawing message interactions, including navigation and coordinate copying.
- Created DrawingMinimapPreview widget for visual representation of drawings in message bubbles.
- Improved DrawingToolbar to handle sharing of drawings, ensuring only unshared drawings are sent.
- Added utility functions in DrawingMessageParser for extracting drawing metadata.
This commit is contained in:
Janez T
2025-10-23 20:14:51 +02:00
parent f32667997e
commit 1688d6f538
25 changed files with 1504 additions and 114 deletions

View File

@@ -150,10 +150,16 @@ class AppProvider with ChangeNotifier {
debugPrint(' Drawing linked to message ID: ${message.id}');
drawingProvider.addReceivedDrawing(drawing);
// Add the original drawing message to chat (not a modified info message)
// Update message to mark as drawing and link to drawing ID
final updatedMessage = message.copyWith(
isDrawing: true,
drawingId: drawing.id,
);
// Add the drawing message to chat with drawing metadata
// This allows users to click on the drawing message to navigate to it
messagesProvider.addMessage(
message,
updatedMessage,
contactLookup: (name) => '',
);
} else {

View File

@@ -31,9 +31,17 @@ class DrawingProvider with ChangeNotifier {
Color get selectedColor => _selectedColor;
bool get showReceivedDrawings => _showReceivedDrawings;
bool get showSarMarkers => _showSarMarkers;
List<MapDrawing> get drawings => _showReceivedDrawings
? List.unmodifiable(_drawings)
: List.unmodifiable(_drawings.where((d) => !d.isReceived).toList());
List<MapDrawing> get drawings {
// Filter out hidden drawings first
var visibleDrawings = _drawings.where((d) => !d.isHidden);
// Then filter by received status if needed
if (!_showReceivedDrawings) {
visibleDrawings = visibleDrawings.where((d) => !d.isReceived);
}
return List.unmodifiable(visibleDrawings.toList());
}
MapDrawing? get currentDrawing => _currentDrawing;
List<LatLng> get currentLinePoints => List.unmodifiable(_currentLinePoints);
LatLng? get rectangleStartPoint => _rectangleStartPoint;
@@ -281,7 +289,155 @@ class DrawingProvider with ChangeNotifier {
return;
}
_drawings.add(drawing);
// Mark as received when adding
final receivedDrawing = _createReceivedCopy(drawing);
_drawings.add(receivedDrawing);
_saveDrawings();
notifyListeners();
}
/// Create a copy of a drawing marked as received
MapDrawing _createReceivedCopy(MapDrawing drawing) {
if (drawing is LineDrawing) {
return LineDrawing(
id: drawing.id,
color: drawing.color,
createdAt: drawing.createdAt,
points: drawing.points,
senderName: drawing.senderName,
isReceived: true,
messageId: drawing.messageId,
isShared: drawing.isShared,
isSent: drawing.isSent,
isHidden: drawing.isHidden,
);
} else if (drawing is RectangleDrawing) {
return RectangleDrawing(
id: drawing.id,
color: drawing.color,
createdAt: drawing.createdAt,
topLeft: drawing.topLeft,
bottomRight: drawing.bottomRight,
senderName: drawing.senderName,
isReceived: true,
messageId: drawing.messageId,
isShared: drawing.isShared,
isSent: drawing.isSent,
isHidden: drawing.isHidden,
);
}
return drawing;
}
/// Get a drawing by its ID
MapDrawing? getDrawingById(String id) {
try {
return _drawings.firstWhere((d) => d.id == id);
} catch (e) {
return null;
}
}
/// Get all unshared drawings (local drawings not yet sent)
List<MapDrawing> getUnsharedDrawings() {
return _drawings.where((d) => !d.isShared && !d.isReceived).toList();
}
/// Mark a drawing as shared
void markDrawingAsShared(String id) {
final index = _drawings.indexWhere((d) => d.id == id);
if (index != -1) {
final drawing = _drawings[index];
// Create a copy with isShared = true
if (drawing is LineDrawing) {
_drawings[index] = LineDrawing(
id: drawing.id,
color: drawing.color,
createdAt: drawing.createdAt,
points: drawing.points,
senderName: drawing.senderName,
isReceived: drawing.isReceived,
messageId: drawing.messageId,
isShared: true,
isSent: drawing.isSent,
isHidden: drawing.isHidden,
);
} else if (drawing is RectangleDrawing) {
_drawings[index] = RectangleDrawing(
id: drawing.id,
color: drawing.color,
createdAt: drawing.createdAt,
topLeft: drawing.topLeft,
bottomRight: drawing.bottomRight,
senderName: drawing.senderName,
isReceived: drawing.isReceived,
messageId: drawing.messageId,
isShared: true,
isSent: drawing.isSent,
isHidden: drawing.isHidden,
);
}
_saveDrawings();
notifyListeners();
}
}
/// Toggle visibility of a drawing (doesn't save to storage)
void toggleDrawingVisibility(String id) {
final index = _drawings.indexWhere((d) => d.id == id);
if (index != -1) {
final drawing = _drawings[index];
// Create a copy with toggled isHidden flag
if (drawing is LineDrawing) {
_drawings[index] = LineDrawing(
id: drawing.id,
color: drawing.color,
createdAt: drawing.createdAt,
points: drawing.points,
senderName: drawing.senderName,
isReceived: drawing.isReceived,
messageId: drawing.messageId,
isShared: drawing.isShared,
isSent: drawing.isSent,
isHidden: !drawing.isHidden,
);
} else if (drawing is RectangleDrawing) {
_drawings[index] = RectangleDrawing(
id: drawing.id,
color: drawing.color,
createdAt: drawing.createdAt,
topLeft: drawing.topLeft,
bottomRight: drawing.bottomRight,
senderName: drawing.senderName,
isReceived: drawing.isReceived,
messageId: drawing.messageId,
isShared: drawing.isShared,
isSent: drawing.isSent,
isHidden: !drawing.isHidden,
);
}
// Don't save to storage - visibility toggle is temporary
notifyListeners();
}
}
/// Remove a drawing and its linked message
void removeDrawingAndMessage(String drawingId, dynamic messagesProvider) {
final drawing = getDrawingById(drawingId);
if (drawing == null) return;
// Remove the drawing
_drawings.removeWhere((d) => d.id == drawingId);
// If the drawing has a linked message, remove it too
if (drawing.messageId != null && messagesProvider != null) {
messagesProvider.deleteMessage(drawing.messageId!);
}
_saveDrawings();
notifyListeners();
}

View File

@@ -44,6 +44,56 @@ class MapProvider with ChangeNotifier {
// Don't notify listeners to avoid rebuilds
}
/// Navigate to a drawing by its ID
void navigateToDrawing(String drawingId, dynamic drawingProvider) {
// Find the drawing in the provider
final drawings = drawingProvider.drawings as List;
final drawing = drawings.cast<dynamic>().firstWhere(
(d) => d.id == drawingId,
orElse: () => null,
);
if (drawing == null) {
debugPrint('⚠️ [MapProvider] Drawing $drawingId not found');
return;
}
// Use MapDrawing's built-in getCenter and getBounds methods
final center = drawing.getCenter();
final bounds = drawing.getBounds();
// Calculate appropriate zoom level based on bounds
// For larger drawings, use lower zoom to fit the whole drawing
// For smaller drawings, use higher zoom for better detail
final latDiff = (bounds.north - bounds.south).abs();
final lonDiff = (bounds.east - bounds.west).abs();
final maxDiff = latDiff > lonDiff ? latDiff : lonDiff;
// Zoom scale: smaller drawings get higher zoom
// 0.001 degrees (~100m) -> zoom 17
// 0.005 degrees (~500m) -> zoom 16
// 0.01 degrees (~1km) -> zoom 15
// 0.05 degrees (~5km) -> zoom 13
// 0.1 degrees (~10km) -> zoom 12
double zoom = 15.0;
if (maxDiff < 0.001) {
zoom = 17.0;
} else if (maxDiff < 0.005) {
zoom = 16.0;
} else if (maxDiff < 0.01) {
zoom = 15.0;
} else if (maxDiff < 0.05) {
zoom = 13.0;
} else if (maxDiff < 0.1) {
zoom = 12.0;
} else {
zoom = 10.0;
}
debugPrint('🗺️ [MapProvider] Navigating to drawing: ${drawing.type.name}, zoom: $zoom');
navigateToLocation(location: center, zoom: zoom, animate: true);
}
void updateZoom(double zoom) {
_targetZoom = zoom;
notifyListeners();

View File

@@ -6,6 +6,7 @@ import '../models/sar_marker.dart';
import '../services/message_storage_service.dart';
import '../services/notification_service.dart';
import '../utils/sar_message_parser.dart';
import '../utils/drawing_message_parser.dart';
import '../l10n/app_localizations.dart';
import 'helpers/message_retry_manager.dart';
@@ -139,7 +140,25 @@ class MessagesProvider with ChangeNotifier {
String Function(String name)? contactLookup,
}) {
// Always enhance message with SAR parser to detect SAR markers
final enhancedMessage = SarMessageParser.enhanceMessage(message);
var enhancedMessage = SarMessageParser.enhanceMessage(message);
// Check if it's a drawing message (D:...) and not already marked
// Don't overwrite if already set by the sender (preserves correct drawing ID)
if (DrawingMessageParser.isDrawingMessage(enhancedMessage.text) &&
!enhancedMessage.isDrawing) {
// Parse the drawing to get its ID
final drawing = DrawingMessageParser.parseDrawingMessage(
enhancedMessage.text,
senderName: enhancedMessage.senderName,
messageId: enhancedMessage.id,
);
// Mark message as drawing and link to drawing ID
enhancedMessage = enhancedMessage.copyWith(
isDrawing: true,
drawingId: drawing?.id,
);
}
// For channel messages with sender name, try to link with contact
Message finalMessage = enhancedMessage;
@@ -497,6 +516,23 @@ class MessagesProvider with ChangeNotifier {
}
}
/// Delete a drawing message and its linked drawing
void deleteDrawingMessage(String messageId, dynamic drawingProvider) {
final message = _messages.firstWhere(
(m) => m.id == messageId,
orElse: () => throw Exception('Message not found'),
);
// If the message has a linked drawing, remove it
if (message.drawingId != null && drawingProvider != null) {
// Remove the drawing (DrawingProvider will handle removing this message)
drawingProvider.removeDrawing(message.drawingId!);
} else {
// No linked drawing, just delete the message
deleteMessage(messageId);
}
}
/// Clear all messages
void clearMessages() {
_messages.clear();
@@ -585,7 +621,25 @@ class MessagesProvider with ChangeNotifier {
);
// Always enhance message with SAR parser to detect SAR markers
final enhancedMessage = SarMessageParser.enhanceMessage(message);
var enhancedMessage = SarMessageParser.enhanceMessage(message);
// Check if it's a drawing message (D:...) and not already marked
// Don't overwrite if already set by the sender (preserves correct drawing ID)
if (DrawingMessageParser.isDrawingMessage(enhancedMessage.text) &&
!enhancedMessage.isDrawing) {
// Parse the drawing to get its ID
final drawing = DrawingMessageParser.parseDrawingMessage(
enhancedMessage.text,
senderName: enhancedMessage.senderName,
messageId: enhancedMessage.id,
);
// Mark message as drawing and link to drawing ID
enhancedMessage = enhancedMessage.copyWith(
isDrawing: true,
drawingId: drawing?.id,
);
}
// Check for duplicates (shouldn't happen for sent messages, but be safe)
if (_isDuplicate(enhancedMessage)) {