book a tanker screens

master
Sneha 3 months ago
parent c72957afb4
commit ae8ec6806d

File diff suppressed because it is too large Load Diff

@ -0,0 +1,187 @@
import 'package:flutter/material.dart';
import 'package:watermanagement/common/settings.dart';
class CancelOrderPage extends StatefulWidget {
var order;
var status;
CancelOrderPage({this.order, this.status});
@override
State<CancelOrderPage> createState() => _CancelOrderPageState();
}
class _CancelOrderPageState extends State<CancelOrderPage> {
String? selectedReason;
final TextEditingController reasonController = TextEditingController();
final List<String> reasons = [
"Changed my mind",
"Found another price",
"Delivery got delayed",
"Do not need it anymore",
"Supplier asked me to cancel",
"Other",
];
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.white,
appBar: AppBar(
backgroundColor: Colors.white,
elevation: 0,
scrolledUnderElevation: 0,
title: Text(
'Cancel Order',
style: fontTextStyle(14, Color(0XFF2A2A2A), FontWeight.w500),
),
iconTheme: IconThemeData(color: Color(0XFF2A2A2A)),
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,
color: Color(0XFF2A2A2A),
height: 24,
width: 24,
),
),
),
),
body: Padding(
padding:EdgeInsets.all(24.0),
child: SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"Why are you cancelling?",
style: fontTextStyle(14, Color(0XFF2A2A2A), FontWeight.w600),
),
SizedBox(height:MediaQuery.of(context).size.height * .008,),
// Radio buttons
...reasons.map((reason) {
return RadioListTile<String>(
value: reason,
groupValue: selectedReason,
activeColor: primaryColor,
contentPadding: EdgeInsets.zero,
title: Text(
reason,
style:
fontTextStyle(12, Color(0XFF2D2E30), FontWeight.w400),
),
onChanged: (value) {
setState(() {
selectedReason = value;
});
},
);
}).toList(),
if (selectedReason == "Other")
Padding(
padding: const EdgeInsets.symmetric(vertical: 10),
child: TextFormField(
controller: reasonController,
maxLines: 5,
style: fontTextStyle(14, Color(0XFF101214), FontWeight.w400),
cursorColor: Color(0XFF1D7AFC),
textCapitalization: TextCapitalization.sentences,
decoration: textFormFieldDecorationHintText(
Icons.phone,
'Describe your reason..',
),
),
),
SizedBox(height:MediaQuery.of(context).size.height * .008,),
Text(
"Cancellation Policy",
style:fontTextStyle(14, Color(0XFF2A2A2A), FontWeight.w600),
),
SizedBox(height:MediaQuery.of(context).size.height * .016,),
Text(
'Cancel anytime before delivery starts. If its already on the way, a ₹100 cancellation fee may apply. Please review our full terms and conditions for more details.',
style:fontTextStyle(12, Color(0XFF2A2A2A), FontWeight.w400),
),
SizedBox(height:MediaQuery.of(context).size.height * .016,),
GestureDetector(
onTap: () {
// open terms page
},
child: Text(
"View Terms & Conditions",
style: fontTextStylewithUnderline(10, Color(0XFF4692FD), FontWeight.w400,Color(0XFF4692FD)),
),
),
],
),
),
),
bottomNavigationBar: Padding(
padding: EdgeInsets.all(24.0),
child: SizedBox(
width: double.infinity,
child: ElevatedButton(
onPressed: () async {
if (selectedReason == null) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text("Please select a reason")),
);
return;
}
String finalReason =
selectedReason == "Other"
? reasonController.text.trim()
: selectedReason!;
if (finalReason.isEmpty) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text("Please enter cancellation reason")),
);
return;
}
bool success = await AppSettings.cancelTankerBooking(
widget.order.dbId,
"cancel",
finalReason,
);
if (success) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text("Order cancelled successfully")),
);
Navigator.pop(context, true);
} else {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text("Failed to cancel order")),
);
}
},
style: ElevatedButton.styleFrom(
backgroundColor: const Color(0XFFE76960),
padding: const EdgeInsets.symmetric(vertical: 14),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(24),
),
),
child: Text(
"CANCEL ORDER",
style: fontTextStyle(14, Color(0XFFFFFFFF), FontWeight.w600),
),
),
),
),
);
}
}

@ -0,0 +1,81 @@
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:watermanagement/common/settings.dart';
import 'package:table_calendar/table_calendar.dart';
import 'package:intl/intl.dart';
import 'package:watermanagement/models/supplier_tankers_model.dart';
class CartSummary extends StatefulWidget {
var details;
var supplierDetails;
CartSummary({this.details,this.supplierDetails});
@override
State<CartSummary> createState() => _CartSummaryState();
}
class _CartSummaryState extends State<CartSummary> {
@override
void initState() {
super.initState();
}
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Color(0XFFFFFFFF),
appBar: AppSettings.appBarWithoutActions(widget.supplierDetails.supplier_name),
body: Padding(
padding: EdgeInsets.fromLTRB(12, 8, 12, 8),
child: Column(
children: [Container(
width: double.infinity, // makes it expand within the Card's width
decoration: BoxDecoration(
borderRadius: BorderRadius.vertical(
bottom: Radius.circular(8),
top:Radius.circular(8),
), // match Card border
gradient: LinearGradient(
colors: [
Color(0xFFFFF8DF),
Color(0xFFFFFFFF),
],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
),
),
padding: EdgeInsets.symmetric(vertical: 12),
alignment: Alignment.center,
child: Padding(
padding: EdgeInsets.fromLTRB(12, 0, 12, 0),
child:Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(widget.supplierDetails.supplier_name,
style: fontTextStyle(
16,
Color(0XFF2D2E30),
FontWeight.w600)),
Text(widget.supplierDetails.distanceInMeters
.toString() +
' Km',
style: fontTextStyle(
16,
Color(0XFF2D2E30),
FontWeight.w600)),
],
),
)),],
)
),
);
}
}

@ -0,0 +1,80 @@
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:geolocator/geolocator.dart';
import 'package:watermanagement/common/settings.dart';
import 'dart:typed_data';
class favouritesModel {
String supplier_name = '';
String status='';
String supplier_address='';
String supplier_phone_number='';
String supplier_alternate_phone_number='';
String supplier_id='';
String about_supplier='';
String startingprice_supplier='';
String supplier_image='';
String distance='';
String distanceInKm='';
String bs64str='';
String picture='';
Map<String,dynamic> picture1={};
Color text_color=Colors.black;
double lat=0;
double lng=0;
var data;
double distanceInMeters=0;
String displayAddress='';
bool isFavorite=false;
//File? updatedImage;
favouritesModel();
factory favouritesModel.fromJson(Map<String, dynamic> json){
favouritesModel rtvm = new favouritesModel();
rtvm.supplier_name = json['suppliername'] ?? '';
rtvm.status = json['status'] ?? '';
rtvm.supplier_address = json['profile']['office_address'] ?? '';
rtvm.supplier_phone_number = json['phone'] ?? '';
rtvm.supplier_alternate_phone_number = json['alternativeContactNumber'] ?? '';
rtvm.supplier_id = json['supplierId'] ?? '';
rtvm.lat = json['latitude'] ?? 0;
rtvm.lng = json['longitude'] ??0;
rtvm.about_supplier = json['description'] ?? '';
rtvm.startingprice_supplier = json['startingPrice'] ?? '';
rtvm.picture = json['picture'] ?? '';
rtvm.isFavorite = json['favorate'] ?? false;
rtvm.distanceInMeters = double.parse((Geolocator.distanceBetween(
rtvm.lat,
rtvm.lng,
AppSettings.userLatitude,
AppSettings.userLongitude
) / 1000).toStringAsFixed(2));
List<String> parts = rtvm.supplier_address.split(',');
rtvm.displayAddress = parts[2].trim();
if(rtvm.status.toString().toLowerCase()=='pending'){
rtvm.text_color=Colors.yellow;
}
else if(rtvm.status.toString().toLowerCase()=='accepted'){
rtvm.text_color=Colors.green;
}
else if(rtvm.status.toString().toLowerCase()=='rejected'){
rtvm.text_color=Colors.red;
}
else{
rtvm.status='Connect?';
rtvm.text_color=primaryColor;
}
return rtvm;
}
}

@ -0,0 +1,296 @@
import 'package:flutter/material.dart';
import 'package:watermanagement/common/settings.dart';
class FilterScreen extends StatefulWidget {
final Map<String, dynamic>? initialFilters;
FilterScreen({this.initialFilters});
@override
_FilterScreenState createState() => _FilterScreenState();
}
class _FilterScreenState extends State<FilterScreen> {
/*bool all = true, connected = false, notConnected = false;
RangeValues radiusRange = RangeValues(4, 15);
RangeValues ratingRange = RangeValues(3.5, 5);
RangeValues priceRange = RangeValues(2500, 5000);
String? pumpChoice;*/
bool all = true, connected = false, notConnected = false;
RangeValues? radiusRange;
RangeValues? ratingRange;
RangeValues? priceRange;
String? pumpChoice;
@override
void initState() {
super.initState();
final filters = widget.initialFilters ?? {};
all = filters['all'] ?? true;
connected = filters['connected'] ?? false;
notConnected = filters['notConnected'] ?? false;
radiusRange = filters['radiusRange'] ?? RangeValues(4, 15);
ratingRange = filters['ratingRange'] ?? RangeValues(3.5, 5);
priceRange = filters['priceRange'] ?? RangeValues(2500, 5000);
pumpChoice = filters['pumpChoice'];
}
void _applyFilters() {
Navigator.pop(context, {
'all': all,
'connected': connected,
'notConnected': notConnected,
'radiusRange': radiusRange,
'ratingRange': ratingRange,
'priceRange': priceRange,
'pumpChoice': pumpChoice,
});
}
void _resetFilters() {
setState(() {
all = true;
connected = false;
notConnected = false;
radiusRange = RangeValues(4, 15);
ratingRange = RangeValues(3.5, 5);
priceRange = RangeValues(2500, 5000);
pumpChoice = null;
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.white,
/*appBar: AppBar(
title: Text("Filters", style: TextStyle(color: Colors.black)),
backgroundColor: Colors.white,
elevation: 0,
automaticallyImplyLeading: false,
actions: [
IconButton(
icon: Icon(Icons.close, color: Colors.black),
onPressed: () => Navigator.pop(context),
),
],
),*/
body: SafeArea(
child:Padding(
padding: EdgeInsets.fromLTRB(16, 0, 16, 0),
child: Column(
children: [
SizedBox(height: MediaQuery.of(context).size.height * .048),
Row(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text("Filters", style: fontTextStyle(14, Color(0XFF2A2A2A), FontWeight.w600),),
GestureDetector(
onTap: (){
Navigator.pop(context);
},
child: Image.asset(
'images/cross.png', // Replace with your arrow image
width: 24, // Adjust size
height: 24,
),
)
],
),
Expanded(
child: ListView(
padding: EdgeInsets.symmetric(horizontal: 0),
children: [
SizedBox(height: MediaQuery.of(context).size.height * .012),
Text("TYPE OF SUPPLIERS", style: fontTextStyle(10, Color(0XFF646566), FontWeight.w400),),
CheckboxListTile(
value: all,
onChanged: (v) => setState(() => all = v!),
contentPadding: EdgeInsets.zero,
dense: true,
activeColor:Color(0XFF000000) ,
visualDensity: VisualDensity.compact,
controlAffinity: ListTileControlAffinity.leading,
title: Text(
"All",
style: fontTextStyle(12, Color(0XFF2D2E30), FontWeight.w400),
),
),
CheckboxListTile(
value: connected,
onChanged: (v) => setState(() => connected = v!),
contentPadding: EdgeInsets.zero,
dense: true,
activeColor:Color(0XFF000000) ,
visualDensity: VisualDensity.compact,
controlAffinity: ListTileControlAffinity.leading,
title: Text(
"Connected",
style: fontTextStyle(12, Color(0XFF2D2E30), FontWeight.w400),
),
),
CheckboxListTile(
value: notConnected,
onChanged: (v) => setState(() => notConnected = v!),
contentPadding: EdgeInsets.zero,
dense: true,
activeColor:Color(0XFF000000) ,
visualDensity: VisualDensity.compact,
controlAffinity: ListTileControlAffinity.leading,
title: Text(
"Not Connected",
style: fontTextStyle(12, Color(0XFF2D2E30), FontWeight.w400),
),
),
Divider(height: 32,color: Color(0XFFC3C4C4) ,),
_buildRangeSlider("SEARCH RADIUS", "km", 0, 25, radiusRange ?? RangeValues(0, 25)),
Divider(height: 32,color: Color(0XFFC3C4C4) ,),
Text("PUMP", style: fontTextStyle(10, Color(0XFF646566), FontWeight.w400),),
RadioListTile<String>(
value: "Yes",
groupValue: pumpChoice,
onChanged: (v) => setState(() => pumpChoice = v),
activeColor:Color(0XFF000000) ,
contentPadding: EdgeInsets.zero,
visualDensity: VisualDensity(horizontal: -4, vertical: -4),
title: Row(
mainAxisSize: MainAxisSize.min,
children: [
Text("Yes", style: fontTextStyle(12, Color(0XFF2D2E30), FontWeight.w400)),
],
),
),
RadioListTile<String>(
value: "No",
groupValue: pumpChoice,
onChanged: (v) => setState(() => pumpChoice = v),
contentPadding: EdgeInsets.zero,
activeColor:Color(0XFF000000) ,
visualDensity: VisualDensity(horizontal: -4, vertical: -4),
title: Row(
mainAxisSize: MainAxisSize.min,
children: [
Text("No", style: fontTextStyle(12, Color(0XFF2D2E30), FontWeight.w400)),
],
),
),
Divider(height: 32,color: Color(0XFFC3C4C4) ,),
_buildRangeSlider("RATING", "", 0, 5, ratingRange?? RangeValues(0, 5)),
Divider(height: 32,color: Color(0XFFC3C4C4) ,),
_buildRangeSlider("PRICE", "", 2500, 5000, priceRange?? RangeValues(0, 5000)),
SizedBox(height: MediaQuery.of(context).size.height * .012),
],
),
),
Padding(
padding: EdgeInsets.fromLTRB(0,0,0,16),
child: Row(
children: [
Expanded(
child: OutlinedButton(
onPressed: _resetFilters,
style: OutlinedButton.styleFrom(
side: BorderSide(color:Color(0XFF2A2A2A)),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
),
child: Text("Reset", style: fontTextStyle(14, Color(0XFF2A2A2A), FontWeight.w600),),
),
),
SizedBox(width: 12),
Expanded(
child: ElevatedButton(
onPressed: _applyFilters,
style: ElevatedButton.styleFrom(
backgroundColor: Color(0XFF2A2A2A),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
),
child: Text("Apply", style: fontTextStyle(14, Color(0XFFFFFFFF), FontWeight.w600),),
),
),
],
),
),
],
),
)
)
);
}
Widget _buildRangeSlider(String title, String suffix, double min, double max, RangeValues values) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
title,
style: fontTextStyle(10, Color(0XFF646566), FontWeight.w400),
),
SliderTheme(
data: SliderTheme.of(context).copyWith(
trackHeight: 1.5,
activeTrackColor: Color(0XFF000000),
inactiveTrackColor: Color(0XFFC3C4C4),
thumbColor: Colors.black,
rangeThumbShape: RoundRangeSliderThumbShape(enabledThumbRadius: 7),
),
child: RangeSlider(
values: values,
min: min,
max: max,
labels: RangeLabels(
"${values.start.round()}$suffix",
"${values.end.round()}$suffix",
),
onChanged: (val) {
setState(() {
if (title == "SEARCH RADIUS") {
radiusRange = val;
} else if (title == "RATING") {
ratingRange = val;
} else if (title == "PRICE") {
priceRange = val;
}
});
},
),
),
Row(
children: [
Text("From", style: fontTextStyle(10, Color(0XFF939495), FontWeight.w400)),
SizedBox(width: MediaQuery.of(context).size.width * .016),
Container(
padding: EdgeInsets.all(2),
decoration: BoxDecoration(
border: Border.all(color: Color(0XFFC3C4C4)),
borderRadius: BorderRadius.circular(6),
),
child: Text(
"${values.start.toStringAsFixed(suffix.isNotEmpty ? 0 : 1)}$suffix",
style: fontTextStyle(10, Color(0XFF2D2E30), FontWeight.w400),
),
),
SizedBox(width: MediaQuery.of(context).size.width * .016),
Text("To", style: fontTextStyle(10, Color(0XFF939495), FontWeight.w400)),
SizedBox(width: MediaQuery.of(context).size.width * .016),
Container(
padding: EdgeInsets.all(2),
decoration: BoxDecoration(
border: Border.all(color: Color(0XFFC3C4C4)),
borderRadius: BorderRadius.circular(6),
),
child: Text(
"${values.end.toStringAsFixed(suffix.isNotEmpty ? 0 : 1)}$suffix",
style: fontTextStyle(10, Color(0XFF2D2E30), FontWeight.w400),
),
),
],
),
],
);
}
}

File diff suppressed because it is too large Load Diff

@ -0,0 +1,661 @@
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:watermanagement/common/settings.dart';
import 'package:watermanagement/supplier/my_orders_model.dart';
import 'cancel_order.dart';
class MyOrders extends StatefulWidget {
final String? navigationFrom;
const MyOrders({this.navigationFrom, Key? key}) : super(key: key);
@override
State<MyOrders> createState() => _MyOrdersState();
}
class _MyOrdersState extends State<MyOrders> with TickerProviderStateMixin {
bool isOrdersDataLoading = false;
List<MyOrdersModel> ordersList = [];
List<MyOrdersModel> upcomingOrders = [];
List<MyOrdersModel> previousOrders = [];
bool isPlansLoading = false;
List<dynamic> plansList = [];
List<dynamic> activePlans = [];
List<dynamic> pendingPlans = [];
late TabController _controller;
String tabMessage = "Welcome to Tab 1";
@override
void initState() {
super.initState();
_controller = TabController(vsync: this, length: 2);
// Listen for tab changes
_controller.addListener(() {
setState(() {
tabMessage =
_controller.index == 0 ? "Welcome to Tab 1" : "Welcome to Tab 2";
});
});
getOrdersData();
getPlansData();
}
Future<void> getPlansData() async {
setState(() => isPlansLoading = true);
try {
final response = await AppSettings.getCustomerPlans();
final decoded = jsonDecode(response);
if (decoded == null || decoded['data'] == null) {
setState(() {
plansList = [];
isPlansLoading = false;
});
return;
}
final List<dynamic> data = decoded['data'];
setState(() {
plansList = data;
activePlans = data.where((p) =>
p['status'] == "processed" ||
p['status'] == "accepted" ||
p['status'] == "payment_completed").toList();
pendingPlans =
data.where((p) => p['status'] == "pending").toList();
isPlansLoading = false;
});
} catch (e) {
print("Plans error $e");
setState(() => isPlansLoading = false);
}
}
Future<void> getOrdersData() async {
setState(() => isOrdersDataLoading = true);
try {
final response = await AppSettings.getAllOrders();
final decoded = jsonDecode(response);
print("🔹 Full API Response: $decoded");
if (decoded == null || decoded['data'] == null) {
print("⚠️ No data key found in API response");
setState(() {
isOrdersDataLoading = false;
ordersList = [];
});
return;
}
final List<dynamic> data = decoded['data'];
if (data.isEmpty) {
print("⚠️ Empty data array");
setState(() {
isOrdersDataLoading = false;
ordersList = [];
});
return;
}
final parsedOrders =
data.map((model) => MyOrdersModel.fromJson(model)).toList();
print("✅ Total orders fetched: ${parsedOrders.length}");
setState(() {
ordersList = parsedOrders;
upcomingOrders = parsedOrders.where((order) {
final status = order.orderStatus.toLowerCase();
return status == "pending" ||
status == "advance_paid" ||
status == "assigned" ||
status == "deliveryboy_assigned";
}).toList();
previousOrders = parsedOrders.where((order) {
final status = order.orderStatus.toLowerCase();
return status == "completed" || status == "cancelled";
}).toList();
isOrdersDataLoading = false;
});
print("📦 Upcoming: ${upcomingOrders.length}, Previous: ${previousOrders.length}");
} catch (e) {
print("❌ Error in getOrdersData: $e");
setState(() => isOrdersDataLoading = false);
}
}
String capitalizeFirst(String input) {
if (input.isEmpty) return input;
return input[0].toUpperCase() + input.substring(1);
}
Widget buildUpcomingOrdersCard(MyOrdersModel order) {
return Card(
color: Colors.white,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
child: Padding(
padding: const EdgeInsets.all(10.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Supplier name & status
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Visibility(
visible: order.supplierName.isNotEmpty,
child: Text(
order.supplierName,
style: fontTextStyle(16, const Color(0xFF2D2E30), FontWeight.w600),
),
),
_statusChip(order.orderStatus, const Color(0xFFFDF3D3), const Color(0xFFF5CD47),
'images/out_for_delivery.png'),
],
),
const SizedBox(height: 6),
// Type + Capacity
Row(
children: [
Text("${order.capacity} L - ${order.typeofwater}",
style: fontTextStyle(12, const Color(0xFF71ABFD), FontWeight.w500)),
const SizedBox(width: 6),
Image.asset('images/arrow-right.png', width: 16, height: 16, color: const Color(0xFF71ABFD)),
],
),
const SizedBox(height: 4),
// Address + Payment
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(
child: Text(order.displayAddress,
style: fontTextStyle(12, const Color(0xFF515253), FontWeight.w400))),
_paymentChip(order.paymentStatus),
],
),
const SizedBox(height: 4),
// Expected Delivery
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text("Expected delivery: ${order.expectedDateOfDelivery}",
style: fontTextStyle(10, const Color(0xFF939495), FontWeight.w500)),
if (order.price.isNotEmpty)
Text("${order.price}",
style: fontTextStyle(12, const Color(0xFF515253), FontWeight.w400)),
],
),
const Divider(height: 20),
// Buttons
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
GestureDetector(
onTap: ()async{
final result = await Navigator.push(
context,
MaterialPageRoute(
builder: (context) => CancelOrderPage(
order: order,
status: order.orderStatus,
),
),
);
if (result == true) {
getOrdersData();
}
},
/* onTap: () => Navigator.push(
context, MaterialPageRoute(builder: (_) => CancelOrderPage(order:order,status: order.orderStatus,))),*/
child: Text("CANCEL ORDER",
style: fontTextStyle(12, const Color(0xFFE2483D), FontWeight.w600))),
const SizedBox(width: 30),
GestureDetector(
onTap: () {},
child: Text("TRACK ORDER",
style: fontTextStyle(12, const Color(0xFF1D7AFC), FontWeight.w600))),
],
),
],
),
),
);
}
Widget buildPreviousOrdersCard(MyOrdersModel order) {
final isDelivered = order.orderStatus == 'completed';
return Card(
color: Colors.white,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
child: Padding(
padding: const EdgeInsets.all(10.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Header Row
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
if (order.supplierName.isNotEmpty)
Text(order.supplierName,
style: fontTextStyle(16, const Color(0xFF2D2E30), FontWeight.w600)),
_statusChip(
order.orderStatus,
isDelivered ? const Color(0xFFC4E8C3) : const Color(0xFFF8D3D0),
isDelivered ? const Color(0xFF098603) : const Color(0xFFE2483D),
isDelivered ? 'images/rite.png' : 'images/cancel.png',
),
],
),
const SizedBox(height: 4),
Text(order.typeofwater,
style: fontTextStyle(12, const Color(0xFF71ABFD), FontWeight.w500)),
const SizedBox(height: 4),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(
child: Text(order.displayAddress,
style: fontTextStyle(12, const Color(0xFF515253), FontWeight.w400))),
_paymentChip(order.paymentStatus),
],
),
const SizedBox(height: 4),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(order.displayDeliveredDate,
style: fontTextStyle(10, const Color(0xFF939495), FontWeight.w500)),
if (order.price.isNotEmpty)
Text("${order.price}",
style: fontTextStyle(12, const Color(0xFF515253), FontWeight.w400)),
],
),
if (isDelivered) ...[
const Divider(height: 20),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
GestureDetector(
onTap: () {},
child: Text("REPEAT ORDER",
style: fontTextStyle(12, const Color(0xFF4692FD), FontWeight.w600)),
),
const SizedBox(width: 30),
GestureDetector(
onTap: () {},
child: Text("RATE ORDER",
style: fontTextStyle(12, const Color(0xFF646566), FontWeight.w600)),
),
],
),
]
],
),
),
);
}
Widget _statusChip(String text, Color bg, Color txt, String img) {
return Container(
padding: const EdgeInsets.fromLTRB(4, 2, 4, 2),
decoration: BoxDecoration(
color: bg, borderRadius: BorderRadius.circular(4), border: Border.all(color: bg)),
child: Row(
children: [
Image.asset(img, width: 12, height: 12),
const SizedBox(width: 4),
Text(capitalizeFirst(text),
style: fontTextStyle(10, txt, FontWeight.w500)),
],
),
);
}
Widget _paymentChip(String status) {
final isPaid = status.toLowerCase() == 'paid';
return Row(
children: [
Image.asset(isPaid ? 'images/paid.png' : 'images/warning.png', width: 16, height: 16),
const SizedBox(width: 4),
Text(capitalizeFirst(status),
style: fontTextStyle(12, const Color(0xFF515253), FontWeight.w400)),
],
);
}
Widget renderUi() {
if (ordersList.isEmpty) {
return const Center(
child: Text("No Data Available",
style: TextStyle(fontSize: 14, fontWeight: FontWeight.w500)));
}
return SingleChildScrollView(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (upcomingOrders.isNotEmpty) ...[
Text("UPCOMING ORDERS",
style: fontTextStyle(12, const Color(0xFF646566), FontWeight.w400)),
const SizedBox(height: 8),
ListView.builder(
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
itemCount: upcomingOrders.length,
itemBuilder: (context, index) =>
buildUpcomingOrdersCard(upcomingOrders[index]),
),
],
if (previousOrders.isNotEmpty) ...[
const SizedBox(height: 16),
Text("PREVIOUS ORDERS",
style: fontTextStyle(12, const Color(0xFF646566), FontWeight.w400)),
const SizedBox(height: 8),
ListView.builder(
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
itemCount: previousOrders.length,
itemBuilder: (context, index) =>
buildPreviousOrdersCard(previousOrders[index]),
),
],
],
),
);
}
Widget buildPlansCard(var plan) {
return Card(
color: Colors.white,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
child: Padding(
padding: const EdgeInsets.all(10),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
"${plan['capacity']} L - ${plan['type_of_water']}",
style: fontTextStyle(
14, const Color(0xFF2D2E30), FontWeight.w600),
),
_statusChip(
plan['status'],
const Color(0xFFE3F2FD),
const Color(0xFF1D7AFC),
'images/rite.png',
),
],
),
const SizedBox(height: 6),
Text(
"Frequency : ${plan['frequency']}",
style: fontTextStyle(
12, const Color(0xFF71ABFD), FontWeight.w500),
),
const SizedBox(height: 4),
Text(
"Start : ${plan['start_date']}",
style: fontTextStyle(
10, const Color(0xFF939495), FontWeight.w500),
),
Text(
"End : ${plan['end_date']}",
style: fontTextStyle(
10, const Color(0xFF939495), FontWeight.w500),
),
const Divider(height: 20),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
GestureDetector(
onTap: () {},
child: Text(
"VIEW PLAN",
style: fontTextStyle(
12, const Color(0xFF1D7AFC), FontWeight.w600),
),
),
const SizedBox(width: 30),
GestureDetector(
onTap: () {},
child: Text(
"CANCEL PLAN",
style: fontTextStyle(
12, const Color(0xFFE2483D), FontWeight.w600),
),
),
],
)
],
),
),
);
}
Widget renderPlansUi() {
if (plansList.isEmpty) {
return const Center(
child: Text("No Plans Available"),
);
}
return SingleChildScrollView(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
/// ACTIVE PLANS
if (activePlans.isNotEmpty) ...[
Text(
"ACTIVE PLANS",
style: fontTextStyle(
12,
const Color(0xFF646566),
FontWeight.w400,
),
),
const SizedBox(height: 10),
ListView.builder(
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
itemCount: activePlans.length,
itemBuilder: (context, index) {
final plan = activePlans[index];
return Padding(
padding: const EdgeInsets.only(bottom: 10),
child: buildPlansCard(plan),
);
},
),
],
/// SPACE
const SizedBox(height: 20),
/// PENDING PLANS
if (pendingPlans.isNotEmpty) ...[
Text(
"PENDING PLANS",
style: fontTextStyle(
12,
const Color(0xFF646566),
FontWeight.w400,
),
),
const SizedBox(height: 10),
ListView.builder(
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
itemCount: pendingPlans.length,
itemBuilder: (context, index) {
final plan = pendingPlans[index];
return Padding(
padding: const EdgeInsets.only(bottom: 10),
child: buildPlansCard(plan),
);
},
),
],
const SizedBox(height: 20),
],
),
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.white,
appBar: AppBar(
backgroundColor: Colors.white,
title: Text(
'Orders',
style: fontTextStyle(14, Color(0XFF2A2A2A), FontWeight.w500),
),
iconTheme: IconThemeData(color: Color(0XFF2A2A2A)),
actions: [
Row(
children: [
Padding(
padding: EdgeInsets.fromLTRB(10, 10, 0, 10),
child: IconButton(
icon: Image(
image: AssetImage('images/customercare_appbar.png')),
onPressed: () {},
),
),
Padding(
padding: EdgeInsets.fromLTRB(0, 10, 10, 10),
child: IconButton(
icon: Image.asset(
'images/notification_appbar.png', // Example URL image
),
onPressed: () {},
),
)
],
)
],
bottom: PreferredSize(
preferredSize: Size.fromHeight(50.0),
child: Container(
color: Colors.white,
child: TabBar(
controller: _controller,
indicatorColor: Colors.transparent,
tabs: [
// Tab 1
Builder(
builder: (BuildContext context) {
return Container(
decoration: BoxDecoration(
color: _controller.index == 0
? Color(0XFFFE8F2FF)
: Colors.white, // Selected tab color
borderRadius: BorderRadius.circular(10),
border: Border.all(
color: _controller.index == 0
? Color(0XFFFE8F2FF)
: Colors.white,
width: 2),
),
child: Tab(
child: Center(
child: Text('Orders',
style: fontTextStyle(12, Color(0XFF101214),
FontWeight.w600))),
),
);
},
),
// Tab 2
Builder(
builder: (BuildContext context) {
return Container(
decoration: BoxDecoration(
color: _controller.index == 1
? Color(0XFFFE8F2FF)
: Colors.white, // Selected tab color
borderRadius: BorderRadius.circular(10),
border: Border.all(
color: _controller.index == 1
? Color(0XFFFE8F2FF)
: Colors.white,
width: 2),
),
child: Tab(
child: Center(
child: Text('Plans',
style: fontTextStyle(12, Color(0XFF101214),
FontWeight.w600))),
),
);
},
),
],
)),
),
),
body: Builder(
builder: (BuildContext context) {
return TabBarView(
controller: _controller,
children: [
isOrdersDataLoading
? const Center(child: CircularProgressIndicator())
: renderUi(),
isPlansLoading
? const Center(child: CircularProgressIndicator())
: renderPlansUi(),
],
);
},
),
);
}
}

@ -0,0 +1,151 @@
import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
class MyOrdersModel {
String tankerName = '';
String bookingid = '';
String supplierId = '';
String dateOfOrder = '';
String typeofwater = '';
String delivery_agent_mobile = '';
String delivery_agent = '';
String capacity = '';
String address = '';
String price = '';
String customerId = '';
String startTime = '';
String stopTime = '';
String orderStatus = '';
String initialWaterLevel = '';
String finalWaterLevel = '';
String quantityDelivered = '';
String amountPaid = '';
String amountDue = '';
String paymentMode = '';
String deliverdWater = '';
String supplierName = '';
String deliveredDate = '';
String displayDeliveredDate = '';
String displayAddress = '';
String expectedDateOfDelivery = '';
String displayDateOfOrder = '';
String paymentStatus = '';
String tankName = '';
var tankerRunningStatus;
double lat = 0;
double lng = 0;
Color cardColor = Colors.white;
String tank_otp='';
String stop_otp='';
String dbId='';
MyOrdersModel();
factory MyOrdersModel.fromJson(Map<String, dynamic> json) {
final rtvm = MyOrdersModel();
rtvm.tankerName = json['tankerName']?.toString() ?? '';
rtvm.bookingid = json['bookingid']?.toString() ?? '';
rtvm.supplierId = json['supplierId']?.toString() ?? '';
rtvm.dateOfOrder = json['dateOfOrder']?.toString() ?? '';
rtvm.typeofwater = json['typeofwater']?.toString() ?? '';
rtvm.supplierName = json['supplierName']?.toString() ?? '';
rtvm.delivery_agent_mobile = json['delivery_agent_mobile']?.toString() ?? '';
rtvm.capacity = json['capacity']?.toString() ?? '';
rtvm.address = json['address']?.toString() ?? '';
rtvm.price = json['price']?.toString() ?? '';
rtvm.orderStatus = json['orderStatus']?.toString().toLowerCase() ?? '';
rtvm.customerId = json['customerId']?.toString() ?? '';
rtvm.startTime = json['start_time']?.toString() ?? '';
rtvm.stopTime = json['stop_time']?.toString() ?? '';
rtvm.initialWaterLevel = json['initial_water_level']?.toString() ?? '';
rtvm.finalWaterLevel = json['final_water_level']?.toString() ?? '';
rtvm.quantityDelivered = json['quantityDelivered']?.toString() ?? '';
rtvm.amountPaid = json['amount_paid']?.toString() ?? '';
rtvm.amountDue = json['amount_due']?.toString() ?? '';
rtvm.paymentMode = json['payment_mode']?.toString() ?? '';
rtvm.deliverdWater = json['quantityDelivered']?.toString() ?? '';
rtvm.deliveredDate = json['deliveredDate']?.toString() ?? '';
rtvm.expectedDateOfDelivery = json["expectedDateOfDelivery"]?.toString() ?? '';
rtvm.tankerRunningStatus = json['tankerRunningStatus'] ?? '';
rtvm.delivery_agent = json['delivery_agent']?.toString() ?? '';
rtvm.paymentStatus = json['payment_status']?.toString() ?? '';
rtvm.tankName = json['tankName']?.toString() ?? '';
rtvm.tank_otp = json['tank_otp']?.toString() ?? '';
rtvm.dbId = json['_id']?.toString() ?? '';
rtvm.stop_otp = json['stop_otp']?.toString() ?? '';
// Handle Delivered Date (Safe Parsing)
if (rtvm.deliveredDate.isNotEmpty) {
try {
final inputFormat = DateFormat("dd-MMM-yyyy - HH:mm");
final dateTime = inputFormat.parse(rtvm.deliveredDate);
final outputFormat = DateFormat("MMMM d yyyy, h:mm a");
rtvm.displayDeliveredDate = outputFormat.format(dateTime);
} catch (_) {
rtvm.displayDeliveredDate = rtvm.deliveredDate; // fallback to raw
}
}
// Handle Date of Order
if (rtvm.dateOfOrder.isNotEmpty) {
final raw = rtvm.dateOfOrder.trim();
DateTime? dt;
try {
// Normalize format
String s = raw.replaceAll(RegExp(r'\s*-\s*'), '-');
s = s.replaceAllMapped(RegExp(r'(\d{1,2})-([A-Za-z]{3,9})-(\d{4})'), (m) {
final mon = m.group(2)!;
final cap = mon[0].toUpperCase() + mon.substring(1).toLowerCase();
return "${m.group(1)}-$cap-${m.group(3)}";
});
final patterns = <String>[
"dd-MMM-yyyy-HH:mm",
"dd-MMM-yyyy HH:mm",
"dd-MMM-yyyy",
"dd-MM-yyyy HH:mm",
"dd-MM-yyyy",
"yyyy-MM-dd HH:mm:ss",
"yyyy-MM-dd",
];
for (final p in patterns) {
try {
dt = DateFormat(p, 'en_US').parseStrict(s);
break;
} catch (_) {}
}
if (dt == null) dt = DateTime.tryParse(raw);
} catch (_) {}
if (dt != null) {
final hasTime = RegExp(r'\d{1,2}:\d{2}').hasMatch(raw);
final fmt = DateFormat(hasTime ? "MMMM d yyyy, h:mm a" : "MMMM d yyyy");
rtvm.displayDateOfOrder = fmt.format(dt);
}
}
// Handle Address
final rawAddr = (rtvm.address ?? '').trim();
if (rawAddr.isNotEmpty) {
final parts = rawAddr.split(',').map((e) => e.trim()).toList();
parts.removeWhere((p) =>
p.toLowerCase().contains("india") ||
RegExp(r'\d{6}').hasMatch(p) ||
p.toLowerCase().contains("telangana"));
rtvm.displayAddress = parts.length >= 2 ? parts[parts.length - 2] : rawAddr;
} else {
rtvm.displayAddress = '';
}
// Latitude & Longitude Parsing
rtvm.lat = double.tryParse(json['latitude']?.toString() ?? '0') ?? 0;
rtvm.lng = double.tryParse(json['longitude']?.toString() ?? '0') ?? 0;
return rtvm;
}
}

