import 'dart:async'; import 'dart:convert'; import 'package:flutter/material.dart'; import 'package:intl/intl.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'package:bookatanker/common/settings.dart'; import 'package:dropdown_button2/dropdown_button2.dart'; import 'package:bookatanker/supplier/filter_screen.dart'; import 'package:bookatanker/supplier/supplier_details.dart'; import 'package:bookatanker/supplier/supplier_details_from_search.dart'; import 'package:bookatanker/supplier/suppliers_model.dart'; class BookATanker extends StatefulWidget { const BookATanker({super.key}); @override State createState() => _BookATankerState(); } class _BookATankerState extends State { String? selectedWaterType; String? selectedCapacity; String? selectedQuantity; String? selectedTime; DateTime? selectedDate; List SuppliersList = []; bool isDataLoading = false; bool isSearchEnabled = false; int sentRequests = 0; int maxRequests = 5; DateTime? firstRequestTime; Timer? _resetTimer; Timer? _countdownTimer; String remainingTime = ''; double progress = 1.0; int totalCooldown = 900; // 15 minutes in seconds RangeValues? selectedRadius; RangeValues? selectedRating; RangeValues? selectedPrice; String? selectedPump; bool filterAll = true, filterConnected = false, filterNotConnected = false; final TextEditingController _quotedAmountController = TextEditingController(); // TimeOfDay _selectedTime = (TimeOfDay.now()); final List waterTypes = ['Drinking Water', 'Bore water']; final List capacities = [ '1,000 L', '2,000 L', '3,000 L', '5,000 L', '10,000 L', '20,000 L' ]; final List quantities = ['1', '2', '3']; final List timeSlots = ['8 AM', '12 PM', '4 PM', '8 PM']; @override void initState() { // TODO: implement initState super.initState(); _selectedTime = _roundToNextHour(TimeOfDay.now()); _loadRequestState(); // Defer the bottom sheet call until after build WidgetsBinding.instance.addPostFrameCallback((_) { _showLocationBottomSheet(); }); } @override void dispose() { _countdownTimer?.cancel(); _quotedAmountController.dispose(); super.dispose(); } Future _loadRequestState() async { List validTimes = await _getValidRequestTimes(); validTimes.sort(); // Ensure chronological order setState(() { sentRequests = validTimes.length; }); if (validTimes.isNotEmpty) { final firstExpiry = validTimes.first.add(Duration(minutes: 15)); final remaining = firstExpiry.difference(DateTime.now()).inSeconds; if (remaining > 0) { _startCountdownTimer(remaining); } } } Future> _getValidRequestTimes() async { SharedPreferences prefs = await SharedPreferences.getInstance(); final rawList = prefs.getStringList('requestTimes') ?? []; final now = DateTime.now(); final validTimes = rawList .map((s) => DateTime.tryParse(s)) .whereType() .where((t) => now.difference(t).inMinutes < 15) .toList(); return validTimes; } Future _saveRequestTimes(List times) async { SharedPreferences prefs = await SharedPreferences.getInstance(); prefs.setStringList('requestTimes', times.map((e) => e.toIso8601String()).toList()); } void _startCountdownTimer(int totalSeconds) { _countdownTimer?.cancel(); totalCooldown = totalSeconds; progress = 1.0; _countdownTimer = Timer.periodic(Duration(seconds: 1), (timer) async { final elapsed = timer.tick; final remaining = totalSeconds - elapsed; if (remaining <= 0) { timer.cancel(); // 🧹 Clean expired request times List updatedTimes = await _getValidRequestTimes(); updatedTimes.sort(); // Ensure order await _saveRequestTimes(updatedTimes); setState(() { sentRequests = updatedTimes.length; remainingTime = ''; progress = 0.0; }); // πŸ• If more valid requests remain, restart countdown with new first one if (updatedTimes.isNotEmpty) { final nextExpiry = updatedTimes.first.add(Duration(minutes: 15)); final newRemaining = nextExpiry.difference(DateTime.now()).inSeconds; if (newRemaining > 0) { _startCountdownTimer(newRemaining); } } } else { final minutes = (remaining ~/ 60).toString().padLeft(2, '0'); final seconds = (remaining % 60).toString().padLeft(2, '0'); setState(() { remainingTime = "$minutes:$seconds"; progress = remaining / totalCooldown; }); } }); } void _showLocationBottomSheet() { showModalBottomSheet( context: context, isScrollControlled: true, isDismissible: false, enableDrag: false, // πŸ”’ Prevents dragging shape: RoundedRectangleBorder( borderRadius: BorderRadius.vertical(top: Radius.circular(16)), ), builder: (context) { return Padding( padding: EdgeInsets.only( bottom: MediaQuery.of(context).viewInsets.bottom, ), child: Container( padding: EdgeInsets.all(16), constraints: BoxConstraints( maxHeight: MediaQuery.of(context).size.height * 0.45, ), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.vertical(top: Radius.circular(16)), ), child: SingleChildScrollView( child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( "Confirm Address?", style: fontTextStyle(16, Color(0XFF343637), FontWeight.w600), ), SizedBox(height: MediaQuery.of(context).size.height * .024), Row( children: [ Text( "Your current selected address is", style: fontTextStyle(12, Color(0XFF515253), FontWeight.w600), ), SizedBox(width: MediaQuery.of(context).size.width * .004), Text( "HOME", style: fontTextStyle(12, Color(0XFF2D2E30), FontWeight.w600), ), ], ), SizedBox(height: MediaQuery.of(context).size.height * .012), Row( children: [ Image.asset( 'images/marker-pin.png', width: 24, height: 24, ), SizedBox(width: MediaQuery.of(context).size.width * .024), Expanded( child: Text( AppSettings.userAddress, style: fontTextStyle(12, Color(0XFF515253), FontWeight.w400), ), ) ], ), SizedBox(height: MediaQuery.of(context).size.height * .012), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Expanded( child: GestureDetector( onTap: () { // Optional: handle "Change" }, child: Container( decoration: BoxDecoration( color: Colors.white, border: Border.all(width: 1, color: Color(0XFF939495)), borderRadius: BorderRadius.circular(24), ), alignment: Alignment.center, padding: EdgeInsets.symmetric(vertical: 12), child: Text( 'Change', style: fontTextStyle(14, Color(0XFF939495), FontWeight.w600), ), ), ), ), SizedBox(width: MediaQuery.of(context).size.width * .010), Expanded( child: GestureDetector( onTap: () { Navigator.pop(context); /* setState(() { _isConfirmed = true; });*/ }, child: Container( decoration: BoxDecoration( color: Color(0XFF1D7AFC), border: Border.all(width: 1, color: Colors.white), borderRadius: BorderRadius.circular(24), ), alignment: Alignment.center, padding: EdgeInsets.symmetric(vertical: 12), child: Text( 'Confirm', style: fontTextStyle(14, Colors.white, FontWeight.w600), ), ), ), ), ], ), ], ), ), ), ); }, ); } TimeOfDay _roundToNextHour(TimeOfDay time) { int nextHour = (time.minute > 0) ? (time.hour + 1) % 24 : time.hour; return TimeOfDay(hour: nextHour, minute: 0); } void sendRequest() { if (sentRequests < maxRequests) { setState(() { sentRequests++; }); } } Future _selectFromDate(BuildContext context) async { final DateTime? picked = await showDatePicker( context: context, initialDate: selectedDate ?? DateTime.now(), firstDate: DateTime.now(), lastDate: DateTime.now().add(Duration(days: 3)), helpText: 'Select Date', initialEntryMode: DatePickerEntryMode.calendarOnly, builder: (BuildContext context, Widget? child) { return Theme( data: ThemeData.light().copyWith( inputDecorationTheme: InputDecorationTheme( border: InputBorder.none, // Removes the text field border ), colorScheme: ColorScheme.light( primary: Color(0XFF1D7AFC), onPrimary: Color(0XFFFFFFFF), // Header text color surface: Color(0XFFFFFFFF), onSurface: Colors.black, secondary: Colors.pink, ), dividerColor: Color(0XFFF5F6F6), textButtonTheme: TextButtonThemeData( style: ButtonStyle( foregroundColor: MaterialStateProperty.all( Color(0XFF1D7AFC), ), // Text color //backgroundColor: MaterialStateProperty.all(Colors.grey[200]), // Background textStyle: MaterialStateProperty.all( fontTextStyle(14, Color(0XFF1D7AFC), FontWeight.w600), ), // Font style shape: MaterialStateProperty.all(RoundedRectangleBorder( borderRadius: BorderRadius.circular(8))), // Rounded corners ), // Background of the dialog box ), textTheme: TextTheme( bodyLarge: fontTextStyle(14, Color(0XFF1D7AFC), FontWeight.w600), labelLarge: fontTextStyle(14, Color(0XFF1D7AFC), FontWeight.w600), titleLarge: fontTextStyle(14, Color(0XFF1D7AFC), FontWeight.w600), headlineLarge: fontTextStyle(20, Color(0XFF1D7AFC), FontWeight.w600), ), ), child: child!, ); }, ); if (picked != null) { setState(() { selectedDate = picked; //toDate1 = null; // Reset To Date //toDateController1.clear(); }); } } TimeOfDay _selectedTime = TimeOfDay.now(); String _timeRangeText = ''; Future _selectTime(BuildContext context) async { final TimeOfDay? picked = await showTimePicker( context: context, initialTime: _selectedTime, cancelText: 'Cancel', confirmText: 'Confirm', builder: (BuildContext context, Widget? child) { return Theme( data: ThemeData.light().copyWith( primaryColor: Color(0XFF1D7AFC), timePickerTheme: TimePickerThemeData( backgroundColor: Colors.white, dialBackgroundColor: Colors.white, hourMinuteTextColor: Color(0XFF1D7AFC), dayPeriodTextColor: Color(0XFF1D7AFC), dialTextColor: Color(0XFF1D7AFC), dayPeriodColor: Color(0XFFC3C4C4), hourMinuteShape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(20), side: BorderSide(color: Color(0XFFFFFFFF), width: 2), ), dayPeriodShape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12)), dialHandColor: Color(0XFFC3C4C4), hourMinuteColor: Color(0XFFFFFFFF), dialTextStyle: TextStyle(fontSize: 14, fontWeight: FontWeight.w600), dayPeriodTextStyle: TextStyle(fontSize: 14, fontWeight: FontWeight.w600), hourMinuteTextStyle: TextStyle(fontSize: 50, fontWeight: FontWeight.w600), helpTextStyle: TextStyle(fontSize: 14, fontWeight: FontWeight.w600), ), ), child: child!, ); }, ); if (picked != null) { final now = TimeOfDay.now(); final today = DateTime.now(); final isToday = selectedDate?.day == today.day && selectedDate?.month == today.month && selectedDate?.year == today.year; bool isValid = true; if (isToday) { final nowMinutes = now.hour * 60 + now.minute; final pickedMinutes = picked.hour * 60 + picked.minute; isValid = pickedMinutes >= nowMinutes; } if (isValid) { setState(() { _selectedTime = picked; _timeRangeText = _formatTimeRange(_selectedTime); }); } else { ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text("Please select a time after the current time.")), ); } } } String _formatTimeRange(TimeOfDay start) { final endHour = (start.hour + 2) % 24; final end = TimeOfDay(hour: endHour, minute: start.minute); return '${_formatTime(start)} to ${_formatTime(end)}'; } String _formatTime(TimeOfDay time) { final now = DateTime.now(); final dt = DateTime(now.year, now.month, now.day, time.hour, time.minute); return TimeOfDay.fromDateTime(dt).format(context); } Widget _beforeDataScreen() { return Padding(padding: EdgeInsets.fromLTRB(16, 0, 16, 0), child: Column( children: [ Align( alignment: Alignment.centerLeft, child: Text( "What’s new?", style: fontTextStyle(12, Color(0XFF515253), FontWeight.w400), ), ), SizedBox(height: MediaQuery.of(context).size.height * .012), RichText( text: TextSpan( text: "Find ", style: fontTextStyle(20, Color(0XFF343637), FontWeight.w600), children: [ TextSpan( text: "exclusive offers", style: fontTextStyle(20, Color(0XFF8270DB), FontWeight.w600), ), TextSpan( text: " & best deals available for you.", style: fontTextStyle(20, Color(0XFF343637), FontWeight.w600), ), ], ), ), SizedBox(height: MediaQuery.of(context).size.height * .024), Container( height: 80, width: double.infinity, padding: EdgeInsets.symmetric(horizontal: 12), decoration: BoxDecoration( borderRadius: BorderRadius.circular(24), gradient: LinearGradient( colors: [ Color(0xFFFFFFFF), Color(0xFFE2DCFF) ], // Light gradient background begin: Alignment.centerLeft, end: Alignment.centerRight, ), border: Border.all(color: Color(0xFF8270DB)), ), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Column( mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( 'AQUINTO', style: fontTextStyle(20, Color(0XFF8270DB), FontWeight.w800), ), SizedBox(height: MediaQuery.of(context).size.height * .004), Text( 'Your Water, Your Data, Your Control.', style: fontTextStyle(12, Color(0XFF8270DB), FontWeight.w500), ), ], ), /*Image.asset( 'images/WaterTankImage.png', // Replace with your actual image asset path height: 40, width: 40, fit: BoxFit.contain, ),*/ ], ), ) ], ),); } Widget _beforeDataScreenAbove() { return Column( children: [ SizedBox(height: MediaQuery.of(context).size.height * .024), CircleAvatar(radius: 80, backgroundColor: Colors.grey.shade300), SizedBox(height: MediaQuery.of(context).size.height * .016), Center( child: Text("Book Tanker", style: fontTextStyle(20, Color(0XFF000000), FontWeight.w600)), ), SizedBox(height: MediaQuery.of(context).size.height * .008), Text( "Book a tanker instantly or schedule it\nanytime in the next 3 days", textAlign: TextAlign.center, style: fontTextStyle(12, Color(0XFF515253), FontWeight.w400), ), ], ); } Widget _suppliersDataScreen() { if (isDataLoading) { return Center( child: CircularProgressIndicator( color: primaryColor, strokeWidth: 5.0, ), ); } if (SuppliersList.isEmpty) { return Center( child: Text( 'No Data Available', style: fontTextStyle(12, Color(0XFF000000), FontWeight.w500), ), ); } return Container( color: Color(0xFFF1F1F1), // Set your desired background color padding: EdgeInsets.fromLTRB(16, 0, 16, 0), child: Column( children: [ Column( children: [ Container( padding: EdgeInsets.symmetric(horizontal: 0, vertical: 12), decoration: BoxDecoration( color: Color(0xFFF1F1F1), borderRadius: BorderRadius.circular(8), ), child: Column( children: [ Row( children: [ Image.asset( 'images/info.png', fit: BoxFit.cover, width: 16, // Match the diameter of the CircleAvatar height: 16, ), SizedBox( width: MediaQuery.of(context).size.width * .008, ), Text( "Order requests sent", style: fontTextStyle(14, Color(0XFF2D2E30), FontWeight.w600), ), SizedBox( width: MediaQuery.of(context).size.width * .012, ), Text("$sentRequests/$maxRequests",style: fontTextStyle(14, Color(0XFF515253), FontWeight.w600),), Spacer(), Text("$remainingTime",style: fontTextStyle(12, Color(0XFF515253), FontWeight.w400),), ], ), SizedBox( height: MediaQuery.of(context).size.height * .016, ), Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: List.generate(maxRequests, (index) { return Expanded( child: Container( margin: EdgeInsets.symmetric(horizontal: 4), height: 3, decoration: BoxDecoration( color: index < sentRequests ?Color(0XFF114690) :Color(0XFF939495), borderRadius: BorderRadius.circular(2), ), ), ); }), ), Padding( padding: EdgeInsets.only(top: 12, right: 0), child: Row( mainAxisAlignment: MainAxisAlignment.end, children: [ GestureDetector( onTap: (){}, child: Container( padding: EdgeInsets.symmetric(horizontal: 16, vertical: 12), decoration: BoxDecoration( border: Border.all(color: Color(0XFF515253)), borderRadius: BorderRadius.circular(45), ), child: Row( mainAxisSize: MainAxisSize.min, children: [ Image.asset( 'images/sort.png', width: 12, height: 12, color: Color(0XFF515253), fit: BoxFit.contain, ), SizedBox(width: MediaQuery.of(context).size.width * .008), Text( "Sort", style: fontTextStyle(12, Color(0XFF515253), FontWeight.w400), ), SizedBox(width: MediaQuery.of(context).size.width * .008), Image.asset( 'images/arrow_down.png', width: 16, height: 16, color: Color(0XFF515253), fit: BoxFit.contain, ), ], ), ), ), SizedBox( width: MediaQuery.of(context).size.width * .016, ), GestureDetector( onTap: () async { final result = await Navigator.push( context, MaterialPageRoute( builder: (_) => FilterScreen( initialFilters: { 'all': filterAll, 'connected': filterConnected, 'notConnected': filterNotConnected, 'radiusRange': selectedRadius, 'ratingRange': selectedRating, 'priceRange': selectedPrice, 'pumpChoice': selectedPump, }, ), ), ); if (result != null && result is Map) { setState(() { filterAll = result['all']; filterConnected = result['connected']; filterNotConnected = result['notConnected']; selectedRadius = result['radiusRange']; selectedRating = result['ratingRange']; selectedPrice = result['priceRange']; selectedPump = result['pumpChoice']; }); await getAllSuppliers(); } }, child: Container( padding: EdgeInsets.symmetric(horizontal: 16, vertical: 12), decoration: BoxDecoration( border: Border.all(color: Color(0XFF515253)), borderRadius: BorderRadius.circular(45), ), child: Row( mainAxisSize: MainAxisSize.min, children: [ Image.asset( 'images/filter.png', width: 12, height: 12, color: Color(0XFF515253), fit: BoxFit.contain, ), SizedBox(width: MediaQuery.of(context).size.width * .008), Text( "Filter", style: fontTextStyle(12, Color(0XFF515253), FontWeight.w400), ), ], ), ), ) ], ), ) ], )), ], ), ListView.separated( shrinkWrap: true, physics: NeverScrollableScrollPhysics(), padding: EdgeInsets.zero, itemCount: SuppliersList.length, separatorBuilder: (context, index) => SizedBox( height: MediaQuery.of(context).size.height * .008, ), itemBuilder: (BuildContext context, int index) { //final supplier = SuppliersList[index]; return GestureDetector( onTap: (){ Navigator.push( context, MaterialPageRoute( builder: (context) => SupplierScreenFromSearch(details: SuppliersList[index],)), ); }, child:Card( color: Colors.white, elevation: 2, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12), ), child: Padding( padding: EdgeInsets.all(12), child: Column( children: [ Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ CircleAvatar( radius: 25, backgroundColor: Color(0XFFE8F2FF), child: Image.asset( 'images/profile_user.png', fit: BoxFit.cover, width: 50, height: 50, ), ), SizedBox( width: MediaQuery.of(context).size.width * .024, ), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ /// Name and Rating Row Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Expanded( child: Text( SuppliersList[index].supplier_name, style: fontTextStyle(16, Color(0XFF2D2E30), FontWeight.w600), overflow: TextOverflow.ellipsis, ), ), Row( children: [ Image.asset( 'images/star.png', width: 16, height: 16, ), SizedBox( width: MediaQuery.of(context).size.width * .008, ), Text( '4.2', style: fontTextStyle(10, Color(0XFF515253), FontWeight.w400), ), ], ), ], ), SizedBox( height: MediaQuery.of(context).size.height * .004, ), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( 'Drinking | Bore Water', style: fontTextStyle( 12, Color(0XFF4692FD), FontWeight.w500), ), Text( SuppliersList[index].matchedPrice.toString()!="null"&&SuppliersList[index].matchedPrice.toString()!="" ""? '\u20B9 ${AppSettings.formDouble(SuppliersList[index].matchedPrice.toString())}': '', style: fontTextStyle(10, Color(0XFF515253), FontWeight.w400), ), ], ), SizedBox( height: MediaQuery.of(context).size.height * .004, ), /// Address and Favourite Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Expanded( child: Text( '${SuppliersList[index].displayAddress} ${SuppliersList[index].distanceInMeters} Km', style: fontTextStyle(12, Color(0XFF515253), FontWeight.w400), overflow: TextOverflow.ellipsis, ), ), GestureDetector( onTap: () async { AppSettings.preLoaderDialog(context); if (SuppliersList[index].isFavorite) { try { bool tankerResponse = await AppSettings .removeFavourites( SuppliersList[index] .supplier_id); if (tankerResponse) { Navigator.of(context, rootNavigator: true) .pop(); AppSettings.longSuccessToast( 'Supplier removed from favourites'); await getAllSuppliers(); // await getConnectedSuppliersData(); } else { Navigator.of(context, rootNavigator: true) .pop(); AppSettings.longFailedToast( 'Failed to remove from favourites'); } } catch (e) { Navigator.of(context, rootNavigator: true) .pop(); AppSettings.longFailedToast( 'Failed to remove from favourites'); } } else { try { bool tankerResponse = await AppSettings.addFavourites( SuppliersList[index] .supplier_id); if (tankerResponse) { Navigator.of(context, rootNavigator: true) .pop(); AppSettings.longSuccessToast( 'Supplier added to favourites'); await getAllSuppliers(); //await getConnectedSuppliersData(); //await getAllFavouritesData(); } else { Navigator.of(context, rootNavigator: true) .pop(); AppSettings.longFailedToast( 'Failed to add favourites'); } } catch (e) { Navigator.of(context, rootNavigator: true) .pop(); AppSettings.longFailedToast( 'Failed to add favourites'); } } }, child: Image.asset( SuppliersList[index].isFavorite ? 'images/heart_active.png' : 'images/heart_outline.png', width: 20, height: 20, ), ), ], ), ], ), ), ], ), SizedBox( height: MediaQuery.of(context).size.height * .016, ), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, crossAxisAlignment: CrossAxisAlignment.start, children: [ Expanded( child: GestureDetector( onTap: () {}, child: Container( decoration: BoxDecoration( shape: BoxShape.rectangle, color: Color(0XFFFFFFFF), border: Border.all( width: 1, color: SuppliersList[index].isRequetsedBooking?Color(0XFF1D7AFC):Color(0XFF939495), ), borderRadius: BorderRadius.circular( 24, )), alignment: Alignment.center, child: Padding( padding: EdgeInsets.fromLTRB(16, 12, 16, 12), child: Text('Call', style: fontTextStyle(12, SuppliersList[index].isRequetsedBooking?Color(0XFF1D7AFC):Color(0XFF939495), FontWeight.w600)), ), ), ), ), SizedBox( width: MediaQuery.of(context).size.width * .010, ), Expanded( child: GestureDetector( onTap: () async{ if(!SuppliersList[index].isRequetsedBooking){ requestOrderDialog(SuppliersList[index]); } /* Navigator.push( context, MaterialPageRoute( builder: (context) => PlaceOrder(details: connectedSuppliersList[index],)), );*/ }, child: Container( decoration: BoxDecoration( shape: BoxShape.rectangle, color: SuppliersList[index].isRequetsedBooking?Color(0XFFDBDBDC):Color(0XFF1D7AFC), border: Border.all( width: 1, color: Color(0XFFFFFFFF), ), borderRadius: BorderRadius.circular( 24, )), alignment: Alignment.center,// child: Padding( padding: EdgeInsets.fromLTRB(16, 12, 16, 12), child: Text('Request Order', style: fontTextStyle( 12, Color(0XFFFFFFFF), FontWeight.w600)), ), ), )) ], ) ], ), ), ) , ) ; }, ) ], ), ); } requestOrderDialog(var details) { 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: Column( children: [ Align( alignment: Alignment.centerLeft, child: Text('ORDER DETAILS',style:fontTextStyle(16,Color(0XFF646566),FontWeight.w400)), ), SizedBox(height: MediaQuery.of(context).size.height * .024,), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text('Water Type',style:fontTextStyle(14,Color(0XFF646566),FontWeight.w500)), Text(selectedWaterType!,style:fontTextStyle(14,Color(0XFF2D2E30),FontWeight.w500)), ], ), SizedBox(height: MediaQuery.of(context).size.height * .010,), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text('Capacity',style:fontTextStyle(14,Color(0XFF646566),FontWeight.w500)), Text(selectedCapacity!,style:fontTextStyle(14,Color(0XFF2D2E30),FontWeight.w500)), ], ), SizedBox(height: MediaQuery.of(context).size.height * .010,), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text('Quantity',style:fontTextStyle(14,Color(0XFF646566),FontWeight.w500)), Text(selectedQuantity!,style:fontTextStyle(14,Color(0XFF2D2E30),FontWeight.w500)), ], ), SizedBox(height: MediaQuery.of(context).size.height * .010,), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text('Date',style:fontTextStyle(14,Color(0XFF646566),FontWeight.w500)), Text(DateFormat('dd-MMM-yyyy').format(selectedDate!),style:fontTextStyle(14,Color(0XFF2D2E30),FontWeight.w500)), ], ), SizedBox(height: MediaQuery.of(context).size.height * .010,), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text('Time',style:fontTextStyle(14,Color(0XFF646566),FontWeight.w500)), Text(_timeRangeText,style:fontTextStyle(14,Color(0XFF2D2E30),FontWeight.w500)), ], ), SizedBox(height: MediaQuery.of(context).size.height * .010,), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text('Actual Price',style:fontTextStyle(14,Color(0XFF646566),FontWeight.w500)), Text( details.matchedPrice.toString()!="null"&&details.matchedPrice.toString()!="" ""? '\u20B9 ${AppSettings.formDouble(details.matchedPrice.toString())}': '', style: fontTextStyle(14,Color(0XFF2D2E30),FontWeight.w500), ), ], ), SizedBox(height: MediaQuery.of(context).size.height * .010,), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text('Your Price',style:fontTextStyle(14,Color(0XFF646566),FontWeight.w500)), SizedBox( width: MediaQuery.of(context).size.width * 0.4, // Adjust width as needed child: TextFormField( controller: _quotedAmountController, keyboardType: TextInputType.number, textCapitalization: TextCapitalization.sentences, maxLength: 10, decoration: textFormFieldDecoration(Icons.phone,''), style:fontTextStyle(14,Color(0XFF2D2E30),FontWeight.w500), cursorColor: Color(0XFF1D7AFC), //TextStyle(color: Colors.black,fontWeight: FontWeight.bold), ) ), ], ), SizedBox(height: MediaQuery.of(context).size.height * .010,), Text('A standard 10% advance is payable before order confirmation. The advance payment is negotiable.',style:italicFontTextStyle(12,Color(0XFF515253),FontWeight.w400)), ], ),), actions: [ Center( child: Row( children: [ Expanded( child: GestureDetector( onTap: () { Navigator.of(context).pop(); }, child: Container( decoration: BoxDecoration( color: Colors.white, border: Border.all(color: Color(0XFF1D7AFC), width: 1), borderRadius: BorderRadius.circular(24), ), alignment: Alignment.center, child: Padding( padding: EdgeInsets.symmetric(vertical: 12), child: Text( 'Cancel', style: fontTextStyle(14, Color(0XFF1D7AFC), FontWeight.w600), ), ), ), ), ), SizedBox(width: MediaQuery.of(context).size.width * .010,), Expanded( child: GestureDetector( onTap: () async { final now = DateTime.now(); List validTimes = await _getValidRequestTimes(); if (validTimes.length >= maxRequests) { AppSettings.longFailedToast( "Maximum 5 requests allowed in any 15-minute window."); return; } final text = _quotedAmountController.text.trim(); if (text.isEmpty) return; final quoted = int.tryParse(text); if (quoted == null) { AppSettings.longFailedToast("Invalid number"); return; } // βœ… SAFE matched price parsing final matchedPriceStr = details.matchedPrice ?? ''; final matched = int.tryParse(matchedPriceStr); if (matched == null) { AppSettings.longFailedToast("Invalid matched price"); return; } if (quoted > matched + 300 || quoted < matched - 300) { AppSettings.longFailedToast( "Enter Β±300 within matched price $matched"); return; } // βœ… SAFE required values final supplierId = details.supplier_id ?? ''; if (supplierId.isEmpty) { AppSettings.longFailedToast("Supplier not available"); return; } if (selectedDate == null) { AppSettings.longFailedToast("Please select date"); return; } AppSettings.preLoaderDialog(context); bool isOnline = await AppSettings.internetConnectivity(); if (!isOnline) { Navigator.of(context, rootNavigator: true).pop(); AppSettings.longFailedToast("Check your internet connection."); return; } // βœ… NULL-SAFE PAYLOAD var payload = { "customerId": AppSettings.customerId ?? '', "type_of_water": selectedWaterType ?? '', "capacity": selectedCapacity ?? '', "quantity": selectedQuantity ?? '', "date": DateFormat('dd-MMM-yyyy').format(selectedDate!), "time": _timeRangeText ?? '', "requested_suppliers": [ { "supplierId": supplierId, "quoted_amount": quoted, "time": DateFormat('dd-MM-yyyy HH:mm').format(now), } ], }; debugPrint("PAYLOAD β†’ $payload"); bool status = await AppSettings.requestOrder(payload); Navigator.of(context, rootNavigator: true).pop(); if (status) { _quotedAmountController.clear(); Navigator.pop(context); validTimes.add(now); validTimes.sort(); await _saveRequestTimes(validTimes); sentRequests = validTimes.length; if (validTimes.isNotEmpty) { final firstExpiry = validTimes.first.add(const Duration(minutes: 15)); final remaining = firstExpiry.difference(DateTime.now()).inSeconds; if (remaining > 0) _startCountdownTimer(remaining); } setState(() {}); await getAllSuppliers(); AppSettings.longSuccessToast("Request Sent"); } else { AppSettings.longFailedToast("Request sending failed"); } }, child: Container( decoration: BoxDecoration( color: const Color(0XFF1D7AFC), border: Border.all(color: const Color(0XFF1D7AFC), width: 1), borderRadius: BorderRadius.circular(24), ), alignment: Alignment.center, child: Padding( padding: const EdgeInsets.symmetric(vertical: 12), child: Text( 'Send Request', style: fontTextStyle(14, Colors.white, FontWeight.w600), ), ), ), ), ) ], ), ), ], ); }); }); } Future getAllSuppliers() async { AppSettings.preLoaderDialog(context); bool isOnline = await AppSettings.internetConnectivity(); if (isOnline) { var payload = new Map(); payload["type_of_water"] = selectedWaterType.toString(); payload["capacity"] = selectedCapacity.toString(); payload["quantity"] = selectedQuantity.toString(); payload["date"] = DateFormat('dd-MMM-yyyy').format(selectedDate!); payload["time"] = _timeRangeText.toString(); payload["radius_from"] = selectedRadius?.start.round().toString() ?? ""; payload["radius_to"] = selectedRadius?.end.round().toString() ?? ""; payload["rating_to"] = selectedRating?.end.toString() ?? ""; payload["price_from"] = selectedPrice?.start.round().toString() ?? ""; payload["price_to"] = selectedPrice?.end.round().toString() ?? ""; payload["pump"] = selectedPump.toString(); setState(() { isDataLoading = true; }); try { var response = await AppSettings.getSuppliersForBooking(payload); setState(() { SuppliersList = ((jsonDecode(response)['suppliers']) as List) .map((dynamic model) { return SuppliersModel.fromJson(model); }).toList(); // Clean the selected value: remove comma and " L" String normalizedSelectedLitres = selectedCapacity!.replaceAll(",", "").replaceAll(" L", "").trim(); List suppliersJson = jsonDecode(response)['suppliers']; SuppliersList = suppliersJson.map((supplierData) { List tankers = supplierData['tankers']; String? matchedPrice; for (var tanker in tankers) { String capacity = tanker['capacity'].replaceAll(",", "").trim(); if (capacity == normalizedSelectedLitres) { matchedPrice = tanker['price']; break; } } // Optional: attach matchedPrice to model if supported var supplierModel = SuppliersModel.fromJson(supplierData); supplierModel.matchedPrice = matchedPrice; // <- Add this field to your model class return supplierModel; }).toList(); isDataLoading = false; Navigator.of(context, rootNavigator: true).pop(); }); } catch (e) { setState(() { isDataLoading = false; Navigator.of(context, rootNavigator: true).pop(); }); } } else { AppSettings.longFailedToast('Please check internet connection'); } } @override Widget build(BuildContext context) { return Scaffold( backgroundColor: Colors.white, appBar: AppBar( backgroundColor: Color(0XFFFFFFFF), elevation: 0, scrolledUnderElevation: 0, titleSpacing: 0, title: GestureDetector( onTap: () { _showLocationBottomSheet(); }, child:Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start, children: [ Row( mainAxisSize: MainAxisSize.min, // Ensures it doesn't take extra space children: [ Text( 'Home', style: fontTextStyle(16, Color(0XFF232527), FontWeight.w600), ), SizedBox(width: 5), // Space between text and icon Image.asset( 'images/arrow_down.png', // Replace with your arrow image width: 24, // Adjust size height: 24, ) ], ), Text( AppSettings.userAddress, style: fontTextStyle(10, Color(0XFF515253), FontWeight.w400), ), ], ) ), iconTheme: IconThemeData(color: Color(0XFF2A2A2A)), actions: [ Row( children: [ Padding( padding: EdgeInsets.fromLTRB(0, 10, 10, 10), child: IconButton( icon: Image.asset( 'images/notification_appbar.png', // Example URL image ), onPressed: () {}, ), ) ], ) ], leading: GestureDetector( onTap: () { Navigator.pop(context); }, child: Padding( padding: const EdgeInsets.fromLTRB( 8, 8, 8, 8), // Add padding if needed child: Image.asset( 'images/backbutton_appbar.png', // Replace with your image path fit: BoxFit.contain, // Adjust the fit ), ), ), ), body: ListView( padding: EdgeInsets.symmetric(horizontal: 0), children: [ Padding(padding: EdgeInsets.fromLTRB(16, 0, 16, 0), child: Column( children: [ !isSearchEnabled ? _beforeDataScreenAbove() : Container(), SizedBox(height: MediaQuery.of(context).size.height * .024), _buildDropdownField( hint: "Type of Water", value: selectedWaterType, items: waterTypes, onChanged: (val) => setState(() => selectedWaterType = val), ), SizedBox(height: MediaQuery.of(context).size.height * .012), Row( children: [ Expanded( child: _buildDropdownField( hint: "Capacity", value: selectedCapacity, items: capacities, onChanged: (val) => setState(() => selectedCapacity = val))), SizedBox(width: MediaQuery.of(context).size.width * .024), Expanded( child: _buildDropdownField( hint: "Quantity", value: selectedQuantity, items: quantities, onChanged: (val) => setState(() => selectedQuantity = val))), ], ), SizedBox(height: MediaQuery.of(context).size.height * .012), Row( children: [ Expanded( child: _buildDatePickerField( hint: "Date", value: selectedDate != null ? DateFormat('dd-MMM-yyyy').format(selectedDate!) : null, onTap: () => _selectFromDate(context))), SizedBox(width: MediaQuery.of(context).size.width * .024), Expanded( child: _buildTimePickerField( hint: "Select Time", value: _timeRangeText != '' ? _timeRangeText : null, onTap: () => selectedDate != null ? _selectTime(context) : ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text("Please select date first"))), )), ], ), SizedBox(height: MediaQuery.of(context).size.height * .024), SizedBox( width: double.infinity, height: 41, child: ElevatedButton( onPressed: () async { setState(() { isSearchEnabled = true; }); if (selectedWaterType != '' && selectedCapacity != '' && selectedDate != '' && _timeRangeText != '' && selectedQuantity != '') { await getAllSuppliers(); } else { AppSettings.longFailedToast('Please enter valid details'); } }, style: ElevatedButton.styleFrom( backgroundColor: Color(0XFF1D7AFC), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(24), ), ), child: Text( "Search", style: fontTextStyle(14, Color(0xFFFFFFFF), FontWeight.w600), ), ), ), SizedBox(height: MediaQuery.of(context).size.height * .036), ], ), ), !isSearchEnabled ? _beforeDataScreen() : _suppliersDataScreen(), ], ), ); } Widget _buildDropdownField({ required String hint, required String? value, required List items, required ValueChanged onChanged, }) { return DropdownButtonHideUnderline( child: DropdownButton2( isExpanded: true, hint: Padding( padding: EdgeInsets.symmetric(vertical: 0), child: Text( hint, style: fontTextStyle(14, Color(0XFF939495), FontWeight.w400), ), ), value: value, items: items.map((item) { return DropdownMenuItem( value: item, child: Padding( padding: EdgeInsets.symmetric(vertical: 4), child: Text( item, style: fontTextStyle(14, Color(0xFF2D2E30), FontWeight.w400), ), ), ); }).toList(), onChanged: onChanged, buttonStyleData: ButtonStyleData( height: 48, padding: EdgeInsets.symmetric(horizontal: 12), decoration: BoxDecoration( border: Border.all(color: Color(0XFF939495)), borderRadius: BorderRadius.circular(12), ), ), iconStyleData: IconStyleData( icon: Padding( padding: EdgeInsets.only(right: 4), // Right spacing from edge child: Image.asset( 'images/arrow_down.png', width: 16, height: 16, color: Color(0XFF939495), ), ), ), dropdownStyleData: DropdownStyleData( padding: EdgeInsets.zero, decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(12), ), ), menuItemStyleData: MenuItemStyleData( padding: EdgeInsets.symmetric(horizontal: 12, vertical: 4), ), ), ); } Widget _buildDatePickerField({ required String hint, String? value, required VoidCallback onTap, }) { return GestureDetector( onTap: onTap, child: Container( height: 48, padding: EdgeInsets.symmetric(horizontal: 12), decoration: BoxDecoration( border: Border.all(color: Colors.grey.shade400), borderRadius: BorderRadius.circular(12), ), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( value ?? hint, style: value != null ? fontTextStyle(14, Color(0xFF2D2E30), FontWeight.w400) : fontTextStyle(14, Color(0XFF939495), FontWeight.w400), ), Image.asset( 'images/calender_supplier_landing.png', fit: BoxFit.cover, width: 16, // Match the diameter of the CircleAvatar height: 16, color: Color(0XFF939495), ), ], ), ), ); } Widget _buildTimePickerField({ required String hint, String? value, required VoidCallback onTap, }) { return GestureDetector( onTap: onTap, child: Container( height: 48, padding: EdgeInsets.symmetric(horizontal: 12), decoration: BoxDecoration( border: Border.all(color: Color(0XFF939495)), borderRadius: BorderRadius.circular(12), ), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Expanded( child: SingleChildScrollView( scrollDirection: Axis.horizontal, child: Text( value ?? hint, style: value != null ? fontTextStyle(14, Color(0xFF2D2E30), FontWeight.w400) : fontTextStyle(14, Color(0XFF939495), FontWeight.w400), overflow: TextOverflow.visible, ), ), ), SizedBox(width: MediaQuery.of(context).size.width * .008), Image.asset( 'images/arrow_down.png', width: 16, height: 16, color: Color(0XFF939495), ), ], ), ), ); } }