Miler rider app: surface system, visible design language, backend lifecycle
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>
This commit is contained in:
891
lib/xpress/views/Dashboard/deliveries/map.dart
Normal file
891
lib/xpress/views/Dashboard/deliveries/map.dart
Normal file
@@ -0,0 +1,891 @@
|
||||
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,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user