File diff suppressed because it is too large Load Diff

@ -0,0 +1,904 @@
// ------------- FULL FILE STARTS HERE ---------------
import 'dart:convert';
import 'package:dropdown_button2/dropdown_button2.dart';
import 'package:flutter/material.dart';
import 'package:watermanagement/common/settings.dart';
import '../models/tanksview_model.dart';
class ArrivalScreen extends StatefulWidget {
var orderDetails;
ArrivalScreen({
required this.orderDetails,
});
@override
State<ArrivalScreen> createState() => _ArrivalScreenState();
}
class _ArrivalScreenState extends State<ArrivalScreen> {
bool isLoading = false;
List<GetTanksDetailsModel> tankLevelsList = [];
List<GetTanksDetailsModel> sumpTanks = [];
GetTanksDetailsModel? selectedTank;
String otp = '';
String unload_complete_otp = '';
String? selectedTankId;
bool _showOrderSummary = false;
int currentStep = 0;
bool allowTankChange = true;
// -------------------------------------------
// FETCH TANK DATA
// -------------------------------------------
Future<void> getAllTanksData() async {
isLoading = true;
var response1 = await AppSettings.getTankLevels();
setState(() {
tankLevelsList =
((jsonDecode(response1)['data']) as List).map((dynamic model) {
return GetTanksDetailsModel.fromJson(model);
}).toList();
sumpTanks = tankLevelsList
.where((product) =>
product.tank_location.toString().toUpperCase() == 'SUMP')
.toList();
isLoading = false;
});
}
@override
void initState() {
getAllTanksData();
super.initState();
otp = widget.orderDetails.tank_otp ?? '';
unload_complete_otp = widget.orderDetails.stop_otp ?? '';
_setChecklistStepFromStatus();
}
// -------------------------------------------
// DETERMINE CHECKLIST STEP + LOCK TANK CHANGE
// -------------------------------------------
void _setChecklistStepFromStatus() {
String status = (widget.orderDetails.orderStatus ?? "").toLowerCase().trim();
if (status == "unloading_started" || status == "in_progress") {
currentStep = 1; // Step 1 active
allowTankChange = false;
}
else if (status == "unloading_stopped" || status == "unloading_completed") {
currentStep = 2; // Step 2 active View Bill ENABLED
allowTankChange = false;
}
else if (status == "payment_pending" || status == "payment_waiting") {
currentStep = 2; // Still show Step 2
allowTankChange = false;
}
else {
currentStep = 0; // Initial stage
allowTankChange = true; // Only here tank change is allowed
}
setState(() {});
}
// -------------------------------------------
// REFRESH ALL DATA
// -------------------------------------------
Future<void> _refreshAllData() async {
try {
var response =
await AppSettings.updateBookingDetailsById(widget.orderDetails.dbId);
if (response != '') {
final data = jsonDecode(response)['data'] ?? {};
widget.orderDetails.orderStatus =
data["orderStatus"] ?? widget.orderDetails.orderStatus;
widget.orderDetails.tank_otp =
data["tank_otp"] ?? widget.orderDetails.tank_otp;
widget.orderDetails.stop_otp =
data["stop_otp"] ?? widget.orderDetails.stop_otp;
widget.orderDetails.tankName =
data["tankName"] ?? widget.orderDetails.tankName;
otp = widget.orderDetails.tank_otp ?? '';
unload_complete_otp = widget.orderDetails.stop_otp ?? '';
selectedTankId = widget.orderDetails.tankName ?? '';
}
await getAllTanksData();
_setChecklistStepFromStatus();
} catch (e) {
print("Refresh failed: $e");
}
}
// -------------------------------------------
// UPDATE TANK NAME TO SERVER
// -------------------------------------------
Future<void> _updateTankName(String tankName) async {
var payload = {"tankName": tankName};
var updateTankStatus = await AppSettings.updateTankNameWhileDelivery(
widget.orderDetails.dbId, payload);
if (updateTankStatus != '') {
AppSettings.longSuccessToast('Updated successfully');
final updatedData = jsonDecode(updateTankStatus)['data'] ?? {};
final updatedTankName = updatedData["tankName"]?.toString() ?? tankName;
final updatedOtp = updatedData["tank_otp"]?.toString() ?? "";
setState(() {
selectedTankId = updatedTankName;
otp = updatedOtp;
try {
widget.orderDetails.tankName = updatedTankName;
widget.orderDetails.tank_otp = updatedOtp;
} catch (_) {}
});
/*Navigator.of(context).pop(true);*/
} else {
AppSettings.longFailedToast('Update failed');
}
}
// -------------------------------------------
// BUILD MAIN UI
// -------------------------------------------
@override
Widget build(BuildContext context) {
final bool hasTankAlready =
widget.orderDetails.tankName != null &&
widget.orderDetails.tankName!.isNotEmpty;
GetTanksDetailsModel? preSelectedTank;
if (hasTankAlready) {
try {
preSelectedTank = sumpTanks.firstWhere(
(t) => t.tank_name == widget.orderDetails.tankName);
} catch (e) {
preSelectedTank = null;
}
}
return Scaffold(
backgroundColor: Colors.white,
appBar: AppSettings.supplierAppBarWithoutActions(
'Order#${widget.orderDetails.bookingid}', context),
body: WillPopScope(
onWillPop: () async {
Navigator.pop(context, true);
return false;
},
child: SafeArea(
child: RefreshIndicator(
onRefresh: _refreshAllData,
displacement: 60,
strokeWidth: 3,
color: Color(0xFF1D7AFC),
backgroundColor: Colors.white,
triggerMode: RefreshIndicatorTriggerMode.onEdge,
child: CustomScrollView(
physics: AlwaysScrollableScrollPhysics(),
slivers: [
SliverToBoxAdapter(
child: buildMainBody(
context, hasTankAlready, preSelectedTank),
),
],
),
),
),
),
);
}
// -------------------------------------------
// YOUR ENTIRE UI BODY
// -------------------------------------------
Widget buildMainBody(context, hasTankAlready, preSelectedTank) {
return SingleChildScrollView(
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
// ------------ BANNER -----------------
Container(
padding: EdgeInsets.all(12),
decoration: BoxDecoration(
color: Color(0xFFFFF4D9),
borderRadius: BorderRadius.circular(12),
),
child: Column(
children: [
Text(
"Arrived at your location!",
style: fontTextStyle(20, Color(0xFF232527), FontWeight.w800),
),
SizedBox(height: 4),
RichText(
text: TextSpan(
children: [
TextSpan(
text: "HOME",
style: fontTextStyle(
11, Color(0xFF343637), FontWeight.w500),
),
TextSpan(
text: " - ${AppSettings.userAddress}",
style: fontTextStyle(
11, Color(0xFF343637), FontWeight.w400),
),
],
),
),
],
),
),
SizedBox(height: 16),
// ------------ PROFILE -----------------
Row(
children: [
CircleAvatar(
radius: 20,
backgroundColor: Color(0XFFE8F2FF),
child: Image.asset('images/profile_user.png',
width: 50, height: 50),
),
SizedBox(width: 8),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
widget.orderDetails.delivery_agent,
style: fontTextStyle(
12, Color(0xFF343637), FontWeight.w500),
),
SizedBox(height: 4),
Text(
"TS J8 8905",
style: fontTextStyle(
12, Color(0xFF646566), FontWeight.w500),
),
],
),
Spacer(),
Row(children: [
Image.asset('images/message.png', width: 24, height: 24),
SizedBox(width: 16),
Image.asset('images/phone.png', width: 24, height: 24),
])
],
),
SizedBox(height: 16),
// ------------ TANK DROPDOWN -----------------
buildTankDropdown(hasTankAlready, preSelectedTank),
SizedBox(height: 16),
// ------------ OTP Boxes -----------------
if (selectedTankId != null || hasTankAlready)
buildStartOtp(),
SizedBox(height: 16),
if (unload_complete_otp != '') buildStopOtp(),
SizedBox(height: 16),
// ------------ ORDER SUMMARY -----------------
buildOrderSummary(),
SizedBox(height: 16),
// ------------ CHECKLIST -----------------
buildChecklist(),
],
),
),
);
}
// -------------------------------------------
// DROPDOWN WITH BLOCKED LOGIC
// -------------------------------------------
Widget buildTankDropdown(hasTankAlready, preSelectedTank) {
return Container(
padding: EdgeInsets.symmetric(horizontal: 12, vertical: 10),
decoration: BoxDecoration(
border: Border.all(color: Colors.grey.shade400),
borderRadius: BorderRadius.circular(8),
),
child: Column(children: [
Row(
children: [
Expanded(
child: Text(
hasTankAlready
? "Change tank to unload water to generate OTP"
: "Choose a tank to unload water to generate OTP",
style: fontTextStyle(12, Color(0xFF444444), FontWeight.w600),
),
),
SizedBox(width: 8),
Expanded(
child: isLoading
? Center(
child: CircularProgressIndicator(
color: primaryColor,
strokeWidth: 5,
),
)
: _buildDropdownField(
hint: hasTankAlready
? widget.orderDetails.tankName
: "Select Tank",
value: selectedTank ?? preSelectedTank,
items: sumpTanks,
// 🔥 OPTION B TANK CHANGE BLOCKED HERE
onChanged: (GetTanksDetailsModel? tank) {
if (tank == null) return;
// BLOCK AFTER UNLOADING STARTED
if (!allowTankChange) {
AppSettings.longFailedToast(
"Tank cannot be changed after unloading has started.");
return;
}
// MOTOR CHECK
bool motorOn = false;
if (tank.inConnections.isNotEmpty) {
motorOn = tank.inConnections.any(
(conn) => conn['motor_status']?.toString() == '2');
}
if (!motorOn && tank.outConnections.isNotEmpty) {
motorOn = tank.outConnections.any(
(conn) => conn['motor_status']?.toString() == '2');
}
if (motorOn) {
showMotorOnAlert(tank);
return;
}
showTankConfirmDialog(tank);
},
),
),
],
)
]),
);
}
// -------------------------------------------
// ALERT WHEN MOTOR ON
// -------------------------------------------
void showMotorOnAlert(GetTanksDetailsModel tank) {
showDialog(
context: context,
barrierDismissible: false,
builder: (_) => AlertDialog(
backgroundColor: Colors.white,
shape:
RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
title: Center(
child: Text(
"The motor of \"${tank.tank_name}\" is turned on. Please turn it off to start unloading.",
style: fontTextStyle(14, Colors.black, FontWeight.w500),
textAlign: TextAlign.center,
),
),
actions: [
Center(
child: Row(
children: [
Expanded(
child: GestureDetector(
onTap: () => Navigator.pop(context),
child: Container(
alignment: Alignment.center,
padding: EdgeInsets.symmetric(vertical: 12),
decoration: BoxDecoration(
border: Border.all(color: Color(0xFF1D7AFC)),
borderRadius: BorderRadius.circular(24),
),
child: Text("Cancel",
style: fontTextStyle(
12, Color(0xFF1D7AFC), FontWeight.w600)),
),
),
),
SizedBox(width: 12),
Expanded(
child: GestureDetector(
onTap: () {
Navigator.pop(context);
setState(() => selectedTank = tank);
},
child: Container(
alignment: Alignment.center,
padding: EdgeInsets.symmetric(vertical: 12),
decoration: BoxDecoration(
color: Color(0xFFC03D34),
borderRadius: BorderRadius.circular(24),
),
child: Text(
"Stop Motor",
style: fontTextStyle(
12, Colors.white, FontWeight.w600),
),
),
),
),
],
),
),
],
),
);
}
// -------------------------------------------
// CONFIRM TANK CHANGE
// -------------------------------------------
void showTankConfirmDialog(GetTanksDetailsModel tank) {
showDialog(
context: context,
barrierDismissible: false,
builder: (_) => AlertDialog(
backgroundColor: Colors.white,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
title: Center(
child: Text(
"You have selected \"${tank.tank_name}\" for water unloading. Do you confirm?",
style: fontTextStyle(14, Colors.black, FontWeight.w500),
textAlign: TextAlign.center,
),
),
actions: [
Center(
child: Row(
children: [
Expanded(
child: GestureDetector(
onTap: () => Navigator.pop(context),
child: Container(
alignment: Alignment.center,
padding: EdgeInsets.symmetric(vertical: 12),
decoration: BoxDecoration(
border: Border.all(color: Color(0xFF1D7AFC)),
borderRadius: BorderRadius.circular(24),
),
child: Text(
"Cancel",
style: fontTextStyle(
12, Color(0xFF1D7AFC), FontWeight.w600),
),
),
),
),
SizedBox(width: 12),
Expanded(
child: GestureDetector(
onTap: () async {
await _updateTankName(tank.tank_name);
setState(() {
selectedTank = tank;
selectedTankId = tank.tank_name;
});
Navigator.pop(context);
},
child: Container(
alignment: Alignment.center,
padding: EdgeInsets.symmetric(vertical: 12),
decoration: BoxDecoration(
color: Color(0xFF1D7AFC),
borderRadius: BorderRadius.circular(24),
),
child: Text(
"Confirm",
style: fontTextStyle(
12, Colors.white, FontWeight.w600),
),
),
),
),
],
),
),
],
),
);
}
// -------------------------------------------
// OTP BOXES
// -------------------------------------------
Widget buildStartOtp() {
return Container(
decoration: BoxDecoration(
color: Color(0xFFE8F2FF),
borderRadius: BorderRadius.circular(8),
),
padding: EdgeInsets.all(8),
child: Row(
children: [
Expanded(
child: Text("Share the OTP to start unloading",
style: fontTextStyle(
12, Color(0xFF444444), FontWeight.w600))),
Expanded(child: buildOtpBoxes(otp)),
],
),
);
}
Widget buildStopOtp() {
return Container(
decoration: BoxDecoration(
color: Color(0xFFE8F2FF),
borderRadius: BorderRadius.circular(8),
),
padding: EdgeInsets.all(8),
child: Row(
children: [
Expanded(
child: Text("Share the OTP to stop unloading",
style: fontTextStyle(
12, Color(0xFF444444), FontWeight.w600))),
Expanded(child: buildOtpBoxes(unload_complete_otp)),
],
),
);
}
Row buildOtpBoxes(String otp) {
return Row(
mainAxisAlignment: MainAxisAlignment.end,
children: (otp.isNotEmpty ? otp : "----")
.split('')
.map((digit) {
return Container(
width: 28,
height: 28,
alignment: Alignment.center,
margin: EdgeInsets.symmetric(horizontal: 1),
decoration: BoxDecoration(
color: Color(0xFF4692FD),
borderRadius: BorderRadius.circular(4),
),
child: Text(
digit,
style: fontTextStyle(12, Colors.white, FontWeight.w500),
),
);
}).toList(),
);
}
// -------------------------------------------
// ORDER SUMMARY
// -------------------------------------------
Widget buildOrderSummary() {
return Column(
children: [
InkWell(
onTap: () => setState(() => _showOrderSummary = !_showOrderSummary),
child: Row(
children: [
Text("View Order Summary",
style: fontTextStyle(
14, Color(0xFF1D7AFC), FontWeight.w600)),
Spacer(),
Image.asset(
_showOrderSummary
? 'images/arrow-up.png'
: 'images/arrow-down.png',
width: 24,
height: 24,
color: Color(0xFF1D7AFC),
)
],
),
),
if (_showOrderSummary)
AnimatedContainer(
duration: Duration(milliseconds: 300),
child: Card(
color: Colors.white,
child: Padding(
padding: EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text("ITEMS",
style: fontTextStyle(
12, Color(0xFF444444), FontWeight.w700)),
SizedBox(height: 8),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text("10,000 L Drinking water x 1",
style: fontTextStyle(
12, Color(0xFF515253), FontWeight.w400)),
Text("₹2,500",
style: fontTextStyle(
12, Color(0xFF515253), FontWeight.w400)),
],
),
Divider(),
_priceRow("Item Total", "₹2,346.00"),
_priceRow("Delivery Charges", "₹150.00"),
_priceRow("Platform Fee", "₹6.00"),
_priceRow("Taxes", "₹12.49"),
Divider(),
_priceRow("Total Bill", "₹2,514", isBold: true),
Divider(),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text("Mode of Payment",
style: fontTextStyle(
12, Color(0xFF444444), FontWeight.w500)),
Row(
children: [
Image.asset('images/success-toast.png',
width: 12, height: 12),
SizedBox(width: 4),
Text("Cash on delivery",
style: fontTextStyle(
12, Color(0xFF444444), FontWeight.w500)),
],
)
],
)
],
),
),
),
)
],
);
}
// -------------------------------------------
// CHECKLIST
// -------------------------------------------
Widget buildChecklist() {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text("Arrival Checklist",
style:
TextStyle(fontSize: 16, fontWeight: FontWeight.bold)),
SizedBox(height: 16),
_buildChecklistItem(
icon: Icons.play_circle_fill,
title: "Start unloading water",
subtitle:
"The status will be updated once the delivery agent starts unloading water.",
stepIndex: 0,
currentStep: currentStep,
selectedTankId: selectedTankId,
onTap: () {},
),
_buildChecklistItem(
icon: Icons.check_circle,
title: "Complete unloading",
subtitle:
"The status will be updated after you confirm that unloading is complete.",
stepIndex: 1,
currentStep: currentStep,
selectedTankId: selectedTankId,
onTap: () {},
),
_buildChecklistItem(
icon: Icons.receipt_long,
title: "View your bill",
subtitle:
"The bill will be calculated after unloading is complete.",
stepIndex: 2,
currentStep: currentStep,
selectedTankId: selectedTankId,
onTap: () {},
),
],
);
}
// -------------------------------------------
// HELPERS
// -------------------------------------------
Widget _buildDropdownField({
required String hint,
required GetTanksDetailsModel? value,
required List<GetTanksDetailsModel> items,
String? path,
required ValueChanged<GetTanksDetailsModel?> onChanged,
}) {
return DropdownButtonHideUnderline(
child: DropdownButton2<GetTanksDetailsModel>(
isExpanded: true,
hint: Row(
children: [
if (path != null && path.isNotEmpty)
Image.asset(path, width: 16, height: 16),
SizedBox(width: 6),
Text(hint,
style: fontTextStyle(
14, Color(0XFF939495), FontWeight.w400)),
],
),
value: value,
items: items.map((tank) {
return DropdownMenuItem(
value: tank,
child: Padding(
padding: EdgeInsets.symmetric(vertical: 4),
child: Text(tank.tank_name,
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)),
),
),
),
);
}
}
Widget _buildChecklistItem({
required IconData icon,
required String title,
required String subtitle,
required int stepIndex,
required int currentStep,
required VoidCallback onTap,
required String? selectedTankId,
}) {
bool isCompleted = currentStep > stepIndex;
bool isActive = currentStep == stepIndex;
// 🔥 Only allow clicking when this is the ACTIVE step AND it is step 2
bool isClickable = (stepIndex == 2 && isActive);
return GestureDetector(
onTap: isClickable ? onTap : null,
child: Opacity(
opacity: 1,
child: Padding(
padding: const EdgeInsets.only(bottom: 16),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Column(
children: [
Icon(
(isActive || isCompleted)
? Icons.radio_button_checked
: Icons.radio_button_unchecked,
color: (isActive || isCompleted) ? Colors.blue : Colors.grey,
),
if (stepIndex < 2)
Container(
width: 2,
height: 50,
color: isCompleted ? Colors.blue : Colors.grey[300],
),
],
),
const SizedBox(width: 12),
Expanded(
child: Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: const Color(0xFFF5F7FA),
borderRadius: BorderRadius.circular(12),
),
child: Row(
children: [
Icon(
icon,
color: isCompleted ? Colors.blue : Colors.grey,
),
const SizedBox(width: 10),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
title,
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: 14,
color: isCompleted ? Colors.black : Colors.grey,
),
),
const SizedBox(height: 4),
Text(
subtitle,
style: const TextStyle(fontSize: 12, color: Colors.black54),
),
],
),
),
],
),
),
)
],
),
),
),
);
}
Widget _priceRow(String label, String amount, {bool isBold = false}) {
return Padding(
padding: EdgeInsets.symmetric(vertical: 4),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(label,
style: fontTextStyle(
12,
Color(0xFF444444),
isBold ? FontWeight.w600 : FontWeight.w400)),
Text(amount,
style: fontTextStyle(
12,
Color(0xFF444444),
isBold ? FontWeight.w600 : FontWeight.w400)),
],
),
);
}
// ------------- FULL FILE ENDS HERE ---------------

@ -0,0 +1,651 @@
import 'package:flutter/material.dart';
import 'package:watermanagement/common/settings.dart';
import 'package:watermanagement/supplier/payment_example2.dart';
import 'package:watermanagement/supplier/payment_screen.dart';
import 'package:razorpay_flutter/razorpay_flutter.dart';
class OrderDetails extends StatefulWidget {
var supplierDetails;
OrderDetails({this.supplierDetails});
@override
State<OrderDetails> createState() => _OrderDetailsState();
}
class _OrderDetailsState extends State<OrderDetails> {
bool isExpanded = false;
double actualPrice = 0.0;
int quantity = 0;
double deliveryCharges = 0.0;
double subtotal = 0.0;
double cgst = 0.0;
double sgst = 0.0;
double total = 0.0;
double platformFee = 0.0;
double totalTaxes = 0.0;
double advance = 0.0;
double balance = 0.0;
late Razorpay _razorpay;
@override
void initState() {
super.initState();
actualPrice =
double.tryParse(widget.supplierDetails.quotedAmount ?? '0') ?? 0;
quantity = int.tryParse(widget.supplierDetails.quantity ?? '0') ?? 0;
deliveryCharges = double.tryParse(widget.supplierDetails.bookingCharges ?? '0') ?? 0;;
platformFee = 11;
subtotal = actualPrice * quantity;
cgst = subtotal * 0.09;
sgst = subtotal * 0.09;
totalTaxes = cgst + sgst;
total = subtotal + cgst + sgst + deliveryCharges + platformFee;
advance = deliveryCharges;
balance = total - advance;
_razorpay = Razorpay();
_razorpay.on(Razorpay.EVENT_PAYMENT_SUCCESS, (PaymentSuccessResponse response) {
_handleSuccess(response, widget.supplierDetails);
});
_razorpay.on(Razorpay.EVENT_PAYMENT_ERROR, _handleError);
_razorpay.on(Razorpay.EVENT_EXTERNAL_WALLET, _handleExternalWallet);
}
Widget _buildAddressRow(String label, String address, Color color) {
return Padding(
padding: const EdgeInsets.only(bottom: 8.0),
child: Row(
children: [
Image.asset(
'images/location_supplier_landing.png',
height: 24,
width: 24,
fit: BoxFit.contain,
color: Color(0XFF939495),
),
SizedBox(
width: MediaQuery.of(context).size.width * .012,
),
Text(
'$label ',
style: fontTextStyle(14, color, FontWeight.bold),
),
Expanded(
child: Text(
address,
overflow: TextOverflow.ellipsis,
style: fontTextStyle(12, Color(0XFF515253), FontWeight.w400),
),
)
],
),
);
}
Widget _itemRow(String title, String price, {bool bold = false}) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 4.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(title,
style: fontTextStyle(12, Color(0XFF646566), FontWeight.w400)),
Text(price,
style: fontTextStyle(12, Color(0XFF646566), FontWeight.w400)),
],
),
);
}
void _openCheckout(double amountInRupees) {
var options = {
'key': 'rzp_test_1VCCWqEXUHdINz', // Replace with your Razorpay Key
'amount': (amountInRupees * 100)
.toInt(), // Convert rupees to paise and ensure integer
'name': 'Test Payment',
'description': 'Water Order',
'prefill': {
'contact': '9876543210',
'email': 'test@example.com',
}
};
try {
_razorpay.open(options);
} catch (e) {
print('Error: $e');
}
}
/* void _handleSuccess(PaymentSuccessResponse response) {
_showAlert('Payment Successful', 'Payment ID: ${response.paymentId}');
}*/
void _handleSuccess(PaymentSuccessResponse response, dynamic object) async {
if (!mounted) return; // ensure widget is still active
var payload = <String, dynamic>{};
payload["supplierId"] = object.supplierId;
payload["action"] = 'accepted';
payload["paymentId"] = response.paymentId;
try {
bool status = await AppSettings.acceptOrderRequests(payload, object.dbId);
if (!mounted) return;
if (status) {
AppSettings.longSuccessToast('Accepted');
Navigator.pop(context, true);
} else {
AppSettings.longFailedToast('Failed to accept order request');
}
} catch (e) {
AppSettings.longFailedToast('Error: $e');
}
}
void _handleError(PaymentFailureResponse response) {
_showAlert('Payment Failed', '${response.code} - ${response.message}');
}
void _handleExternalWallet(ExternalWalletResponse response) {
_showAlert('Wallet Selected', '${response.walletName}');
}
void _showAlert(String title, String content) {
showDialog(
context: context,
builder: (_) => AlertDialog(
title: Text(title),
content: Text(content),
actions: [
TextButton(onPressed: () => Navigator.pop(context), child: Text('OK'))
],
),
);
}
@override
void dispose() {
_razorpay.clear();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Color(0XFFFFFFFF),
appBar: AppSettings.supplierAppBarWithoutActions(
widget.supplierDetails.supplierName, context),
body: SingleChildScrollView(
child: Padding(
padding: EdgeInsets.fromLTRB(12, 8, 12, 8),
child: Column(
children: [
_buildAddressRow(widget.supplierDetails.supplierName,
'| ' + widget.supplierDetails.address, Color(0XFF4692FD)),
Padding(
padding: const EdgeInsets.only(
left: 8.0, bottom: 4.0), // 👈 Shift right
child: Align(
alignment: Alignment.centerLeft,
child: Image.asset(
'images/Line.png',
height: 12,
width: 6,
fit: BoxFit.contain,
),
),
),
_buildAddressRow('Home', '| ' + AppSettings.userAddress,
Color(0XFF2D2E30)),
SizedBox(
height: MediaQuery.of(context).size.height * .012,
),
Card(
color: Color(0XFFFFFFFF),
child: Padding(
padding: EdgeInsets.all(4),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.start,
children: [
Container(
width: double
.infinity, // makes it expand within the Card's width
decoration: BoxDecoration(
borderRadius: BorderRadius.vertical(
bottom: Radius.circular(8),
top: Radius.circular(8),
), // match Card border
gradient: LinearGradient(
colors: [
Color(0xFFFFF8DF),
Color(0xFFFFFFFF),
],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
),
),
padding: EdgeInsets.symmetric(vertical: 12),
alignment: Alignment.center,
child: Padding(
padding: EdgeInsets.fromLTRB(12, 0, 12, 0),
child: Column(
crossAxisAlignment:
CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.start,
children: [
Row(
mainAxisAlignment:
MainAxisAlignment.spaceBetween,
crossAxisAlignment:
CrossAxisAlignment.start,
children: [
Text(
widget
.supplierDetails.supplierName,
style: fontTextStyle(
16,
Color(0XFF2D2E30),
FontWeight.w600)),
Text(
widget.supplierDetails
.distanceInMeters
.toString() +
' Km',
style: fontTextStyle(
12,
Color(0XFF2D2E30),
FontWeight.w600)),
],
),
Text(
widget.supplierDetails.displayAddress,
style: fontTextStyle(
12,
Color(0XFF515253),
FontWeight.w400)),
],
))),
SizedBox(
height: MediaQuery.of(context).size.height * .012,
),
Container(
width: double
.infinity, // makes it expand within the Card's width
decoration: BoxDecoration(
color: Color(0XFFF5F6F6), // Same as Card color
borderRadius:
BorderRadius.circular(4), // Rounded corners
border: Border.all(
color: Color(0XFFF5F6F6), // Border color
width: 0.5, // Border width
),
),
padding: EdgeInsets.symmetric(vertical: 4),
child: Padding(
padding: EdgeInsets.fromLTRB(12, 0, 12, 0),
child: Text('ITEMS',
style: fontTextStyle(10, Color(0XFF646566),
FontWeight.w400)),
)),
SizedBox(
height: MediaQuery.of(context).size.height * .012,
),
Padding(
padding: EdgeInsets.fromLTRB(4, 0, 4, 0),
child: Text(
widget.supplierDetails.capacity +
' - ' +
widget.supplierDetails.typeofwater,
style: fontTextStyle(
12, Color(0XFF2D2E30), FontWeight.w500),
),
),
SizedBox(
height: MediaQuery.of(context).size.height * .012,
),
Container(
width: double
.infinity, // makes it expand within the Card's width
decoration: BoxDecoration(
color: Color(0XFFF5F6F6), // Same as Card color
borderRadius:
BorderRadius.circular(4), // Rounded corners
border: Border.all(
color: Color(0XFFF5F6F6), // Border color
width: 0.5, // Border width
),
),
padding: EdgeInsets.symmetric(vertical: 4),
child: Padding(
padding: EdgeInsets.fromLTRB(12, 0, 12, 0),
child: Text('DELIVERY DETAILS',
style: fontTextStyle(10, Color(0XFF646566),
FontWeight.w400)),
)),
SizedBox(
height: MediaQuery.of(context).size.height * .012,
),
Padding(
padding: EdgeInsets.fromLTRB(4, 0, 4, 0),
child: Column(
children: [
Row(
mainAxisAlignment:
MainAxisAlignment.spaceBetween,
children: [
Text(
'Date',
style: fontTextStyle(12,
Color(0XFF515253), FontWeight.w500),
),
Text(
widget.supplierDetails.date,
style: fontTextStyle(12,
Color(0XFF232527), FontWeight.w500),
)
],
),
SizedBox(
height:
MediaQuery.of(context).size.height * .012,
),
Row(
mainAxisAlignment:
MainAxisAlignment.spaceBetween,
children: [
Text(
'Time',
style: fontTextStyle(12,
Color(0XFF515253), FontWeight.w500),
),
Text(
widget.supplierDetails.time,
style: fontTextStyle(12,
Color(0XFF232527), FontWeight.w500),
)
],
)
],
),
),
SizedBox(
height: MediaQuery.of(context).size.height * .012,
),
Align(
alignment: Alignment.center,
child: Container(
decoration: BoxDecoration(
color:
Color(0XFFFFFFFF), // Same as Card color
borderRadius: BorderRadius.circular(
4), // Rounded corners
border: Border.all(
color: Color(0XFF939495), // Border color
width: 0.5, // Border width
),
),
padding: EdgeInsets.symmetric(vertical: 4),
child: Padding(
padding: EdgeInsets.fromLTRB(12, 0, 12, 0),
child: Text('Change Delivery Details',
style: fontTextStyle(14,
Color(0XFF646566), FontWeight.w500)),
)),
),
SizedBox(
height: MediaQuery.of(context).size.height * .012,
),
],
),
),
),
SizedBox(
height: MediaQuery.of(context).size.height * .012,
),
Card(
color: Color(0XFFFFFFFF),
child: Padding(
padding: EdgeInsets.fromLTRB(12, 12, 12, 12),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.start,
children: [
Text('SAVINGS',
style: fontTextStyle(
10, Color(0XFF646566), FontWeight.w400)),
SizedBox(
height: MediaQuery.of(context).size.height * .012,
),
Row(
children: [
Image.asset(
'images/coupon.png',
height: 16,
width: 16,
fit: BoxFit.contain,
),
SizedBox(
width: MediaQuery.of(context).size.width *
.012),
Text(
'Apply Coupon',
style: fontTextStyle(
12,
Color(0XFF2D2E30),
FontWeight.w500,
),
),
Spacer(), // pushes the arrow to the right
Image.asset(
'images/arrow-right.png',
height: 20,
width: 20,
fit: BoxFit.contain,
),
],
),
SizedBox(
height: MediaQuery.of(context).size.height * .012,
),
],
),
)),
SizedBox(
height: MediaQuery.of(context).size.height * .012,
),
Column(
children: [
// Header card
GestureDetector(
onTap: () => setState(() => isExpanded = !isExpanded),
child: Card(
color: Color(0XFFFFFFFF),
child: Padding(
padding: EdgeInsets.fromLTRB(12, 12, 12, 12),
child: Row(
children: [
Image.asset(
'images/receipt.png',
height: 16,
width: 16,
fit: BoxFit.contain,
),
SizedBox(
width:
MediaQuery.of(context).size.width * .012,
),
Expanded(
child: Text(
'To Pay ₹${AppSettings.moneyConvertion(total.toString())}',
style: fontTextStyle(12,
Color(0XFF2E302D), FontWeight.w600)),
),
isExpanded
? Image.asset(
'images/arrow-up.png',
height: 20,
width: 20,
fit: BoxFit.contain,
)
: Image.asset(
'images/arrow-down.png',
height: 20,
width: 20,
fit: BoxFit.contain,
)
],
),
),
),
),
AnimatedContainer(
duration: Duration(milliseconds: 300),
curve: Curves.easeInOut,
height: isExpanded ? null : 0,
padding:
isExpanded ? EdgeInsets.all(0) : EdgeInsets.zero,
child: isExpanded
? Card(
color: Color(0XFFFFFFFF),
child: Padding(
padding: EdgeInsets.all(8),
child: Column(
crossAxisAlignment:
CrossAxisAlignment.start,
children: [
Text('ITEMS',
style: fontTextStyle(
14,
Color(0XFF2D2E30),
FontWeight.w500)),
SizedBox(
height:
MediaQuery.of(context).size.height *
.012,
),
_itemRow(
widget.supplierDetails.capacity +
' ' +
widget
.supplierDetails.typeofwater +
' x ' +
widget.supplierDetails.quantity,
'' +
AppSettings.moneyConvertion(
subtotal.toString())),
Divider(),
_itemRow('Item Total',
'${AppSettings.moneyConvertion(total.toString())}'),
_itemRow('Delivery Charges',
'${AppSettings.moneyConvertion(deliveryCharges.toString())}'),
_itemRow('Platform Fee', '${AppSettings.moneyConvertion(platformFee.toString())}'),
_itemRow('Taxes', '${AppSettings.moneyConvertion(totalTaxes.toString())}'),
Divider(),
_itemRow('Total Bill',
'${AppSettings.moneyConvertion(total.toString())}',
bold: true),
Divider(),
_itemRow('Advance', '${AppSettings.moneyConvertion(advance.toString())}'),
_itemRow('To pay(after delivery)',
'${AppSettings.moneyConvertion(balance.toString())}'),
],
),
),
)
: null,
),
],
)
],
)),
),
bottomNavigationBar: Container(
decoration: BoxDecoration(
color: Colors.white,
border: Border(
top: BorderSide(color: Color(0XFFC3C4C4), width: 0.8),
),
boxShadow: [
BoxShadow(
color: Color(0XFFC3C4C4),
blurRadius: 0,
offset: Offset(0, 0),
),
],
),
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
child: Row(
children: [
// Button 1: Pay Advance
Expanded(
child: ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: Color(0XFF0A9E04),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(24),
),
padding: EdgeInsets.symmetric(vertical: 12), // Only vertical padding
),
onPressed: () {
/*Navigator.push(
context,
MaterialPageRoute(
builder: (context) => RazorpayScreen()),
);*/
/*Navigator.push(
context,
MaterialPageRoute(
builder: (context) => PaymentOptionsPage(amount: advance),
),
);*/
_openCheckout(advance);
/*Navigator.push(
context,
MaterialPageRoute(
builder: (context) => UpiIntentLauncher(),
),
);*/
},
child: Text(
"Pay advance ₹${AppSettings.moneyConvertion(advance.toString())}",
style: fontTextStyle(14, Color(0XFFFFFFFF), FontWeight.w600),
textAlign: TextAlign.center,
),
),
),
SizedBox(
width: MediaQuery.of(context).size.width * .012,
),// spacing between buttons
// Button 2: Pay Full
Expanded(
child: ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: Color(0XFF1D7AFC),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(24),
),
padding: EdgeInsets.symmetric(vertical: 12),
),
onPressed: () {
// Handle full payment
},
child: Text(
"Pay full ₹${AppSettings.moneyConvertion(total.toStringAsFixed(2))}",
style: fontTextStyle(14, Color(0XFFFFFFFF), FontWeight.w600),
textAlign: TextAlign.center,
),
),
),
],
),
),
);
}
}

