import 'dart:math' as math; import 'package:flutter/material.dart'; import 'package:flutter_map/flutter_map.dart'; import 'package:latlong2/latlong.dart'; import 'package:manager_api_new/api.dart'; import 'package:manager_app/constants.dart'; import 'package:manager_app/Components/rounded_button.dart'; import 'package:manager_app/Components/color_picker.dart'; class MapGeometryPicker extends StatefulWidget { final GeometryDTO? initialGeometry; final String? initialColor; final Function(GeometryDTO, String) onSave; MapGeometryPicker({ this.initialGeometry, this.initialColor, required this.onSave, }); @override _MapGeometryPickerState createState() => _MapGeometryPickerState(); } class _MapGeometryPickerState extends State { List points = []; String currentType = "Point"; Color selectedColor = kPrimaryColor; final MapController _mapController = MapController(); @override void initState() { super.initState(); if (widget.initialGeometry != null) { currentType = widget.initialGeometry!.type ?? "Point"; _parseInitialGeometry(); } if (widget.initialColor != null) { try { selectedColor = _hexToColor(widget.initialColor!); } catch (e) { selectedColor = kPrimaryColor; } } } Color _hexToColor(String hex) { hex = hex.replaceFirst('#', ''); if (hex.length == 6) hex = 'FF' + hex; return Color(int.parse(hex, radix: 16)); } void _parseInitialGeometry() { if (widget.initialGeometry?.coordinates == null) return; try { if (currentType == "Point") { var coords = widget.initialGeometry!.coordinates as List; points = [LatLng(coords[0].toDouble(), coords[1].toDouble())]; } else if (currentType == "LineString") { var list = widget.initialGeometry!.coordinates as List; points = list.map((e) { var pair = e as List; return LatLng(pair[0].toDouble(), pair[1].toDouble()); }).toList(); } else if (currentType == "Polygon") { // Polygon coordinates: [[[lat,lng],...]] — first element is exterior ring var rings = widget.initialGeometry!.coordinates as List; var ring = rings[0] as List; points = ring.map((e) { var pair = e as List; return LatLng(pair[0].toDouble(), pair[1].toDouble()); }).toList(); // Remove closing point if it duplicates the first if (points.length > 1 && points.first == points.last) { points.removeLast(); } } } catch (e) { print("Error parsing geometry: $e"); } } void _handleTap(TapPosition tapPosition, LatLng latLng) { setState(() { if (currentType == "Point") { points = [latLng]; } else { points.add(latLng); } }); } GeometryDTO _buildGeometry() { if (currentType == "Point") { return GeometryDTO( type: "Point", coordinates: points.isNotEmpty ? [points[0].latitude, points[0].longitude] : null, ); } else if (currentType == "Polygon") { // Polygon: [[[lat,lng],...]] — wrap ring in outer array return GeometryDTO( type: "Polygon", coordinates: [points.map((e) => [e.latitude, e.longitude]).toList()], ); } else { return GeometryDTO( type: currentType, coordinates: points.map((e) => [e.latitude, e.longitude]).toList(), ); } } String _colorToHex(Color color) { return '#${color.value.toRadixString(16).padLeft(8, '0').substring(2)}'; } @override Widget build(BuildContext context) { Size size = MediaQuery.of(context).size; return Dialog( insetPadding: EdgeInsets.all(20), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(15)), child: Container( width: size.width * 0.9, height: size.height * 0.9, child: ClipRRect( borderRadius: BorderRadius.circular(15), child: Column( children: [ Container( padding: EdgeInsets.symmetric(horizontal: 16, vertical: 12), color: kPrimaryColor, child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text("Éditeur de Géométrie", style: TextStyle( color: Colors.white, fontSize: 18, fontWeight: FontWeight.bold)), Row( children: [ IconButton( icon: Icon(Icons.palette, color: Colors.white), onPressed: () { showColorPicker(selectedColor, (Color color) { setState(() => selectedColor = color); }, context); }, ), IconButton( icon: Icon(Icons.delete, color: Colors.white), onPressed: () => setState(() => points.clear()), ), IconButton( icon: Icon(Icons.close, color: Colors.white), onPressed: () => Navigator.pop(context), ), ], ), ], ), ), Padding( padding: const EdgeInsets.all(12.0), child: Column( children: [ Wrap( spacing: 12, alignment: WrapAlignment.center, children: [ _buildTypeButton("Point", Icons.location_on), _buildTypeButton("LineString", Icons.show_chart), _buildTypeButton("Polygon", Icons.pentagon), ], ), SizedBox(height: 8), Text( _getInstructions(), style: TextStyle( fontStyle: FontStyle.italic, color: Colors.grey[600]), textAlign: TextAlign.center, ), ], ), ), Expanded( child: Stack( children: [ FlutterMap( key: _mapKey, mapController: _mapController, options: MapOptions( initialCenter: points.isNotEmpty ? points[0] : LatLng(50.429333, 4.891434), initialZoom: 14, onTap: _handleTap, ), children: [ TileLayer( urlTemplate: "https://tile.openstreetmap.org/{z}/{x}/{y}.png", ), if (currentType == "Polygon" && points.length >= 3) PolygonLayer( polygons: [ Polygon( points: points, color: selectedColor.withOpacity(0.3), borderStrokeWidth: 2, borderColor: selectedColor, isFilled: true, ), ], ), if (currentType == "LineString" && points.length >= 2) PolylineLayer( polylines: [ Polyline( points: points, color: selectedColor, strokeWidth: 4, ), ], ), MarkerLayer( markers: points.asMap().entries.map((entry) { int idx = entry.key; LatLng p = entry.value; return Marker( point: p, width: 30, height: 30, child: GestureDetector( onPanUpdate: (details) { _handleDrag(idx, details); }, child: Container( decoration: BoxDecoration( color: Colors.white, shape: BoxShape.circle, border: Border.all( color: selectedColor, width: 2), boxShadow: [ BoxShadow( blurRadius: 4, color: Colors.black26, offset: Offset(0, 2)) ], ), child: Center( child: Icon(Icons.circle, color: selectedColor, size: 14), ), ), ), ); }).toList(), ), ], ), ], ), ), Container( padding: EdgeInsets.all(16), decoration: BoxDecoration( color: Colors.white, boxShadow: [ BoxShadow( color: Colors.black12, blurRadius: 4, offset: Offset(0, -2)) ], ), child: Row( mainAxisAlignment: MainAxisAlignment.end, children: [ Container( height: 45, child: RoundedButton( text: "Annuler", color: Colors.grey[200]!, textColor: Colors.black87, press: () => Navigator.pop(context), fontSize: 16, horizontal: 24, ), ), SizedBox(width: 12), Container( height: 45, child: RoundedButton( text: "Sauvegarder", color: kPrimaryColor, press: () { widget.onSave( _buildGeometry(), _colorToHex(selectedColor)); Navigator.pop(context); }, fontSize: 16, horizontal: 32, ), ), ], ), ) ], ), ), ), ); } String _getInstructions() { switch (currentType) { case "Point": return "Touchez la carte pour placer le point. Faites glisser le point pour le déplacer."; case "LineString": return "Touchez la carte pour ajouter des points à la ligne. Faites glisser un point pour le déplacer."; case "Polygon": return "Touchez la carte pour définir les sommets du polygone. Faites glisser un sommet pour le déplacer."; default: return ""; } } final GlobalKey _mapKey = GlobalKey(); void _handleDrag(int index, DragUpdateDetails details) { if (index < 0 || index >= points.length) return; final RenderBox? mapBox = _mapKey.currentContext?.findRenderObject() as RenderBox?; if (mapBox != null) { final Offset localOffset = mapBox.globalToLocal(details.globalPosition); try { LatLng newLatLng = _mapController.camera .pointToLatLng(math.Point(localOffset.dx, localOffset.dy)); setState(() { points[index] = newLatLng; }); } catch (e) { print("Error dragging point: $e"); } } } Widget _buildTypeButton(String type, IconData icon) { bool isSelected = currentType == type; return ChoiceChip( label: Text(type == "LineString" ? "Ligne" : type == "Polygon" ? "Polygone" : "Point"), avatar: Icon(icon, size: 18, color: isSelected ? Colors.white : kPrimaryColor), selected: isSelected, onSelected: (val) { if (val) { setState(() { currentType = type; if (currentType == "Point" && points.length > 1) { points = [points[0]]; } }); } }, selectedColor: kPrimaryColor, labelStyle: TextStyle(color: isSelected ? Colors.white : kPrimaryColor), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)), ); } }