import 'dart:convert'; import 'dart:io'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:image_picker/image_picker.dart'; import 'package:supplier_new/resources/driver_trips_model.dart'; import 'package:supplier_new/resources/resources_drivers.dart'; import '../common/settings.dart'; import 'package:http/http.dart' as http; class DriverDetailsPage extends StatefulWidget { var driverDetails; var status; DriverDetailsPage({this.driverDetails, this.status}); @override State createState() => _DriverDetailsPageState(); } class _DriverDetailsPageState extends State { // tweak if you want a different size/offset double _avSize = 80; // avatar diameter double _cardHPad = 16; // horizontal padding double _cardTPad = 16; // top inner padding double _avatarOverlap = 28; // how much avatar rises above the card final _nameCtrl = TextEditingController(); final _mobileCtrl = TextEditingController(); final _ageCtrl = TextEditingController(); final _altMobileCtrl = TextEditingController(); final _locationCtrl = TextEditingController(); final _licenseController = TextEditingController(); final _experienceController = TextEditingController(); final GlobalKey _formKey = GlobalKey(); // Unused in UI but kept if you later need them final _commissionCtrl = TextEditingController(); final _joinDateCtrl = TextEditingController(); bool isLoading = false; List driverTripsList = []; bool isTripsLoading = false; // Dropdown state String? _status; // 'available' | 'on delivery' | 'offline' final List _statusOptions = const [ 'Available', 'On delivery', 'Offline' ]; /*String? selectedLicense; final List licenseNumbers = const [ 'DL-042019-9876543', 'DL-052020-1234567', 'DL-072021-7654321', ]; */ String? _required(String? v, {String field = "This field"}) { if (v == null || v.trim().isEmpty) return "$field is required"; return null; } String? _validatePhone(String? v, {String label = "Phone"}) { if (v == null || v.trim().isEmpty) return "$label is required"; if (v.trim().length != 10) return "$label must be 10 digits"; return null; } final ImagePicker _picker = ImagePicker(); XFile? newProfileImage; List newLicenseImages = []; List tankerImageUrls = []; final int maxImages = 2; @override void initState() { super.initState(); if(widget.driverDetails.licenseImages != null){ tankerImageUrls = List.from( widget.driverDetails.licenseImages ?? [] ); } _fetchDriverTrips(); } Future _fetchDriverTrips() async { setState(() => isTripsLoading = true); try { String response = await AppSettings.getDriverTrips(widget.driverDetails.phone_number); final data = (jsonDecode(response)['data'] as List) .map((e) => DriverTripsModel.fromJson(e)) .toList(); setState(() { driverTripsList = data; isTripsLoading = false; }); } catch (e) { print(e); setState(() => isTripsLoading = false); } } String? fitToOption(String? incoming, List options) { if (incoming == null) return null; final inc = incoming.trim(); if (inc.isEmpty) return null; final match = options.firstWhere( (o) => o.toLowerCase() == inc.toLowerCase(), orElse: () => '', ); return match.isEmpty ? null : match; // return the exact option string } Future _refreshDriverDetails({bool showToast = true}) async { try{ if(!mounted) return; setState(() => isLoading = true); final updatedDetails = await AppSettings.getDriverDetailsByPhone( widget.driverDetails.phone_number /// ⭐ FIXED ); if(!mounted) return; if(updatedDetails != null){ setState((){ widget.driverDetails = updatedDetails; /// ⭐ FIX IMAGE REFRESH BUG tankerImageUrls = List.from( updatedDetails.licenseImages ?? [] ); }); } else{ if(showToast){ AppSettings.longFailedToast( "Failed to fetch updated driver details"); } } } catch(e){ if(showToast){ AppSettings.longFailedToast( "Error refreshing driver details"); } } finally{ if(mounted){ setState(() => isLoading = false); } } } Future _updateDriver() async { // run all validators, including the dropdowns final ok = _formKey.currentState?.validate() ?? false; if (!ok) { setState(() {}); // ensure error texts render return; } // Build payload (adjust keys to your API if needed) final payload = { "name": _nameCtrl.text.trim(), "license_number": _licenseController.text ?? "", "address": _locationCtrl.text.trim().isEmpty ? AppSettings.userAddress : _locationCtrl.text.trim(), "supplier_name": AppSettings.userName, "phone": _mobileCtrl.text.trim(), "age": _ageCtrl.text.trim(), "alternativeContactNumber": _altMobileCtrl.text.trim(), "years_of_experience": _experienceController.text ?? "", "status": _status ?? "available", }; try { final bool created = await AppSettings.updateDrivers(payload, _mobileCtrl.text); if (!mounted) return; if (created) { AppSettings.longSuccessToast("Driver Updated successfully"); await uploadProfileImage(); await uploadUpdatedImages(); Navigator.pop(context, true); // close sheet _refreshDriverDetails(); } else { Navigator.pop(context, true); AppSettings.longFailedToast("failed to update driver details"); } } catch (e) { debugPrint("⚠️ addDrivers error: $e"); if (!mounted) return; AppSettings.longFailedToast("Something went wrong"); } } Future _openDriverFormSheet(BuildContext context) async { _nameCtrl.text = widget.driverDetails.driver_name ?? ''; _mobileCtrl.text = widget.driverDetails.phone_number ?? ''; _ageCtrl.text = widget.driverDetails.age ?? ''; _altMobileCtrl.text = widget.driverDetails.alt_phone_number ?? ''; _locationCtrl.text = widget.driverDetails.address ?? ''; _experienceController.text = widget.driverDetails.years_of_experience ?? ''; _licenseController.text = widget.driverDetails.license_number ?? ''; _status = fitToOption(widget.driverDetails.status, _statusOptions); await showModalBottomSheet( context: context, isScrollControlled: true, backgroundColor: Colors.transparent, // style inner container builder: (context) { final bottomInset = MediaQuery.of(context).viewInsets.bottom; return FractionallySizedBox( heightFactor: 0.75, // fixed height (75% of screen) child: Container( decoration: const BoxDecoration( color: Colors.white, borderRadius: BorderRadius.vertical(top: Radius.circular(20)), ), child: Padding( padding: EdgeInsets.fromLTRB(20, 16, 20, 20 + bottomInset), child: Form( key: _formKey, child: SingleChildScrollView( child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ // Handle + close (optional, simple close icon) Row( children: [ Expanded( child: Center( child: Container( width: 86, height: 4, margin: const EdgeInsets.only(bottom: 12), decoration: BoxDecoration( color: const Color(0xFFE0E0E0), borderRadius: BorderRadius.circular(2), ), ), ), ), ], ), _LabeledField( label: "Driver Name *", child: TextFormField( controller: _nameCtrl, validator: (v) => _required(v, field: "Driver Name"), textCapitalization: TextCapitalization.none, inputFormatters: const [ FirstCharUppercaseFormatter(), // << live first-letter caps ], decoration: InputDecoration( hintText: "Full Name", hintStyle: fontTextStyle( 14, const Color(0xFF939495), FontWeight.w400), border: const OutlineInputBorder(), isDense: true, ), textInputAction: TextInputAction.next, ), ), _LabeledField( label: "Driver License Number *", child: TextFormField( controller: _licenseController, textCapitalization: TextCapitalization.characters, decoration: const InputDecoration( border: OutlineInputBorder(), hintText: "Enter Driving License Number", contentPadding: EdgeInsets.symmetric( horizontal: 12, vertical: 14), ), style: fontTextStyle( 14, const Color(0xFF2A2A2A), FontWeight.w400), autovalidateMode: AutovalidateMode.onUserInteraction, validator: (value) { if (value == null || value.trim().isEmpty) { return "Driver License required"; } return null; }, ), ), _LabeledField( label: "Age *", child: TextFormField( controller: _ageCtrl, validator: (value) { if (value == null || value.trim().isEmpty) { return "Driver Age required"; } return null; }, keyboardType: TextInputType.number, inputFormatters: [ FilteringTextInputFormatter.digitsOnly, LengthLimitingTextInputFormatter(10), ], decoration: InputDecoration( hintText: "Age Of driver", hintStyle: fontTextStyle( 14, const Color(0xFF939495), FontWeight.w400), border: const OutlineInputBorder(), isDense: true, ), textInputAction: TextInputAction.next, ), ), _LabeledField( label: "Years of Experience *", child: TextFormField( controller: _experienceController, keyboardType: TextInputType.number, autovalidateMode: AutovalidateMode.onUserInteraction, inputFormatters: [ FilteringTextInputFormatter .digitsOnly, // Only numbers LengthLimitingTextInputFormatter(2), // Max 2 digits ], decoration: const InputDecoration( border: OutlineInputBorder(), hintText: "Enter Years", contentPadding: EdgeInsets.symmetric( horizontal: 12, vertical: 14), ), style: fontTextStyle( 14, const Color(0xFF2A2A2A), FontWeight.w400), validator: (value) { if (value == null || value.trim().isEmpty) { return "Experience is required"; } if (value.length > 2) { return "Only 2 digits allowed"; } return null; }, ), ), _LabeledField( label: "Phone Number *", child: TextFormField( controller: _mobileCtrl, validator: (v) => _validatePhone(v, label: "Phone Number"), keyboardType: TextInputType.phone, inputFormatters: [ FilteringTextInputFormatter.digitsOnly, LengthLimitingTextInputFormatter(10), ], decoration: InputDecoration( hintText: "Mobile Number", hintStyle: fontTextStyle( 14, const Color(0xFF939495), FontWeight.w400), border: const OutlineInputBorder(), isDense: true, ), textInputAction: TextInputAction.next, ), ), _LabeledField( label: "Alternate Phone Number", child: TextFormField( controller: _altMobileCtrl, validator: (v) { if (v == null || v.trim().isEmpty) return null; // optional return _validatePhone(v, label: "Alternate Phone Number"); }, keyboardType: TextInputType.phone, inputFormatters: [ FilteringTextInputFormatter.digitsOnly, LengthLimitingTextInputFormatter(10), ], decoration: InputDecoration( hintText: "Mobile Number", hintStyle: fontTextStyle( 14, const Color(0xFF939495), FontWeight.w400), border: const OutlineInputBorder(), isDense: true, ), textInputAction: TextInputAction.next, ), ), _LabeledField( label: "Location *", child: TextFormField( controller: _locationCtrl, validator: (v) => _required(v, field: "Location"), textCapitalization: TextCapitalization.none, inputFormatters: const [ FirstCharUppercaseFormatter(), // << live first-letter caps ], decoration: InputDecoration( hintText: "Area / locality", hintStyle: fontTextStyle( 14, const Color(0xFF939495), FontWeight.w400), border: const OutlineInputBorder(), isDense: true, ), textInputAction: TextInputAction.done, ), ), _LabeledField( label: "Status *", child: DropdownButtonFormField( value: _status, items: _statusOptions .map((s) => DropdownMenuItem(value: s, child: Text(s))) .toList(), onChanged: (v) => setState(() => _status = v), validator: (v) => v == null || v.isEmpty ? "Status is required" : null, isExpanded: true, alignment: Alignment.centerLeft, hint: Text( "Select status", style: fontTextStyle( 14, const Color(0xFF939495), FontWeight.w400), ), icon: const Icon(Icons.keyboard_arrow_down_rounded), decoration: const InputDecoration( border: OutlineInputBorder(), isDense: false, contentPadding: EdgeInsets.symmetric( horizontal: 12, vertical: 14), ), ), ), const SizedBox(height: 20), SizedBox( width: double.infinity, child: ElevatedButton( style: ElevatedButton.styleFrom( backgroundColor: const Color(0xFF8270DB), foregroundColor: Colors.white, padding: const EdgeInsets.symmetric(vertical: 14), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(24), ), ), onPressed: () { _updateDriver(); }, child: Text( "Update", style: fontTextStyle( 14, Colors.white, FontWeight.w600), ), ), ), ], ), ), ), ), ), ); }, ); } showDeleteDriverDialog(BuildContext context) async { return showDialog( context: context, barrierDismissible: false, builder: (BuildContext context) { return StatefulBuilder( builder: (BuildContext context, StateSetter setState) { return AlertDialog( backgroundColor: Color(0XFFFFFFFF), // Set your desired background color shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12), // Optional: Rounded corners ), title: Center( child: Text( 'Delete Driver?', style: fontTextStyle(16, Color(0XFF3B3B3B), FontWeight.w600), ), ), content: SingleChildScrollView( child: ListBody( children: [ Container( child: Text( 'Do u want to delete "${widget.driverDetails.driver_name}"', style: fontTextStyle(14, Color(0XFF101214), FontWeight.w600), ), ), ], ), ), actions: [ Center( child: Row( mainAxisSize: MainAxisSize.min, children: [ Expanded( child: GestureDetector( onTap: () { Navigator.pop(context); }, child: Container( decoration: BoxDecoration( shape: BoxShape.rectangle, color: Color(0XFFFFFFFF), border: Border.all( width: 1, color: Color(0XFF1D7AFC)), borderRadius: BorderRadius.circular( 12, )), alignment: Alignment.center, child: Visibility( visible: true, child: Padding( padding: EdgeInsets.fromLTRB(16, 12, 16, 12), child: Text('Cancel', style: fontTextStyle( 12, Color(0XFF1D7AFC), FontWeight.w600)), ), ), ), ), ), SizedBox( width: MediaQuery.of(context).size.width * .016, ), Expanded( child: GestureDetector( onTap: () async { bool status = await AppSettings.deleteDriver( widget.driverDetails.phone_number, ); if (status) { AppSettings.longSuccessToast( 'Driver deleted successfully'); Navigator.of(context).pop(true); Navigator.of(context).pop(true); } else { Navigator.of(context).pop(true); AppSettings.longFailedToast( 'Failed to delete driver'); } }, child: Container( decoration: BoxDecoration( shape: BoxShape.rectangle, color: Color(0XFFE2483D), border: Border.all( width: 1, color: Color(0XFFE2483D)), borderRadius: BorderRadius.circular( 12, )), alignment: Alignment.center, child: Visibility( visible: true, child: Padding( padding: EdgeInsets.fromLTRB(16, 12, 16, 12), child: Text('Delete', style: fontTextStyle(12, Color(0XFFFFFFFF), FontWeight.w600)), ), ), )), ) ], ), ), ], ); }); }, ); } Future pickProfileImage() async { final XFile? image = await _picker.pickImage(source: ImageSource.gallery, imageQuality: 70); if (image != null) { setState(() { newProfileImage = image; }); /// ⭐ UPLOAD IMMEDIATELY await uploadProfileImage(); /// ⭐ REFRESH DATA if (mounted) { await _refreshDriverDetails(showToast: false); } AppSettings.longSuccessToast("Profile updated"); } } Future pickNewImages() async { int totalImages = tankerImageUrls.length + newLicenseImages.length; if (totalImages >= maxImages) { AppSettings.longFailedToast("Maximum 2 images allowed"); return; } showModalBottomSheet( context: context, builder: (context) { return SafeArea( child: Column( mainAxisSize: MainAxisSize.min, children: [ ListTile( leading: Icon(Icons.photo), title: Text("Gallery"), onTap: () async { Navigator.pop(context); await _pickFromGallery(); }, ), ListTile( leading: Icon(Icons.camera_alt), title: Text("Camera"), onTap: () async { Navigator.pop(context); await _pickFromCamera(); }, ), ], ), ); }); } Future _pickFromGallery() async { final picker = ImagePicker(); final images = await picker.pickMultiImage(); if (images.isEmpty) return; int remaining = maxImages - (tankerImageUrls.length + newLicenseImages.length); if (images.length > remaining) { AppSettings.longFailedToast("You can upload only $remaining more images"); newLicenseImages = images.take(remaining).toList(); } else { newLicenseImages = images; } await uploadUpdatedImages(); if(mounted){ await _refreshDriverDetails( showToast:false); } } Future _pickFromCamera() async { if (tankerImageUrls.length >= maxImages) { AppSettings.longFailedToast("Maximum 5 images allowed"); return; } final picker = ImagePicker(); final image = await picker.pickImage( source: ImageSource.camera, imageQuality: 70, maxWidth: 1280, maxHeight: 1280, ); if (image == null) return; newLicenseImages = [image]; await uploadUpdatedImages(); if(mounted){ await _refreshDriverDetails( showToast:false); } } /*Future pickLicenseImages() async { final images = await _picker.pickMultiImage(imageQuality: 70); if (images != null && images.isNotEmpty) { setState(() { newLicenseImages = images.take(2).toList(); }); /// ⭐ UPLOAD IMMEDIATELY await uploadUpdatedImages(); /// ⭐ REFRESH DATA if (mounted) { await _refreshDriverDetails(showToast: false); } AppSettings.longSuccessToast("License updated"); } }*/ Future uploadProfileImage() async { if (newProfileImage == null) return; var request = http.MultipartRequest( 'POST', Uri.parse( AppSettings.host + "uploads_delievry_profile/${widget.driverDetails.phone_number}", ), ); request.headers.addAll(await AppSettings.buildRequestHeaders()); request.files.add( await http.MultipartFile.fromPath( 'file', newProfileImage!.path, ), ); await request.send(); } Future uploadUpdatedImages() async { try{ AppSettings.preLoaderDialog(context); var request = http.MultipartRequest( 'POST', Uri.parse( AppSettings.host + "uploads_delivery_liosence_images/${widget.driverDetails.phone_number}", ), ); request.headers.addAll( await AppSettings.buildRequestHeaders() ); for(var img in newLicenseImages){ request.files.add( await http.MultipartFile.fromPath( 'files', img.path ) ); } var response = await request.send(); Navigator.pop(context); var resp = await http.Response.fromStream(response); if(response.statusCode==200){ AppSettings.longSuccessToast( "Images Updated" ); newLicenseImages.clear(); /// ⭐ FORCE REFRESH await _refreshDriverDetails(showToast:false); } else{ print(resp.body); AppSettings.longFailedToast( "Upload failed" ); } } catch(e){ Navigator.pop(context); AppSettings.longFailedToast( "Upload failed" ); } } /* Future uploadLicenseImages() async { if (newLicenseImages.isEmpty) return; var request = http.MultipartRequest( 'POST', Uri.parse( AppSettings.host + "uploads_delivery_liosence_images/${widget.driverDetails.phone_number}", ), ); request.headers.addAll(await AppSettings.buildRequestHeaders()); for (var img in newLicenseImages) { request.files.add( await http.MultipartFile.fromPath( 'files', img.path, ), ); } await request.send(); if (mounted) { await _refreshDriverDetails(showToast: false); } }*/ @override Widget build(BuildContext context) { final Map statusColors = { 'available': Color(0xFF0A9E04), 'on delivery': Color(0xFFD0AE3C), 'offline': Color(0xFF939495), }; final statusColor = statusColors[widget.driverDetails.status.toLowerCase().trim()] ?? Colors.grey; return WillPopScope( onWillPop: () async { Navigator.pop(context, true); return false; // prevent default pop since we manually handled it }, child: Scaffold( backgroundColor: Color(0XFFFFFFFF), appBar: AppSettings.supplierAppBarWithActionsText( widget.driverDetails.driver_name.isNotEmpty ? widget.driverDetails.driver_name[0].toUpperCase() + widget.driverDetails.driver_name.substring(1) : '', context, result: true // ⭐ ADD THIS ), body: SingleChildScrollView( child: Padding( padding: const EdgeInsets.all(0), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ SizedBox( height: MediaQuery.of(context).size.height * .1, ), // 👤 Driver Profile Card Stack( clipBehavior: Clip.none, children: [ // Main card (give extra top padding so text starts below the avatar) Container( padding: EdgeInsets.fromLTRB( _cardHPad, _cardTPad + _avSize - _avatarOverlap, // space for avatar _cardHPad, 16, ), decoration: const BoxDecoration( color: Color(0xFFF3F1FB), borderRadius: BorderRadius.only( topLeft: Radius.circular(24), topRight: Radius.circular(24), ), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // status chip (just under avatar, aligned left) Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Container( padding: const EdgeInsets.symmetric( horizontal: 6, vertical: 2), decoration: BoxDecoration( borderRadius: BorderRadius.circular(4), border: Border.all(color: statusColor), ), child: Text(widget.driverDetails.status, style: fontTextStyle(10, statusColor, FontWeight.w400)), ), const SizedBox(height: 6), Text( widget.driverDetails.driver_name, style: fontTextStyle(20, Color(0XFF2D2E30), FontWeight.w500), ), const SizedBox(height: 2), Text( "+91 " + widget.driverDetails.phone_number, style: fontTextStyle(12, Color(0XFF646566), FontWeight.w400), ), ], ), ), PopupMenuButton( // 🔁 Use `child:` so you can place any widget (your 3-dots image) child: Padding( padding: const EdgeInsets.symmetric( horizontal: 8.0, vertical: 8.0), child: Image.asset( 'images/popup_menu.png', // your 3-dots image width: 22, height: 22, // If you want to tint it like an icon: color: Color( 0XFF939495), // remove if you want original colors colorBlendMode: BlendMode.srcIn, ), ), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12)), offset: const Offset(0, 40), color: Colors.white, elevation: 4, onSelected: (value) { if (value == 'edit') { _openDriverFormSheet(context); } else if (value == 'disable') { ScaffoldMessenger.of(context) .showSnackBar( const SnackBar( content: Text('Disable selected')), ); } else if (value == 'delete') { showDeleteDriverDialog(context); } }, itemBuilder: (context) => [ PopupMenuItem( value: 'edit', child: Row( children: [ Image.asset( 'images/edit.png', width: 20, height: 20, color: Color( 0XFF646566), // tint (optional) colorBlendMode: BlendMode.srcIn, ), const SizedBox(width: 12), Text( 'Edit', style: fontTextStyle( 14, const Color(0XFF646566), FontWeight.w400), ), ], ), ), PopupMenuItem( value: 'disable', child: Row( children: [ Image.asset( 'images/disable.png', width: 20, height: 20, color: Color( 0XFF646566), // tint (optional) colorBlendMode: BlendMode.srcIn, ), const SizedBox(width: 12), Text( 'Disable', style: fontTextStyle( 14, const Color(0XFF646566), FontWeight.w400), ), ], ), ), PopupMenuItem( value: 'delete', child: Row( children: [ Image.asset( 'images/delete.png', width: 20, height: 20, color: Color( 0XFFE2483D), // red like your example colorBlendMode: BlendMode.srcIn, ), const SizedBox(width: 12), Text( 'Delete', style: fontTextStyle( 14, const Color(0xFFE2483D), FontWeight.w400), ), ], ), ), ], ) ], ), const SizedBox(height: 8), // buttons row /* OutlinedButton( style: OutlinedButton.styleFrom( foregroundColor: Color(0XFF515253), backgroundColor: Color(0xFFF3F1FB), side: const BorderSide( color: Color(0xFF939495), width: 0.5, ), padding: EdgeInsets.symmetric(vertical: 10), // uniform height ), onPressed: () { }, child: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ Text( "Change Status", style: fontTextStyle( 14, const Color(0XFF515253), FontWeight.w500), ), ], ), ), const SizedBox(height: 24),*/ // 🪪 License Card /*ClipRRect( borderRadius: BorderRadius.circular(12), child: GestureDetector( onTap: pickLicenseImages, /// tap to update images child: Container( width: double.infinity, decoration: const BoxDecoration( color: Colors.black, ), child: Stack( children: [ /// LICENSE IMAGE BACKGROUND Container( height: 140, width: double.infinity, decoration: BoxDecoration( image: /// PRIORITY 1 → NEW IMAGE newLicenseImages.isNotEmpty ? DecorationImage( image: FileImage( File(newLicenseImages[0] .path), ), fit: BoxFit.cover, ) /// PRIORITY 2 → API IMAGE : widget.driverDetails .licenseImages != null && widget .driverDetails .licenseImages .isNotEmpty ? DecorationImage( image: NetworkImage( widget.driverDetails .licenseImages[0], ), fit: BoxFit.cover, ) /// PRIORITY 3 → NO IMAGE : null, color: newLicenseImages.isEmpty && (widget.driverDetails .licenseImages == null || widget .driverDetails .licenseImages .isEmpty) ? Colors.black.withOpacity(0.3) : null, ), child: Container( padding: const EdgeInsets.all(16), /// DARK OVERLAY decoration: newLicenseImages .isNotEmpty || (widget.driverDetails .licenseImages != null && widget .driverDetails .licenseImages .isNotEmpty) ? BoxDecoration( color: Colors.black .withOpacity(0.45), ) : null, child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( mainAxisAlignment: MainAxisAlignment .spaceBetween, children: [ Text( "DRIVING LICENSE", style: fontTextStyle( 10, const Color(0xFFFFFFFF), FontWeight.w400), ), /// CAMERA ICON Container( padding: EdgeInsets.all(6), decoration: BoxDecoration( color: Colors.white .withOpacity(.9), shape: BoxShape.circle, ), child: Center( child: Image.asset( 'images/edit.png', width: 14, height: 14, fit: BoxFit.contain, color: primaryColor, ), )) ], ), Spacer(), Row( mainAxisAlignment: MainAxisAlignment .spaceBetween, children: [ Column( crossAxisAlignment: CrossAxisAlignment .start, children: [ Text( widget.driverDetails .driver_name, style: fontTextStyle( 12, const Color( 0xFFFFFFFF), FontWeight.w500), ), SizedBox(height: 2), Text( widget.driverDetails .license_number, style: fontTextStyle( 12, const Color( 0xFFFFFFFF), FontWeight.w500), ), ], ), ], ) ], ), ), ), ], ), ), ), ),*/ Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( "Images (${tankerImageUrls.length}/2)", style: fontTextStyle( 12, Color(0XFF515253), FontWeight.w600 ), ), Text( "Max 2 allowed", style: fontTextStyle( 10, Color(0XFF939495), FontWeight.w400 ), ), ], ), SizedBox(height:8), ClipRRect( borderRadius: BorderRadius.circular(12), child: SizedBox( height:180, child:tankerImageUrls.isEmpty ? ClipRRect( borderRadius: BorderRadius.circular(12), child: Container( width: double.infinity, decoration: const BoxDecoration( color: Colors.black, ), child: Stack( children: [ // background pattern /*Image.asset( 'images/license_bg.png', fit: BoxFit.cover, width: double.infinity, height: 140, ),*/ Container( height: 140, width: double.infinity, padding: const EdgeInsets.all(16), color: Colors.black.withOpacity(0.3), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( "DRIVING LICENSE", style: fontTextStyle(10, const Color(0xFFFFFFFF), FontWeight.w400), ), Spacer(), Row( crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( widget.driverDetails.driver_name, style: fontTextStyle(12, const Color(0xFFFFFFFF), FontWeight.w500), ), Text( widget.driverDetails.license_number, style: fontTextStyle(12, const Color(0xFFFFFFFF), FontWeight.w500), ), ], ), /*Align( alignment: Alignment.bottomRight, child: Text( "Expires on 29/02/2028", style: fontTextStyle(10, const Color(0xFFFFFFFF), FontWeight.w300), ), ),*/ ], ) ], ), ), ], ), ), ) : ListView.builder( scrollDirection:Axis.horizontal, itemCount:tankerImageUrls.length, itemBuilder:(context,index){ return Padding( padding:EdgeInsets.only(right:8), child:ClipRRect( borderRadius: BorderRadius.circular(12), child:Stack( children:[ Image.network( tankerImageUrls[index], width:250, height:180, fit:BoxFit.cover, errorBuilder:(context,error,stack){ return Container( width:250, height:180, color:Colors.grey[300], child:Icon( Icons.image_not_supported, size:40, color:Colors.grey, ), ); }, loadingBuilder:(context,child,progress){ if(progress == null) return child; return Container( width:250, height:180, alignment:Alignment.center, child:CircularProgressIndicator(), ); }, ), /// DELETE BUTTON Positioned( top:6, right:6, child:GestureDetector( onTap:(){ showDialog( context:context, builder:(context){ return AlertDialog( title:Text("Delete Image?"), actions:[ TextButton( onPressed:(){ Navigator.pop(context); }, child:Text("Cancel") ), TextButton( onPressed:(){ Navigator.pop(context); //deleteImage(index); }, child:Text("Delete") ) ], ); } ); }, child:Container( decoration:BoxDecoration( color:Colors.white, shape:BoxShape.circle ), padding:EdgeInsets.all(6), child:Container( decoration: BoxDecoration( shape: BoxShape.circle, ), padding: EdgeInsets.all(6), child: Image.asset( 'images/delete.png', width: 18, height: 18, colorBlendMode: BlendMode.srcIn, ), ), ), ), ), ], ), ), ); }, ), ), ), SizedBox(height:8), ElevatedButton( style: ElevatedButton.styleFrom( backgroundColor: const Color(0xFF8270DB), foregroundColor: Colors.white, minimumSize: const Size(double.infinity, 44), padding: const EdgeInsets.symmetric( horizontal: 20, vertical: 12, ), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(24), ), ), onPressed: pickNewImages, child: Text( "Update License Images", style: fontTextStyle(14, Colors.white, FontWeight.w600), ), ) ], ), ), // Floating avatar (top-left, overlapping the card) Positioned( top: -_avatarOverlap, left: _cardHPad, child: GestureDetector( onTap: pickProfileImage, child: Stack( children: [ ClipRRect( borderRadius: BorderRadius.circular(_avSize / 2), child: newProfileImage != null ? Image.file( File(newProfileImage!.path), width: _avSize, height: _avSize, fit: BoxFit.cover, ) : widget.driverDetails.picture != null && widget.driverDetails.picture .isNotEmpty ? Image.network( widget.driverDetails.picture, width: _avSize, height: _avSize, fit: BoxFit.cover, ) : Image.asset( 'images/avatar.png', width: _avSize, height: _avSize, fit: BoxFit.cover, ), ), Positioned( bottom: 0, right: 0, child: Container( padding: EdgeInsets.all(4), decoration: BoxDecoration( color: Colors.white, shape: BoxShape.circle, ), child: Center( child: Image.asset( 'images/edit.png', width: 14, height: 14, fit: BoxFit.contain, color: primaryColor, ), )), ) ], ), ), ), // Call icon aligned with the card’s top-right /*Positioned( top: _cardTPad - _avatarOverlap + 4, // aligns near chip row right: _cardHPad - 4, child: IconButton( onPressed: () { // 📞 Call action }, icon: const Icon(Icons.call, color: Color(0xFF4F46E5)), ), ),*/ ], ), const SizedBox(height: 24), Padding( padding: const EdgeInsets.all(16), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( "RECENT TRIPS", style: fontTextStyle( 10, const Color(0xFF343637), FontWeight.w600), ), const SizedBox(height: 12), isTripsLoading ? const Center(child: CircularProgressIndicator()) : driverTripsList.isEmpty ? Center( child: Padding( padding: const EdgeInsets.symmetric( vertical: 12), child: Text( "No trips found", style: fontTextStyle( 12, const Color(0xFF939495), FontWeight.w500), ), ), ) : ListView.separated( shrinkWrap: true, physics: const NeverScrollableScrollPhysics(), itemCount: driverTripsList.length, separatorBuilder: (_, __) => const SizedBox(height: 10), itemBuilder: (context, index) { final trip = driverTripsList[index]; return _buildTripCard(trip); }, ) ], ), ), ], ), ), ), )); } Widget _buildTripCard(DriverTripsModel trip) { Color statusColor = trip.status == "delivered" ? Colors.green : trip.status == "cancelled" ? Colors.red : Colors.orange; return Container( padding: const EdgeInsets.all(12), decoration: BoxDecoration( color: const Color(0xFFFFFFFF), borderRadius: BorderRadius.circular(8), border: Border.all(color: const Color(0XFFC3C4C4)), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Image.asset('images/recent_trips.png', width: 28, height: 28), const SizedBox(width: 10), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( trip.tankerName, style: fontTextStyle( 14, const Color(0xFF2D2E30), FontWeight.w500), ), Text( trip.customerName, style: fontTextStyle( 11, const Color(0xFF646566), FontWeight.w400), ), ], ), ), Container( padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2), decoration: BoxDecoration( borderRadius: BorderRadius.circular(4), border: Border.all(color: statusColor)), child: Text( trip.status, style: fontTextStyle(10, statusColor, FontWeight.w400), ), ) ], ), const SizedBox(height: 6), Text( "${trip.date} • ${trip.time}", style: fontTextStyle(11, const Color(0xFF939495), FontWeight.w400), ), const SizedBox(height: 4), Text( trip.address, maxLines: 2, overflow: TextOverflow.ellipsis, style: fontTextStyle(11, const Color(0xFF646566), FontWeight.w400), ), const SizedBox(height: 8), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( "₹${trip.price}", style: fontTextStyle(13, const Color(0xFF2D2E30), FontWeight.w600), ), Text( "Paid ₹${trip.amountPaid}", style: fontTextStyle(11, Colors.green, FontWeight.w500), ), ], ) ], ), ); } } class _LabeledField extends StatelessWidget { final String label; final Widget child; const _LabeledField({required this.label, required this.child}); String _capFirstWord(String input) { if (input.isEmpty) return input; final i = input.indexOf(RegExp(r'\S')); if (i == -1) return input; return input.replaceRange(i, i + 1, input[i].toUpperCase()); } @override Widget build(BuildContext context) { return Padding( padding: const EdgeInsets.only(bottom: 14.0), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( _capFirstWord(label), style: fontTextStyle(12, const Color(0xFF515253), FontWeight.w600), ), const SizedBox(height: 6), child, ], ), ); } }