master
Sneha 1 month ago
parent e4d998cf43
commit e054f2eb92

@ -34,6 +34,8 @@ class DashboardScreen extends StatefulWidget {
} }
class _DashboardScreenState extends State<DashboardScreen> { class _DashboardScreenState extends State<DashboardScreen> {
int _currentIndex = 0; int _currentIndex = 0;
final ImagePicker _picker = ImagePicker(); final ImagePicker _picker = ImagePicker();
final storage = FlutterSecureStorage( final storage = FlutterSecureStorage(
@ -45,16 +47,58 @@ class _DashboardScreenState extends State<DashboardScreen> {
final GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>(); final GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>();
final TextEditingController orderSearchController = TextEditingController(); final TextEditingController orderSearchController = TextEditingController();
final TextEditingController planSearchController = TextEditingController(); final TextEditingController planSearchController = TextEditingController();
Widget _getScreen(){
switch(_currentIndex){
case 0:
return HomeScreen();
case 1:
return AllOrders(
navigationFrom: 'bottombar',
externalSearchController: orderSearchController,
);
case 2:
return AllPlans(
navigationFrom: 'bottombar',
);
case 3:
return ResourcesMainScreen();
case 4:
return FinancialMainScreen();
default:
return HomeScreen();
}
}
// Define a list of widgets for each screen // Define a list of widgets for each screen
final List<Widget> _screens = [ /* final List<Widget> _screens = [
HomeScreen(), HomeScreen(),
AllOrders(navigationFrom: 'bottombar',), AllOrders(
navigationFrom: 'bottombar',
externalSearchController: orderSearchController,
),
AllPlans(navigationFrom: 'bottombar',), AllPlans(navigationFrom: 'bottombar',),
ResourcesMainScreen(), ResourcesMainScreen(),
FinancialMainScreen(), FinancialMainScreen(),
]; ];*/
late List<Widget> _screens;
// List of bottom navigation bar items // List of bottom navigation bar items
final List<BottomNavigationBarItem> _bottomNavItems = [ final List<BottomNavigationBarItem> _bottomNavItems = [
BottomNavigationBarItem( BottomNavigationBarItem(
@ -275,17 +319,25 @@ class _DashboardScreenState extends State<DashboardScreen> {
PreferredSizeWidget? _buildAppBar() { PreferredSizeWidget? _buildAppBar() {
// 👉 Orders tab (index 1): Custom Search AppBar // 👉 Orders tab (index 1): Custom Search AppBar
if (_currentIndex == 1) { if (_currentIndex == 1) {
return SearchOrderAppBar( return
SearchOrderAppBar(
controller: orderSearchController, controller: orderSearchController,
onBack: () { onBack: () {
setState(() { setState(() {
_currentIndex = 0; // go back to Home _currentIndex = 0; // go back to Home
}); });
}, },
onHelp: () {
// handle help button action onHelp: () {},
debugPrint("Help tapped");
onSearch: (value){
orderSearchController.text = value;
}, },
); );
} }
@ -727,7 +779,7 @@ class _DashboardScreenState extends State<DashboardScreen> {
), ),
body: body:
_screens[_currentIndex], _getScreen(),
/*_body(),,*/ /*_body(),,*/
bottomNavigationBar: Padding( bottomNavigationBar: Padding(
padding:EdgeInsets.fromLTRB(0, 0, 0, 0) , padding:EdgeInsets.fromLTRB(0, 0, 0, 0) ,
@ -795,6 +847,8 @@ class _HomeScreenState extends State<HomeScreen> {
double yesterdayRevenue = 0; double yesterdayRevenue = 0;
String revenueChangeText = "0% from yesterday"; String revenueChangeText = "0% from yesterday";
Color revenueChangeColor = Colors.grey; Color revenueChangeColor = Colors.grey;
int orderRequestsCount = 0;
int planRequestsCount = 0;
@override @override
void initState() { void initState() {
@ -803,6 +857,94 @@ class _HomeScreenState extends State<HomeScreen> {
_fetchTankerCounts(); _fetchTankerCounts();
_fetchTodayOrders(); _fetchTodayOrders();
_fetchTodayRevenue(); _fetchTodayRevenue();
_fetchOrderRequestsCount();
_fetchPlanRequestsCount();
}
Future<void> _fetchOrderRequestsCount() async {
try{
final response =
await AppSettings.getOrderRequestsFromUsers();
final List data =
jsonDecode(response)['data'] ?? [];
int count = 0;
for(var order in data){
String status =
(order['status'] ?? "")
.toString()
.toLowerCase();
/// Only pending/new requests
if(status == "pending"){
count++;
}
}
if(!mounted) return;
setState(() {
orderRequestsCount = count;
});
}
catch(e){
debugPrint("Order requests error $e");
}
}
Future<void> _fetchPlanRequestsCount() async {
try{
final response =
await AppSettings.getPlanRequestsFromUsers();
final List data =
jsonDecode(response)['data'] ?? [];
int count = 0;
for(var plan in data){
String status =
(plan['status'] ?? "")
.toString()
.toLowerCase();
/// only pending requests
if(status == "pending"){
count++;
}
}
if(!mounted) return;
setState(() {
planRequestsCount = count;
});
}
catch(e){
debugPrint("Plan requests error $e");
}
} }
@ -1106,11 +1248,16 @@ class _HomeScreenState extends State<HomeScreen> {
subtitle: revenueChangeText, subtitle: revenueChangeText,
icon: Icons.currency_rupee, icon: Icons.currency_rupee,
onTap: () { onTap: () {
Navigator.push(
context, final dashboardState =
MaterialPageRoute( context.findAncestorStateOfType<_DashboardScreenState>();
builder: (context) => FinancialMainScreen()),
); dashboardState?.setState(() {
dashboardState._currentIndex = 4;
});
}, },
), ),
], ],
@ -1241,7 +1388,9 @@ class _HomeScreenState extends State<HomeScreen> {
}, },
child: RequestCard( child: RequestCard(
title: "Order Requests", title: "Order Requests",
subtitle: "1 new request", subtitle: orderRequestsCount > 0
? "$orderRequestsCount new request"
: null,
), ),
), ),
), ),
@ -1256,7 +1405,9 @@ class _HomeScreenState extends State<HomeScreen> {
}, },
child: RequestCard( child: RequestCard(
title: "Plan Requests", title: "Plan Requests",
subtitle: "2 new request", subtitle: planRequestsCount > 0
? "$planRequestsCount new request"
: null,
), ),
) )
), ),
@ -1347,12 +1498,12 @@ class DashboardCard extends StatelessWidget {
class RequestCard extends StatelessWidget { class RequestCard extends StatelessWidget {
final String title; final String title;
final String subtitle; final String? subtitle;
const RequestCard({ const RequestCard({
super.key, super.key,
required this.title, required this.title,
required this.subtitle, this.subtitle,
}); });
@override @override
@ -1372,17 +1523,21 @@ class RequestCard extends StatelessWidget {
style: const TextStyle(fontSize: 14, fontWeight: FontWeight.w600), style: const TextStyle(fontSize: 14, fontWeight: FontWeight.w600),
), ),
const SizedBox(height: 8), const SizedBox(height: 8),
Container( if(subtitle != null)
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), Container(
decoration: BoxDecoration( padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
color: Colors.green[50], decoration: BoxDecoration(
borderRadius: BorderRadius.circular(8), color: Colors.green[50],
), borderRadius: BorderRadius.circular(8),
child: Text( ),
subtitle, child: Text(
style: const TextStyle(color: Colors.green, fontSize: 12), subtitle!,
style: const TextStyle(
color: Colors.green,
fontSize: 12,
),
),
), ),
),
], ],
), ),
); );

