Design system - MilerSurface ladder (canvas → working → raised → floating) with MilerPanel as layer 1; canvas moved to #DEE3EA so white separates at 1.290:1. - Visible vocabulary applied across Home, Deliveries, Activity, Account and the sheets: hero heads (tabular numeral + small caption, clamped at 1.3x), canvas wells for anything that opens, small filled tags for shelf labels, demoted placeholders. Recorded in DESIGN_SYSTEM.md §6. - One icon family: 222 Material glyphs migrated to Lucide; none left outside lib/xpress. - Colour semantics corrected: amber only for what is genuinely owed, brand red reserved for the live stop, disabled primaries go neutral rather than pale. Data and lifecycle - lib/data/lifecycle.dart reads mutations for what they prove; route_order.dart makes admin sequence the single ordering authority; service_day.dart, and stop_area.dart rewritten against live Coimbatore addresses (digit-token stripping, city stoplist, street suffixes, stammer collapse). - countLabel states the load once, in bags. Testing - 1440 tests passing; golden shot harnesses for Home, Deliveries, Activity, sheets and verify, with test/failures/ now gitignored (diff debris). - New pins: home_gutter_test, stop_area_test, plus updated structural bounds. Note: this commit also carries pre-existing working-tree deletions that were present before this work (API_SPEC.md, README.md, demo test fixtures). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
892 lines
46 KiB
Dart
892 lines
46 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> {
|
|
DoormileMapController? _mapController;
|
|
late final LatLng _pickupLocation;
|
|
late final LatLng _dropLocation;
|
|
List<DoormileMapMarker> _markers = const [];
|
|
List<DoormileMapPolyline> _polylines = const [];
|
|
bool _isNavigating = false; // Prevent multiple clicks
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
_resolveLocationsFromDelivery();
|
|
_setMarkers();
|
|
_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;
|
|
}
|
|
|
|
Future<void> _setMarkers() async {
|
|
final pickupIcon = await MapMarkerIcons.pin(Colors.red);
|
|
final dropIcon = await MapMarkerIcons.pin(Colors.green);
|
|
if (!mounted) return;
|
|
setState(() {
|
|
_markers = [
|
|
DoormileMapMarker(
|
|
id: 'pickup',
|
|
position: _pickupLocation,
|
|
icon: pickupIcon,
|
|
),
|
|
DoormileMapMarker(id: 'drop', position: _dropLocation, icon: dropIcon),
|
|
];
|
|
});
|
|
}
|
|
|
|
Future<void> _createRealRoute() async {
|
|
final route = await RoutingService.getRoute(
|
|
origin: _pickupLocation,
|
|
destination: _dropLocation,
|
|
);
|
|
|
|
if (!mounted) return;
|
|
|
|
// 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 = [
|
|
DoormileMapPolyline(
|
|
id: 'real_route',
|
|
points: points,
|
|
color: ColorConstants.primaryColor,
|
|
width: 6,
|
|
),
|
|
];
|
|
});
|
|
|
|
_fitMapToRoute();
|
|
}
|
|
|
|
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,
|
|
);
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return Scaffold(
|
|
body: Stack(
|
|
children: [
|
|
SizedBox.expand(
|
|
child: DoormileMap(
|
|
initialCameraPosition: CameraPosition(
|
|
target: _pickupLocation,
|
|
zoom: 14.5,
|
|
),
|
|
markers: _markers,
|
|
polylines: _polylines,
|
|
onMapReady: (controller) {
|
|
_mapController = controller;
|
|
_fitMapToRoute();
|
|
},
|
|
),
|
|
),
|
|
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(
|
|
'doormile/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,
|
|
),
|
|
),
|
|
],
|
|
);
|
|
}
|
|
}
|