@ -0,0 +1,812 @@
import 'dart:convert';
import 'package:auto_size_text/auto_size_text.dart';
import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
import 'package:watermanagement/supplier/order_details.dart';
import '../common/settings.dart';
import 'supplier_orders_model.dart';
class OrderRequestsPage extends StatefulWidget {
const OrderRequestsPage({super.key});
@override
State<OrderRequestsPage> createState() => _OrderRequestsPageState();
}
class _OrderRequestsPageState extends State<OrderRequestsPage> {
bool isSupplierOrdersDataLoading = false;
List<SupplierOrdersModel> supplierOrdersList = [];
List<SupplierOrdersModel> acceptedOrders = [];
List<SupplierOrdersModel> acceptedByUserOrders = [];
List<SupplierOrdersModel> rejectedOrders = [];
void groupOrdersByStatus() {
acceptedOrders = supplierOrdersList
.where((order) => order.status == "accept") // adjust field name
.toList();
acceptedByUserOrders = supplierOrdersList
.where(
(order) => order.status == "accepted_by_user") // adjust field name
.toList();
rejectedOrders = supplierOrdersList
.where(
(order) => order.status == "rejected_by_user") // adjust field name
.toList();
}
@override
void initState() {
// TODO: implement initState
super.initState();
getAcceptedOrdersFromSupplier();
}
Future<void> getAcceptedOrdersFromSupplier() async {
isSupplierOrdersDataLoading = true;
var response1 = await AppSettings.getAcceptedOrdersDataFromSupplier();
var json = jsonDecode(response1);
setState(() {
supplierOrdersList = (json['data'] as List)
.map((model) => SupplierOrdersModel.fromJson(model))
.toList();
groupOrdersByStatus();
isSupplierOrdersDataLoading = false;
});
}
String getOrderTimeOnly(String orderTimeStr) {
if (orderTimeStr.isEmpty) return "";
try {
final format = DateFormat("dd-MM-yyyy HH:mm");
final orderTime = format.parse(orderTimeStr);
final now = DateTime.now();
final difference = now.difference(orderTime);
if (difference.inDays < 2) {
return "New";
} else if (difference.inDays < 10) {
final remaining = 10 - difference.inDays;
return "Expires in ${remaining}d";
} else {
return "Expired";
}
} catch (e) {
return "";
}
}
showRejectOrdersDialog(var object) async {
return showDialog(
context: context,
barrierDismissible: false,
builder: (BuildContext context) {
return StatefulBuilder(
builder: (BuildContext context, StateSetter setState) {
return AlertDialog(
backgroundColor: Color(0XFFFFFFFF),// Set your desired background color
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12), // Optional: Rounded corners
),
title: Center(
child: Text('Reject Order Request' ,style: fontTextStyle(16,Color(0XFF3B3B3B),FontWeight.w600),),
),
content: SingleChildScrollView(
child: ListBody(
children: <Widget>[
Container(
child: Text('Do u want to reject order request',style: fontTextStyle(14,Color(0XFF101214),FontWeight.w600),),
),
],
),
),
actions: <Widget>[
Center(
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Expanded(child: GestureDetector(
onTap: (){
Navigator.pop(context);
},
child: Container(
decoration: BoxDecoration(
shape: BoxShape.rectangle,
color: Color(0XFFFFFFFF),
border: Border.all(
width: 1,
color: Color(0XFF1D7AFC)),
borderRadius: BorderRadius.circular(
12,
)),
alignment: Alignment.center,
child: Visibility(
visible: true,
child: Padding(
padding: EdgeInsets.fromLTRB(16,12,16,12),
child: Text('Cancel', style: fontTextStyle(12, Color(0XFF1D7AFC), FontWeight.w600)),
),
),
),
),),
SizedBox(width:MediaQuery.of(context).size.width * .016,),
Expanded(child: GestureDetector(
onTap: ()async{
var payload = <String, dynamic>{};
payload["supplierId"] = object.supplierId;
payload["action"] = 'reject';
payload["paymentId"] = '';
try {
bool status = await AppSettings.acceptOrderRequests(payload, object.dbId);
if (!mounted) return;
if (status) {
AppSettings.longSuccessToast('Rejected');
Navigator.pop(context);
getAcceptedOrdersFromSupplier();
} else {
AppSettings.longFailedToast('Failed to reject order request');
}
} catch (e) {
AppSettings.longFailedToast('Error: $e');
}
},
child: Container(
decoration: BoxDecoration(
shape: BoxShape.rectangle,
color: Color(0XFFE2483D),
border: Border.all(
width: 1,
color: Color(0XFFE2483D)),
borderRadius: BorderRadius.circular(
12,
)),
alignment: Alignment.center,
child: Visibility(
visible: true,
child: Padding(
padding: EdgeInsets.fromLTRB(16,12,16,12),
child: Text(
'Reject',
style: fontTextStyle(
12,
Color(0XFFFFFFFF),
FontWeight.w600)),
),
),
)
),)
],
),
),
],
);
});
},
);
}
Widget orderCard(SupplierOrdersModel order) {
final orderDateTime = "${order.acceptedTime}".trim();
final statusTime = getOrderTimeOnly(orderDateTime);
// status badge color
Color statusColor = Color(0xFF1D7AFC);
if (statusTime == "Expired")
statusColor = Color(0xFF757575);
else if (statusTime == "New")
statusColor = Color(0XFF1D7AFC);
else if (statusTime.endsWith("m")) statusColor = Color(0XFFE56910);
return Padding(
padding: EdgeInsets.all(10),
child: Container(
decoration: BoxDecoration(
color: Color(0XFFFFFFFF), // Same as Card color
borderRadius: BorderRadius.circular(12), // Rounded corners
border: Border.all(
color: Color(0XFF9F9F9F), // Border color
width: 0.5, // Border width
),
),
//color: Color(0XFFFFFFFF),
child: Padding(
padding: EdgeInsets.all(16),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Visibility(
visible: order.supplierName != '',
child: Text(
order.supplierName,
style: fontTextStyle(
14, Color(0XFF343637), FontWeight.w600),
),
),
Visibility(
visible: statusTime.isNotEmpty,
child: Padding(
padding: const EdgeInsets.only(top: 4.0, bottom: 4.0),
child: Container(
padding:
EdgeInsets.symmetric(horizontal: 6, vertical: 2),
decoration: BoxDecoration(
color: statusColor,
borderRadius: BorderRadius.circular(4),
),
child: Text(
statusTime,
style: fontTextStyle(
10, Colors.white, FontWeight.w500),
),
),
),
),
],
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Visibility(
visible: order.displayAddress != '',
child: Text(
order.displayAddress,
style: fontTextStyle(
10, Color(0XFF646566), FontWeight.w400),
),
),
Visibility(
visible: order.date != '',
child: Text(
order.date + ' ' + order.time,
style: fontTextStyle(
9, Color(0XFF646566), FontWeight.w400),
),
),
],
),
SizedBox(
height: MediaQuery.of(context).size.height * .012,
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.start,
children: [
Container(
padding:
EdgeInsets.symmetric(horizontal: 4, vertical: 2),
decoration: BoxDecoration(
color: order.typeofwater.toString().toLowerCase() ==
'bore water'
? Color(0xFF8877DD)
: Color(0xFFCA86B0),
borderRadius: BorderRadius.circular(4),
border: Border.all(
width: 1,
color:
order.typeofwater.toString().toLowerCase() ==
'bore water'
? Color(0xFF8877DD)
: Color(0xFFCA86B0),
),
),
child: AutoSizeText(
capitalizeFirst(order.typeofwater),
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: fontTextStyle(
12, Color(0xFFFFFFFF), FontWeight.w400),
),
),
Row(
children: [
Text(
'' +
AppSettings.formDouble(order.quotedAmount) +
'/',
style: fontTextStyle(
20, Color(0XFF515253), FontWeight.w600),
),
Text(
order.capacity,
style: fontTextStyle(
12, Color(0XFF2D2E30), FontWeight.w600),
)
],
),
],
),
Row(
children: [
GestureDetector(
onTap: statusTime == "Expired"
? null
: () {
showRejectOrdersDialog(order);
},
child: ColorFiltered(
colorFilter: statusTime == "Expired"
? ColorFilter.mode(
Color(0XFF9F9F9F), BlendMode.srcIn)
: ColorFilter.mode(
Colors.transparent, BlendMode.multiply),
child: Image.asset(
'images/wrong_button.png',
width: 44,
height: 44,
),
),
),
SizedBox(
width: MediaQuery.of(context).size.width * .012,
),
GestureDetector(
onTap: statusTime == "Expired"
? null
: () {
//showAcceptOrdersDialog(order);
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => OrderDetails(
supplierDetails: order,
),
),
);
},
child: ColorFiltered(
colorFilter: statusTime == "Expired"
? ColorFilter.mode(
Color(0XFF9F9F9F), BlendMode.srcIn)
: ColorFilter.mode(
Colors.transparent, BlendMode.multiply),
child: Image.asset(
'images/right_button.png',
width: 44,
height: 44,
),
),
),
],
)
],
)
],
)),
),
);
}
Widget acceptedOrdersCard(SupplierOrdersModel order) {
final orderDateTime = "${order.acceptedTime}".trim();
final statusTime = getOrderTimeOnly(orderDateTime);
// status badge color
Color statusColor = Color(0xFF1D7AFC);
if (statusTime == "Expired")
statusColor = Color(0xFF757575);
else if (statusTime == "New")
statusColor = Color(0XFF1D7AFC);
else if (statusTime.endsWith("m")) statusColor = Color(0XFFE56910);
return Padding(
padding: EdgeInsets.all(10),
child: Container(
decoration: BoxDecoration(
color: Color(0XFFFFFFFF), // Same as Card color
borderRadius: BorderRadius.circular(12), // Rounded corners
border: Border.all(
color: Color(0XFF9F9F9F), // Border color
width: 0.5, // Border width
),
),
//color: Color(0XFFFFFFFF),
child: Padding(
padding: EdgeInsets.all(16),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Visibility(
visible: order.supplierName != '',
child: Text(
order.supplierName,
style: fontTextStyle(
14, Color(0XFF343637), FontWeight.w600),
),
),
Visibility(
visible: order.date != '',
child: Text(
order.date + ' ' + order.time,
style: fontTextStyle(
9, Color(0XFF646566), FontWeight.w400),
),
),
],
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Visibility(
visible: order.displayAddress != '',
child: Text(
order.displayAddress,
style: fontTextStyle(
10, Color(0XFF646566), FontWeight.w400),
),
),
Row(
children: [
Image.asset(
'images/paid.png',
fit: BoxFit.cover,
width:
16, // Match the diameter of the CircleAvatar
height: 16,
),
SizedBox(
width:
MediaQuery.of(context).size.width *
.004,
),
Text(capitalizeFirst('Paid'), style: fontTextStyle(12, Color(0XFF515253), FontWeight.w400)),
SizedBox(
width: MediaQuery.of(context).size.width * .012,
),
Text(
'' + AppSettings.formDouble(order.advancePaid),
style: fontTextStyle(
12,
Color(0XFF515253),
FontWeight.w400),
),
],
)
],
),
SizedBox(
height: MediaQuery.of(context).size.height * .004,
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Container(
padding: EdgeInsets.symmetric(horizontal: 4, vertical: 2),
decoration: BoxDecoration(
color: order.typeofwater.toString().toLowerCase() == 'bore water'
? Color(0xFF8877DD)
: Color(0xFFCA86B0),
borderRadius: BorderRadius.circular(4),
border: Border.all(
width: 1,
color: order.typeofwater.toString().toLowerCase() == 'bore water'
? Color(0xFF8877DD)
: Color(0xFFCA86B0),
),
),
child: AutoSizeText(
capitalizeFirst(order.typeofwater),
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: fontTextStyle(12, Color(0xFFFFFFFF), FontWeight.w400),
),
),
Row(
children: [
Image.asset(
'images/warning.png',
fit: BoxFit.cover,
width:
16, // Match the diameter of the CircleAvatar
height: 16,
),
SizedBox(
width:
MediaQuery.of(context).size.width *
.004,
),
Text(capitalizeFirst('Due'), style: fontTextStyle(12, Color(0XFF515253), FontWeight.w400)),
SizedBox(
width: MediaQuery.of(context).size.width * .012,
),
Text(
'' + AppSettings.formDouble(order.advancePaid),
style: fontTextStyle(
12,
Color(0XFF515253),
FontWeight.w400),
),
],
)
],
),
Row(
children: [
Text(
'' + AppSettings.formDouble(order.quotedAmount) + '/',
style: fontTextStyle(20, Color(0XFF515253), FontWeight.w600),
),
Text(
order.capacity,
style: fontTextStyle(12, Color(0XFF2D2E30), FontWeight.w600),
),
],
),
],
),
),
],
)
],
)),
),
);
}
Widget rejectedOrdersCard(SupplierOrdersModel order) {
final orderDateTime = "${order.acceptedTime}".trim();
final statusTime = getOrderTimeOnly(orderDateTime);
// status badge color
Color statusColor = Color(0xFF1D7AFC);
if (statusTime == "Expired")
statusColor = Color(0xFF757575);
else if (statusTime == "New")
statusColor = Color(0XFF1D7AFC);
else if (statusTime.endsWith("m")) statusColor = Color(0XFFE56910);
return Padding(
padding: EdgeInsets.all(10),
child: Container(
decoration: BoxDecoration(
color: Color(0XFFFFFFFF), // Same as Card color
borderRadius: BorderRadius.circular(12), // Rounded corners
border: Border.all(
color: Color(0XFF9F9F9F), // Border color
width: 0.5, // Border width
),
),
//color: Color(0XFFFFFFFF),
child: Padding(
padding: EdgeInsets.all(16),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Visibility(
visible: order.supplierName != '',
child: Text(
order.supplierName,
style: fontTextStyle(
14, Color(0XFF343637), FontWeight.w600),
),
),
Visibility(
visible: order.date != '',
child: Text(
order.date + ' ' + order.time,
style: fontTextStyle(
9, Color(0XFF646566), FontWeight.w400),
),
),
],
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Visibility(
visible: order.displayAddress != '',
child: Text(
order.displayAddress,
style: fontTextStyle(
10, Color(0XFF646566), FontWeight.w400),
),
),
],
),
SizedBox(
height: MediaQuery.of(context).size.height * .004,
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Container(
padding: EdgeInsets.symmetric(horizontal: 4, vertical: 2),
decoration: BoxDecoration(
color: order.typeofwater.toString().toLowerCase() == 'bore water'
? Color(0xFF8877DD)
: Color(0xFFCA86B0),
borderRadius: BorderRadius.circular(4),
border: Border.all(
width: 1,
color: order.typeofwater.toString().toLowerCase() == 'bore water'
? Color(0xFF8877DD)
: Color(0xFFCA86B0),
),
),
child: AutoSizeText(
capitalizeFirst(order.typeofwater),
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: fontTextStyle(12, Color(0xFFFFFFFF), FontWeight.w400),
),
),
],
),
Row(
children: [
Text(
'' + AppSettings.formDouble(order.quotedAmount) + '/',
style: fontTextStyle(20, Color(0XFF515253), FontWeight.w600),
),
Text(
order.capacity,
style: fontTextStyle(12, Color(0XFF2D2E30), FontWeight.w600),
),
],
),
],
),
),
],
)
],
)),
),
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Color(0XFFFFFFFF),
appBar:
AppSettings.supplierAppBarWithoutActions('Order Requests', context),
body: Center(
child: isSupplierOrdersDataLoading
? Center(
child: CircularProgressIndicator(
color: primaryColor,
strokeWidth: 5.0,
),
)
: supplierOrdersList.length != 0
? Column(
children: [
Expanded(
child: ListView(
children: [
// Accepted by User Orders
if (acceptedByUserOrders.isNotEmpty) ...[
Padding(
padding: EdgeInsets.all(10),
child: Text(
"ACCEPTED REQUESTS",
style: fontTextStyle(
10, Color(0XFF646566), FontWeight.w400),
),
),
...acceptedByUserOrders
.map((order) => acceptedOrdersCard(order))
.toList(),
],
// Group acceptedOrders
if (acceptedOrders.isNotEmpty) ...[
// Active / New Orders
if (acceptedOrders.any((o) =>
getOrderTimeOnly(o.acceptedTime) !=
"Expired")) ...[
Padding(
padding: EdgeInsets.all(10),
child: Text(
"NEW REQUESTS",
style: fontTextStyle(
10, Color(0XFF646566), FontWeight.w400),
),
),
...acceptedOrders
.where((o) =>
getOrderTimeOnly(o.acceptedTime) !=
"Expired")
.map((order) => orderCard(order))
.toList(),
],
// Expired Orders
if (acceptedOrders.any((o) =>
getOrderTimeOnly(o.acceptedTime) ==
"Expired")) ...[
Padding(
padding: EdgeInsets.all(10),
child: Text(
"EXPIRED REQUESTS",
style: fontTextStyle(
10, Color(0XFF646566), FontWeight.w400),
),
),
...acceptedOrders
.where((o) =>
getOrderTimeOnly(o.acceptedTime) ==
"Expired")
.map((order) => orderCard(order))
.toList(),
],
],
//rejected orders]
if (rejectedOrders.isNotEmpty) ...[
Padding(
padding: EdgeInsets.all(10),
child: Text(
"REJECTED REQUESTS",
style: fontTextStyle(
10, Color(0XFF646566), FontWeight.w400),
),
),
...rejectedOrders
.map((order) => rejectedOrdersCard(order))
.toList(),
],
],
),
),
SizedBox(
height: MediaQuery.of(context).size.height * .010,
),
],
)
: Center(
child: Text(
'No Data Available',
style:
fontTextStyle(12, Color(0XFF000000), FontWeight.w500),
),
),
),
);
}
}

@ -0,0 +1,101 @@
import 'package:flutter/material.dart';
import '../supplier/my_orders_model.dart';
import '../common/settings.dart';
class OrderStatusOverlayMulti extends StatelessWidget {
final bool isVisible;
final List<MyOrdersModel> activeOrders;
final void Function(MyOrdersModel order)? onTap;
const OrderStatusOverlayMulti({
super.key,
required this.isVisible,
required this.activeOrders,
this.onTap,
});
@override
Widget build(BuildContext context) {
if (!isVisible || activeOrders.isEmpty) return const SizedBox.shrink();
return Positioned(
bottom: 0,
left: 0,
right: 0,
child: SizedBox(
height: 67, // Enough height for full details
width: double.infinity,
child: PageView.builder(
itemCount: activeOrders.length,
controller: PageController(viewportFraction: 1.0),
itemBuilder: (context, index) {
final order = activeOrders[index];
return GestureDetector(
onTap: () => onTap?.call(order),
child: Container(
margin: const EdgeInsets.symmetric(horizontal: 4),
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: Color(0XFF0A9E04),
borderRadius: BorderRadius.circular(10),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
/// Supplier name
/* Text(
order.supplierName,
style: const TextStyle(
color: Colors.black,
fontSize: 14,
fontWeight: FontWeight.w700,
),
overflow: TextOverflow.ellipsis,
),
const SizedBox(height: 4),
*/
/* /// Address
Text(
order.displayAddress,
style: const TextStyle(
color: Colors.black54,
fontSize: 12,
),
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
const SizedBox(height: 8),*/
/// Row for type & price
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
order.typeofwater,
style: fontTextStyle(12,Color(0XFFFFFFFF),FontWeight.w600),
),
Text(
order.capacity,
style: fontTextStyle(12,Color(0XFFFFFFFF),FontWeight.w600),
),
],
),
const SizedBox(height: 6),
/// Optional date or status
if (order.price != '' && order.price.isNotEmpty)
Text(
"${order.price}",
style: fontTextStyle(12,Color(0XFFFFFFFF),FontWeight.w600),
),
],
),
),
);
},
),
),
);
}
}

@ -0,0 +1,673 @@
import 'dart:async';
import 'dart:convert';
import 'dart:math';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_polyline_points/flutter_polyline_points.dart';
import 'package:get/get.dart';
import 'package:google_maps_flutter/google_maps_flutter.dart';
import 'package:location/location.dart';
import 'package:http/http.dart' as http;
import '../common/settings.dart';
import '../maps/location_controller.dart';
import 'dart:ui' as ui;
import 'package:flutter_compass/flutter_compass.dart';
import 'order_arrived.dart';
class OrderTrackingPage extends StatefulWidget {
var orderDetails;
final double lat;
final double lng;
final double d_lat;
final double d_lng;
final String u_address;
OrderTrackingPage({
required this.lat,
required this.lng,
required this.d_lat,
required this.d_lng,
required this.u_address,
this.orderDetails
});
@override
State<OrderTrackingPage> createState() => _OrderTrackingPageState();
}
class _OrderTrackingPageState extends State<OrderTrackingPage> {
final Completer<GoogleMapController> _mapController = Completer();
final Location _location = Location();
late StreamSubscription<LocationData> _locationSubscription;
StreamSubscription<CompassEvent>? _compassSubscription;
LocationData? _currentLocation;
bool _showOrderSummary = false;
BitmapDescriptor? truckIcon;
BitmapDescriptor? destinationIcon;
Set<Marker> _markers = {};
Map<PolylineId, Polyline> _polylines = {};
List<LatLng> _routeCoords = [];
String _eta = '';
double _distance = 0.0;
double _truckRotation = 0.0;
final String _googleApiKey = 'AIzaSyDJpK9RVhlBejtJu9xSGfneuTN6HOfJgSM';
late LatLng userLatLng;
late LatLng driverLatLng;
// ----------------------- NEWLY ADDED -----------------------
bool _arrivalTriggered = false;
Timer? _arrivalTimer;
// ------------------------------------------------------------
Timer? _autoNavigateTimer;
@override
void initState() {
super.initState();
userLatLng = LatLng(widget.lat, widget.lng);
driverLatLng = LatLng(widget.d_lat, widget.d_lng);
_loadIcons().then((_) => _initLocationTracking());
_initCompass();
// 👉 Auto navigate after 20 seconds
_autoNavigateTimer = Timer(Duration(seconds: 5), () {
if (mounted) {
Navigator.pushReplacement(
context,
MaterialPageRoute(
builder: (context) => ArrivalScreen(
orderDetails: widget.orderDetails,
),
),
);
}
});
}
void _initCompass() {
_compassSubscription = FlutterCompass.events?.listen((event) {
if (!mounted) return; // 🛡 lifecycle guard
final heading = event.heading;
if (heading == null) return;
setState(() {
_truckRotation = heading;
});
});
}
@override
void dispose() {
try { _locationSubscription.cancel(); } catch (_) {}
_compassSubscription?.cancel();
_arrivalTimer?.cancel();
_autoNavigateTimer?.cancel();
super.dispose();
}
Future<void> _loadIcons() async {
truckIcon =
await getResizedMarker('images/tanker_map_horizontal.png', 90, null);
destinationIcon = await getResizedMarker(
'images/location_supplier_landing.png', 90, Color(0XFFE76960));
}
Future<void> _initLocationTracking() async {
bool serviceEnabled = await _location.serviceEnabled();
if (!serviceEnabled) serviceEnabled = await _location.requestService();
if (!serviceEnabled) return;
PermissionStatus permissionGranted = await _location.hasPermission();
if (permissionGranted == PermissionStatus.denied) {
permissionGranted = await _location.requestPermission();
if (permissionGranted != PermissionStatus.granted) return;
}
_currentLocation = await _location.getLocation();
_updateRouteAndMarkers();
_locationSubscription = _location.onLocationChanged.listen((newLoc) {
if (!mounted) return; // 🛡 REQUIRED
_currentLocation = newLoc;
_updateRouteAndMarkers();
});
}
Future<BitmapDescriptor> getResizedMarker(
String imagePath, int width, Color? tintColor) async {
final ByteData data = await rootBundle.load(imagePath);
final Uint8List imageData = data.buffer.asUint8List();
final ui.Codec codec = await ui.instantiateImageCodec(
imageData,
targetWidth: width,
);
final ui.FrameInfo fi = await codec.getNextFrame();
final ui.PictureRecorder recorder = ui.PictureRecorder();
final Canvas canvas = Canvas(recorder);
final Paint paint = Paint();
if (tintColor != null) {
paint.colorFilter = ui.ColorFilter.mode(tintColor, BlendMode.srcIn);
}
canvas.drawImage(fi.image, Offset.zero, paint);
final ui.Image finalImage =
await recorder.endRecording().toImage(fi.image.width, fi.image.height);
final ByteData? byteData =
await finalImage.toByteData(format: ui.ImageByteFormat.png);
return BitmapDescriptor.fromBytes(byteData!.buffer.asUint8List());
}
// ----------------------- NEWLY ADDED METHOD -----------------------
void _checkDriverArrival() {
if (_arrivalTriggered) return;
double dist = _calculateDistance(
driverLatLng.latitude,
driverLatLng.longitude,
userLatLng.latitude,
userLatLng.longitude,
);
if (dist <= 0.05) {
_arrivalTriggered = true;
_arrivalTimer = Timer(Duration(minutes: 1), () {
if (mounted) {
Navigator.pushReplacement(
context,
MaterialPageRoute(builder: (context) => ArrivalScreen(orderDetails: widget.orderDetails,)),
);
}
});
}
}
// ------------------------------------------------------------
Future<void> _updateRouteAndMarkers() async {
if (_currentLocation == null) return;
await _fetchPolyline(driverLatLng, userLatLng);
await _fetchETA(driverLatLng, userLatLng);
setState(() {
_markers = {
Marker(
markerId: MarkerId('truck'),
position: driverLatLng,
icon: truckIcon ?? BitmapDescriptor.defaultMarker,
rotation: _truckRotation,
anchor: Offset(0.5, 0.5),
flat: true,
),
Marker(
markerId: MarkerId('destination'),
position: userLatLng,
icon: destinationIcon ?? BitmapDescriptor.defaultMarker,
infoWindow: InfoWindow(title: widget.u_address),
),
};
});
final controller = await _mapController.future;
LatLngBounds bounds = LatLngBounds(
southwest: LatLng(
min(driverLatLng.latitude, userLatLng.latitude),
min(driverLatLng.longitude, userLatLng.longitude),
),
northeast: LatLng(
max(driverLatLng.latitude, userLatLng.latitude),
max(driverLatLng.longitude, userLatLng.longitude),
),
);
controller.animateCamera(CameraUpdate.newLatLngBounds(bounds, 80));
// ----------------------- NEWLY ADDED -----------------------
_checkDriverArrival();
// ------------------------------------------------------------
}
Future<void> _fetchPolyline(LatLng from, LatLng to) async {
PolylinePoints polylinePoints = PolylinePoints();
PolylineResult result = await polylinePoints.getRouteBetweenCoordinates(
_googleApiKey,
PointLatLng(from.latitude, from.longitude),
PointLatLng(to.latitude, to.longitude),
travelMode: TravelMode.driving,
);
if (result.points.isNotEmpty) {
_routeCoords =
result.points.map((p) => LatLng(p.latitude, p.longitude)).toList();
double totalDistance = 0;
for (int i = 0; i < _routeCoords.length - 1; i++) {
totalDistance += _calculateDistance(
_routeCoords[i].latitude,
_routeCoords[i].longitude,
_routeCoords[i + 1].latitude,
_routeCoords[i + 1].longitude,
);
}
_distance = totalDistance;
setState(() {
_polylines = {
PolylineId('route'): Polyline(
polylineId: PolylineId('route'),
points: _routeCoords,
color: primaryColor,
width: 4,
patterns: [
PatternItem.dash(20),
PatternItem.gap(0),
],
jointType: JointType.round,
)
};
});
}
}
Future<void> _fetchETA(LatLng from, LatLng to) async {
final url = Uri.parse(
'https://maps.googleapis.com/maps/api/directions/json?origin=${from.latitude},${from.longitude}&destination=${to.latitude},${to.longitude}&key=$_googleApiKey',
);
final response = await http.get(url);
if (response.statusCode == 200) {
final data = json.decode(response.body);
String duration = data['routes'][0]['legs'][0]['duration']['text'];
setState(() {
_eta = duration;
});
}
}
double _calculateDistance(lat1, lon1, lat2, lon2) {
var p = 0.017453292519943295;
var a = 0.5 -
cos((lat2 - lat1) * p) / 2 +
cos(lat1 * p) * cos(lat2 * p) *
(1 - cos((lon2 - lon1) * p)) / 2;
return 12742 * asin(sqrt(a));
}
Widget _priceRow(String label, String amount, {bool isBold = false}) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 4.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
label,
style: fontTextStyle(
12,
Color(0xFF444444),
isBold ? FontWeight.w600 : FontWeight.w400,
),
),
Text(
amount,
style: fontTextStyle(
12,
Color(0xFF444444),
isBold ? FontWeight.w600 : FontWeight.w400,
),
),
],
),
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppSettings.supplierAppBarWithoutActions(
'Order#${widget.orderDetails.bookingid}', context),
body: Stack(
children: [
Positioned(
top: 0,
left: 0,
right: 0,
height: MediaQuery.of(context).size.height * 0.6,
child: GoogleMap(
initialCameraPosition: CameraPosition(
target: driverLatLng,
zoom: 14,
),
myLocationEnabled: true,
zoomControlsEnabled: false,
markers: _markers,
polylines: Set<Polyline>.of(_polylines.values),
onMapCreated: (controller) =>
_mapController.complete(controller),
),
),
DraggableScrollableSheet(
initialChildSize: 0.38,
minChildSize: 0.38,
maxChildSize: 0.70,
builder: (context, scrollController) {
return Container(
padding: EdgeInsets.only(left: 16, right: 16),
decoration: BoxDecoration(
color: Colors.white,
borderRadius:
BorderRadius.vertical(top: Radius.circular(16)),
),
child: ListView(
controller: scrollController,
padding: EdgeInsets.only(top: 10),
children: [
Center(
child: Container(
width: 53,
height: 2,
margin: EdgeInsets.only(bottom: 12),
decoration: BoxDecoration(
color: Color(0XFF757575),
borderRadius: BorderRadius.circular(10),
),
),
),
Center(
child: Column(
children: [
Text(
"Arriving in $_eta",
style: fontTextStyle(
16,
Color(0xFF232527),
FontWeight.w800,
),
),
SizedBox(
height: MediaQuery.of(context).size.height * .004,
),
RichText(
textAlign: TextAlign.center,
text: TextSpan(
children: [
TextSpan(
text: "HOME",
style: fontTextStyle(
11, Color(0xFF343637), FontWeight.w500),
),
TextSpan(
text: " - ${AppSettings.userAddress}",
style: fontTextStyle(
11, Color(0xFF343637), FontWeight.w400),
),
],
),
),
],
),
),
SizedBox(
height: MediaQuery.of(context).size.height * .012,
),
Divider(color: Color(0xFFC3C4C4), thickness: 1),
SizedBox(
height: MediaQuery.of(context).size.height * .012,
),
Row(
children: [
CircleAvatar(
radius: 20,
backgroundColor: Color(0XFFE8F2FF),
child: Image.asset(
'images/profile_user.png',
fit: BoxFit.cover,
width: 50,
height: 50,
),
),
SizedBox(
width: MediaQuery.of(context).size.width * .012,
),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"Prashanth",
style: fontTextStyle(
12,
Color(0xFF343637),
FontWeight.w500,
),
),
SizedBox(
height: MediaQuery.of(context).size.height * .004,
),
Text(
"TS J8 8905",
style: fontTextStyle(
12,
Color(0xFF646566),
FontWeight.w500,
),
),
],
),
Spacer(),
Padding(
padding: EdgeInsets.fromLTRB(8, 0, 8, 0),
child: Row(
children: [
Image.asset(
'images/message.png',
width: 24,
height: 24,
),
SizedBox(
width:
MediaQuery.of(context).size.width * .020,
),
Image.asset(
'images/phone.png',
width: 24,
height: 24,
),
],
),
)
],
),
SizedBox(
height: MediaQuery.of(context).size.height * .024,
),
InkWell(
onTap: () {
setState(() {
_showOrderSummary = !_showOrderSummary;
});
},
child: Row(
children: [
Text(
"View Order Summary",
style: fontTextStyle(
12,
Color(0xFF444444),
FontWeight.w500,
),
),
Spacer(),
Image.asset(
_showOrderSummary
? 'images/arrow-up.png'
: 'images/arrow-down.png',
width: 16,
height: 16,
),
],
),
),
SizedBox(
height: MediaQuery.of(context).size.height * .010,
),
AnimatedContainer(
duration: Duration(milliseconds: 300),
curve: Curves.easeInOut,
height: _showOrderSummary ? null : 0,
padding: _showOrderSummary
? EdgeInsets.all(0)
: EdgeInsets.zero,
child: _showOrderSummary
? Card(
color: Color(0XFFFFFFFF),
child: Padding(
padding: EdgeInsets.all(16),
child: Column(
crossAxisAlignment:
CrossAxisAlignment.start,
children: [
Text(
"ITEMS",
style: fontTextStyle(
12,
Color(0xFF444444),
FontWeight.w700,
),
),
SizedBox(height: 8),
Row(
mainAxisAlignment:
MainAxisAlignment.spaceBetween,
children: [
Text(
"10,000 L Drinking water x 1",
style: fontTextStyle(
12,
Color(0xFF515253),
FontWeight.w400,
),
),
Text(
"₹2,500",
style: fontTextStyle(
12,
Color(0xFF515253),
FontWeight.w400,
),
),
],
),
Divider(
thickness: 1,
color: Color(0xFFC3C4C4)),
_priceRow(
"Item Total", "₹2,346.00"),
_priceRow(
"Delivery Charges", "₹150.00"),
_priceRow(
"Platform Fee", "₹6.00"),
_priceRow("Taxes", "₹12.49"),
Divider(
thickness: 1,
color: Color(0xFFC3C4C4)),
_priceRow("Total Bill", "₹2,514",
isBold: true),
Divider(
thickness: 1,
color: Color(0xFFC3C4C4)),
SizedBox(height: 12),
Row(
mainAxisAlignment:
MainAxisAlignment.spaceBetween,
children: [
Text(
"Mode of Payment",
style: fontTextStyle(
12,
Color(0xFF444444),
FontWeight.w500,
),
),
Row(
children: [
Image.asset(
'images/success-toast.png',
width: 12,
height: 12,
),
SizedBox(width: 4),
Text(
"Cash on delivery",
style: fontTextStyle(
12,
Color(0xFF444444),
FontWeight.w500,
),
),
],
)
],
),
],
),
),
)
: null,
),
SizedBox(
height:
MediaQuery.of(context).size.height * .012,
),
Padding(
padding:
EdgeInsets.fromLTRB(0, 0, 0, 12),
child: Container(
width: double.infinity,
height: 80,
decoration: BoxDecoration(
color: Color(0xFFE8F2FF),
borderRadius: BorderRadius.circular(12),
),
),
),
],
),
);
},
),
],
),
);
}
Widget _itemRow(String title, String price,
{bool bold = false}) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 4.0),
child: Row(
mainAxisAlignment:
MainAxisAlignment.spaceBetween,
children: [
Text(title,
style: fontTextStyle(
12, Color(0XFF646566), FontWeight.w400)),
Text(price,
style: fontTextStyle(
12, Color(0XFF646566), FontWeight.w400)),
],
),
);
}
}