@ -196,6 +196,8 @@ class AppSettings{
static String connectedCustomersUrl = host + 'connectedCustomers'; static String connectedCustomersUrl = host + 'connectedCustomers';
static String acceptRequestUrl = host +"friend-request/accept"; static String acceptRequestUrl = host +"friend-request/accept";
static String rejectRequestUrl = host +"friend-request/reject"; static String rejectRequestUrl = host +"friend-request/reject";
static String getDriverTripsUrl = host +"getdeliveryboybookings";
static int driverAvailableCount = 0; static int driverAvailableCount = 0;
static int driverOnDeliveryCount = 0; static int driverOnDeliveryCount = 0;
@ -1652,6 +1654,22 @@ class AppSettings{
return false; return false;
} }
} }
static Future<String> getDriverTrips(
String mobile) async {
var uri = Uri.parse(getDriverTripsUrl+'/'+mobile );
var response =
await http.get(
uri,
headers:
await buildRequestHeaders()
);
return response.body;
}
/*Apis ends here*/ /*Apis ends here*/
//save data local //save data local
@ -1884,44 +1902,107 @@ class AppSettings{
); );
} }
static supplierAppBarWithActionsText(String title,context) { static supplierAppBarWithActionsText(
String title,
BuildContext context,
{dynamic result}
){
title = title ?? ''; title = title ?? '';
return AppBar( return AppBar(
backgroundColor: Colors.white, backgroundColor: Colors.white,
elevation: 0, elevation: 0,
scrolledUnderElevation: 0, scrolledUnderElevation: 0,
titleSpacing: 0, titleSpacing: 0,
title: Text( title: Text(
title, title,
style: fontTextStyle(16, Color(0XFF2A2A2A), FontWeight.w600),
style: fontTextStyle(
16,
Color(0XFF2A2A2A),
FontWeight.w600),
), ),
iconTheme: IconThemeData(color: Color(0XFF2A2A2A)),
iconTheme:
IconThemeData(
color: Color(0XFF2A2A2A)),
leading: GestureDetector( leading: GestureDetector(
onTap: () { onTap: () {
Navigator.pop(context);
Navigator.pop(
context,
result // IMPORTANT
);
}, },
child: Padding( child: Padding(
padding: padding:
const EdgeInsets.fromLTRB(8, 8, 8, 8), // Add padding if needed EdgeInsets.fromLTRB(
8,8,8,8),
child: Image.asset( child: Image.asset(
'images/backbutton_appbar.png', // Replace with your image path
'images/backbutton_appbar.png',
fit: BoxFit.contain, fit: BoxFit.contain,
color: Color(0XFF2A2A2A), color: Color(0XFF2A2A2A),
height: 24, height: 24,
width: 24, width: 24,
), ),
), ),
), ),
actions: [ actions: [
Padding(padding: EdgeInsets.all(8),
Padding(
padding: EdgeInsets.all(8),
child: TextButton( child: TextButton(
onPressed: () async {
}, onPressed: (){},
child: Text('HELP',
style: fontTextStyle(14,primaryColor,FontWeight.w600),), child: Text(
),)
'HELP',
style: fontTextStyle(
14,
primaryColor,
FontWeight.w600),
),
),
)
], ],
); );
} }
static appBarWithoutActions(String title) { static appBarWithoutActions(String title) {

@ -14,20 +14,31 @@ import 'change_driver.dart';
class AllOrders extends StatefulWidget { class AllOrders extends StatefulWidget {
final String navigationFrom; final String navigationFrom;
final TextEditingController? externalSearchController;
AllOrders({ AllOrders({
super.key, super.key,
required this.navigationFrom, required this.navigationFrom,
this.externalSearchController,
}); });
@override @override
State<AllOrders> createState() => _AllOrdersState(); State<AllOrders> createState() => _AllOrdersState();
} }
class _AllOrdersState extends State<AllOrders> { class _AllOrdersState extends State<AllOrders> {
final TextEditingController searchController = TextEditingController(); late TextEditingController searchController;
bool isLoading=false; bool isLoading=false;
final List<OrdersModel> orders =[]; final List<OrdersModel> orders =[];
List<OrdersModel> allOrders = [];
List<OrdersModel> ordersList = []; List<OrdersModel> ordersList = [];
bool isSearching = false;
int todaysOrdersCount = 0; int todaysOrdersCount = 0;
int boreWaterCount = 0; int boreWaterCount = 0;
int drinkingWaterCount = 0; int drinkingWaterCount = 0;
@ -38,6 +49,26 @@ class _AllOrdersState extends State<AllOrders> {
void initState() { void initState() {
super.initState(); super.initState();
_fetchOrders(); _fetchOrders();
searchController =
widget.externalSearchController ??
TextEditingController();
searchController.addListener(_searchOrders);
}
@override
void dispose(){
searchController.removeListener(_searchOrders);
if(widget.externalSearchController == null){
searchController.dispose();
}
super.dispose();
} }
Future<void> _fetchOrders() async { Future<void> _fetchOrders() async {
@ -50,7 +81,8 @@ class _AllOrdersState extends State<AllOrders> {
.toList(); .toList();
if (!mounted) return; if (!mounted) return;
setState(() { setState(() {
ordersList = data; allOrders = data;
ordersList = List.from(data);
ordersList.sort((a, b) { ordersList.sort((a, b) {
try { try {
@ -91,6 +123,92 @@ class _AllOrdersState extends State<AllOrders> {
} }
} }
void _searchOrders() {
String query = searchController.text.trim().toLowerCase();
if(query.isEmpty){
setState(() {
ordersList = List.from(allOrders);
isSearching = false;
});
return;
}
List<OrdersModel> filtered = [];
for(var order in allOrders){
String building = order.building_name.toString().toLowerCase();
String address = order.displayAddress.toString().toLowerCase();
String water = order.type_of_water.toString().toLowerCase();
String status = order.status.toString().toLowerCase();
String capacity = order.capacity.toString().toLowerCase();
String amount = order.quoted_amount.toString().toLowerCase();
String date = order.date.toString().toLowerCase();
String time = order.time.toString().toLowerCase();
String driver = order.delivery_agent_name.toString().toLowerCase();
String tanker = order.tanker_name.toString().toLowerCase();
/// normalize status
if(status=='advance_paid' || status=='accepted'){
status='pending';
}
if(status=='deliveryboy_assigned' || status=='tanker_assigned'){
status='assigned';
}
if(status=='delivered'){
status='completed';
}
if(
building.contains(query) ||
address.contains(query) ||
water.contains(query) ||
status.contains(query) ||
capacity.contains(query) ||
amount.contains(query) ||
date.contains(query) ||
time.contains(query) ||
driver.contains(query) ||
tanker.contains(query)
){
filtered.add(order);
}
}
if(!mounted) return;
setState(() {
ordersList = filtered;
isSearching = true;
});
}
void sortOrders(String type) { void sortOrders(String type) {
setState(() { setState(() {
selectedFilter = type; selectedFilter = type;
@ -133,7 +251,7 @@ class _AllOrdersState extends State<AllOrders> {
Widget build(BuildContext context) { Widget build(BuildContext context) {
// Group orders by date // Group orders by date
final Map<String, List<OrdersModel>> groupedOrders = {}; Map<String, List<OrdersModel>> groupedOrders = {};
String formatOrderDate(String? dateStr) { String formatOrderDate(String? dateStr) {
if (dateStr == null || dateStr.trim().isEmpty) { if (dateStr == null || dateStr.trim().isEmpty) {
return ""; return "";
@ -157,17 +275,52 @@ class _AllOrdersState extends State<AllOrders> {
backgroundColor: Color(0XFFF2F2F2), backgroundColor: Color(0XFFF2F2F2),
appBar: widget.navigationFrom.toString().toLowerCase()=='dashboard'? appBar: widget.navigationFrom.toString().toLowerCase()=='dashboard'?
SearchOrderAppBar( SearchOrderAppBar(
controller: searchController, controller: searchController,
onBack: () => Navigator.pop(context), onBack: () => Navigator.pop(context),
onHelp: () {}, onHelp: () {},
onSearch: (value){
_searchOrders();
},
):null, ):null,
body: isLoading body: isLoading
? const Center(child: CircularProgressIndicator()) ? const Center(child: CircularProgressIndicator())
: ordersList.isEmpty?Center( : ordersList.isEmpty ?
child: Text(
'No Data Available', Center(
style: fontTextStyle(16,Color(0XFF000000),FontWeight.w700),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children:[
Icon(Icons.search_off,size:60,color:Colors.grey),
SizedBox(height:10),
Text(
isSearching?
"No matching orders found":
"No Data Available",
style: fontTextStyle(16,Color(0XFF000000),FontWeight.w700),
),
],
), ),
):SingleChildScrollView( ):SingleChildScrollView(
child: child:
Padding( Padding(

@ -1,94 +1,223 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:supplier_new/common/settings.dart'; import 'package:supplier_new/common/settings.dart';
class SearchOrderAppBar extends StatelessWidget implements PreferredSizeWidget { class SearchOrderAppBar extends StatefulWidget implements PreferredSizeWidget {
final TextEditingController controller; final TextEditingController controller;
final VoidCallback onBack; final VoidCallback onBack;
final VoidCallback onHelp; final VoidCallback onHelp;
final Function(String)? onSearch;
const SearchOrderAppBar({ const SearchOrderAppBar({
super.key, super.key,
required this.controller, required this.controller,
required this.onBack, required this.onBack,
required this.onHelp, required this.onHelp,
required this.onSearch,
}); });
@override
State<SearchOrderAppBar> createState() => _SearchOrderAppBarState();
@override
Size get preferredSize => const Size.fromHeight(kToolbarHeight);
}
class _SearchOrderAppBarState extends State<SearchOrderAppBar>{
late VoidCallback _listener;
@override
void initState(){
super.initState();
_listener = (){
if(!mounted) return;
setState((){});
};
widget.controller.addListener(_listener);
}
@override
void dispose(){
widget.controller.removeListener(_listener);
super.dispose();
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return AppBar( return AppBar(
backgroundColor: Colors.white, backgroundColor: Colors.white,
scrolledUnderElevation: 0, scrolledUnderElevation: 0,
elevation: 0, elevation: 0,
leading: GestureDetector( leading: GestureDetector(
onTap: onBack, // 👉 Controlled by parent
onTap: widget.onBack,
child: Padding( child: Padding(
padding: const EdgeInsets.all(8.0), padding: const EdgeInsets.all(8.0),
child: Image.asset( child: Image.asset(
'images/backbutton_appbar.png', 'images/backbutton_appbar.png',
height: 24, height: 24,
width: 24, width: 24,
fit: BoxFit.contain, fit: BoxFit.contain,
), ),
), ),
), ),
titleSpacing: 0, titleSpacing: 0,
title: Container( title: Container(
height: 40, height: 40,
decoration: BoxDecoration( decoration: BoxDecoration(
color: Colors.white, color: Colors.white,
borderRadius: BorderRadius.circular(22), borderRadius: BorderRadius.circular(22),
border: Border.all(color: const Color(0XFF939495)), border: Border.all(color: const Color(0XFF939495)),
), ),
child: TextField( child: TextField(
controller: controller,
controller: widget.controller,
onChanged: widget.onSearch,
decoration: InputDecoration( decoration: InputDecoration(
hintText: "Search order", hintText: "Search order",
hintStyle: fontTextStyle( hintStyle: fontTextStyle(
16, 16,
const Color(0XFF646566), const Color(0XFF646566),
FontWeight.w400, FontWeight.w400,
), ),
prefixIcon: SizedBox( prefixIcon: SizedBox(
height: 20, height: 20,
width: 20, width: 20,
child: Padding( child: Padding(
padding: const EdgeInsets.all(8.0), padding: const EdgeInsets.all(8.0),
child: Image.asset( child: Image.asset(
'images/search.png', 'images/search.png',
fit: BoxFit.contain, fit: BoxFit.contain,
), ),
), ),
), ),
/// CLEAR BUTTON ADDED HERE
suffixIcon: widget.controller.text.isNotEmpty
? IconButton(
icon: const Icon(
Icons.close,
size:18,
color: Color(0XFF646566)
),
onPressed: (){
widget.controller.clear();
},
)
: null,
border: InputBorder.none, border: InputBorder.none,
contentPadding: const EdgeInsets.symmetric(vertical: 0), contentPadding: const EdgeInsets.symmetric(vertical: 0),
), ),
style: fontTextStyle( style: fontTextStyle(
16, 16,
const Color(0XFF2A2A2A), const Color(0XFF2A2A2A),
FontWeight.w400, FontWeight.w400,
), ),
onSubmitted: (value) {
debugPrint("Search Orders: $value");
},
), ),
), ),
actions: [ actions: [
Padding( Padding(
padding: const EdgeInsets.all(8.0), padding: const EdgeInsets.all(8.0),
child: GestureDetector( child: GestureDetector(
onTap: onHelp, // 👉 Controlled by parent
onTap: widget.onHelp,
child: Image.asset( child: Image.asset(
'images/help_appbar.png', 'images/help_appbar.png',
height: 24, height: 24,
width: 24, width: 24,
fit: BoxFit.contain, fit: BoxFit.contain,
), ),
), ),
), ),
], ],
); );
} }
@override }
Size get preferredSize => const Size.fromHeight(kToolbarHeight);
}

@ -1,5 +1,8 @@
import 'dart:convert';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:supplier_new/resources/driver_trips_model.dart';
import 'package:supplier_new/resources/resources_drivers.dart'; import 'package:supplier_new/resources/resources_drivers.dart';
import '../common/settings.dart'; import '../common/settings.dart';
@ -24,30 +27,32 @@ class _DriverDetailsPageState extends State<DriverDetailsPage> {
final _mobileCtrl = TextEditingController(); final _mobileCtrl = TextEditingController();
final _altMobileCtrl = TextEditingController(); final _altMobileCtrl = TextEditingController();
final _locationCtrl = TextEditingController(); final _locationCtrl = TextEditingController();
final _licenseController = TextEditingController();
final _experienceController = TextEditingController();
final GlobalKey<FormState> _formKey = GlobalKey<FormState>(); final GlobalKey<FormState> _formKey = GlobalKey<FormState>();
// Unused in UI but kept if you later need them // Unused in UI but kept if you later need them
final _commissionCtrl = TextEditingController(); final _commissionCtrl = TextEditingController();
final _joinDateCtrl = TextEditingController(); final _joinDateCtrl = TextEditingController();
bool isLoading=false; bool isLoading=false;
List<DriverTripsModel> driverTripsList = [];
bool isTripsLoading = false;
// Dropdown state // Dropdown state
String? _status; // 'available' | 'on delivery' | 'offline' String? _status; // 'available' | 'on delivery' | 'offline'
final List<String> _statusOptions = const [ final List<String> _statusOptions = const [
'available', 'Available',
'on delivery', 'On delivery',
'offline' 'Offline'
]; ];
String? selectedLicense; /*String? selectedLicense;
final List<String> licenseNumbers = const [ final List<String> licenseNumbers = const [
'DL-042019-9876543', 'DL-042019-9876543',
'DL-052020-1234567', 'DL-052020-1234567',
'DL-072021-7654321', 'DL-072021-7654321',
]; ];
*/
String? selectedExperience; // years as string
final List<String> yearOptions =
List<String>.generate(41, (i) => '$i'); // 0..40
String? _required(String? v, {String field = "This field"}) { String? _required(String? v, {String field = "This field"}) {
if (v == null || v.trim().isEmpty) return "$field is required"; if (v == null || v.trim().isEmpty) return "$field is required";
@ -60,6 +65,52 @@ class _DriverDetailsPageState extends State<DriverDetailsPage> {
return null; return null;
} }
@override
void initState(){
super.initState();
_fetchDriverTrips();
}
Future<void> _fetchDriverTrips() async {
setState(() => isTripsLoading = true);
try{
String response =
await AppSettings.getDriverTrips(
widget.driverDetails.phone_number
);
final data =
(jsonDecode(response)['data'] as List)
.map((e)=>DriverTripsModel.fromJson(e))
.toList();
setState(() {
driverTripsList = data;
isTripsLoading = false;
});
}
catch(e){
print(e);
setState(() =>
isTripsLoading = false);
}
}
String? fitToOption(String? incoming, List<String> options) { String? fitToOption(String? incoming, List<String> options) {
if (incoming == null) return null; if (incoming == null) return null;
final inc = incoming.trim(); final inc = incoming.trim();
@ -106,14 +157,14 @@ class _DriverDetailsPageState extends State<DriverDetailsPage> {
// Build payload (adjust keys to your API if needed) // Build payload (adjust keys to your API if needed)
final payload = <String, dynamic>{ final payload = <String, dynamic>{
"name": _nameCtrl.text.trim(), "name": _nameCtrl.text.trim(),
"license_number": selectedLicense ?? "", "license_number": _licenseController.text ?? "",
"address": _locationCtrl.text.trim().isEmpty "address": _locationCtrl.text.trim().isEmpty
? AppSettings.userAddress ? AppSettings.userAddress
: _locationCtrl.text.trim(), : _locationCtrl.text.trim(),
"supplier_name": AppSettings.userName, "supplier_name": AppSettings.userName,
"phone": _mobileCtrl.text.trim(), "phone": _mobileCtrl.text.trim(),
"alternativeContactNumber": _altMobileCtrl.text.trim(), "alternativeContactNumber": _altMobileCtrl.text.trim(),
"years_of_experience": selectedExperience ?? "", "years_of_experience": _experienceController.text ?? "",
"status": _status ?? "available", "status": _status ?? "available",
}; };
@ -142,8 +193,8 @@ class _DriverDetailsPageState extends State<DriverDetailsPage> {
_mobileCtrl.text = widget.driverDetails.phone_number ?? ''; _mobileCtrl.text = widget.driverDetails.phone_number ?? '';
_altMobileCtrl.text = widget.driverDetails.alt_phone_number ?? ''; _altMobileCtrl.text = widget.driverDetails.alt_phone_number ?? '';
_locationCtrl.text = widget.driverDetails.address ?? ''; _locationCtrl.text = widget.driverDetails.address ?? '';
selectedExperience = fitToOption(widget.driverDetails.years_of_experience, yearOptions); _experienceController.text = widget.driverDetails.years_of_experience ?? '';
selectedLicense = fitToOption(widget.driverDetails.license_number, licenseNumbers); _licenseController.text = widget.driverDetails.license_number ?? '';
_status = fitToOption(widget.driverDetails.status, _statusOptions); _status = fitToOption(widget.driverDetails.status, _statusOptions);
await showModalBottomSheet( await showModalBottomSheet(
@ -209,62 +260,61 @@ class _DriverDetailsPageState extends State<DriverDetailsPage> {
_LabeledField( _LabeledField(
label: "Driver License Number *", label: "Driver License Number *",
child: DropdownButtonFormField<String>( child: TextFormField(
value: selectedLicense, controller: _licenseController, // create controller
items: licenseNumbers
.map((t) =>
DropdownMenuItem(value: t, child: Text(t)))
.toList(),
onChanged: (v) => setState(() => selectedLicense = v),
validator: (v) => v == null || v.isEmpty
? "Driver License required"
: null,
isExpanded: true,
alignment: Alignment.centerLeft,
hint: Text(
"Select License Number",
style: fontTextStyle(
14, const Color(0xFF939495), FontWeight.w400),
),
icon: Image.asset('images/downarrow.png',
width: 16, height: 16),
decoration: const InputDecoration( decoration: const InputDecoration(
border: OutlineInputBorder(), border: OutlineInputBorder(),
isDense: false, hintText: "Enter License Number",
contentPadding: EdgeInsets.symmetric( contentPadding:
horizontal: 12, vertical: 14), EdgeInsets.symmetric(horizontal: 12, vertical: 14),
), ),
style: fontTextStyle(
14, const Color(0xFF2A2A2A), FontWeight.w400),
autovalidateMode: AutovalidateMode.onUserInteraction,
validator: (value) {
if (value == null || value.trim().isEmpty) {
return "Driver License required";
}
return null;
},
), ),
), ),
_LabeledField( _LabeledField(
label: "Years of Experience *", label: "Years of Experience *",
child: DropdownButtonFormField<String>( child: TextFormField(
value: selectedExperience, controller: _experienceController,
items: yearOptions keyboardType: TextInputType.number,
.map((t) => autovalidateMode: AutovalidateMode.onUserInteraction,
DropdownMenuItem(value: t, child: Text(t)))
.toList(), inputFormatters: [
onChanged: (v) => FilteringTextInputFormatter.digitsOnly, // Only numbers
setState(() => selectedExperience = v), LengthLimitingTextInputFormatter(2), // Max 2 digits
validator: (v) => v == null || v.isEmpty ],
? "Experience is required"
: null,
isExpanded: true,
alignment: Alignment.centerLeft,
hint: Text(
"Years",
style: fontTextStyle(
14, const Color(0xFF939495), FontWeight.w400),
),
icon: Image.asset('images/downarrow.png',
width: 16, height: 16),
decoration: const InputDecoration( decoration: const InputDecoration(
border: OutlineInputBorder(), border: OutlineInputBorder(),
isDense: false, hintText: "Enter Years",
contentPadding: EdgeInsets.symmetric( contentPadding:
horizontal: 12, vertical: 14), EdgeInsets.symmetric(horizontal: 12, vertical: 14),
), ),
style: fontTextStyle(
14, const Color(0xFF2A2A2A), FontWeight.w400),
validator: (value) {
if (value == null || value.trim().isEmpty) {
return "Experience is required";
}
if (value.length > 2) {
return "Only 2 digits allowed";
}
return null;
},
), ),
), ),
@ -525,10 +575,19 @@ class _DriverDetailsPageState extends State<DriverDetailsPage> {
child: child:
Scaffold( Scaffold(
backgroundColor: Color(0XFFFFFFFF), backgroundColor: Color(0XFFFFFFFF),
appBar: AppSettings.supplierAppBarWithActionsText( widget.driverDetails.driver_name.isNotEmpty
? widget.driverDetails.driver_name[0].toUpperCase() + appBar: AppSettings.supplierAppBarWithActionsText(
widget.driverDetails.driver_name.substring(1)
: '', context), widget.driverDetails.driver_name.isNotEmpty
? widget.driverDetails.driver_name[0].toUpperCase() +
widget.driverDetails.driver_name.substring(1)
: '',
context,
result: true // ADD THIS
),
body: SingleChildScrollView( body: SingleChildScrollView(
child: Padding( child: Padding(
padding: const EdgeInsets.all(0), padding: const EdgeInsets.all(0),
@ -687,7 +746,7 @@ class _DriverDetailsPageState extends State<DriverDetailsPage> {
const SizedBox(height: 8), const SizedBox(height: 8),
// buttons row // buttons row
OutlinedButton( /* OutlinedButton(
style: OutlinedButton.styleFrom( style: OutlinedButton.styleFrom(
foregroundColor: Color(0XFF515253), foregroundColor: Color(0XFF515253),
backgroundColor: Color(0xFFF3F1FB), backgroundColor: Color(0xFFF3F1FB),
@ -712,7 +771,7 @@ class _DriverDetailsPageState extends State<DriverDetailsPage> {
], ],
), ),
), ),
const SizedBox(height: 24), const SizedBox(height: 24),*/
// 🪪 License Card // 🪪 License Card
ClipRRect( ClipRRect(
@ -761,13 +820,13 @@ class _DriverDetailsPageState extends State<DriverDetailsPage> {
), ),
], ],
), ),
Align( /*Align(
alignment: Alignment.bottomRight, alignment: Alignment.bottomRight,
child: Text( child: Text(
"Expires on 29/02/2028", "Expires on 29/02/2028",
style: fontTextStyle(10, const Color(0xFFFFFFFF), FontWeight.w300), style: fontTextStyle(10, const Color(0xFFFFFFFF), FontWeight.w300),
), ),
), ),*/
], ],
) )
], ],
@ -810,25 +869,85 @@ class _DriverDetailsPageState extends State<DriverDetailsPage> {
], ],
), ),
const SizedBox(height: 24), const SizedBox(height:24),
Padding(padding: EdgeInsets.all(16), Padding(
child: Column( padding: const EdgeInsets.all(16),
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"RECENT TRIPS",
style:fontTextStyle(10, const Color(0xFF343637), FontWeight.w600),
),
const SizedBox(height: 12),
_buildTripCard("Drinking Water - 10,000 L", "7:02 PM, 28 Jun 2025"), child: Column(
const SizedBox(height: 8),
_buildTripCard("Drinking Water - 10,000 L", "7:02 PM, 28 Jun 2025"), crossAxisAlignment:
const SizedBox(height: 8), CrossAxisAlignment.start,
_buildTripCard("Drinking Water - 10,000 L", "7:02 PM, 28 Jun 2025"),
], children: [
),)
Text(
"RECENT TRIPS",
style: fontTextStyle(
10,
const Color(0xFF343637),
FontWeight.w600),
),
const SizedBox(height:12),
isTripsLoading
? const Center(
child:
CircularProgressIndicator())
: driverTripsList.isEmpty
? Center(
child: Padding(
padding:
const EdgeInsets.symmetric(
vertical:12),
child: Text(
"No trips found",
style: fontTextStyle(
12,
const Color(0xFF939495),
FontWeight.w500),
),
),
)
: ListView.separated(
shrinkWrap:true,
physics:
const NeverScrollableScrollPhysics(),
itemCount:
driverTripsList.length,
separatorBuilder:
(_,__) =>
const SizedBox(height:10),
itemBuilder:(context,index){
final trip =
driverTripsList[index];
return _buildTripCard(trip);
},
)
],
),
),
], ],
), ),
@ -837,38 +956,198 @@ class _DriverDetailsPageState extends State<DriverDetailsPage> {
)); ));
} }
Widget _buildTripCard(String title, String time) { Widget _buildTripCard(
DriverTripsModel trip){
Color statusColor =
trip.status == "delivered"
? Colors.green
: trip.status == "cancelled"
? Colors.red
: Colors.orange;
return Container( return Container(
padding: const EdgeInsets.all(12),
padding:
const EdgeInsets.all(12),
decoration: BoxDecoration( decoration: BoxDecoration(
color: const Color(0xFFFFFFFF),
borderRadius: BorderRadius.circular(8), color:
border: Border.all(color: Color(0XFFC3C4C4)), const Color(0xFFFFFFFF),
borderRadius:
BorderRadius.circular(8),
border: Border.all(
color:
const Color(0XFFC3C4C4)
),
), ),
child: Row(
child: Column(
crossAxisAlignment:
CrossAxisAlignment.start,
children: [ children: [
Image.asset('images/recent_trips.png', width: 28, height: 28), Row(
const SizedBox(width: 12),
Expanded( children: [
child: Column(
crossAxisAlignment: CrossAxisAlignment.start, Image.asset(
children: [ 'images/recent_trips.png',
Text( width:28,
title, height:28),
style:fontTextStyle(14, const Color(0xFF2D2E30), FontWeight.w500),
const SizedBox(width:10),
Expanded(
child: Column(
crossAxisAlignment:
CrossAxisAlignment.start,
children: [
Text(
trip.tankerName,
style: fontTextStyle(
14,
const Color(0xFF2D2E30),
FontWeight.w500),
),
Text(
trip.customerName,
style: fontTextStyle(
11,
const Color(0xFF646566),
FontWeight.w400),
),
],
),
),
Container(
padding:
const EdgeInsets.symmetric(
horizontal:6,
vertical:2),
decoration: BoxDecoration(
borderRadius:
BorderRadius.circular(4),
border: Border.all(
color: statusColor
)
), ),
const SizedBox(height: 2),
Text( child: Text(
time,
style:fontTextStyle(10, const Color(0xFF939495), FontWeight.w400), trip.status,
style: fontTextStyle(
10,
statusColor,
FontWeight.w400),
), ),
],
), )
],
), ),
const SizedBox(height:6),
Text(
"${trip.date}${trip.time}",
style: fontTextStyle(
11,
const Color(0xFF939495),
FontWeight.w400),
),
const SizedBox(height:4),
Text(
trip.address,
maxLines:2,
overflow:
TextOverflow.ellipsis,
style: fontTextStyle(
11,
const Color(0xFF646566),
FontWeight.w400),
),
const SizedBox(height:8),
Row(
mainAxisAlignment:
MainAxisAlignment.spaceBetween,
children: [
Text(
"${trip.price}",
style: fontTextStyle(
13,
const Color(0xFF2D2E30),
FontWeight.w600),
),
Text(
"Paid ₹${trip.amountPaid}",
style: fontTextStyle(
11,
Colors.green,
FontWeight.w500),
),
],
)
], ],
), ),
); );
} }
} }

@ -0,0 +1,76 @@
class DriverTripsModel {
String id = '';
String bookingId = '';
String tankerName = '';
String buildingName = '';
String address = '';
String date = '';
String time = '';
String waterType = '';
String capacity = '';
String price = '';
String status = '';
String customerName = '';
String amountPaid = '';
String amountDue = '';
String paymentStatus = '';
DriverTripsModel();
factory DriverTripsModel.fromJson(
Map<String,dynamic> json){
DriverTripsModel d =
DriverTripsModel();
d.id =
json['_id'] ?? '';
d.bookingId =
json['bookingid'] ?? '';
d.tankerName =
json['tankerName'] ?? '';
d.buildingName =
json['buildingName'] ?? '';
d.address =
json['address'] ?? '';
d.date =
json['dateOfOrder'] ?? '';
d.time =
json['time'] ?? '';
d.waterType =
json['typeofwater'] ?? '';
d.capacity =
json['capacity'] ?? '';
d.price =
json['price'] ?? '';
d.status =
json['orderStatus'] ?? '';
d.customerName =
json['customerName'] ?? '';
d.amountPaid =
json['amount_paid'] ?? '';
d.amountDue =
json['amount_due'] ?? '';
d.paymentStatus =
json['payment_status'] ?? '';
return d;
}
}

@ -3,13 +3,14 @@ class DriversModel {
String driver_name=''; String driver_name='';
String status=''; String status='';
String address=''; String address='';
String deliveries='13'; String deliveries='';
String commision=''; String commision='';
String years_of_experience=''; String years_of_experience='';
List<String> availability= ['filled', 'available']; List<String> availability= ['filled', 'available'];
String phone_number=''; String phone_number='';
String alt_phone_number=''; String alt_phone_number='';
String license_number=''; String license_number='';
String license_expiry_date='';
DriversModel(); DriversModel();
factory DriversModel.fromJson(Map<String, dynamic> json) { factory DriversModel.fromJson(Map<String, dynamic> json) {
@ -23,6 +24,8 @@ class DriversModel {
rtvm.alt_phone_number = json['alternativeContactNumber'] ?? ''; rtvm.alt_phone_number = json['alternativeContactNumber'] ?? '';
rtvm.years_of_experience = json['years_of_experience'] ?? ''; rtvm.years_of_experience = json['years_of_experience'] ?? '';
rtvm.license_number = json['license_number'] ?? ''; rtvm.license_number = json['license_number'] ?? '';
rtvm.deliveries = json['deliveries'] ?? '';
rtvm.license_expiry_date=json['license_number'] ?? '';
return rtvm; return rtvm;
} }
} }

@ -4,6 +4,7 @@ import 'package:flutter/services.dart';
import 'package:supplier_new/common/settings.dart'; import 'package:supplier_new/common/settings.dart';
import 'package:supplier_new/resources/driver_details.dart'; import 'package:supplier_new/resources/driver_details.dart';
import 'package:supplier_new/resources/drivers_model.dart'; import 'package:supplier_new/resources/drivers_model.dart';
import 'package:url_launcher/url_launcher.dart';
class FirstCharUppercaseFormatter extends TextInputFormatter { class FirstCharUppercaseFormatter extends TextInputFormatter {
const FirstCharUppercaseFormatter(); const FirstCharUppercaseFormatter();
@ -70,9 +71,9 @@ class _ResourcesDriverScreenState extends State<ResourcesDriverScreen> {
// Dropdown state // Dropdown state
String? _status; // 'available' | 'on delivery' | 'offline' String? _status; // 'available' | 'on delivery' | 'offline'
final List<String> _statusOptions = const [ final List<String> _statusOptions = const [
'available', 'Available',
'on delivery', 'On delivery',
'offline' 'Offline'
]; ];
String? selectedLicense; String? selectedLicense;
@ -179,14 +180,14 @@ class _ResourcesDriverScreenState extends State<ResourcesDriverScreen> {
// Build payload (adjust keys to your API if needed) // Build payload (adjust keys to your API if needed)
final payload = <String, dynamic>{ final payload = <String, dynamic>{
"Name": _nameCtrl.text.trim(), "Name": _nameCtrl.text.trim(),
"license_number": selectedLicense ?? "", "license_number": _licenseController.text ?? "",
"address": _locationCtrl.text.trim().isEmpty "address": _locationCtrl.text.trim().isEmpty
? AppSettings.userAddress ? AppSettings.userAddress
: _locationCtrl.text.trim(), : _locationCtrl.text.trim(),
"supplier_name": AppSettings.userName, "supplier_name": AppSettings.userName,
"phone": _mobileCtrl.text.trim(), "phone": _mobileCtrl.text.trim(),
"alternativeContactNumber": _altMobileCtrl.text.trim(), "alternativeContactNumber": _altMobileCtrl.text.trim(),
"years_of_experience": selectedExperience ?? "", "years_of_experience": _experienceController.text ?? "",
"status": _status ?? "available", "status": _status ?? "available",
}; };
@ -555,10 +556,10 @@ class _ResourcesDriverScreenState extends State<ResourcesDriverScreen> {
Text(driversList.length.toString(), Text(driversList.length.toString(),
style: fontTextStyle( style: fontTextStyle(
24, const Color(0xFF0D3771), FontWeight.w500)), 24, const Color(0xFF0D3771), FontWeight.w500)),
const SizedBox(height: 6), /* const SizedBox(height: 6),
Text('+1 since last month', Text('+1 since last month',
style: fontTextStyle( style: fontTextStyle(
10, const Color(0xFF646566), FontWeight.w400)), 10, const Color(0xFF646566), FontWeight.w400)),*/
], ],
), ),
], ],
@ -692,9 +693,9 @@ class _ResourcesDriverScreenState extends State<ResourcesDriverScreen> {
name: d.driver_name, name: d.driver_name,
status: d.status, status: d.status,
location: d.address, location: d.address,
deliveries: deliveries: int.tryParse(d.deliveries) ?? 0,
int.tryParse(d.deliveries) ?? 0,
commission: d.commision, commission: d.commision,
phone: d.phone_number, // ADD
), ),
); );
}, },
@ -758,6 +759,7 @@ class DriverCard extends StatelessWidget {
final String location; final String location;
final int deliveries; final int deliveries;
final String commission; final String commission;
final String phone;
const DriverCard({ const DriverCard({
super.key, super.key,
@ -766,6 +768,7 @@ class DriverCard extends StatelessWidget {
required this.location, required this.location,
required this.deliveries, required this.deliveries,
required this.commission, required this.commission,
required this.phone,
}); });
@override @override
@ -777,6 +780,19 @@ class DriverCard extends StatelessWidget {
_ => Colors.grey, _ => Colors.grey,
}; };
Future<void> _makePhoneCall(String phone) async {
final Uri url = Uri(
scheme: 'tel',
path: phone,
);
if(await canLaunchUrl(url)){
await launchUrl(url);
}
}
return Container( return Container(
padding: const EdgeInsets.all(14), padding: const EdgeInsets.all(14),
decoration: BoxDecoration( decoration: BoxDecoration(
@ -818,15 +834,23 @@ class DriverCard extends StatelessWidget {
), ),
], ],
), ),
Container( GestureDetector(
padding: const EdgeInsets.all(8), onTap: (){
decoration: const BoxDecoration( _makePhoneCall(phone);
color: Color(0xFFF5F6F6), },
shape: BoxShape.circle, child: Container(
padding: const EdgeInsets.all(8),
decoration: const BoxDecoration(
color: Color(0xFFF5F6F6),
shape: BoxShape.circle,
),
child: Image.asset(
"images/phone_icon.png",
width: 20,
height: 20,
),
), ),
child: )
Image.asset("images/phone_icon.png", width: 20, height: 20),
),
], ],
), ),
const SizedBox(height: 12), const SizedBox(height: 12),
@ -882,7 +906,7 @@ class DriverCard extends StatelessWidget {
),) ),)
], ],
), ),
const SizedBox(height: 12), /* const SizedBox(height: 12),
Row( Row(
mainAxisAlignment: MainAxisAlignment.end, mainAxisAlignment: MainAxisAlignment.end,
children: [ children: [
@ -910,7 +934,7 @@ class DriverCard extends StatelessWidget {
12, const Color(0xFFFFFFFF), FontWeight.w400)), 12, const Color(0xFFFFFFFF), FontWeight.w400)),
), ),
], ],
) )*/
], ],
), ),
); );

@ -485,10 +485,10 @@ class _ResourcesFleetScreenState extends State<ResourcesFleetScreen> {
tankersList.length.toString(), tankersList.length.toString(),
style: fontTextStyle(24, const Color(0xFF0D3771), FontWeight.w500), style: fontTextStyle(24, const Color(0xFF0D3771), FontWeight.w500),
), ),
const SizedBox(height: 6), /*const SizedBox(height: 6),
Text('+2 since last month', Text('+2 since last month',
style: fontTextStyle(10, const Color(0xFF646566), FontWeight.w400), style: fontTextStyle(10, const Color(0xFF646566), FontWeight.w400),
), ),*/
], ],
), ),
], ],

