master
Sneha 2 months ago
parent 8ebdc2b505
commit 37a5d3ce83

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 927 B

Before

Width:  |  Height:  |  Size: 945 B

After

Width:  |  Height:  |  Size: 945 B

@ -153,10 +153,11 @@ class AppSettings{
static String uploadPicUrl = host + 'uploads-user'; static String uploadPicUrl = host + 'uploads-user';
static String getOrderRequestsFromUsersUrl = host + 'getuserRequestbookingsforsupplier'; static String getOrderRequestsFromUsersUrl = host + 'getuserRequestbookingsforsupplier';
static String acceptOrderRequestsUrl = host + 'request-booking-with-charges'; static String acceptOrderRequestsUrl = host + 'request-booking-with-charges';
static String getAcceptedOrdersFromUsersUrl = host + 'supplier/booking/advance-paid'; static String getAcceptedOrdersFromUsersUrl = host + 'getAllTankersBookingdetails';
static String getPlanRequestsFromUsersUrl = host + 'getuserRequestbookingsforplansforsupplier'; static String getPlanRequestsFromUsersUrl = host + 'getuserRequestbookingsforplansforsupplier';
static String getTankersUrl = host + 'getTankers'; static String getTankersUrl = host + 'getTankers';
static String addTankerUrl = host + 'addTankers'; static String addTankerUrl = host + 'addTankers';
static String updateTankerUrl = host + 'updateTankers';
static String getDriversUrl = host + 'getalldeliveryboys'; static String getDriversUrl = host + 'getalldeliveryboys';
static String addDriversUrl = host + 'addDeliveryboys'; static String addDriversUrl = host + 'addDeliveryboys';
static String addSourceLocationsUrl = host + 'addSource'; static String addSourceLocationsUrl = host + 'addSource';
@ -164,7 +165,7 @@ class AppSettings{
static String setRatesDailyUrl = host + 'tankers'; static String setRatesDailyUrl = host + 'tankers';
static String getSupplierDetailsUrl = host + 'suppliers'; static String getSupplierDetailsUrl = host + 'suppliers';
static String updatePumpFeeUrl = host + 'suppliers'; static String updatePumpFeeUrl = host + 'suppliers';
static String assignTankerUrl = host + 'suppliers'; static String assignTankerUrl = host + 'assign-deliveryboy-tanker';
@ -542,6 +543,40 @@ class AppSettings{
} }
} }
static Future<bool> updateTanker(payload,tankerName) async {
var uri = Uri.parse(updateTankerUrl+'/'+supplierId);
uri = uri.replace(query: 'tankerName=$tankerName');
var response = await http.put(uri,
body: json.encode(payload), headers: await buildRequestHeaders());
if (response.statusCode == 200) {
try {
var _response = json.decode(response.body);
print(_response);
return true;
} catch (e) {
// display error toast
return false;
}
} else if (response.statusCode == 401) {
bool status = await AppSettings.resetToken();
if (status) {
response = await http.put(uri,
body: json.encode(payload), headers: await buildRequestHeaders());
if (response.statusCode == 200) {
return true;
} else {
return false;
}
} else {
return false;
}
} else {
return false;
}
}
static Future<String> getDrivers() async { static Future<String> getDrivers() async {
var uri = Uri.parse(getDriversUrl+'/'+supplierId); var uri = Uri.parse(getDriversUrl+'/'+supplierId);
//uri = uri.replace(query: 'supplierId=$supplierId'); //uri = uri.replace(query: 'supplierId=$supplierId');

@ -364,7 +364,7 @@ class _FinancialMainScreenState extends State<FinancialMainScreen>
), ),
Image.asset("images/icon_tune.png", width: 24, height: 24), Image.asset("images/icon_tune.png", width: 24, height: 24),
const SizedBox(width: 16), const SizedBox(width: 16),
Image.asset("images/up_down arrow.png", width: 24, height: 24), Image.asset("images/up_down_arrow.png", width: 24, height: 24),
], ],
), ),
), ),

