You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1642 lines
68 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

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<BookATanker> createState() => _BookATankerState();
}
class _BookATankerState extends State<BookATanker> {
String? selectedWaterType;
String? selectedCapacity;
String? selectedQuantity;
String? selectedTime;
DateTime? selectedDate;
List<SuppliersModel> 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<String> waterTypes = ['Drinking Water', 'Bore water'];
final List<String> capacities = [
'1,000 L',
'2,000 L',
'3,000 L',
'5,000 L',
'10,000 L',
'20,000 L'
];
final List<String> quantities = ['1', '2', '3'];
final List<String> 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<void> _loadRequestState() async {
List<DateTime> 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<List<DateTime>> _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<DateTime>()
.where((t) => now.difference(t).inMinutes < 15)
.toList();
return validTimes;
}
Future<void> _saveRequestTimes(List<DateTime> 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<DateTime> 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<void> _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<void> _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(
"Whats 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: <Widget>[
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<DateTime> 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<void> getAllSuppliers() async {
AppSettings.preLoaderDialog(context);
bool isOnline = await AppSettings.internetConnectivity();
if (isOnline) {
var payload = new Map<String, dynamic>();
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<dynamic> suppliersJson = jsonDecode(response)['suppliers'];
SuppliersList = suppliersJson.map((supplierData) {
List<dynamic> 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<String> items,
required ValueChanged<String?> onChanged,
}) {
return DropdownButtonHideUnderline(
child: DropdownButton2<String>(
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<String>(
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),
),
],
),
),
);
}
}