@ -0,0 +1,92 @@
import 'package:flutter/material.dart';
import 'package:geolocator/geolocator.dart';
import 'package:intl/intl.dart';
import '../common/settings.dart';
class PlanRequestModel {
String typeofwater='';
String supplierName = '';
String date = '';
String time = '';
String address='';
String displayAddress = '';
String capacity = '';
String quantity = '';
String quotedAmount='';
String status='';
double lat=0;
double lng=0;
double distanceInMeters=0;
Color cardColor=Colors.white;
String dbId = '';
String supplierId = '';
String acceptedTime = '';
String startDate = '';
String endDate = '';
String biddingPrice = '';
String actualPrice='';
String paymentType='';
String advanceAmount='';
List dates=[];
String frequency='';
PlanRequestModel();
factory PlanRequestModel.fromJson(Map<String, dynamic> json){
PlanRequestModel rtvm = new PlanRequestModel();
rtvm.status = json['status'] ?? '';
rtvm.typeofwater = json['type_of_water'] ?? '';
rtvm.date = json['date'] ?? '';
rtvm.time = json['time'] ?? '';
rtvm.startDate=json['start_date']??'';
rtvm.endDate=json['end_date']??'';
rtvm.capacity = json['capacity'] ?? '';
rtvm.quantity = json['quantity'] ?? '';
rtvm.biddingPrice = json['bidding_price'].toString() ?? '';
rtvm.actualPrice = json['actual_price'].toString() ?? '';
rtvm.paymentType = json['payment_type'].toString() ?? '';
rtvm.advanceAmount = json['adavnce_amount'].toString() ?? '';
rtvm.dates = json['dates']?? [];
rtvm.dbId=json['_id']?? '';
rtvm.frequency= json["weekly_count"].toString()??'';
final suppliers = json['requested_suppliers'] as List?;
if (suppliers != null && suppliers.isNotEmpty) {
final supplier = suppliers[0];
rtvm.quotedAmount = supplier['quoted_amount']?.toString() ?? '0.00';
rtvm.supplierName = supplier['supplier']?['suppliername'] ?? '';
rtvm.supplierId = supplier['supplierId'] ?? '';
rtvm.address = supplier['supplier']['profile']['office_address'] ?? '';
rtvm.lat =supplier['supplier']?['latitude'] ?? '';
rtvm.lng = supplier['supplier']?['longitude'] ?? '';
rtvm.acceptedTime = supplier['time'] ?? '';
} else {
rtvm.quotedAmount = '';
rtvm.supplierName = '';
rtvm.address = '';
rtvm.lat =0.0;
rtvm.lng =0.0;
}
List<String> parts = rtvm.address.split(',');
if (parts.length > 4) {
rtvm.displayAddress = parts[2].trim();
} else {
rtvm.displayAddress = rtvm.address; // fallback
}
rtvm.distanceInMeters = double.parse((Geolocator.distanceBetween(
rtvm.lat,
rtvm.lng,
AppSettings.userLatitude,
AppSettings.userLongitude
) / 1000).toStringAsFixed(2));
return rtvm;
}
}

@ -0,0 +1,39 @@
import 'package:flutter/material.dart';
import 'package:url_launcher/url_launcher.dart';
void main() {
runApp(MaterialApp(home: UpiIntentExample()));
}
class UpiIntentExample extends StatelessWidget {
final String upiId = "sneha.reddymalla@ybl"; // Replace with your UPI ID
final String name = "Sneha Reddy";
final String note = "Sample";
final String amount = "1.00";
void launchUPIApp(BuildContext context) async {
final uri = Uri.parse(
"upi://pay?pa=$upiId&pn=$name&tn=$note&am=$amount&cu=INR");
if (await canLaunchUrl(uri)) {
await launchUrl(uri, mode: LaunchMode.externalApplication);
} else {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text("No UPI app found to handle this request")),
);
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("UPI Payment via Intent")),
body: Center(
child: ElevatedButton(
onPressed: () => launchUPIApp(context),
child: Text("Pay with UPI App"),
),
),
);
}
}

@ -0,0 +1,155 @@
import 'package:flutter/material.dart';
import 'package:razorpay_flutter/razorpay_flutter.dart';
import 'package:upi_india/upi_india.dart';
class PaymentOptionsPage extends StatefulWidget {
final double amount;
const PaymentOptionsPage({required this.amount});
@override
State<PaymentOptionsPage> createState() => _PaymentOptionsPageState();
}
class _PaymentOptionsPageState extends State<PaymentOptionsPage> {
late Razorpay _razorpay;
late UpiIndia _upiIndia;
List<UpiApp> upiApps = [];
bool isFetching = true;
@override
void initState() {
super.initState();
_razorpay = Razorpay();
_razorpay.on(Razorpay.EVENT_PAYMENT_SUCCESS, _handleRazorpaySuccess);
_razorpay.on(Razorpay.EVENT_PAYMENT_ERROR, _handleRazorpayError);
_razorpay.on(Razorpay.EVENT_EXTERNAL_WALLET, _handleExternalWallet);
_upiIndia = UpiIndia();
fetchUpiApps();
}
void fetchUpiApps() async {
try {
upiApps = await _upiIndia.getAllUpiApps(mandatoryTransactionId: false);
} catch (e) {
upiApps = [];
}
setState(() {
isFetching = false;
});
}
void _openRazorpay() {
var options = {
'key': 'rzp_test_1DP5mmOlF5G5ag', // Replace with your Razorpay key
'amount': (widget.amount * 100).toInt(), // In paise
'name': 'Water Supplier',
'description': 'Advance Payment',
'prefill': {
'contact': '9876543210',
'email': 'test@example.com',
},
};
try {
_razorpay.open(options);
} catch (e) {
print('Error: $e');
}
}
void _startUpiTransaction(UpiApp app) async {
UpiResponse response = await _upiIndia.startTransaction(
app: app,
receiverUpiId: 'yourupi@okaxis', // Replace with your real UPI ID
receiverName: 'Water Supplier',
transactionRefId: 'TXN${DateTime.now().millisecondsSinceEpoch}',
transactionNote: 'Advance Payment',
amount: widget.amount,
);
_handleUpiResponse(response);
}
void _handleUpiResponse(UpiResponse res) {
String status = res.status?.toLowerCase() ?? "unknown";
if (status == "success") {
Navigator.pop(context);
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text("UPI Payment Successful")));
} else if (status == "failure") {
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text("UPI Payment Failed")));
} else {
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text("UPI Payment Cancelled")));
}
}
void _handleRazorpaySuccess(PaymentSuccessResponse response) {
Navigator.pop(context);
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text("Razorpay Payment Success")));
}
void _handleRazorpayError(PaymentFailureResponse response) {
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text("Razorpay Payment Failed")));
}
void _handleExternalWallet(ExternalWalletResponse response) {
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text("Wallet: ${response.walletName}")));
}
@override
void dispose() {
_razorpay.clear();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("Choose Payment Option")),
body: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
children: [
// Razorpay Button
ElevatedButton.icon(
onPressed: _openRazorpay,
icon: Icon(Icons.credit_card),
label: Text("Pay with Razorpay (Cards, UPI, Wallets)"),
style: ElevatedButton.styleFrom(
backgroundColor: Color(0xFF1D7AFC),
padding: EdgeInsets.symmetric(vertical: 14),
textStyle: TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
),
),
SizedBox(height: 24),
Align(
alignment: Alignment.centerLeft,
child: Text(
"Or choose a UPI App:",
style: TextStyle(fontSize: 14, fontWeight: FontWeight.w600),
),
),
SizedBox(height: 12),
Expanded(
child: isFetching
? Center(child: CircularProgressIndicator())
: upiApps.isEmpty
? Center(child: Text("No UPI apps found"))
: ListView.builder(
itemCount: upiApps.length,
itemBuilder: (context, index) {
final app = upiApps[index];
return ListTile(
leading: Image.memory(app.icon, height: 40, width: 40),
title: Text(app.name),
onTap: () => _startUpiTransaction(app),
);
},
),
),
],
),
),
);
}
}

@ -0,0 +1,34 @@
import 'package:flutter/material.dart';
import 'package:url_launcher/url_launcher.dart';
class UpiIntentLauncher extends StatelessWidget {
const UpiIntentLauncher({super.key});
Future<void> pay() async {
const upiId = '9000950877@ybl'; // supplier UPI
const name = 'Sneha Supplier';
const amount = '1.00';
const note = 'Test Payment';
final uri = Uri.parse(
'upi://pay?pa=$upiId&pn=$name&am=$amount&cu=INR&tn=$note',
);
if (!await launchUrl(uri, mode: LaunchMode.externalApplication)) {
throw Exception('Could not launch UPI intent');
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Pay with UPI')),
body: Center(
child: ElevatedButton(
onPressed: pay,
child: const Text('Pay ₹1.00'),
),
),
);
}
}

@ -0,0 +1,326 @@
import 'dart:convert';
import 'package:dotted_border/dotted_border.dart';
import 'package:flutter/material.dart';
import 'package:razorpay_flutter/razorpay_flutter.dart';
import 'package:watermanagement/common/settings.dart';
import 'package:watermanagement/supplier/paymnets/credit_accounts_details.dart';
class CreditAccountsScreen extends StatefulWidget {
const CreditAccountsScreen({super.key});
@override
State<CreditAccountsScreen> createState() => _CreditAccountsScreenState();
}
class _CreditAccountsScreenState extends State<CreditAccountsScreen> {
bool isLoading = true;
List<Supplier> suppliers = [];
Razorpay? _razorpay;
Supplier? _currentPaySupplier;
@override
void initState() {
super.initState();
fetchCreditAccounts();
_razorpay = Razorpay();
_razorpay?.on(
Razorpay.EVENT_PAYMENT_SUCCESS, _handlePaymentSuccess);
_razorpay?.on(
Razorpay.EVENT_PAYMENT_ERROR, _handlePaymentError);
_razorpay?.on(
Razorpay.EVENT_EXTERNAL_WALLET, _handleExternalWallet);
}
@override
void dispose() {
_razorpay?.clear();
_razorpay = null;
super.dispose();
}
// ================= FETCH CREDIT ACCOUNTS =================
Future<void> fetchCreditAccounts() async {
try {
final response =
await AppSettings.getAdvanceTransactionsByCustomer();
final decoded = jsonDecode(response);
final List data = decoded["data"] ?? [];
final List<Supplier> list = data.map<Supplier>((e) {
final status = (e["status"] ?? "").toString().toLowerCase();
final amount =
double.tryParse(e["advance_amount"].toString()) ?? 0;
double balance = 0;
if (status == "approved") balance = amount;
if (status == "pending") {
balance = -amount;
}
if (status == "completed") {
balance = amount;
}
if (status == "paid_by_customer") balance = amount;
return Supplier(
name: e["supplierName"] ?? "Supplier",
supplierId:e["supplierId"] ?? "***",
monthlyAmount: amount,
balance: balance,
status: status,
raw: e,
);
}).toList();
setState(() {
suppliers = list;
isLoading = false;
});
} catch (e) {
setState(() => isLoading = false);
}
}
// ================= RAZORPAY =================
void _openRazorpay(Supplier supplier) {
_currentPaySupplier = supplier;
var options = {
'key': 'rzp_test_1VCCWqEXUHdINz', // 🔑 replace in prod
'amount': (supplier.monthlyAmount * 100).toInt(),
'name': 'Advance Payment',
'description': 'Water Credit Top Up',
/*'prefill': {
'contact': AppSettings.userMobile,
'email': AppSettings.userEmail,
}*/
};
try {
_razorpay?.open(options);
} catch (e) {
debugPrint("Razorpay error: $e");
}
}
Future<void> _handlePaymentSuccess(
PaymentSuccessResponse response) async {
if (_currentPaySupplier == null) return;
AppSettings.preLoaderDialog(context);
final success = await AppSettings.payAdvance(
transactionId:
_currentPaySupplier!.raw["transactionId"],
payload: {
"advance_amount": _currentPaySupplier!.monthlyAmount,
"payment_type": "razorpay",
"ref_number": response.paymentId,
},
);
Navigator.of(context, rootNavigator: true).pop();
if (success) {
AppSettings.longSuccessToast("Payment successful");
fetchCreditAccounts();
} else {
AppSettings.longFailedToast("Payment failed");
}
_currentPaySupplier = null;
}
void _handlePaymentError(
PaymentFailureResponse response) {
AppSettings.longFailedToast("Payment failed");
}
void _handleExternalWallet(
ExternalWalletResponse response) {}
// ================= UI =================
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: const Color(0XFFFFFFFF),
appBar: AppSettings.supplierAppBarWithoutActions(
"Credit Accounts", context),
body: Padding(
padding: const EdgeInsets.all(16),
child: isLoading
? const Center(child: CircularProgressIndicator())
: ListView(
children: [
_addSupplierCard(),
const SizedBox(height: 20),
...suppliers.map(_supplierCard).toList(),
],
),
),
);
}
Widget _addSupplierCard() {
return InkWell(
onTap: () {},
child: Row(
children: [
DottedBorder(
borderType: BorderType.Circle,
dashPattern: const [6, 3],
color: const Color(0XFF444444),
strokeWidth: 1,
child: Container(
width: 48,
height: 48,
alignment: Alignment.center,
child: Image.asset(
'images/plus.png',
width: 24,
height: 24,
color: const Color(0XFF444444),
),
),
),
const SizedBox(width: 12),
Text(
"Add Supplier",
style: fontTextStyle(
12, const Color(0XFF232527), FontWeight.w600),
),
],
),
);
}
Widget _supplierCard(Supplier supplier) {
return GestureDetector(
onTap: () {
if (supplier.status == "pending" ||
supplier.status == "paid_by_customer") {
return; // 🚫 no navigation
}
Navigator.push(
context,
MaterialPageRoute(
builder: (_) =>
CreditAccountsDetails(details: supplier),
),
);
},
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 10),
child: Row(
children: [
Container(
width: 56,
height: 56,
decoration: BoxDecoration(
color: const Color(0xFFE8F2FF),
borderRadius: BorderRadius.circular(28),
),
child: ClipRRect(
borderRadius: BorderRadius.circular(28),
child: Image.asset("images/profile_user.png"),
),
),
const SizedBox(width: 12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
supplier.supplierId,
style: fontTextStyle(
12, const Color(0XFF232527), FontWeight.w600),
),
/* RichText(
text: TextSpan(children: [
TextSpan(
text:
"${supplier.monthlyAmount.toStringAsFixed(0)} ",
style: fontTextStyle(
10,
const Color(0xFF515253),
FontWeight.w400),
),
TextSpan(
text: "/month",
style: fontTextStyle(
10,
const Color(0xFF939495),
FontWeight.w400),
),
]),
),*/
// ===== STATUS MESSAGES =====
if (supplier.status == "pending")
Padding(
padding: const EdgeInsets.only(top: 4),
child: InkWell(
onTap: () => _openRazorpay(supplier),
child: Text(
"Please top up",
style: fontTextStyle(
10,
const Color(0XFFE2483D),
FontWeight.w600),
),
),
),
if (supplier.status == "paid_by_customer")
Padding(
padding: const EdgeInsets.only(top: 4),
child: Text(
"Confirmation needed from supplier",
style: fontTextStyle(
10,
const Color(0XFF939495),
FontWeight.w600),
),
),
],
),
),
Text(
"${AppSettings.formDouble(supplier.balance.toStringAsFixed(2))}",
style: fontTextStyle(
16,
supplier.balance < 0
? const Color(0XFFE2483D)
: const Color(0XFF0A9E04),
FontWeight.w600,
),
),
],
),
),
);
}
}
// ================= MODEL =================
class Supplier {
final String name;
final double monthlyAmount;
final double balance;
final String status;
final String supplierId;
final Map<String, dynamic> raw;
Supplier({
required this.name,
required this.monthlyAmount,
required this.balance,
required this.status,
required this.supplierId,
required this.raw,
});
}

@ -0,0 +1,311 @@
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
import 'package:watermanagement/common/settings.dart';
import 'package:watermanagement/supplier/paymnets/top_up_page.dart';
class CreditAccountsDetails extends StatefulWidget {
var details;
CreditAccountsDetails({this.details});
@override
State<CreditAccountsDetails> createState() => _CreditAccountsDetailsState();
}
class _CreditAccountsDetailsState extends State<CreditAccountsDetails> {
List<Map<String, dynamic>> transactions = [];
bool isLoading = true;
@override
void initState() {
fetchTransactions();
super.initState();
}
Future<void> fetchTransactions() async {
try {
final supplierId = widget.details.supplierId;
final customerId = AppSettings.customerId;
final response = await AppSettings.getAdvanceTransactionsBySupplierAndCustomer(
supplierId,
customerId,
);
final decoded = jsonDecode(response);
final List data = decoded["data"] ?? [];
setState(() {
transactions = data.map<Map<String, dynamic>>((e) {
final amount = (e["advance_amount"] ?? 0).toDouble();
final status = (e["status"] ?? "").toString().toLowerCase();
return {
"title": status == "completed" ? "Advance Received" : "Water Delivery",
"subtitle": e["payment_type"] ?? "",
"amount": status == "completed" ? amount : -amount,
"date": DateFormat("dd MMM, yyyy").format(
DateTime.parse(e["date_of_transaction"]),
),
};
}).toList();
isLoading = false;
});
} catch (e) {
setState(() => isLoading = false);
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: const Color(0XFFFFFFFF),
appBar: AppSettings.supplierAppBarWithoutActions(widget.details.name, context),
body: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Header
Container(
width: double.infinity,
padding: const EdgeInsets.all(20),
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [
widget.details.balance < 0 ? const Color(0XFFFADEDE) : const Color(0XFFC3DEC2),
const Color(0XFFFFFFFF)
],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
widget.details.name,
style: fontTextStyle(16, const Color(0XFF3B3B3B), FontWeight.w600),
),
const SizedBox(height: 4),
Text(
"Credit Account",
style: fontTextStyle(12, const Color(0XFF3B3B3B), FontWeight.w500),
),
const SizedBox(height: 12),
Text(
"Available Balance",
style: fontTextStyle(
12,
widget.details.balance < 0 ? const Color(0XFFE2483D) : const Color(0XFF0A9E04),
FontWeight.w500,
),
),
const SizedBox(height: 6),
Text(
"+₹${AppSettings.formDouble(widget.details.balance.toStringAsFixed(2))}",
style: fontTextStyle(
36,
widget.details.balance < 0 ? const Color(0XFFE2483D) : const Color(0XFF0A9E04),
FontWeight.w700,
),
),
Padding(
padding: const EdgeInsets.all(0),
child: Row(
children: [
// TopUp navigation + reload after payment
Expanded(
child: GestureDetector(
onTap: () async {
final result = await Navigator.push(
context,
MaterialPageRoute(
builder: (_) => CreditTopUpConfirmScreen(
supplierName: widget.details.name,
supplierId: widget.details.supplierId,
),
),
);
if (result == true) {
// 🔥 Reload transactions + you can also re-fetch balance from your profile API if needed
fetchTransactions();
setState(() {}); // keep safe
}
},
child: _actionButton(
'images/plus_credit_account.png',
"Top Up",
const Color(0XFF8270DB),
),
),
),
if (widget.details.balance > 0) ...[
const SizedBox(width: 12),
Expanded(child: _actionButton('images/request_credit_account.png', "Request", const Color(0XFF8270DB))),
const SizedBox(width: 12),
Expanded(child: _actionButton('images/cross_credit_account.png', "Close", const Color(0XFFE2483D))),
]
],
),
),
],
),
),
Visibility(
visible: widget.details.balance < 0,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: const EdgeInsets.fromLTRB(16, 16, 16, 0),
child: Text(
'Dues',
style: fontTextStyle(16, const Color(0XFF3B3B3B), FontWeight.w600),
),
),
Padding(
padding: const EdgeInsets.all(16),
child: Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: const Color(0xFF1D7AFC),
borderRadius: BorderRadius.circular(12),
),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
width: 32,
height: 32,
decoration: const BoxDecoration(
shape: BoxShape.circle,
color: Color(0xFF1968D6),
),
child: Center(
child: Image.asset('images/truck-payments.png', width: 20, height: 20),
),
),
const SizedBox(height: 8),
Text("Payment due", style: fontTextStyle(12, const Color(0xFFFFFFFF), FontWeight.w500)),
const SizedBox(height: 4),
Text(
"Due Today, ${DateFormat("MMM d, y").format(DateTime.now())}",
style: fontTextStyle(10, const Color(0xFFFFFFFF), FontWeight.w400),
),
],
),
),
Column(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
Text(
"${AppSettings.formDouble(widget.details.balance.abs().toStringAsFixed(2))}",
style: fontTextStyle(16, const Color(0xFFFFFFFF), FontWeight.w500),
),
const SizedBox(height: 30),
Container(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 4),
decoration: BoxDecoration(
color: const Color(0xFF36AF31),
borderRadius: BorderRadius.circular(12),
),
child: Text("Pay now", style: fontTextStyle(10, const Color(0xFFFFFFFF), FontWeight.w600)),
),
],
),
],
),
),
),
],
),
),
Padding(
padding: EdgeInsets.fromLTRB(16, widget.details.balance < 0 ? 0 : 16, 16, 0),
child: Text('Transactions', style: fontTextStyle(16, const Color(0XFF3B3B3B), FontWeight.w600)),
),
SizedBox(height: MediaQuery.of(context).size.width * .012),
Expanded(
child: isLoading
? const Center(child: CircularProgressIndicator())
: ListView.separated(
itemCount: transactions.length,
separatorBuilder: (_, __) => const Divider(color: Color(0xFFE0E0E0)),
itemBuilder: (context, index) {
final t = transactions[index];
final double amount = (t["amount"] as num).toDouble();
final bool isCredit = amount > 0;
return Padding(
padding: const EdgeInsets.all(12),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(t["title"].toString(), style: fontTextStyle(12, const Color(0XFF000000), FontWeight.w400)),
if (t["subtitle"].toString().isNotEmpty)
Text(t["subtitle"].toString(),
style: fontTextStyle(10, const Color(0XFF114690), FontWeight.w400)),
],
),
),
Column(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
Text(
"${isCredit ? '+' : ''}${amount.abs().toStringAsFixed(2)}",
style: fontTextStyle(
12,
isCredit ? const Color(0XFF0A9E04) : const Color(0XFFE2483D),
FontWeight.w500,
),
),
Text(t["date"].toString(), style: fontTextStyle(10, const Color(0XFF757575), FontWeight.w400)),
],
),
],
),
);
},
),
)
],
),
);
}
Widget _actionButton(String path, String label, Color color) {
return Container(
height: 80,
width: double.infinity,
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(8),
border: Border.all(color: Colors.grey.shade300),
),
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Image.asset(path, width: 24, height: 24, color: color),
const SizedBox(height: 6),
Text(label, style: fontTextStyle(14, const Color(0XFF2A2A2A), FontWeight.w400)),
],
),
),
);
}
}

@ -0,0 +1,395 @@
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:dotted_border/dotted_border.dart';
import 'package:watermanagement/supplier/paymnets/credit_accounts.dart';
import 'package:watermanagement/supplier/paymnets/transaction_model.dart';
import 'package:watermanagement/supplier/paymnets/transactions.dart';
import 'package:http/http.dart' as http;
import '../../common/settings.dart';
class PaymentsMainScreen extends StatefulWidget {
const PaymentsMainScreen({super.key});
@override
State<PaymentsMainScreen> createState() => _PaymentsMainScreenState();
}
class _PaymentsMainScreenState extends State<PaymentsMainScreen> {
List<TransactionModel> transactions = [];
bool isLoading = true;
Future<void> fetchTransactions() async {
String customerId = AppSettings.customerId;
final url = Uri.parse(
"http://armintaaqua.com:3000/api/userAccounts/$customerId");
try {
final response = await http.get(url);
if (response.statusCode != 200) return;
final body = json.decode(response.body);
final data = body["data"] ?? {};
final List<TransactionModel> loaded = [];
double parseAmount(dynamic v) {
if (v == null) return 0;
final s = v.toString().trim().toLowerCase();
if (s.isEmpty || s == "null" || s == "undefined" || s == "nan") return 0;
return double.tryParse(s) ?? 0;
}
String safe(dynamic v) {
if (v == null) return "-";
final s = v.toString().trim();
if (s.isEmpty || s == "null" || s == "undefined") return "-";
return s;
}
// ================= PAID =================
for (final item in data["advance_paid_entries"] ?? []) {
final amount = parseAmount(item["amount_paid"]);
loaded.add(TransactionModel(
safe(item["supplierName"]),
safe(item["dateOfOrder"]),
"+₹${amount.toStringAsFixed(0)}",
Colors.green,
));
}
// ================= DELIVERED =================
for (final item in data["delivered_entries"] ?? []) {
final amount = parseAmount(item["amount"]);
loaded.add(TransactionModel(
safe(item["supplierName"]),
safe(item["date"] ?? item["dateOfOrder"]),
"+₹${amount.toStringAsFixed(0)}",
Colors.blue,
));
}
// ================= PENDING =================
for (final item in data["pending_entries"] ?? []) {
final amount = parseAmount(item["amount"]);
loaded.add(TransactionModel(
safe(item["supplierName"]),
safe(item["date"]),
"${amount.toStringAsFixed(0)}",
Colors.orange,
));
}
// ================= REJECTED =================
for (final item in data["rejected_entries"] ?? []) {
final amount = parseAmount(item["amount"]);
loaded.add(TransactionModel(
safe(item["supplierName"]),
safe(item["date"]),
"${amount.toStringAsFixed(0)}",
Colors.grey,
));
}
setState(() {
transactions = loaded; // ALL RECORDS NOW
isLoading = false;
});
} catch (e) {
isLoading = false;
debugPrint("Transactions error: $e");
}
}
@override
void initState() {
super.initState();
fetchTransactions();
}
Widget _quickMenuItem(String imagePath, String title) {
return Container(
padding: EdgeInsets.all(12),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(12),
border: Border.all(color: Color(0XFFC3C4C4), width: 1.0),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Image.asset(imagePath, width: 20, height: 20, fit: BoxFit.contain),
SizedBox(height: 8),
Text(
title,
style: fontTextStyle(14, Color(0xFF2A2A2A), FontWeight.w600),
),
],
),
);
}
Widget _transferItem(String name, String imagePath) {
return Column(
children: [
Container(
width: 56,
height: 56,
decoration: BoxDecoration(
color: Color(0xFFE8F2FF),
borderRadius: BorderRadius.circular(28), // 28px radius = circle
),
child: ClipRRect(
borderRadius: BorderRadius.circular(28),
child: Image.asset(
imagePath,
width: 56,
height: 56,
),
),
),
SizedBox(height: 4),
Text(name, style: TextStyle(fontSize: 12)),
],
);
}
Widget _transactionItem(
String supplier, String dateTime, String amount, Color color) {
return ListTile(
contentPadding: EdgeInsets.zero, // 👈 removes default padding
title: Text(
supplier,
style: TextStyle(fontWeight: FontWeight.w600),
),
subtitle: Text(dateTime),
trailing: Text(
amount,
style: TextStyle(color: color, fontWeight: FontWeight.w600),
),
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.white,
body: SingleChildScrollView(
padding: EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Quick Menu
Text(
"Quick Menu",
style: fontTextStyle(14, Color(0xFF2A2A2A), FontWeight.w600),
),
SizedBox(height: 12),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Expanded(
child: GestureDetector(
onTap: (){
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => CreditAccountsScreen()),
);
},
child: _quickMenuItem('images/credit-accounts.png', "Credit Accounts"),
),
),
SizedBox(width: 12), // spacing between items
Expanded(
child: _quickMenuItem('images/payment-methods.png', "Payment methods"),
),
SizedBox(width: 12),
Expanded(
child: _quickMenuItem('images/spending-summary.png', "Spending\nSummary"),
),
],
),
SizedBox(height: 20),
// Payment due card
Container(
padding: EdgeInsets.all(16),
decoration: BoxDecoration(
color: Color(0xFF1D7AFC),
borderRadius: BorderRadius.circular(12),
),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
width: 32,
height: 32,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: Color(0xFF1968D6),
),
child: Center(
child: Image.asset(
'images/truck-payments.png',
width: 20,
height: 20,
),
),
),
SizedBox(height: 8),
Text(
"Payment due",
style:
fontTextStyle(12, Color(0xFFFFFFFF), FontWeight.w500),
),
SizedBox(height: 4),
Text(
"Due today, May 20, 2025",
style:
fontTextStyle(10, Color(0xFFFFFFFF), FontWeight.w400),
),
],
),
),
Column(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
Text(
"₹2,864.10",
style:
fontTextStyle(16, Color(0xFFFFFFFF), FontWeight.w500),
),
SizedBox(height: 30),
Container(
padding:
EdgeInsets.symmetric(horizontal: 12, vertical: 4),
decoration: BoxDecoration(
color: Color(0xFF36AF31),
borderRadius: BorderRadius.circular(12),
),
child: Text(
"Pay now",
style:
fontTextStyle(10, Color(0xFFFFFFFF), FontWeight.w600),
),
),
],
),
],
),
),
SizedBox(height: 20),
// Transfer
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
"Transfer",
style: fontTextStyle(14, Color(0xFF2A2A2A), FontWeight.w600),
),
Image.asset('images/arrow-right.png', width: 16, height: 16),
],
),
SizedBox(height: 12),
Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
Column(
children: [
DottedBorder(
borderType: BorderType.Circle,
dashPattern: [6, 3],
color: Colors.grey,
strokeWidth: 1,
child: Container(
width: 48,
height: 48,
alignment: Alignment.center,
child: Icon(Icons.add, color: Colors.black),
),
),
SizedBox(height: 4),
Text("Add", style: TextStyle(fontSize: 12)),
],
),
_transferItem("Name", 'images/profile_user.png'),
_transferItem("Name", 'images/profile_user.png'),
_transferItem("Name", 'images/profile_user.png'),
_transferItem("Name", 'images/profile_user.png'),
],
),
SizedBox(height: 20),
// Transactions
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
"Transactions",
style: fontTextStyle(14, Color(0xFF2A2A2A), FontWeight.w600),
),
GestureDetector(
onTap: (){
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => Transactions(
transactions: transactions,
),
),
);
},
child: Image.asset(
'images/arrow-right.png', // Replace with your actual image path
width: 16,
height: 16,
),
)
],
),
SizedBox(height: 12),
// Transaction list
isLoading
? const Center(child: CircularProgressIndicator(color: primaryColor,)):
(transactions.length != 0
? ListView.builder(
physics: NeverScrollableScrollPhysics(),
shrinkWrap: true,
itemCount: transactions.length,
itemBuilder: (context, index) {
final txn = transactions[index];
return _transactionItem(
txn.name,
txn.dateTime,
txn.amount,
txn.color,
);
},
)
: Center(
child: Text(
'No Data Available',
style: fontTextStyle(12, Color(0XFF000000), FontWeight.w500),
),
))
],
),
),
);
}
}