@ -367,7 +367,7 @@ class _AcceptOrderRequestsState extends State<AcceptOrderRequests> {
payload["supplierId"] = AppSettings.supplierId; payload["supplierId"] = AppSettings.supplierId;
payload["amount"] = int.parse(widget.order.quoted_amount); payload["amount"] = int.parse(widget.order.quoted_amount);
payload["delivery_charges"] = advance; payload["delivery_charges"] = advance;
payload["action"] = 'reject'; payload["action"] = 'rejected';
bool status = await AppSettings.acceptOrderRequests( bool status = await AppSettings.acceptOrderRequests(
payload, widget.order.dbId); payload, widget.order.dbId);
@ -424,7 +424,7 @@ class _AcceptOrderRequestsState extends State<AcceptOrderRequests> {
payload["supplierId"] = AppSettings.supplierId; payload["supplierId"] = AppSettings.supplierId;
payload["amount"] = int.parse(widget.order.quoted_amount); payload["amount"] = int.parse(widget.order.quoted_amount);
payload["delivery_charges"] = advance; payload["delivery_charges"] = advance;
payload["action"] = 'accept'; payload["action"] = 'accepted';
bool status = await AppSettings.acceptOrderRequests( bool status = await AppSettings.acceptOrderRequests(
payload, widget.order.dbId); payload, widget.order.dbId);

@ -2,6 +2,7 @@ import 'dart:convert';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:supplier_new/common/settings.dart'; import 'package:supplier_new/common/settings.dart';
import 'package:supplier_new/orders/delivery_updates.dart';
import 'package:supplier_new/orders/order_requests.dart'; import 'package:supplier_new/orders/order_requests.dart';
import 'package:supplier_new/orders/orders_model.dart'; import 'package:supplier_new/orders/orders_model.dart';
import 'package:supplier_new/orders/search_order_appbar.dart'; import 'package:supplier_new/orders/search_order_appbar.dart';
@ -9,6 +10,7 @@ import 'package:intl/intl.dart';
import 'assign_driver.dart'; import 'assign_driver.dart';
import 'cancel_order.dart'; import 'cancel_order.dart';
import 'change_driver.dart';
class AllOrders extends StatefulWidget { class AllOrders extends StatefulWidget {
final String navigationFrom; final String navigationFrom;
@ -294,29 +296,42 @@ class OrderCard extends StatelessWidget {
const OrderCard({super.key, required this.order, this.onRefresh}); const OrderCard({super.key, required this.order, this.onRefresh});
Color _getStatusColor() { Color _getStatusColor() {
switch (order.status.toLowerCase()) { String st='';
if(order.status.toLowerCase()=='advance_paid'){
st='pending';
}
else{
st=order.status.toLowerCase();
}
switch (st.toLowerCase()) {
case "completed": case "completed":
return Color(0XFFC4E8C3); return Color(0XFFC4E8C3);
case "in-progress": case "in_progress":
return Color(0XFFE8F2FF); return Color(0XFFC9DFFE);
case "cancelled": case "cancelled":
return Color(0XFFFCEDEC); return Color(0XFFFCEDEC);
case "assigned": case "assigned":
return Color(0XFFF9DBC6); return Color(0XFFF9DBC6);
case "pending": case "pending":
return Color(0XFFFDF3D3); return Color(0XFFFDF3D3);
case "advance_paid":
return Color(0XFFFDF3D3);
default: default:
return Colors.grey; return Colors.grey;
} }
} }
Color _getTextStatusColor() { Color _getTextStatusColor() {
switch (order.status.toLowerCase()) { String st='';
if(order.status.toLowerCase()=='advance_paid'){
st='pending';
}
else{
st=order.status.toLowerCase();
}
switch (st.toLowerCase()) {
case "completed": case "completed":
return Color(0XFF0A9E04); return Color(0XFF0A9E04);
case "in-progress": case "in_progress":
return Color(0XFF1D7AFC); return Color(0XFF1D7AFC);
case "cancelled": case "cancelled":
return Color(0XFFE2483D); return Color(0XFFE2483D);
@ -324,8 +339,6 @@ class OrderCard extends StatelessWidget {
return Color(0XFFE56910); return Color(0XFFE56910);
case "pending": case "pending":
return Color(0XFFD0AE3C); return Color(0XFFD0AE3C);
case "advance_paid":
return Color(0XFFD0AE3C);
default: default:
return Colors.grey; return Colors.grey;
} }
@ -336,171 +349,289 @@ class OrderCard extends StatelessWidget {
final statusColor = _getStatusColor(); final statusColor = _getStatusColor();
final textStatusColor = _getTextStatusColor(); final textStatusColor = _getTextStatusColor();
return Padding(padding: EdgeInsets.fromLTRB(0, 8, 0, 0), String st = (order.status == 'advance_paid') ? 'pending' : order.status;
child: Container(
margin: const EdgeInsets.only(bottom: 12), return Padding(
decoration: BoxDecoration( padding: const EdgeInsets.fromLTRB(0, 8, 0, 0),
color: Colors.white, child: Container(
borderRadius: BorderRadius.circular(12), margin: const EdgeInsets.only(bottom: 12),
boxShadow: [ decoration: BoxDecoration(
BoxShadow( color: Colors.white,
color: Colors.black.withOpacity(0.05), borderRadius: BorderRadius.circular(12),
blurRadius: 5, boxShadow: [
offset: const Offset(0, 3), BoxShadow(
) color: Colors.black.withOpacity(0.05),
], blurRadius: 5,
), offset: const Offset(0, 3),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Image
ClipRRect(
borderRadius: const BorderRadius.only(
topLeft: Radius.circular(12),
bottomLeft: Radius.circular(0),
),
child: order.imageAsset != ''
? Image.asset(
order.imageAsset,
height: 145,
width: 145,
fit: BoxFit.cover,
) )
: Image.network( ],
order.imageAsset, ),
height: 100,
width: 100,
fit: BoxFit.cover,
),
),
// Right side content
Expanded(
child: Padding(
padding: const EdgeInsets.all(10.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Status chip
Container(
padding: const EdgeInsets.symmetric(
horizontal: 8,
vertical: 2,
),
decoration: BoxDecoration(
color: statusColor,
borderRadius: BorderRadius.circular(4),
),
child: Text(
order.status,
style: fontTextStyle(10,textStatusColor,FontWeight.w400)
),
),
const SizedBox(height: 6),
Text( /// 👇 IntrinsicHeight allows both sides of the row to match height dynamically
order.building_name, child: IntrinsicHeight(
style:fontTextStyle(16,Color(0XFF2D2E30),FontWeight.w600) child: Row(
), crossAxisAlignment: CrossAxisAlignment.stretch, // 👈 image stretches
Text( children: [
order.displayAddress, /// 🖼 Left Image
style: fontTextStyle(12,Color(0XFF646566),FontWeight.w400) ClipRRect(
borderRadius: const BorderRadius.only(
topLeft: Radius.circular(12),
bottomLeft: Radius.circular(12),
),
child: SizedBox(
width: 145, // fixed width
child: order.imageAsset.isNotEmpty
? Image.asset(
order.imageAsset,
fit: BoxFit.cover, // 👈 fills height
)
: Image.network(
order.imageAsset,
fit: BoxFit.cover,
), ),
const SizedBox(height: 4), ),
),
Text( /// 📄 Right Content
order.capacity+' - '+order.type_of_water, Expanded(
style: fontTextStyle(14,Color(0XFF444444),FontWeight.w500) child: Padding(
), padding: const EdgeInsets.all(10.0),
Text( child: Column(
order.time+' , '+order.date, crossAxisAlignment: CrossAxisAlignment.start,
style:fontTextStyle(8,Color(0XFF646566),FontWeight.w400)
),
const SizedBox(height: 12),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
Visibility( // Status chip
visible: order.status.toLowerCase().toString()=='advance_paid', Container(
child: GestureDetector( padding: const EdgeInsets.symmetric(
onTap: ()async{ horizontal: 8,
final result = await Navigator.push( vertical: 2,
context, ),
MaterialPageRoute( decoration: BoxDecoration(
builder: (context) => AssignDriverScreen(order: order), color: statusColor,
), borderRadius: BorderRadius.circular(4),
); ),
child: Text(
st,
style: fontTextStyle(10, textStatusColor, FontWeight.w400),
),
),
const SizedBox(height: 6),
// If result indicates API reload Text(
if (result == true) { order.building_name,
onRefresh?.call(); // 👈 safe call to refresh parent style: fontTextStyle(16, const Color(0XFF2D2E30), FontWeight.w600),
} ),
}, Text(
child: Container( order.displayAddress,
decoration: BoxDecoration( style: fontTextStyle(12, const Color(0XFF646566), FontWeight.w400),
borderRadius: BorderRadius.circular(22), ),
border: Border.all(color: const Color(0XFF939495)), const SizedBox(height: 4),
Text(
'${order.capacity} - ${order.type_of_water}',
style: fontTextStyle(14, const Color(0XFF444444), FontWeight.w500),
),
Text(
'${order.time} , ${order.date}',
style: fontTextStyle(8, const Color(0XFF646566), FontWeight.w400),
),
const SizedBox(height: 12),
/// ===================
/// 🟡 ASSIGN & CANCEL BUTTONS
/// ===================
Visibility(
visible: order.status.toLowerCase() == 'advance_paid',
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
GestureDetector(
onTap: () async {
final result = await Navigator.push(
context,
MaterialPageRoute(
builder: (context) => AssignDriverScreen(order: order),
),
);
if (result == true) {
onRefresh?.call();
}
},
child: Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(22),
border: Border.all(color: const Color(0XFF939495)),
),
child: const Padding(
padding: EdgeInsets.fromLTRB(8, 4, 8, 4),
child: Text(
"Assign",
style: TextStyle(
fontSize: 14,
color: Color(0XFF515253),
fontWeight: FontWeight.w400),
),
),
), ),
child: Padding( ),
padding: EdgeInsets.fromLTRB(8,4,8,4), GestureDetector(
child: Text( onTap: () {
"Assign", Navigator.push(
style: fontTextStyle( context,
14, const Color(0XFF515253), FontWeight.w400), MaterialPageRoute(
builder: (context) => DeliveryUpdatesPage(
orderId: order.dbId,
initialStatus: order.status,
),
),
);
},
child: Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(22),
border: Border.all(color: const Color(0XFFE2483D)),
color: const Color(0XFFE2483D),
), ),
) child: const Padding(
), padding: EdgeInsets.fromLTRB(8, 4, 8, 4),
),), child: Text(
"Cancel",
style: TextStyle(
fontSize: 14,
color: Colors.white,
fontWeight: FontWeight.w400),
),
),
),
),
],
),
),
/// ===================
/// 🟣 TRACK DELIVERY BUTTON
/// ===================
Visibility( Visibility(
visible: order.status.toLowerCase().toString()=='advance_paid', visible: order.status.toLowerCase() == 'in_progress',
child: GestureDetector( child: GestureDetector(
onTap: ()async{ onTap: () async {
final result = await Navigator.push( final result = await Navigator.push(
context, context,
MaterialPageRoute( MaterialPageRoute(
builder: (context) => CancelOrderScreen(order: order), builder: (context) => AssignDriverScreen(order: order),
), ),
); );
// If result indicates API reload
if (result == true) { if (result == true) {
onRefresh?.call(); // 👈 safe call to refresh parent onRefresh?.call();
} }
}, },
child: Container( child: Container(
decoration: BoxDecoration( decoration: BoxDecoration(
borderRadius: BorderRadius.circular(22), borderRadius: BorderRadius.circular(22),
border: Border.all(color: const Color(0XFFE2483D)), color: const Color(0XFF8270DB),
color: Color(0XFFE2483D) border: Border.all(color: const Color(0XFF8270DB)),
),
child: const Padding(
padding: EdgeInsets.fromLTRB(8, 4, 8, 4),
child: Text(
"Track Delivery",
style: TextStyle(
fontSize: 14,
color: Colors.white,
fontWeight: FontWeight.w400),
), ),
child: Padding( ),
padding: EdgeInsets.fromLTRB(8,4,8,4),
child: Text(
"Cancel",
style: fontTextStyle(
14, const Color(0XFFFFFFFF), FontWeight.w400),
),
)
), ),
),), ),
], ),
) /// ===================
/// 🟣 TRACK DELIVERY BUTTON
/// ===================
Visibility(
visible: order.status.toLowerCase() == 'assigned',
/* child: */
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Image.asset(
'images/avatar.png',
fit: BoxFit.cover,
width: 12,
height: 12,
),
const SizedBox(width: 8),
// Title + Chips
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Expanded(
child: Text(
"Assigned to ${order.delivery_agent_name}",
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: fontTextStyle(
8, const Color(0xFF646566), FontWeight.w400),
),
),
],
),
],
),
),
/*Text( ],
order.extraInfo, ),
style:fontTextStyle(12, order.status == "in-progress" SizedBox(height: MediaQuery.of(context).size.height * .004),
? Color(0XFF1D7AFC) GestureDetector(
: Color(0XFF646566),FontWeight.w400) onTap: () async {
final result = await Navigator.push(
context,
MaterialPageRoute(
builder: (context) => ChangeDriverScreen(order: order),
),
);
if (result == true) {
onRefresh?.call();
}
},
child: Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(22),
color: const Color(0XFF8270DB),
border: Border.all(color: const Color(0XFF8270DB)),
),
child: const Padding(
padding: EdgeInsets.fromLTRB(8, 4, 8, 4),
child: Text(
"Change",
style: TextStyle(
fontSize: 14,
color: Colors.white,
fontWeight: FontWeight.w400),
),
),
),
),
],
)
),
),*/ ],
], ),
),
), ),
), ],
), ),
], ),
), ),
),); );
} }
} }