@ -517,10 +517,18 @@ class _SourceDetailsScreenState extends State<SourceDetailsScreen> {
}, },
child:Scaffold( child:Scaffold(
backgroundColor: Colors.white, backgroundColor: Colors.white,
appBar:AppSettings.supplierAppBarWithActionsText( widget.sourceDetails.source_name.isNotEmpty appBar: AppSettings.supplierAppBarWithActionsText(
? widget.sourceDetails.source_name[0].toUpperCase() +
widget.sourceDetails.source_name.substring(1) widget.sourceDetails.source_name.isNotEmpty
: '', context), ? widget.sourceDetails.source_name[0].toUpperCase() +
widget.sourceDetails.source_name.substring(1)
: '',
context,
result: true // ADD THIS
),
body: SingleChildScrollView( body: SingleChildScrollView(
child: child:
Column( Column(
@ -708,7 +716,7 @@ class _SourceDetailsScreenState extends State<SourceDetailsScreen> {
// Filling & Wait time cards // Filling & Wait time cards
Padding( /* Padding(
padding: const EdgeInsets.symmetric(horizontal: 16), padding: const EdgeInsets.symmetric(horizontal: 16),
child: Row( child: Row(
children: [ children: [
@ -810,7 +818,7 @@ class _SourceDetailsScreenState extends State<SourceDetailsScreen> {
); );
}, },
), ),
const SizedBox(height: 20), const SizedBox(height: 20),*/
],)))); ],))));
} }
} }

