new changes maps

This commit is contained in:
2026-08-12 10:50:45 +05:30
parent df4e044d74
commit c65d78878a
25 changed files with 6120 additions and 3814 deletions

View File

@@ -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();
},
),
);