Files

412 lines
17 KiB
Dart

part of 'deliveries.dart';
// -------------------------------------------------------------------------
// SKIP REASONS SHEET
// -------------------------------------------------------------------------
Future<void> _showMyOptionsSheet(
BuildContext context,
Map<String, dynamic> delivery,
_MyDeliveriesState? parentState,
) async {
int selected = -1;
bool isLoading = false;
final List<String> skipReasons = [
'Customer unreachable',
'Customer not at the location',
];
await showModalBottomSheet(
context: context,
isScrollControlled: true,
backgroundColor: Colors.white,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.vertical(top: Radius.circular(20)),
),
builder: (context) {
return SafeArea(
top: false,
left: false,
right: false,
bottom: true,
child: StatefulBuilder(
builder: (context, setState) {
Widget optionBox(String title, int index) {
final bool isSelected = selected == index;
return GestureDetector(
onTap: () {
if (!isLoading) {
setState(() => selected = index);
}
},
child: Container(
padding: const EdgeInsets.all(16),
margin: const EdgeInsets.only(bottom: 12),
decoration: BoxDecoration(
color: isSelected
? ColorConstants.primaryColor.withOpacity(0.1)
: Colors.grey.shade100,
borderRadius: BorderRadius.circular(12),
border: Border.all(
color: isSelected
? ColorConstants.primaryColor
: Colors.grey.shade300,
width: isSelected ? 2 : 1,
),
),
child: Row(
children: [
Icon(
isSelected ? Icons.check_circle : Icons.circle_outlined,
color: isSelected
? ColorConstants.primaryColor
: Colors.grey,
size: 28,
),
const SizedBox(width: 12),
Text(
title,
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w600,
color: Colors.black87,
fontFamily: FontConstants.fontFamily,
),
),
],
),
),
);
}
Future<void> handleConfirm() async {
if (selected == -1 || isLoading) return;
// Check skip limits before proceeding
final dc = Get.put(DeliveriesController(), permanent: true);
final skipStatus = await dc.checkSkipStatus();
final int skipCount = skipStatus['count'] ?? 0;
bool applyPenalty = false;
if (skipCount >= 2) {
// Show Styled Alert Dialog
final bool? confirm = await showDialog<bool>(
context: context,
barrierDismissible: false,
builder: (BuildContext context) {
return Dialog(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(20),
),
elevation: 5,
backgroundColor: Colors.white,
child: Padding(
padding: const EdgeInsets.all(20),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: Colors.red.shade50,
shape: BoxShape.circle,
),
child: Icon(
Icons.warning_amber_rounded,
color: Colors.red.shade600,
size: 40,
),
),
const SizedBox(height: 20),
Text(
'Skip Limit Reached',
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 22,
fontWeight: FontWeight.bold,
fontFamily: FontConstants.fontFamily,
color: Colors.black87,
),
),
const SizedBox(height: 12),
Text(
'You have exceeded the limit of 2 skips within 3 hours.',
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 16,
fontFamily: FontConstants.fontFamily,
color: Colors.black54,
),
),
const SizedBox(height: 12),
Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: Colors.orange.shade50,
borderRadius: BorderRadius.circular(10),
border: Border.all(
color: Colors.orange.shade200,
),
),
child: Row(
children: [
Icon(Icons.info_outline, color: Colors.orange.shade800, size: 20),
const SizedBox(width: 8),
Expanded(
child: Text(
"Proceeding will forfeit your bonus points for this session.",
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w600,
color: Colors.orange.shade900,
fontFamily: FontConstants.fontFamily,
),
),
),
],
),
),
const SizedBox(height: 24),
Row(
children: [
Expanded(
child: OutlinedButton(
onPressed: () => Navigator.pop(context, false),
style: OutlinedButton.styleFrom(
padding: const EdgeInsets.symmetric(vertical: 14),
side: BorderSide(color: Colors.grey.shade300),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
),
child: Text(
"Cancel",
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
color: Colors.black54,
fontFamily: FontConstants.fontFamily,
),
),
),
),
const SizedBox(width: 12),
Expanded(
child: ElevatedButton(
onPressed: () => Navigator.pop(context, true),
style: ElevatedButton.styleFrom(
backgroundColor: Colors.red.shade600,
padding: const EdgeInsets.symmetric(vertical: 14),
elevation: 0,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
),
child: Text(
"Confirm Skip",
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
color: Colors.white,
fontFamily: FontConstants.fontFamily,
),
),
),
),
],
),
],
),
),
);
},
);
if (confirm != true) return; // User cancelled or dismissed
applyPenalty = true;
}
setState(() => isLoading = true);
try {
final dc = Get.put(DeliveriesController(), permanent: true);
final d = delivery;
final int deliveryId =
int.tryParse('${d['deliveryid'] ?? 0}') ?? 0;
final int orderHeaderId =
int.tryParse('${d['orderheaderid'] ?? 0}') ?? 0;
final String reason = selected >= 0 && selected < skipReasons.length
? skipReasons[selected]
: '';
if (deliveryId > 0 && orderHeaderId > 0) {
String riderLatStr = '0';
String riderLngStr = '0';
try {
final position = await Geolocator.getCurrentPosition(
desiredAccuracy: LocationAccuracy.high,
timeLimit: const Duration(seconds: 5),
);
riderLatStr = position.latitude.toStringAsFixed(6);
riderLngStr = position.longitude.toStringAsFixed(6);
} catch (e) {
debugPrint('[SKIP] 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 (_) {}
}
debugPrint(
'[SKIP] Updating status for deliveryId=$deliveryId orderHeaderId=$orderHeaderId reason="$reason"',
);
final ok = await dc.updateSkippedStatus(
deliveryId: deliveryId,
orderHeaderId: orderHeaderId,
ridersLat: riderLatStr,
ridersLng: riderLngStr,
notes: reason,
);
debugPrint('[SKIP] Status update result: $ok');
if (ok && context.mounted) {
// Register skip locally only on success
await dc.registerSkip(applyPenalty: applyPenalty);
if (context.mounted) {
Navigator.pop(context); // Close the sheet
if (parentState != null) {
// Only update list if called from the list view
parentState.markOrderAsSkipped(d, reason);
} else {
// If called from Navigation/Map (where parentState is null),
// we just close the sheet and let the caller handle the UI update
// typically by popping the route or showing a snackbar.
// We DO NOT force navigation to "MyDeliveries".
debugPrint(
'[SKIP] Skipped from Nav/Map screen, sheet closed.',
);
}
}
} else if (context.mounted) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text(
'Failed to skip delivery. Please try again.',
),
backgroundColor: Colors.red,
),
);
}
} else {
debugPrint(
'[SKIP] Invalid deliveryId: $deliveryId or orderHeaderId: $orderHeaderId',
);
if (context.mounted) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Invalid delivery information.'),
backgroundColor: Colors.red,
),
);
}
}
} catch (e) {
debugPrint('[SKIP] Error updating skip status: $e');
if (context.mounted) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('An error occurred. Please try again.'),
backgroundColor: Colors.red,
),
);
}
} finally {
if (context.mounted) {
setState(() => isLoading = false);
}
}
}
return Padding(
padding: const EdgeInsets.fromLTRB(16, 20, 16, 30),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
'Select Reason',
style: TextStyle(
fontSize: 22,
fontWeight: FontWeight.bold,
fontFamily: FontConstants.fontFamily,
),
),
IconButton(
icon: const Icon(Icons.cancel,
color: Colors.red, size: 32),
onPressed: isLoading ? null : () => Navigator.pop(context),
),
],
),
const SizedBox(height: 20),
optionBox('Customer unreachable', 0),
optionBox('Customer not at the location', 1),
const SizedBox(height: 20),
SizedBox(
width: double.infinity,
height: 55,
child: ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: (selected == -1 || isLoading)
? Colors.grey.shade300
: ColorConstants.primaryColor,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
),
onPressed:
(selected == -1 || isLoading) ? null : handleConfirm,
child: isLoading
? const SizedBox(
width: 24,
height: 24,
child: CircularProgressIndicator(
strokeWidth: 3,
valueColor:
AlwaysStoppedAnimation<Color>(Colors.white),
),
)
: Text(
'Confirm',
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
fontFamily: FontConstants.fontFamily,
color: (selected == -1 || isLoading)
? Colors.black45
: Colors.white,
),
),
),
),
],
),
);
},
),
);
},
);
}