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/plans/plan_suppliers_model.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 PlanRequestScreen extends StatefulWidget { const PlanRequestScreen({super.key}); @override State createState() => _PlanRequestScreenState(); } class _PlanRequestScreenState extends State { String? selectedWaterType; String? selectedFrequency; String? selectedCapacity; String? selectedQuantity; String? selectedTime; DateTime? selectStartDate; DateTime? selectStopDate; List PlanSuppliersList = []; 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 frequencyTypes = ['Daily', 'Weekly Once','Weekly Twice','Weekly Thrice']; 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: selectStartDate ?? 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(() { selectStartDate = picked; //toDate1 = null; // Reset To Date //toDateController1.clear(); }); } } Future _selectToDate(BuildContext context) async { if (selectStartDate == null) { // Handle case when start date is not selected return; } final DateTime firstDate = selectStartDate!; final DateTime lastDate = DateTime( firstDate.year, firstDate.month + 1, firstDate.day, ); final DateTime? picked = await showDatePicker( context: context, initialDate: selectStopDate ?? firstDate, firstDate: firstDate, lastDate: lastDate, helpText: 'Select Date', initialEntryMode: DatePickerEntryMode.calendarOnly, builder: (BuildContext context, Widget? child) { return Theme( data: ThemeData.light().copyWith( inputDecorationTheme: const InputDecorationTheme( border: InputBorder.none, ), colorScheme: const ColorScheme.light( primary: Color(0XFF1D7AFC), onPrimary: Colors.white, surface: Colors.white, onSurface: Colors.black, secondary: Colors.pink, ), dividerColor: Color(0XFFF5F6F6), textButtonTheme: TextButtonThemeData( style: ButtonStyle( foregroundColor: MaterialStatePropertyAll(Color(0XFF1D7AFC)), textStyle: MaterialStatePropertyAll( fontTextStyle(14, Color(0XFF1D7AFC), FontWeight.w600), ), shape: MaterialStatePropertyAll( RoundedRectangleBorder( borderRadius: BorderRadius.all(Radius.circular(8)), ), ), ), ), 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 ?? const SizedBox.shrink(), ); }, ); if (picked != null) { setState(() { selectStopDate = picked; }); } } 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 = selectStartDate?.day == today.day && selectStartDate?.month == today.month && selectStartDate?.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("Plans", style: fontTextStyle(20, Color(0XFF000000), FontWeight.w600)), ), SizedBox(height: MediaQuery.of(context).size.height * .008), Text( "No more last-minute orders! Set up a regular water delivery schedule with this supplier and stay stress-free", textAlign: TextAlign.center, style: fontTextStyle(12, Color(0XFF515253), FontWeight.w400), ), ], ); } Widget _suppliersDataScreen() { if (isDataLoading) { return Center( child: CircularProgressIndicator( color: primaryColor, strokeWidth: 5.0, ), ); } if (PlanSuppliersList.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( "Plan 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 getAllSuppliersPlans(); } }, 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: PlanSuppliersList.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: PlanSuppliersList[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( PlanSuppliersList[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( PlanSuppliersList[index].matchedPrice.toString()!="null"&&PlanSuppliersList[index].matchedPrice.toString()!="" ""? '\u20B9 ${AppSettings.formDouble(PlanSuppliersList[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( '${PlanSuppliersList[index].displayAddress} ${PlanSuppliersList[index].distanceInMeters} Km', style: fontTextStyle(12, Color(0XFF515253), FontWeight.w400), overflow: TextOverflow.ellipsis, ), ), GestureDetector( onTap: () async { AppSettings.preLoaderDialog(context); if (PlanSuppliersList[index].isFavorite) { try { bool tankerResponse = await AppSettings .removeFavourites( PlanSuppliersList[index] .supplier_id); if (tankerResponse) { Navigator.of(context, rootNavigator: true) .pop(); AppSettings.longSuccessToast( 'Supplier removed from favourites'); await getAllSuppliersPlans(); // 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( PlanSuppliersList[index] .supplier_id); if (tankerResponse) { Navigator.of(context, rootNavigator: true) .pop(); AppSettings.longSuccessToast( 'Supplier added to favourites'); await getAllSuppliersPlans(); //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( PlanSuppliersList[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: PlanSuppliersList[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, PlanSuppliersList[index].isRequetsedBooking?Color(0XFF1D7AFC):Color(0XFF939495), FontWeight.w600)), ), ), ), ), SizedBox( width: MediaQuery.of(context).size.width * .010, ), Expanded( child: GestureDetector( onTap: () async{ if(!PlanSuppliersList[index].isRequetsedBooking){ requestPlanDialog(PlanSuppliersList[index]); } /* Navigator.push( context, MaterialPageRoute( builder: (context) => PlaceOrder(details: connectedSuppliersList[index],)), );*/ }, child: Container( decoration: BoxDecoration( shape: BoxShape.rectangle, color: PlanSuppliersList[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 plan', style: fontTextStyle(12, Color(0XFFFFFFFF), FontWeight.w600)), ), ), )) ], ) ], ), ), ) , ) ; }, ) ], ), ); } requestPlanDialog(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('Start Date',style:fontTextStyle(14,Color(0XFF646566),FontWeight.w500)), Text(DateFormat('dd-MMM-yyyy').format(selectStartDate!),style:fontTextStyle(14,Color(0XFF2D2E30),FontWeight.w500)), ], ), SizedBox(height: MediaQuery.of(context).size.height * .010,), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text('End Date',style:fontTextStyle(14,Color(0XFF646566),FontWeight.w500)), Text(DateFormat('dd-MMM-yyyy').format(selectStopDate!),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), ) ), ], ), ], ),), 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; } int matched = int.parse(details.matchedPrice); if (quoted > matched + 300 || quoted < matched - 300) { AppSettings.longFailedToast("Enter less than 300 within matched price ${details.matchedPrice}"); return; } AppSettings.preLoaderDialog(context); bool isOnline = await AppSettings.internetConnectivity(); if (!isOnline) { Navigator.of(context, rootNavigator: true).pop(); AppSettings.longFailedToast("Check your internet connection."); return; } String freq=''; if(selectedFrequency.toString().toLowerCase()=='daily'){ freq='daily'; } else if(selectedFrequency.toString().toLowerCase()=='weekly once'){ freq='weekly_once'; } else if(selectedFrequency.toString().toLowerCase()=='weekly twice'){ freq='weekly_twice'; } else if(selectedFrequency.toString().toLowerCase()=='weekly thrice'){ freq='weekly_thrice'; } var payload = { "customerId": AppSettings.customerId, "type_of_water":selectedWaterType, "capacity":selectedCapacity, "quantity":selectedQuantity, "start_date": DateFormat('dd-MMM-yyyy').format(selectStartDate!), "end_date": DateFormat('dd-MMM-yyyy').format(selectStopDate!), "time": DateFormat('dd-MM-yyyy HH:mm').format(now), "frequency": freq, "weekly_count": 1, "requested_suppliers": [ { "supplierId": details.supplier_id, "quoted_amount": quoted, "time": DateFormat('dd-MM-yyyy HH:mm').format(now), } ], }; bool status = await AppSettings.requestPlan(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(Duration(minutes: 15)); final remaining = firstExpiry.difference(DateTime.now()).inSeconds; if (remaining > 0) _startCountdownTimer(remaining); } setState(() {}); await getAllSuppliersPlans(); AppSettings.longSuccessToast("Request Sent"); } else { AppSettings.longFailedToast("Request sending failed"); } /* final text = _quotedAmountController.text.trim(); if (text.isNotEmpty) { final quoted = int.tryParse(text); if (quoted == null) { // Handle invalid number if needed } else if (quoted > int.parse(details.matchedPrice) + 300 || quoted < int.parse(details.matchedPrice) - 300) { AppSettings.longFailedToast("Please Enter below 300 or above 300 for the selected tanker price ${details.matchedPrice}"); } else { AppSettings.preLoaderDialog(context); bool isOnline = await AppSettings.internetConnectivity(); if (isOnline) { var payload = { "customerId": AppSettings.customerId, "type_of_water": selectedWaterType, "capacity": selectedCapacity, "quantity": selectedQuantity, "date": DateFormat('dd-MMM-yyyy').format(selectedDate!), // e.g., '2025-07-01' "time": _timeRangeText, // e.g., '14:00' "requested_suppliers": [ { "supplierId": details.supplier_id, "quoted_amount": int.parse(_quotedAmountController.text), "time": DateFormat('dd-MM-yyyy HH:mm').format(DateTime.now()), } ], }; bool status = await AppSettings.requestOrder(payload); if(status){ _quotedAmountController.clear(); Navigator.of(context, rootNavigator: true).pop(); Navigator.pop(context); await getAllSuppliers(); AppSettings.longSuccessToast("Request Sent"); } else{ _quotedAmountController.clear(); Navigator.of(context, rootNavigator: true).pop(); AppSettings.longFailedToast("Request sending failed"); } } else { Navigator.of(context, rootNavigator: true).pop(); AppSettings.longFailedToast("Please Check internet"); } } }*/ }, child: Container( decoration: BoxDecoration( color: Color(0XFF1D7AFC), border: Border.all(color: Color(0XFF1D7AFC), width: 1), borderRadius: BorderRadius.circular(24), ), alignment: Alignment.center, child: Padding( padding: EdgeInsets.symmetric(vertical: 12), child: Text( 'Send Request', style: fontTextStyle(14, Colors.white, FontWeight.w600), ), ), ), ), ), ], ), ), ], ); }); }); } Future getAllSuppliersPlans() async { AppSettings.preLoaderDialog(context); bool isOnline = await AppSettings.internetConnectivity(); String freq=''; if(selectedFrequency.toString().toLowerCase()=='daily'){ freq='daily'; } else if(selectedFrequency.toString().toLowerCase()=='weekly once'){ freq='weekly_once'; } else if(selectedFrequency.toString().toLowerCase()=='weekly twice'){ freq='weekly_twice'; } else if(selectedFrequency.toString().toLowerCase()=='weekly thrice'){ freq='weekly_thrice'; } if (isOnline) { var payload = new Map(); payload["type_of_water"] = selectedWaterType.toString(); payload["capacity"] = selectedCapacity.toString(); payload["quantity"] = selectedQuantity.toString(); payload["frequency"] = freq; payload["start_date"] = DateFormat('dd-MMM-yyyy').format(selectStartDate!); payload["end_date"] = DateFormat('dd-MMM-yyyy').format(selectStopDate!); payload["radius_from"] = selectedRadius?.start.round().toString() ?? ""; payload["radius_to"] = selectedRadius?.end.round().toString() ?? ""; payload["rating_from"] = selectedRating?.start.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.getSuppliersForPlan(payload); setState(() { PlanSuppliersList = ((jsonDecode(response)['suppliers']) as List) .map((dynamic model) { return PlanSuppliersModel.fromJson(model); }).toList(); // Clean the selected value: remove comma and " L" String normalizedSelectedLitres = selectedCapacity!.replaceAll(",", "").replaceAll(" L", "").trim(); List suppliersJson = jsonDecode(response)['suppliers']; PlanSuppliersList = 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 = PlanSuppliersModel.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, path: 'images/water.png', onChanged: (val) => setState(() => selectedWaterType = val), ), SizedBox(height: MediaQuery.of(context).size.height * .012), Row( children: [ Expanded( child: _buildDropdownField( hint: "Capacity", value: selectedCapacity, items: capacities, path:'', onChanged: (val) => setState(() => selectedCapacity = val))), SizedBox(width: MediaQuery.of(context).size.width * .024), Expanded( child: _buildDropdownField( hint: "Quantity", value: selectedQuantity, items: quantities, path:'', onChanged: (val) => setState(() => selectedQuantity = val))), ], ), SizedBox(height: MediaQuery.of(context).size.height * .012), _buildDropdownField( hint: "Frequency", value: selectedFrequency, items: frequencyTypes, path:'images/frequency.png', onChanged: (val) => setState(() => selectedFrequency = val), ), SizedBox(height: MediaQuery.of(context).size.height * .012), Row( children: [ Expanded( child: _buildDatePickerField( hint: "Start date", value: selectStartDate != null ? DateFormat('dd-MMM-yyyy').format(selectStartDate!) : null, onTap: () => _selectFromDate(context))), SizedBox(width: MediaQuery.of(context).size.width * .024), Expanded( child: _buildDatePickerField( hint: "End date", value: selectStopDate != null ? DateFormat('dd-MMM-yyyy').format(selectStopDate!) : null, onTap: () => _selectToDate(context))), ], ), SizedBox(height: MediaQuery.of(context).size.height * .024), SizedBox( width: double.infinity, height: 41, child: ElevatedButton( onPressed: () async { // βœ… FIELD VALIDATIONS if (selectedWaterType == null || selectedWaterType!.isEmpty) { AppSettings.longFailedToast('Please select Water Type'); return; } if (selectedCapacity == null || selectedCapacity!.isEmpty) { AppSettings.longFailedToast('Please select Capacity'); return; } if (selectedQuantity == null || selectedQuantity!.isEmpty) { AppSettings.longFailedToast('Please select Quantity'); return; } if (selectedFrequency == null || selectedFrequency!.isEmpty) { AppSettings.longFailedToast('Please select Frequency'); return; } if (selectStartDate == null) { AppSettings.longFailedToast('Please select Start Date'); return; } if (selectStopDate == null) { AppSettings.longFailedToast('Please select End Date'); return; } // βœ… END DATE VALIDATION if (selectStopDate!.isBefore(selectStartDate!)) { AppSettings.longFailedToast('End Date must be after Start Date'); return; } // βœ… SEARCH ENABLE setState(() { isSearchEnabled = true; }); await getAllSuppliersPlans(); }, 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, String? path, // βœ… Make path optional required ValueChanged onChanged, }) { return DropdownButtonHideUnderline( child: DropdownButton2( isExpanded: true, hint: Row( mainAxisSize: MainAxisSize.min, children: [ if (path != null&&path!='') ...[ Image.asset( path, width: 16, height: 16, color: Color(0XFF939495), ), SizedBox(width: 6), ], 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.only(left: 0, right: 12), decoration: BoxDecoration( border: Border.all(color: Color(0XFF939495)), borderRadius: BorderRadius.circular(12), ), ), iconStyleData: IconStyleData( icon: Padding( padding: EdgeInsets.only(right: 4), 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: [ Row( children: [ Image.asset( 'images/calender_supplier_landing.png', fit: BoxFit.cover, width: 16, // Match the diameter of the CircleAvatar height: 16, color: Color(0XFF939495), ), SizedBox( width: MediaQuery.of(context).size.width * .012, ), Text( value ?? hint, style: value != null ? fontTextStyle(14, Color(0xFF2D2E30), FontWeight.w400) : fontTextStyle(14, Color(0XFF939495), FontWeight.w400), ), ], ), Image.asset( 'images/arrow_down.png', width: 16, 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), ), ], ), ), ); } }