362 lines
13 KiB
Dart
362 lines
13 KiB
Dart
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<MapGeometryPicker> {
|
|
List<LatLng> 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<dynamic>;
|
|
points = [LatLng(coords[0].toDouble(), coords[1].toDouble())];
|
|
} else if (currentType == "LineString" || currentType == "Polygon") {
|
|
var list = widget.initialGeometry!.coordinates as List<dynamic>;
|
|
points = list.map((e) {
|
|
var pair = e as List<dynamic>;
|
|
return LatLng(pair[0].toDouble(), pair[1].toDouble());
|
|
}).toList();
|
|
}
|
|
} 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 {
|
|
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)),
|
|
);
|
|
}
|
|
}
|