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