new changes maps
This commit is contained in:
@@ -24,15 +24,11 @@ class MultiCustomerMapScreen extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _MultiCustomerMapScreenState extends State<MultiCustomerMapScreen> {
|
||||
GoogleMapController? mapController;
|
||||
Set<Marker> markers = {};
|
||||
Set<Polyline> polylines = {};
|
||||
NearleMapController? mapController;
|
||||
List<NearleMapMarker> markers = const [];
|
||||
List<NearleMapPolyline> polylines = const [];
|
||||
LatLng? currentLocation;
|
||||
bool _isLoading = true;
|
||||
bool _mapReady = false;
|
||||
final PolylinePoints _polylinePoints = PolylinePoints(
|
||||
apiKey: 'AIzaSyBhkGfnq27sN0wV5y_S-M2KojpFTk_by-Q',
|
||||
);
|
||||
final Map<String, Map<String, dynamic>> _deliveryMap = {};
|
||||
|
||||
@override
|
||||
@@ -199,7 +195,7 @@ class _MultiCustomerMapScreenState extends State<MultiCustomerMapScreen> {
|
||||
}
|
||||
|
||||
Future<void> _createDeliveryMarkers() async {
|
||||
Set<Marker> tempMarkers = {};
|
||||
final List<NearleMapMarker> tempMarkers = [];
|
||||
List<_StepPoint> stepPoints = [];
|
||||
_deliveryMap.clear();
|
||||
|
||||
@@ -231,8 +227,6 @@ class _MultiCustomerMapScreenState extends State<MultiCustomerMapScreen> {
|
||||
);
|
||||
if (lat == 0 || lon == 0) continue;
|
||||
|
||||
final customerName = (delivery['deliverycustomer'] ?? 'Customer ${i + 1}')
|
||||
.toString();
|
||||
final orderId = (delivery['orderid'] ?? '').toString();
|
||||
final status = (delivery['orderstatus']?.toString().toLowerCase() ?? '')
|
||||
.trim();
|
||||
@@ -242,23 +236,19 @@ class _MultiCustomerMapScreenState extends State<MultiCustomerMapScreen> {
|
||||
// Store delivery data for dialog
|
||||
_deliveryMap['delivery_$orderId'] = delivery;
|
||||
|
||||
final icon = await _createCircularMarkerBitmap(
|
||||
final icon = await MapMarkerIcons.numberedCircle(
|
||||
displayStep,
|
||||
primaryColor: ColorConstants.primaryColor,
|
||||
isSkipped: isSkipped,
|
||||
isNext: isNext,
|
||||
);
|
||||
|
||||
tempMarkers.add(
|
||||
Marker(
|
||||
markerId: MarkerId('delivery_$orderId'),
|
||||
NearleMapMarker(
|
||||
id: 'delivery_$orderId',
|
||||
position: LatLng(lat, lon),
|
||||
icon: icon,
|
||||
infoWindow: InfoWindow(
|
||||
title: isSkipped
|
||||
? 'Step $displayStep: $customerName (SKIPPED)'
|
||||
: 'Step $displayStep: $customerName',
|
||||
snippet: 'Order #$orderId',
|
||||
),
|
||||
anchor: MarkerAnchor.center,
|
||||
onTap: () {
|
||||
_showCustomerDetailsSheet(delivery, displayStep);
|
||||
},
|
||||
@@ -270,13 +260,11 @@ class _MultiCustomerMapScreenState extends State<MultiCustomerMapScreen> {
|
||||
|
||||
if (currentLocation != null) {
|
||||
tempMarkers.add(
|
||||
Marker(
|
||||
markerId: const MarkerId('current_location'),
|
||||
NearleMapMarker(
|
||||
id: 'current_location',
|
||||
position: currentLocation!,
|
||||
icon: BitmapDescriptor.defaultMarkerWithHue(
|
||||
BitmapDescriptor.hueAzure,
|
||||
),
|
||||
infoWindow: const InfoWindow(title: 'You are here'),
|
||||
icon: await MapMarkerIcons.dot(Colors.blueAccent),
|
||||
anchor: MarkerAnchor.center,
|
||||
),
|
||||
);
|
||||
stepPoints.insert(0, _StepPoint(0, currentLocation!));
|
||||
@@ -284,67 +272,7 @@ class _MultiCustomerMapScreenState extends State<MultiCustomerMapScreen> {
|
||||
|
||||
stepPoints.sort((a, b) => a.step.compareTo(b.step));
|
||||
|
||||
Set<Polyline> newPolylines = {};
|
||||
int segmentIndex = 0;
|
||||
|
||||
for (int i = 0; i < stepPoints.length - 1; i++) {
|
||||
final start = stepPoints[i].position;
|
||||
final end = stepPoints[i + 1].position;
|
||||
|
||||
try {
|
||||
// ignore: deprecated_member_use
|
||||
final request = PolylineRequest(
|
||||
origin: PointLatLng(start.latitude, start.longitude),
|
||||
destination: PointLatLng(end.latitude, end.longitude),
|
||||
mode: TravelMode.driving,
|
||||
);
|
||||
|
||||
final result = await _polylinePoints.getRouteBetweenCoordinates(
|
||||
request: request,
|
||||
);
|
||||
|
||||
if (result.points.isNotEmpty) {
|
||||
final routePoints = result.points
|
||||
.map((p) => LatLng(p.latitude, p.longitude))
|
||||
.toList();
|
||||
|
||||
newPolylines.add(
|
||||
Polyline(
|
||||
polylineId: PolylineId('segment_$segmentIndex'),
|
||||
points: routePoints,
|
||||
width: 6,
|
||||
color: Colors.blue,
|
||||
startCap: Cap.roundCap,
|
||||
endCap: Cap.roundCap,
|
||||
jointType: JointType.round,
|
||||
geodesic: true,
|
||||
),
|
||||
);
|
||||
segmentIndex++;
|
||||
} else {
|
||||
newPolylines.add(
|
||||
Polyline(
|
||||
polylineId: PolylineId('segment_fallback_$segmentIndex'),
|
||||
points: [start, end],
|
||||
width: 4,
|
||||
color: Colors.blue.shade200,
|
||||
),
|
||||
);
|
||||
segmentIndex++;
|
||||
}
|
||||
} catch (e) {
|
||||
debugPrint('[CUSTOMER_MAP] Directions error for segment $i: $e');
|
||||
newPolylines.add(
|
||||
Polyline(
|
||||
polylineId: PolylineId('segment_error_$segmentIndex'),
|
||||
points: [start, end],
|
||||
width: 4,
|
||||
color: Colors.blue.shade200,
|
||||
),
|
||||
);
|
||||
segmentIndex++;
|
||||
}
|
||||
}
|
||||
final newPolylines = await _buildRoutePolylines(stepPoints);
|
||||
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
@@ -357,8 +285,50 @@ class _MultiCustomerMapScreenState extends State<MultiCustomerMapScreen> {
|
||||
_fitBoundsToMarkersAndPolylines();
|
||||
}
|
||||
|
||||
/// Builds the route through every stop in one Directions call.
|
||||
///
|
||||
/// This used to issue one request per consecutive pair of stops, so opening
|
||||
/// this screen with N stops cost N billable requests. OSRM takes every stop
|
||||
/// as a coordinate in one request, so the whole route is a single call.
|
||||
Future<List<NearleMapPolyline>> _buildRoutePolylines(
|
||||
List<_StepPoint> stepPoints,
|
||||
) async {
|
||||
if (stepPoints.length < 2) return const [];
|
||||
|
||||
final route = await RoutingService.getRoute(
|
||||
origin: stepPoints.first.position,
|
||||
destination: stepPoints.last.position,
|
||||
waypoints: stepPoints
|
||||
.sublist(1, stepPoints.length - 1)
|
||||
.map((s) => s.position)
|
||||
.toList(),
|
||||
);
|
||||
|
||||
if (route != null && route.points.isNotEmpty) {
|
||||
return [
|
||||
NearleMapPolyline(
|
||||
id: 'route',
|
||||
points: route.points,
|
||||
width: 6,
|
||||
color: Colors.blue,
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
debugPrint('[CUSTOMER_MAP] Routing unavailable, drawing direct segments');
|
||||
return [
|
||||
NearleMapPolyline(
|
||||
id: 'route_fallback',
|
||||
points: stepPoints.map((s) => s.position).toList(),
|
||||
width: 4,
|
||||
color: Colors.blue.shade200,
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
Future<void> _fitBoundsToMarkersAndPolylines() async {
|
||||
if (mapController == null) return;
|
||||
final controller = mapController;
|
||||
if (controller == null) return;
|
||||
|
||||
double minLat = double.infinity;
|
||||
double maxLat = -double.infinity;
|
||||
@@ -367,8 +337,7 @@ class _MultiCustomerMapScreenState extends State<MultiCustomerMapScreen> {
|
||||
|
||||
bool hasPoint = false;
|
||||
|
||||
for (final m in markers) {
|
||||
final pos = m.position;
|
||||
void include(LatLng pos) {
|
||||
minLat = math.min(minLat, pos.latitude);
|
||||
maxLat = math.max(maxLat, pos.latitude);
|
||||
minLng = math.min(minLng, pos.longitude);
|
||||
@@ -376,137 +345,24 @@ class _MultiCustomerMapScreenState extends State<MultiCustomerMapScreen> {
|
||||
hasPoint = true;
|
||||
}
|
||||
|
||||
for (final m in markers) {
|
||||
include(m.position);
|
||||
}
|
||||
for (final poly in polylines) {
|
||||
for (final pos in poly.points) {
|
||||
minLat = math.min(minLat, pos.latitude);
|
||||
maxLat = math.max(maxLat, pos.latitude);
|
||||
minLng = math.min(minLng, pos.longitude);
|
||||
maxLng = math.max(maxLng, pos.longitude);
|
||||
hasPoint = true;
|
||||
include(pos);
|
||||
}
|
||||
}
|
||||
|
||||
if (!hasPoint) return;
|
||||
|
||||
final bounds = LatLngBounds(
|
||||
southwest: LatLng(minLat, minLng),
|
||||
northeast: LatLng(maxLat, maxLng),
|
||||
);
|
||||
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) async {
|
||||
try {
|
||||
await mapController!.animateCamera(
|
||||
CameraUpdate.newLatLngBounds(bounds, 80),
|
||||
);
|
||||
} catch (e) {
|
||||
Future.delayed(const Duration(milliseconds: 300), () async {
|
||||
try {
|
||||
await mapController!.animateCamera(
|
||||
CameraUpdate.newLatLngBounds(bounds, 80),
|
||||
);
|
||||
} catch (e) {
|
||||
debugPrint('[CUSTOMER_MAP] Retry failed: $e');
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Future<BitmapDescriptor> _createCircularMarkerBitmap(
|
||||
int number, {
|
||||
bool isSkipped = false,
|
||||
bool isNext = false,
|
||||
}) async {
|
||||
const double size = 70;
|
||||
final pictureRecorder = ui.PictureRecorder();
|
||||
final canvas = Canvas(pictureRecorder);
|
||||
final center = Offset(size / 2, size / 2);
|
||||
|
||||
final paint = Paint()
|
||||
..color = isSkipped ? Colors.orange : ColorConstants.primaryColor;
|
||||
canvas.drawCircle(center, 15, paint);
|
||||
|
||||
final border = Paint()
|
||||
..color = isSkipped ? Colors.orange.shade900 : Colors.white
|
||||
..style = PaintingStyle.stroke
|
||||
..strokeWidth = isSkipped ? 4 : 3;
|
||||
canvas.drawCircle(center, 15, border);
|
||||
|
||||
final textPainter = TextPainter(
|
||||
text: TextSpan(
|
||||
text: number.toString(),
|
||||
style: const TextStyle(
|
||||
fontSize: 19,
|
||||
color: Colors.white,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
await controller.fitBounds(
|
||||
LatLngBounds(
|
||||
southwest: LatLng(minLat, minLng),
|
||||
northeast: LatLng(maxLat, maxLng),
|
||||
),
|
||||
textDirection: TextDirection.ltr,
|
||||
padding: 80,
|
||||
);
|
||||
textPainter.layout();
|
||||
textPainter.paint(
|
||||
canvas,
|
||||
Offset(
|
||||
center.dx - textPainter.width / 2,
|
||||
center.dy - textPainter.height / 2,
|
||||
),
|
||||
);
|
||||
|
||||
// Draw "NEXT" indicator (road sign with down arrow) on top
|
||||
if (isNext) {
|
||||
// Draw road sign background (rectangle)
|
||||
final signPaint = Paint()
|
||||
..color = Colors.green
|
||||
..style = PaintingStyle.fill;
|
||||
final signRect = RRect.fromRectAndRadius(
|
||||
Rect.fromCenter(center: Offset(size / 2, 8), width: 45, height: 30),
|
||||
const Radius.circular(4),
|
||||
);
|
||||
canvas.drawRRect(signRect, signPaint);
|
||||
|
||||
// Draw border
|
||||
final signBorder = Paint()
|
||||
..color = Colors.white
|
||||
..style = PaintingStyle.stroke
|
||||
..strokeWidth = 1.5;
|
||||
canvas.drawRRect(signRect, signBorder);
|
||||
|
||||
// Draw "NEXT" text
|
||||
final nextTextPainter = TextPainter(
|
||||
text: const TextSpan(
|
||||
text: 'NEXT',
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
color: Colors.white,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
textDirection: TextDirection.ltr,
|
||||
);
|
||||
nextTextPainter.layout();
|
||||
nextTextPainter.paint(
|
||||
canvas,
|
||||
Offset(size / 2 - nextTextPainter.width / 2, 4),
|
||||
);
|
||||
|
||||
// Draw down arrow
|
||||
final arrowPath = Path();
|
||||
arrowPath.moveTo(size / 2, 18);
|
||||
arrowPath.lineTo(size / 2 - 4, 24);
|
||||
arrowPath.lineTo(size / 2 + 4, 24);
|
||||
arrowPath.close();
|
||||
final arrowPaint = Paint()
|
||||
..color = Colors.white
|
||||
..style = PaintingStyle.fill;
|
||||
canvas.drawPath(arrowPath, arrowPaint);
|
||||
}
|
||||
|
||||
final img = await pictureRecorder.endRecording().toImage(
|
||||
size.toInt(),
|
||||
size.toInt(),
|
||||
);
|
||||
final data = await img.toByteData(format: ui.ImageByteFormat.png);
|
||||
return BitmapDescriptor.bytes(data!.buffer.asUint8List());
|
||||
}
|
||||
|
||||
void _showCustomerDetailsSheet(
|
||||
@@ -872,21 +728,14 @@ class _MultiCustomerMapScreenState extends State<MultiCustomerMapScreen> {
|
||||
),
|
||||
body: _isLoading
|
||||
? const Center(child: CircularProgressIndicator())
|
||||
: GoogleMap(
|
||||
: NearleMap(
|
||||
initialCameraPosition: initialCameraPosition,
|
||||
markers: markers,
|
||||
polylines: polylines,
|
||||
myLocationEnabled: true,
|
||||
myLocationButtonEnabled: true,
|
||||
onMapCreated: (controller) {
|
||||
onMapReady: (controller) {
|
||||
mapController = controller;
|
||||
_mapReady = true;
|
||||
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
Future.delayed(const Duration(milliseconds: 150), () {
|
||||
if (_mapReady) _fitBoundsToMarkersAndPolylines();
|
||||
});
|
||||
});
|
||||
_fitBoundsToMarkersAndPolylines();
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user