@ -129,8 +129,8 @@ class _AssignDriverScreenState extends State<AssignDriverScreen> {
children: [ children: [
Text( Text(
"Assign Tanker", "Assign Tanker",
style: fontTextStyle( style: fontTextStyle(16,
16, const Color(0XFF2A2A2A), FontWeight.w600), const Color(0XFF2A2A2A), FontWeight.w600),
), ),
GestureDetector( GestureDetector(
onTap: () => Navigator.pop(context), onTap: () => Navigator.pop(context),
@ -157,7 +157,8 @@ class _AssignDriverScreenState extends State<AssignDriverScreen> {
child: Row( child: Row(
children: [ children: [
Column( Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment:
CrossAxisAlignment.start,
children: [ children: [
Text( Text(
widget.order.building_name, widget.order.building_name,
@ -197,70 +198,75 @@ class _AssignDriverScreenState extends State<AssignDriverScreen> {
const SizedBox(height: 12), const SizedBox(height: 12),
isTankersDataLoading isTankersDataLoading
? const Center(child: CircularProgressIndicator()) ? const Center(
child: CircularProgressIndicator())
: (tankersList.isEmpty : (tankersList.isEmpty
? Center( ? Center(
child: Text( child: Text(
'No Data Available For Capacity ${widget.order.capacity}', 'No Data Available For Capacity ${widget.order.capacity}',
style: fontTextStyle( style: fontTextStyle(
12, 12,
const Color(0xFF939495), const Color(0xFF939495),
FontWeight.w500), FontWeight.w500),
), ),
) )
: ListView.separated( : ListView.separated(
shrinkWrap: true, shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(), physics:
padding: EdgeInsets.zero, const NeverScrollableScrollPhysics(),
itemCount: tankersList padding: EdgeInsets.zero,
.where((t) => itemCount: tankersList
_capToLiters(t.capacity) == .where((t) =>
_capToLiters(widget.order.capacity)) _capToLiters(t.capacity) ==
.length, _capToLiters(
separatorBuilder: (_, __) => widget.order.capacity))
const SizedBox(height: 12), .length,
itemBuilder: (context, idx) { separatorBuilder: (_, __) =>
final filteredTankers = tankersList const SizedBox(height: 12),
.where((t) => itemBuilder: (context, idx) {
_capToLiters(t.capacity) == final filteredTankers = tankersList
_capToLiters(widget.order.capacity)) .where((t) =>
.toList(); _capToLiters(t.capacity) ==
final d = filteredTankers[idx]; _capToLiters(
final isSelected = widget.order.capacity))
selectedTankerIndex == idx; .toList();
final d = filteredTankers[idx];
final isSelected =
selectedTankerIndex == idx;
return GestureDetector( return GestureDetector(
onTap: () { onTap: () {
setModalState(() { setModalState(() {
selectedTankerIndex = idx; selectedTankerIndex = idx;
selectedDriverIndex = null; // reset driver selection if tanker changes selectedDriverIndex =
}); null; // reset driver selection if tanker changes
}, });
child: Card( },
elevation: 1, child: Card(
shape: RoundedRectangleBorder( elevation: 1,
borderRadius: shape: RoundedRectangleBorder(
BorderRadius.circular(12), borderRadius:
side: BorderSide( BorderRadius.circular(12),
color: isSelected side: BorderSide(
? primaryColor color: isSelected
: const Color(0XFFC3C4C4), ? primaryColor
width: 1, : const Color(0XFFC3C4C4),
), width: 1,
), ),
child: TankersCard( ),
title: d.tanker_name, child: TankersCard(
subtitle: d.type_of_water, title: d.tanker_name,
capacity: d.capacity, subtitle: d.type_of_water,
code: d.license_plate, capacity: d.capacity,
owner: d.supplier_name, code: d.license_plate,
status: List<String>.from( owner: d.supplier_name,
d.availability), status: List<String>.from(
), d.availability),
), ),
); ),
}, );
)), },
)),
const SizedBox(height: 8), const SizedBox(height: 8),
// 🧍 Driver List // 🧍 Driver List
@ -274,59 +280,70 @@ class _AssignDriverScreenState extends State<AssignDriverScreen> {
// 🚨 Driver list disabled until tanker is selected // 🚨 Driver list disabled until tanker is selected
selectedTankerIndex == null selectedTankerIndex == null
? Container( ? Container(
width: double.infinity, width: double.infinity,
padding: const EdgeInsets.all(16), padding: const EdgeInsets.all(16),
margin: const EdgeInsets.only(top: 8), margin: const EdgeInsets.only(top: 8),
decoration: BoxDecoration( decoration: BoxDecoration(
color: const Color(0XFFFFFFFF), color: const Color(0XFFFFFFFF),
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(12),
border: Border.all(color: const Color(0xFFC3C4C4)), border: Border.all(
), color: const Color(0xFFC3C4C4)),
child: Center( ),
child: Text( child: Center(
'Select a tanker to choose driver', child: Text(
style: fontTextStyle( 'Select a tanker to choose driver',
14, const Color(0xFF2D2E30), FontWeight.w400), style: fontTextStyle(
), 14,
), const Color(0xFF2D2E30),
) FontWeight.w400),
),
),
)
: isLoading : isLoading
? const Center(child: CircularProgressIndicator()) ? const Center(
: (driversList.isEmpty child: CircularProgressIndicator())
? Center( : (driversList.isEmpty
child: Padding( ? Center(
padding: const EdgeInsets.symmetric(vertical: 12), child: Padding(
child: Text( padding:
'No Data Available', const EdgeInsets.symmetric(
style: fontTextStyle( vertical: 12),
12, child: Text(
const Color(0xFF939495), 'No Data Available',
FontWeight.w500), style: fontTextStyle(
), 12,
), const Color(0xFF939495),
) FontWeight.w500),
: ListView.separated( ),
shrinkWrap: true, ),
physics: const NeverScrollableScrollPhysics(), )
padding: EdgeInsets.zero, : ListView.separated(
itemCount: driversList.length, shrinkWrap: true,
separatorBuilder: (_, __) => const SizedBox(height: 12), physics:
itemBuilder: (context, idx) { const NeverScrollableScrollPhysics(),
final d = driversList[idx]; padding: EdgeInsets.zero,
final isSelected = selectedDriverIndex == idx; itemCount: driversList.length,
final isAvailable = d.status == "available"; separatorBuilder: (_, __) =>
const SizedBox(height: 12),
itemBuilder: (context, idx) {
final d = driversList[idx];
final isSelected =
selectedDriverIndex == idx;
final isAvailable =
d.status == "available";
final statusColor = isAvailable final statusColor = isAvailable
? const Color(0XFF0A9E04) ? const Color(0XFF0A9E04)
: (d.status == "on delivery" : (d.status == "on delivery"
? const Color(0XFFD0AE3C) ? const Color(0XFFD0AE3C)
: (d.status == "offline" : (d.status == "offline"
? const Color(0XFF939495) ? const Color(
: Colors.grey)); 0XFF939495)
: Colors.grey));
return GestureDetector( return GestureDetector(
onTap: () { onTap: () {
/* if (isAvailable) { /* if (isAvailable) {
setModalState(() { setModalState(() {
selectedDriverIndex = idx; selectedDriverIndex = idx;
}); });
@ -335,57 +352,86 @@ class _AssignDriverScreenState extends State<AssignDriverScreen> {
'Only available drivers can be selected', 'Only available drivers can be selected',
); );
}*/ }*/
setModalState(() { setModalState(() {
selectedDriverIndex = idx; selectedDriverIndex = idx;
}); });
}, },
child: Card( child: Card(
color: const Color(0XFFFFFFFF), color:
elevation: 1, const Color(0XFFFFFFFF),
shape: RoundedRectangleBorder( elevation: 1,
borderRadius: BorderRadius.circular(12), shape: RoundedRectangleBorder(
side: BorderSide( borderRadius:
color: isSelected ? primaryColor : const Color(0XFFC3C4C4), BorderRadius.circular(
width: 1, 12),
), side: BorderSide(
), color: isSelected
child: Padding( ? primaryColor
padding: const EdgeInsets.all(12.0), : const Color(
child: Row( 0XFFC3C4C4),
children: [ width: 1,
Image.asset( ),
'images/avatar.png', ),
fit: BoxFit.cover, child: Padding(
width: 20, padding:
height: 20, const EdgeInsets.all(
), 12.0),
const SizedBox(width: 8), child: Row(
Expanded( children: [
child: Text( Image.asset(
d.driver_name, 'images/avatar.png',
style: fontTextStyle( fit: BoxFit.cover,
14, const Color(0XFF2D2E30), FontWeight.w500), width: 20,
), height: 20,
), ),
const SizedBox(width: 8), const SizedBox(
Container( width: 8),
padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2), Expanded(
decoration: BoxDecoration( child: Text(
borderRadius: BorderRadius.circular(4), d.driver_name,
border: Border.all(color: statusColor), style: fontTextStyle(
), 14,
child: Text( const Color(
d.status, 0XFF2D2E30),
style: fontTextStyle(10, statusColor, FontWeight.w400), FontWeight
), .w500),
), ),
], ),
), const SizedBox(
), width: 8),
), Container(
); padding:
}, const EdgeInsets
)), .symmetric(
horizontal: 6,
vertical: 2),
decoration:
BoxDecoration(
borderRadius:
BorderRadius
.circular(
4),
border: Border.all(
color:
statusColor),
),
child: Text(
d.status,
style:
fontTextStyle(
10,
statusColor,
FontWeight
.w400),
),
),
],
),
),
),
);
},
)),
], ],
), ),
), ),
@ -395,76 +441,73 @@ class _AssignDriverScreenState extends State<AssignDriverScreen> {
SafeArea( SafeArea(
top: false, top: false,
child: Padding( child: Padding(
padding: padding: const EdgeInsets.symmetric(
const EdgeInsets.symmetric(horizontal: 16, vertical: 12), horizontal: 16, vertical: 12),
child: SizedBox( child: SizedBox(
width: double.infinity, width: double.infinity,
child: GestureDetector( child: GestureDetector(
onTap: () async { onTap: () async {
if (selectedTankerIndex == null) { if (selectedTankerIndex == null) {
AppSettings.longFailedToast('Please select tanker'); AppSettings.longFailedToast(
'Please select tanker');
return; return;
} }
/*if (selectedDriverIndex == null) {
AppSettings.longFailedToast('Please select driver');
return;
}*/
final filteredTankers = tankersList final filteredTankers = tankersList
.where((t) => .where((t) =>
_capToLiters(t.capacity) == _capToLiters(t.capacity) ==
_capToLiters(widget.order.capacity)) _capToLiters(widget.order.capacity))
.toList(); .toList();
final selectedTanker = filteredTankers[selectedTankerIndex!]; final selectedTanker =
final selectedDriver = driversList[selectedDriverIndex!]; filteredTankers[selectedTankerIndex!];
final selectedDriver = selectedDriverIndex != null
? driversList[selectedDriverIndex!]
: null;
AppSettings.preLoaderDialog(context); AppSettings.preLoaderDialog(context);
bool isOnline =
bool isOnline = await AppSettings.internetConnectivity(); await AppSettings.internetConnectivity();
if (isOnline) { if (isOnline) {
var payload = new Map<String, dynamic>(); var payload = <String, dynamic>{};
payload["tankerName"] = selectedTanker.tanker_name; payload["tankerName"] =
if(selectedDriverIndex != null){ selectedTanker.tanker_name;
payload["delivery_agent"] = selectedDriver.driver_name; payload["delivery_agent"] =
payload["delivery_agent_mobile"] = selectedDriver.phone_number; selectedDriver?.driver_name;
} payload["delivery_agent_mobile"] =
else{ selectedDriver?.phone_number;
payload["delivery_agent"] =null;
payload["delivery_agent_mobile"] = null;
}
bool status = await AppSettings.assignTanker(
payload, widget.order.dbId);
bool status = await AppSettings.assignTanker(payload, widget.order.dbId); Navigator.of(context, rootNavigator: true)
.pop(); // close loader
try { if (status) {
if (status) { AppSettings.longSuccessToast(
Navigator.of(context,rootNavigator: true).pop(); "Tanker assigned successfully");
AppSettings.longFailedToast("Tanker assigned successfully");
Navigator.pop(context, true);
} // 👇 Pop bottom sheet first
else{ Navigator.pop(context);
Navigator.of(context,rootNavigator: true).pop();
AppSettings.longFailedToast("Failed to assign tanker"); // 👇 Then pop the AssignDriverScreen and send true to parent
} Navigator.pop(context, true);
} catch (e) { } else {
Navigator.of(context,rootNavigator: true).pop(); AppSettings.longFailedToast(
print(e); "Failed to assign tanker");
} }
} else {
Navigator.of(context, rootNavigator: true)
.pop();
AppSettings.longFailedToast(
"Please Check internet");
} }
else{
Navigator.of(context,rootNavigator: true).pop();
AppSettings.longFailedToast("Please Check internet");
}
if (context.mounted) Navigator.pop(context);
}, },
child: Container( child: Container(
decoration: BoxDecoration( decoration: BoxDecoration(
color: const Color(0XFF8270DB), color: const Color(0XFF8270DB),
borderRadius: BorderRadius.circular(24), borderRadius : BorderRadius.circular(24),
), ),
alignment: Alignment.center, alignment: Alignment.center,
padding: const EdgeInsets.symmetric(vertical: 12), padding: const EdgeInsets.symmetric(vertical: 12),
@ -954,7 +997,8 @@ class TankersCard extends StatelessWidget {
borderRadius: BorderRadius.circular(8), borderRadius: BorderRadius.circular(8),
border: Border.all(color: chipTextColor, width: 1), border: Border.all(color: chipTextColor, width: 1),
), ),
child: Text(s, style: fontTextStyle(10, chipTextColor, FontWeight.w400)), child:
Text(s, style: fontTextStyle(10, chipTextColor, FontWeight.w400)),
); );
} }
@ -991,7 +1035,8 @@ class TankersCard extends StatelessWidget {
"$subtitle$title", "$subtitle$title",
maxLines: 1, maxLines: 1,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
style: fontTextStyle(14, const Color(0xFF2D2E30), FontWeight.w500), style: fontTextStyle(
14, const Color(0xFF2D2E30), FontWeight.w500),
), ),
), ),
const SizedBox(width: 8), const SizedBox(width: 8),
@ -1009,7 +1054,8 @@ class TankersCard extends StatelessWidget {
), ),
Text( Text(
code, code,
style: fontTextStyle(10, const Color(0xFF646566), FontWeight.w400), style: fontTextStyle(
10, const Color(0xFF646566), FontWeight.w400),
), ),
], ],
), ),
@ -1019,6 +1065,5 @@ class TankersCard extends StatelessWidget {
], ],
), ),
); );
} }
} }

