import 'dart:io'; import 'package:flutter/material.dart'; import 'package:lucide_icons_flutter/lucide_icons.dart'; import 'package:image_picker/image_picker.dart'; import 'package:miler/views/helpers/constants/Font_constant.dart'; import 'package:miler/views/helpers/constants/Colorconstants.dart'; import 'package:miler/views/helpers/constants/design_constants.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:miler/views/Dashboard/pickups/stop_type.dart'; import 'package:slider_button_lite/feature/presentation/slider_button/slider.dart'; import 'package:slider_button_lite/feature/presentation/slider_button/slider_button_prop.dart'; class PickupVerificationPage extends StatefulWidget { final Map pickup; const PickupVerificationPage({super.key, required this.pickup}); @override State createState() => _PickupVerificationPageState(); } class _PickupVerificationPageState extends State with TickerProviderStateMixin { File? _parcelImage; final ImagePicker _picker = ImagePicker(); bool _isConfirming = false; bool _photoVerified = false; // Form controllers final TextEditingController _weightController = TextEditingController(); final TextEditingController _descriptionController = TextEditingController(); final TextEditingController _otpController = TextEditingController(); final TextEditingController _dropAddressController = TextEditingController(); late final String _customerName; late final String _pickupAddress; late final String _orderId; late final String _dropAddressDefault; // Delivery stops (Amazon/Flipkart-style) verify differently: proof is // OTP + photo (prepaid, no weight/signature). Pickups keep the full form. late final bool _isDelivery; late AnimationController _fadeController; late Animation _fadeAnim; // Red Theme Colors static Color get _primaryRed => ColorConstants.primary; static Color get _darkRed => ColorConstants.primary; static Color get _lightRed => ColorConstants.errorLight; static Color get _successGreen => ColorConstants.acceptGreen; @override void initState() { super.initState(); final d = widget.pickup; _customerName = (d['PickupCustomer'] ?? d['pickupcustomer'] ?? 'Customer') .toString(); _pickupAddress = (d['pickupaddress'] ?? d['PickupAddress'] ?? 'Pickup Location') .toString(); _orderId = (d['orderid'] ?? '').toString(); _dropAddressDefault = (d['DropAddress'] ?? d['dropaddress'] ?? 'Drop Location').toString(); _dropAddressController.text = _dropAddressDefault; _isDelivery = stopKindOf(d).isDelivery; _fadeController = AnimationController( duration: const Duration(milliseconds: 500), vsync: this, ); _fadeAnim = CurvedAnimation(parent: _fadeController, curve: Curves.easeOut); _fadeController.forward(); } @override void dispose() { _weightController.dispose(); _descriptionController.dispose(); _otpController.dispose(); _dropAddressController.dispose(); _fadeController.dispose(); super.dispose(); } Future _takeParcelPhoto() async { try { final XFile? photo = await _picker.pickImage( source: ImageSource.camera, imageQuality: 85, ); if (photo != null && mounted) { setState(() { _parcelImage = File(photo.path); _photoVerified = true; }); } } catch (e) { if (mounted) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('Failed to capture photo')), ); } } } // Required proof per stop is kept light: Photo + Code (pickup) / Photo + OTP // (delivery). Weight is optional — no keyboard entry needed at every doorstep. bool get _isFormValid => _isDelivery ? (_photoVerified && _otpController.text.trim().isNotEmpty) : (_photoVerified && _otpController.text.trim().isNotEmpty); void _confirmPickup() { if (!_isFormValid) { String msg = 'Please complete all required fields'; if (!_photoVerified) { msg = _isDelivery ? 'Take a photo of the delivered parcel first' : 'Take a photo of the parcel first'; } else if (_isDelivery && _otpController.text.trim().isEmpty) { msg = 'Enter the delivery OTP from the customer'; } else if (!_isDelivery && _otpController.text.trim().isEmpty) { msg = 'Enter the pickup code from the customer'; } ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text(msg), backgroundColor: _primaryRed, behavior: SnackBarBehavior.floating, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(DesignConstants.radiusLg), ), ), ); return; } setState(() => _isConfirming = true); final verified = { 'verified': true, 'parcelImage': _parcelImage?.path, 'weight': _weightController.text.trim(), 'description': _descriptionController.text.trim(), 'dropAddress': _dropAddressController.text.trim(), 'otp': _otpController.text.trim(), }; debugPrint( '[PICKUP_VERIFY] Confirming pickup and popping with verified data', ); // Pop with verified data Navigator.pop(context, verified); } @override Widget build(BuildContext context) { return Scaffold( backgroundColor: ColorConstants.daylightSurface, appBar: _buildAppBar(), body: FadeTransition( opacity: _fadeAnim, child: SingleChildScrollView( physics: const BouncingScrollPhysics(), padding: EdgeInsets.fromLTRB(16.w, 12.h, 16.w, 32.h), child: SizedBox( width: MediaQuery.of(context).size.width - 32.w, child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ _buildProgressSteps(), SizedBox(height: 20.h), _buildCustomerCard(), SizedBox(height: 20.h), _buildSectionHeader( icon: LucideIcons.camera, title: _isDelivery ? 'Delivery Photo' : 'Parcel Photo', badge: 'Required', isComplete: _photoVerified, ), SizedBox(height: 10.h), _buildPhotoCapture(), SizedBox(height: 20.h), // Weight is pickup-only. Deliveries are prepaid and proven by // OTP + photo. Signature was dropped — photo is enough proof for // a first-mile pickup, so it was pure friction on every stop. if (!_isDelivery) ...[ _buildSectionHeader( icon: LucideIcons.package, title: 'Parcel Details (Optional)', isComplete: _weightController.text.isNotEmpty, ), SizedBox(height: 10.h), _buildParcelDetailsCard(), SizedBox(height: 20.h), ], _buildSectionHeader( icon: LucideIcons.lock, title: _isDelivery ? 'Delivery OTP' : 'Pickup Code', badge: 'Required', isComplete: _otpController.text.isNotEmpty, ), SizedBox(height: 10.h), _buildCard([ TextField( controller: _otpController, keyboardType: TextInputType.number, maxLength: 6, onChanged: (_) => setState(() {}), decoration: _inputDecoration( _isDelivery ? 'Enter OTP from customer' : 'Enter code from customer', ).copyWith( counterText: '', prefixIcon: Icon( LucideIcons.lock, color: ColorConstants.secondaryText, size: 20.sp, ), ), style: TextStyle( fontSize: 15.sp, fontFamily: FontConstants.fontFamily, ), ), ]), SizedBox(height: 32.h), _buildConfirmSlider(), SizedBox(height: 20.h), ], ), ), ), ), ); } AppBar _buildAppBar() { return AppBar( backgroundColor: ColorConstants.pureSurface, elevation: 0, centerTitle: false, surfaceTintColor: Colors.transparent, // 56 → 68, the same air the tab bars gained. Height rather than a title // inset so the back arrow moves down with the heading instead of the two // drifting apart. toolbarHeight: 68, leading: IconButton( tooltip: 'Back', icon: Icon( LucideIcons.chevronLeft, color: ColorConstants.onSurface, size: 20.sp, ), onPressed: () => Navigator.pop(context), ), title: Row( children: [ // No brand mark. It is gone from every bar in the app: on a pushed // sub-page the back arrow beside it already says where the rider is, // and the logo was repeating what the screen he came from said. Text( _isDelivery ? 'Verify Delivery' : 'Verify Pickup', style: TextStyle( fontSize: 21.sp, color: ColorConstants.onSurface, fontWeight: FontWeight.w700, fontFamily: FontConstants.fontFamily, ), ), ], ), bottom: PreferredSize( preferredSize: const Size.fromHeight(1), child: Container(height: 1, color: ColorConstants.dividerColor), ), ); } Widget _buildProgressSteps() { final steps = _isDelivery ? [ {'label': 'Photo', 'done': _photoVerified}, {'label': 'OTP', 'done': _otpController.text.isNotEmpty}, ] : [ {'label': 'Photo', 'done': _photoVerified}, {'label': 'Code', 'done': _otpController.text.isNotEmpty}, ]; return Container( padding: EdgeInsets.symmetric(horizontal: 4.w, vertical: 12.h), decoration: BoxDecoration( color: ColorConstants.pureSurface, borderRadius: BorderRadius.circular(DesignConstants.radiusXl), border: Border.all(color: ColorConstants.dividerColor), ), child: Row( children: List.generate(steps.length * 2 - 1, (i) { if (i.isOdd) { final leftDone = steps[i ~/ 2]['done'] as bool; return Expanded( child: Container( height: 2.h, margin: EdgeInsets.symmetric(horizontal: 4.w), decoration: BoxDecoration( color: leftDone ? _successGreen : ColorConstants.dividerColor, borderRadius: BorderRadius.circular(DesignConstants.radiusLg), ), ), ); } final idx = i ~/ 2; final step = steps[idx]; final done = step['done'] as bool; final label = step['label'] as String; return Expanded( child: Column( children: [ AnimatedContainer( duration: const Duration(milliseconds: 300), width: 32.w, height: 32.w, decoration: BoxDecoration( color: done ? _successGreen : ColorConstants.neutralLight, shape: BoxShape.circle, border: Border.all( color: done ? _successGreen : ColorConstants.dividerColor, width: 2, ), ), child: Icon( done ? LucideIcons.check : LucideIcons.circle, color: done ? Colors.white : ColorConstants.secondaryText, size: 16.sp, ), ), SizedBox(height: 4.h), Text( label, style: TextStyle( fontSize: 10.sp, fontWeight: done ? FontWeight.w600 : FontWeight.w500, color: done ? _successGreen : ColorConstants.onSurfaceVariant, fontFamily: FontConstants.fontFamily, ), ), ], ), ); }), ), ); } Widget _buildCustomerCard() { final initials = _customerName.trim().isNotEmpty ? _customerName .trim() .split(' ') .map((w) => w.isNotEmpty ? w[0] : '') .take(2) .join() .toUpperCase() : 'C'; return Container( padding: EdgeInsets.all(16.r), decoration: BoxDecoration( gradient: LinearGradient( colors: [_primaryRed, _darkRed], begin: Alignment.topLeft, end: Alignment.bottomRight, ), borderRadius: BorderRadius.circular(DesignConstants.radiusXl), boxShadow: [ BoxShadow( color: _primaryRed.withValues(alpha: 0.25), blurRadius: 16, offset: const Offset(0, 6), ), ], ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Container( width: 44.w, height: 44.w, decoration: BoxDecoration( color: ColorConstants.pureSurface.withValues(alpha: 0.2), shape: BoxShape.circle, border: Border.all( color: Colors.white.withValues(alpha: 0.4), ), ), child: Center( child: Text( initials, style: TextStyle( color: Colors.white, fontSize: 16.sp, fontWeight: FontWeight.w700, fontFamily: FontConstants.fontFamily, ), ), ), ), SizedBox(width: 12.w), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( _customerName, style: TextStyle( fontSize: 16.sp, fontWeight: FontWeight.w700, color: Colors.white, fontFamily: FontConstants.fontFamily, ), ), SizedBox(height: 2.h), Text( 'Booking #$_orderId', style: TextStyle( fontSize: 12.sp, color: Colors.white.withValues(alpha: 0.8), fontFamily: FontConstants.fontFamily, ), ), ], ), ), ], ), SizedBox(height: 12.h), Container( height: 1, color: ColorConstants.pureSurface.withValues(alpha: 0.2), ), SizedBox(height: 12.h), _buildLocationRow( LucideIcons.mapPin, _pickupAddress, _isDelivery ? 'DELIVERY' : 'PICKUP', ColorConstants.warning.withValues(alpha: 0.40), ), ], ), ); } Widget _buildLocationRow( IconData icon, String address, String label, Color accentColor, ) { return Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ Container( padding: EdgeInsets.all(5.r), decoration: BoxDecoration( color: ColorConstants.pureSurface.withValues(alpha: 0.15), borderRadius: BorderRadius.circular(DesignConstants.radiusLg), ), child: Icon(icon, size: 14.sp, color: accentColor), ), SizedBox(width: 10.w), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( label, style: TextStyle( fontSize: 9.sp, fontWeight: FontWeight.w600, color: accentColor, letterSpacing: 0.8, fontFamily: FontConstants.fontFamily, ), ), SizedBox(height: 1.h), Text( address, style: TextStyle( fontSize: 12.sp, color: Colors.white.withValues(alpha: 0.9), fontFamily: FontConstants.fontFamily, ), maxLines: 1, overflow: TextOverflow.ellipsis, ), ], ), ), ], ); } Widget _buildSectionHeader({ required IconData icon, required String title, String? badge, bool isComplete = false, }) { return Row( children: [ Container( padding: EdgeInsets.all(7.r), decoration: BoxDecoration( color: isComplete ? ColorConstants.successLight : ColorConstants.neutralLight, borderRadius: BorderRadius.circular(DesignConstants.radiusLg), ), child: Icon( isComplete ? LucideIcons.circleCheck : icon, size: 16.sp, color: isComplete ? _successGreen : ColorConstants.onSurfaceVariant, ), ), SizedBox(width: 10.w), Expanded( child: Text( title, style: TextStyle( fontSize: 15.sp, fontWeight: FontWeight.w600, color: ColorConstants.onSurface, fontFamily: FontConstants.fontFamily, ), ), ), if (badge != null) Container( padding: EdgeInsets.symmetric(horizontal: 8.w, vertical: 3.h), decoration: BoxDecoration( color: _lightRed, borderRadius: BorderRadius.circular(DesignConstants.radiusLg), ), child: Text( badge, style: TextStyle( fontSize: 10.sp, fontWeight: FontWeight.w600, color: _primaryRed, fontFamily: FontConstants.fontFamily, ), ), ), ], ); } Widget _buildPhotoCapture() { return GestureDetector( onTap: _takeParcelPhoto, child: AnimatedContainer( duration: const Duration(milliseconds: 300), width: double.infinity, height: 180.h, decoration: BoxDecoration( color: _photoVerified ? Colors.transparent : ColorConstants.pureSurface, borderRadius: BorderRadius.circular(DesignConstants.radiusXl), border: Border.all( color: _photoVerified ? _successGreen : ColorConstants.borderSubtle, width: _photoVerified ? 2 : 1, ), boxShadow: _photoVerified ? [ BoxShadow( color: _successGreen.withValues(alpha: 0.1), blurRadius: 12, offset: const Offset(0, 4), ), ] : [], ), child: _parcelImage != null ? ClipRRect( borderRadius: BorderRadius.circular(DesignConstants.radiusXl), child: Stack( fit: StackFit.expand, children: [ Image.file(_parcelImage!, fit: BoxFit.cover), Positioned( bottom: 0, left: 0, right: 0, child: Container( padding: EdgeInsets.symmetric( horizontal: 14.w, vertical: 10.h, ), decoration: BoxDecoration( gradient: LinearGradient( begin: Alignment.bottomCenter, end: Alignment.topCenter, colors: [ Colors.black.withValues(alpha: 0.6), Colors.transparent, ], ), ), child: Row( children: [ Icon( LucideIcons.circleCheck, color: Colors.white, size: 16.sp, ), SizedBox(width: 6.w), Text( 'Photo captured — tap to retake', style: TextStyle( color: Colors.white, fontSize: 12.sp, fontFamily: FontConstants.fontFamily, ), ), ], ), ), ), Positioned( top: 10.h, right: 10.w, child: Container( padding: EdgeInsets.all(6.r), decoration: BoxDecoration( color: _successGreen, shape: BoxShape.circle, ), child: Icon( LucideIcons.check, color: Colors.white, size: 14.sp, ), ), ), ], ), ) : Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Container( padding: EdgeInsets.all(18.r), decoration: BoxDecoration( color: ColorConstants.neutralLight, shape: BoxShape.circle, ), child: Icon( LucideIcons.camera, color: ColorConstants.secondaryText, size: 36.sp, ), ), SizedBox(height: 10.h), Text( 'Tap to take parcel photo', style: TextStyle( fontSize: 14.sp, fontWeight: FontWeight.w500, color: ColorConstants.onSurfaceVariant, fontFamily: FontConstants.fontFamily, ), ), SizedBox(height: 4.h), Text( 'Required for pickup confirmation', style: TextStyle( fontSize: 11.sp, color: ColorConstants.secondaryText, fontFamily: FontConstants.fontFamily, ), ), ], ), ), ); } Widget _buildParcelDetailsCard() { return _buildCard([ _fieldLabel('Weight (kg)'), SizedBox(height: 6.h), TextField( controller: _weightController, keyboardType: const TextInputType.numberWithOptions(decimal: true), onChanged: (_) => setState(() {}), decoration: _inputDecoration('e.g. 2.5').copyWith( suffixText: 'kg', suffixStyle: TextStyle( fontSize: 13.sp, color: ColorConstants.onSurfaceVariant, fontFamily: FontConstants.fontFamily, ), ), style: TextStyle(fontSize: 15.sp, fontFamily: FontConstants.fontFamily), ), SizedBox(height: 16.h), _fieldLabel('Description (Optional)'), SizedBox(height: 6.h), TextField( controller: _descriptionController, maxLines: 3, decoration: _inputDecoration('Item description, fragile, etc.'), style: TextStyle(fontSize: 15.sp, fontFamily: FontConstants.fontFamily), ), SizedBox(height: 16.h), ]); } Widget _fieldLabel(String label, {bool required = false}) { return Row( children: [ Text( label, style: TextStyle( fontSize: 13.sp, fontWeight: FontWeight.w500, color: ColorConstants.onSurface, fontFamily: FontConstants.fontFamily, ), ), if (required) ...[ SizedBox(width: 4.w), Text( '*', style: TextStyle( fontSize: 13.sp, color: _primaryRed, fontWeight: FontWeight.w600, fontFamily: FontConstants.fontFamily, ), ), ], ], ); } Widget _buildConfirmSlider() { if (_isConfirming) { return Container( height: ButtonSizes.primary, decoration: BoxDecoration( color: ColorConstants.primary, borderRadius: BorderRadius.circular(DesignConstants.radiusXl), ), child: const Center( child: SizedBox( width: 22, height: 22, child: CircularProgressIndicator( color: Colors.white, strokeWidth: 2, ), ), ), ); } if (!_isFormValid) { return Container( height: ButtonSizes.primary, decoration: BoxDecoration( color: ColorConstants.dividerColor, borderRadius: BorderRadius.circular(DesignConstants.radiusXl), ), child: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon( LucideIcons.lock, color: ColorConstants.secondaryText, size: 18.sp, ), SizedBox(width: 8.w), Text( !_photoVerified ? 'Take a photo to continue' : _otpController.text.isEmpty ? (_isDelivery ? 'Enter OTP to continue' : 'Enter pickup code to continue') : 'Complete all fields to continue', style: TextStyle( fontSize: 14.sp, fontWeight: FontWeight.w500, color: ColorConstants.secondaryText, fontFamily: FontConstants.fontFamily, ), ), ], ), ); } return SliderButton( properties: SliderButtonProperties( height: ButtonSizes.primary, buttonSize: 48, width: MediaQuery.of(context).size.width - 32.w, backgroundColor: ColorConstants.primary, dismissThresholds: 0.85, action: () async { debugPrint( '[PICKUP_VERIFY_SLIDER] Action called, form valid: $_isFormValid', ); if (!_isFormValid) { debugPrint( '[PICKUP_VERIFY_SLIDER] Form not valid, returning false', ); return false; } _confirmPickup(); debugPrint( '[PICKUP_VERIFY_SLIDER] _confirmPickup called, returning true', ); return true; }, label: Text( 'Slide to Confirm Pickup', textAlign: TextAlign.center, style: TextStyle( fontSize: 15.sp, fontWeight: FontWeight.w600, color: Colors.white, fontFamily: FontConstants.fontFamily, ), ), icon: Container( margin: const EdgeInsets.all(4), decoration: BoxDecoration( color: ColorConstants.pureSurface, shape: BoxShape.circle, ), child: Icon( LucideIcons.check, size: 22.sp, color: ColorConstants.primary, ), ), ), ); } Widget _buildCard(List children) { return Container( width: double.infinity, padding: EdgeInsets.all(16.r), decoration: BoxDecoration( color: ColorConstants.pureSurface, borderRadius: BorderRadius.circular(DesignConstants.radiusXl), border: Border.all(color: ColorConstants.borderSubtle), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: children, ), ); } InputDecoration _inputDecoration(String hint) { return InputDecoration( hintText: hint, hintStyle: TextStyle( fontSize: 14.sp, color: ColorConstants.secondaryText, fontFamily: FontConstants.fontFamily, ), filled: true, fillColor: ColorConstants.daylightSurface, contentPadding: EdgeInsets.symmetric(horizontal: 14.w, vertical: 12.h), border: OutlineInputBorder( borderRadius: BorderRadius.circular(DesignConstants.radiusLg), borderSide: BorderSide(color: ColorConstants.borderStrong), ), enabledBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(DesignConstants.radiusLg), borderSide: BorderSide(color: ColorConstants.borderStrong), ), focusedBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(DesignConstants.radiusLg), borderSide: BorderSide(color: ColorConstants.primary, width: 1.5), ), ); } }