@ -79,25 +79,43 @@ class _TankerDetailsPageState extends State<TankerDetailsPage> {
@override @override
void initState() { void initState() {
super.initState(); super.initState();
_nameCtrl.text = widget.tankerDetails.tanker_name ?? '';
_nameCtrl.text =
widget.tankerDetails.tanker_name ?? '';
/// FIX allow null availability
if (widget.tankerDetails.availability != null && if (widget.tankerDetails.availability != null &&
widget.tankerDetails.availability is List && widget.tankerDetails.availability is List &&
widget.tankerDetails.availability.length == 2) { widget.tankerDetails.availability.length == 2) {
final a = widget.tankerDetails.availability;
currentAvailability = "${a[0]}|${a[1]}";
_fetchTankerTrips(); final a =
widget.tankerDetails.availability;
currentAvailability =
"${a[0]}|${a[1]}";
}
else{
/// default status if null
currentAvailability =
"empty|available";
} }
}
/// ALWAYS FETCH TRIPS
_fetchTankerTrips();
}
Future<void> _fetchTankerTrips() async { Future<void> _fetchTankerTrips() async {
setState(() => isTankerTripsLoading = true); setState(() => isTankerTripsLoading = true);
try { try {
var payload = new Map<String, dynamic>(); var payload = new Map<String, dynamic>();
payload["customerId"] = widget.tankerDetails.tanker_name;
payload["tankerName"] = widget.tankerDetails.tanker_name; payload["tankerName"] =
widget.tankerDetails.tanker_name;
final response = await AppSettings.getTankerTrips(payload); final response = await AppSettings.getTankerTrips(payload);
final data = (jsonDecode(response)['data'] as List) final data = (jsonDecode(response)['data'] as List)
@ -823,11 +841,17 @@ class _TankerDetailsPageState extends State<TankerDetailsPage> {
child: Scaffold( child: Scaffold(
backgroundColor: Color(0XFFF1F1F1), backgroundColor: Color(0XFFF1F1F1),
appBar: AppSettings.supplierAppBarWithActionsText( appBar: AppSettings.supplierAppBarWithActionsText(
widget.tankerDetails.tanker_name.isNotEmpty widget.tankerDetails.tanker_name.isNotEmpty
? widget.tankerDetails.tanker_name[0].toUpperCase() + ? widget.tankerDetails.tanker_name[0].toUpperCase() +
widget.tankerDetails.tanker_name.substring(1) widget.tankerDetails.tanker_name.substring(1)
: '', : '',
context),
context,
result: true // ADD THIS
),
body: SingleChildScrollView( body: SingleChildScrollView(
child: Padding( child: Padding(
padding: const EdgeInsets.all(0), padding: const EdgeInsets.all(0),
@ -1128,11 +1152,11 @@ class _TankerDetailsPageState extends State<TankerDetailsPage> {
), ),
Padding( Padding(
padding: EdgeInsets.all(16), padding: const EdgeInsets.all(16),
child: Column( child: Column(
crossAxisAlignment: crossAxisAlignment: CrossAxisAlignment.start,
CrossAxisAlignment.start,
children: [ children: [
Text( Text(
"RECENT TRIPS", "RECENT TRIPS",
style: fontTextStyle( style: fontTextStyle(
@ -1140,89 +1164,108 @@ class _TankerDetailsPageState extends State<TankerDetailsPage> {
const Color(0xFF343637), const Color(0xFF343637),
FontWeight.w600), FontWeight.w600),
), ),
const SizedBox(height: 12), const SizedBox(height: 12),
_buildTripCard( isTankerTripsLoading
driverName: "Ramesh Krishna",
time: ? const Center(
"7:02 PM, 28 Jun 2025", child: CircularProgressIndicator())
from:
"Bachupally Filling Station", : tankerTripsList.isEmpty
to: "Akriti Heights",
), ? Center(
const SizedBox(height: 8), child: Padding(
_buildTripCard( padding: const EdgeInsets.symmetric(
driverName: "Ramesh Krishna", vertical: 12),
time: child: Text(
"12:44 PM, 26 Jun 2025", 'No Data Available',
from: style: fontTextStyle(
"Bachupally Filling Station", 12,
to: "Akriti Heights", const Color(0xFF939495),
FontWeight.w500),
),
),
)
: ListView.separated(
itemCount: tankerTripsList.length,
shrinkWrap: true,
physics:
const NeverScrollableScrollPhysics(),
separatorBuilder: (_, __) =>
const SizedBox(height: 10),
itemBuilder: (context, idx) {
final it = tankerTripsList[idx];
/// DRIVER NAME
String driverName =
(it.driver_name != "" &&
it.driver_name != "null")
? it.driver_name
: it.supplierName;
/// DATE + TIME
String tripTime = "";
if(it.dateOfOrder.isNotEmpty &&
it.time.isNotEmpty){
tripTime =
"${it.dateOfOrder}${it.time}";
}
else{
tripTime = "-";
}
/// PICKUP LOCATION
String pickupLocation =
(it.water_source_location.isNotEmpty &&
it.water_source_location != "null")
? it.water_source_location
: "Water Source";
/// DELIVERY LOCATION
String deliveryLocation =
(it.building_name.isNotEmpty)
? it.building_name
: it.address;
return _buildTripCard(
driverName: driverName,
time: tripTime,
from: pickupLocation,
to: deliveryLocation,
);
},
), ),
], ],
), ),
), ),
// FIX Removed Expanded (illegal inside ScrollView) // FIX Removed Expanded (illegal inside ScrollView)
Padding(
padding: const EdgeInsets.symmetric(
horizontal: 16),
child: isTankerTripsLoading
? const Center(
child:
CircularProgressIndicator())
: tankerTripsList.isEmpty
? Center(
child: Padding(
padding:
const EdgeInsets
.symmetric(
vertical:
12),
child: Text(
'No Data Available',
style: fontTextStyle(
12,
const Color(
0xFF939495),
FontWeight
.w500),
),
),
)
: ListView.separated(
itemCount:
tankerTripsList
.length,
shrinkWrap: true, // Fix
physics:
NeverScrollableScrollPhysics(), // Fix
separatorBuilder:
(_, __) =>
const SizedBox(
height:
10),
itemBuilder:
(context, idx) {
final it =
tankerTripsList[
idx];
return GestureDetector(
onTap: () async {},
child: _buildTripCard(
driverName:
it.driver_name,
time:
"7:02 PM, 28 Jun 2025",
from:
"Bachupally Filling Station",
to:
"Akriti Heights",
),
);
},
),
),
], ],
), ),
), ),
@ -1268,7 +1311,7 @@ class _TankerDetailsPageState extends State<TankerDetailsPage> {
], ],
), ),
Text( Text(
time, time.isEmpty ? "-" : time,
style: const TextStyle( style: const TextStyle(
fontSize: 11, fontSize: 11,
color: Colors.black54, color: Colors.black54,

@ -1,29 +1,78 @@
class TankerTripsModel { class TankerTripsModel {
String tanker_name = ''; String tanker_name = '';
String address = ''; String address = '';
String dbId = ''; String dbId = '';
String driver_name = ''; String driver_name = '';
String status = ''; String status = '';
String building_name = ''; String building_name = '';
String water_source_location=''; String water_source_location = '';
String deliveredDate=''; String deliveredDate = '';
/// ADD THESE
String dateOfOrder = '';
String time = '';
String supplierName = '';
String amount_paid = '';
String amount_due = '';
TankerTripsModel(); TankerTripsModel();
factory TankerTripsModel.fromJson(Map<String, dynamic> json){ factory TankerTripsModel.fromJson(
TankerTripsModel rtvm = new TankerTripsModel(); Map<String, dynamic> json){
rtvm.tanker_name = json['tankerName']?? ''; TankerTripsModel rtvm =
rtvm.dbId = json['_id']?? ''; TankerTripsModel();
rtvm.address = json['supplier_address']?? '';
rtvm.driver_name = json['delivery_agent']?? ''; rtvm.tanker_name =
rtvm.status = json['orderStatus']?? ''; json['tankerName'] ?? '';
rtvm.building_name = json['buildingName']?? '';
rtvm.water_source_location = json['water_source_location']?? ''; rtvm.dbId =
rtvm.deliveredDate = json['deliveredDate']?? ''; json['_id'] ?? '';
/// FIX (you used wrong key)
rtvm.address =
json['address'] ?? '';
rtvm.driver_name =
json['delivery_agent'] ?? '';
rtvm.status =
json['orderStatus'] ?? '';
rtvm.building_name =
json['buildingName'] ?? '';
rtvm.water_source_location =
json['water_source_location'] ?? '';
rtvm.deliveredDate =
json['deliveredDate'] ?? '';
/// ADD THESE
rtvm.dateOfOrder =
json['dateOfOrder'] ?? '';
rtvm.time =
json['time'] ?? '';
rtvm.supplierName =
json['supplierName'] ?? '';
rtvm.amount_paid =
json['amount_paid'] ?? '';
rtvm.amount_due =
json['amount_due'] ?? '';
return rtvm; return rtvm;
} }
Map<String, dynamic> toJson() => { Map<String, dynamic> toJson() => {
"boreName":this.tanker_name,
"tankerName": tanker_name,
}; };
} }

@ -1,180 +1,372 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:sms_autofill/sms_autofill.dart';
import 'package:supplier_new/signup/password_textbox_screen.dart'; import 'package:supplier_new/signup/password_textbox_screen.dart';
import '../common/settings.dart'; import '../common/settings.dart';
class Otpscreen extends StatefulWidget { class Otpscreen extends StatefulWidget {
var mobileNumber;
Otpscreen({ final String mobileNumber;
this.mobileNumber
const Otpscreen({
super.key,
required this.mobileNumber
}); });
@override @override
State<Otpscreen> createState() => _OtpscreenState(); State<Otpscreen> createState() => _OtpscreenState();
} }
class _OtpscreenState extends State<Otpscreen> { class _OtpscreenState extends State<Otpscreen>
final List<TextEditingController> _controllers = List.generate(6, (index) => TextEditingController()); with CodeAutoFill {
final FocusNode _focusNode = FocusNode();
String otpCode = "";
bool isLoading = false;
int seconds = 30;
bool canResend = false;
@override @override
void dispose() { void initState(){
_controllers.forEach((controller) => controller.dispose());
_focusNode.dispose(); super.initState();
super.dispose();
listenForCode();
startTimer();
}
void startTimer(){
Future.delayed(const Duration(seconds:1),(){
if(seconds>0){
setState(()=>seconds--);
startTimer();
}
else{
setState(()=>canResend=true);
}
});
}
@override
void codeUpdated(){
setState((){
otpCode = code!;
});
if(otpCode.length==6){
verifyOtp();
}
}
Future<void> verifyOtp() async{
if(isLoading) return;
if(otpCode.length!=6){
AppSettings.longFailedToast(
"Enter 6 digit OTP");
return;
}
setState(()=>isLoading=true);
AppSettings.preLoaderDialog(context);
bool isOnline =
await AppSettings.internetConnectivity();
if(isOnline){
var payload={
"phoneVerificationCode":otpCode,
"phone":widget.mobileNumber
};
bool status =
await AppSettings.verifyPhn(payload);
Navigator.pop(context);
if(status){
Navigator.pushReplacement(
context,
MaterialPageRoute(
builder:(_)=>
PasswordTextBoxesScreen(
mobileNumber:
widget.mobileNumber),
),
);
}
else{
AppSettings.longFailedToast(
"Invalid OTP");
}
}
else{
Navigator.pop(context);
AppSettings.longFailedToast(
"Check internet");
}
setState(()=>isLoading=false);
} }
void _submitOtp() { String maskNumber(String number){
final otp = _controllers.map((controller) => controller.text).join();
print("Entered OTP: $otp"); return "*******${number.substring(7)}";
// Add OTP validation or submission logic here
} }
Future<void> resendOtp() async{
if(!canResend) return;
var payload={
"mobileNumbers":
widget.mobileNumber
};
await AppSettings.getOtp(payload);
setState((){
seconds=30;
canResend=false;
});
startTimer();
String maskMobileNumber(String number) {
if (number.length < 3) return number; // Handle invalid numbers
final stars = '*' * (number.length - 3);
final lastThree = number.substring(number.length - 3);
return '$stars$lastThree';
} }
@override
void dispose(){
cancel();
super.dispose();
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context){
return Scaffold( return Scaffold(
body: Stack(children: <Widget>[
/*Container( backgroundColor:Colors.white,
decoration: const BoxDecoration(
image: DecorationImage( body:SafeArea(
image: AssetImage("images/backgroundimage.png"),
fit: BoxFit.cover, child:Padding(
padding:
const EdgeInsets.all(24),
child:Column(
children:[
const Spacer(),
Text(
"Enter OTP",
style:fontTextStyle(
20,
Color(0XFF101214),
FontWeight.w700),
),
const SizedBox(height:10),
Text(
"Code sent to +91 ${maskNumber(widget.mobileNumber)}",
style:fontTextStyle(
12,
Color(0XFF7E7F80),
FontWeight.w400),
),
const SizedBox(height:40),
/// OTP BOX
PinFieldAutoFill(
codeLength:6,
currentCode:otpCode,
onCodeChanged:(code){
otpCode=code??"";
},
decoration:
BoxLooseDecoration(
radius:
const Radius.circular(8),
strokeColorBuilder:
FixedColorBuilder(
primaryColor),
textStyle:
fontTextStyle(
18,
Color(0XFF101214),
FontWeight.w600),
),
),
const SizedBox(height:30),
/// TIMER
canResend
? GestureDetector(
onTap:resendOtp,
child:Text(
"Resend OTP",
style:fontTextStyle(
14,
primaryColor,
FontWeight.w600),
),
)
: Text(
"Resend in 00:$seconds",
style:fontTextStyle(
12,
Color(0XFF7E7F80),
FontWeight.w500),
), ),
),
),*/ const SizedBox(height:40),
GestureDetector(
onTap: () { /// BUTTON
FocusScope.of(context).requestFocus(new FocusNode()); SizedBox(
},
child: SafeArea( width:double.infinity,
child: SingleChildScrollView(
child: Padding( height:55,
padding: EdgeInsets.fromLTRB(24, 0, 24, 0),
child: Column(children: <Widget>[ child:ElevatedButton(
SizedBox(height:MediaQuery.of(context).size.height * .2,),
style:
Container( ElevatedButton.styleFrom(
child: Text( backgroundColor:
'Enter confirmation code', primaryColor,
style: fontTextStyle(16,Color(0XFF101214),FontWeight.w800),
), shape:
), RoundedRectangleBorder(
SizedBox(height:MediaQuery.of(context).size.height * .02,),
Container( borderRadius:
BorderRadius.circular(24),
child: Text(
'A 6-digit code was sent to +91${maskMobileNumber(widget.mobileNumber)}', ),
style: fontTextStyle(12,Color(0XFF7E7F80),FontWeight.w400),
), ),
),
SizedBox(height:MediaQuery.of(context).size.height * .040,), onPressed:verifyOtp,
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly, child:isLoading
children: List.generate(6, (index) {
return SizedBox( ? const CircularProgressIndicator(
width: 50, color:Colors.white)
child: TextFormField(
cursorColor:primaryColor, :Text(
controller: _controllers[index],
focusNode: index == 0 ? _focusNode : null, "Verify",
maxLength: 1,
textAlign: TextAlign.center, style:fontTextStyle(
style: fontTextStyle(14,Color(0XFF101214),FontWeight.w400), 16,
keyboardType: TextInputType.number, Colors.white,
decoration: textFormFieldDecoration(Icons.ice_skating, ''), FontWeight.w600),
onChanged: (value) {
if (value.isNotEmpty && index < 5) { ),
FocusScope.of(context).nextFocus();
} else if (value.isEmpty && index > 0) { ),
FocusScope.of(context).previousFocus();
} ),
},
), const Spacer(),
);
}), ],
),
),
SizedBox(height:MediaQuery.of(context).size.height * .08,),
GestureDetector( ),
onTap: (){
),
},
child: Text('Resend code',style:fontTextStyle(12,Color(0XFF1D7AFC),FontWeight.w600),), );
),
SizedBox(height:MediaQuery.of(context).size.height * .024,),
Container(
width: double.infinity,
height: MediaQuery.of(context).size.height * .06,
child: ElevatedButton(
style: ElevatedButton.styleFrom(
foregroundColor: Colors.white,
backgroundColor: primaryColor,
),
onPressed: () async{
AppSettings.preLoaderDialog(context);
bool isOnline = await AppSettings.internetConnectivity();
if(isOnline){
final otp = _controllers.map((controller) => controller.text).join();
if(otp.length==6){
var phoneVerifyPayload = new Map<String, dynamic>();
phoneVerifyPayload["phoneVerificationCode"] = otp.toString();
phoneVerifyPayload["phone"] = widget.mobileNumber.toString();
bool verifyPhnStatus = await AppSettings.verifyPhn(phoneVerifyPayload);
if (verifyPhnStatus) {
Navigator.of(context, rootNavigator: true).pop();
//AppSettings.longSuccessToast("User SignUp Successfully");
Navigator.push(
context,
new MaterialPageRoute(
builder: (__) => new PasswordTextBoxesScreen(mobileNumber: widget.mobileNumber,)));
/* await Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const Login()),
);*/
} else {
Navigator.of(context, rootNavigator: true).pop();
AppSettings.longFailedToast("Phone verification failed");
}
}
else{
Navigator.of(context, rootNavigator: true).pop();
AppSettings.longFailedToast("Please enter 6 digit otp code");
}
}
else{
Navigator.of(context,rootNavigator: true).pop();
AppSettings.longFailedToast("Please Check internet");
}
},
child: Text('Continue',style:fontTextStyle(12,Color(0XFFFFFFFF),FontWeight.w600),),
)),
]),
)))),
]));
} }
}
}

@ -856,6 +856,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.15.0" version: "0.15.0"
pin_input_text_field:
dependency: transitive
description:
name: pin_input_text_field
sha256: f45683032283d30b670ec343781660655e3e1953438b281a0bc6e2d358486236
url: "https://pub.dev"
source: hosted
version: "4.5.2"
platform: platform:
dependency: transitive dependency: transitive
description: description:
@ -965,6 +973,14 @@ packages:
description: flutter description: flutter
source: sdk source: sdk
version: "0.0.99" version: "0.0.99"
sms_autofill:
dependency: "direct main"
description:
name: sms_autofill
sha256: c65836abe9c1f62ce411bb78d5546a09ece4297558070b1bd871db1db283aaf9
url: "https://pub.dev"
source: hosted
version: "2.4.1"
source_span: source_span:
dependency: transitive dependency: transitive
description: description:

@ -36,6 +36,7 @@ dependencies:
table_calendar: ^3.0.2 table_calendar: ^3.0.2
photo_view: ^0.15.0 photo_view: ^0.15.0
url_launcher: ^6.1.9 url_launcher: ^6.1.9
sms_autofill: ^2.4.0
dev_dependencies: dev_dependencies:
flutter_test: flutter_test:

Loading…
Cancel
Save