File diff suppressed because it is too large Load Diff

@ -0,0 +1,131 @@
import 'package:flutter/material.dart';
class DeliveryUpdatesPage extends StatefulWidget {
final String orderId;
final String initialStatus;
const DeliveryUpdatesPage({super.key, required this.orderId, required this.initialStatus});
@override
State<DeliveryUpdatesPage> createState() => _DeliveryUpdatesPageState();
}
class _DeliveryUpdatesPageState extends State<DeliveryUpdatesPage> {
List<String> statuses = [
"Tanker reached source",
"Water filling started",
"Water filling completed",
"Tanker started to customer location",
"Offloading water started",
"Offloading water completed",
"Payment completed",
"Delivery completed"
];
int currentStep = 0;
@override
void initState() {
super.initState();
// Example: Get live updates from backend (MQTT, WebSocket, Firestore, etc.)
_simulateStatusUpdates();
}
void _simulateStatusUpdates() async {
// This is just simulation replace with your listener
for (int i = 0; i < statuses.length; i++) {
await Future.delayed(const Duration(seconds: 3));
setState(() {
currentStep = i;
});
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text("Track Order"),
backgroundColor: const Color(0XFF0A9E04),
),
body: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
children: [
Expanded(
child: ListView.builder(
itemCount: statuses.length,
itemBuilder: (context, index) {
final isCompleted = index <= currentStep;
return Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Column(
children: [
// Circle with tanker icon or checkmark
Container(
width: 30,
height: 30,
decoration: BoxDecoration(
color: isCompleted ? Colors.green : Colors.grey[300],
shape: BoxShape.circle,
),
child: Center(
child: index == currentStep
? const Icon(Icons.local_shipping)
: Icon(
isCompleted
? Icons.check
: Icons.circle_outlined,
size: 16,
color: isCompleted ? Colors.white : Colors.grey,
),
),
),
if (index != statuses.length - 1)
Container(
width: 4,
height: 50,
color: index < currentStep ? Colors.green : Colors.grey[300],
),
],
),
const SizedBox(width: 12),
Expanded(
child: Padding(
padding: const EdgeInsets.only(top: 4),
child: Text(
statuses[index],
style: TextStyle(
fontSize: 14,
fontWeight: isCompleted ? FontWeight.w600 : FontWeight.w400,
color: isCompleted ? Colors.black : Colors.grey[600],
),
),
),
)
],
);
},
),
),
// 🔸 Current status shown at bottom
Container(
width: double.infinity,
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: Colors.green.withOpacity(0.1),
borderRadius: BorderRadius.circular(8),
),
child: Text(
"Current Status: ${statuses[currentStep]}",
style: const TextStyle(fontSize: 15, fontWeight: FontWeight.w600),
),
),
],
),
),
);
}
}