@ -0,0 +1,440 @@
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:razorpay_flutter/razorpay_flutter.dart';
import 'package:upi_india/upi_india.dart';
import 'package:watermanagement/common/settings.dart';
class CreditTopUpConfirmScreen extends StatefulWidget {
final String supplierName;
final String supplierId;
const CreditTopUpConfirmScreen({
super.key,
required this.supplierName,
required this.supplierId,
});
@override
State<CreditTopUpConfirmScreen> createState() => _CreditTopUpConfirmScreenState();
}
class _CreditTopUpConfirmScreenState extends State<CreditTopUpConfirmScreen> {
final TextEditingController amountCtrl = TextEditingController(text: "2000");
bool isPaying = false;
String? errorText;
// UPI
late UpiIndia _upiIndia;
List<UpiApp> _apps = [];
UpiApp? _selectedUpiApp;
// Razorpay
Razorpay? _razorpay;
@override
void initState() {
super.initState();
_upiIndia = UpiIndia();
_loadUpiApps();
_razorpay = Razorpay();
_razorpay!.on(Razorpay.EVENT_PAYMENT_SUCCESS, _onRazorpaySuccess);
_razorpay!.on(Razorpay.EVENT_PAYMENT_ERROR, _onRazorpayError);
_razorpay!.on(Razorpay.EVENT_EXTERNAL_WALLET, _onRazorpayExternalWallet);
}
@override
void dispose() {
amountCtrl.dispose();
_razorpay?.clear();
super.dispose();
}
Future<void> _loadUpiApps() async {
try {
final apps = await _upiIndia.getAllUpiApps(mandatoryTransactionId: false);
if (!mounted) return;
setState(() {
_apps = apps;
// Prefer PhonePe if exists else first
_selectedUpiApp = _apps.firstWhere(
(a) => a.name.toLowerCase().contains("phonepe"),
orElse: () => _apps.isNotEmpty ? _apps.first : UpiApp.bhim,
);
});
} catch (_) {
// Ignore, we will show Razorpay option anyway
}
}
double _readAmount() {
final raw = amountCtrl.text.trim().replaceAll(",", "");
final v = double.tryParse(raw) ?? 0;
return v;
}
bool _validateAmount() {
final amt = _readAmount();
if (amt < 1) {
setState(() => errorText = "Enter a valid amount");
return false;
}
if (amt > 1000000) {
setState(() => errorText = "Amount too high");
return false;
}
setState(() => errorText = null);
return true;
}
Future<void> _payViaSelectedUpiApp() async {
if (!_validateAmount()) return;
if (_selectedUpiApp == null) {
setState(() => errorText = "No UPI app found. Use Razorpay option.");
return;
}
final amt = _readAmount();
setState(() {
isPaying = true;
errorText = null;
});
try {
// 🔥 Use YOUR business UPI ID here (store / supplier / platform UPI)
// If you want supplier-wise UPI ID, pass it in widget.
const receiverUpiId = "9912686262@xyz";
const receiverName = "Water Management";
final txnRef = "TOPUP_${DateTime.now().millisecondsSinceEpoch}";
final res = await _upiIndia.startTransaction(
app: _selectedUpiApp!,
receiverUpiId: receiverUpiId,
receiverName: receiverName,
transactionRefId: txnRef,
transactionNote: "Credit TopUp",
amount: amt,
);
if (!mounted) return;
// UPI result handling
final status = (res.status ?? "").toLowerCase();
if (status == UpiPaymentStatus.SUCCESS.toLowerCase()) {
// Save in your backend
await AppSettings.addAdvanceTopUp(
widget.supplierId,
AppSettings.customerId,
amt,
"upi_${_selectedUpiApp!.name.toLowerCase()}",
gatewayTxnId: res.transactionId ?? txnRef,
);
if (!mounted) return;
Navigator.pop(context, true);
} else if (status == UpiPaymentStatus.SUBMITTED.toLowerCase()) {
setState(() => errorText = "Payment pending. Please check in your UPI app.");
} else {
setState(() => errorText = "Payment failed or cancelled.");
}
} catch (e) {
if (!mounted) return;
setState(() => errorText = "Payment failed. Try again.");
} finally {
if (mounted) setState(() => isPaying = false);
}
}
/// Razorpay (Recommended for full PhonePe/cards/netbanking etc.)
/// Backend creates order_id securely, then open checkout.
Future<void> _payViaRazorpay() async {
if (!_validateAmount()) return;
final amt = _readAmount();
setState(() {
isPaying = true;
errorText = null;
});
try {
// 1) Create Razorpay order from backend
final orderResp = await AppSettings.createRazorpayOrder(
amount: amt,
supplierId: widget.supplierId,
customerId: AppSettings.customerId,
);
final decoded = jsonDecode(orderResp);
final orderId = decoded["order_id"]; // backend should return this
final keyId = decoded["key_id"]; // backend may return key_id or keep static in app
if (orderId == null || keyId == null) {
throw Exception("Invalid order response");
}
final options = {
'key': keyId,
'amount': (amt * 100).round(), // paise
'name': widget.supplierName,
'description': 'Credit Top Up',
'order_id': orderId,
'retry': {'enabled': true, 'max_count': 1},
'prefill': {
'contact': AppSettings.phoneNumber ?? '',
'email': AppSettings.email ?? '',
},
'theme': {'color': '#1D7AFC'},
// 'external': {'wallets': ['paytm']} // optional
};
_razorpay?.open(options);
} catch (e) {
if (!mounted) return;
setState(() {
isPaying = false;
errorText = "Unable to start payment. Try again.";
});
}
}
// Razorpay callbacks
Future<void> _onRazorpaySuccess(PaymentSuccessResponse response) async {
final amt = _readAmount();
try {
// 2) Verify payment on backend (signature verification)
await AppSettings.verifyRazorpayPayment(
razorpayPaymentId: response.paymentId ?? "",
razorpayOrderId: response.orderId ?? "",
razorpaySignature: response.signature ?? "",
);
// 3) Save topup
await AppSettings.addAdvanceTopUp(
widget.supplierId,
AppSettings.customerId,
amt,
"razorpay",
gatewayTxnId: response.paymentId ?? "",
);
if (!mounted) return;
Navigator.pop(context, true);
} catch (e) {
if (!mounted) return;
setState(() {
errorText = "Payment captured but verification failed. Contact support.";
isPaying = false;
});
}
}
void _onRazorpayError(PaymentFailureResponse response) {
if (!mounted) return;
setState(() {
isPaying = false;
errorText = "Payment failed. Please try again.";
});
}
void _onRazorpayExternalWallet(ExternalWalletResponse response) {
// optional
}
@override
Widget build(BuildContext context) {
final amount = _readAmount();
return Scaffold(
backgroundColor: Colors.white,
appBar: AppBar(
elevation: 0,
backgroundColor: Colors.white,
leading: IconButton(
icon: const Icon(Icons.close, color: Colors.black),
onPressed: () => Navigator.pop(context),
),
),
body: Padding(
padding: const EdgeInsets.all(24),
child: Column(
children: [
const SizedBox(height: 24),
AnimatedSwitcher(
duration: const Duration(milliseconds: 250),
child: Text(
widget.supplierName,
key: ValueKey(widget.supplierName),
style: fontTextStyle(16, Colors.black, FontWeight.w600),
),
),
const SizedBox(height: 6),
Text("Credit Account · Top Up", style: fontTextStyle(12, Colors.grey, FontWeight.w400)),
const SizedBox(height: 24),
// Editable amount
AnimatedContainer(
duration: const Duration(milliseconds: 250),
padding: const EdgeInsets.symmetric(horizontal: 18, vertical: 8),
decoration: BoxDecoration(
color: const Color(0xFFEFF4FF),
borderRadius: BorderRadius.circular(16),
border: Border.all(
color: errorText != null ? Colors.red.withOpacity(0.5) : Colors.transparent,
),
),
child: TextField(
controller: amountCtrl,
keyboardType: TextInputType.number,
textAlign: TextAlign.center,
onChanged: (_) {
if (errorText != null) setState(() => errorText = null);
setState(() {}); // animate amount changes
},
style: fontTextStyle(26, const Color(0xFF1D7AFC), FontWeight.w700),
decoration: const InputDecoration(
border: InputBorder.none,
prefixText: "",
),
),
),
const SizedBox(height: 8),
Text("What is it for?", style: fontTextStyle(12, Colors.grey, FontWeight.w400)),
if (errorText != null) ...[
const SizedBox(height: 12),
AnimatedOpacity(
opacity: 1,
duration: const Duration(milliseconds: 250),
child: Text(errorText!, style: fontTextStyle(12, Colors.red, FontWeight.w500)),
),
],
const SizedBox(height: 18),
// UPI App Selector
if (_apps.isNotEmpty) ...[
Align(
alignment: Alignment.centerLeft,
child: Text("Pay using UPI App", style: fontTextStyle(12, const Color(0xFF3B3B3B), FontWeight.w600)),
),
const SizedBox(height: 10),
SizedBox(
height: 56,
child: ListView.separated(
scrollDirection: Axis.horizontal,
itemCount: _apps.length,
separatorBuilder: (_, __) => const SizedBox(width: 10),
itemBuilder: (_, i) {
final app = _apps[i];
final selected = _selectedUpiApp?.name == app.name;
return GestureDetector(
onTap: () => setState(() => _selectedUpiApp = app),
child: AnimatedContainer(
duration: const Duration(milliseconds: 200),
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 10),
decoration: BoxDecoration(
color: selected ? const Color(0xFF1D7AFC).withOpacity(0.12) : Colors.white,
borderRadius: BorderRadius.circular(14),
border: Border.all(
color: selected ? const Color(0xFF1D7AFC) : const Color(0xFFE0E0E0),
),
),
child: Row(
children: [
if (app.icon != null) Image.memory(app.icon!, width: 22, height: 22),
const SizedBox(width: 8),
Text(app.name, style: fontTextStyle(12, const Color(0xFF3B3B3B), FontWeight.w500)),
],
),
),
);
},
),
),
],
const Spacer(),
// Buttons (Animated)
AnimatedSwitcher(
duration: const Duration(milliseconds: 250),
child: isPaying
? SizedBox(
key: const ValueKey("loading"),
width: double.infinity,
height: 48,
child: ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xFF1D7AFC),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(24)),
),
onPressed: null,
child: const SizedBox(
width: 22,
height: 22,
child: CircularProgressIndicator(strokeWidth: 2, color: Colors.white),
),
),
)
: Column(
key: const ValueKey("buttons"),
children: [
// Confirm = UPI app selected
SizedBox(
width: double.infinity,
height: 48,
child: ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xFF1D7AFC),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(24)),
),
onPressed: (_apps.isNotEmpty) ? _payViaSelectedUpiApp : null,
child: Text(
_apps.isNotEmpty
? "Confirm (UPI: ${_selectedUpiApp?.name ?? ''})"
: "Confirm (UPI not available)",
style: fontTextStyle(14, Colors.white, FontWeight.w600),
),
),
),
const SizedBox(height: 10),
// Razorpay = best for PhonePe/cards/netbanking
SizedBox(
width: double.infinity,
height: 48,
child: OutlinedButton(
style: OutlinedButton.styleFrom(
side: const BorderSide(color: Color(0xFF1D7AFC)),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(24)),
),
onPressed: _payViaRazorpay,
child: Text(
"Pay via Razorpay / PhonePe",
style: fontTextStyle(14, const Color(0xFF1D7AFC), FontWeight.w600),
),
),
),
const SizedBox(height: 10),
Text(
"Amount: ₹${amount.toStringAsFixed(0)}",
style: fontTextStyle(10, const Color(0xFF757575), FontWeight.w400),
),
],
),
),
],
),
),
);
}
}

@ -0,0 +1,10 @@
import 'package:flutter/material.dart';
class TransactionModel {
final String name;
final String dateTime;
final String amount;
final Color color;
TransactionModel(this.name, this.dateTime, this.amount, this.color);
}

@ -0,0 +1,74 @@
import 'package:flutter/material.dart';
import 'package:watermanagement/common/settings.dart';
import 'package:watermanagement/supplier/paymnets/transaction_model.dart';
class Transactions extends StatefulWidget {
final List<TransactionModel> transactions;
const Transactions({
super.key,
required this.transactions,
});
@override
State<Transactions> createState() => _TransactionsState();
}
class _TransactionsState extends State<Transactions> {
Widget _transactionItem(
String supplier, String dateTime, String amount, Color color) {
return ListTile(
contentPadding: EdgeInsets.zero,
title: Text(supplier, style: const TextStyle(fontWeight: FontWeight.w600)),
subtitle: Text(dateTime),
trailing: Text(
amount,
style: TextStyle(color: color, fontWeight: FontWeight.w600),
),
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: const Color(0XFFFFFFFF),
appBar: AppSettings.supplierAppBarWithoutActions(
'Transactions', context),
body: Padding(
padding: const EdgeInsets.fromLTRB(16, 0, 16, 0),
child: widget.transactions.isEmpty
? Center(
child: Text(
'No Data Available',
style: fontTextStyle(
12, const Color(0XFF000000), FontWeight.w500),
),
)
: ListView.builder(
itemCount: widget.transactions.length,
itemBuilder: (context, index) {
final txn = widget.transactions[index];
return Column(
children: [
_transactionItem(
txn.name,
txn.dateTime,
txn.amount,
txn.color,
),
if (index != widget.transactions.length - 1)
const Divider(
thickness: 1,
color: Color(0xFFE0E0E0),
height: 1,
),
],
);
},
),
),
);
}
}

@ -0,0 +1,686 @@
import 'dart:convert';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
import 'package:watermanagement/common/settings.dart';
import 'package:watermanagement/models/supplier_tankers_model.dart';
class PlaceOrder extends StatefulWidget {
var details;
PlaceOrder({this.details});
@override
State<PlaceOrder> createState() => _PlaceOrderState();
}
class _PlaceOrderState extends State<PlaceOrder> {
bool isTankerDataLoading = false;
bool isSereverIssue = false;
List<SupplierTankersModel> tankersList = [];
String? _selectedOption = 'Yes';
int quantity = 1;
DateTime? _selectedDate;
String displayDeliveryDate='';
TimeOfDay? _selectedTime;
TimeOfDay _selectedTime1 = TimeOfDay(hour: 9, minute: 0);
Future<void> _pickTime() async {
final TimeOfDay now = TimeOfDay.now();
final TimeOfDay? picked = await showTimePicker(
context: context,
initialTime: _selectedTime ?? now,
helpText: 'Select a time',
);
if (picked != null && picked != _selectedTime) {
setState(() {
_selectedTime = picked;
});
}
}
void _showTimePicker() {
showCupertinoModalPopup(
context: context,
builder: (_) => Container(
height: 300,
color: Colors.white,
child: Column(
children: [
Container(
padding: EdgeInsets.all(16),
child: Text(
'Select Time',
style: TextStyle(fontWeight: FontWeight.bold),
),
),
Expanded(
child: CupertinoDatePicker(
mode: CupertinoDatePickerMode.time,
use24hFormat: false, // true for 24-hour format
initialDateTime: DateTime(
0,
0,
0,
/* _selectedTime!.hour,
_selectedTime!.minute,*/
),
onDateTimeChanged: (DateTime newTime) {
setState(() {
_selectedTime = TimeOfDay(
hour: newTime.hour,
minute: newTime.minute,
);
});
},
),
),
CupertinoButton(
child: Text('Done'),
onPressed: () => Navigator.of(context).pop(),
),
],
),
),
);
}
Future<void> _selectTime(BuildContext context) async {
final TimeOfDay? picked = await showTimePicker(
context: context,
initialTime: _selectedTime1,
cancelText: 'Cancel',
confirmText: 'Confirm',
builder: (BuildContext context, Widget? child) {
return Theme(
data: ThemeData.dark().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: fontTextStyle(14, Color(0XFF1D7AFC), FontWeight.w600),
dayPeriodTextStyle: fontTextStyle(14, Color(0XFF1D7AFC), FontWeight.w600),
hourMinuteTextStyle: fontTextStyle(50, Color(0XFF1D7AFC), FontWeight.w600),
helpTextStyle: fontTextStyle(14, Color(0XFF1D7AFC), FontWeight.w600),
cancelButtonStyle: ButtonStyle(
foregroundColor: MaterialStateProperty.all<Color>(Color(0XFF1D7AFC)),
padding: MaterialStateProperty.all<EdgeInsets>(EdgeInsets.symmetric(horizontal: 20, vertical: 10)),
textStyle: MaterialStateProperty.all<TextStyle>(fontTextStyle(14, Colors.white, FontWeight.w600)),
),
confirmButtonStyle: ButtonStyle(
foregroundColor: MaterialStateProperty.all<Color>(Color(0XFF1D7AFC)),
padding: MaterialStateProperty.all<EdgeInsets>(EdgeInsets.symmetric(horizontal: 20, vertical: 10)),
textStyle: MaterialStateProperty.all<TextStyle>(fontTextStyle(14, Colors.white, FontWeight.w600)),
),
)),
child: child!,
);
},
);
if (picked != null && picked != _selectedTime) {
setState(() {
_selectedTime = picked;
});
}
}
Future<void> _pickDate() async {
DateTime now = DateTime.now();
DateTime lastDate = now.add(Duration(days: 15));
final DateTime? picked = await showDatePicker(
context: context,
initialDate: _selectedDate ?? now,
firstDate: now, // Restrict to today or later
lastDate: lastDate, // Only allow next 15 days
helpText: 'Select From 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 && picked != _selectedDate) {
setState(() {
_selectedDate = picked;
//apiFromDate = DateFormat('dd-MMM-yyyy - 23:50').format(_selectedDate!);
String from=DateFormat('dd-MMM-yyyy - HH:mm').format(_selectedDate!);
DateTime parsedFromDate = DateFormat('dd-MMM-yyyy - HH:mm').parse(from);
String formattedFromDate = DateFormat('dd MMM yyyy').format(parsedFromDate);
displayDeliveryDate = formattedFromDate;
});
}
}
Future<void> getTankers() async {
isTankerDataLoading = true;
try {
var tankerResponse = await AppSettings.getAllTankers(widget.details.supplier_id);
setState(() {
tankersList =
((jsonDecode(tankerResponse)['data']) as List).map((dynamic model) {
return SupplierTankersModel.fromJson(model);
}).toList();
isTankerDataLoading = false;
});
} catch (e) {
setState(() {
isTankerDataLoading = false;
isSereverIssue = true;
});
/* AppSettings.longFailedToast('There is an issue at server side please try after some time');
Navigator.pop(context);*/
}
}
@override
void initState() {
// TODO: implement initState
super.initState();
getTankers();
}
void increment() {
setState(() {
quantity++;
});
}
void decrement() {
setState(() {
if (quantity > 1) quantity--;
});
}
Widget renderUi(){
Map<String, List<SupplierTankersModel>> groupByType(List<SupplierTankersModel> items) {
final Map<String, List<SupplierTankersModel>> grouped = {};
for (var item in items) {
grouped.putIfAbsent(item.type_of_water, () => []).add(item);
}
return grouped;
}
final groupedData = groupByType(tankersList);
if(tankersList.isNotEmpty){
return Padding(padding: EdgeInsets.fromLTRB(12, 8, 12, 8),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.start,
children: [
// 🔹 Supplier Name
SizedBox(height: MediaQuery.of(context).size.height * .016),
Text(
widget.details.supplier_name,
style: fontTextStyle(16, Color(0XFF2D2E30), FontWeight.w600),
),
SizedBox(height: MediaQuery.of(context).size.height * .020),
// 🔹 Grouped Items
...groupedData.entries.map((entry) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(entry.key,
style: fontTextStyle(12, Color(0XFF232527), FontWeight.w500)),
SizedBox(height: MediaQuery.of(context).size.height * .020),
Wrap(
spacing: 8,
runSpacing: 8,
children: entry.value.map((item) {
return Container(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
decoration: BoxDecoration(
color: item.isStrikethrough
? Colors.black87
: item.isSelected
? Colors.black
: Colors.transparent,
border: Border.all(
color: item.isSelected
? Colors.orange
: Colors.grey.shade400,
),
borderRadius: BorderRadius.circular(30),
),
child: Text(
item.capacity + ' L',
style: TextStyle(
color: item.isStrikethrough ? Colors.white : Colors.black,
decoration: item.isStrikethrough
? TextDecoration.lineThrough
: TextDecoration.none,
),
),
);
}).toList(),
),
SizedBox(height: MediaQuery.of(context).size.height * .020),
Divider(color: Colors.grey.shade300,),
],
);
}).toList(),
SizedBox(height: MediaQuery.of(context).size.height * .016),
// 🔹 Quantity Row: Comes immediately after list
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Quantity',
style: fontTextStyle(12, Color(0XFF232527), FontWeight.w500),
),
Row(
children: [
GestureDetector(
onTap: () {
decrement();
},
child: Container(
width: 24,
height: 24,
decoration: BoxDecoration(
color: Color(0XFFC9DFFE),
shape: BoxShape.circle,
),
child: Center(
child: Image.asset(
'images/minus.png',
width: 12,
height: 12,
),
),
),
),
SizedBox(width: MediaQuery.of(context).size.width * .008),
Text(
quantity.toString(),
style: fontTextStyle(12, Color(0XFF000000), FontWeight.w400),
),
SizedBox(width: MediaQuery.of(context).size.width * .008),
GestureDetector(
onTap: () {
increment();
},
child: Container(
width: 24,
height: 24,
decoration: BoxDecoration(
color: Color(0XFFC9DFFE),
shape: BoxShape.circle,
),
child: Center(
child: Image.asset(
'images/plus.png',
width: 12,
height: 12,
),
),
),
),
],
),
],
),
SizedBox(height: MediaQuery.of(context).size.height * .016),
Divider(color: Colors.grey.shade300,),
SizedBox(height: MediaQuery.of(context).size.height * .016),
Text(
'Pump',
style: fontTextStyle(12, Color(0XFF232527), FontWeight.w500),
),
SizedBox(height: MediaQuery.of(context).size.height * .004),
Row(
children: [
Row(
children: [
Radio<String>(
value: 'Yes',
groupValue: _selectedOption,
activeColor: primaryColor,
visualDensity: VisualDensity.compact,
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
onChanged: (value) {
setState(() {
_selectedOption = value;
});
},
),
Text('Yes',style: fontTextStyle(12, Color(0XFF2D2E30), FontWeight.w500),),
],
),
SizedBox(width: MediaQuery.of(context).size.width * .040),
Row(
children: [
Radio<String>(
value: 'No',
groupValue: _selectedOption,
activeColor: primaryColor,
visualDensity: VisualDensity.compact,
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
onChanged: (value) {
setState(() {
_selectedOption = value;
});
},
),
Text('No',style: fontTextStyle(12, Color(0XFF2D2E30), FontWeight.w500),),
],
),
],
),
SizedBox(height: MediaQuery.of(context).size.height * .016),
Divider(color: Colors.grey.shade300,),
SizedBox(height: MediaQuery.of(context).size.height * .016),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Date of delivery',
style: fontTextStyle(12, Color(0XFF232527), FontWeight.w500),
),
Row(
children: [
Container(
decoration: BoxDecoration(
shape: BoxShape.rectangle,
color: Color(0XFFF6F6F6),
border: Border.all(
width: 1,
color: Color(0XFFF6F6F6),
),
borderRadius:
BorderRadius.circular(
5,
)),
padding: EdgeInsets.fromLTRB(4, 4, 4,4),
child: Text(
displayDeliveryDate.toString(),
style: fontTextStyle(12, Color(0XFF646566), FontWeight.w500),
),
),
SizedBox(width: MediaQuery.of(context).size.width * .032),
GestureDetector(
onTap: () {
_pickDate();
},
child: Image.asset(
'images/calender.png',
width: 20,
height: 20,
),
),
],
),
],
),
SizedBox(height: MediaQuery.of(context).size.height * .016),
Divider(color: Colors.grey.shade300,),
SizedBox(height: MediaQuery.of(context).size.height * .016),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Time of delivery',
style: fontTextStyle(12, Color(0XFF232527), FontWeight.w500),
),
Row(
children: [
Container(
decoration: BoxDecoration(
shape: BoxShape.rectangle,
color: Color(0XFFF6F6F6),
border: Border.all(
width: 1,
color: Color(0XFFF6F6F6),
),
borderRadius:
BorderRadius.circular(
5,
)),
padding: EdgeInsets.fromLTRB(4, 4, 4,4),
child: Text(
_selectedTime1.format(context),
style: fontTextStyle(12, Color(0XFF646566), FontWeight.w500),
),
),
SizedBox(width: MediaQuery.of(context).size.width * .032),
GestureDetector(
onTap: () {
//_pickTime();
//_showTimePicker();
_selectTime(context);
},
child: Image.asset(
'images/clock.png',
width: 20,
height: 20,
),
),
],
),
],
),
SizedBox(height: MediaQuery.of(context).size.height * .016),
Row(
children: [
GestureDetector(
onTap: () {},
child: Image.asset(
'images/add_icon.png',
width: 24,
height: 24,
),
),
SizedBox(width: MediaQuery.of(context).size.width * .032),
Text(
'Add order',
style: fontTextStyle(12, Color(0XFF232527), FontWeight.w500),
),
],
),
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: Color(0XFF1D7AFC),
),
borderRadius:
BorderRadius.circular(
12,
)),
alignment: Alignment.center,
child: Padding(
padding: EdgeInsets.fromLTRB(
16, 12, 16, 12),
child: Text('SAVE DRAFT',
style: fontTextStyle(
12,
Color(0XFF1D7AFC),
FontWeight.w600)),
),
),
),),
SizedBox(
width: MediaQuery.of(context)
.size
.width *
.032,
),
Expanded(child: GestureDetector(
onTap: () {
/*Navigator.push(
context,
MaterialPageRoute(
builder: (context) => PlaceOrder(details: connectedSuppliersList[index],)),
);*/
},
child: Container(
decoration: BoxDecoration(
shape: BoxShape.rectangle,
color: Color(0XFF1D7AFC),
border: Border.all(
width: 1,
color: Color(0XFFFFFFFF),
),
borderRadius:
BorderRadius.circular(
12,
)),
alignment: Alignment.center,
child: Padding(
padding: EdgeInsets.fromLTRB(
16, 12, 16, 12),
child: Text('CONTINUE',
style: fontTextStyle(
12,
Color(0XFFFFFFFF),
FontWeight.w600)),
),
),
))
],
)
],
),
);
}
else{
return Center(
child: Text(
'No Data Available',
style: fontTextStyle(
12, Color(0XFF000000), FontWeight.w500),
),
);
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Color(0XFFFFFFFF),
appBar: AppBar(
backgroundColor: Colors.white,
title: Text(
'Place Order',
style: fontTextStyle(14, Color(0XFF2A2A2A), FontWeight.w500),
),
iconTheme: IconThemeData(color: Color(0XFF2A2A2A)),
actions: [
Row(
children: [
Padding(
padding: EdgeInsets.fromLTRB(10, 10, 0, 10),
child: IconButton(
icon: Image(
image: AssetImage(
'images/calender_supplier_landing.png')),
onPressed: () {},
),
),
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: isTankerDataLoading
? Center(
child: CircularProgressIndicator(
color: primaryColor,
strokeWidth: 5.0,
),
)
: renderUi(),
);
}
}

@ -0,0 +1,768 @@
import 'package:flutter/material.dart';
import 'package:watermanagement/common/settings.dart';
import 'package:watermanagement/supplier/payment_example2.dart';
import 'package:watermanagement/supplier/payment_screen.dart';
import 'package:razorpay_flutter/razorpay_flutter.dart';
class PlanDetails extends StatefulWidget {
var supplierDetails;
PlanDetails({this.supplierDetails});
@override
State<PlanDetails> createState() => _PlanDetailsState();
}
class _PlanDetailsState extends State<PlanDetails> {
bool isExpanded = false;
double actualPrice = 0.0;
int quantity = 0;
double deliveryCharges = 0.0;
double subtotal = 0.0;
double cgst = 0.0;
double sgst = 0.0;
double total = 0.0;
double platformFee = 0.0;
double totalTaxes = 0.0;
double advance = 0.0;
double balance = 0.0;
late Razorpay _razorpay;
@override
void initState() {
super.initState();
actualPrice =
double.tryParse(widget.supplierDetails.quotedAmount ?? '0') ?? 0;
quantity = int.tryParse(widget.supplierDetails.quantity ?? '0') ?? 0;
//deliveryCharges = double.tryParse(widget.supplierDetails.bookingCharges ?? '0') ?? 0;;
platformFee = 11;
subtotal = actualPrice * quantity;
cgst = subtotal * 0.09;
sgst = subtotal * 0.09;
totalTaxes = cgst + sgst;
total = subtotal + cgst + sgst + deliveryCharges + platformFee;
advance = deliveryCharges;
//balance = total - advance;
_razorpay = Razorpay();
_razorpay.on(Razorpay.EVENT_PAYMENT_SUCCESS, (PaymentSuccessResponse response) {
_handleSuccess(response, widget.supplierDetails);
});
_razorpay.on(Razorpay.EVENT_PAYMENT_ERROR, _handleError);
_razorpay.on(Razorpay.EVENT_EXTERNAL_WALLET, _handleExternalWallet);
}
Widget _buildAddressRow(String label, String address, Color color) {
return Padding(
padding: const EdgeInsets.only(bottom: 8.0),
child: Row(
children: [
Image.asset(
'images/location_supplier_landing.png',
height: 24,
width: 24,
fit: BoxFit.contain,
color: Color(0XFF939495),
),
SizedBox(
width: MediaQuery.of(context).size.width * .012,
),
Text(
'$label ',
style: fontTextStyle(14, color, FontWeight.bold),
),
Expanded(
child: Text(
address,
overflow: TextOverflow.ellipsis,
style: fontTextStyle(12, Color(0XFF515253), FontWeight.w400),
),
)
],
),
);
}
Widget _itemRow(String title, String price, {bool bold = false}) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 4.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(title,
style: fontTextStyle(12, Color(0XFF646566), FontWeight.w400)),
Text(price,
style: fontTextStyle(12, Color(0XFF646566), FontWeight.w400)),
],
),
);
}
void _openCheckout(double amountInRupees) {
var options = {
'key': 'rzp_test_1VCCWqEXUHdINz', // Replace with your Razorpay Key
'amount': (amountInRupees * 100)
.toInt(), // Convert rupees to paise and ensure integer
'name': 'Test Payment',
'description': 'Water Order',
'prefill': {
'contact': '9876543210',
'email': 'test@example.com',
}
};
try {
_razorpay.open(options);
} catch (e) {
print('Error: $e');
}
}
/* void _handleSuccess(PaymentSuccessResponse response) {
_showAlert('Payment Successful', 'Payment ID: ${response.paymentId}');
}*/
void _handleSuccess(PaymentSuccessResponse response, dynamic object) async {
if (!mounted) return; // ensure widget is still active
var payload = <String, dynamic>{};
payload["action"] = 'accept';
payload["ref_number"] = response.paymentId;
payload["payment_type"] = object.paymentType;
try {
bool status = await AppSettings.acceptPlanRequestsWithPayment(payload, object.dbId);
if (!mounted) return;
if (status) {
AppSettings.longSuccessToast('Accepted');
Navigator.pop(context, true);
// Use Navigator safely
/* Navigator.pushReplacement(
context,
MaterialPageRoute(
builder: (context) => OrderDetails(supplierDetails: object),
),
);*/
} else {
AppSettings.longFailedToast('Failed to accept order request');
}
} catch (e) {
AppSettings.longFailedToast('Error: $e');
}
}
void _handleError(PaymentFailureResponse response) {
_showAlert('Payment Failed', '${response.code} - ${response.message}');
}
void _handleExternalWallet(ExternalWalletResponse response) {
_showAlert('Wallet Selected', '${response.walletName}');
}
void _showAlert(String title, String content) {
showDialog(
context: context,
builder: (_) => AlertDialog(
title: Text(title),
content: Text(content),
actions: [
TextButton(onPressed: () => Navigator.pop(context), child: Text('OK'))
],
),
);
}
@override
void dispose() {
_razorpay.clear();
super.dispose();
}
@override
Widget build(BuildContext context) {
final int totalOrders = widget.supplierDetails.dates.length;
final double biddingPrice =
double.tryParse(widget.supplierDetails.biddingPrice) ?? 0;
final double totalAmount = totalOrders * biddingPrice;
final num total = totalAmount is num
? totalAmount
: num.tryParse(totalAmount.toString()) ?? 0;
final num advance = widget.supplierDetails.advanceAmount is num
? widget.supplierDetails.advanceAmount
: num.tryParse(widget.supplierDetails.advanceAmount.toString()) ?? 0;
final num balance = total - advance;
return Scaffold(
backgroundColor: Color(0XFFFFFFFF),
appBar: AppSettings.supplierAppBarWithoutActions(
widget.supplierDetails.supplierName, context),
body: SingleChildScrollView(
child: Padding(
padding: EdgeInsets.fromLTRB(12, 8, 12, 8),
child: Column(
children: [
_buildAddressRow(widget.supplierDetails.supplierName,
'| ' + widget.supplierDetails.address, Color(0XFF4692FD)),
Padding(
padding: const EdgeInsets.only(
left: 8.0, bottom: 4.0), // 👈 Shift right
child: Align(
alignment: Alignment.centerLeft,
child: Image.asset(
'images/Line.png',
height: 12,
width: 6,
fit: BoxFit.contain,
),
),
),
_buildAddressRow('Home', '| ' + AppSettings.userAddress,
Color(0XFF2D2E30)),
SizedBox(
height: MediaQuery.of(context).size.height * .012,
),
Card(
color: Color(0XFFFFFFFF),
child: Padding(
padding: EdgeInsets.all(4),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.start,
children: [
Container(
width: double
.infinity, // makes it expand within the Card's width
decoration: BoxDecoration(
borderRadius: BorderRadius.vertical(
bottom: Radius.circular(8),
top: Radius.circular(8),
), // match Card border
gradient: LinearGradient(
colors: [
Color(0xFFFFF8DF),
Color(0xFFFFFFFF),
],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
),
),
padding: EdgeInsets.symmetric(vertical: 12),
alignment: Alignment.center,
child: Padding(
padding: EdgeInsets.fromLTRB(12, 0, 12, 0),
child: Column(
crossAxisAlignment:
CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.start,
children: [
Row(
mainAxisAlignment:
MainAxisAlignment.spaceBetween,
crossAxisAlignment:
CrossAxisAlignment.start,
children: [
Text(
widget
.supplierDetails.supplierName,
style: fontTextStyle(
16,
Color(0XFF2D2E30),
FontWeight.w600)),
Text(
widget.supplierDetails
.distanceInMeters
.toString() +
' Km',
style: fontTextStyle(
12,
Color(0XFF2D2E30),
FontWeight.w600)),
],
),
Text(
widget.supplierDetails.displayAddress,
style: fontTextStyle(
12,
Color(0XFF515253),
FontWeight.w400)),
],
))),
SizedBox(
height: MediaQuery.of(context).size.height * .012,
),
Container(
width: double
.infinity, // makes it expand within the Card's width
decoration: BoxDecoration(
color: Color(0XFFF5F6F6), // Same as Card color
borderRadius:
BorderRadius.circular(4), // Rounded corners
border: Border.all(
color: Color(0XFFF5F6F6), // Border color
width: 0.5, // Border width
),
),
padding: EdgeInsets.symmetric(vertical: 4),
child: Padding(
padding: EdgeInsets.fromLTRB(12, 0, 12, 0),
child: Text('ITEMS',
style: fontTextStyle(10, Color(0XFF646566),
FontWeight.w400)),
)),
SizedBox(
height: MediaQuery.of(context).size.height * .012,
),
Padding(
padding: EdgeInsets.fromLTRB(4, 0, 4, 0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
widget.supplierDetails.capacity +
' - ' +
widget.supplierDetails.typeofwater+' - ' +
widget.supplierDetails.frequency+'/week',
style: fontTextStyle(12, Color(0XFF2D2E30), FontWeight.w500),
),
Row(
children: [
Text('Actual Price: ', style: fontTextStyle(
12,
Color(0XFF646566),
FontWeight.w400),),
Text(
'' +
AppSettings.formDouble(widget.supplierDetails.quotedAmount) +
'/',
style: fontTextStyle(12, Color(0XFF2D2E30), FontWeight.w500),
),
Text(
widget.supplierDetails.capacity,
style: fontTextStyle(12, Color(0XFF2D2E30), FontWeight.w500),
)
],
),
Row(
children: [
Text('Bidding Price: ', style: fontTextStyle(
12,
Color(0XFF646566),
FontWeight.w400),),
Text(
'' +
AppSettings.formDouble(widget.supplierDetails.biddingPrice) +
'/',
style: fontTextStyle(12, Color(0XFF2D2E30), FontWeight.w500),
),
Text(
widget.supplierDetails.capacity,
style: fontTextStyle(12, Color(0XFF2D2E30), FontWeight.w500),
)
],
),
Row(
children: [
Text('Payment Type: ', style: fontTextStyle(
12,
Color(0XFF646566),
FontWeight.w400),),
Text(
widget.supplierDetails.paymentType,
style: fontTextStyle(
12,
Color(0XFF515253),
FontWeight.w600),
),
],
),
Visibility(
visible:widget.supplierDetails.paymentType!='after_delivery' ,
child: Row(
children: [
Text(widget.supplierDetails.paymentType=='credit'?'Credit Limit: ':'Advance Amount: ', style: fontTextStyle(
12,
Color(0XFF646566),
FontWeight.w400),),
Text(
widget.supplierDetails.advanceAmount,
style: fontTextStyle(
12,
Color(0XFF515253),
FontWeight.w600),
),
],
),),
Text.rich(
TextSpan(
children: [
TextSpan(
text: "Total amount for this plan orders ",
style: fontTextStyle(
12,
const Color(0xFF939495),
FontWeight.w400,
),
),
TextSpan(
text:
'$totalOrders × ${biddingPrice.toStringAsFixed(0)} = ₹${totalAmount.toStringAsFixed(0)}',
style: fontTextStyle(
12,
const Color(0xFF515253),
FontWeight.w500,
),
),
],
),
softWrap: true,
maxLines: 2,
overflow: TextOverflow.visible,
textAlign: TextAlign.center,
)
],
)
),
SizedBox(
height: MediaQuery.of(context).size.height * .012,
),
Container(
width: double
.infinity, // makes it expand within the Card's width
decoration: BoxDecoration(
color: Color(0XFFF5F6F6), // Same as Card color
borderRadius:
BorderRadius.circular(4), // Rounded corners
border: Border.all(
color: Color(0XFFF5F6F6), // Border color
width: 0.5, // Border width
),
),
padding: EdgeInsets.symmetric(vertical: 4),
child: Padding(
padding: EdgeInsets.fromLTRB(12, 0, 12, 0),
child: Text('DELIVERY DETAILS',
style: fontTextStyle(10, Color(0XFF646566),
FontWeight.w400)),
)),
SizedBox(
height: MediaQuery.of(context).size.height * .012,
),
Padding(
padding: EdgeInsets.fromLTRB(4, 0, 4, 0),
child: Column(
children: [
Row(
mainAxisAlignment:
MainAxisAlignment.spaceBetween,
children: [
Text(
'Plan Period',
style: fontTextStyle(12,
Color(0XFF515253), FontWeight.w500),
),
Visibility(
visible: widget.supplierDetails.startDate !=
'',
child: Text(
widget.supplierDetails.startDate +
' to ' +
widget.supplierDetails.endDate,
style: fontTextStyle(
12,
Color(0XFF232527),
FontWeight
.w500),
),
)
],
),
],
),
),
SizedBox(
height: MediaQuery.of(context).size.height * .012,
),
Align(
alignment: Alignment.center,
child: Container(
decoration: BoxDecoration(
color:
Color(0XFFFFFFFF), // Same as Card color
borderRadius: BorderRadius.circular(
4), // Rounded corners
border: Border.all(
color: Color(0XFF939495), // Border color
width: 0.5, // Border width
),
),
padding: EdgeInsets.symmetric(vertical: 4),
child: Padding(
padding: EdgeInsets.fromLTRB(12, 0, 12, 0),
child: Text('Change Delivery Details',
style: fontTextStyle(14,
Color(0XFF646566), FontWeight.w500)),
)),
),
SizedBox(
height: MediaQuery.of(context).size.height * .012,
),
],
),
),
),
SizedBox(
height: MediaQuery.of(context).size.height * .012,
),
Card(
color: Color(0XFFFFFFFF),
child: Padding(
padding: EdgeInsets.fromLTRB(12, 12, 12, 12),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.start,
children: [
Text('SAVINGS',
style: fontTextStyle(
10, Color(0XFF646566), FontWeight.w400)),
SizedBox(
height: MediaQuery.of(context).size.height * .012,
),
Row(
children: [
Image.asset(
'images/coupon.png',
height: 16,
width: 16,
fit: BoxFit.contain,
),
SizedBox(
width: MediaQuery.of(context).size.width *
.012),
Text(
'Apply Coupon',
style: fontTextStyle(
12,
Color(0XFF2D2E30),
FontWeight.w500,
),
),
Spacer(), // pushes the arrow to the right
Image.asset(
'images/arrow-right.png',
height: 20,
width: 20,
fit: BoxFit.contain,
),
],
),
SizedBox(
height: MediaQuery.of(context).size.height * .012,
),
],
),
)),
SizedBox(
height: MediaQuery.of(context).size.height * .012,
),
Column(
children: [
// Header card
GestureDetector(
onTap: () => setState(() => isExpanded = !isExpanded),
child: Card(
color: Color(0XFFFFFFFF),
child: Padding(
padding: EdgeInsets.fromLTRB(12, 12, 12, 12),
child: Row(
children: [
Image.asset(
'images/receipt.png',
height: 16,
width: 16,
fit: BoxFit.contain,
),
SizedBox(
width:
MediaQuery.of(context).size.width * .012,
),
Expanded(
child: Text(
'To Pay ₹${AppSettings.moneyConvertion(widget.supplierDetails.advanceAmount.toString())}',
style: fontTextStyle(12,
Color(0XFF2E302D), FontWeight.w600)),
),
isExpanded
? Image.asset(
'images/arrow-up.png',
height: 20,
width: 20,
fit: BoxFit.contain,
)
: Image.asset(
'images/arrow-down.png',
height: 20,
width: 20,
fit: BoxFit.contain,
)
],
),
),
),
),
AnimatedContainer(
duration: Duration(milliseconds: 300),
curve: Curves.easeInOut,
height: isExpanded ? null : 0,
padding:
isExpanded ? EdgeInsets.all(0) : EdgeInsets.zero,
child: isExpanded
? Card(
color: Color(0XFFFFFFFF),
child: Padding(
padding: EdgeInsets.all(8),
child: Column(
crossAxisAlignment:
CrossAxisAlignment.start,
children: [
Text('ITEMS',
style: fontTextStyle(
14,
Color(0XFF2D2E30),
FontWeight.w500)),
SizedBox(
height:
MediaQuery.of(context).size.height *
.012,
),
_itemRow(
widget.supplierDetails.capacity +
' ' +
widget
.supplierDetails.typeofwater +
' x ' +
widget.supplierDetails.quantity,
'' +
AppSettings.moneyConvertion(
biddingPrice.toString())),
Divider(),
_itemRow('Total Amount For Plan',
'${AppSettings.moneyConvertion(totalAmount.toStringAsFixed(0))}'),
_itemRow('Delivery Charges',
'₹ 0.0'),
//_itemRow('Platform Fee', '${AppSettings.moneyConvertion(platformFee.toString())}'),
_itemRow('Platform Fee', '₹ 0.0'),
_itemRow('Taxes', '₹ 0.0'),
Divider(),
_itemRow('Total Bill',
'${AppSettings.moneyConvertion(totalAmount.toString())}',
bold: true),
Divider(),
_itemRow('Advance', '${AppSettings.moneyConvertion(widget.supplierDetails.advanceAmount.toString())}'),
_itemRow('To pay(after delivery)',
'${AppSettings.moneyConvertion(balance.toString())}'),
],
),
),
)
: null,
),
],
)
],
)),
),
bottomNavigationBar: Container(
decoration: BoxDecoration(
color: Colors.white,
border: Border(
top: BorderSide(color: Color(0XFFC3C4C4), width: 0.8),
),
boxShadow: [
BoxShadow(
color: Color(0XFFC3C4C4),
blurRadius: 0,
offset: Offset(0, 0),
),
],
),
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
child: Row(
children: [
// Button 1: Pay Advance
Expanded(
child: ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: Color(0XFF0A9E04),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(24),
),
padding: EdgeInsets.symmetric(vertical: 12), // Only vertical padding
),
onPressed: () {
/*Navigator.push(
context,
MaterialPageRoute(
builder: (context) => RazorpayScreen()),
);*/
/*Navigator.push(
context,
MaterialPageRoute(
builder: (context) => PaymentOptionsPage(amount: advance),
),
);*/
_openCheckout(double.parse(widget.supplierDetails.advanceAmount));
/*Navigator.push(
context,
MaterialPageRoute(
builder: (context) => UpiIntentLauncher(),
),
);*/
},
child: Text(
"Pay advance ₹${AppSettings.moneyConvertion(advance.toString())}",
style: fontTextStyle(14, Color(0XFFFFFFFF), FontWeight.w600),
textAlign: TextAlign.center,
),
),
),
SizedBox(
width: MediaQuery.of(context).size.width * .012,
),// spacing between buttons
// Button 2: Pay Full
Expanded(
child: ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: Color(0XFF1D7AFC),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(24),
),
padding: EdgeInsets.symmetric(vertical: 12),
),
onPressed: () {
// Handle full payment
},
child: Text(
"Pay full ₹${AppSettings.moneyConvertion(total.toStringAsFixed(2))}",
style: fontTextStyle(14, Color(0XFFFFFFFF), FontWeight.w600),
textAlign: TextAlign.center,
),
),
),
],
),
),
);
}
}

