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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'dart:convert';
|
||||
import 'package:flutter_polyline_points/flutter_polyline_points.dart';
|
||||
import 'dart:async';
|
||||
import 'package:image_picker/image_picker.dart';
|
||||
import 'dart:io';
|
||||
@@ -22,8 +21,10 @@ import 'package:nearle/controllers/delivery.dart';
|
||||
import 'package:nearle/controllers/deliveries_controller.dart';
|
||||
import 'package:geolocator/geolocator.dart';
|
||||
import 'package:slide_to_submit_button/slide_to_submit_button.dart';
|
||||
import 'package:google_maps_flutter/google_maps_flutter.dart';
|
||||
import 'package:geocoding/geocoding.dart';
|
||||
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:url_launcher/url_launcher.dart';
|
||||
import 'package:floating/floating.dart';
|
||||
import 'dart:math' as math;
|
||||
@@ -2624,7 +2625,6 @@ void _showmapDetailsSheet(
|
||||
double pickupLat = parseD(pickupLatStr);
|
||||
double pickupLng = parseD(pickupLngStr);
|
||||
bool hasPickup = pickupLat != 0 && pickupLng != 0;
|
||||
final Completer<GoogleMapController> mapController = Completer();
|
||||
|
||||
double riderLat = 0.0;
|
||||
double riderLng = 0.0;
|
||||
@@ -2661,36 +2661,46 @@ void _showmapDetailsSheet(
|
||||
|
||||
final bool hasAny = hasRiderLocation && hasPickup;
|
||||
|
||||
final List<LatLng> polylineCoords = [];
|
||||
final Set<Polyline> polylines = {};
|
||||
|
||||
const String googleAPIKey = "AIzaSyBhkGfnq27sN0wV5y_S-M2KojpFTk_by-Q";
|
||||
final List<NearleMapPolyline> polylines = [];
|
||||
|
||||
if (hasRiderLocation && hasPickup) {
|
||||
final PolylinePoints polylinePoints = PolylinePoints(apiKey: googleAPIKey);
|
||||
final PolylineResult result = await polylinePoints
|
||||
.getRouteBetweenCoordinates(
|
||||
request: PolylineRequest(
|
||||
origin: PointLatLng(riderLat, riderLng),
|
||||
destination: PointLatLng(pickupLat, pickupLng),
|
||||
mode: TravelMode.driving,
|
||||
),
|
||||
);
|
||||
if (result.points.isNotEmpty) {
|
||||
for (var point in result.points) {
|
||||
polylineCoords.add(LatLng(point.latitude, point.longitude));
|
||||
}
|
||||
final route = await RoutingService.getRoute(
|
||||
origin: LatLng(riderLat, riderLng),
|
||||
destination: LatLng(pickupLat, pickupLng),
|
||||
);
|
||||
if (route != null && route.points.isNotEmpty) {
|
||||
polylines.add(
|
||||
Polyline(
|
||||
polylineId: const PolylineId("route"),
|
||||
NearleMapPolyline(
|
||||
id: 'route',
|
||||
points: route.points,
|
||||
color: Colors.blueAccent,
|
||||
width: 5,
|
||||
points: polylineCoords,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
final List<NearleMapMarker> mapMarkers = [];
|
||||
if (hasRiderLocation) {
|
||||
mapMarkers.add(
|
||||
NearleMapMarker(
|
||||
id: 'rider',
|
||||
position: LatLng(riderLat, riderLng),
|
||||
icon: await MapMarkerIcons.dot(Colors.blueAccent),
|
||||
anchor: MarkerAnchor.center,
|
||||
),
|
||||
);
|
||||
}
|
||||
if (hasPickup) {
|
||||
mapMarkers.add(
|
||||
NearleMapMarker(
|
||||
id: 'pickup',
|
||||
position: LatLng(pickupLat, pickupLng),
|
||||
icon: await MapMarkerIcons.pin(Colors.red),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// ⭐ STEP 3 — Close loader
|
||||
Navigator.pop(context);
|
||||
|
||||
@@ -2848,7 +2858,7 @@ void _showmapDetailsSheet(
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(color: Colors.grey.shade200),
|
||||
),
|
||||
child: GoogleMap(
|
||||
child: NearleMap(
|
||||
initialCameraPosition: CameraPosition(
|
||||
target: hasRiderLocation && hasPickup
|
||||
? LatLng(
|
||||
@@ -2862,52 +2872,22 @@ void _showmapDetailsSheet(
|
||||
: const LatLng(0, 0),
|
||||
zoom: hasRiderLocation && hasPickup ? 13 : 15,
|
||||
),
|
||||
markers: {
|
||||
if (hasRiderLocation)
|
||||
Marker(
|
||||
markerId: const MarkerId("rider"),
|
||||
position: LatLng(riderLat, riderLng),
|
||||
infoWindow: const InfoWindow(
|
||||
title: "Your Location",
|
||||
),
|
||||
icon: BitmapDescriptor.defaultMarkerWithHue(
|
||||
BitmapDescriptor.hueAzure,
|
||||
),
|
||||
),
|
||||
if (hasPickup)
|
||||
Marker(
|
||||
markerId: const MarkerId("pickup"),
|
||||
position: LatLng(pickupLat, pickupLng),
|
||||
infoWindow: const InfoWindow(
|
||||
title: "Pickup Location",
|
||||
),
|
||||
icon: BitmapDescriptor.defaultMarkerWithHue(
|
||||
BitmapDescriptor.hueRed,
|
||||
),
|
||||
),
|
||||
},
|
||||
markers: mapMarkers,
|
||||
polylines: polylines,
|
||||
zoomGesturesEnabled: true,
|
||||
scrollGesturesEnabled: true,
|
||||
tiltGesturesEnabled: true,
|
||||
rotateGesturesEnabled: true,
|
||||
zoomControlsEnabled: false,
|
||||
myLocationButtonEnabled: false,
|
||||
onMapCreated: (GoogleMapController controller) async {
|
||||
mapController.complete(controller);
|
||||
onMapReady: (controller) async {
|
||||
if (hasRiderLocation && hasPickup) {
|
||||
final bounds = LatLngBounds(
|
||||
southwest: LatLng(
|
||||
riderLat < pickupLat ? riderLat : pickupLat,
|
||||
riderLng < pickupLng ? riderLng : pickupLng,
|
||||
await controller.fitBounds(
|
||||
LatLngBounds(
|
||||
southwest: LatLng(
|
||||
riderLat < pickupLat ? riderLat : pickupLat,
|
||||
riderLng < pickupLng ? riderLng : pickupLng,
|
||||
),
|
||||
northeast: LatLng(
|
||||
riderLat > pickupLat ? riderLat : pickupLat,
|
||||
riderLng > pickupLng ? riderLng : pickupLng,
|
||||
),
|
||||
),
|
||||
northeast: LatLng(
|
||||
riderLat > pickupLat ? riderLat : pickupLat,
|
||||
riderLng > pickupLng ? riderLng : pickupLng,
|
||||
),
|
||||
);
|
||||
await controller.animateCamera(
|
||||
CameraUpdate.newLatLngBounds(bounds, 80),
|
||||
padding: 80,
|
||||
);
|
||||
}
|
||||
},
|
||||
|
||||
@@ -45,7 +45,7 @@ class HelpCenter extends StatelessWidget {
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
"We make sure your delivery experience is smooth and clear. Whether you’re on your first trip or your hundredth, we’ve got your back. Browse through frequently asked questions or reach out directly if you need further help.",
|
||||
"We make sure your delivery experience is smooth and clear. Whether you're on your first trip or your hundredth, we've got your back. Browse through frequently asked questions or reach out directly if you need further help.",
|
||||
style: TextStyle(fontSize: 18,color: Colors.grey.shade700, height: 1.4, fontFamily: FontConstants.fontFamily),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
|
||||
Reference in New Issue
Block a user