parent
c72957afb4
commit
ae8ec6806d
File diff suppressed because it is too large
Load Diff
@ -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(),
|
||||
);
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
Loading…
Reference in new issue