File diff suppressed because it is too large Load Diff

@ -0,0 +1,369 @@
import 'dart:convert';
import 'package:auto_size_text/auto_size_text.dart';
import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
import '../../common/settings.dart';
import '../paln_requests_model.dart';
class PlanRequestsScreen extends StatefulWidget {
const PlanRequestsScreen({super.key});
@override
State<PlanRequestsScreen> createState() => _PlanRequestsScreenState();
}
class _PlanRequestsScreenState extends State<PlanRequestsScreen> {
bool isPlanRequestsDataLoading = false;
List<PlanRequestModel> planRequestsList = [];
List<PlanRequestModel> acceptedPlans = [];
List<PlanRequestModel> rejectedPlans = [];
@override
void initState() {
super.initState();
getAcceptedPlansFromSupplier();
}
/// API CALL
Future<void> getAcceptedPlansFromSupplier() async {
isPlanRequestsDataLoading = true;
var response = await AppSettings.getAcceptedPlansFromSupplier();
var json = jsonDecode(response);
setState(() {
planRequestsList = (json['data'] as List)
.map((model) => PlanRequestModel.fromJson(model))
.toList();
groupPlansByStatus();
isPlanRequestsDataLoading = false;
});
}
/// GROUP DATA
void groupPlansByStatus() {
acceptedPlans = planRequestsList
.where((plan) => plan.status.toLowerCase() == "accepted")
.toList();
rejectedPlans = planRequestsList
.where((plan) => plan.status.toLowerCase() == "rejected")
.toList();
}
/// TIME BADGE
String getOrderTimeOnly(String orderTimeStr) {
if (orderTimeStr.isEmpty) return "";
try {
final format = DateFormat("dd-MM-yyyy HH:mm");
final orderTime = format.parse(orderTimeStr);
final now = DateTime.now();
final difference = now.difference(orderTime);
if (difference.inDays < 2) {
return "New";
}
else if (difference.inDays < 10) {
final remaining = 10 - difference.inDays;
return "Expires in ${remaining}d";
}
else {
return "Expired";
}
} catch (e) {
return "";
}
}
/// PLAN CARD
Widget planCard(PlanRequestModel plan) {
final statusTime = getOrderTimeOnly(plan.acceptedTime);
Color statusColor = Color(0xFF1D7AFC);
if (statusTime == "Expired") {
statusColor = Color(0xFF757575);
}
int totalOrders = plan.dates.length;
double biddingPrice =
double.tryParse(plan.biddingPrice) ?? 0;
double totalAmount = totalOrders * biddingPrice;
return Padding(
padding: EdgeInsets.all(10),
child: Container(
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(12),
border: Border.all(color: Color(0XFF1D7AFC)),
),
child: Padding(
padding: EdgeInsets.all(14),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
/// SUPPLIER + BADGE
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
plan.supplierName,
style: fontTextStyle(
14, Color(0XFF343637), FontWeight.w600),
),
if (statusTime.isNotEmpty)
Container(
padding:
EdgeInsets.symmetric(horizontal: 6, vertical: 2),
decoration: BoxDecoration(
color: statusColor,
borderRadius: BorderRadius.circular(4),
),
child: Text(
statusTime,
style: fontTextStyle(
10, Colors.white, FontWeight.w500),
),
)
],
),
SizedBox(height: 6),
/// ADDRESS
Text(
plan.displayAddress,
style: fontTextStyle(
10, Color(0XFF646566), FontWeight.w400),
),
SizedBox(height: 4),
/// DATE RANGE
Text(
"${plan.startDate} to ${plan.endDate}",
style: fontTextStyle(
10, Color(0XFF646566), FontWeight.w400),
),
SizedBox(height: 12),
/// WATER TYPE
Container(
padding: EdgeInsets.symmetric(horizontal: 6, vertical: 3),
decoration: BoxDecoration(
color: plan.typeofwater.toLowerCase() == "bore water"
? Color(0xFF8877DD)
: Color(0xFFCA86B0),
borderRadius: BorderRadius.circular(4),
),
child: AutoSizeText(
capitalizeFirst(plan.typeofwater),
style: fontTextStyle(
12, Colors.white, FontWeight.w400),
),
),
SizedBox(height: 8),
/// ACTUAL PRICE
Row(
children: [
Text(
"Actual Price: ",
style: fontTextStyle(
12, Color(0XFF646566), FontWeight.w400),
),
Text(
"${AppSettings.formDouble(plan.quotedAmount)}/${plan.capacity}",
style: fontTextStyle(
12, Color(0XFF515253), FontWeight.w600),
)
],
),
/// BIDDING PRICE
Row(
children: [
Text(
"Bidding Price: ",
style: fontTextStyle(
12, Color(0XFF646566), FontWeight.w400),
),
Text(
"${AppSettings.formDouble(plan.biddingPrice)}/${plan.capacity}",
style: fontTextStyle(
12, Color(0XFF515253), FontWeight.w600),
)
],
),
/// FREQUENCY
Row(
children: [
Text(
"Frequency: ",
style: fontTextStyle(
12, Color(0XFF646566), FontWeight.w400),
),
Text(
"${plan.frequency}/week",
style: fontTextStyle(
12, Color(0XFF2D2E30), FontWeight.w600),
)
],
),
SizedBox(height: 10),
/// PAYMENT TYPE
Row(
children: [
Text(
"Payment Type: ",
style: fontTextStyle(
12, Color(0XFF646566), FontWeight.w400),
),
Text(
plan.paymentType,
style: fontTextStyle(
12, Color(0XFF515253), FontWeight.w600),
)
],
),
/// ADVANCE / CREDIT
if (plan.paymentType != "after_delivery")
Row(
children: [
Text(
plan.paymentType == "credit"
? "Credit Limit: "
: "Advance Amount: ",
style: fontTextStyle(
12, Color(0XFF646566), FontWeight.w400),
),
Text(
plan.advanceAmount,
style: fontTextStyle(
12, Color(0XFF515253), FontWeight.w600),
)
],
),
SizedBox(height: 10),
/// TOTAL PLAN AMOUNT
Text.rich(
TextSpan(
children: [
TextSpan(
text: "Total amount for this plan orders ",
style: fontTextStyle(
12,
Color(0xFF939495),
FontWeight.w400,
),
),
TextSpan(
text:
"$totalOrders × ${biddingPrice.toStringAsFixed(0)} = ₹${totalAmount.toStringAsFixed(0)}",
style: fontTextStyle(
12,
Color(0xFF515253),
FontWeight.w500,
),
),
],
),
),
],
),
),
),
);
}
/// UI
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.white,
appBar: AppSettings.supplierAppBarWithoutActions(
"Plan Requests", context),
body: isPlanRequestsDataLoading
? Center(
child: CircularProgressIndicator(
color: primaryColor,
strokeWidth: 5,
))
: planRequestsList.isEmpty
? Center(
child: Text(
"No Data Available",
style: fontTextStyle(
12, Colors.black, FontWeight.w500),
),
)
: ListView(
children: [
/// NEW REQUESTS
Padding(
padding: EdgeInsets.all(10),
child: Text(
"PLAN REQUESTS",
style: fontTextStyle(
10, Color(0XFF646566), FontWeight.w400),
),
),
...acceptedPlans
.map((plan) => planCard(plan))
.toList(),
/// REJECTED
if (rejectedPlans.isNotEmpty) ...[
Padding(
padding: EdgeInsets.all(10),
child: Text(
"REJECTED REQUESTS",
style: fontTextStyle(
10, Color(0XFF646566), FontWeight.w400),
),
),
...rejectedPlans
.map((plan) => planCard(plan))
.toList()
]
],
),
);
}
}

@ -0,0 +1,52 @@
import 'package:flutter/material.dart';
import 'package:geolocator/geolocator.dart';
import 'package:watermanagement/common/settings.dart';
class PlanSuppliersModel {
String supplier_name = '';
String status='';
String supplier_address='';
String supplier_phone_number='';
String supplier_id='';
Color text_color=Colors.black;
double lat=0;
double lng=0;
String distrubance_price='';
String amount_difference='';
double distanceInMeters=0;
String displayAddress='';
bool isFavorite=false;
bool isRequetsedBooking=false;
String? matchedPrice;
PlanSuppliersModel();
factory PlanSuppliersModel.fromJson(Map<String, dynamic> json){
PlanSuppliersModel rtvm = new PlanSuppliersModel();
rtvm.supplier_name = json['supplier']['suppliername'] ?? '';
rtvm.status = json['supplier']['status'] ?? '';
rtvm.supplier_address = json['supplier']['profile']['office_address'] ?? '';
rtvm.supplier_phone_number =json['supplier']['phone'] ?? '';
rtvm.supplier_id = json['supplier']['supplierId'] ?? '';
rtvm.isFavorite = json['isFavorite'] ?? false;
rtvm.isRequetsedBooking = json['requestedBooking']['status'] ?? false;
rtvm.lat = json['supplier']['latitude'] ?? 0;
rtvm.lng = json['supplier']['longitude'] ?? 0;
List<String> parts = rtvm.supplier_address.split(',');
rtvm.displayAddress = parts[2].trim();
rtvm.distanceInMeters = double.parse((Geolocator.distanceBetween(
rtvm.lat,
rtvm.lng,
AppSettings.userLatitude,
AppSettings.userLongitude
) / 1000).toStringAsFixed(2));
return rtvm;
}
}

@ -0,0 +1,249 @@
import 'package:flutter/material.dart';
import '../common/UpdateProfile.dart';
import '../common/settings.dart';
class Profile extends StatefulWidget {
const Profile({super.key});
@override
State<Profile> createState() => _ProfileState();
}
class _ProfileState extends State<Profile> {
// Section Header with title + subtitle
Widget _buildSectionHeader(String title, String subtitle) {
return Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
title,
style: fontTextStyle(12, const Color(0XFF232527), FontWeight.w600),
),
const SizedBox(height: 4),
Text(
subtitle,
style: fontTextStyle(10, const Color(0XFF646464), FontWeight.w400),
),
],
),
if (title != "My Transactions")
Image.asset(
'images/arrow-right.png',
fit: BoxFit.cover,
width:
16, // Match the diameter of the CircleAvatar
height: 16,
),
],
);
}
// List Tile with arrow
Widget _buildListTile(String title) {
return ListTile(
dense: true,
contentPadding: EdgeInsets.zero,
title: Text(title,
style:fontTextStyle(10, Color(0XFF646464), FontWeight.w400)),
trailing:Image.asset(
'images/arrow-right.png',
fit: BoxFit.cover,
width:
16, // Match the diameter of the CircleAvatar
height: 16,
),
onTap: () {},
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.white,
appBar: AppSettings.supplierAppBarWithActionsText('Profile',context),
body: Padding(
padding: EdgeInsets.all(24),
child: Container(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.start,
children: [
SizedBox(height:MediaQuery.of(context).size.height * .008,),
Padding( padding: EdgeInsets.all(0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.start,
children: [
Row(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.start,
children: [
AppSettings.loginType.toString().toLowerCase()=='user'?
GestureDetector(
onTap: () {
/*Navigator.push(
context,
new MaterialPageRoute(
builder: (__) => new ImageZoomPage(
imageName: 'Profile',
imageDetails: AppSettings.profilePictureUrl)));*/
},
child: Stack(
alignment: Alignment.center, // Centers the stack's children
children: [
// CircleAvatar background
ClipOval(
child: Container(
height: 56, // Ensure the container's height is the same as the profile image
width: 56,
decoration: BoxDecoration(
color: Colors.transparent,
shape: BoxShape.circle, // Makes the container circular
image: DecorationImage(
image: (AppSettings.profilePictureUrl !=
'' &&
AppSettings.profilePictureUrl !=
'null')
? NetworkImage(AppSettings.profilePictureUrl)
as ImageProvider
: AssetImage(
"images/profile_pic.png"), // picked file
fit: BoxFit.cover)),
),
),
// Positioned image on top of CircleAvatar
],
),
):
ClipOval(
child: Container(
height: 56, // Ensure the container's height is the same as the profile image
width: 56,
decoration: BoxDecoration(
color: Colors.transparent,
shape: BoxShape.circle, // Makes the container circular
image: DecorationImage(
image: (AssetImage(
"images/profile_pic.png")), // picked file
fit: BoxFit.contain)),
),
),
SizedBox(width:MediaQuery.of(context).size.width * .032,),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.start,
children: [
Text(
AppSettings.userName,
style: fontTextStyle(14,Color(0XFF343637),FontWeight.w500),
),
Row(
children: [
Text(
'+91 ${AppSettings.phoneNumber}',
style: fontTextStyle(10,Color(0XFF646566),FontWeight.w400),
),
SizedBox(width:MediaQuery.of(context).size.width * .032,),
Visibility(
visible:AppSettings.loginType.toString().toLowerCase()=='user',
child: Text(
AppSettings.email,
style: fontTextStyle(10,Color(0XFF646566),FontWeight.w400),
),),
],
),
SizedBox(height:MediaQuery.of(context).size.height * .004,),
GestureDetector(
onTap: (){
Navigator.push(
context,
MaterialPageRoute(builder: (context) => UpdateProfile()),
);
},
child: Text('Edit',style: fontTextStyle(14,Color(0XFF1D7AFC),FontWeight.w600),),
)
],
)
],
),
],
),),
SizedBox(height:MediaQuery.of(context).size.height * .008,),
Divider(
height: 24, // space around divider
color: Color(0XFFC3C4C4), // divider color
thickness: 2, // 👈 increase thickness here
),
Expanded(child: ListView(
padding: const EdgeInsets.symmetric(horizontal: 0, vertical: 24),
children: [
// My Accounts Section
_buildSectionHeader("My Accounts", "Favorites & Suppliers"),
const Divider(thickness: 1, height: 24, color: Color(0xFFC3C4C4)),
// My Transactions (Expandable)
ExpansionTile(
tilePadding: EdgeInsets.zero,
childrenPadding: EdgeInsets.zero,
title: _buildSectionHeader(
"My Transactions",
"View all Payments, Credit accounts & Refunds",
),
children: [
_buildListTile("My Payments"),
_buildListTile("Credit Accounts"),
_buildListTile("Modes of Payment"),
_buildListTile("Refunds"),
],
trailing: Image.asset(
'images/arrow-right.png',
fit: BoxFit.cover,
width:
16, // Match the diameter of the CircleAvatar
height: 16,
),
// 👇 This removes that unwanted grey line
shape: const Border(), // no top/bottom border
collapsedShape: const Border(), // even when collapsed
),
const Divider(thickness: 1, height: 24, color: Color(0xFFC3C4C4)),
// Addresses
_buildSectionHeader("Addresses", "Share, Edit and Add New Addresses"),
const Divider(thickness: 1, height: 24, color: Color(0xFFC3C4C4)),
_buildSectionHeader("Account Statements", "Get statements for reimbursement or book keeping!"),
const Divider(thickness: 1, height: 24, color: Color(0xFFC3C4C4)),
_buildSectionHeader("Settings", "Manage Account Settings"),
Divider(
height: 48, // space around divider
color: Color(0XFFC3C4C4), // divider color
thickness: 2, // 👈 increase thickness here
),
],
),)
],
),
),
)
);
}
}

@ -0,0 +1,530 @@
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:table_calendar/table_calendar.dart';
import '../common/settings.dart';
class SupplyCalendarScreen extends StatefulWidget {
var dates;
SupplyCalendarScreen({this.dates});
@override
State<SupplyCalendarScreen> createState() => _SupplyCalendarScreenState();
}
class _SupplyCalendarScreenState extends State<SupplyCalendarScreen> {
late DateTime _focusedDay;
late DateTime _firstDay;
late DateTime _lastDay;
Map<DateTime, List<Map<String, dynamic>>> calendarEvents = {};
DateTime? _selectedDay;
bool isLoading = true;
@override
void initState() {
super.initState();
final now = DateTime.now();
_focusedDay = now;
_firstDay = DateTime(2025, 1, 1);
_lastDay = DateTime(now.year, now.month + 6, 0);
fetchOrdersFromApi();
}
// ===========================================================================
// FETCH API BUILD TWO EVENTS (ORIGINAL + RESCHEDULED)
// ===========================================================================
Future<void> fetchOrdersFromApi() async {
try {
final response = await AppSettings.getAllOrders();
final decoded = jsonDecode(response);
if (decoded == null || decoded['data'] == null) {
setState(() => isLoading = false);
return;
}
final List<dynamic> orders = decoded['data'];
calendarEvents.clear();
for (var order in orders) {
String? originalDate = order["date"];
String? newDate = order["reScheduleDateOfDelivery"];
String? resStatus = order["rescheduleOrderStatus"];
bool isRescheduled =
newDate != null && newDate.isNotEmpty && resStatus != null && resStatus.isNotEmpty;
// ===================== ORIGINAL DATE EVENT =====================
if (originalDate != null && order["orderStatus"] != "cancelled") {
try {
DateTime d = DateTime.parse(originalDate);
DateTime key = DateTime(d.year, d.month, d.day);
calendarEvents.putIfAbsent(key, () => []);
calendarEvents[key]!.add({
"status": isRescheduled ? "rescheduled_from" : "delivery",
"_id": order["_id"],
"supplierName": order["supplierName"] ?? "Supplier",
"capacity": order["capacity"] ?? "",
"time": order["time"] ?? "",
"originalDate": originalDate,
"newDate": newDate,
});
} catch (e) {}
}
// ===================== NEW RESCHEDULED DATE EVENT =====================
if (isRescheduled) {
try {
DateTime d2 = DateTime.parse(newDate);
DateTime key2 = DateTime(d2.year, d2.month, d2.day);
calendarEvents.putIfAbsent(key2, () => []);
calendarEvents[key2]!.add({
"status": "rescheduled_to",
"_id": order["_id"],
"supplierName": order["supplierName"] ?? "Supplier",
"capacity": order["capacity"] ?? "",
"time": order["time"] ?? "",
"originalDate": originalDate,
"newDate": newDate,
});
} catch (e) {}
}
// ===================== CANCELLED EVENT =====================
if (order["orderStatus"] == "cancelled") {
try {
DateTime d = DateTime.parse(originalDate!);
DateTime key = DateTime(d.year, d.month, d.day);
calendarEvents.putIfAbsent(key, () => []);
calendarEvents[key]!.add({
"status": "cancelled",
"_id": order["_id"],
"supplierName": order["supplierName"] ?? "Supplier",
"capacity": order["capacity"] ?? "",
"time": order["time"] ?? "",
});
} catch (e) {}
}
}
setState(() => isLoading = false);
} catch (e) {
setState(() => isLoading = false);
}
}
// ===========================================================================
// CANCEL ORDER API
// ===========================================================================
Future<void> cancelDelivery(String id) async {
try {
await AppSettings.cancelPlanOrder(id); // your endpoint
await fetchOrdersFromApi();
ScaffoldMessenger.of(context)
.showSnackBar(SnackBar(content: Text("Order cancelled successfully")));
} catch (e) {
ScaffoldMessenger.of(context)
.showSnackBar(SnackBar(content: Text("Cancel failed")));
}
}
void _confirmCancel(String orderId) {
showDialog(
context: context,
builder: (_) => AlertDialog(
title: Text("Cancel Delivery"),
content: Text("Are you sure you want to cancel this delivery?"),
actions: [
TextButton(
onPressed: () => Navigator.pop(context), child: Text("No")),
TextButton(
onPressed: () {
Navigator.pop(context);
cancelDelivery(orderId);
},
child: Text("Yes, Cancel", style: TextStyle(color: Colors.red))),
],
),
);
}
// ===========================================================================
// HELPER FUNCTIONS
// ===========================================================================
String formatShort(String date) {
try {
final d = DateTime.parse(date);
return "${d.day} ${monthShort[d.month - 1]}";
} catch (e) {
return date;
}
}
List<String> monthShort = [
"Jan","Feb","Mar","Apr","May","Jun",
"Jul","Aug","Sep","Oct","Nov","Dec"
];
Widget _buildStatusIcon(String status) {
if (status == "rescheduled_from") {
return Icon(Icons.subdirectory_arrow_left, color: Colors.orange);
}
if (status == "rescheduled_to") {
return Icon(Icons.subdirectory_arrow_right, color: Colors.deepOrange);
}
if (status == "cancelled") {
return Icon(Icons.cancel, color: Colors.red);
}
return Icon(Icons.local_shipping, color: Colors.blue);
}
String _buildStatusText(Map<String, dynamic> d) {
String status = d["status"];
String? originalDate = d["originalDate"];
String? newDate = d["newDate"];
if (status == "rescheduled_from") {
return "Rescheduled from this date → ${formatShort(newDate!)}";
}
if (status == "rescheduled_to") {
return "Rescheduled to this date (from ${formatShort(originalDate!)})";
}
if (status == "cancelled") {
return "Cancelled";
}
return status;
}
// ===========================================================================
// OPEN RESCHEDULE CALENDAR
// ===========================================================================
Future<void> openRescheduleCalendar(Map<String, dynamic> delivery) async {
DateTime now = DateTime.now();
final DateTime? pickedDate = await showDatePicker(
context: context,
initialDate: now.add(Duration(days: 1)),
firstDate: now.add(Duration(days: 1)),
lastDate: DateTime(now.year + 1, 12, 31),
);
if (pickedDate == null) return;
String formatted =
"${pickedDate.year}-${pickedDate.month.toString().padLeft(2,'0')}-${pickedDate.day.toString().padLeft(2,'0')}";
await sendRescheduleToServer(delivery["_id"], formatted);
}
Future<void> sendRescheduleToServer(String id, String newDate) async {
try {
final payload = {"reScheduleDateOfDelivery": newDate};
await AppSettings.rescheduleOrder(id, payload);
await fetchOrdersFromApi();
ScaffoldMessenger.of(context)
.showSnackBar(SnackBar(content: Text("Delivery rescheduled to $newDate")));
} catch (e) {
ScaffoldMessenger.of(context)
.showSnackBar(SnackBar(content: Text("Reschedule failed")));
}
}
// ===========================================================================
// SHOW DELIVERY LIST
// ===========================================================================
void showDeliveryList(DateTime date) {
final key = DateTime(date.year, date.month, date.day);
final events = calendarEvents[key];
if (events == null || events.isEmpty) return;
bool isPast = date.isBefore(DateTime(
DateTime.now().year, DateTime.now().month, DateTime.now().day));
showModalBottomSheet(
context: context,
isScrollControlled: true,
builder: (_) {
final list = events.toList();
return DraggableScrollableSheet(
initialChildSize: 0.9,
maxChildSize: 0.9,
minChildSize: 0.4,
builder: (_, controller) {
return Container(
padding: EdgeInsets.all(20),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.vertical(top: Radius.circular(20)),
),
child: Column(
children: [
Row(
children: [
Expanded(
child: Text(
"Select Delivery",
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
),
),
GestureDetector(
onTap: () => Navigator.pop(context),
child: Icon(Icons.close)),
],
),
Expanded(
child: ListView(
controller: controller,
children: list.map((delivery) {
return ListTile(
leading: _buildStatusIcon(delivery["status"]),
title: Text(_buildStatusText(delivery)),
subtitle: Text(
"Capacity: ${delivery["capacity"]}${delivery["time"]}"),
onTap: () {
Navigator.pop(context);
showActionsForSingleDelivery(delivery, isPast);
},
);
}).toList(),
),
)
],
),
);
},
);
},
);
}
// ===========================================================================
// ACTIONS SHEET
// ===========================================================================
void showActionsForSingleDelivery(Map<String, dynamic> delivery, bool isPast) {
String status = delivery["status"];
// ORIGINAL DATE NO ACTIONS
if (status == "rescheduled_from") {
showModalBottomSheet(
context: context,
builder: (_) {
return Container(
padding: EdgeInsets.all(20),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Text("Rescheduled from this date",
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
SizedBox(height: 10),
Text("This delivery was moved to another date.",
style: TextStyle(color: Colors.grey)),
],
),
);
},
);
return;
}
// CANCELLED ORDER NO ACTIONS
if (status == "cancelled") {
showModalBottomSheet(
context: context,
builder: (_) =>
Container(
padding: EdgeInsets.all(20),
child: Text("Order was cancelled", style: TextStyle(color: Colors.red)),
),
);
return;
}
// NORMAL OR RESCHEDULED_TO (ACTIONS ENABLED)
showModalBottomSheet(
context: context,
builder: (_) {
return Container(
padding: EdgeInsets.all(20),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Text("${delivery["supplierName"]}",
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
SizedBox(height: 16),
if (!isPast) ...[
ListTile(
leading: Icon(Icons.calendar_month, color: Colors.blue),
title: Text("Reschedule Delivery"),
onTap: () {
Navigator.pop(context);
openRescheduleCalendar(delivery);
},
),
ListTile(
leading: Icon(Icons.delete_forever, color: Colors.red),
title: Text("Cancel Delivery"),
onTap: () {
Navigator.pop(context);
_confirmCancel(delivery["_id"]);
},
),
],
if (isPast)
Text("Past delivery — actions disabled",
style: TextStyle(color: Colors.grey)),
],
),
);
},
);
}
// ===========================================================================
// BUILD UI
// ===========================================================================
@override
Widget build(BuildContext context) {
if (isLoading) {
return Scaffold(body: Center(child: CircularProgressIndicator()));
}
final height = MediaQuery.of(context).size.height;
return Scaffold(
backgroundColor: Colors.white,
body: Column(
children: [
SizedBox(height: 50),
Text(
"${monthShort[_focusedDay.month - 1]} ${_focusedDay.year}",
style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
),
Expanded(
child: TableCalendar(
firstDay: _firstDay,
lastDay: _lastDay,
focusedDay: _focusedDay,
headerVisible: false,
calendarFormat: CalendarFormat.month,
rowHeight: (height - 200) / 6,
daysOfWeekHeight: 40,
onPageChanged: (focused) {
setState(() => _focusedDay = focused);
},
onDaySelected: (selected, focused) {
_focusedDay = focused;
_selectedDay = selected;
showDeliveryList(selected);
setState(() {});
},
calendarBuilders: CalendarBuilders(
defaultBuilder: (context, date, _) {
final key = DateTime(date.year, date.month, date.day);
final events = calendarEvents[key];
Color bg = Colors.transparent;
if (events != null && events.isNotEmpty) {
String s = events.first["status"];
if (s == "rescheduled_to")
bg = Colors.deepOrange.withOpacity(0.10);
else if (s == "rescheduled_from")
bg = Colors.orange.withOpacity(0.10);
else if (s == "cancelled")
bg = Colors.red.withOpacity(0.10);
else
bg = Colors.blue.withOpacity(0.08);
}
// Grouping counts (delivery, cancelled, rescheduled)
Map<String, int> grouped = {};
if (events != null) {
for (var e in events) {
grouped[e["status"]] = (grouped[e["status"]] ?? 0) + 1;
}
}
return Container(
decoration: BoxDecoration(
color: bg,
border: Border.all(color: Colors.grey.shade300)),
child: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Text("${date.day}",
style: TextStyle(
fontSize: 14, fontWeight: FontWeight.bold)),
if (events != null && events.isNotEmpty)
Wrap(
spacing: 3,
alignment: WrapAlignment.center,
children: grouped.entries.map((entry) {
IconData icon;
Color color;
if (entry.key == "rescheduled_from") {
icon = Icons.subdirectory_arrow_left;
color = Colors.orange;
}
else if (entry.key == "rescheduled_to") {
icon = Icons.subdirectory_arrow_right;
color = Colors.deepOrange;
}
else if (entry.key == "cancelled") {
icon = Icons.cancel;
color = Colors.red;
}
else {
icon = Icons.local_shipping;
color = Colors.blue;
}
return Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(icon, size: 10, color: color),
Text("x${entry.value}",
style: TextStyle(
fontSize: 10,
fontWeight: FontWeight.bold,
color: color)),
],
);
}).toList(),
)
],
),
),
);
},
),
),
),
],
),
);
}
}

