import 'package:flutter/material.dart'; import 'package:nearle/views/helpers/constants/Colorconstants.dart'; import 'dart:async'; import 'package:nearle/views/helpers/constants/Font_constant.dart'; import 'package:slide_to_submit_button/slide_to_submit_button.dart'; import 'package:image_picker/image_picker.dart'; /// ------------------------------ /// MAIN WIDGET WITH TWO BUTTONS /// ------------------------------ class OrderStatusRow extends StatefulWidget { final String currentStatus; final Future Function(String newStatus, {String? notes, String? proofImagePath}) onStatusChange; final bool enabled; const OrderStatusRow({ super.key, required this.currentStatus, required this.onStatusChange, this.enabled = true, }); @override State createState() => _OrderStatusRowState(); } class _OrderStatusRowState extends State { bool _isProcessing = false; String? _overrideStatus; @override void didUpdateWidget(covariant OrderStatusRow oldWidget) { super.didUpdateWidget(oldWidget); if (oldWidget.currentStatus != widget.currentStatus && mounted && _overrideStatus != null && widget.currentStatus == _overrideStatus) { setState(() { _overrideStatus = null; }); } else if (oldWidget.currentStatus != widget.currentStatus && _overrideStatus != null) { _overrideStatus = null; } } String get _effectiveStatus => _overrideStatus ?? widget.currentStatus; Future _onStatusChanged(String newStatus, {String? notes, String? proofImagePath}) async { debugPrint('[OSR] _onStatusChanged: $newStatus, proofImagePath: $proofImagePath'); if (_isProcessing) return false; // Optimistic UI: flip status immediately to avoid visible lag. setState(() { _isProcessing = true; _overrideStatus = newStatus; }); bool success = false; bool timedOut = false; try { // Hard cap wait time to keep UI from spinning indefinitely. success = await widget .onStatusChange(newStatus, notes: notes, proofImagePath: proofImagePath) .timeout( const Duration(seconds: 3), onTimeout: () { timedOut = true; // Assume success on timeout to avoid UI rollback; data will // refresh from server on next fetch. return true; }, ); } catch (_) { success = false; } finally { if (!mounted) return success; setState(() { _isProcessing = false; // If API failed, revert the optimistic status. // If timed out, keep the optimistic status (server likely completed). _overrideStatus = (success || timedOut) ? newStatus : null; }); } return success; } // ------------------------------ // REJECT SHEET // ------------------------------ void _showRejectSheet(BuildContext context) { showModalBottomSheet( context: context, shape: const RoundedRectangleBorder( borderRadius: BorderRadius.vertical(top: Radius.circular(16)), ), builder: (context) { String selectedReason = ""; bool isLoading = false; final List reasons = [ "Customer not reachable", "Wrong address", "Out of delivery area", "Other reason", ]; return SafeArea( top: false, left: false, right: false, bottom: true, child: StatefulBuilder( builder: (context, setModalState) { return Padding( padding: const EdgeInsets.all(16.0), child: Column( mainAxisSize: MainAxisSize.min, children: [ Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ const Text( "Reject Order", style: TextStyle( fontSize: 22, fontWeight: FontWeight.bold, ), ), IconButton( icon: const Icon(Icons.close, color: Colors.red), onPressed: () => Navigator.pop(context), ), ], ), const SizedBox(height: 10), for (String reason in reasons) Padding( padding: const EdgeInsets.only(bottom: 10), child: GestureDetector( onTap: () => setModalState(() { selectedReason = reason; }), child: Container( height: 55, width: double.infinity, decoration: BoxDecoration( color: selectedReason == reason ? Colors.red : Colors.grey[300], borderRadius: BorderRadius.circular(12), ), alignment: Alignment.center, child: Text( reason, style: TextStyle( color: selectedReason == reason ? Colors.white : Colors.black, fontWeight: FontWeight.bold, fontFamily: FontConstants.fontFamily, fontSize: 16, ), ), ), ), ), const SizedBox(height: 20), ElevatedButton( style: ElevatedButton.styleFrom( backgroundColor: Colors.red, minimumSize: const Size(double.infinity, 50), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12), ), ), onPressed: selectedReason.isEmpty || isLoading ? null : () async { setModalState(() => isLoading = true); final success = await _onStatusChanged( "REJECTED", notes: selectedReason, ); if (context.mounted) { Navigator.pop(context, success); } }, child: isLoading ? const CircularProgressIndicator( color: Colors.white, strokeWidth: 2, ) : const Text( "Reject Order", style: TextStyle( fontSize: 18, fontWeight: FontWeight.bold, color: Colors.white, ), ), ), ], ), ); }, ), ); }, ); } // ------------------------------ // CANCEL SHEET // ------------------------------ void _showCancelSheet(BuildContext context) { showModalBottomSheet( context: context, shape: const RoundedRectangleBorder( borderRadius: BorderRadius.vertical(top: Radius.circular(16)), ), builder: (context) { bool confirmCancel = false; bool isLoading = false; return SafeArea( top: false, left: false, right: false, bottom: true, child: StatefulBuilder( builder: (context, setModalState) { return Padding( padding: const EdgeInsets.all(20), child: Column( mainAxisSize: MainAxisSize.min, children: [ const Text( "Cancel this order?", style: TextStyle(fontSize: 22, fontWeight: FontWeight.bold), ), const SizedBox(height: 10), const Text( "Once cancelled, this order will return to pending state.", textAlign: TextAlign.center, ), const SizedBox(height: 25), CheckboxListTile( value: confirmCancel, onChanged: (value) => setModalState( () => confirmCancel = value ?? false, ), title: const Text("I confirm to cancel this order"), controlAffinity: ListTileControlAffinity.leading, ), const SizedBox(height: 20), ElevatedButton( style: ElevatedButton.styleFrom( backgroundColor: Colors.orange, minimumSize: const Size(double.infinity, 50), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12), ), ), onPressed: !confirmCancel ? null : () async { setModalState(() => isLoading = true); await Future.delayed(const Duration(seconds: 1)); if (context.mounted) Navigator.pop(context); ScaffoldMessenger.of(context).showSnackBar( const SnackBar( content: Text("Order Cancelled"), ), ); }, child: isLoading ? const CircularProgressIndicator( color: Colors.white, strokeWidth: 2, ) : const Text( "Confirm Cancel", style: TextStyle( fontSize: 18, fontWeight: FontWeight.bold, color: Colors.white, ), ), ), ], ), ); }, ), ); }, ); } @override Widget build(BuildContext context) { final String currentStatus = _effectiveStatus; final bool showCancel = currentStatus == "ARRIVED" || currentStatus == "PICKED"; final String leftText = showCancel ? "CANCEL" : "REJECT"; return Stack( children: [ Row( children: [ // LEFT BUTTON (Reject / Cancel) Expanded( child: InkWell( onTap: () { if (showCancel) { _showCancelSheet(context); } else { _showRejectSheet(context); } }, child: Container( padding: const EdgeInsets.symmetric(vertical: 10), decoration: const BoxDecoration( color: Colors.red, // borderRadius: BorderRadius.only( bottomLeft: Radius.circular(12), ), ), alignment: Alignment.center, child: Text( leftText, style: TextStyle( fontSize: 19, color: Colors.white, fontWeight: FontWeight.bold, fontFamily: FontConstants.fontFamily, ), ), ), ), ), // RIGHT BUTTON (ACCEPT / ARRIVED / PICKED) OrderStatusButton( key: ValueKey(currentStatus), currentStatus: currentStatus, onStatusChange: (status, {proofImagePath}) => _onStatusChanged(status, proofImagePath: proofImagePath), enabled: widget.enabled && !_isProcessing, ), ], ), if (_isProcessing) Positioned.fill( child: IgnorePointer( child: Container( decoration: BoxDecoration( color: Colors.black.withOpacity(0.12), borderRadius: const BorderRadius.only( bottomLeft: Radius.circular(12), bottomRight: Radius.circular(12), ), ), child: const Center( child: SizedBox( height: 22, width: 22, child: CircularProgressIndicator( strokeWidth: 2.5, color: Colors.white, ), ), ), ), ), ), ], ); } } /// ------------------------------ /// STATUS BUTTON LOGIC (NO DELIVERY) /// ------------------------------ class OrderStatusButton extends StatefulWidget { final String currentStatus; final Future Function(String newStatus, {String? proofImagePath}) onStatusChange; final bool enabled; const OrderStatusButton({ super.key, required this.currentStatus, required this.onStatusChange, this.enabled = true, }); @override State createState() => _OrderStatusButtonState(); } class _OrderStatusButtonState extends State { String get _buttonText => widget.currentStatus; Color _getButtonColor() { switch (_buttonText) { case "ARRIVED": return Colors.orange; case "PICKED": return ColorConstants.primaryColor; default: return Colors.green; } } void _showStatusSheet() { if (_buttonText == "PICKED") return; // final stage now bool isConfirmLoading = false; // Determine next status (single step) String? nextStatus; if (_buttonText == "ACCEPT") { nextStatus = "ACCEPTED"; } else if (_buttonText == "ACCEPTED") { nextStatus = "ARRIVED"; } else if (_buttonText == "ARRIVED") { nextStatus = "PICKED"; } showModalBottomSheet( context: context, isScrollControlled: true, shape: const RoundedRectangleBorder( borderRadius: BorderRadius.vertical(top: Radius.circular(16)), ), builder: (context) { // If for some reason we don't have a valid next status, show nothing if (nextStatus == null) { return const SizedBox.shrink(); } return SafeArea( top: false, left: false, right: false, bottom: true, child: StatefulBuilder( builder: (context, setModalState) { return Padding( padding: const EdgeInsets.all(16.0), child: Column( mainAxisSize: MainAxisSize.min, children: [ Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ const Text( "Move Your Order to", style: TextStyle( fontSize: 22, fontWeight: FontWeight.bold, ), ), Transform.translate( offset: const Offset(0, -5), child: IconButton( icon: const Icon( Icons.cancel, color: Colors.red, size: 36, ), onPressed: () => Navigator.pop(context), ), ), ], ), const SizedBox(height: 20), // Slider to confirm moving to next status wrapped with SafeArea SafeArea( top: false, left: false, right: false, bottom: true, child: SizedBox( width: double.infinity, child: SlideToSubmit.custom( height: 55, sliderWidth: 40, padding: const EdgeInsets.all(8), backgroundDecoration: BoxDecoration( color: _getButtonColor().withOpacity(0.5), borderRadius: BorderRadius.circular(40), ), foregroundDecoration: BoxDecoration( color: _getButtonColor(), borderRadius: BorderRadius.circular(999), ), slider: Center( child: ClipOval( child: Container( height: 40, width: 40, color: Colors.white, padding: const EdgeInsets.all(8), child: const Icon( Icons.arrow_forward_ios, size: 24, color: Colors.black, ), ), ), ), hint: Align( alignment: Alignment.center, child: Text( 'Slide to mark $nextStatus', style: TextStyle( fontSize: 18, fontWeight: FontWeight.bold, fontFamily: FontConstants.fontFamily, color: const Color.fromARGB(255, 15, 14, 14), ), ), ), onSubmit: (controller) async { if (isConfirmLoading) return; setModalState(() => isConfirmLoading = true); // Close sheet after slide completes if (context.mounted) { Navigator.pop(context); } // Trigger status change callback WidgetsBinding.instance.addPostFrameCallback(( _, ) async { if (nextStatus == "PICKED") { debugPrint('[OSB] Status is PICKED, launching camera...'); // Trigger Camera final ImagePicker picker = ImagePicker(); final XFile? photo = await picker.pickImage( source: ImageSource.camera, imageQuality: 50, // Optimize size ); if (photo != null) { debugPrint('[OSB] Photo taken: ${photo.path}'); // Process with image await widget.onStatusChange( nextStatus!, proofImagePath: photo.path, ); } else { debugPrint('[OSB] Camera cancelled or photo null'); } } else { debugPrint('[OSB] Status NOT PICKED (is $nextStatus), normal flow'); // Normal flow await widget.onStatusChange(nextStatus!); } try { controller.reset(); } catch (_) {} }); }, ), ), ), ], ), ); }, ), ); }, ); } @override Widget build(BuildContext context) { return Expanded( child: InkWell( onTap: widget.enabled ? _showStatusSheet : null, child: Container( padding: const EdgeInsets.symmetric(vertical: 10), decoration: BoxDecoration( color: widget.enabled ? _getButtonColor() : Colors.grey, borderRadius: const BorderRadius.only( bottomRight: Radius.circular(12), ), ), alignment: Alignment.center, child: Text( _buttonText, style: TextStyle( fontSize: 19, color: Colors.white, fontWeight: FontWeight.bold, fontFamily: FontConstants.fontFamily, ), ), ), ), ); } }