@ -21,6 +21,8 @@ class OrdersModel {
String status=''; String status='';
String date=''; String date='';
String imageAsset='images/building.png'; String imageAsset='images/building.png';
String delivery_agent_name = '';
String tanker_name = '';
OrdersModel(); OrdersModel();
@ -39,6 +41,8 @@ class OrdersModel {
rtvm.quoted_amount = json['price'].toString() ?? ''; rtvm.quoted_amount = json['price'].toString() ?? '';
rtvm.lng=json['longitude'] ?? 0.0; rtvm.lng=json['longitude'] ?? 0.0;
rtvm.lat=json['latitude'] ?? 0.0; rtvm.lat=json['latitude'] ?? 0.0;
rtvm.delivery_agent_name = json['delivery_agent'] ?? '';
rtvm.tanker_name = json['tankerName'] ?? '';
// Split and trim // Split and trim
List<String> parts = rtvm.address.split(',').map((e) => e.trim()).toList(); List<String> parts = rtvm.address.split(',').map((e) => e.trim()).toList();

@ -509,7 +509,7 @@ class _ResourcesDriverScreenState extends State<ResourcesDriverScreen> {
const SizedBox(width: 16), const SizedBox(width: 16),
Image.asset("images/icon_tune.png", width: 24, height: 24), Image.asset("images/icon_tune.png", width: 24, height: 24),
const SizedBox(width: 16), const SizedBox(width: 16),
Image.asset("images/up_down arrow.png", width: 24, height: 24), Image.asset("images/up_down_arrow.png", width: 24, height: 24),
], ],
), ),
const SizedBox(height: 12), const SizedBox(height: 12),

