feat: Implement compact JSON handling for map drawings and add chat notifications for sent/received drawings

This commit is contained in:
Janez T
2025-10-15 22:45:30 +02:00
parent 800786ec9c
commit 5a096c048e
4 changed files with 109 additions and 8 deletions

View File

@@ -54,11 +54,29 @@ abstract class MapDrawing {
/// Convert to JSON for persistence
Map<String, dynamic> toJson();
/// Convert to JSON for network transmission (includes sender name)
Map<String, dynamic> toNetworkJson(String senderName) {
final json = toJson();
json['sender'] = senderName;
return json;
/// Convert to JSON for network transmission (compact format)
/// Uses short field names and excludes createdAt to minimize message size
Map<String, dynamic> toNetworkJson(String senderName);
/// Parse network JSON (compact format)
static MapDrawing? fromNetworkJson(Map<String, dynamic> json) {
final typeStr = json['t'] as String?;
if (typeStr == null) return null;
try {
final type = DrawingShapeType.values.firstWhere(
(e) => e.name == typeStr,
);
switch (type) {
case DrawingShapeType.line:
return LineDrawing.fromNetworkJson(json);
case DrawingShapeType.rectangle:
return RectangleDrawing.fromNetworkJson(json);
}
} catch (e) {
return null;
}
}
/// Create from JSON
@@ -107,6 +125,18 @@ class LineDrawing extends MapDrawing {
};
}
@override
Map<String, dynamic> toNetworkJson(String senderName) {
// Compact format: t=type, c=color, s=sender, p=points
// Points are encoded as flat array [lat1,lon1,lat2,lon2,...]
return {
't': type.name,
'c': color.value,
's': senderName,
'p': points.expand((p) => [p.latitude, p.longitude]).toList(),
};
}
static LineDrawing fromJson(Map<String, dynamic> json) {
final pointsJson = json['points'] as List<dynamic>;
final points = pointsJson.map((p) => LatLng(p['lat'] as double, p['lon'] as double)).toList();
@@ -122,6 +152,25 @@ class LineDrawing extends MapDrawing {
);
}
static LineDrawing fromNetworkJson(Map<String, dynamic> json) {
// Parse compact format
final pointsFlat = (json['p'] as List<dynamic>).cast<double>();
final points = <LatLng>[];
for (int i = 0; i < pointsFlat.length; i += 2) {
points.add(LatLng(pointsFlat[i], pointsFlat[i + 1]));
}
final senderName = json['s'] as String?;
return LineDrawing(
id: DateTime.now().millisecondsSinceEpoch.toString(), // Generate new ID
color: Color(json['c'] as int),
createdAt: DateTime.now(),
points: points,
senderName: senderName,
isReceived: true,
);
}
/// Create a copy with updated points
LineDrawing copyWith({List<LatLng>? points}) {
return LineDrawing(
@@ -169,6 +218,17 @@ class RectangleDrawing extends MapDrawing {
};
}
@override
Map<String, dynamic> toNetworkJson(String senderName) {
// Compact format: t=type, c=color, s=sender, b=bounds [lat1,lon1,lat2,lon2]
return {
't': type.name,
'c': color.value,
's': senderName,
'b': [topLeft.latitude, topLeft.longitude, bottomRight.latitude, bottomRight.longitude],
};
}
static RectangleDrawing fromJson(Map<String, dynamic> json) {
final topLeftJson = json['topLeft'] as Map<String, dynamic>;
final bottomRightJson = json['bottomRight'] as Map<String, dynamic>;
@@ -185,6 +245,22 @@ class RectangleDrawing extends MapDrawing {
);
}
static RectangleDrawing fromNetworkJson(Map<String, dynamic> json) {
// Parse compact format
final bounds = (json['b'] as List<dynamic>).cast<double>();
final senderName = json['s'] as String?;
return RectangleDrawing(
id: DateTime.now().millisecondsSinceEpoch.toString(), // Generate new ID
color: Color(json['c'] as int),
createdAt: DateTime.now(),
topLeft: LatLng(bounds[0], bounds[1]),
bottomRight: LatLng(bounds[2], bounds[3]),
senderName: senderName,
isReceived: true,
);
}
/// Create a copy with updated corners
RectangleDrawing copyWith({
LatLng? topLeft,

View File

@@ -72,10 +72,20 @@ class AppProvider with ChangeNotifier {
if (drawing != null) {
debugPrint('🎨 [AppProvider] Drawing parsed successfully: ${drawing.type.name} from ${drawing.senderName ?? "unknown"}');
drawingProvider.addReceivedDrawing(drawing);
// Add informational message to chat
final drawingTypeStr = drawing.type.name.substring(0, 1).toUpperCase() +
drawing.type.name.substring(1);
final infoMessage = message.copyWith(
text: '📍 Received map drawing ($drawingTypeStr) from ${drawing.senderName ?? "unknown"}',
);
messagesProvider.addMessage(
infoMessage,
contactLookup: (name) => '',
);
} else {
debugPrint('⚠️ [AppProvider] Failed to parse drawing message');
}
// Don't add drawing messages to the message list
return;
}

View File

@@ -25,8 +25,8 @@ class DrawingMessageParser {
// Parse JSON
final json = jsonDecode(jsonStr) as Map<String, dynamic>;
// Use existing fromJson method
return MapDrawing.fromJson(json);
// Use compact network format parser
return MapDrawing.fromNetworkJson(json);
} catch (e) {
return null;
}

View File

@@ -4,6 +4,7 @@ import 'package:provider/provider.dart';
import '../../providers/drawing_provider.dart';
import '../../providers/connection_provider.dart';
import '../../providers/contacts_provider.dart';
import '../../providers/messages_provider.dart';
import '../../models/map_drawing.dart';
import '../../models/contact.dart';
@@ -480,6 +481,13 @@ class DrawingToolbar extends StatelessWidget {
return;
}
// Add informational message to chat
final messagesProvider = Provider.of<MessagesProvider>(context, listen: false);
messagesProvider.logSystemMessage(
text: '📤 Sent ${drawings.length} map drawing${drawings.length > 1 ? 's' : ''} to Public Channel',
level: 'info',
);
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(
@@ -547,6 +555,13 @@ class DrawingToolbar extends StatelessWidget {
return;
}
// Add informational message to chat
final messagesProvider = Provider.of<MessagesProvider>(context, listen: false);
messagesProvider.logSystemMessage(
text: '📤 Sent ${drawings.length} map drawing${drawings.length > 1 ? 's' : ''} to ${room.advName}',
level: 'info',
);
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(