@ -0,0 +1,620 @@
import 'dart:convert';
import 'package:http/http.dart' as http;
import 'package:flutter/material.dart';
import 'package:watermanagement/common/settings.dart';
import 'package:table_calendar/table_calendar.dart';
import 'package:intl/intl.dart';
import 'package:watermanagement/models/supplier_tankers_model.dart';
import 'package:watermanagement/supplier/cart_summary.dart';
class SupplierScreen extends StatefulWidget {
var details;
SupplierScreen({this.details});
@override
State<SupplierScreen> createState() => _SupplierScreenState();
}
class _SupplierScreenState extends State<SupplierScreen> {
DateTime _focusedDay = DateTime.now();
DateTime? _selectedDay;
bool isTankerDataLoading = false;
bool isCartDataLoading = false;
List<SupplierTankersModel> tankersList = [];
bool isSereverIssue = false;
List cart = [];
Map<String, int> localQuantities = {};
Map<String, int> localTotalPrices = {};
Future<void> getTankers() async {
isTankerDataLoading = true;
try {
var tankerResponse = await AppSettings.getAllTankers(widget.details.supplier_id);
setState(() {
tankersList =
((jsonDecode(tankerResponse)['data']) as List).map((dynamic model) {
return SupplierTankersModel.fromJson(model);
}).toList();
isTankerDataLoading = false;
});
} catch (e) {
setState(() {
isTankerDataLoading = false;
isSereverIssue = true;
});
/* AppSettings.longFailedToast('There is an issue at server side please try after some time');
Navigator.pop(context);*/
}
}
Future<void> getCart() async {
isCartDataLoading = true;
try {
var tankerResponse = await AppSettings.getCartItems();
setState(() {
cart = ((jsonDecode(tankerResponse)['data']['items']));
isCartDataLoading = false;
});
} catch (e) {
setState(() {
isCartDataLoading = false;
isSereverIssue = true;
});
/* AppSettings.longFailedToast('There is an issue at server side please try after some time');
Navigator.pop(context);*/
}
}
@override
void initState() {
super.initState();
_selectedDay = _focusedDay;
getTankers();
getCart();
}
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Color(0XFFFFFFFF),
appBar:AppSettings.SupplierAppBar('Place Order',context),
body: Padding(
padding: EdgeInsets.fromLTRB(12, 8, 12, 8),
child: ListView(
children: [
// Supplier Card
Card(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12)),
elevation: 2,
color: Colors.white,
child: Column(
children: [
Padding(
padding: EdgeInsets.fromLTRB(8, 8, 8, 8),
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Visibility(
visible: true,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Visibility(
visible: widget.details.supplier_name != '',
child: Text(
widget.details.supplier_name.toString(),
style: fontTextStyle(
16, Color(0XFF2D2E30), FontWeight.w600),
),
),
Row(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Image.asset(
'images/star.png',
fit: BoxFit.cover,
width:
16, // Match the diameter of the CircleAvatar
height: 16,
),
Visibility(
visible: true,
child: Text(
'4.2 (20K+ Ratings)',
style: fontTextStyle(9,
Color(0XFF515253), FontWeight.w400),
),
),
],
)
],
),
),
Visibility(
visible: true,
child: Text(
'Drinking | Bore Water',
style: fontTextStyle(
12, Color(0XFF4692FD), FontWeight.w500),
),
),
Visibility(
visible: true,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
widget.details.displayAddress +
' ' +
widget.details.distanceInMeters
.toString() +
' Km',
style: fontTextStyle(12, Color(0XFF515253),
FontWeight.w400)),
Image.asset(
'images/heart_outline.png',
fit: BoxFit.cover,
width:
16, // Match the diameter of the CircleAvatar
height: 16,
),
],
),
),
],
),
),
Padding(
padding: EdgeInsets.zero,
child: Container(
width: double
.infinity, // makes it expand within the Card's width
decoration: BoxDecoration(
borderRadius: BorderRadius.vertical(
bottom: Radius.circular(12),
), // match Card border
gradient: LinearGradient(
colors: [
Color(0xFFFFE8A3),
Color(0xFFFFF8DF),
Color(0xFFFFFFFF),
],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
),
),
padding: EdgeInsets.symmetric(vertical: 12),
alignment: Alignment.center,
child: Padding(
padding: EdgeInsets.fromLTRB(12, 0, 12, 0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Image.asset(
'images/ring.png',
fit: BoxFit.cover,
width:
16, // Match the diameter of the CircleAvatar
height: 16,
),
Text('Best water quality',
style: fontTextStyle(
10,
Color(0XFF2D2E30),
FontWeight.w400)),
],
),
Row(
children: [
Image.asset(
'images/ring.png',
fit: BoxFit.cover,
width:
16, // Match the diameter of the CircleAvatar
height: 16,
),
Text('Steel casing tankers',
style: fontTextStyle(
10,
Color(0XFF2D2E30),
FontWeight.w400)),
],
)
],
),
)),
),
],
)),
Card(
color: Colors.white,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12)),
child: Padding(
padding: const EdgeInsets.fromLTRB(12, 12, 12, 0),
child: Column(
children: [
// Header Row
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
DateFormat.yMMM().format(_focusedDay),
style: fontTextStyle(12, Color(0XFF646566), FontWeight.w600),
),
Image.asset(
'images/calender.png',
fit: BoxFit.cover,
width: 16, // Match the diameter of the CircleAvatar
height: 16,
),
],
),
Divider(
color: Colors.grey.shade300,
),
// Weekly Calendar
TableCalendar(
focusedDay: _focusedDay,
firstDay: DateTime.now(),
lastDay: DateTime.now().add(Duration(days: 15)),
calendarFormat: CalendarFormat.week,
startingDayOfWeek: StartingDayOfWeek.sunday,
availableCalendarFormats: const {
CalendarFormat.week: 'Week',
},
selectedDayPredicate: (day) {
return isSameDay(_selectedDay, day);
},
onDaySelected: (selectedDay, focusedDay) {
setState(() {
_selectedDay = selectedDay;
_focusedDay = focusedDay;
});
},
headerVisible: false,
calendarStyle: CalendarStyle(
todayDecoration: BoxDecoration(), // No box for today
todayTextStyle:fontTextStyle(12, Color(0XFF1D7AFC), FontWeight.w400),
selectedDecoration: BoxDecoration(
color: Color(0XFF1D7AFC),
shape: BoxShape.circle,
),
selectedTextStyle: fontTextStyle(12, Color(0XFFFFFFFF), FontWeight.w400),
weekendTextStyle: TextStyle(color: Colors.black),
defaultTextStyle: TextStyle(color: Colors.black),
outsideDaysVisible: false,
),
daysOfWeekStyle: DaysOfWeekStyle(
weekdayStyle: fontTextStyle(12, Color(0XFF343637), FontWeight.w400),
weekendStyle: fontTextStyle(12, Color(0XFF343637), FontWeight.w400),
),
),
],
),
)),
SizedBox(height: MediaQuery.of(context).size.height * .016),
Padding(padding: EdgeInsets.fromLTRB(6, 0, 6, 0),
child: Container(
width: double.infinity,
padding:
EdgeInsets.symmetric(horizontal: 8, vertical: 4),
decoration: BoxDecoration(
color: Color(0XFFFCF0E7),
borderRadius: BorderRadius.circular(6),
border: Border.all
(
color: Color(0XFFEFA168)),
),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.start,
children: [
Image.asset(
'images/warning.png',
fit: BoxFit.cover,
width:
22, // Match the diameter of the CircleAvatar
height: 22,
),
SizedBox(width: MediaQuery.of(context).size.width * .020),
Expanded(child: Text(
'The prices shown below are NOT inclusive of the transport charges. Transport charges are calculated based on the distance between the sourcing location and the delivery location.',
style: fontTextStyle(
10,
Color(0XFF444444),
FontWeight.w400),
),)
],
)
),),
SizedBox(height: MediaQuery.of(context).size.height * .016),
// Supply Locations
Text('SUPPLY LOCATIONS',
style: fontTextStyle(
12, Color(0XFF646566), FontWeight.w400),),
SizedBox(height: MediaQuery.of(context).size.height * .016),
Card(
elevation: 2,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
color: Color(0XFFFFFFFF),
child: Padding(
padding: const EdgeInsets.all(12.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
...[
{'name': 'Gandipet', 'type': 'Drinking water', 'km': '4.5 Km'},
{'name': 'Nizampet', 'type': 'Drinking water', 'km': '7.3 Km'},
{'name': 'Secunderabad', 'type': 'Bore water', 'km': '12.4 Km'},
].map((location) => Padding(
padding: const EdgeInsets.only(bottom: 12.0),
child: ListBody(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(location['name']!, style: fontTextStyle(12, Color(0XFF2D2E30), FontWeight.w500),),
Text(location['km']!,style: fontTextStyle(10, Color(0XFF2D2E30), FontWeight.w500),),
],
),
Text(location['type']!, style: fontTextStyle(10, Color(0XFF515253), FontWeight.w400),),
],
),
)),
],
),
),
),
// Tankers
SizedBox(height: MediaQuery.of(context).size.height * .016),
Text('TANKERS',
style: fontTextStyle(
12, Color(0XFF646566), FontWeight.w400),),
SizedBox(height: MediaQuery.of(context).size.height * .016),
GridView.builder(
padding: const EdgeInsets.all(0),
itemCount: tankersList.length,
shrinkWrap: true,
physics: NeverScrollableScrollPhysics(),
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
mainAxisSpacing: 12,
crossAxisSpacing: 12,
childAspectRatio: 1.6,
),
itemBuilder: (context, index) {
final tanker = tankersList[index];
final isAvailable = tankersList[index].status == 'Available';
final isEnabled =tankersList[index].enabled;
return Card(
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
elevation: 2,
color: Color(0XFFFFFFFF),
child: Padding(
padding: const EdgeInsets.all(12),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
tankersList[index].capacity+' Litres',
style: fontTextStyle(12, Color(0XFF2D2E30), FontWeight.w500),
),
Text(
'\u20B9 '+AppSettings.formDouble(tankersList[index].price),
style: fontTextStyle(12, Color(0XFF515253), FontWeight.w400),
),
],
),
Text(
tankersList[index].type_of_water,
style: fontTextStyle(12, Color(0XFF515253), FontWeight.w400),
),
Expanded(child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Container(
padding:
EdgeInsets.symmetric(horizontal: 8, vertical: 4),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(6),
border: Border.all(
color: isAvailable ? Color(0XFF098603) : Colors.grey,),
),
child: Text(
tankersList[index].status,
style: fontTextStyle(
12,
isAvailable ? Color(0XFF098603) : Colors.grey,
FontWeight.w500),
),
),
ElevatedButton(
onPressed: isEnabled
? () async {
AppSettings.preLoaderDialog(context);
final product = tankersList[index];
final productId = product.id;
final name = product.tanker_name;
// Convert price safely from string like "1,000"
final priceString = product.price;
final unitPrice = int.parse(priceString.replaceAll(',', ''));
// Get current quantity, or 0 if not added yet
int currentQuantity = localQuantities[productId] ?? 0;
int newQuantity = currentQuantity + 1;
// Calculate total price for UI use (unitPrice * quantity)
int totalPrice = unitPrice * newQuantity;
// Prepare payload to send unit price and quantity only
final payload = {
"productId": productId,
"name": name,
"quantity": newQuantity,
"price": unitPrice, // unit price ONLY
};
try {
final response = await http.post(
Uri.parse('${AppSettings.host}cart/${AppSettings.customerId}/add'),
headers: await AppSettings.buildRequestHeaders(),
body: jsonEncode(payload),
);
if (response.statusCode == 200 || response.statusCode == 201) {
setState(() {
Navigator.of(context, rootNavigator: true).pop();
// Update local tracking maps
localQuantities[productId] = newQuantity;
localTotalPrices[productId] = totalPrice;
// Update or add the cart list entry
int existingIndex = cart.indexWhere((item) => item['productId'] == productId);
if (existingIndex != -1) {
cart[existingIndex]['quantity'] = newQuantity;
cart[existingIndex]['price'] = unitPrice; // unit price
cart[existingIndex]['total'] = totalPrice; // total price for UI
} else {
cart.add({
"productId": productId,
"name": name,
"quantity": newQuantity,
"price": unitPrice,
"total": totalPrice,
});
}
});
// Optional: refresh cart from server if you want
await getCart();
} else {
Navigator.of(context, rootNavigator: true).pop();
print('Failed to add to cart: ${response.body}');
}
} catch (e) {
Navigator.of(context, rootNavigator: true).pop();
print('Error adding to cart: $e');
}
}
: null,
style: ElevatedButton.styleFrom(
backgroundColor: isEnabled ? primaryColor : Colors.grey[300],
foregroundColor: Colors.white,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
),
minimumSize: Size(48, 32),
padding: EdgeInsets.zero,
),
child: Text(
'Add',
style: fontTextStyle(12, Color(0XFFFFFFFF), FontWeight.w400),
),
),
],
))
],
),
),
);
},
),
],
),
),
bottomNavigationBar: cart.isNotEmpty
? Padding(
padding: EdgeInsets.all(0),
child: Container(
height: 56,
width: double.infinity,
decoration: BoxDecoration(
color: Color(0XFFF5CD47),
borderRadius: BorderRadius.only(
topLeft: Radius.circular(12),
topRight: Radius.circular(12),
),
),
padding: EdgeInsets.symmetric(horizontal: 24),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
"Order updated",
style: fontTextStyle(12, Color(0XFF232527), FontWeight.w500),
),
GestureDetector(
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => CartSummary(details: cart,supplierDetails: widget.details,)),
);
},
child: Row(
children: [
Text(
"View summary",
style: fontTextStyle(12, Color(0XFF232527), FontWeight.w500),
),
Image.asset(
'images/arrow-right.png',
fit: BoxFit.cover,
width:
20, // Match the diameter of the CircleAvatar
height: 20,
color: Color(0XFF343637),
),
],
),
),
],
),
),
)
: null,
);
}
}

@ -0,0 +1,687 @@
import 'dart:convert';
import 'package:auto_size_text/auto_size_text.dart';
import 'package:flutter/widgets.dart';
import 'package:http/http.dart' as http;
import 'package:flutter/material.dart';
import 'package:watermanagement/common/settings.dart';
import 'package:table_calendar/table_calendar.dart';
import 'package:intl/intl.dart';
import 'package:watermanagement/models/supplier_tankers_model.dart';
import 'package:watermanagement/supplier/cart_summary.dart';
class SupplierScreenFromSearch extends StatefulWidget {
var details;
SupplierScreenFromSearch({this.details});
@override
State<SupplierScreenFromSearch> createState() => _SupplierScreenFromSearchState();
}
class _SupplierScreenFromSearchState extends State<SupplierScreenFromSearch> {
DateTime _focusedDay = DateTime.now();
DateTime? _selectedDay;
bool isTankerDataLoading = false;
bool isCartDataLoading = false;
List<SupplierTankersModel> tankersList = [];
bool isSereverIssue = false;
List cart = [];
Map<String, int> localQuantities = {};
Map<String, int> localTotalPrices = {};
Future<void> getTankers() async {
isTankerDataLoading = true;
try {
var tankerResponse = await AppSettings.getAllTankers(widget.details.supplier_id);
setState(() {
tankersList =
((jsonDecode(tankerResponse)['data']) as List).map((dynamic model) {
return SupplierTankersModel.fromJson(model);
}).toList();
isTankerDataLoading = false;
});
} catch (e) {
setState(() {
isTankerDataLoading = false;
isSereverIssue = true;
});
/* AppSettings.longFailedToast('There is an issue at server side please try after some time');
Navigator.pop(context);*/
}
}
@override
void initState() {
super.initState();
_selectedDay = _focusedDay;
getTankers();
}
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Color(0XFFFFFFFF),
appBar:AppSettings.SupplierAppBar('',context),
body: Padding(
padding: EdgeInsets.fromLTRB(0, 8, 0, 8),
child: Column(
children: [
// Fixed Content
Expanded(
flex: 0,
child: Column(
children: [
Padding(padding: EdgeInsets.fromLTRB(12, 0, 12, 0),
child: Column(
children: [
Padding(
padding: EdgeInsets.fromLTRB(8, 8, 8, 8),
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Visibility(
visible: true,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Visibility(
visible: widget.details.supplier_name != '',
child: Text(
widget.details.supplier_name.toString(),
style: fontTextStyle(
16, Color(0XFF2D2E30), FontWeight.w600),
),
),
SizedBox(
width:
MediaQuery.of(context).size.width *
.012,
),
widget.details.isFavorite?Image.asset(
'images/heart_active.png',
fit: BoxFit.cover,
width:
16, // Match the diameter of the CircleAvatar
height: 16,
):
Image.asset(
'images/heart_outline.png',
fit: BoxFit.cover,
width:
16, // Match the diameter of the CircleAvatar
height: 16,
),
],
),
Container(
decoration: BoxDecoration(
shape: BoxShape.rectangle,
color: Color(0XFFFDF3D3),
border: Border.all(
width: 0,
color: Color(0XFFFDF3D3),
),
borderRadius:
BorderRadius.circular(
12,
)),
child: Padding(
padding: EdgeInsets.fromLTRB(10,2,10,2),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Image.asset(
'images/star.png',
fit: BoxFit.cover,
width:
16, // Match the diameter of the CircleAvatar
height: 16,
),
Visibility(
visible: true,
child: Text(
'4.2',
style: fontTextStyle(11,
Color(0XFF232527), FontWeight.w600),
),
),
],
),
),
)
],
),
),
Visibility(
visible: true,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
widget.details.displayAddress +
' ' +
widget.details.distanceInMeters
.toString() +
' Km',
style: fontTextStyle(12, Color(0XFF515253),
FontWeight.w400)),
],
),
),
SizedBox(height: MediaQuery.of(context).size.height * .004,),
Row(
children: [
Container(
padding: EdgeInsets.symmetric(horizontal: 4, vertical: 2),
decoration: BoxDecoration(
color: Color(0xFF8877DD),
borderRadius: BorderRadius.circular(4),
border: Border.all(
width: 1,
color:Color(0xFF8877DD),
),
),
child: AutoSizeText(
capitalizeFirst('Bore Water'),
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: fontTextStyle(12, Color(0xFFFFFFFF), FontWeight.w400),
),
),
SizedBox(width: MediaQuery.of(context).size.width * .016,),
Container(
padding: EdgeInsets.symmetric(horizontal: 4, vertical: 2),
decoration: BoxDecoration(
color: Color(0xFFCA86B0),
borderRadius: BorderRadius.circular(4),
border: Border.all(
width: 1,
color:Color(0xFFCA86B0),
),
),
child: AutoSizeText(
capitalizeFirst('Drinking Water'),
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: fontTextStyle(12, Color(0xFFFFFFFF), FontWeight.w400),
),
),
],
),
SizedBox(height: MediaQuery.of(context).size.height * .016,),
Row(
children: [
Container(
padding: EdgeInsets.symmetric(horizontal: 4, vertical: 2),
decoration: BoxDecoration(
color: Color(0xFFFFFFFF),
borderRadius: BorderRadius.circular(12),
border: Border.all(
width: 1,
color:Color(0xFF1D7AFC),
),
),
child: Row(
children: [
Image.asset(
'images/ring.png',
fit: BoxFit.cover,
width:
16, // Match the diameter of the CircleAvatar
height: 16,
),
AutoSizeText(
capitalizeFirst('Best water quality'),
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: fontTextStyle(10, Color(0xFF2D2E30), FontWeight.w400),
),
],
),
),
SizedBox(width: MediaQuery.of(context).size.width * .016,),
Container(
padding: EdgeInsets.symmetric(horizontal: 4, vertical: 2),
decoration: BoxDecoration(
color: Color(0xFFFFFFFF),
borderRadius: BorderRadius.circular(12),
border: Border.all(
width: 1,
color:Color(0xFF1D7AFC),
),
),
child: Row(
children: [
Image.asset(
'images/ring.png',
fit: BoxFit.cover,
width:
16, // Match the diameter of the CircleAvatar
height: 16,
),
AutoSizeText(
capitalizeFirst('Steel casing tankers'),
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: fontTextStyle(10, Color(0xFF2D2E30), FontWeight.w400),
),
],
),
),
],
),
],
),
),
],
),
),
Padding(
padding: EdgeInsets.zero,
child: Container(
width: double
.infinity, // makes it expand within the Card's width
decoration: BoxDecoration(
borderRadius: BorderRadius.vertical(
bottom: Radius.circular(0),
), // match Card border
gradient: LinearGradient(
colors: [
Color(0xFFFFE8A3),
Color(0xFFFFF8DF),
Color(0xFFFFFFFF),
],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
),
),
padding: EdgeInsets.symmetric(vertical: 12),
alignment: Alignment.center,
child: Padding(
padding: EdgeInsets.fromLTRB(12, 0, 12, 0),
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('Start regular deliveries?',
style: fontTextStyle(
16,
Color(0XFF2D2E30),
FontWeight.w600)),
SizedBox(height: MediaQuery.of(context).size.height * .012),
Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Expanded(
flex: 3,
child: Text(
'Want to get water regularly from this supplier? Send a plan request.',
style: fontTextStyle(10, Color(0XFF515253), FontWeight.w400),
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
),
SizedBox(width: MediaQuery.of(context).size.width * .036),
Expanded(
flex: 1,
child: GestureDetector(
onTap: () {},
child: Container(
decoration: BoxDecoration(
color: Color(0XFFFFFFFF),
border: Border.all(width: 1, color: Color(0XFF1D7AFC)),
borderRadius: BorderRadius.circular(24),
),
alignment: Alignment.center,
padding: EdgeInsets.symmetric(vertical: 10, horizontal: 8),
child: Text(
'Request',
style: fontTextStyle(12, Color(0XFF1D7AFC), FontWeight.w600),
),
),
),
),
],
)
],
)
,
)),
),
SizedBox(height: MediaQuery.of(context).size.height * .016),
],
),
),
// Scrollable
Expanded(
child: SingleChildScrollView(
padding: EdgeInsets.fromLTRB(0, 0, 0, 0),
child: Column(
children: [
Padding(padding: EdgeInsets.fromLTRB(18, 0, 18, 0),
child: Container(
width: double.infinity,
padding:
EdgeInsets.symmetric(horizontal: 8, vertical: 4),
decoration: BoxDecoration(
color: Color(0XFFFCF0E7),
borderRadius: BorderRadius.circular(6),
border: Border.all
(
color: Color(0XFFEFA168)),
),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.start,
children: [
Image.asset(
'images/warning.png',
fit: BoxFit.cover,
width:
22, // Match the diameter of the CircleAvatar
height: 22,
),
SizedBox(width: MediaQuery.of(context).size.width * .020),
Expanded(child: Text(
'The prices shown below are NOT inclusive of the transport charges. Transport charges are calculated based on the distance between the sourcing location and the delivery location.',
style: fontTextStyle(
10,
Color(0XFF444444),
FontWeight.w400),
),)
],
)
),),
SizedBox(height: MediaQuery.of(context).size.height * .016),
Padding(
padding: EdgeInsets.fromLTRB(12, 0, 12, 0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'SOURCE LOCATIONS',
style: fontTextStyle(12, Color(0XFF646566), FontWeight.w400),
),
SizedBox(height: MediaQuery.of(context).size.height * .016),
Card(
elevation: 2,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
color: Color(0XFFFFFFFF),
child: Padding(
padding: const EdgeInsets.all(12.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
...[
{'name': 'Gandipet', 'type': 'Drinking water', 'km': '4.5 Km'},
{'name': 'Nizampet', 'type': 'Drinking water', 'km': '7.3 Km'},
{'name': 'Secunderabad', 'type': 'Bore water', 'km': '12.4 Km'},
].map((location) => Padding(
padding: const EdgeInsets.only(bottom: 12.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(location['name']!,
style: fontTextStyle(12, Color(0XFF2D2E30), FontWeight.w500)),
Text(location['km']!,
style: fontTextStyle(10, Color(0XFF2D2E30), FontWeight.w500)),
],
),
Text(location['type']!,
style: fontTextStyle(10, Color(0XFF515253), FontWeight.w400)),
],
),
)),
],
),
),
),
// Tankers
SizedBox(height: MediaQuery.of(context).size.height * .016),
Text(
'TANKERS',
style: fontTextStyle(12, Color(0XFF646566), FontWeight.w400),
),
SizedBox(height: MediaQuery.of(context).size.height * .016),
ListView.builder(
itemCount: tankersList.length,
shrinkWrap: true,
physics: NeverScrollableScrollPhysics(),
itemBuilder: (context, index) {
final tanker = tankersList[index];
final isAvailable = tanker.status == 'Available';
final isEnabled = tanker.enabled;
return Card(
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
elevation: 2,
color: Color(0XFFFFFFFF),
child: Padding(
padding: const EdgeInsets.all(12),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
'${tanker.capacity} Litres',
style: fontTextStyle(14, Color(0XFF2D2E30), FontWeight.w500),
),
Text(
'\u20B9 ${AppSettings.formDouble(tanker.price)}',
style: fontTextStyle(10, Color(0XFF515253), FontWeight.w400),
),
],
),
SizedBox(height: MediaQuery.of(context).size.height * .004,),
Container(
padding: EdgeInsets.symmetric(horizontal: 4, vertical: 2),
decoration: BoxDecoration(
color: tanker.type_of_water.toString().toLowerCase()=='bore water'?Color(0xFF8877DD): Color(0xFFCA86B0),
borderRadius: BorderRadius.circular(4),
border: Border.all(
width: 1,
color:tanker.type_of_water.toString().toLowerCase()=='bore water'?Color(0xFF8877DD): Color(0xFFCA86B0),
),
),
child: AutoSizeText(
capitalizeFirst(tanker.type_of_water),
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: fontTextStyle(12, Color(0xFFFFFFFF), FontWeight.w400),
),
),
SizedBox(height: MediaQuery.of(context).size.height * .008,),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Container(
padding: EdgeInsets.symmetric(horizontal: 8, vertical: 4),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(6),
border: Border.all(
color: isAvailable ? Color(0XFF098603) : Colors.grey,
),
),
child: Text(
tanker.status,
style: fontTextStyle(
12,
isAvailable ? Color(0XFF098603) : Colors.grey,
FontWeight.w500,
),
),
),
/* ElevatedButton(
onPressed: isEnabled
? () async {
AppSettings.preLoaderDialog(context);
final productId = tanker.id;
final name = tanker.tanker_name;
final priceString = tanker.price;
final unitPrice = int.parse(priceString.replaceAll(',', ''));
int currentQuantity = localQuantities[productId] ?? 0;
int newQuantity = currentQuantity + 1;
int totalPrice = unitPrice * newQuantity;
final payload = {
"productId": productId,
"name": name,
"quantity": newQuantity,
"price": unitPrice,
};
try {
final response = await http.post(
Uri.parse('${AppSettings.host}cart/${AppSettings.customerId}/add'),
headers: await AppSettings.buildRequestHeaders(),
body: jsonEncode(payload),
);
Navigator.of(context, rootNavigator: true).pop();
if (response.statusCode == 200 || response.statusCode == 201) {
setState(() {
localQuantities[productId] = newQuantity;
localTotalPrices[productId] = totalPrice;
int existingIndex = cart.indexWhere((item) => item['productId'] == productId);
if (existingIndex != -1) {
cart[existingIndex]['quantity'] = newQuantity;
cart[existingIndex]['price'] = unitPrice;
cart[existingIndex]['total'] = totalPrice;
} else {
cart.add({
"productId": productId,
"name": name,
"quantity": newQuantity,
"price": unitPrice,
"total": totalPrice,
});
}
});
} else {
print('Failed to add to cart: ${response.body}');
}
} catch (e) {
Navigator.of(context, rootNavigator: true).pop();
print('Error adding to cart: $e');
}
}
: null,
style: ElevatedButton.styleFrom(
backgroundColor: isEnabled ? primaryColor : Colors.grey[300],
foregroundColor: Colors.white,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
),
minimumSize: Size(64, 36),
padding: EdgeInsets.zero,
),
child: Text(
'Add',
style: fontTextStyle(12, Colors.white, FontWeight.w400),
),
),*/
],
)
],
),
),
);
},
),
],
),
)
],
),
),
),
],
)
),
bottomNavigationBar: cart.isNotEmpty
? Padding(
padding: EdgeInsets.all(0),
child: Container(
height: 56,
width: double.infinity,
decoration: BoxDecoration(
color: Color(0XFFF5CD47),
borderRadius: BorderRadius.only(
topLeft: Radius.circular(12),
topRight: Radius.circular(12),
),
),
padding: EdgeInsets.symmetric(horizontal: 24),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
"Order updated",
style: fontTextStyle(12, Color(0XFF232527), FontWeight.w500),
),
GestureDetector(
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => CartSummary(details: cart,supplierDetails: widget.details,)),
);
},
child: Row(
children: [
Text(
"View summary",
style: fontTextStyle(12, Color(0XFF232527), FontWeight.w500),
),
Image.asset(
'images/arrow-right.png',
fit: BoxFit.cover,
width:
20, // Match the diameter of the CircleAvatar
height: 20,
color: Color(0XFF343637),
),
],
),
),
],
),
),
)
: null,
);
}
}

@ -0,0 +1,83 @@
import 'package:flutter/material.dart';
import 'package:geolocator/geolocator.dart';
import 'package:intl/intl.dart';
import '../common/settings.dart';
class SupplierOrdersModel {
String typeofwater='';
String supplierName = '';
String supplierId = '';
String date = '';
String dbId = '';
String time = '';
String acceptedTime = '';
String address='';
String displayAddress = '';
String capacity = '';
String quantity = '';
String quotedAmount='';
String bookingCharges='';
String advancePaid='';
String status='';
double lat=0;
double lng=0;
double distanceInMeters=0;
Color cardColor=Colors.white;
SupplierOrdersModel();
factory SupplierOrdersModel.fromJson(Map<String, dynamic> json){
SupplierOrdersModel rtvm = new SupplierOrdersModel();
rtvm.status = json['status'] ?? '';
rtvm.typeofwater = json['type_of_water'] ?? '';
rtvm.date = json['date'] ?? '';
rtvm.time = json['time'] ?? '';
rtvm.dbId=json['_id']?? '';
rtvm.capacity = json['capacity'] ?? '';
rtvm.quantity = json['quantity'] ?? '';
final suppliers = json['requested_suppliers'] as List?;
if (suppliers != null && suppliers.isNotEmpty) {
final supplier = suppliers[0];
rtvm.quotedAmount = supplier['quoted_amount']?.toString() ?? '0.00';
rtvm.bookingCharges = supplier['delivery_charges']?.toString() ?? '0.00';
rtvm.advancePaid = supplier['advance_paid']?.toString() ?? '0.00';
rtvm.supplierName = supplier['supplier_details']?['supplierName'] ?? '';
rtvm.supplierId = supplier['supplier_details']?['supplierId'] ?? '';
rtvm.address = supplier['supplier_details']?['address'] ?? '';
rtvm.acceptedTime = supplier['time'] ?? '';
rtvm.lat =supplier['supplier_details']?['latitude'] ?? '';
rtvm.lng = supplier['supplier_details']?['longitude'] ?? '';
} else {
rtvm.quotedAmount = '';
rtvm.bookingCharges='';
rtvm.supplierName = '';
rtvm.address = '';
rtvm.acceptedTime = '';
rtvm.supplierId='';
rtvm.lat =0.0;
rtvm.lng =0.0;
}
List<String> parts = rtvm.address.split(',');
if (parts.length > 4) {
rtvm.displayAddress = parts[2].trim();
} else {
rtvm.displayAddress = rtvm.address; // fallback
}
rtvm.distanceInMeters = double.parse((Geolocator.distanceBetween(
rtvm.lat,
rtvm.lng,
AppSettings.userLatitude,
AppSettings.userLongitude
) / 1000).toStringAsFixed(2));
return rtvm;
}
}

@ -0,0 +1,52 @@
import 'package:flutter/material.dart';
import 'package:geolocator/geolocator.dart';
import 'package:watermanagement/common/settings.dart';
class SuppliersModel {
String supplier_name = '';
String status='';
String supplier_address='';
String supplier_phone_number='';
String supplier_id='';
Color text_color=Colors.black;
double lat=0;
double lng=0;
String distrubance_price='';
String amount_difference='';
double distanceInMeters=0;
String displayAddress='';
bool isFavorite=false;
bool isRequetsedBooking=false;
String? matchedPrice;
SuppliersModel();
factory SuppliersModel.fromJson(Map<String, dynamic> json){
SuppliersModel rtvm = new SuppliersModel();
rtvm.supplier_name = json['supplier']['suppliername'] ?? '';
rtvm.status = json['supplier']['status'] ?? '';
rtvm.supplier_address = json['supplier']['profile']['office_address'] ?? '';
rtvm.supplier_phone_number =json['supplier']['phone'] ?? '';
rtvm.supplier_id = json['supplier']['supplierId'] ?? '';
rtvm.isFavorite = json['isFavorite'] ?? false;
rtvm.isRequetsedBooking = json['requestedBooking']['status'] ?? false;
rtvm.lat = json['supplier']['latitude'] ?? 0;
rtvm.lng = json['supplier']['longitude'] ?? 0;
List<String> parts = rtvm.supplier_address.split(',');
rtvm.displayAddress = parts[2].trim();
rtvm.distanceInMeters = double.parse((Geolocator.distanceBetween(
rtvm.lat,
rtvm.lng,
AppSettings.userLatitude,
AppSettings.userLongitude
) / 1000).toStringAsFixed(2));
return rtvm;
}
}