@ -36,7 +36,6 @@ class FirstCharUppercaseFormatter extends TextInputFormatter {
); );
} }
} }
void main() => runApp(const MaterialApp(home: ResourcesFleetScreen()));
class ResourcesFleetScreen extends StatefulWidget { class ResourcesFleetScreen extends StatefulWidget {
const ResourcesFleetScreen({super.key}); const ResourcesFleetScreen({super.key});
@ -570,13 +569,16 @@ class _ResourcesFleetScreenState extends State<ResourcesFleetScreen> {
itemBuilder: (context, idx) { itemBuilder: (context, idx) {
final it = filtered[idx]; final it = filtered[idx];
return GestureDetector( return GestureDetector(
onTap: (){ onTap: () async{
Navigator.push( final result = await Navigator.push(
context, context,
MaterialPageRoute( MaterialPageRoute(
builder: (context) => TankerDetailsPage(tankerDetails: it), builder: (context) => TankerDetailsPage(tankerDetails: it),
), ),
); );
if (result == true) {
_fetchTankers();
}
}, },
child: TankCard( child: TankCard(
title: it.tanker_name, title: it.tanker_name,
@ -735,21 +737,7 @@ class TankCard extends StatelessWidget {
const SizedBox(height: 10), const SizedBox(height: 10),
Row( Row(
children: [ children: [
ClipOval( Image.asset('images/avatar.png', width: 12, height: 12),
child: Container(
height: 12,
width: 12,
decoration: BoxDecoration(
shape: BoxShape.circle,
image: DecorationImage(
image: (AppSettings.profilePictureUrl != '' && AppSettings.profilePictureUrl != 'null')
? NetworkImage(AppSettings.profilePictureUrl)
: const AssetImage("images/profile_pic.png") as ImageProvider,
fit: BoxFit.cover,
),
),
),
),
const SizedBox(width: 6), const SizedBox(width: 6),
Expanded( Expanded(
child: Text(owner, style: fontTextStyle(8, const Color(0xFF646566), FontWeight.w400)), child: Text(owner, style: fontTextStyle(8, const Color(0xFF646566), FontWeight.w400)),

@ -1,4 +1,36 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import '../common/settings.dart';
class FirstCharUppercaseFormatter extends TextInputFormatter {
const FirstCharUppercaseFormatter();
@override
TextEditingValue formatEditUpdate(
TextEditingValue oldValue,
TextEditingValue newValue,
) {
final text = newValue.text;
if (text.isEmpty) return newValue;
// Find first non-space char
final i = text.indexOf(RegExp(r'\S'));
if (i == -1) return newValue;
final first = text[i];
final upper = first.toUpperCase();
if (first == upper) return newValue;
final newText = text.replaceRange(i, i + 1, upper);
return newValue.copyWith(
text: newText,
selection: newValue.selection,
composing: TextRange.empty,
);
}
}
class TankerDetailsPage extends StatefulWidget { class TankerDetailsPage extends StatefulWidget {
var tankerDetails; var tankerDetails;
@ -9,61 +41,451 @@ class TankerDetailsPage extends StatefulWidget {
} }
class _TankerDetailsPageState extends State<TankerDetailsPage> { class _TankerDetailsPageState extends State<TankerDetailsPage> {
@override
Widget build(BuildContext context) { final _formKey = GlobalKey<FormState>();
return Scaffold( final _nameCtrl = TextEditingController();
backgroundColor: Colors.white, final _capacityCtrl = TextEditingController();
appBar: AppBar( final _plateCtrl = TextEditingController();
backgroundColor: Colors.white, final _mfgYearCtrl = TextEditingController();
elevation: 0, final _insExpiryCtrl = TextEditingController();
leading: IconButton(
icon: const Icon(Icons.arrow_back_ios, color: Colors.black), // Dropdown selections (sheet)
onPressed: () => Navigator.pop(context), String? selectedType;
), String? selectedTypeOfWater;
title: Text(
widget.tankerDetails.tanker_name.isNotEmpty // Dropdown options (adjust to your backend)
? widget.tankerDetails.tanker_name[0].toUpperCase() + final List<String> tankerTypes = const [
widget.tankerDetails.tanker_name.substring(1) "Standard Tanker",
: '', "High-Capacity Tanker",
style: const TextStyle( "Small Tanker",
color: Colors.black, ];
fontSize: 16, final List<String> typeOfWater = const [
fontWeight: FontWeight.w600, "Drinking water",
"Bore water",
];
String? _required(String? v, {String field = "This field"}) {
if (v == null || v.trim().isEmpty) return "$field is required";
return null;
}
int selectedTab = 0;
String search = '';
bool isLoading = false;
Color _chipColor(String s) {
switch (s) {
case 'filled':
return const Color(0xFFFFFFFF);
case 'available':
return const Color(0xFFE8F0FF);
case 'empty':
return const Color(0xFFFFEEEE);
case 'in-use':
return const Color(0xFFFFF0E6);
case 'maintenance':
return const Color(0xFFFFF4E6);
default:
return const Color(0xFFECECEC);
}
}
Color _chipTextColor(String s) {
switch (s) {
case 'filled':
return const Color(0xFF1D7AFC);
case 'available':
return const Color(0xFF0A9E04);
case 'empty':
return const Color(0xFFE2483D);
case 'in-use':
return const Color(0xFFEA843B);
case 'maintenance':
return const Color(0xFFD0AE3C);
default:
return Colors.black87;
}
}
Future<void> _pickInsuranceDate() async {
final picked = await showDatePicker(
context: context,
initialDate: DateTime.now(),
firstDate: DateTime(2000),
lastDate: DateTime(2100),
builder: (context, child) {
return Theme(
data: Theme.of(context).copyWith(
dialogBackgroundColor: Colors.white,
colorScheme: Theme.of(context).colorScheme.copyWith(
primary: const Color(0xFF8270DB),
surface: Colors.white,
onSurface: const Color(0xFF101214),
),
), ),
), child: child!,
centerTitle: false, );
actions: [ },
TextButton( );
onPressed: () {}, if (picked != null) {
child: const Text( final dd = picked.day.toString().padLeft(2, '0');
"HELP", final mm = picked.month.toString().padLeft(2, '0');
style: TextStyle( final yyyy = picked.year.toString();
color: Color(0xFF4F46E5), _insExpiryCtrl.text = "$dd-$mm-$yyyy";
fontWeight: FontWeight.w600, setState(() {});
fontSize: 14, }
}
void _resetForm() {
_formKey.currentState?.reset();
_nameCtrl.clear();
_capacityCtrl.clear();
_plateCtrl.clear();
_mfgYearCtrl.clear();
_insExpiryCtrl.clear();
selectedType = null;
selectedTypeOfWater = null;
}
String? fitToOption(String? incoming, List<String> options) {
if (incoming == null) return null;
final inc = incoming.trim();
if (inc.isEmpty) return null;
final match = options.firstWhere(
(o) => o.toLowerCase() == inc.toLowerCase(),
orElse: () => '',
);
return match.isEmpty ? null : match; // return the exact option string
}
Future<void> _updateTanker() async {
// Validate
final ok = _formKey.currentState?.validate() ?? false;
if (!ok) {
setState(() {}); // force rebuild to show errors
return;
}
// Build payload keys to match your backend
final payload = <String, dynamic>{
"tankerName": _nameCtrl.text.trim(),
"capacity": _capacityCtrl.text.trim(),
"typeofwater": selectedTypeOfWater ?? "",
"supplier_address": AppSettings.userAddress,
"supplier_name": AppSettings.userName,
"phoneNumber": AppSettings.phoneNumber,
"tanker_type": selectedType ?? "",
"license_plate": _plateCtrl.text.trim(),
"manufacturing_year": _mfgYearCtrl.text.trim(),
"insurance_exp_date": _insExpiryCtrl.text.trim(),
"delivery_fee": widget.tankerDetails.delivery_fee,
"pumping_fee":widget.tankerDetails.pumping_fee,
"price": widget.tankerDetails.price,
};
try {
final bool tankStatus = await AppSettings.updateTanker(payload,widget.tankerDetails.tanker_name);
if (!mounted) return;
if (tankStatus) {
AppSettings.longSuccessToast("Tanker Updated Successfully");
Navigator.pop(context, true); // close sheet
_resetForm();
// refresh from server
} else {
AppSettings.longFailedToast("Tanker update failed");
}
} catch (e) {
debugPrint("⚠️ update tanker error: $e");
if (!mounted) return;
AppSettings.longFailedToast("Something went wrong");
}
}
Future<void> openTankerSimpleSheet(BuildContext context) async {
_nameCtrl.text = widget.tankerDetails.tanker_name ?? '';
_capacityCtrl.text = widget.tankerDetails.capacity ?? '';
_plateCtrl.text = widget.tankerDetails.license_plate ?? '';
_mfgYearCtrl.text = widget.tankerDetails.manufacturing_year ?? '';
_insExpiryCtrl.text = widget.tankerDetails.insurance_expiry ?? '';
selectedType = fitToOption(widget.tankerDetails.tanker_type, tankerTypes);
selectedTypeOfWater = fitToOption(widget.tankerDetails.type_of_water, typeOfWater);
await showModalBottomSheet(
context: context,
isScrollControlled: true,
backgroundColor: Colors.transparent,
builder: (context) {
final viewInsets = MediaQuery.of(context).viewInsets.bottom;
return FractionallySizedBox(
heightFactor: 0.75,
child: Container(
decoration: const BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.vertical(top: Radius.circular(20)),
),
child: Padding(
padding: EdgeInsets.fromLTRB(20, 16, 20, 20 + viewInsets),
child: Form(
key: _formKey,
child: SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Row(
children: [
Expanded(
child: Center(
child: Container(
width: 86,
height: 4,
margin: const EdgeInsets.only(bottom: 12),
decoration: BoxDecoration(
color: const Color(0xFFE0E0E0),
borderRadius: BorderRadius.circular(2),
),
),
),
),
],
),
_LabeledField(
label: "Tanker Name *",
child: TextFormField(
controller: _nameCtrl,
validator: (v) => _required(v, field: "Tanker Name"),
textCapitalization: TextCapitalization.none,
inputFormatters: const [
FirstCharUppercaseFormatter(), // << live first-letter caps
],
decoration: InputDecoration(
hintText: "Enter Tanker Name",
hintStyle: fontTextStyle(14, const Color(0xFF939495), FontWeight.w400),
border: const OutlineInputBorder(),
isDense: true,
),
textInputAction: TextInputAction.next,
),
),
_LabeledField(
label: "Tanker Capacity (in L) *",
child: TextFormField(
controller: _capacityCtrl,
validator: (v) => _required(v, field: "Tanker Capacity"),
decoration: InputDecoration(
hintText: "10,000",
hintStyle: fontTextStyle(14, const Color(0xFF939495), FontWeight.w400),
border: const OutlineInputBorder(),
isDense: true,
),
keyboardType: TextInputType.number,
inputFormatters: [
FilteringTextInputFormatter.allow(RegExp(r'[0-9,]')),
],
textInputAction: TextInputAction.next,
),
),
_LabeledField(
label: "Tanker Type *",
child: DropdownButtonFormField<String>(
value: selectedType,
items: tankerTypes
.map((t) => DropdownMenuItem(value: t, child: Text(t)))
.toList(),
onChanged: (v) => setState(() => selectedType = v),
validator: (v) => v == null || v.isEmpty ? "Tanker Type is required" : null,
isExpanded: true,
alignment: Alignment.centerLeft,
hint: Text(
"Select Type",
style: fontTextStyle(14, const Color(0xFF939495), FontWeight.w400),
),
icon: Image.asset('images/downarrow.png', width: 16, height: 16),
decoration: const InputDecoration(
border: OutlineInputBorder(),
isDense: false,
contentPadding: EdgeInsets.symmetric(horizontal: 12, vertical: 14),
),
),
),
_LabeledField(
label: "Type of water *",
child: DropdownButtonFormField<String>(
value: (selectedTypeOfWater != null && typeOfWater.contains(selectedTypeOfWater))
? selectedTypeOfWater
: null, // <- ensure null instead of "" or a non-member
items: typeOfWater
.map((t) => DropdownMenuItem(value: t, child: Text(t)))
.toList(),
onChanged: (v) => setState(() => selectedTypeOfWater = v),
validator: (v) => v == null || v.isEmpty ? "Type of water is required" : null,
isExpanded: true,
alignment: Alignment.centerLeft,
hint: Text(
"Select type of water",
style: fontTextStyle(14, const Color(0xFF939495), FontWeight.w400),
),
icon: Image.asset('images/downarrow.png', width: 16, height: 16),
decoration: const InputDecoration(
border: OutlineInputBorder(),
isDense: false,
contentPadding: EdgeInsets.symmetric(horizontal: 12, vertical: 14),
),
),
),
_LabeledField(
label: "License Plate *",
child: TextFormField(
controller: _plateCtrl,
validator: (v) => _required(v, field: "License Plate"),
decoration: InputDecoration(
hintText: "AB 05 H 4948",
hintStyle: fontTextStyle(14, const Color(0xFF939495), FontWeight.w400),
border: const OutlineInputBorder(),
isDense: true,
),
textCapitalization: TextCapitalization.characters,
textInputAction: TextInputAction.next,
),
),
_LabeledField(
label: "Manufacturing Year (opt)",
child: TextFormField(
controller: _mfgYearCtrl,
decoration: InputDecoration(
hintText: "YYYY",
hintStyle: fontTextStyle(14, const Color(0xFF939495), FontWeight.w400),
border: const OutlineInputBorder(),
isDense: true,
),
keyboardType: TextInputType.number,
inputFormatters: [
FilteringTextInputFormatter.digitsOnly,
LengthLimitingTextInputFormatter(4),
],
textInputAction: TextInputAction.next,
),
),
_LabeledField(
label: "Insurance Expiry Date (opt)",
child: TextFormField(
controller: _insExpiryCtrl,
readOnly: true,
decoration: InputDecoration(
hintText: "DD-MM-YYYY",
hintStyle: fontTextStyle(14, const Color(0xFF939495), FontWeight.w400),
border: const OutlineInputBorder(),
isDense: true,
suffixIcon: const Icon(Icons.calendar_today_outlined, size: 18),
),
onTap: _pickInsuranceDate,
),
),
const SizedBox(height: 20),
SizedBox(
width: double.infinity,
child: ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xFF8270DB),
foregroundColor: Colors.white,
padding: const EdgeInsets.symmetric(vertical: 14),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(24),
),
),
onPressed: (){
_updateTanker();
/*if (_formKey.currentState!.validate()) {
final updatedTanker = {
"tanker_name": _nameCtrl.text.trim(),
"capacity": _capacityCtrl.text.trim(),
"license_plate": _plateCtrl.text.trim(),
"manufacturing_year": _mfgYearCtrl.text.trim(),
"insurance_expiry": _insExpiryCtrl.text.trim(),
"tanker_type": selectedType,
"type_of_water": selectedTypeOfWater,
};
// You can now call your update API or local DB function
print("Updated Tanker: $updatedTanker");
Navigator.pop(context);
}*/
},
child: Text(
"Update",
style: fontTextStyle(14, Colors.white, FontWeight.w600),
),
),
),
],
),
),
), ),
), ),
), ),
], );
), },
);
}
@override
Widget build(BuildContext context) {
return WillPopScope(
onWillPop: () async {
// Return true to indicate successful tanker update refresh
Navigator.pop(context, true);
return false; // prevent default pop since we manually handled it
},
child: Scaffold(
backgroundColor: Color(0XFFF1F1F1),
appBar: AppSettings.supplierAppBarWithActionsText(widget.tankerDetails.tanker_name.isNotEmpty
? widget.tankerDetails.tanker_name[0].toUpperCase() +
widget.tankerDetails.tanker_name.substring(1)
: '', context),
body: SingleChildScrollView( body: SingleChildScrollView(
child: Padding( child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 16), padding: const EdgeInsets.all(0),
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
// 🛻 Tanker Image Container(
ClipRRect( decoration: const BoxDecoration(
borderRadius: BorderRadius.circular(12), color: Color(0XFFFFFFFF),
child: Stack( borderRadius: BorderRadius.only(
bottomLeft: Radius.circular(24),
bottomRight: Radius.circular(24),
),
),
padding: const EdgeInsets.all(16),
child: Column(
children: [ children: [
Image.asset( ClipRRect(
'images/tanker_image.jpeg', // Replace with your image borderRadius: BorderRadius.circular(12),
width: double.infinity, child: Stack(
height: 180, children: [
fit: BoxFit.cover, Image.asset(
), 'images/tanker_image.jpeg', // Replace with your image
/*Positioned( width: double.infinity,
height: 180,
fit: BoxFit.cover,
),
/*Positioned(
bottom: 12, bottom: 12,
left: 12, left: 12,
child: Row( child: Row(
@ -74,101 +496,237 @@ class _TankerDetailsPageState extends State<TankerDetailsPage> {
], ],
), ),
),*/ ),*/
], ],
), ),
), ),
const SizedBox(height: 16), const SizedBox(height: 16),
Row( Row(
children: [ children: widget.tankerDetails.availability
_buildStatusChip("filled", const Color(0xFF4F46E5)), .map<Widget>((s) {
const SizedBox(width: 8), final chipTextColor = _chipTextColor(s);
_buildStatusChip("available", const Color(0xFF0A9E04)), return Container(
], margin: const EdgeInsets.only(right: 6),
), padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
const SizedBox(height: 16), decoration: BoxDecoration(
color: _chipColor(s),
// 🚚 Tanker Info borderRadius: BorderRadius.circular(8),
Row( border: Border.all(color: chipTextColor, width: 1),
crossAxisAlignment: CrossAxisAlignment.start, ),
children: [ child: Text(
const CircleAvatar( s,
radius: 18, style: fontTextStyle(10, chipTextColor, FontWeight.w400),
backgroundImage: AssetImage('images/avatar.png'), ),
), );
const SizedBox(width: 12), })
Expanded( .toList(),
child: Column( ),
const SizedBox(height: 16),
// 🚚 Tanker Info
Row(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text( Expanded(
"Ramesh Krishna", child: Column(
style: TextStyle( crossAxisAlignment: CrossAxisAlignment.start,
fontSize: 14, children: [
fontWeight: FontWeight.w600, Text(
color: Colors.black, widget.tankerDetails.tanker_name,
style: fontTextStyle(16, const Color(0xFF2A2A2A), FontWeight.w600),
),
SizedBox(height: 2),
Text(
widget.tankerDetails.type_of_water+' - '+widget.tankerDetails.capacity+' L',
style: fontTextStyle(10, const Color(0xFF646566), FontWeight.w400),
),
SizedBox(height: 2),
Text(
widget.tankerDetails.license_plate,
style: fontTextStyle(10, const Color(0xFF646566), FontWeight.w400),
),
],
), ),
), ),
SizedBox(height: 2),
Text( PopupMenuButton<String>(
widget.tankerDetails.tanker_name, // 🔁 Use `child:` so you can place any widget (your 3-dots image)
style: TextStyle( child: Padding(
fontSize: 16, padding: const EdgeInsets.symmetric(horizontal: 8.0, vertical: 8.0),
fontWeight: FontWeight.w700, child: Image.asset(
color: Colors.black, 'images/popup_menu.png', // your 3-dots image
width: 22,
height: 22,
// If you want to tint it like an icon:
color: Color(0XFF939495), // remove if you want original colors
colorBlendMode: BlendMode.srcIn,
),
), ),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
offset: const Offset(0, 40),
color: Colors.white,
elevation: 4,
onSelected: (value) {
if (value == 'edit') {
openTankerSimpleSheet(context);
} else if (value == 'disable') {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Disable selected')),
);
} else if (value == 'delete') {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Delete selected')),
);
}
},
itemBuilder: (context) => [
PopupMenuItem(
value: 'edit',
child: Row(
children: [
Image.asset(
'images/edit.png',
width: 20,
height: 20,
color: Color(0XFF646566), // tint (optional)
colorBlendMode: BlendMode.srcIn,
),
const SizedBox(width: 12),
Text(
'Edit',
style: fontTextStyle(14, const Color(0XFF646566), FontWeight.w400),
),
],
),
),
PopupMenuItem(
value: 'disable',
child: Row(
children: [
Image.asset(
'images/disable.png',
width: 20,
height: 20,
color: Color(0XFF646566), // tint (optional)
colorBlendMode: BlendMode.srcIn,
),
const SizedBox(width: 12),
Text(
'Disable',
style: fontTextStyle(14, const Color(0XFF646566), FontWeight.w400),
),
],
),
),
PopupMenuItem(
value: 'delete',
child: Row(
children: [
Image.asset(
'images/delete.png',
width: 20,
height: 20,
color: Color(0XFFE2483D), // red like your example
colorBlendMode: BlendMode.srcIn,
),
const SizedBox(width: 12),
Text(
'Delete',
style: fontTextStyle(14, const Color(0xFFE2483D), FontWeight.w400),
),
],
),
),
],
)
],
),
const SizedBox(height: 12),
Row(
children: [
Expanded(
child: ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xFFFFFFFF),
foregroundColor: const Color(0xFF515253),
padding: const EdgeInsets.symmetric(vertical: 10),
elevation: 0,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(24),
side: const BorderSide(color: Color(0xFF939495), width: 0.5),
),
// Or: side: const BorderSide(color: Color(0xFFC3C4C4), width: 1.2),
),
onPressed: () async {
// _showAssignTankerBottomSheet();
},
child: Text(
"Change Status",
style: fontTextStyle(14, const Color(0xFF515253), FontWeight.w500),
),
)
), ),
SizedBox(height: 2), SizedBox(width: 8),
Text( Expanded(
widget.tankerDetails.type_of_water+' - '+widget.tankerDetails.capacity+' L', child: ElevatedButton(
style: TextStyle( style: ElevatedButton.styleFrom(
fontSize: 12, backgroundColor: const Color(0xFF8270DB),
color: Colors.black54, foregroundColor: const Color(0xFFFFFFFF),
), padding: const EdgeInsets.symmetric(vertical: 10),
elevation: 0,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(24),
side: const BorderSide(color: Color(0xFF8270DB), width: 0.5),
),
// Or: side: const BorderSide(color: Color(0xFFC3C4C4), width: 1.2),
),
onPressed: () async {
// _showAssignTankerBottomSheet();
},
child: Text(
"Assign",
style: fontTextStyle(14, const Color(0xFFFFFFFF), FontWeight.w500),
),
)
), ),
], ],
), ),
), ],
Text(
widget.tankerDetails.license_plate,
style: TextStyle(
fontSize: 12,
color: Colors.black54,
fontWeight: FontWeight.w500,
),
),
],
),
const SizedBox(height: 24),
// 📍 Recent Trips
const Text(
"RECENT TRIPS",
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.w600,
color: Colors.black87,
), ),
), ),
const SizedBox(height: 12),
_buildTripCard( Padding(padding: EdgeInsets.all(16),
driverName: "Ramesh Krishna", child: Column(
time: "7:02 PM, 28 Jun 2025", crossAxisAlignment: CrossAxisAlignment.start,
from: "Bachupally Filling Station", children: [
to: "Akriti Heights", Text(
), "RECENT TRIPS",
const SizedBox(height: 8), style: fontTextStyle(10, const Color(0xFF343637), FontWeight.w600),
_buildTripCard( ),
driverName: "Ramesh Krishna", const SizedBox(height: 12),
time: "12:44 PM, 26 Jun 2025",
from: "Bachupally Filling Station", _buildTripCard(
to: "Akriti Heights", driverName: "Ramesh Krishna",
time: "7:02 PM, 28 Jun 2025",
from: "Bachupally Filling Station",
to: "Akriti Heights",
),
const SizedBox(height: 8),
_buildTripCard(
driverName: "Ramesh Krishna",
time: "12:44 PM, 26 Jun 2025",
from: "Bachupally Filling Station",
to: "Akriti Heights",
),
],
),
), ),
const SizedBox(height: 30),
], ],
), ),
), ),
), ),
)
); );
} }
@ -278,4 +836,35 @@ class _TankerDetailsPageState extends State<TankerDetailsPage> {
} }
// ====== Labeled Field Wrapper ======
class _LabeledField extends StatelessWidget {
final String label;
final Widget child;
const _LabeledField({required this.label, required this.child});
String _capFirstWord(String input) {
if (input.isEmpty) return input;
final i = input.indexOf(RegExp(r'\S'));
if (i == -1) return input;
return input.replaceRange(i, i + 1, input[i].toUpperCase());
}
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.only(bottom: 14.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
_capFirstWord(label),
style: fontTextStyle(12, const Color(0xFF515253), FontWeight.w600),
),
const SizedBox(height: 6),
child,
],
),
);
}
}

@ -9,6 +9,10 @@ class TankersModel {
String status=''; String status='';
String license_plate=''; String license_plate='';
String supplier_name=''; String supplier_name='';
String manufacturing_year='';
String insurance_expiry='';
String tanker_type='';
String pumping_fee='';
List<dynamic> availability= []; List<dynamic> availability= [];
TankersModel(); TankersModel();
@ -25,7 +29,10 @@ class TankersModel {
rtvm.price = json['price'] ?? ''; rtvm.price = json['price'] ?? '';
rtvm.delivery_fee = json['delivery_fee'] ?? ''; rtvm.delivery_fee = json['delivery_fee'] ?? '';
rtvm.availability = json['availability'] ?? []; rtvm.availability = json['availability'] ?? [];
rtvm.manufacturing_year = json['manufacturing_year'] ?? '';
rtvm.insurance_expiry = json['insurance_exp_date'] ?? '';
rtvm.tanker_type = json['tanker_type'] ?? '';
rtvm.pumping_fee = json['pumping_fee'] ?? '';
return rtvm; return rtvm;
} }
Map<String, dynamic> toJson() => { Map<String, dynamic> toJson() => {

Loading…
Cancel
Save