new changes maps
This commit is contained in:
@@ -5,12 +5,13 @@ import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:ui' as ui;
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_polyline_points/flutter_polyline_points.dart';
|
||||
import 'package:flutter_slidable/flutter_slidable.dart';
|
||||
import 'package:geolocator/geolocator.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:google_maps_flutter/google_maps_flutter.dart';
|
||||
import 'package:lottie/lottie.dart' hide Marker;
|
||||
import 'package:nearle/services/maps/map_marker_icons.dart';
|
||||
import 'package:nearle/services/maps/nearle_map.dart';
|
||||
import 'package:nearle/services/maps/routing_service.dart';
|
||||
import 'package:nearle/views/helpers/constants/Colorconstants.dart';
|
||||
import 'package:nearle/views/helpers/constants/Font_constant.dart';
|
||||
import 'package:slider_button_lite/feature/presentation/slider_button/slider.dart';
|
||||
|
||||
@@ -16,24 +16,18 @@ class _DeliveryMapScreen extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _DeliveryMapScreenState extends State<_DeliveryMapScreen> {
|
||||
GoogleMapController? _mapController;
|
||||
NearleMapController? _mapController;
|
||||
late final LatLng _pickupLocation;
|
||||
late final LatLng _dropLocation;
|
||||
final Set<Marker> _markers = {};
|
||||
final Set<Polyline> _polylines = {};
|
||||
late final PolylinePoints _polylinePoints;
|
||||
bool _isLoadingRoute = true;
|
||||
List<NearleMapMarker> _markers = const [];
|
||||
List<NearleMapPolyline> _polylines = const [];
|
||||
bool _isNavigating = false; // Prevent multiple clicks
|
||||
|
||||
static const String _googleApiKey = 'AIzaSyBhkGfnq27sN0wV5y_S-M2KojpFTk_by-Q';
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_polylinePoints = PolylinePoints(apiKey: _googleApiKey);
|
||||
_resolveLocationsFromDelivery();
|
||||
_setMarkers();
|
||||
_isLoadingRoute = false;
|
||||
_createRealRoute();
|
||||
}
|
||||
|
||||
@@ -73,113 +67,70 @@ class _DeliveryMapScreenState extends State<_DeliveryMapScreen> {
|
||||
_dropLocation = drop;
|
||||
}
|
||||
|
||||
void _setMarkers() {
|
||||
_markers.addAll([
|
||||
Marker(
|
||||
markerId: const MarkerId('pickup'),
|
||||
position: _pickupLocation,
|
||||
infoWindow: const InfoWindow(title: 'Pickup Location'),
|
||||
icon: BitmapDescriptor.defaultMarkerWithHue(BitmapDescriptor.hueRed),
|
||||
),
|
||||
Marker(
|
||||
markerId: const MarkerId('drop'),
|
||||
position: _dropLocation,
|
||||
infoWindow: const InfoWindow(title: 'Drop Location'),
|
||||
icon: BitmapDescriptor.defaultMarkerWithHue(BitmapDescriptor.hueGreen),
|
||||
),
|
||||
]);
|
||||
Future<void> _setMarkers() async {
|
||||
final pickupIcon = await MapMarkerIcons.pin(Colors.red);
|
||||
final dropIcon = await MapMarkerIcons.pin(Colors.green);
|
||||
if (!mounted) return;
|
||||
setState(() {
|
||||
_markers = [
|
||||
NearleMapMarker(
|
||||
id: 'pickup',
|
||||
position: _pickupLocation,
|
||||
icon: pickupIcon,
|
||||
),
|
||||
NearleMapMarker(id: 'drop', position: _dropLocation, icon: dropIcon),
|
||||
];
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> _createRealRoute() async {
|
||||
try {
|
||||
final request = PolylineRequest(
|
||||
origin: PointLatLng(
|
||||
_pickupLocation.latitude,
|
||||
_pickupLocation.longitude,
|
||||
),
|
||||
destination: PointLatLng(
|
||||
_dropLocation.latitude,
|
||||
_dropLocation.longitude,
|
||||
),
|
||||
mode: TravelMode.driving,
|
||||
);
|
||||
final route = await RoutingService.getRoute(
|
||||
origin: _pickupLocation,
|
||||
destination: _dropLocation,
|
||||
);
|
||||
|
||||
final result = await _polylinePoints.getRouteBetweenCoordinates(
|
||||
request: request,
|
||||
);
|
||||
|
||||
if (result.points.isNotEmpty) {
|
||||
final routePoints = result.points
|
||||
.map((e) => LatLng(e.latitude, e.longitude))
|
||||
.toList();
|
||||
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_polylines.add(
|
||||
Polyline(
|
||||
polylineId: const PolylineId('real_route'),
|
||||
color: ColorConstants.primaryColor,
|
||||
width: 6,
|
||||
points: routePoints,
|
||||
),
|
||||
);
|
||||
_isLoadingRoute = false;
|
||||
});
|
||||
|
||||
_fitMapToRoute();
|
||||
}
|
||||
} else {
|
||||
debugPrint('[MAP_PREVIEW] No route found');
|
||||
if (mounted) {
|
||||
setState(() => _isLoadingRoute = false);
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
debugPrint('[MAP_PREVIEW] Error creating route: $e');
|
||||
if (mounted) {
|
||||
setState(() => _isLoadingRoute = false);
|
||||
}
|
||||
}
|
||||
}
|
||||
Future<void> _fitMapToRoute() async {
|
||||
if (!mounted) return;
|
||||
if (_mapController == null) return;
|
||||
|
||||
// Check if controller is still alive (important!)
|
||||
try {
|
||||
await _mapController!.getVisibleRegion();
|
||||
} catch (e) {
|
||||
debugPrint("❌ Map controller is dead. Skip animateCamera.");
|
||||
return;
|
||||
}
|
||||
|
||||
final bounds = LatLngBounds(
|
||||
southwest: LatLng(
|
||||
math.min(_pickupLocation.latitude, _dropLocation.latitude),
|
||||
math.min(_pickupLocation.longitude, _dropLocation.longitude),
|
||||
),
|
||||
northeast: LatLng(
|
||||
math.max(_pickupLocation.latitude, _dropLocation.latitude),
|
||||
math.max(_pickupLocation.longitude, _dropLocation.longitude),
|
||||
),
|
||||
);
|
||||
|
||||
// Try animate safely
|
||||
for (int i = 0; i < 10; i++) {
|
||||
if (!mounted) return;
|
||||
|
||||
try {
|
||||
await _mapController!.animateCamera(
|
||||
CameraUpdate.newLatLngBounds(bounds, 80),
|
||||
);
|
||||
return;
|
||||
} catch (e) {
|
||||
await Future.delayed(const Duration(milliseconds: 150));
|
||||
}
|
||||
// Fall back to a straight line so the screen still shows the connection
|
||||
// when routing is unavailable.
|
||||
final points = (route != null && route.points.isNotEmpty)
|
||||
? route.points
|
||||
: <LatLng>[_pickupLocation, _dropLocation];
|
||||
|
||||
if (route == null) debugPrint('[MAP_PREVIEW] No route found');
|
||||
|
||||
setState(() {
|
||||
_polylines = [
|
||||
NearleMapPolyline(
|
||||
id: 'real_route',
|
||||
points: points,
|
||||
color: ColorConstants.primaryColor,
|
||||
width: 6,
|
||||
),
|
||||
];
|
||||
});
|
||||
|
||||
_fitMapToRoute();
|
||||
}
|
||||
|
||||
debugPrint("❌ animateCamera failed after retries (map probably disposed)");
|
||||
}
|
||||
Future<void> _fitMapToRoute() async {
|
||||
final controller = _mapController;
|
||||
if (!mounted || controller == null) return;
|
||||
|
||||
await controller.fitBounds(
|
||||
LatLngBounds(
|
||||
southwest: LatLng(
|
||||
math.min(_pickupLocation.latitude, _dropLocation.latitude),
|
||||
math.min(_pickupLocation.longitude, _dropLocation.longitude),
|
||||
),
|
||||
northeast: LatLng(
|
||||
math.max(_pickupLocation.latitude, _dropLocation.latitude),
|
||||
math.max(_pickupLocation.longitude, _dropLocation.longitude),
|
||||
),
|
||||
),
|
||||
padding: 80,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -188,27 +139,20 @@ Future<void> _fitMapToRoute() async {
|
||||
return Scaffold(
|
||||
body: Stack(
|
||||
children: [
|
||||
SizedBox.expand(
|
||||
child: GoogleMap(
|
||||
initialCameraPosition: CameraPosition(
|
||||
target: _pickupLocation,
|
||||
zoom: 14.5,
|
||||
),
|
||||
onMapCreated: (controller) {
|
||||
_mapController = controller;
|
||||
|
||||
Future.delayed(const Duration(milliseconds: 500), () {
|
||||
if (!_isLoadingRoute && _polylines.isNotEmpty) {
|
||||
_fitMapToRoute();
|
||||
}
|
||||
});
|
||||
},
|
||||
markers: _markers,
|
||||
polylines: _polylines,
|
||||
zoomControlsEnabled: false,
|
||||
myLocationButtonEnabled: false,
|
||||
),
|
||||
),
|
||||
SizedBox.expand(
|
||||
child: NearleMap(
|
||||
initialCameraPosition: CameraPosition(
|
||||
target: _pickupLocation,
|
||||
zoom: 14.5,
|
||||
),
|
||||
markers: _markers,
|
||||
polylines: _polylines,
|
||||
onMapReady: (controller) {
|
||||
_mapController = controller;
|
||||
_fitMapToRoute();
|
||||
},
|
||||
),
|
||||
),
|
||||
Positioned(
|
||||
top: 50,
|
||||
left: 16,
|
||||
|
||||
@@ -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();
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
@@ -19,12 +19,15 @@ class _RiderNavigationScreen extends StatefulWidget {
|
||||
|
||||
class _RiderNavigationScreenState extends State<_RiderNavigationScreen>
|
||||
with WidgetsBindingObserver {
|
||||
GoogleMapController? mapController;
|
||||
NearleMapController? mapController;
|
||||
bool _mapReady = false;
|
||||
bool _isDisposed = false;
|
||||
Position? _currentPosition;
|
||||
StreamSubscription<Position>? _positionStream;
|
||||
final Set<Marker> _markers = {};
|
||||
List<NearleMapMarker> _markers = const [];
|
||||
MarkerIcon? _riderIcon;
|
||||
MarkerIcon? _dropIcon;
|
||||
LatLng? _lastRiderPosition;
|
||||
late final LatLng dropLocation;
|
||||
bool _isInitializing = true;
|
||||
bool _hasOpenedGoogleMaps = false;
|
||||
@@ -46,6 +49,7 @@ class _RiderNavigationScreenState extends State<_RiderNavigationScreen>
|
||||
super.initState();
|
||||
WidgetsBinding.instance.addObserver(this);
|
||||
_resolveDropFromDelivery();
|
||||
_loadMarkerIcons();
|
||||
_initializeMap();
|
||||
_initEtaForNav();
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
@@ -214,8 +218,8 @@ class _RiderNavigationScreenState extends State<_RiderNavigationScreen>
|
||||
if (lastPos != null && mounted) {
|
||||
setState(() {
|
||||
_currentPosition = lastPos;
|
||||
_addMarkers(LatLng(lastPos.latitude, lastPos.longitude));
|
||||
});
|
||||
_addMarkers(LatLng(lastPos.latitude, lastPos.longitude));
|
||||
_startLiveTracking();
|
||||
}
|
||||
});
|
||||
@@ -338,8 +342,8 @@ class _RiderNavigationScreenState extends State<_RiderNavigationScreen>
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_currentPosition = position;
|
||||
_addMarkers(LatLng(position.latitude, position.longitude));
|
||||
});
|
||||
_addMarkers(LatLng(position.latitude, position.longitude));
|
||||
|
||||
final distance = Geolocator.distanceBetween(
|
||||
position.latitude,
|
||||
@@ -362,27 +366,36 @@ class _RiderNavigationScreenState extends State<_RiderNavigationScreen>
|
||||
});
|
||||
}
|
||||
|
||||
/// Marker bitmaps are drawn on a canvas, so they arrive asynchronously.
|
||||
/// Once they land, rebuild whatever markers are already pending.
|
||||
Future<void> _loadMarkerIcons() async {
|
||||
final rider = await MapMarkerIcons.dot(Colors.blueAccent);
|
||||
final drop = await MapMarkerIcons.pin(Colors.red);
|
||||
if (!mounted || _isDisposed) return;
|
||||
setState(() {
|
||||
_riderIcon = rider;
|
||||
_dropIcon = drop;
|
||||
_addMarkers(_lastRiderPosition ?? dropLocation);
|
||||
});
|
||||
}
|
||||
|
||||
/// Mutates [_markers] only — callers own the [setState] so this can be
|
||||
/// invoked from inside an existing one without nesting.
|
||||
void _addMarkers(LatLng riderPos) {
|
||||
_markers
|
||||
..clear()
|
||||
..add(
|
||||
Marker(
|
||||
markerId: const MarkerId('rider'),
|
||||
position: riderPos,
|
||||
icon: BitmapDescriptor.defaultMarkerWithHue(
|
||||
BitmapDescriptor.hueAzure,
|
||||
),
|
||||
infoWindow: const InfoWindow(title: 'You (Rider)'),
|
||||
),
|
||||
)
|
||||
..add(
|
||||
Marker(
|
||||
markerId: const MarkerId('drop'),
|
||||
position: dropLocation,
|
||||
icon: BitmapDescriptor.defaultMarkerWithHue(BitmapDescriptor.hueRed),
|
||||
infoWindow: const InfoWindow(title: 'Drop Location'),
|
||||
),
|
||||
);
|
||||
_lastRiderPosition = riderPos;
|
||||
final riderIcon = _riderIcon;
|
||||
final dropIcon = _dropIcon;
|
||||
if (riderIcon == null || dropIcon == null) return;
|
||||
|
||||
_markers = [
|
||||
NearleMapMarker(
|
||||
id: 'rider',
|
||||
position: riderPos,
|
||||
icon: riderIcon,
|
||||
anchor: MarkerAnchor.center,
|
||||
),
|
||||
NearleMapMarker(id: 'drop', position: dropLocation, icon: dropIcon),
|
||||
];
|
||||
}
|
||||
|
||||
Future<void> _attemptAutoOpenNavigation({bool fromTimer = false}) async {
|
||||
@@ -511,9 +524,8 @@ class _RiderNavigationScreenState extends State<_RiderNavigationScreen>
|
||||
return Scaffold(
|
||||
body: Stack(
|
||||
children: [
|
||||
GoogleMap(
|
||||
mapType: MapType.normal,
|
||||
onMapCreated: (controller) {
|
||||
NearleMap(
|
||||
onMapReady: (controller) {
|
||||
mapController = controller;
|
||||
_mapReady = true;
|
||||
},
|
||||
@@ -527,10 +539,7 @@ class _RiderNavigationScreenState extends State<_RiderNavigationScreen>
|
||||
),
|
||||
markers: _markers,
|
||||
myLocationEnabled: true,
|
||||
myLocationButtonEnabled: false,
|
||||
trafficEnabled: true,
|
||||
compassEnabled: true,
|
||||
buildingsEnabled: true,
|
||||
tiltGesturesEnabled: true,
|
||||
),
|
||||
if (!_hasOpenedGoogleMaps)
|
||||
@@ -994,11 +1003,7 @@ class _RiderNavigationScreenState extends State<_RiderNavigationScreen>
|
||||
}
|
||||
|
||||
void _safeAnimateCamera(CameraUpdate update) {
|
||||
if (!_mapReady || _isDisposed || mapController == null) return;
|
||||
try {
|
||||
mapController?.animateCamera(update);
|
||||
} catch (e) {
|
||||
debugPrint('[NAVIGATION] animateCamera skipped: $e');
|
||||
}
|
||||
if (!_mapReady || _isDisposed) return;
|
||||
mapController?.animateCamera(update);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user