@ -0,0 +1,413 @@
import 'package:flutter/material.dart';
import '../../common/settings.dart';
class AdvancedDateRangePicker extends StatefulWidget {
final DateTimeRange? initialRange;
const AdvancedDateRangePicker({super.key, this.initialRange});
@override
State<AdvancedDateRangePicker> createState() => _AdvancedDateRangePickerState();
}
class _AdvancedDateRangePickerState extends State<AdvancedDateRangePicker> {
late DateTime _currentMonth;
DateTime? _start;
DateTime? _end;
late PageController _pageController;
int _initialPage = 500; // large buffer for infinite scroll
@override
void initState() {
super.initState();
_initialPage = 500;
_pageController = PageController(initialPage: _initialPage);
_currentMonth = DateTime(DateTime.now().year, DateTime.now().month, 1);
if (widget.initialRange != null) {
_start = widget.initialRange!.start;
_end = widget.initialRange!.end;
}
}
@override
Widget build(BuildContext context) {
return Dialog(
backgroundColor: Colors.white, // White background
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
child: Container(
padding: const EdgeInsets.all(5),
width: 420,
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
_buildHeader(),
const SizedBox(height: 12),
_buildWeekDays(),
SizedBox(
height: 270,
child: PageView.builder(
controller: _pageController,
onPageChanged: (pageIndex) { //to update the current month state when user swipes
setState(() {
int diff = pageIndex - _initialPage;
_currentMonth = DateTime(
DateTime.now().year,
DateTime.now().month + diff,
1,
);
});
},
itemBuilder: (context, index) { //to build the calendar for each month
int diff = index - _initialPage;
DateTime monthToShow = DateTime(
DateTime.now().year,
DateTime.now().month + diff,
1,
);
return _buildCalendarGrid(monthToShow);
},
),
),
const SizedBox(height: 12),
_buildSelectionText(),
const SizedBox(height: 16),
_buildFooter()
],
),
),
);
}
// -----------------------------------------------------------
// 🔵 HEADER (Month Year + Arrows)
// -----------------------------------------------------------
Widget _buildHeader() {
return Column(
children: [
// Title
const Padding(
padding: EdgeInsets.symmetric(vertical: 8.0),
child: Text(
"Select a Date Range",
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
color: Colors.black,
),
),
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
IconButton(
icon: const Icon(Icons.arrow_left, color: Colors.black),
onPressed: () {
_pageController.previousPage(
duration: const Duration(milliseconds: 250),
curve: Curves.easeOut,
);
},
),
GestureDetector(
onTap: () => _showYearPicker(context),
child: Text(
"${_monthName(_currentMonth.month)} ${_currentMonth.year}",
style: fontTextStyle(18, Colors.black, FontWeight.w400),
),
),
IconButton(
icon: const Icon(Icons.arrow_right, color: Colors.black),
onPressed: () {
_pageController.nextPage(
duration: const Duration(milliseconds: 250),
curve: Curves.easeOut,
);
},
),
],
)
],
);
}
// -----------------------------------------------------------
// 🔵 WEEK DAY ROW
// -----------------------------------------------------------
Widget _buildWeekDays() {
const days = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
return Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: days
.map((d) => Expanded(
child: Center(
child: Text(
d,
style: fontTextStyle(14, Colors.black, FontWeight.w600),
),
),
))
.toList(),
);
}
// -----------------------------------------------------------
// 🔵 BUILD CALENDAR GRID
// -----------------------------------------------------------
Widget _buildCalendarGrid(DateTime month) {
List<Widget> rows = [];
DateTime firstDay = DateTime(month.year, month.month, 1);
int startingWeekday = firstDay.weekday % 7; // Sunday=0
int daysInMonth = DateTime(month.year, month.month + 1, 0).day;
List<Widget> dayCells = [];
// Empty cells before month starts
for (int i = 0; i < startingWeekday; i++) {
dayCells.add(const Expanded(child: SizedBox()));
}
// Month days
for (int d = 1; d <= daysInMonth; d++) {
DateTime date = DateTime(month.year, month.month, d);
bool isToday = _isSame(date, DateTime.now()); // Highlight today
bool isSelectedStart = _isSame(date, _start);
bool isSelectedEnd = _isSame(date, _end);
bool inRange =
_start != null && _end != null && date.isAfter(_start!) && date.isBefore(_end!);
dayCells.add(
Expanded(
child: GestureDetector(
onTap: () => _onDateTap(date),
child: Container(
margin: const EdgeInsets.all(4),
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(
color: isSelectedStart || isSelectedEnd
? Colors.black
: inRange
? Colors.black12
: Colors.transparent,
shape: BoxShape.circle,
border: isToday && !isSelectedStart && !isSelectedEnd
? Border.all(color: Colors.blueAccent, width: 2) // Highlight today
: null,
),
child: Center(
child: Text(
"$d",
style: TextStyle(
color: isSelectedStart || isSelectedEnd
? Colors.white
: Colors.black,
fontWeight: isToday ? FontWeight.bold : FontWeight.normal,
),
),
),
),
),
),
);
// Create a row after each week
if ((dayCells.length) % 7 == 0) {
rows.add(Row(children: dayCells));
dayCells = [];
}
}
// Last row fill remaining empty cells
if (dayCells.isNotEmpty) {
while (dayCells.length < 7) {
dayCells.add(const Expanded(child: SizedBox()));
}
rows.add(Row(children: dayCells));
}
return Column(children: rows);
}
// -----------------------------------------------------------
// 🔵 DATE TAP LOGIC
// -----------------------------------------------------------
void _onDateTap(DateTime date) {
setState(() {
if (_start == null || (_start != null && _end != null)) {
_start = date;
_end = null;
} else if (date.isBefore(_start!)) {
_end = _start;
_start = date;
} else {
_end = date;
}
});
}
// -----------------------------------------------------------
// 🔵 SELECTED TEXT
// -----------------------------------------------------------
// Widget _buildSelectionText() {
// return Text(
// _start == null
// ? "Choose Start Date"
// : _end == null
// ? "Choose End Date"
// : "Selected: ${_fmt(_start!)}${_fmt(_end!)}",
// style: fontTextStyle(14, Colors.blueAccent, FontWeight.w600),
// );
// }
Widget _buildSelectionText() {
if (_start == null || _end == null) {
return const SizedBox(); // Show nothing
}
return Text(
"Selected: ${_fmt(_start!)}${_fmt(_end!)}",
style: fontTextStyle(14, Colors.blueAccent, FontWeight.w600),
);
}
// -----------------------------------------------------------
// 🔵 FOOTER BUTTONS
// -----------------------------------------------------------
Widget _buildFooter() {
return Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
TextButton(
child: Text("Cancel", style: fontTextStyle(14, Colors.black, FontWeight.w600)),
onPressed: () => Navigator.pop(context),
),
ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: Colors.black,
),
onPressed: (_start != null && _end != null)
? () => Navigator.pop(
context,
DateTimeRange(start: _start!, end: _end!),
)
: null,
child: Text("Apply", style: fontTextStyle(14, Colors.white, FontWeight.bold)),
),
],
);
}
// -----------------------------------------------------------
// 🔧 HELPERS
// -----------------------------------------------------------
bool _isSame(DateTime? a, DateTime? b) {
if (a == null || b == null) return false;
return a.year == b.year && a.month == b.month && a.day == b.day;
}
String _fmt(DateTime d) => "${d.year}-${d.month}-${d.day}";
String _monthName(int m) {
const names = [
"January","February","March","April","May","June",
"July","August","September","October","November","December"
];
return names[m - 1];
}
// -----------------------------------------------------------
// 🔵 YEAR PICKER SHEET
// -----------------------------------------------------------
void _showYearPicker(BuildContext context) {
int currentYear = DateTime.now().year;
int startYear = currentYear - 25;
int endYear = currentYear + 25;
int selectedYear = _currentMonth.year;
ScrollController scrollController = ScrollController(
initialScrollOffset: (selectedYear - startYear) * 48.0, // 56 is approx ListTile height
);
showDialog(
context: context,
builder: (_) => Dialog(
backgroundColor: Colors.white,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
child: SizedBox(
height: 300,
child: ListView.builder(
controller: scrollController,
itemCount: endYear - startYear + 1,
itemBuilder: (context, index) {
int y = startYear + index;
bool isSelected = y == selectedYear;
return Center(
child: GestureDetector(
onTap: () {
setState(() {
_currentMonth = DateTime(y, _currentMonth.month, 1);
});
Navigator.pop(context);
},
child: Container(
margin: const EdgeInsets.symmetric(vertical: 4),
padding:
const EdgeInsets.symmetric(horizontal: 20, vertical: 10),
decoration: BoxDecoration(
color: isSelected ? Colors.black : Colors.white,
borderRadius: BorderRadius.circular(12),
border: Border.all(color: Colors.black12),
),
child: Text(
"$y",
style: fontTextStyle(
16,
isSelected ? Colors.white : Colors.black,
FontWeight.bold,
),
),
),
),
);
},
),
),
),
);
}
//To clear the selected dates inside your custom calendar grid
void _clearCalendarSelection() {
setState(() {
_start = null;
_end = null;
});
}
}

@ -0,0 +1,864 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:watermanagement/supplier/todaysprice/custom_calendar.dart';
import '../../common/settings.dart';
// Contains date picker + filtered graph
class GraphScreen extends StatefulWidget {
final int selectedType;
const GraphScreen({super.key, required this.selectedType});
@override
State<GraphScreen> createState() => _GraphScreenState();
}
class _GraphScreenState extends State<GraphScreen> {
List<Map<String, dynamic>> filteredDrinking = [];
List<Map<String, dynamic>> filteredBore = [];
DateTimeRange? selectedRange;
@override
void initState() {
super.initState();
_loadLastWeek(); // Load last week by default
}
void _loadLastWeek() {
DateTime today = DateTime.now();
DateTime lastWeek = today.subtract(const Duration(days: 7));
debugPrint('$lastWeek');
// Update selectedRange so the card shows last week
selectedRange = DateTimeRange(start: lastWeek, end: today);
_filterData(lastWeek, today);
setState(() {}); // trigger rebuild
}
void _pickDateRange() async {
final result = await showDialog<DateTimeRange>(
context: context,
builder: (context) => AdvancedDateRangePicker(
initialRange: selectedRange,
),
);
if (result != null) {
selectedRange = result;
_filterData(result.start, result.end);
setState(() {
selectedRange = result; // store this for showing in card above graph
});
}
}
void _filterData(DateTime start, DateTime end) {
setState(() {
filteredDrinking = weeklyPriceData["Drinking Water"]!
.where((d) {
DateTime date = DateTime.parse(d["date"]);
return !date.isBefore(start) && !date.isAfter(end);
}).toList();
filteredBore = weeklyPriceData["bore Water"]!
.where((d) {
DateTime date = DateTime.parse(d["date"]);
return !date.isBefore(start) && !date.isAfter(end);
}).toList();
});
}
void _openDateMenu() {
showMenu(
context: context,
color: Colors.white,
elevation: 6, // smooth shadow
position: const RelativeRect.fromLTRB(1000, 100, 10, 0), // position near icon
items: [
PopupMenuItem(
value: "week",
child: Text("Last Week",
style: fontTextStyle(14, Colors.black, FontWeight.w500),
),
),
PopupMenuItem(
value: "month",
child: Text("Last Month",
style: fontTextStyle(14, Colors.black, FontWeight.w500),
),
),
PopupMenuItem(
value: "custom",
child: Text("Custom Dates",
style: fontTextStyle(14, Colors.black, FontWeight.w500),
),
),
],
).then((value) {
if (value == null) return;
if (value == "week") {
DateTime today = DateTime.now();
DateTime lastWeek = today.subtract(const Duration(days: 7));
setState(() {
selectedRange = DateTimeRange(start: lastWeek, end: today);
_filterData(lastWeek, today);
});
}
else if (value == "month") {
DateTime today = DateTime.now();
DateTime lastMonth = DateTime(today.year, today.month - 1, today.day);
setState(() {
selectedRange = DateTimeRange(start: lastMonth, end: today);
_filterData(lastMonth, today);
});
}
else if (value == "custom") {
_pickDateRange();
}
});
}
// For showing date in card above graph
String _formattedDateRange() {
if (selectedRange == null) return "Select Date";
final start = selectedRange!.start;
final end = selectedRange!.end;
const months = [
"", "Jan", "Feb", "Mar", "Apr", "May", "Jun",
"Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
];
if (start.month == end.month) {
return "${start.day} ${end.day} ${months[start.month]}";
} else {
return "${start.day} ${months[start.month]} ${end.day} ${months[end.month]}";
}
}
String _formattedAvgPrice() {
List<Map<String, dynamic>> activeList =
widget.selectedType == 0 ? filteredDrinking : filteredBore;
debugPrint('$activeList');
if (activeList.isEmpty) return "-";
double total = 0;
for (var item in activeList) {
total += (item["price"] as num).toDouble();
}
double avg = total / activeList.length;
return "${avg.toStringAsFixed(2)}";
}
@override
Widget build(BuildContext context) {
return Column(
children: [
// 🔥 TOP ROW (LEFT: BOX, RIGHT: CALENDAR)
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
// LEFT BOX
Padding(
padding: const EdgeInsets.only(top: 14, left: 10),
child: Container(
height: 100,
width: 180,
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(12),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.15),
blurRadius: 8,
spreadRadius: 2,
offset: const Offset(2, 4),
),
],
),
child: AnimatedSwitcher(
duration: const Duration(milliseconds: 350),
transitionBuilder: (child, animation) {
return FadeTransition(
opacity: animation,
child: SlideTransition(
position: Tween<Offset>(
begin: const Offset(0.2, 0), // Soft slide from right
end: Offset.zero,
).animate(animation),
child: child,
),
);
},
child: Column(
key: ValueKey(widget.selectedType), // IMPORTANT 🔑
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
widget.selectedType == 0 ? "Drinking Water" : "Bore Water",
style: fontTextStyle(14, Colors.black, FontWeight.w600),
),
const SizedBox(height: 4),
Text(
_formattedDateRange(),
style: fontTextStyle(14, Colors.grey.shade700, FontWeight.w500),
),
const SizedBox(height: 4),
RichText(
text: TextSpan(
children: [
TextSpan(
text: "${_formattedAvgPrice()} ",
style: fontTextStyle(20, Colors.black, FontWeight.bold),
),
TextSpan(
text: "/5000 L",
style: fontTextStyle(14, Colors.grey.shade600, FontWeight.w400),
),
],
),
),
],
),
),
),
),
// RIGHT CALENDAR BUTTON
Padding(
padding: const EdgeInsets.only(top: 4, right: 8),
child: InkWell(
onTap: () => _openDateMenu(),
borderRadius: BorderRadius.circular(10),
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 4, vertical: 8),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(10),
border: Border.all(color: Colors.grey.shade300),
boxShadow: const [
BoxShadow(
color: Colors.black12,
blurRadius: 4,
offset: Offset(0, 2),
),
],
),
child: Row(
children: const [
Icon(Icons.calendar_month, size: 28, color: Colors.blueAccent),
Icon(Icons.arrow_drop_down, size: 28, color: Colors.blueAccent),
],
),
),
),
),
],
),
const SizedBox(height: 5),
// Show graph only if data is available
if (filteredDrinking.isNotEmpty || filteredBore.isNotEmpty)
Expanded(
child: SingleChildScrollView(
scrollDirection: Axis.horizontal,
padding: const EdgeInsets.symmetric(horizontal: 10),
child: DoublePriceBarGraph(
drinkingData: filteredDrinking,
boreData: filteredBore,
),
),
)
else
Padding(
padding: EdgeInsets.all(30),
child: Text("No data available for the selected dates",
style: fontTextStyle(13, Colors.black87, FontWeight.w600),
),
),
],
);
}
}
Widget buildLegend() {
return Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
// Drinking Water Legend
Row(
children: [
Container(
width: 14,
height: 14,
decoration: BoxDecoration(
color: Colors.blueAccent,
borderRadius: BorderRadius.circular(4),
),
),
const SizedBox(width: 6),
Text(
"Drinking Water",
style: fontTextStyle(12, Colors.black87, FontWeight.w600),
),
],
),
const SizedBox(width: 20),
// Bore Water Legend
Row(
children: [
Container(
width: 14,
height: 14,
decoration: BoxDecoration(
color: Colors.orangeAccent,
borderRadius: BorderRadius.circular(4),
),
),
const SizedBox(width: 6),
Text(
"Bore Water",
style: fontTextStyle(12, Colors.black87, FontWeight.w600),
),
],
),
],
);
}
class DoublePriceBarGraph extends StatelessWidget {
final List<Map<String, dynamic>> drinkingData;
final List<Map<String, dynamic>> boreData;
const DoublePriceBarGraph({
super.key,
required this.drinkingData,
required this.boreData,
});
@override
Widget build(BuildContext context) {
final drinkMap = {for (var d in drinkingData) d["date"]: d};
final boreMap = {for (var b in boreData) b["date"]: b};
final allDates = [...drinkMap.keys, ...boreMap.keys].toSet().toList()
..sort((a, b) =>
DateTime.parse(a).compareTo(DateTime.parse(b))); // FIXED
return Row(
crossAxisAlignment: CrossAxisAlignment.end,
children: allDates.map((date) {
final d = drinkMap[date];
final b = boreMap[date];
double drinkPrice = d?["price"]?.toDouble() ?? 0.0;
double borePrice = b?["price"]?.toDouble() ?? 0.0;
String day = DateTime.parse(date).day.toString();
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 5),
child: Column(
mainAxisAlignment: MainAxisAlignment.end,
children: [
Row(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
// Drinking
_buildBar(drinkPrice, Colors.blueAccent),
const SizedBox(width: 0),
// Bore
_buildBar(borePrice, Colors.orangeAccent)
],
),
const SizedBox(height: 6),
Text(day, style: fontTextStyle(12, Colors.black87, FontWeight.w600)),
],
),
);
}).toList(),
);
}
Widget _buildBar(double price, Color color) {
return Column(
children: [
Text(
"${price.toInt()}",
style: fontTextStyle(9, color, FontWeight.bold),
),
Container(
width: 20,
height: price / 15, // Adjust scaling if bars too short
decoration: BoxDecoration(
color: color,
borderRadius: BorderRadius.circular(5),
),
),
],
);
}
}
final Map<String, List<Map<String, dynamic>>> weeklyPriceData = {
"Drinking Water": [
{
"date": "2025-11-01",
"price": 1570,
"minPrice": 1550,
"maxPrice": 1650,
"avgPrice": 1600
},
{
"date": "2025-11-02",
"price": 1590,
"minPrice": 1560,
"maxPrice": 1660,
"avgPrice": 1610
},
{
"date": "2025-11-03",
"price": 1550,
"minPrice": 1520,
"maxPrice": 1600,
"avgPrice": 1560
},
{
"date": "2025-11-04",
"price": 1610,
"minPrice": 1580,
"maxPrice": 1670,
"avgPrice": 1620
},
{
"date": "2025-11-05",
"price": 1630,
"minPrice": 1600,
"maxPrice": 1700,
"avgPrice": 1650
},
{
"date": "2025-11-06",
"price": 1580,
"minPrice": 1550,
"maxPrice": 1650,
"avgPrice": 1600
},
{
"date": "2025-11-07",
"price": 1600,
"minPrice": 1580,
"maxPrice": 1670,
"avgPrice": 1620
},
{
"date": "2025-11-08",
"price": 1640,
"minPrice": 1600,
"maxPrice": 1700,
"avgPrice": 1650
},
{
"date": "2025-11-09",
"price": 1620,
"minPrice": 1590,
"maxPrice": 1680,
"avgPrice": 1630
},
{
"date": "2025-11-10",
"price": 1575,
"minPrice": 1550,
"maxPrice": 1640,
"avgPrice": 1600
},
{
"date": "2025-11-11",
"price": 1585,
"minPrice": 1550,
"maxPrice": 1650,
"avgPrice": 1605
},
{
"date": "2025-11-12",
"price": 1615,
"minPrice": 1580,
"maxPrice": 1670,
"avgPrice": 1625
},
{
"date": "2025-11-13",
"price": 1590,
"minPrice": 1560,
"maxPrice": 1650,
"avgPrice": 1610
},
{
"date": "2025-11-14",
"price": 1645,
"minPrice": 1610,
"maxPrice": 1710,
"avgPrice": 1660
},
{
"date": "2025-11-15",
"price": 1625,
"minPrice": 1590,
"maxPrice": 1680,
"avgPrice": 1635
},
{
"date": "2025-11-16",
"price": 1580,
"minPrice": 1550,
"maxPrice": 1640,
"avgPrice": 1595
},
{
"date": "2025-11-17",
"price": 1570,
"minPrice": 1540,
"maxPrice": 1630,
"avgPrice": 1580
},
{
"date": "2025-11-18",
"price": 1600,
"minPrice": 1580,
"maxPrice": 1660,
"avgPrice": 1620
},
{
"date": "2025-11-19",
"price": 1630,
"minPrice": 1600,
"maxPrice": 1690,
"avgPrice": 1650
},
{
"date": "2025-11-20",
"price": 1650,
"minPrice": 1610,
"maxPrice": 1720,
"avgPrice": 1670
},
{
"date": "2025-11-21",
"price": 1610,
"minPrice": 1580,
"maxPrice": 1670,
"avgPrice": 1630
},
{
"date": "2025-11-22",
"price": 1590,
"minPrice": 1560,
"maxPrice": 1650,
"avgPrice": 1605
},
{
"date": "2025-11-23",
"price": 1620,
"minPrice": 1590,
"maxPrice": 1680,
"avgPrice": 1630
},
{
"date": "2025-11-24",
"price": 1670,
"minPrice": 1630,
"maxPrice": 1730,
"avgPrice": 1680
},
{
"date": "2025-11-25",
"price": 1690,
"minPrice": 1650,
"maxPrice": 1750,
"avgPrice": 1700
},
{
"date": "2025-11-26",
"price": 1580,
"minPrice": 1550,
"maxPrice": 1640,
"avgPrice": 1600
},
{
"date": "2025-11-27",
"price": 1600,
"minPrice": 1570,
"maxPrice": 1670,
"avgPrice": 1620
},
{
"date": "2025-11-28",
"price": 1615,
"minPrice": 1580,
"maxPrice": 1680,
"avgPrice": 1630
},
{
"date": "2025-11-29",
"price": 1635,
"minPrice": 1600,
"maxPrice": 1700,
"avgPrice": 1650
},
{
"date": "2025-11-30",
"price": 1605,
"minPrice": 1580,
"maxPrice": 1670,
"avgPrice": 1620
},
],
"bore Water": [
{
"date": "2025-11-01",
"price": 780,
"minPrice": 760,
"maxPrice": 810,
"avgPrice": 790
},
{
"date": "2025-11-02",
"price": 800,
"minPrice": 780,
"maxPrice": 840,
"avgPrice": 810
},
{
"date": "2025-11-03",
"price": 770,
"minPrice": 750,
"maxPrice": 800,
"avgPrice": 780
},
{
"date": "2025-11-04",
"price": 820,
"minPrice": 800,
"maxPrice": 850,
"avgPrice": 830
},
{
"date": "2025-11-05",
"price": 830,
"minPrice": 810,
"maxPrice": 860,
"avgPrice": 840
},
{
"date": "2025-11-06",
"price": 785,
"minPrice": 760,
"maxPrice": 820,
"avgPrice": 795
},
{
"date": "2025-11-07",
"price": 810,
"minPrice": 790,
"maxPrice": 840,
"avgPrice": 820
},
{
"date": "2025-11-08",
"price": 825,
"minPrice": 800,
"maxPrice": 860,
"avgPrice": 835
},
{
"date": "2025-11-09",
"price": 795,
"minPrice": 770,
"maxPrice": 830,
"avgPrice": 810
},
{
"date": "2025-11-10",
"price": 820,
"minPrice": 790,
"maxPrice": 860,
"avgPrice": 830
},
{
"date": "2025-11-11",
"price": 810,
"minPrice": 780,
"maxPrice": 850,
"avgPrice": 820
},
{
"date": "2025-11-12",
"price": 830,
"minPrice": 800,
"maxPrice": 870,
"avgPrice": 840
},
{
"date": "2025-11-13",
"price": 785,
"minPrice": 770,
"maxPrice": 820,
"avgPrice": 795
},
{
"date": "2025-11-14",
"price": 750,
"minPrice": 730,
"maxPrice": 790,
"avgPrice": 760
},
{
"date": "2025-11-15",
"price": 770,
"minPrice": 750,
"maxPrice": 810,
"avgPrice": 780
},
{
"date": "2025-11-16",
"price": 825,
"minPrice": 800,
"maxPrice": 870,
"avgPrice": 840
},
{
"date": "2025-11-17",
"price": 840,
"minPrice": 820,
"maxPrice": 880,
"avgPrice": 850
},
{
"date": "2025-11-18",
"price": 810,
"minPrice": 790,
"maxPrice": 840,
"avgPrice": 820
},
{
"date": "2025-11-19",
"price": 790,
"minPrice": 770,
"maxPrice": 830,
"avgPrice": 800
},
{
"date": "2025-11-20",
"price": 830,
"minPrice": 800,
"maxPrice": 870,
"avgPrice": 840
},
{
"date": "2025-11-21",
"price": 850,
"minPrice": 820,
"maxPrice": 890,
"avgPrice": 860
},
{
"date": "2025-11-22",
"price": 780,
"minPrice": 760,
"maxPrice": 810,
"avgPrice": 790
},
{
"date": "2025-11-23",
"price": 790,
"minPrice": 770,
"maxPrice": 820,
"avgPrice": 800
},
{
"date": "2025-11-24",
"price": 835,
"minPrice": 810,
"maxPrice": 870,
"avgPrice": 845
},
{
"date": "2025-11-25",
"price": 820,
"minPrice": 800,
"maxPrice": 860,
"avgPrice": 830
},
{
"date": "2025-11-26",
"price": 760,
"minPrice": 740,
"maxPrice": 800,
"avgPrice": 780
},
{
"date": "2025-11-27",
"price": 810,
"minPrice": 780,
"maxPrice": 850,
"avgPrice": 820
},
{
"date": "2025-11-28",
"price": 795,
"minPrice": 770,
"maxPrice": 830,
"avgPrice": 805
},
{
"date": "2025-11-29",
"price": 835,
"minPrice": 810,
"maxPrice": 870,
"avgPrice": 845
},
{
"date": "2025-11-30",
"price": 820,
"minPrice": 790,
"maxPrice": 860,
"avgPrice": 830
},
]
};

@ -0,0 +1,481 @@
import 'package:flutter/material.dart';
import 'package:flutter/cupertino.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:watermanagement/supplier/todaysprice/graph_data2.dart';
import '../../common/settings.dart';
class TodaysPriceMainScreen extends StatefulWidget {
const TodaysPriceMainScreen({super.key});
@override
State<TodaysPriceMainScreen> createState() => _TodaysPriceMainScreenState();
}
class _TodaysPriceMainScreenState extends State<TodaysPriceMainScreen> {
int selectedType = 0;
late PageController waterController;
@override
void initState() {
super.initState();
waterController = PageController();
}
@override
void dispose() {
waterController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.black,
appBar: AppBar(
backgroundColor: Colors.white,
elevation: 3,
leading: Padding(
padding: const EdgeInsets.only(left: 10),
child: GestureDetector(
onTap: () => Navigator.pop(context),
child: Container(
padding: const EdgeInsets.all(6),
child: const Icon(Icons.arrow_back_ios_new,
size: 18, color: Colors.black
),
),
),
),
title: Text(
"Today's Prices",
style: fontTextStyle(20, Colors.black, FontWeight.w600),
),
),
body: Column(
children: [
// TOP CARD WITH CHART (80% height)
Container(
height: MediaQuery.of(context).size.height* 0.38,
width: double.infinity,
decoration: BoxDecoration(
color: Colors.white,
borderRadius: const BorderRadius.only(
bottomLeft: Radius.circular(30),
bottomRight: Radius.circular(30)
)
),
child: Column(
children: [
// DOUBLE BAR GRAPH
Expanded(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 10),
child: GraphScreen(selectedType: selectedType),
),
),
const SizedBox(height: 1),
// LEGEND
buildLegend(),
const SizedBox(height: 10),
],
),
),
const SizedBox(height: 15),
// ------------ WATER TABS ------------
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
waterTab(0),
const SizedBox(width: 20),
waterTab(1),
],
),
const SizedBox(height: 10),
// ----------- PAGE VIEW SWIPE FOR BOTH -----------
Expanded(
child: PageView.builder(
itemCount: 2, // 0 = Drinking, 1 = Bore
controller: waterController, // 🔥 added controller
scrollDirection: Axis.horizontal,
onPageChanged: (i) => setState(() => selectedType = i),
itemBuilder: (context, pageIndex) {
final title = pageIndex == 0 ? "Drinking Water" : "Bore Water";
final list = pageIndex == 0
? drinkingWaterSuppliers
: boreWaterSuppliers;
return Stack(
children: [
// SCROLLING SUPPLIER LIST UNDER WATER CARD
Positioned.fill(
child: ListView.builder(
padding: EdgeInsets.only(top: 150),
itemCount: list.length,
itemBuilder: (context, i) {
final s = list[i];
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 10),
child: SupplierCard(
name: s["name"]!,
distance: s["distance"]!,
rating: s["rating"]!,
reviews: s["reviews"]!,
price: s["price"]!,
),
);
},
),
),
// WATER CARD FIXED ON TOP
Positioned(
top: 0,
left: 25,
right: 25,
child: WaterCard(
title: selectedType == 0 ? "Drinking Water" : "Bore Water",
priceRange: selectedType == 0
? "₹1,575 ₹1,700"
: "₹740 ₹850",
),
)
],
);
}
)
)
],
)
);
}
Widget waterTab(int i) {
return GestureDetector(
onTap: () {
waterController.animateToPage(
i,
duration: const Duration(milliseconds: 250),
curve: Curves.easeInOut,
);
setState(() => selectedType = i);
},
child: AnimatedContainer(
duration: const Duration(milliseconds: 250),
width: 160,
height: 4,
decoration: BoxDecoration(
color: selectedType == i ? Colors.white : Colors.white24,
borderRadius: BorderRadius.circular(10),
),
),
);
}
}
class WaterSection extends StatefulWidget {
final Function(int) onTabChanged;
const WaterSection({super.key, required this.onTabChanged});
@override
State<WaterSection> createState() => _WaterSectionState();
}
class _WaterSectionState extends State<WaterSection> {
final PageController controller = PageController();
int index = 0;
@override
Widget build(BuildContext context) {
return Column(
children: [
// ------------ TABS ------------
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
tabBar(0),
const SizedBox(width: 20),
tabBar(1),
],
),
// const SizedBox(height: 20),
// ------------ PAGE VIEW ------------
Expanded(
child: Padding(
padding: EdgeInsets.only(top: 10),
child: PageView(
controller: controller,
onPageChanged: (i) {
setState(() => index = i);
widget.onTabChanged(i); // 🔥 send tab index to parent
},
children: const [
WaterCard(
title: "Drinking Water",
priceRange: "₹1,200 ₹1,500",
),
WaterCard(
title: "Bore Water",
priceRange: "₹740 ₹850",
),
],
),
),
)
],
);
}
// ---------------- TAB BAR ---------------
Widget tabBar(int i) {
return GestureDetector(
onTap: () {
controller.animateToPage(
i,
duration: const Duration(milliseconds: 250),
curve: Curves.easeInOut,
);
setState(() => index = i);
widget.onTabChanged(i); // 🔥 send index
},
child: AnimatedContainer(
duration: const Duration(milliseconds: 250),
width: 160,
height: 4,
decoration: BoxDecoration(
color: index == i ? Colors.white : Colors.white24,
borderRadius: BorderRadius.circular(10),
),
),
);
}
}
class WaterCard extends StatelessWidget {
final String title;
final String priceRange;
final String volume;
const WaterCard({
super.key,
required this.title,
required this.priceRange,
this.volume = "/5,000L", // Default value
});
@override
Widget build(BuildContext context) {
return Container(
margin: const EdgeInsets.symmetric(horizontal: 6),
padding: const EdgeInsets.all(18),
height: 130,
width: 350,
decoration: BoxDecoration(
color: const Color(0xFF2E2E2E),
borderRadius: BorderRadius.circular(18),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.25),
blurRadius: 8,
offset: const Offset(0, 3),
)
],
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
title,
style: fontTextStyle(16, Colors.white.withOpacity(0.8), FontWeight.w600),
),
const SizedBox(height: 6),
Text(
priceRange,
style: fontTextStyle(26, Colors.white, FontWeight.w700),
),
const SizedBox(height: 4),
Text(
volume,
style: fontTextStyle(14, Colors.white54, FontWeight.w400),
),
],
),
);
}
}
final List<Map<String, String>> drinkingWaterSuppliers = [
{
"name": "Balaji Suppliers",
"distance": "2.5 km",
"rating": "4.2",
"reviews": "20k+",
"price": "₹1575"
},
{
"name": "Manikanta Water",
"distance": "4.2 km",
"rating": "4.5",
"reviews": "18k+",
"price": "₹1650"
},
{
"name": "Sai Durga Water",
"distance": "3.1 km",
"rating": "4.1",
"reviews": "25k+",
"price": "₹1500"
},
{
"name": "Tirupati Suppliers",
"distance": "1.5 km",
"rating": "4.2",
"reviews": "20k+",
"price": "₹1500"
},
];
final List<Map<String, String>> boreWaterSuppliers = [
{
"name": "Green Borewell",
"distance": "3.4 km",
"rating": "4.0",
"reviews": "12k+",
"price": "₹799"
},
{
"name": "Crystal Bore Water",
"distance": "5.1 km",
"rating": "4.3",
"reviews": "9k+",
"price": "₹850"
},
{
"name": "PureDrop Bore",
"distance": "2.8 km",
"rating": "4.1",
"reviews": "14k+",
"price": "₹820"
},
{
"name": "Blue Borewell",
"distance": "4.4 km",
"rating": "4.3",
"reviews": "10k+",
"price": "₹899"
},
];
class SupplierCard extends StatelessWidget {
final String name;
final String distance;
final String rating;
final String reviews;
final String price;
const SupplierCard({
super.key,
required this.name,
required this.distance,
required this.rating,
required this.reviews,
required this.price,
});
@override
Widget build(BuildContext context) {
return Container(
margin: const EdgeInsets.symmetric(horizontal: 14, vertical: 6),
padding: const EdgeInsets.all(14),
decoration: BoxDecoration(
color: const Color(0xFFF2F2F2).withOpacity(0.2), // light grey
borderRadius: BorderRadius.circular(22),
border: Border.all(color: Colors.black12, width: 1),
),
child: Row(
children: [
// Avatar
const CircleAvatar(
radius: 22,
backgroundColor: Colors.white,
child: Icon(Icons.person, color: Colors.grey),
),
const SizedBox(width: 12),
// Name + Location
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
name,
style: fontTextStyle(17, Colors.white.withOpacity(0.95), FontWeight.w700),
),
const SizedBox(height: 4),
Text(
"Location: $distance",
style: fontTextStyle(12, Colors.white, FontWeight.w500),
),
],
),
),
// Rating badge + Price
Column(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
Container(
padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 3),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(8),
),
child: Row(
children: [
const Icon(Icons.star, color: Colors.amber, size: 14),
Text(
" $rating (${reviews})",
style: fontTextStyle(12, Colors.black87, FontWeight.w600),
),
],
),
),
const SizedBox(height: 10),
Text(
price,
style: fontTextStyle(18, Colors.white.withOpacity(0.95), FontWeight.w700),
),
],
),
],
),
);
}
}
Loading…
Cancel
Save