From e054f2eb92b4df8b85c64c67898996399e6621aa Mon Sep 17 00:00:00 2001 From: Sneha Date: Mon, 16 Mar 2026 18:03:18 +0530 Subject: [PATCH] changes --- lib/common/dashboard.dart | 209 +++++++-- lib/common/settings.dart | 105 ++++- lib/orders/all_orders.dart | 167 ++++++- lib/orders/search_order_appbar.dart | 149 ++++++- lib/resources/driver_details.dart | 485 ++++++++++++++++----- lib/resources/driver_trips_model.dart | 76 ++++ lib/resources/drivers_model.dart | 5 +- lib/resources/resources_drivers.dart | 62 ++- lib/resources/resources_fleet.dart | 4 +- lib/resources/sourcelocation_details.dart | 20 +- lib/resources/tanker_details.dart | 217 ++++++---- lib/resources/tanker_trips_model.dart | 79 +++- lib/signup/otp_screen.dart | 502 +++++++++++++++------- pubspec.lock | 16 + pubspec.yaml | 1 + 15 files changed, 1653 insertions(+), 444 deletions(-) create mode 100644 lib/resources/driver_trips_model.dart diff --git a/lib/common/dashboard.dart b/lib/common/dashboard.dart index 86c0ff2..4c98958 100644 --- a/lib/common/dashboard.dart +++ b/lib/common/dashboard.dart @@ -34,6 +34,8 @@ class DashboardScreen extends StatefulWidget { } class _DashboardScreenState extends State { + + int _currentIndex = 0; final ImagePicker _picker = ImagePicker(); final storage = FlutterSecureStorage( @@ -45,16 +47,58 @@ class _DashboardScreenState extends State { final GlobalKey _scaffoldKey = GlobalKey(); final TextEditingController orderSearchController = 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 - final List _screens = [ + /* final List _screens = [ HomeScreen(), - AllOrders(navigationFrom: 'bottombar',), + AllOrders( + + navigationFrom: 'bottombar', + + externalSearchController: orderSearchController, + + ), AllPlans(navigationFrom: 'bottombar',), ResourcesMainScreen(), FinancialMainScreen(), - ]; + ];*/ + + late List _screens; // List of bottom navigation bar items final List _bottomNavItems = [ BottomNavigationBarItem( @@ -275,17 +319,25 @@ class _DashboardScreenState extends State { PreferredSizeWidget? _buildAppBar() { // 👉 Orders tab (index 1): Custom Search AppBar if (_currentIndex == 1) { - return SearchOrderAppBar( + return + SearchOrderAppBar( + controller: orderSearchController, + onBack: () { setState(() { _currentIndex = 0; // go back to Home }); }, - onHelp: () { - // handle help button action - debugPrint("Help tapped"); + + onHelp: () {}, + + onSearch: (value){ + + orderSearchController.text = value; + }, + ); } @@ -727,7 +779,7 @@ class _DashboardScreenState extends State { ), body: - _screens[_currentIndex], + _getScreen(), /*_body(),,*/ bottomNavigationBar: Padding( padding:EdgeInsets.fromLTRB(0, 0, 0, 0) , @@ -795,6 +847,8 @@ class _HomeScreenState extends State { double yesterdayRevenue = 0; String revenueChangeText = "0% from yesterday"; Color revenueChangeColor = Colors.grey; + int orderRequestsCount = 0; + int planRequestsCount = 0; @override void initState() { @@ -803,6 +857,94 @@ class _HomeScreenState extends State { _fetchTankerCounts(); _fetchTodayOrders(); _fetchTodayRevenue(); + _fetchOrderRequestsCount(); + _fetchPlanRequestsCount(); + } + + Future _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 _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 { subtitle: revenueChangeText, icon: Icons.currency_rupee, onTap: () { - Navigator.push( - context, - MaterialPageRoute( - builder: (context) => FinancialMainScreen()), - ); + + final dashboardState = + context.findAncestorStateOfType<_DashboardScreenState>(); + + dashboardState?.setState(() { + + dashboardState._currentIndex = 4; + + }); + }, ), ], @@ -1241,7 +1388,9 @@ class _HomeScreenState extends State { }, child: RequestCard( title: "Order Requests", - subtitle: "1 new request", + subtitle: orderRequestsCount > 0 + ? "$orderRequestsCount new request" + : null, ), ), ), @@ -1256,7 +1405,9 @@ class _HomeScreenState extends State { }, child: RequestCard( 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 { final String title; - final String subtitle; + final String? subtitle; const RequestCard({ super.key, required this.title, - required this.subtitle, + this.subtitle, }); @override @@ -1372,17 +1523,21 @@ class RequestCard extends StatelessWidget { style: const TextStyle(fontSize: 14, fontWeight: FontWeight.w600), ), const SizedBox(height: 8), - Container( - padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), - decoration: BoxDecoration( - color: Colors.green[50], - borderRadius: BorderRadius.circular(8), - ), - child: Text( - subtitle, - style: const TextStyle(color: Colors.green, fontSize: 12), + if(subtitle != null) + Container( + padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), + decoration: BoxDecoration( + color: Colors.green[50], + borderRadius: BorderRadius.circular(8), + ), + child: Text( + subtitle!, + style: const TextStyle( + color: Colors.green, + fontSize: 12, + ), + ), ), - ), ], ), ); diff --git a/lib/common/settings.dart b/lib/common/settings.dart index 81a72da..e448e74 100644 --- a/lib/common/settings.dart +++ b/lib/common/settings.dart @@ -196,6 +196,8 @@ class AppSettings{ static String connectedCustomersUrl = host + 'connectedCustomers'; static String acceptRequestUrl = host +"friend-request/accept"; static String rejectRequestUrl = host +"friend-request/reject"; + static String getDriverTripsUrl = host +"getdeliveryboybookings"; + static int driverAvailableCount = 0; static int driverOnDeliveryCount = 0; @@ -1652,6 +1654,22 @@ class AppSettings{ return false; } } + + static Future 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*/ //save data local @@ -1884,44 +1902,107 @@ class AppSettings{ ); } - static supplierAppBarWithActionsText(String title,context) { + static supplierAppBarWithActionsText( + + String title, + + BuildContext context, + + {dynamic result} + + ){ + title = title ?? ''; + return AppBar( + backgroundColor: Colors.white, + elevation: 0, + scrolledUnderElevation: 0, + titleSpacing: 0, + title: Text( + 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( + onTap: () { - Navigator.pop(context); + + Navigator.pop( + context, + result // ⭐ IMPORTANT + ); + }, + child: Padding( + padding: - const EdgeInsets.fromLTRB(8, 8, 8, 8), // Add padding if needed + EdgeInsets.fromLTRB( + 8,8,8,8), + child: Image.asset( - 'images/backbutton_appbar.png', // Replace with your image path + + 'images/backbutton_appbar.png', + fit: BoxFit.contain, + color: Color(0XFF2A2A2A), + height: 24, + width: 24, + ), + ), + ), + actions: [ - Padding(padding: EdgeInsets.all(8), + + Padding( + + padding: EdgeInsets.all(8), + child: TextButton( - onPressed: () async { - }, - child: Text('HELP', - style: fontTextStyle(14,primaryColor,FontWeight.w600),), - ),) + + onPressed: (){}, + + child: Text( + + 'HELP', + + style: fontTextStyle( + 14, + primaryColor, + FontWeight.w600), + + ), + + ), + + ) + ], + ); + } static appBarWithoutActions(String title) { diff --git a/lib/orders/all_orders.dart b/lib/orders/all_orders.dart index 57791b8..7d59955 100644 --- a/lib/orders/all_orders.dart +++ b/lib/orders/all_orders.dart @@ -14,20 +14,31 @@ import 'change_driver.dart'; class AllOrders extends StatefulWidget { final String navigationFrom; + + final TextEditingController? externalSearchController; + AllOrders({ + super.key, + required this.navigationFrom, + + this.externalSearchController, + }); @override State createState() => _AllOrdersState(); } class _AllOrdersState extends State { - final TextEditingController searchController = TextEditingController(); + late TextEditingController searchController; bool isLoading=false; final List orders =[]; + List allOrders = []; List ordersList = []; + bool isSearching = false; + int todaysOrdersCount = 0; int boreWaterCount = 0; int drinkingWaterCount = 0; @@ -38,6 +49,26 @@ class _AllOrdersState extends State { void initState() { super.initState(); _fetchOrders(); + searchController = + widget.externalSearchController ?? + TextEditingController(); + + searchController.addListener(_searchOrders); + } + + @override + void dispose(){ + + searchController.removeListener(_searchOrders); + + if(widget.externalSearchController == null){ + + searchController.dispose(); + + } + + super.dispose(); + } Future _fetchOrders() async { @@ -50,7 +81,8 @@ class _AllOrdersState extends State { .toList(); if (!mounted) return; setState(() { - ordersList = data; + allOrders = data; + ordersList = List.from(data); ordersList.sort((a, b) { try { @@ -91,6 +123,92 @@ class _AllOrdersState extends State { } } + void _searchOrders() { + + String query = searchController.text.trim().toLowerCase(); + + if(query.isEmpty){ + + setState(() { + + ordersList = List.from(allOrders); + isSearching = false; + + }); + + return; + + } + + List 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) { setState(() { selectedFilter = type; @@ -133,7 +251,7 @@ class _AllOrdersState extends State { Widget build(BuildContext context) { // Group orders by date - final Map> groupedOrders = {}; + Map> groupedOrders = {}; String formatOrderDate(String? dateStr) { if (dateStr == null || dateStr.trim().isEmpty) { return ""; @@ -157,17 +275,52 @@ class _AllOrdersState extends State { backgroundColor: Color(0XFFF2F2F2), appBar: widget.navigationFrom.toString().toLowerCase()=='dashboard'? SearchOrderAppBar( + controller: searchController, + onBack: () => Navigator.pop(context), + onHelp: () {}, + + onSearch: (value){ + + _searchOrders(); + + }, + ):null, body: isLoading ? const Center(child: CircularProgressIndicator()) - : ordersList.isEmpty?Center( - child: Text( - 'No Data Available', - style: fontTextStyle(16,Color(0XFF000000),FontWeight.w700), + : ordersList.isEmpty ? + + Center( + + 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( child: Padding( diff --git a/lib/orders/search_order_appbar.dart b/lib/orders/search_order_appbar.dart index fd13cdb..bf1d948 100644 --- a/lib/orders/search_order_appbar.dart +++ b/lib/orders/search_order_appbar.dart @@ -1,94 +1,223 @@ import 'package:flutter/material.dart'; import 'package:supplier_new/common/settings.dart'; -class SearchOrderAppBar extends StatelessWidget implements PreferredSizeWidget { +class SearchOrderAppBar extends StatefulWidget implements PreferredSizeWidget { + final TextEditingController controller; final VoidCallback onBack; final VoidCallback onHelp; + final Function(String)? onSearch; const SearchOrderAppBar({ super.key, required this.controller, required this.onBack, required this.onHelp, + required this.onSearch, }); + @override + State createState() => _SearchOrderAppBarState(); + + @override + Size get preferredSize => const Size.fromHeight(kToolbarHeight); + +} + +class _SearchOrderAppBarState extends State{ + + 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 Widget build(BuildContext context) { + return AppBar( + backgroundColor: Colors.white, + scrolledUnderElevation: 0, + elevation: 0, + leading: GestureDetector( - onTap: onBack, // 👉 Controlled by parent + + onTap: widget.onBack, + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Image.asset( + 'images/backbutton_appbar.png', + height: 24, + width: 24, + fit: BoxFit.contain, + ), + ), + ), + titleSpacing: 0, + title: Container( + height: 40, + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(22), + border: Border.all(color: const Color(0XFF939495)), + ), + child: TextField( - controller: controller, + + controller: widget.controller, + + onChanged: widget.onSearch, + decoration: InputDecoration( + hintText: "Search order", + hintStyle: fontTextStyle( + 16, + const Color(0XFF646566), + FontWeight.w400, + ), + prefixIcon: SizedBox( + height: 20, + width: 20, + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Image.asset( + 'images/search.png', + 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, + contentPadding: const EdgeInsets.symmetric(vertical: 0), + ), + style: fontTextStyle( + 16, + const Color(0XFF2A2A2A), + FontWeight.w400, + ), - onSubmitted: (value) { - debugPrint("Search Orders: $value"); - }, + ), + ), + actions: [ + Padding( + padding: const EdgeInsets.all(8.0), + child: GestureDetector( - onTap: onHelp, // 👉 Controlled by parent + + onTap: widget.onHelp, + child: Image.asset( + 'images/help_appbar.png', + height: 24, + width: 24, + fit: BoxFit.contain, + ), + ), + ), + ], + ); + } - @override - Size get preferredSize => const Size.fromHeight(kToolbarHeight); -} +} \ No newline at end of file diff --git a/lib/resources/driver_details.dart b/lib/resources/driver_details.dart index 7d2159e..6d34d74 100644 --- a/lib/resources/driver_details.dart +++ b/lib/resources/driver_details.dart @@ -1,5 +1,8 @@ +import 'dart:convert'; + import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; +import 'package:supplier_new/resources/driver_trips_model.dart'; import 'package:supplier_new/resources/resources_drivers.dart'; import '../common/settings.dart'; @@ -24,30 +27,32 @@ class _DriverDetailsPageState extends State { final _mobileCtrl = TextEditingController(); final _altMobileCtrl = TextEditingController(); final _locationCtrl = TextEditingController(); + final _licenseController = TextEditingController(); + final _experienceController = TextEditingController(); final GlobalKey _formKey = GlobalKey(); // Unused in UI but kept if you later need them final _commissionCtrl = TextEditingController(); final _joinDateCtrl = TextEditingController(); bool isLoading=false; + List driverTripsList = []; + bool isTripsLoading = false; + // Dropdown state String? _status; // 'available' | 'on delivery' | 'offline' final List _statusOptions = const [ - 'available', - 'on delivery', - 'offline' + 'Available', + 'On delivery', + 'Offline' ]; - String? selectedLicense; + /*String? selectedLicense; final List licenseNumbers = const [ 'DL-042019-9876543', 'DL-052020-1234567', 'DL-072021-7654321', ]; - - String? selectedExperience; // years as string - final List yearOptions = - List.generate(41, (i) => '$i'); // 0..40 +*/ String? _required(String? v, {String field = "This field"}) { if (v == null || v.trim().isEmpty) return "$field is required"; @@ -60,6 +65,52 @@ class _DriverDetailsPageState extends State { return null; } + + @override + void initState(){ + + super.initState(); + + _fetchDriverTrips(); + + } + + Future _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 options) { if (incoming == null) return null; final inc = incoming.trim(); @@ -106,14 +157,14 @@ class _DriverDetailsPageState extends State { // Build payload (adjust keys to your API if needed) final payload = { "name": _nameCtrl.text.trim(), - "license_number": selectedLicense ?? "", + "license_number": _licenseController.text ?? "", "address": _locationCtrl.text.trim().isEmpty ? AppSettings.userAddress : _locationCtrl.text.trim(), "supplier_name": AppSettings.userName, "phone": _mobileCtrl.text.trim(), "alternativeContactNumber": _altMobileCtrl.text.trim(), - "years_of_experience": selectedExperience ?? "", + "years_of_experience": _experienceController.text ?? "", "status": _status ?? "available", }; @@ -142,8 +193,8 @@ class _DriverDetailsPageState extends State { _mobileCtrl.text = widget.driverDetails.phone_number ?? ''; _altMobileCtrl.text = widget.driverDetails.alt_phone_number ?? ''; _locationCtrl.text = widget.driverDetails.address ?? ''; - selectedExperience = fitToOption(widget.driverDetails.years_of_experience, yearOptions); - selectedLicense = fitToOption(widget.driverDetails.license_number, licenseNumbers); + _experienceController.text = widget.driverDetails.years_of_experience ?? ''; + _licenseController.text = widget.driverDetails.license_number ?? ''; _status = fitToOption(widget.driverDetails.status, _statusOptions); await showModalBottomSheet( @@ -209,62 +260,61 @@ class _DriverDetailsPageState extends State { _LabeledField( label: "Driver License Number *", - child: DropdownButtonFormField( - value: selectedLicense, - 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), + child: TextFormField( + controller: _licenseController, // create controller decoration: const InputDecoration( border: OutlineInputBorder(), - isDense: false, - contentPadding: EdgeInsets.symmetric( - horizontal: 12, vertical: 14), + hintText: "Enter License Number", + contentPadding: + 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( label: "Years of Experience *", - child: DropdownButtonFormField( - value: selectedExperience, - items: yearOptions - .map((t) => - DropdownMenuItem(value: t, child: Text(t))) - .toList(), - onChanged: (v) => - setState(() => selectedExperience = v), - 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), + child: TextFormField( + controller: _experienceController, + keyboardType: TextInputType.number, + autovalidateMode: AutovalidateMode.onUserInteraction, + + inputFormatters: [ + FilteringTextInputFormatter.digitsOnly, // Only numbers + LengthLimitingTextInputFormatter(2), // Max 2 digits + ], + decoration: const InputDecoration( border: OutlineInputBorder(), - isDense: false, - contentPadding: EdgeInsets.symmetric( - horizontal: 12, vertical: 14), + hintText: "Enter Years", + contentPadding: + 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 { child: Scaffold( backgroundColor: Color(0XFFFFFFFF), - appBar: AppSettings.supplierAppBarWithActionsText( widget.driverDetails.driver_name.isNotEmpty - ? widget.driverDetails.driver_name[0].toUpperCase() + - widget.driverDetails.driver_name.substring(1) - : '', context), + + appBar: AppSettings.supplierAppBarWithActionsText( + + widget.driverDetails.driver_name.isNotEmpty + ? widget.driverDetails.driver_name[0].toUpperCase() + + widget.driverDetails.driver_name.substring(1) + : '', + + context, + + result: true // ⭐ ADD THIS + + ), body: SingleChildScrollView( child: Padding( padding: const EdgeInsets.all(0), @@ -687,7 +746,7 @@ class _DriverDetailsPageState extends State { const SizedBox(height: 8), // buttons row - OutlinedButton( + /* OutlinedButton( style: OutlinedButton.styleFrom( foregroundColor: Color(0XFF515253), backgroundColor: Color(0xFFF3F1FB), @@ -712,7 +771,7 @@ class _DriverDetailsPageState extends State { ], ), ), - const SizedBox(height: 24), + const SizedBox(height: 24),*/ // 🪪 License Card ClipRRect( @@ -761,13 +820,13 @@ class _DriverDetailsPageState extends State { ), ], ), - Align( + /*Align( alignment: Alignment.bottomRight, child: Text( "Expires on 29/02/2028", style: fontTextStyle(10, const Color(0xFFFFFFFF), FontWeight.w300), ), - ), + ),*/ ], ) ], @@ -810,25 +869,85 @@ class _DriverDetailsPageState extends State { ], ), - const SizedBox(height: 24), + const SizedBox(height:24), - Padding(padding: EdgeInsets.all(16), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - "RECENT TRIPS", - style:fontTextStyle(10, const Color(0xFF343637), FontWeight.w600), - ), - const SizedBox(height: 12), + Padding( + padding: const EdgeInsets.all(16), - _buildTripCard("Drinking Water - 10,000 L", "7:02 PM, 28 Jun 2025"), - const SizedBox(height: 8), - _buildTripCard("Drinking Water - 10,000 L", "7:02 PM, 28 Jun 2025"), - const SizedBox(height: 8), - _buildTripCard("Drinking Water - 10,000 L", "7:02 PM, 28 Jun 2025"), - ], - ),) + child: Column( + + crossAxisAlignment: + CrossAxisAlignment.start, + + 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 { )); } - 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( - padding: const EdgeInsets.all(12), + + padding: + const EdgeInsets.all(12), + decoration: BoxDecoration( - color: const Color(0xFFFFFFFF), - borderRadius: BorderRadius.circular(8), - border: Border.all(color: Color(0XFFC3C4C4)), + + color: + const Color(0xFFFFFFFF), + + borderRadius: + BorderRadius.circular(8), + + border: Border.all( + color: + const Color(0XFFC3C4C4) + ), + ), - child: Row( + + child: Column( + + crossAxisAlignment: + CrossAxisAlignment.start, + children: [ - Image.asset('images/recent_trips.png', width: 28, height: 28), - const SizedBox(width: 12), - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - title, - style:fontTextStyle(14, const Color(0xFF2D2E30), FontWeight.w500), + Row( + + children: [ + + Image.asset( + 'images/recent_trips.png', + width:28, + height:28), + + 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( - time, - style:fontTextStyle(10, const Color(0xFF939495), FontWeight.w400), + + child: Text( + + 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), + + ), + + ], + + ) + ], + ), + ); + } } diff --git a/lib/resources/driver_trips_model.dart b/lib/resources/driver_trips_model.dart new file mode 100644 index 0000000..a69ce6b --- /dev/null +++ b/lib/resources/driver_trips_model.dart @@ -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 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; + + } + +} \ No newline at end of file diff --git a/lib/resources/drivers_model.dart b/lib/resources/drivers_model.dart index d8d5bdd..6c13492 100644 --- a/lib/resources/drivers_model.dart +++ b/lib/resources/drivers_model.dart @@ -3,13 +3,14 @@ class DriversModel { String driver_name=''; String status=''; String address=''; - String deliveries='13'; + String deliveries=''; String commision=''; String years_of_experience=''; List availability= ['filled', 'available']; String phone_number=''; String alt_phone_number=''; String license_number=''; + String license_expiry_date=''; DriversModel(); factory DriversModel.fromJson(Map json) { @@ -23,6 +24,8 @@ class DriversModel { rtvm.alt_phone_number = json['alternativeContactNumber'] ?? ''; rtvm.years_of_experience = json['years_of_experience'] ?? ''; rtvm.license_number = json['license_number'] ?? ''; + rtvm.deliveries = json['deliveries'] ?? ''; + rtvm.license_expiry_date=json['license_number'] ?? ''; return rtvm; } } \ No newline at end of file diff --git a/lib/resources/resources_drivers.dart b/lib/resources/resources_drivers.dart index 3957216..b955da3 100644 --- a/lib/resources/resources_drivers.dart +++ b/lib/resources/resources_drivers.dart @@ -4,6 +4,7 @@ import 'package:flutter/services.dart'; import 'package:supplier_new/common/settings.dart'; import 'package:supplier_new/resources/driver_details.dart'; import 'package:supplier_new/resources/drivers_model.dart'; +import 'package:url_launcher/url_launcher.dart'; class FirstCharUppercaseFormatter extends TextInputFormatter { const FirstCharUppercaseFormatter(); @@ -70,9 +71,9 @@ class _ResourcesDriverScreenState extends State { // Dropdown state String? _status; // 'available' | 'on delivery' | 'offline' final List _statusOptions = const [ - 'available', - 'on delivery', - 'offline' + 'Available', + 'On delivery', + 'Offline' ]; String? selectedLicense; @@ -179,14 +180,14 @@ class _ResourcesDriverScreenState extends State { // Build payload (adjust keys to your API if needed) final payload = { "Name": _nameCtrl.text.trim(), - "license_number": selectedLicense ?? "", + "license_number": _licenseController.text ?? "", "address": _locationCtrl.text.trim().isEmpty ? AppSettings.userAddress : _locationCtrl.text.trim(), "supplier_name": AppSettings.userName, "phone": _mobileCtrl.text.trim(), "alternativeContactNumber": _altMobileCtrl.text.trim(), - "years_of_experience": selectedExperience ?? "", + "years_of_experience": _experienceController.text ?? "", "status": _status ?? "available", }; @@ -555,10 +556,10 @@ class _ResourcesDriverScreenState extends State { Text(driversList.length.toString(), style: fontTextStyle( 24, const Color(0xFF0D3771), FontWeight.w500)), - const SizedBox(height: 6), + /* const SizedBox(height: 6), Text('+1 since last month', style: fontTextStyle( - 10, const Color(0xFF646566), FontWeight.w400)), + 10, const Color(0xFF646566), FontWeight.w400)),*/ ], ), ], @@ -692,9 +693,9 @@ class _ResourcesDriverScreenState extends State { name: d.driver_name, status: d.status, location: d.address, - deliveries: - int.tryParse(d.deliveries) ?? 0, + deliveries: int.tryParse(d.deliveries) ?? 0, commission: d.commision, + phone: d.phone_number, // ✅ ADD ), ); }, @@ -758,6 +759,7 @@ class DriverCard extends StatelessWidget { final String location; final int deliveries; final String commission; + final String phone; const DriverCard({ super.key, @@ -766,6 +768,7 @@ class DriverCard extends StatelessWidget { required this.location, required this.deliveries, required this.commission, + required this.phone, }); @override @@ -777,6 +780,19 @@ class DriverCard extends StatelessWidget { _ => Colors.grey, }; + Future _makePhoneCall(String phone) async { + + final Uri url = Uri( + scheme: 'tel', + path: phone, + ); + + if(await canLaunchUrl(url)){ + await launchUrl(url); + } + + } + return Container( padding: const EdgeInsets.all(14), decoration: BoxDecoration( @@ -818,15 +834,23 @@ class DriverCard extends StatelessWidget { ), ], ), - Container( - padding: const EdgeInsets.all(8), - decoration: const BoxDecoration( - color: Color(0xFFF5F6F6), - shape: BoxShape.circle, + GestureDetector( + onTap: (){ + _makePhoneCall(phone); + }, + 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), @@ -882,7 +906,7 @@ class DriverCard extends StatelessWidget { ),) ], ), - const SizedBox(height: 12), + /* const SizedBox(height: 12), Row( mainAxisAlignment: MainAxisAlignment.end, children: [ @@ -910,7 +934,7 @@ class DriverCard extends StatelessWidget { 12, const Color(0xFFFFFFFF), FontWeight.w400)), ), ], - ) + )*/ ], ), ); diff --git a/lib/resources/resources_fleet.dart b/lib/resources/resources_fleet.dart index a18dc85..df8b0d6 100644 --- a/lib/resources/resources_fleet.dart +++ b/lib/resources/resources_fleet.dart @@ -485,10 +485,10 @@ class _ResourcesFleetScreenState extends State { tankersList.length.toString(), style: fontTextStyle(24, const Color(0xFF0D3771), FontWeight.w500), ), - const SizedBox(height: 6), + /*const SizedBox(height: 6), Text('+2 since last month', style: fontTextStyle(10, const Color(0xFF646566), FontWeight.w400), - ), + ),*/ ], ), ], diff --git a/lib/resources/sourcelocation_details.dart b/lib/resources/sourcelocation_details.dart index df1f408..39a3fb0 100644 --- a/lib/resources/sourcelocation_details.dart +++ b/lib/resources/sourcelocation_details.dart @@ -517,10 +517,18 @@ class _SourceDetailsScreenState extends State { }, child:Scaffold( backgroundColor: Colors.white, - appBar:AppSettings.supplierAppBarWithActionsText( widget.sourceDetails.source_name.isNotEmpty - ? widget.sourceDetails.source_name[0].toUpperCase() + - widget.sourceDetails.source_name.substring(1) - : '', context), + appBar: AppSettings.supplierAppBarWithActionsText( + + widget.sourceDetails.source_name.isNotEmpty + ? widget.sourceDetails.source_name[0].toUpperCase() + + widget.sourceDetails.source_name.substring(1) + : '', + + context, + + result: true // ⭐ ADD THIS + + ), body: SingleChildScrollView( child: Column( @@ -708,7 +716,7 @@ class _SourceDetailsScreenState extends State { // Filling & Wait time cards - Padding( + /* Padding( padding: const EdgeInsets.symmetric(horizontal: 16), child: Row( children: [ @@ -810,7 +818,7 @@ class _SourceDetailsScreenState extends State { ); }, ), - const SizedBox(height: 20), + const SizedBox(height: 20),*/ ],)))); } } diff --git a/lib/resources/tanker_details.dart b/lib/resources/tanker_details.dart index 03c1e4e..7499fb4 100644 --- a/lib/resources/tanker_details.dart +++ b/lib/resources/tanker_details.dart @@ -79,25 +79,43 @@ class _TankerDetailsPageState extends State { @override void initState() { + super.initState(); - _nameCtrl.text = widget.tankerDetails.tanker_name ?? ''; + _nameCtrl.text = + widget.tankerDetails.tanker_name ?? ''; + + /// FIX — allow null availability if (widget.tankerDetails.availability != null && widget.tankerDetails.availability is List && 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 _fetchTankerTrips() async { setState(() => isTankerTripsLoading = true); try { var payload = new Map(); - 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 data = (jsonDecode(response)['data'] as List) @@ -823,11 +841,17 @@ class _TankerDetailsPageState extends State { 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), + + context, + + result: true // ⭐ ADD THIS + + ), body: SingleChildScrollView( child: Padding( padding: const EdgeInsets.all(0), @@ -1128,11 +1152,11 @@ class _TankerDetailsPageState extends State { ), Padding( - padding: EdgeInsets.all(16), + padding: const EdgeInsets.all(16), child: Column( - crossAxisAlignment: - CrossAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, children: [ + Text( "RECENT TRIPS", style: fontTextStyle( @@ -1140,89 +1164,108 @@ class _TankerDetailsPageState extends State { const Color(0xFF343637), FontWeight.w600), ), + const SizedBox(height: 12), - _buildTripCard( - 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", + 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, + + 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) - 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 { ], ), Text( - time, + time.isEmpty ? "-" : time, style: const TextStyle( fontSize: 11, color: Colors.black54, diff --git a/lib/resources/tanker_trips_model.dart b/lib/resources/tanker_trips_model.dart index b17cae8..11688b5 100644 --- a/lib/resources/tanker_trips_model.dart +++ b/lib/resources/tanker_trips_model.dart @@ -1,29 +1,78 @@ -class TankerTripsModel { +class TankerTripsModel { + String tanker_name = ''; String address = ''; String dbId = ''; String driver_name = ''; String status = ''; String building_name = ''; - String water_source_location=''; - String deliveredDate=''; + String water_source_location = ''; + String deliveredDate = ''; + + /// ADD THESE + String dateOfOrder = ''; + String time = ''; + String supplierName = ''; + String amount_paid = ''; + String amount_due = ''; TankerTripsModel(); - factory TankerTripsModel.fromJson(Map json){ - TankerTripsModel rtvm = new TankerTripsModel(); - - rtvm.tanker_name = json['tankerName']?? ''; - rtvm.dbId = json['_id']?? ''; - rtvm.address = json['supplier_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']?? ''; + factory TankerTripsModel.fromJson( + Map json){ + + TankerTripsModel rtvm = + TankerTripsModel(); + + rtvm.tanker_name = + json['tankerName'] ?? ''; + + rtvm.dbId = + 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; + } + Map toJson() => { - "boreName":this.tanker_name, + + "tankerName": tanker_name, + }; + } \ No newline at end of file diff --git a/lib/signup/otp_screen.dart b/lib/signup/otp_screen.dart index 740873d..70f3c69 100644 --- a/lib/signup/otp_screen.dart +++ b/lib/signup/otp_screen.dart @@ -1,180 +1,372 @@ import 'package:flutter/material.dart'; +import 'package:sms_autofill/sms_autofill.dart'; import 'package:supplier_new/signup/password_textbox_screen.dart'; - import '../common/settings.dart'; class Otpscreen extends StatefulWidget { - var mobileNumber; - Otpscreen({ - this.mobileNumber + + final String mobileNumber; + + const Otpscreen({ + super.key, + required this.mobileNumber }); @override State createState() => _OtpscreenState(); + } -class _OtpscreenState extends State { - final List _controllers = List.generate(6, (index) => TextEditingController()); - final FocusNode _focusNode = FocusNode(); +class _OtpscreenState extends State + with CodeAutoFill { + + String otpCode = ""; + + bool isLoading = false; + + int seconds = 30; + + bool canResend = false; @override - void dispose() { - _controllers.forEach((controller) => controller.dispose()); - _focusNode.dispose(); - super.dispose(); + void initState(){ + + super.initState(); + + 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 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() { - final otp = _controllers.map((controller) => controller.text).join(); - print("Entered OTP: $otp"); - // Add OTP validation or submission logic here + String maskNumber(String number){ + + return "*******${number.substring(7)}"; + } + Future 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 - Widget build(BuildContext context) { + Widget build(BuildContext context){ + return Scaffold( - body: Stack(children: [ - /*Container( - decoration: const BoxDecoration( - image: DecorationImage( - image: AssetImage("images/backgroundimage.png"), - fit: BoxFit.cover, + + backgroundColor:Colors.white, + + body:SafeArea( + + 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), + ), - ), - ),*/ - GestureDetector( - onTap: () { - FocusScope.of(context).requestFocus(new FocusNode()); - }, - child: SafeArea( - child: SingleChildScrollView( - child: Padding( - padding: EdgeInsets.fromLTRB(24, 0, 24, 0), - child: Column(children: [ - SizedBox(height:MediaQuery.of(context).size.height * .2,), - - Container( - - child: Text( - 'Enter confirmation code', - style: fontTextStyle(16,Color(0XFF101214),FontWeight.w800), - ), - ), - SizedBox(height:MediaQuery.of(context).size.height * .02,), - Container( - - 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,), - Row( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: List.generate(6, (index) { - return SizedBox( - width: 50, - child: TextFormField( - cursorColor:primaryColor, - controller: _controllers[index], - focusNode: index == 0 ? _focusNode : null, - maxLength: 1, - textAlign: TextAlign.center, - style: fontTextStyle(14,Color(0XFF101214),FontWeight.w400), - keyboardType: TextInputType.number, - decoration: textFormFieldDecoration(Icons.ice_skating, ''), - onChanged: (value) { - if (value.isNotEmpty && index < 5) { - FocusScope.of(context).nextFocus(); - } else if (value.isEmpty && index > 0) { - FocusScope.of(context).previousFocus(); - } - }, - ), - ); - }), - ), - - 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(); - - 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),), - )), - - ]), - )))), - ])); + + const SizedBox(height:40), + + /// BUTTON + SizedBox( + + width:double.infinity, + + height:55, + + child:ElevatedButton( + + style: + ElevatedButton.styleFrom( + + backgroundColor: + primaryColor, + + shape: + RoundedRectangleBorder( + + borderRadius: + BorderRadius.circular(24), + + ), + + ), + + onPressed:verifyOtp, + + child:isLoading + + ? const CircularProgressIndicator( + color:Colors.white) + + :Text( + + "Verify", + + style:fontTextStyle( + 16, + Colors.white, + FontWeight.w600), + + ), + + ), + + ), + + const Spacer(), + + ], + + ), + + ), + + ), + + ); + } -} + +} \ No newline at end of file diff --git a/pubspec.lock b/pubspec.lock index 140341b..a684d14 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -856,6 +856,14 @@ packages: url: "https://pub.dev" source: hosted 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: dependency: transitive description: @@ -965,6 +973,14 @@ packages: description: flutter source: sdk 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: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index e363d79..c85bf74 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -36,6 +36,7 @@ dependencies: table_calendar: ^3.0.2 photo_view: ^0.15.0 url_launcher: ^6.1.9 + sms_autofill: ^2.4.0 dev_dependencies: flutter_test: