832 lines
38 KiB
Dart
832 lines
38 KiB
Dart
part of 'deliveries.dart';
|
|
|
|
// -------------------------------------------------------------------------
|
|
// SCREEN 1: DELIVERY MAP PREVIEW (Shows route, has "Start" button)
|
|
// -------------------------------------------------------------------------
|
|
class _DeliveryMapScreen extends StatefulWidget {
|
|
final Map<String, dynamic> delivery;
|
|
final _MyDeliveriesState? parentState;
|
|
const _DeliveryMapScreen({
|
|
required this.delivery,
|
|
this.parentState,
|
|
});
|
|
|
|
@override
|
|
State<_DeliveryMapScreen> createState() => _DeliveryMapScreenState();
|
|
}
|
|
|
|
class _DeliveryMapScreenState extends State<_DeliveryMapScreen> {
|
|
GoogleMapController? _mapController;
|
|
late final LatLng _pickupLocation;
|
|
late final LatLng _dropLocation;
|
|
final Set<Marker> _markers = {};
|
|
final Set<Polyline> _polylines = {};
|
|
late final PolylinePoints _polylinePoints;
|
|
bool _isLoadingRoute = true;
|
|
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();
|
|
}
|
|
|
|
double _parseD(dynamic v) {
|
|
if (v == null) return 0.0;
|
|
if (v is num) return v.toDouble();
|
|
return double.tryParse(v.toString()) ?? 0.0;
|
|
}
|
|
|
|
void _resolveLocationsFromDelivery() {
|
|
final d = widget.delivery;
|
|
final double pickLat = _parseD(d['pickuplat'] ?? d['PickupLat']);
|
|
final double pickLon = _parseD(d['pickuplon'] ?? d['PickupLon']);
|
|
final double dropLat = _parseD(
|
|
d['droplat'] ?? d['DropLat'] ?? d['deliverylat'],
|
|
);
|
|
final double dropLon = _parseD(
|
|
d['droplon'] ?? d['DropLon'] ?? d['deliverylong'],
|
|
);
|
|
final double riderLat = _parseD(d['riderslat']);
|
|
final double riderLon = _parseD(d['riderslon']);
|
|
|
|
final bool hasPickup = pickLat != 0 && pickLon != 0;
|
|
final bool hasDrop = dropLat != 0 && dropLon != 0;
|
|
|
|
final LatLng pickup = hasPickup
|
|
? LatLng(pickLat, pickLon)
|
|
: (riderLat != 0 && riderLon != 0
|
|
? LatLng(riderLat, riderLon)
|
|
: const LatLng(10.998356, 76.977596));
|
|
|
|
final LatLng drop = hasDrop
|
|
? LatLng(dropLat, dropLon)
|
|
: const LatLng(11.004556, 76.967696);
|
|
|
|
_pickupLocation = pickup;
|
|
_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> _createRealRoute() async {
|
|
try {
|
|
final request = PolylineRequest(
|
|
origin: PointLatLng(
|
|
_pickupLocation.latitude,
|
|
_pickupLocation.longitude,
|
|
),
|
|
destination: PointLatLng(
|
|
_dropLocation.latitude,
|
|
_dropLocation.longitude,
|
|
),
|
|
mode: TravelMode.driving,
|
|
);
|
|
|
|
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));
|
|
}
|
|
}
|
|
|
|
debugPrint("❌ animateCamera failed after retries (map probably disposed)");
|
|
}
|
|
|
|
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
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,
|
|
),
|
|
),
|
|
Positioned(
|
|
top: 50,
|
|
left: 16,
|
|
child: CircleAvatar(
|
|
backgroundColor: Colors.white,
|
|
child: IconButton(
|
|
icon: const Icon(Icons.arrow_back, color: Colors.black),
|
|
onPressed: () => Navigator.pop(context),
|
|
),
|
|
),
|
|
),
|
|
Align(
|
|
alignment: Alignment.bottomCenter,
|
|
child: Container(
|
|
width: double.infinity,
|
|
padding: const EdgeInsets.all(16),
|
|
decoration: const BoxDecoration(
|
|
color: Colors.white,
|
|
borderRadius: BorderRadius.vertical(top: Radius.circular(20)),
|
|
boxShadow: [
|
|
BoxShadow(
|
|
color: Colors.black26,
|
|
blurRadius: 8,
|
|
offset: Offset(0, -2),
|
|
),
|
|
],
|
|
),
|
|
child: SafeArea(
|
|
top: false,
|
|
child: LayoutBuilder(
|
|
builder: (ctx, constraints) {
|
|
final double maxSheetHeight =
|
|
MediaQuery.of(ctx).size.height * 0.35;
|
|
// ignore: unused_local_variable
|
|
final double allowedHeight = math.min(
|
|
constraints.maxHeight,
|
|
maxSheetHeight,
|
|
);
|
|
return ListView(
|
|
padding: EdgeInsets.zero,
|
|
shrinkWrap: true,
|
|
children: [
|
|
Row(
|
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
children: [
|
|
Text(
|
|
'Customer Details',
|
|
style: TextStyle(
|
|
fontSize: FontConstants.xxLarge(context),
|
|
fontWeight: FontWeight.bold,
|
|
fontFamily: FontConstants.fontFamily,
|
|
),
|
|
),
|
|
InkWell(
|
|
onTap: () async {
|
|
// Just launch dialer; PiP is handled only from navigation screen
|
|
final phone =
|
|
(widget.delivery['deliverycontactno'] ?? '')
|
|
.toString();
|
|
final bool success = await launchPhoneDialer(
|
|
phone.isNotEmpty ? phone : '9876543210',
|
|
);
|
|
if (!success && context.mounted) {
|
|
ScaffoldMessenger.of(context).showSnackBar(
|
|
const SnackBar(
|
|
content: Text('Could not launch dialer'),
|
|
),
|
|
);
|
|
}
|
|
},
|
|
child: Image.asset(
|
|
'assets/images/phone-call .png',
|
|
height: 27,
|
|
width: 27,
|
|
errorBuilder: (c, e, s) => const Icon(
|
|
Icons.phone,
|
|
size: 27,
|
|
color: Colors.green,
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
const SizedBox(height: 8),
|
|
const Divider(thickness: 1),
|
|
const SizedBox(height: 8),
|
|
_buildCustomerInfo(),
|
|
const SizedBox(height: 20),
|
|
ElevatedButton(
|
|
style: ElevatedButton.styleFrom(
|
|
backgroundColor: ColorConstants.primaryColor,
|
|
minimumSize: const Size(double.infinity, 48),
|
|
shape: RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.circular(12),
|
|
),
|
|
),
|
|
onPressed: _isNavigating ? null : () async {
|
|
// Prevent multiple clicks
|
|
if (_isNavigating || !mounted || !context.mounted) return;
|
|
|
|
setState(() {
|
|
_isNavigating = true;
|
|
});
|
|
|
|
try {
|
|
// Capture screen size before navigation
|
|
final screenSize = MediaQuery.of(context).size *
|
|
MediaQuery.of(context).devicePixelRatio;
|
|
|
|
final dc = Get.put(
|
|
DeliveriesController(),
|
|
permanent: true,
|
|
);
|
|
final d = widget.delivery;
|
|
final int deliveryId =
|
|
int.tryParse(
|
|
'${d['deliveryid'] ?? d['DeliveryId'] ?? 0}',
|
|
) ??
|
|
0;
|
|
final String orderId = (d['orderid'] ??
|
|
d['OrderId'] ??
|
|
'')
|
|
.toString();
|
|
final int orderHeaderId =
|
|
int.tryParse(
|
|
'${d['orderheaderid'] ?? d['OrderHeaderId'] ?? 0}',
|
|
) ??
|
|
0;
|
|
// Save ridertime start at the moment navigation is started
|
|
try {
|
|
if (deliveryId > 0) {
|
|
final prefs =
|
|
await SharedPreferences.getInstance();
|
|
// 1) Save rider time start (existing behaviour)
|
|
await prefs.setString(
|
|
'ridertime_start_$deliveryId',
|
|
DateTime.now().toIso8601String(),
|
|
);
|
|
// 2) Save ETA end time for this order so PiP timer can resume correctly
|
|
final rawEta = d['eta'];
|
|
int etaMinutes = 0;
|
|
if (rawEta != null) {
|
|
etaMinutes =
|
|
int.tryParse(rawEta.toString()) ?? 0;
|
|
}
|
|
if (etaMinutes > 0) {
|
|
final now = DateTime.now();
|
|
final endTime = now
|
|
.add(Duration(minutes: etaMinutes))
|
|
.millisecondsSinceEpoch ~/
|
|
1000; // store seconds
|
|
await prefs.setInt(
|
|
'eta_endtime_$orderId',
|
|
endTime,
|
|
);
|
|
debugPrint(
|
|
'[ACTIVE][ETA] Saved eta_endtime_$orderId -> $endTime (eta=$etaMinutes min)',
|
|
);
|
|
}
|
|
debugPrint(
|
|
'[ACTIVE] Saved ridertime_start for deliveryId=$deliveryId',
|
|
);
|
|
}
|
|
} catch (e) {
|
|
debugPrint(
|
|
'[ACTIVE] Error saving ridertime_start: $e',
|
|
);
|
|
}
|
|
|
|
final parentState =
|
|
widget.parentState ??
|
|
context
|
|
.findAncestorStateOfType<
|
|
_MyDeliveriesState
|
|
>();
|
|
|
|
// ✅ CRITICAL: Check active delivery BEFORE navigation
|
|
if (parentState != null) {
|
|
final currentOrderId = (d['orderid'] ??
|
|
d['OrderId'] ??
|
|
'')
|
|
.toString();
|
|
final activeOrderIds = parentState
|
|
._activeDeliveries
|
|
.map(
|
|
(del) => (del['orderid'] ??
|
|
del['OrderId'] ??
|
|
'')
|
|
.toString(),
|
|
)
|
|
.where((id) => id.isNotEmpty)
|
|
.toSet();
|
|
|
|
// Block if there's a different active delivery
|
|
if (parentState._activeDeliveries.isNotEmpty &&
|
|
!activeOrderIds.contains(currentOrderId)) {
|
|
if (mounted) {
|
|
setState(() {
|
|
_isNavigating = false;
|
|
});
|
|
showDialog(
|
|
context: context,
|
|
barrierDismissible: true,
|
|
builder: (BuildContext dialogContext) {
|
|
return Dialog(
|
|
shape: RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.circular(20),
|
|
),
|
|
child: Container(
|
|
padding: const EdgeInsets.all(24),
|
|
decoration: BoxDecoration(
|
|
borderRadius: BorderRadius.circular(20),
|
|
gradient: LinearGradient(
|
|
begin: Alignment.topLeft,
|
|
end: Alignment.bottomRight,
|
|
colors: [
|
|
Colors.orange.shade400,
|
|
Colors.red.shade500,
|
|
],
|
|
),
|
|
),
|
|
child: Column(
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: [
|
|
// Icon
|
|
Container(
|
|
padding: const EdgeInsets.all(16),
|
|
decoration: BoxDecoration(
|
|
color: Colors.white.withOpacity(0.2),
|
|
shape: BoxShape.circle,
|
|
),
|
|
child: const Icon(
|
|
Icons.warning_rounded,
|
|
color: Colors.white,
|
|
size: 48,
|
|
),
|
|
),
|
|
const SizedBox(height: 20),
|
|
// Title
|
|
Text(
|
|
'Active Delivery in Progress',
|
|
textAlign: TextAlign.center,
|
|
style: TextStyle(
|
|
color: Colors.white,
|
|
fontSize: FontConstants.xxLarge(context),
|
|
fontWeight: FontWeight.bold,
|
|
fontFamily: FontConstants.fontFamily,
|
|
),
|
|
),
|
|
const SizedBox(height: 12),
|
|
// Message
|
|
Text(
|
|
'Please complete your active delivery first before starting another delivery.',
|
|
textAlign: TextAlign.center,
|
|
style: TextStyle(
|
|
color: Colors.white.withOpacity(0.95),
|
|
fontSize: FontConstants.medium(context),
|
|
height: 1.4,
|
|
fontFamily: FontConstants.fontFamily,
|
|
),
|
|
),
|
|
const SizedBox(height: 24),
|
|
// Action Button
|
|
SizedBox(
|
|
width: double.infinity,
|
|
child: ElevatedButton(
|
|
onPressed: () {
|
|
Navigator.of(dialogContext).pop();
|
|
// Navigate to active delivery
|
|
if (parentState._activeDeliveries.isNotEmpty) {
|
|
parentState.startDelivery(parentState._activeDeliveries.first);
|
|
}
|
|
},
|
|
style: ElevatedButton.styleFrom(
|
|
backgroundColor: Colors.white,
|
|
foregroundColor: Colors.red.shade600,
|
|
padding: const EdgeInsets.symmetric(vertical: 16),
|
|
shape: RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.circular(12),
|
|
),
|
|
elevation: 2,
|
|
),
|
|
child: Row(
|
|
mainAxisAlignment: MainAxisAlignment.center,
|
|
children: [
|
|
const Icon(
|
|
Icons.two_wheeler,
|
|
size: 22,
|
|
),
|
|
const SizedBox(width: 8),
|
|
Text(
|
|
'View Active Delivery',
|
|
style: TextStyle(
|
|
fontSize: FontConstants.regular(context),
|
|
fontWeight: FontWeight.bold,
|
|
fontFamily: FontConstants.fontFamily,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
const SizedBox(height: 12),
|
|
// Close Button
|
|
TextButton(
|
|
onPressed: () {
|
|
Navigator.of(dialogContext).pop();
|
|
},
|
|
child: Text(
|
|
'Close',
|
|
style: TextStyle(
|
|
color: Colors.white.withOpacity(0.9),
|
|
fontSize: 15,
|
|
fontWeight: FontWeight.w500,
|
|
fontFamily: FontConstants.fontFamily,
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
},
|
|
);
|
|
}
|
|
return; // Return early to prevent starting another delivery
|
|
}
|
|
}
|
|
|
|
if (deliveryId > 0 && orderId.isNotEmpty) {
|
|
String riderLatStr = '0';
|
|
String riderLngStr = '0';
|
|
try {
|
|
final position =
|
|
await Geolocator.getCurrentPosition(
|
|
locationSettings: const LocationSettings(
|
|
accuracy: LocationAccuracy.medium,
|
|
timeLimit: Duration(seconds: 3),
|
|
),
|
|
).timeout(const Duration(seconds: 3));
|
|
riderLatStr = position.latitude
|
|
.toStringAsFixed(6);
|
|
riderLngStr = position.longitude
|
|
.toStringAsFixed(6);
|
|
} catch (e) {
|
|
debugPrint(
|
|
'[ACTIVE] Error getting rider location: $e',
|
|
);
|
|
try {
|
|
final lastPos =
|
|
await Geolocator.getLastKnownPosition();
|
|
if (lastPos != null) {
|
|
riderLatStr = lastPos.latitude
|
|
.toStringAsFixed(6);
|
|
riderLngStr = lastPos.longitude
|
|
.toStringAsFixed(6);
|
|
}
|
|
} catch (_) {}
|
|
}
|
|
|
|
// ✅ CRITICAL: Navigate FIRST, then handle status updates
|
|
if (!mounted || !context.mounted) {
|
|
setState(() {
|
|
_isNavigating = false;
|
|
});
|
|
return;
|
|
}
|
|
|
|
// Navigate immediately - this must happen
|
|
Navigator.of(context).push(
|
|
MaterialPageRoute(
|
|
builder: (context) => _RiderNavigationScreen(
|
|
delivery: widget.delivery,
|
|
parentState: widget.parentState,
|
|
),
|
|
),
|
|
);
|
|
|
|
// Reset navigation state after navigation completes
|
|
setState(() {
|
|
_isNavigating = false;
|
|
});
|
|
|
|
// Continue with status updates and PiP in background
|
|
debugPrint(
|
|
'[ACTIVE] Updating status for deliveryId=$deliveryId orderId=$orderId lat=$riderLatStr lng=$riderLngStr',
|
|
);
|
|
|
|
final ok = await dc.updateActiveStatus(
|
|
deliveryId: deliveryId,
|
|
orderHeaderId: orderHeaderId,
|
|
ridersLat: riderLatStr,
|
|
ridersLng: riderLngStr,
|
|
orderId: orderId,
|
|
);
|
|
|
|
debugPrint(
|
|
'[ACTIVE] Status update result: $ok',
|
|
);
|
|
|
|
// ✅ CRITICAL: ENFORCE PiP when delivery becomes active (compulsory)
|
|
if (ok && !dc.isPipEnabled.value) {
|
|
try {
|
|
debugPrint('[ACTIVE] Delivery is now active - Enforcing PiP mode');
|
|
final floating = Floating();
|
|
const rational = Rational.landscape();
|
|
|
|
final height = (screenSize.height * 0.5).toInt();
|
|
final width = (screenSize.width * 0.9).toInt();
|
|
|
|
final arguments = ImmediatePiP(
|
|
aspectRatio: rational,
|
|
sourceRectHint: math.Rectangle<int>(
|
|
((screenSize.width - width) ~/ 2).toInt(),
|
|
((screenSize.height - height) ~/ 2).toInt(),
|
|
width,
|
|
height,
|
|
),
|
|
);
|
|
|
|
await floating.enable(arguments);
|
|
dc.isPipEnabled.value = true;
|
|
debugPrint('[ACTIVE] PiP enabled successfully');
|
|
|
|
// Also try method channel as backup
|
|
try {
|
|
const channel = MethodChannel('nearle/pip');
|
|
await channel.invokeMethod<bool>('enterPip');
|
|
} catch (_) {}
|
|
} catch (e) {
|
|
debugPrint('[ACTIVE] Error enabling PiP: $e');
|
|
}
|
|
}
|
|
|
|
if (parentState != null) {
|
|
final previousActive =
|
|
parentState._activeDeliveryOrderId;
|
|
if (previousActive != null &&
|
|
previousActive != orderId) {
|
|
parentState._stopDeliveryPosting(
|
|
previousActive,
|
|
);
|
|
}
|
|
|
|
parentState._activeDeliveryOrderId = orderId;
|
|
d['orderstatus'] = 'active';
|
|
|
|
final orderKey = parentState._getOrderKey(d);
|
|
if (parentState._skippedOrdersCache
|
|
.containsKey(orderKey)) {
|
|
parentState._skippedOrdersCache.remove(
|
|
orderKey,
|
|
);
|
|
parentState._skippedOrderTimestamps.remove(
|
|
orderKey,
|
|
);
|
|
await parentState._saveSkippedOrdersCache();
|
|
debugPrint(
|
|
'[ACTIVE] Removed from skipped cache (resumed): $orderKey',
|
|
);
|
|
}
|
|
|
|
await parentState._startDeliveryPosting(d);
|
|
|
|
try {
|
|
final prefs =
|
|
await SharedPreferences.getInstance();
|
|
await prefs.setString(
|
|
'active_delivery_order_id',
|
|
orderId,
|
|
);
|
|
} catch (e) {
|
|
debugPrint(
|
|
'[ACTIVE] Error saving active delivery ID: $e',
|
|
);
|
|
}
|
|
|
|
// ignore: invalid_use_of_protected_member
|
|
parentState.setState(() {});
|
|
}
|
|
} else {
|
|
debugPrint(
|
|
'[ACTIVE] Invalid deliveryId: $deliveryId or orderId: $orderId',
|
|
);
|
|
setState(() {
|
|
_isNavigating = false;
|
|
});
|
|
}
|
|
} catch (e) {
|
|
debugPrint(
|
|
'[ACTIVE] Error in navigation flow: $e',
|
|
);
|
|
if (mounted) {
|
|
setState(() {
|
|
_isNavigating = false;
|
|
});
|
|
}
|
|
}
|
|
},
|
|
child: _isNavigating
|
|
? Row(
|
|
mainAxisAlignment: MainAxisAlignment.center,
|
|
children: [
|
|
SizedBox(
|
|
width: 20,
|
|
height: 20,
|
|
child: CircularProgressIndicator(
|
|
strokeWidth: 2,
|
|
valueColor: AlwaysStoppedAnimation<Color>(
|
|
Colors.white,
|
|
),
|
|
),
|
|
),
|
|
const SizedBox(width: 12),
|
|
Text(
|
|
'Starting...',
|
|
style: TextStyle(
|
|
fontSize: FontConstants.xLarge(context),
|
|
fontWeight: FontWeight.bold,
|
|
fontFamily: FontConstants.fontFamily,
|
|
color: Colors.white,
|
|
),
|
|
),
|
|
],
|
|
)
|
|
: Text(
|
|
'Start Navigation',
|
|
style: TextStyle(
|
|
fontSize: FontConstants.xLarge(context),
|
|
fontWeight: FontWeight.bold,
|
|
fontFamily: FontConstants.fontFamily,
|
|
color: Colors.white,
|
|
),
|
|
),
|
|
),
|
|
],
|
|
);
|
|
},
|
|
),
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildCustomerInfo() {
|
|
return Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
_buildInfoRow(
|
|
'Name:',
|
|
(widget.delivery['deliverycustomer'] ?? 'Customer').toString(),
|
|
),
|
|
const SizedBox(height: 10),
|
|
_buildInfoRow(
|
|
'Address:',
|
|
(widget.delivery['deliveryaddress'] ?? 'Address not available')
|
|
.toString(),
|
|
isExpanded: true,
|
|
),
|
|
const SizedBox(height: 10),
|
|
_buildInfoRow(
|
|
'Order ID:',
|
|
'#${(widget.delivery['orderid'] ?? '').toString()}',
|
|
),
|
|
const SizedBox(height: 10),
|
|
_buildInfoRow(
|
|
'Distance:',
|
|
'${(widget.delivery['kms'] ?? '0').toString()} km',
|
|
valueColor: Colors.red,
|
|
),
|
|
],
|
|
);
|
|
}
|
|
|
|
Widget _buildInfoRow(
|
|
String label,
|
|
String value, {
|
|
bool isExpanded = false,
|
|
Color? valueColor,
|
|
}) {
|
|
return Row(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(
|
|
label,
|
|
style: TextStyle(
|
|
fontSize: FontConstants.xLarge(context),
|
|
fontWeight: FontWeight.w600,
|
|
fontFamily: FontConstants.fontFamily,
|
|
),
|
|
),
|
|
const SizedBox(width: 15),
|
|
isExpanded
|
|
? Expanded(
|
|
child: Text(
|
|
value,
|
|
maxLines: 2,
|
|
overflow: TextOverflow.ellipsis,
|
|
style: TextStyle(
|
|
fontSize: FontConstants.large(context),
|
|
fontFamily: FontConstants.fontFamily,
|
|
color: valueColor ?? Colors.black87,
|
|
),
|
|
),
|
|
)
|
|
: Text(
|
|
value,
|
|
style: TextStyle(
|
|
fontSize: FontConstants.large(context),
|
|
fontFamily: FontConstants.fontFamily,
|
|
color: valueColor ?? Colors.black87,
|
|
),
|
|
),
|
|
],
|
|
);
|
|
}
|
|
}
|