diff --git a/lib/controller/home_controller.dart b/lib/controller/home_controller.dart index 3499aaf..94e48ca 100644 --- a/lib/controller/home_controller.dart +++ b/lib/controller/home_controller.dart @@ -1,110 +1,36 @@ -import 'package:cheminova/controller/home_service.dart'; import 'package:cheminova/models/user_model.dart'; +import 'package:cheminova/services/api_service.dart'; import 'package:get/get.dart'; -import 'package:shared_preferences/shared_preferences.dart'; class HomeController extends GetxController { - final HomeService homeService = HomeService(); - - UserModel? user; - - // var userModel = UserModel( - // id: '', - // uniqueId: '', - // name: '', - // email: '', - // phone: '', - // role: '', - // sbu: '', - // createdAt: '', - // updatedAt: '', - // ).obs; // Observable for UserModel + final ApiService _apiService = ApiService(); + final Rx userProfile = Rx(null); + final RxBool isLoading = false.obs; + final RxString error = ''.obs; @override void onInit() { - getUser(); super.onInit(); + fetchUserProfile(); } - Future getUser() async { - try { - print("Starting getUser function in controller"); - SharedPreferences prefs = await SharedPreferences.getInstance(); - String? token = prefs.getString('token'); + Future fetchUserProfile() async { + // isLoading.value = true; + error.value = ''; - print("Token from SharedPreferences: $token"); + // try { + final response = await _apiService.get('/api/rd-get-me'); - HomeService homeService = HomeService(); - print("Calling homeService.getUser"); - user = await homeService.getUser(token: token); - print("homeService.getUser completed. User: $user"); - - update(); - - if (user != null) { - print("User fetched successfully: $user"); + if (response.statusCode == 200) { + userProfile.value = UserProfile.fromJson(response.data); } else { - print('Failed to fetch user data'); + error.value = + 'Failed to load user profile. Status code: ${response.statusCode}'; } - } catch (e) { - print("Error in getUser controller function: ${e.toString()}"); - } + // } catch (e) { + // error.value = 'An error occurred: $e'; + // } finally { + // isLoading.value = false; + // } } } - - - - - -// import 'package:cheminova/controller/home_service.dart'; -// import 'package:cheminova/models/user_model.dart'; -// import 'package:get/get.dart'; -// import 'package:shared_preferences/shared_preferences.dart'; -// -// import '../notification_service.dart'; -// -// class HomeController extends GetxController { -// final HomeService homeService = HomeService(); -// NotificationServices notificationServices = NotificationServices(); -// -// -// -// var userModel = UserModel(id: '', uniqueId: '', name: '', email: '', phone: '', role: '', sbu: '', createdAt: '', updatedAt: '' -// -// ).obs; // Observable for UserModel -// -// @override -// void onInit() { -// getUser(); -// super.onInit(); -// notificationServices.requestNotificationPermission(); -// notificationServices.getDeviceToken().then((value) { -// print('Device Token: $value'); -// fcmToken(); -// }); -// } -// -// Future fcmToken() async { -// SharedPreferences prefs = await SharedPreferences.getInstance(); -// String? token = prefs.getString('token'); -// final fcmToken = await NotificationServices().getDeviceToken(); -// print('fcmToken: $fcmToken'); -// homeService.fcmToken({"fcmToken": fcmToken}, token!); -// } -// -// Future getUser() async { -// SharedPreferences prefs = await SharedPreferences.getInstance(); -// -// String? token = prefs.getString('token'); -// -// userModel = (await homeService.getUser(token: token)) as dynamic; -// -// -// // if (userModel != null) { -// // if (userModel != null) {ddddd -// // userModel.value = userResponse as UserModel; // Update the userModel with API response -// // update(); // Notify GetX to rebuild widgets using GetBuilder/Obx -// // } -// } -// } -// diff --git a/lib/controller/home_service.dart b/lib/controller/home_service.dart index 173a24f..60c5f62 100644 --- a/lib/controller/home_service.dart +++ b/lib/controller/home_service.dart @@ -4,7 +4,7 @@ import 'package:cheminova/utils/show_snackbar.dart'; import '../utils/api_urls.dart'; class HomeService { - Future getUser({String? token}) async { + Future getUser({String? token}) async { try { print("Starting getUser method in HomeService"); print("Token: $token"); @@ -40,7 +40,7 @@ class HomeService { } print("Attempting to create UserModel from myData"); - final userModel = UserModel.fromJson(response['myData']); + final userModel = UserProfile.fromJson(response['myData']); print("UserModel created successfully: $userModel"); return userModel; } catch (e) { diff --git a/lib/models/user_model.dart b/lib/models/user_model.dart index 6c5f627..4767e8d 100644 --- a/lib/models/user_model.dart +++ b/lib/models/user_model.dart @@ -1,76 +1,70 @@ -class UserModel { - final String id; - final String uniqueId; - final String name; - final String email; - final String mobileNumber; - final String designation; - final String userType; - final String? principalDistributer; - final String? addedBy; - final String? kyc; - final String? fcmToken; - final String createdAt; - final String updatedAt; - final String? mappedSC; +class UserProfile { + bool? success; + String? message; + MyData? myData; - UserModel({ - required this.id, - required this.uniqueId, - required this.name, - required this.email, - required this.mobileNumber, - required this.designation, - required this.userType, - this.principalDistributer, - this.addedBy, - this.kyc, - this.fcmToken, - required this.createdAt, - required this.updatedAt, - this.mappedSC, - }); + UserProfile({this.success, this.message, this.myData}); - factory UserModel.fromJson(Map json) { - return UserModel( + factory UserProfile.fromJson(Map json) { + return UserProfile( + success: json['success'], + message: json['message'], + myData: json['myData'] != null ? MyData.fromJson(json['myData']) : null, + ); + } +} + +class MyData { + String? id; + String? designation; + String? name; + String? email; + String? mobileNumber; + String? principalDistributer; + String? addedBy; + String? userType; + String? kyc; + String? fcmToken; + String? createdAt; + String? updatedAt; + String? mappedSC; + String? uniqueId; + int? v; + + MyData( + {this.id, + this.designation, + this.name, + this.email, + this.mobileNumber, + this.principalDistributer, + this.addedBy, + this.userType, + this.kyc, + this.fcmToken, + this.createdAt, + this.updatedAt, + this.mappedSC, + this.uniqueId, + this.v}); + + factory MyData.fromJson(Map json) { + return MyData( id: json['_id'], - uniqueId: json['uniqueId'], + designation: json['designation'], name: json['name'], email: json['email'], mobileNumber: json['mobile_number'], - designation: json['designation'], - userType: json['userType'], principalDistributer: json['principal_distributer'], addedBy: json['addedBy'], + userType: json['userType'], kyc: json['kyc'], fcmToken: json['fcm_token'], createdAt: json['createdAt'], updatedAt: json['updatedAt'], mappedSC: json['mappedSC'], + uniqueId: json['uniqueId'], + v: json['__v'], ); } - - Map toJson() { - return { - '_id': id, - 'uniqueId': uniqueId, - 'name': name, - 'email': email, - 'mobile_number': mobileNumber, - 'designation': designation, - 'userType': userType, - 'principal_distributer': principalDistributer, - 'addedBy': addedBy, - 'kyc': kyc, - 'fcm_token': fcmToken, - 'createdAt': createdAt, - 'updatedAt': updatedAt, - 'mappedSC': mappedSC, - }; - } - - @override - String toString() { - return 'UserModel{id: $id, uniqueId: $uniqueId, name: $name, email: $email, mobileNumber: $mobileNumber, designation: $designation, userType: $userType, principalDistributer: $principalDistributer, addedBy: $addedBy, kyc: $kyc, fcmToken: $fcmToken, createdAt: $createdAt, updatedAt: $updatedAt, mappedSC: $mappedSC}'; - } } \ No newline at end of file diff --git a/lib/screens/authentication/Profile.dart b/lib/screens/authentication/Profile.dart index 82f4588..fdd334c 100644 --- a/lib/screens/authentication/Profile.dart +++ b/lib/screens/authentication/Profile.dart @@ -4,87 +4,75 @@ import 'package:flutter/material.dart'; import 'package:flutter_svg/svg.dart'; import 'package:get/get.dart'; - import '../../widgets/comman_background.dart'; import '../../widgets/common_appbar.dart'; class ProfileScreen extends StatefulWidget { - // String? name; - // final String uniqueId; - // final String email; - // final String mobileNumber; - // final String designation; - - const ProfileScreen({ - Key? key, - - }) : super(key: key); + const ProfileScreen({super.key}); @override State createState() => _ProfileScreenState(); } class _ProfileScreenState extends State { + final HomeController _homeController = Get.find(); - final homecontroller = Get.put(HomeController()); @override Widget build(BuildContext context) { - final user = homecontroller!.user; - return Stack( - children: [ - CommonBackground( + return Stack(children: [ + CommonBackground( isFullWidth: true, child: Scaffold( - drawer: MyDrawer(), - backgroundColor: Colors.transparent, - appBar: CommonAppBar( - title: const Text('Profile'), + drawer: const MyDrawer(), backgroundColor: Colors.transparent, - elevation: 0, - actions: [ - IconButton( - onPressed: () { - Navigator.pop(context); - }, - icon: SvgPicture.asset( - 'assets/svg/back_arrow.svg', - ), - padding: const EdgeInsets.only(right: 20), - ), - ], - ), - body: SingleChildScrollView( - child: Column( - children: [ - Container( - padding: const EdgeInsets.all(20.0) - .copyWith(top: 15, bottom: 30), - margin: const EdgeInsets.symmetric( - horizontal: 30.0, vertical: 20.0), - decoration: BoxDecoration( - border: Border.all(color: Colors.white), - color: const Color(0xffB4D1E5).withOpacity(0.9), - borderRadius: BorderRadius.circular(26.0), - ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - const SizedBox(height: 20), - _buildProfileItem('Name', user!.name), - _buildProfileItem('ID', user.uniqueId), - _buildProfileItem('Email ID', user.email), - // _buildProfileItem('Mobile Number', user.phone), - // _buildProfileItem('Designation', user.role), - ], - ), - ), - ], - ), - ), - ), - ), - ], - ); + appBar: CommonAppBar( + title: const Text('Profile'), + backgroundColor: Colors.transparent, + elevation: 0, + actions: [ + IconButton( + onPressed: () => Navigator.pop(context), + icon: SvgPicture.asset('assets/svg/back_arrow.svg'), + padding: const EdgeInsets.only(right: 20)) + ]), + body: Obx(() { + if (_homeController.isLoading.value) { + return const Center(child: CircularProgressIndicator()); + } else if (_homeController.error.value.isNotEmpty) { + return Center(child: Text(_homeController.error.value)); + } else { + final user = _homeController.userProfile.value; + return SingleChildScrollView( + child: Column(children: [ + Container( + padding: const EdgeInsets.all(20.0) + .copyWith(top: 15, bottom: 30), + margin: const EdgeInsets.symmetric( + horizontal: 30.0, vertical: 20.0), + decoration: BoxDecoration( + border: Border.all(color: Colors.white), + color: const Color(0xffB4D1E5).withOpacity(0.9), + borderRadius: BorderRadius.circular(26.0), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const SizedBox(height: 20), + _buildProfileItem( + 'Name', user!.myData!.name ?? ''), + _buildProfileItem( + 'ID', user.myData!.uniqueId ?? ''), + _buildProfileItem( + 'Email ID', user.myData!.email ?? ''), + _buildProfileItem('Mobile Number', + user.myData!.mobileNumber ?? ''), + _buildProfileItem( + 'Designation', user.myData!.designation ?? '') + ])) + ])); + } + }))) + ]); } Widget _buildProfileItem(String label, String value) { diff --git a/lib/screens/authentication/login_screen.dart b/lib/screens/authentication/login_screen.dart index 546d577..cbbfe10 100644 --- a/lib/screens/authentication/login_screen.dart +++ b/lib/screens/authentication/login_screen.dart @@ -16,8 +16,13 @@ class LoginScreen extends StatefulWidget { } class _LoginScreenState extends State { + // Form key to manage the state of the form final formKey = GlobalKey(); + + // Instantiate the AuthController using GetX for state management final authController = Get.put(AuthController()); + + // Regular expressions for validating email and password formats final String emailPattern = r'^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$'; final String passwordPattern = r'^.{6,}$'; // At least 6 characters @@ -27,6 +32,7 @@ class _LoginScreenState extends State { body: Stack( alignment: Alignment.topCenter, children: [ + // Background image and styling Container( decoration: const BoxDecoration( image: DecorationImage( @@ -43,14 +49,16 @@ class _LoginScreenState extends State { height: Get.height * 0.7, ), ), + // Form for user login SingleChildScrollView( child: Form( - key: formKey, + key: formKey, // Link the form to the GlobalKey child: Column( mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center, children: [ - SizedBox(height: Get.height * 0.1), + SizedBox(height: Get.height * 0.1), // Spacer for top + // Welcome message Container( margin: const EdgeInsets.symmetric(vertical: 20), child: Text( @@ -64,6 +72,7 @@ class _LoginScreenState extends State { ), ), ), + // Logo container Container( decoration: BoxDecoration( color: const Color(0xFFFFFFFF), @@ -85,7 +94,8 @@ class _LoginScreenState extends State { ), ), ), - SizedBox(height: Get.height * 0.05), + SizedBox(height: Get.height * 0.05), // Spacer + // Login Card Card( margin: const EdgeInsets.symmetric(horizontal: 24), shape: RoundedRectangleBorder( @@ -97,6 +107,7 @@ class _LoginScreenState extends State { padding: const EdgeInsets.all(16.0), child: Column( children: [ + // Login icon Container( alignment: Alignment.centerLeft, padding: const EdgeInsets.only(bottom: 10), @@ -105,6 +116,7 @@ class _LoginScreenState extends State { height: Get.height * 0.05, ), ), + // Login header Container( padding: const EdgeInsets.only(bottom: 10), alignment: Alignment.centerLeft, @@ -118,6 +130,7 @@ class _LoginScreenState extends State { ), ), ), + // Subheader Container( padding: const EdgeInsets.only(bottom: 10), alignment: Alignment.centerLeft, @@ -131,10 +144,12 @@ class _LoginScreenState extends State { ), ), ), + // Email input field CommonTextFormField( controller: authController.emailController, keyboardType: TextInputType.emailAddress, validator: (value) { + // Validate email input if (value == null || value.isEmpty) { return 'Please enter your email id'; } @@ -147,9 +162,11 @@ class _LoginScreenState extends State { label: "Email", ), const SizedBox(height: 15), + // Password input field CommonTextFormField( controller: authController.passwordController, validator: (value) { + // Validate password input if (value == null || value.isEmpty) { return 'Please enter your password'; } @@ -158,12 +175,13 @@ class _LoginScreenState extends State { } return null; }, - obscureText: true, + obscureText: true, // Hide password text title: 'Password', maxLines: 1, label: "Password", ), const SizedBox(height: 30), + // Link to forgot password screen GestureDetector( onTap: () => Get.to(() => const ForgetPasswordScreen()), @@ -179,13 +197,14 @@ class _LoginScreenState extends State { ), ), const SizedBox(height: 30), + // Login button with loading state Obx( () => CustomButton( text: "Login", onPressed: () { + // Validate form and perform login action if (formKey.currentState!.validate()) { - // Perform login action - authController.login(); + authController.login(); // Call login method } }, isLoading: authController.isLoading.value, diff --git a/lib/screens/authentication/verify_otp_screen.dart b/lib/screens/authentication/verify_otp_screen.dart index 45c6b3d..b8b06af 100644 --- a/lib/screens/authentication/verify_otp_screen.dart +++ b/lib/screens/authentication/verify_otp_screen.dart @@ -14,37 +14,40 @@ class VerifyOtpScreen extends StatefulWidget { } class _VerifyOtpScreenState extends State { + // Controller for managing the OTP input final otpController = TextEditingController(); + @override Widget build(BuildContext context) { return Scaffold( - extendBodyBehindAppBar: true, + extendBodyBehindAppBar: true, // Extend body behind app bar for a seamless design appBar: AppBar( - backgroundColor: Colors.transparent, - elevation: 0, + backgroundColor: Colors.transparent, // Transparent background for the app bar + elevation: 0, // No shadow leading: GestureDetector( - onTap: () => Get.back(), + onTap: () => Get.back(), // Navigate back on tap child: Padding( - padding: const EdgeInsets.all(8.0), // Adjust the padding as needed + padding: const EdgeInsets.all(8.0), // Padding for the back arrow icon child: SizedBox( - width: 24, // Adjust the width as needed - height: 24, // Adjust the height as needed + width: 24, // Icon width + height: 24, // Icon height child: SvgPicture.asset( - 'assets/svg/back_arrow.svg', + 'assets/svg/back_arrow.svg', // Back arrow SVG asset ), ), ), ), ), body: Stack( - alignment: Alignment.topCenter, + alignment: Alignment.topCenter, // Align items to the top center children: [ + // Background image setup Container( decoration: const BoxDecoration( image: DecorationImage( fit: BoxFit.cover, image: AssetImage( - 'assets/images/image_1.png', + 'assets/images/image_1.png', // Background image asset ), ), borderRadius: BorderRadius.only( @@ -53,33 +56,36 @@ class _VerifyOtpScreenState extends State { ), ), child: SizedBox( - width: Get.width, - height: Get.height * 0.7, + width: Get.width, // Full width + height: Get.height * 0.7, // 70% height of the screen ), ), + // Main content area Center( child: SingleChildScrollView( child: Card( - margin: const EdgeInsets.symmetric(horizontal: 24), + margin: const EdgeInsets.symmetric(horizontal: 24), // Margin for the card shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(19), - side: const BorderSide(color: Color(0xFFFDFDFD)), + borderRadius: BorderRadius.circular(19), // Rounded corners + side: const BorderSide(color: Color(0xFFFDFDFD)), // Border color ), - color: const Color(0xFFB4D1E5).withOpacity(0.9), + color: const Color(0xFFB4D1E5).withOpacity(0.9), // Background color with opacity child: Padding( - padding: const EdgeInsets.all(16.0), + padding: const EdgeInsets.all(16.0), // Padding inside the card child: Column( children: [ + // Verification icon Container( alignment: Alignment.centerLeft, - padding: const EdgeInsets.only(bottom: 10), + padding: const EdgeInsets.only(bottom: 10), // Padding below the icon child: SvgPicture.asset( - 'assets/svg/verify_otp.svg', - height: Get.height * 0.05, + 'assets/svg/verify_otp.svg', // OTP verification icon asset + height: Get.height * 0.05, // Height of the icon ), ), + // Title for the OTP verification Container( - padding: const EdgeInsets.only(bottom: 10), + padding: const EdgeInsets.only(bottom: 10), // Padding below the title alignment: Alignment.centerLeft, child: Text( 'Verification Code', @@ -91,8 +97,9 @@ class _VerifyOtpScreenState extends State { ), ), ), + // Subtext for OTP instructions Container( - padding: const EdgeInsets.only(bottom: 10), + padding: const EdgeInsets.only(bottom: 10), // Padding below the subtext alignment: Alignment.centerLeft, child: Text( 'OTP has sent to your registered mobile number, Please verify', @@ -104,38 +111,40 @@ class _VerifyOtpScreenState extends State { ), ), ), + // Pin code input field for OTP SizedBox( - width: Get.width * 0.8, + width: Get.width * 0.8, // Width of the input field child: PinCodeTextField( appContext: context, - length: 6, - obscureText: false, - animationType: AnimationType.fade, + length: 6, // Length of the OTP + obscureText: false, // Not obscured + animationType: AnimationType.fade, // Fade animation pinTheme: PinTheme( - shape: PinCodeFieldShape.box, - borderRadius: BorderRadius.circular(5), - fieldHeight: 50, - fieldWidth: 40, - activeFillColor: Colors.white, - inactiveFillColor: Colors.white, - selectedFillColor: Colors.white, - activeColor: Colors.blue, - inactiveColor: Colors.grey, - selectedColor: Colors.blue, + shape: PinCodeFieldShape.box, // Shape of the pin fields + borderRadius: BorderRadius.circular(5), // Corner radius + fieldHeight: 50, // Height of each pin field + fieldWidth: 40, // Width of each pin field + activeFillColor: Colors.white, // Background color when active + inactiveFillColor: Colors.white, // Background color when inactive + selectedFillColor: Colors.white, // Background color when selected + activeColor: Colors.blue, // Border color when active + inactiveColor: Colors.grey, // Border color when inactive + selectedColor: Colors.blue, // Border color when selected ), - animationDuration: const Duration(milliseconds: 300), - backgroundColor: Colors.transparent, - enableActiveFill: true, - controller: otpController, + animationDuration: const Duration(milliseconds: 300), // Animation duration + backgroundColor: Colors.transparent, // Transparent background + enableActiveFill: true, // Enable active fill color + controller: otpController, // Link the controller onCompleted: (v) { - print("Completed: $v"); + print("Completed: $v"); // Log when OTP is completed }, onChanged: (value) { - print(value); + print(value); // Log changes to the input }, ), ), - const SizedBox(height: 30), + const SizedBox(height: 30), // Spacer + // Resend OTP timer text Container( alignment: Alignment.center, child: Text( @@ -148,9 +157,10 @@ class _VerifyOtpScreenState extends State { ), ), ), - const SizedBox(height: 30), + const SizedBox(height: 30), // Spacer + // Cancel button to go back GestureDetector( - onTap: () => Get.back(), + onTap: () => Get.back(), // Navigate back on tap child: Text( 'Cancel', style: GoogleFonts.getFont( @@ -162,11 +172,12 @@ class _VerifyOtpScreenState extends State { ), ), ), - const SizedBox(height: 30), + const SizedBox(height: 30), // Spacer + // Verify button to proceed CustomButton( text: "Verify", onPressed: () => Get.to( - () => const VerificationSuccessScreen(), + () => const VerificationSuccessScreen(), // Navigate to success screen ), ), ], diff --git a/lib/screens/home_screen.dart b/lib/screens/home_screen.dart index 94167be..61f309b 100644 --- a/lib/screens/home_screen.dart +++ b/lib/screens/home_screen.dart @@ -13,6 +13,8 @@ import 'package:flutter/material.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:get/get.dart'; +// HomeScreen displays various feature options (Product Catalogue, Order Tracking, etc.) as a grid of cards. +// It uses the GetX package for navigation and state management. class HomeScreen extends StatefulWidget { const HomeScreen({super.key}); @@ -21,113 +23,143 @@ class HomeScreen extends StatefulWidget { } class _HomeScreenState extends State { + // HomeController is being initialized using GetX for state management. final HomeController homeController = Get.put(HomeController()); + @override Widget build(BuildContext context) { return Scaffold( - extendBodyBehindAppBar: true, + extendBodyBehindAppBar: true, // The app bar extends behind the body content. + + // AppBar configuration with transparent background and a custom menu icon appBar: AppBar( centerTitle: true, - backgroundColor: Colors.transparent, - elevation: 0, + backgroundColor: Colors.transparent, // AppBar background is transparent to blend with the background image. + elevation: 0, // No shadow under the AppBar. + + // Leading widget is a custom menu icon that opens the drawer when tapped. leading: Builder( builder: (context) { return GestureDetector( - onTap: () => Scaffold.of(context).openDrawer(), + onTap: () => Scaffold.of(context).openDrawer(), // Opens the drawer. child: Padding( padding: const EdgeInsets.all(16.0), child: SvgPicture.asset( - 'assets/svg/menu.svg', + 'assets/svg/menu.svg', // SVG asset for the menu icon. ), ), ); }, ), + + // Title in the AppBar. title: const Text( "Welcome", ), ), + + // Drawer widget displayed on the side when the menu icon is tapped. drawer: const MyDrawer(), + + // Stack widget to layer the background image and content on top. body: Stack( - fit: StackFit.expand, + fit: StackFit.expand, // Background image will fill the screen. children: [ + // Background image covering the entire screen. Image.asset( 'assets/images/image_1.png', - fit: BoxFit.cover, + fit: BoxFit.cover, // The image is resized to cover the entire area. ), + + // SafeArea widget to ensure the content is placed within safe boundaries. SafeArea( child: Padding( padding: const EdgeInsets.all(8.0), + + // SingleChildScrollView allows scrolling if the content is larger than the screen. child: SingleChildScrollView( child: Column( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, + mainAxisAlignment: MainAxisAlignment.spaceEvenly, // Evenly spaces the rows of cards. children: [ + // Row with two HomeCard widgets for Product Catalogue and Order Tracking. Row( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, + mainAxisAlignment: MainAxisAlignment.spaceEvenly, // Cards are spaced evenly across the row. children: [ + // HomeCard widget with the title and navigation to the Product Catalog screen. HomeCard( title: 'Product Catalogue', onTap: () => - Get.to(() => const ProductCatalogScreen()), + Get.to(() => const ProductCatalogScreen()), // Navigates to ProductCatalogScreen using GetX. ), + + // HomeCard widget with the title and navigation to the Order Tracking screen. HomeCard( title: 'Order Tracking', onTap: () => Get.to( - () => const OrderTrackingScreen(), + () => const OrderTrackingScreen(), // Navigates to OrderTrackingScreen using GetX. ), ), ], ), - const SizedBox(height: 10), + + const SizedBox(height: 10), // Adds vertical spacing between rows. + + // Row with two HomeCard widgets for Order Management and Shipping Management. Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ HomeCard( title: 'Order Management', onTap: () => Get.to( - () => OrderManagementScreen(), + () => OrderManagementScreen(), // Navigates to OrderManagementScreen. ), ), HomeCard( title: 'Shipping Management', onTap: () => Get.to( - () => const ShippingManagementScreen(), + () => const ShippingManagementScreen(), // Navigates to ShippingManagementScreen. ), ), ], ), + const SizedBox(height: 10), + + // Row with two HomeCard widgets for Inventory Management and Reporting & Analytics. Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ HomeCard( title: 'Inventory Management', onTap: () => Get.to( - () => const InventoryManagementScreen(), + () => const InventoryManagementScreen(), // Navigates to InventoryManagementScreen. ), ), HomeCard( title: 'Reporting & Analytics', onTap: () => Get.to( - () => const ReportingAnalyticsScreen(), + () => const ReportingAnalyticsScreen(), // Navigates to ReportingAnalyticsScreen. ), ), ], ), + const SizedBox(height: 10), + + // Row with two HomeCard widgets for Order Data Export and Retail Distributors Onboarding. Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ HomeCard( title: 'Order Data Export', onTap: () => Get.to( - () => const OrderHistoryReportScreen(), + () => const OrderHistoryReportScreen(), // Navigates to OrderHistoryReportScreen. ), ), HomeCard( title: 'Retail Distributors Onboarding', onTap: () => Get.to( - () => const RetailDistributerOnBoardingScreen(), + () => const RetailDistributerOnBoardingScreen(), // Navigates to RetailDistributerOnBoardingScreen. ), ), ], diff --git a/lib/screens/product/cart_screen.dart b/lib/screens/product/cart_screen.dart index 0e1581f..1b0c18c 100644 --- a/lib/screens/product/cart_screen.dart +++ b/lib/screens/product/cart_screen.dart @@ -1,4 +1,3 @@ - import 'package:cheminova/screens/order/checkout_screen.dart'; import 'package:cheminova/widgets/my_drawer.dart'; import 'package:cheminova/widgets/product_card.dart'; @@ -11,9 +10,11 @@ import '../../controller/cart_controller.dart'; import '../../models/oder_place_model.dart'; import '../../models/product_model1.dart'; +// CartScreen represents the cart view in the application, where users can review +// products they have added, select/deselect them, and proceed to checkout. class CartScreen extends StatefulWidget { - Product? productModel; - PlacedOrderModel? placedOrder; + Product? productModel; // Optional product model to add a product directly to the cart. + PlacedOrderModel? placedOrder; // Optional order placement model. CartScreen({super.key, this.productModel, this.placedOrder}); @@ -22,31 +23,39 @@ class CartScreen extends StatefulWidget { } class _CartScreenState extends State { + // CartController handles cart-related logic such as adding/removing products, selections, and totals. final CartController _cartController = Get.find(); - bool _selectAll = true; // Default to true to select all products + bool _selectAll = true; // Boolean flag to determine if all products are selected by default. @override void initState() { super.initState(); + + // If a product model is passed when navigating to this screen, add it to the cart. if (widget.productModel != null) { _cartController.addToCart(widget.productModel!); } - // Ensure all products are pre-selected by default + + // Initialize product selections to ensure all products are selected by default. _cartController.initializeSelections(); - _checkIfAllSelected(); // Check if all products are selected by default + + // Check if all products are selected and update the _selectAll flag accordingly. + _checkIfAllSelected(); } + // Toggles the selection of all products in the cart. void _toggleSelectAll(bool? value) { setState(() { - _selectAll = value ?? false; - _cartController.selectAllProducts(_selectAll); + _selectAll = value ?? false; // Update _selectAll based on the checkbox value. + _cartController.selectAllProducts(_selectAll); // Select or deselect all products. }); } + // Check if all products in the cart are selected. Updates the _selectAll flag. void _checkIfAllSelected() { - // This function checks if all items are selected or not and updates _selectAll setState(() { + // If every product in the cart is selected, _selectAll is set to true. _selectAll = _cartController.cartList.every( (product) => _cartController.selectedProducts.contains(product), ); @@ -57,62 +66,73 @@ class _CartScreenState extends State { Widget build(BuildContext context) { return Scaffold( extendBodyBehindAppBar: true, + + // Transparent AppBar with a menu icon and back button. appBar: AppBar( - backgroundColor: Colors.transparent, - elevation: 0, + backgroundColor: Colors.transparent, // Transparent background. + elevation: 0, // No elevation. leading: Builder( builder: (context) { return GestureDetector( - onTap: () => Scaffold.of(context).openDrawer(), + onTap: () => Scaffold.of(context).openDrawer(), // Open the drawer on tap. child: Padding( padding: const EdgeInsets.all(16.0), child: SvgPicture.asset( - 'assets/svg/menu.svg', + 'assets/svg/menu.svg', // SVG asset for the menu icon. ), ), ); }, ), actions: [ + // Back button to navigate to the previous screen. GestureDetector( - onTap: () => Get.back(), + onTap: () => Get.back(), // Go back on tap. child: Padding( padding: const EdgeInsets.all(8.0), child: SvgPicture.asset( - 'assets/svg/back_arrow.svg', + 'assets/svg/back_arrow.svg', // SVG asset for the back icon. ), ), ), ], title: const Center( child: Text( - "Cart", + "Cart", // Title for the AppBar. ), ), ), - drawer: const MyDrawer(), + + drawer: const MyDrawer(), // Drawer menu. + + // Stack for the background image and the content above it. body: Stack( - fit: StackFit.expand, + fit: StackFit.expand, // Ensure the background image fills the entire screen. children: [ + // Background image that covers the entire screen. Image.asset( 'assets/images/image_1.png', - fit: BoxFit.cover, + fit: BoxFit.cover, // Cover the entire screen with the image. ), + + // SafeArea ensures content is within safe device boundaries. SafeArea( + // Obx listens to changes in the cartList and updates the UI. child: Obx(() { + // If the cart is empty, show an empty cart message and image. if (_cartController.cartList.isEmpty) { return Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Image.asset( - 'assets/images/cart.png', + 'assets/images/cart.png', // Display an empty cart image. width: Get.width * 0.5, height: Get.width * 0.5, ), const SizedBox(height: 20), Text( - 'Your Cart is empty', + 'Your Cart is empty', // Message indicating the cart is empty. style: GoogleFonts.roboto( fontSize: 24, fontWeight: FontWeight.w500, @@ -123,11 +143,15 @@ class _CartScreenState extends State { ), ); } + + // If there are items in the cart, show the cart list and summary. return Column( children: [ SizedBox( height: Get.height * 0.02, ), + + // Card widget for displaying the cart details and selection options. Card( margin: const EdgeInsets.symmetric(horizontal: 18), shape: RoundedRectangleBorder( @@ -140,15 +164,15 @@ class _CartScreenState extends State { child: Column( mainAxisSize: MainAxisSize.min, children: [ - // "Select All" Checkbox + // Row for "Select All" checkbox and label. Row( children: [ Checkbox( - value: _selectAll, - onChanged: _toggleSelectAll, + value: _selectAll, // Checkbox value linked to _selectAll flag. + onChanged: _toggleSelectAll, // Toggle all selections when changed. ), Text( - "Select All", + "Select All", // Label for the select all checkbox. style: GoogleFonts.roboto( fontSize: 16, fontWeight: FontWeight.w500, @@ -156,30 +180,34 @@ class _CartScreenState extends State { ), ], ), + + // List of products in the cart. SizedBox( height: Get.height * 0.6, child: ListView.builder( padding: EdgeInsets.zero, - itemCount: _cartController.cartList.length, + itemCount: _cartController.cartList.length, // Number of items in the cart. itemBuilder: (context, index) { return Row( children: [ + // Checkbox for individual product selection. Checkbox( value: _cartController.selectedProducts.contains( _cartController.cartList[index]), onChanged: (value) { _cartController.toggleProductSelection( _cartController.cartList[index], - value!, + value!, // Toggle product selection. ); - _checkIfAllSelected(); // Check if all are selected after each toggle + _checkIfAllSelected(); // Recheck if all products are selected. }, ), + + // ProductCard displaying product details in the cart. Expanded( child: ProductCard( - productModel: - _cartController.cartList[index], - isInCart: true, + productModel: _cartController.cartList[index], + isInCart: true, // Indicates this product is in the cart. placedOrder: widget.placedOrder, ), ), @@ -189,6 +217,8 @@ class _CartScreenState extends State { ), ), const SizedBox(height: 10), + + // Row displaying the subtotal amount. Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ @@ -200,10 +230,12 @@ class _CartScreenState extends State { fontWeight: FontWeight.bold, ), ), - Obx(() => - Text("₹ ${_cartController.subtotal.value}")), + // Display the subtotal using an Obx to update automatically. + Obx(() => Text("₹ ${_cartController.subtotal.value}")), ], ), + + // Row displaying the GST amount. Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ @@ -215,10 +247,11 @@ class _CartScreenState extends State { fontWeight: FontWeight.bold, ), ), - Obx(() => - Text("₹ ${_cartController.gstTotal.value}")), + Obx(() => Text("₹ ${_cartController.gstTotal.value}")), ], ), + + // Row displaying the total amount (subtotal + GST). Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ @@ -230,25 +263,27 @@ class _CartScreenState extends State { fontWeight: FontWeight.bold, ), ), - Obx(() => Text( - "₹ ${_cartController.grandTotal.value}")), + Obx(() => Text("₹ ${_cartController.grandTotal.value}")), ], ), ], ), ), ), + SizedBox(height: Get.height * 0.020), + + // Button to proceed to the checkout screen. SizedBox( width: Get.width * 0.9, height: Get.height * 0.06, child: ElevatedButton( onPressed: () => Get.to(() => CheckoutScreen( - selectedProducts: _cartController.selectedProducts, + selectedProducts: _cartController.selectedProducts, // Pass selected products to CheckoutScreen. )), style: ElevatedButton.styleFrom( foregroundColor: Colors.white, - backgroundColor: const Color(0xFF00784C), + backgroundColor: const Color(0xFF00784C), // Green button for checkout. shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(10), ), @@ -276,6 +311,7 @@ class _CartScreenState extends State { + // import 'package:cheminova/models/oder_place_model.dart'; // import 'package:cheminova/models/product_model.dart'; // import 'package:cheminova/screens/order/checkout_screen.dart'; diff --git a/lib/screens/product/product_catalog_screen.dart b/lib/screens/product/product_catalog_screen.dart index 4090e9b..50631a9 100644 --- a/lib/screens/product/product_catalog_screen.dart +++ b/lib/screens/product/product_catalog_screen.dart @@ -8,8 +8,8 @@ import '../../controller/cart_controller.dart'; import '../../widgets/my_drawer.dart'; import '../../widgets/product_card.dart'; import '../../controller/product_service.dart'; -import '../../models/product_model.dart'; // Import your ProductModel +// ProductCatalogScreen displays a catalog of products that users can browse and filter. class ProductCatalogScreen extends StatefulWidget { const ProductCatalogScreen({super.key}); @@ -18,100 +18,107 @@ class ProductCatalogScreen extends StatefulWidget { } class _ProductCatalogScreenState extends State { - final ProductService _productService = ProductService(); - final ScrollController _scrollController = ScrollController(); - final CartController cartController = Get.put(CartController()); + final ProductService _productService = ProductService(); // Service to fetch product data. + final ScrollController _scrollController = ScrollController(); // Controller for scroll events. + final CartController cartController = Get.put(CartController()); // Cart controller for managing cart state. - List _products = []; // Use ProductModel here - List> _categories = []; + List _products = []; // List to hold the fetched products. + List> _categories = []; // List to hold product categories. - int _currentPage = 1; - bool _isLoading = false; - bool _hasMoreData = true; + int _currentPage = 1; // Tracks the current page of products being fetched. + bool _isLoading = false; // Indicates if data is being loaded. + bool _hasMoreData = true; // Indicates if more data can be fetched. - String? _selectedCategory; - String? _selectedPriceRange; - String? _selectedAvailability; + String? _selectedCategory; // Tracks the currently selected category for filtering. + String? _selectedPriceRange; // Placeholder for selected price range filtering. + String? _selectedAvailability; // Placeholder for selected availability filtering. @override void initState() { super.initState(); - _fetchCategories(); - _fetchProducts(); + _fetchCategories(); // Fetch categories on initialization. + _fetchProducts(); // Fetch initial products. + + // Add listener to fetch more products when reaching the bottom of the scroll view. _scrollController.addListener(() { if (_scrollController.position.pixels == _scrollController.position.maxScrollExtent && - !_isLoading && - _hasMoreData) { + !_isLoading && _hasMoreData) { _fetchMoreProducts(); } }); } + // Fetch products from the server. Future _fetchProducts() async { setState(() { - _isLoading = true; + _isLoading = true; // Set loading to true while fetching data. }); - final category = _selectedCategory == 'All' ? null : _selectedCategory; + final category = _selectedCategory == 'All' ? null : _selectedCategory; // Adjust category filter. + // Get products using the product service. final products = await _productService.getProduct( _currentPage, category: category); setState(() { if (products != null) { - _products.addAll(products); // Assuming products is a List - _hasMoreData = products.isNotEmpty; + _products.addAll(products); // Append fetched products to the list. + _hasMoreData = products.isNotEmpty; // Update the flag for more data availability. } - _isLoading = false; + _isLoading = false; // Set loading to false after fetching data. }); } + // Fetch available product categories. Future _fetchCategories() async { - final categories = await _productService.getCategory(); + final categories = await _productService.getCategory(); // Fetch categories. if (categories != null) { setState(() { - _categories = categories; - _categories.insert(1, {'categoryName': 'All'}); - _selectedCategory = 'All'; + _categories = categories; // Set the fetched categories. + _categories.insert(1, {'categoryName': 'All'}); // Add 'All' category option. + _selectedCategory = 'All'; // Default selected category. }); } } + // Handle category changes when the user selects a new category. void _onCategoryChanged(String? newCategory) { if (newCategory != null) { setState(() { - _selectedCategory = newCategory; - _products.clear(); - _currentPage = 1; + _selectedCategory = newCategory; // Update selected category. + _products.clear(); // Clear current product list for new fetch. + _currentPage = 1; // Reset page counter. }); - _fetchProducts(); + _fetchProducts(); // Fetch products for the selected category. } } + // Fetch additional products when scrolling to the bottom. Future _fetchMoreProducts() async { - if (!_isLoading && _hasMoreData) { + if (!_isLoading && _hasMoreData) { // Ensure not already loading and more data is available. setState(() { - _isLoading = true; - _currentPage++; + _isLoading = true; // Set loading to true while fetching more products. + _currentPage++; // Increment the page number for next fetch. }); - await _fetchProducts(); + await _fetchProducts(); // Fetch more products. } } + // Clear all filters and reset the product list. void _clearFilters() { setState(() { - _selectedCategory = null; - _selectedPriceRange = null; - _selectedAvailability = null; - _products.clear(); - _currentPage = 1; + _selectedCategory = null; // Reset category filter. + _selectedPriceRange = null; // Reset price range filter. + _selectedAvailability = null; // Reset availability filter. + _products.clear(); // Clear current product list. + _currentPage = 1; // Reset page counter. }); - _fetchProducts(); + _fetchProducts(); // Fetch products without filters. } @override void dispose() { - _scrollController.dispose(); + _scrollController.dispose(); // Dispose of the scroll controller when done. super.dispose(); } @@ -120,65 +127,67 @@ class _ProductCatalogScreenState extends State { return Scaffold( extendBodyBehindAppBar: true, appBar: AppBar( - backgroundColor: Colors.transparent, - elevation: 0, + backgroundColor: Colors.transparent, // Make the AppBar transparent. + elevation: 0, // No shadow for the AppBar. leading: Builder( builder: (context) { return GestureDetector( - onTap: () => Scaffold.of(context).openDrawer(), + onTap: () => Scaffold.of(context).openDrawer(), // Open drawer on tap. child: Padding( padding: const EdgeInsets.all(16.0), child: SvgPicture.asset( - 'assets/svg/menu.svg', + 'assets/svg/menu.svg', // Menu icon. ), ), ); }, ), actions: [ + // Cart icon with count indicator. GestureDetector( - onTap: () => Get.to(CartScreen()), + onTap: () => Get.to(CartScreen()), // Navigate to cart on tap. child: Padding( padding: const EdgeInsets.all(8.0), child: Stack( children: [ - Icon(Icons.shopping_cart, size: 30,), + const Icon(Icons.shopping_cart, size: 30,), // Shopping cart icon. // Cart count display Positioned( right: 0, child: Obx(() => cartController.cartCount.value > 0 ? Container( - padding: EdgeInsets.all(4), - decoration: BoxDecoration( + padding: const EdgeInsets.all(4), + decoration: const BoxDecoration( color: Colors.red, shape: BoxShape.circle, ), child: Text( - '${cartController.cartCount.value}', - style: TextStyle( + '${cartController.cartCount.value}', // Display number of items in cart. + style: const TextStyle( color: Colors.white, fontSize: 12, ), ), ) - : SizedBox.shrink()), + : const SizedBox.shrink()), // Hide if cart is empty. ), ], ), ), ), ], - title: Center( - child: const Text( - "Product Catalogue", + title: const Center( + child: Text( + "Product Catalogue", // Title of the screen. ), ), ), - drawer: const MyDrawer(), + drawer: const MyDrawer(), // Navigation drawer. body: Stack( fit: StackFit.expand, children: [ + // Background image for the screen. Image.asset( 'assets/images/image_1.png', fit: BoxFit.cover, @@ -187,12 +196,14 @@ class _ProductCatalogScreenState extends State { child: SingleChildScrollView( child: Column( children: [ - SizedBox(height: Get.height * 0.02), + SizedBox(height: Get.height * 0.02), // Spacing. + + // Search bar for products. Padding( padding: const EdgeInsets.symmetric(horizontal: 18.0), child: TextField( decoration: InputDecoration( - hintText: "Search Products", + hintText: "Search Products", // Placeholder for search. border: OutlineInputBorder( borderRadius: BorderRadius.circular(15), ), @@ -201,7 +212,9 @@ class _ProductCatalogScreenState extends State { ), ), ), - SizedBox(height: Get.height * 0.02), + SizedBox(height: Get.height * 0.02), // Spacing. + + // Card for filters and product listing. Card( margin: const EdgeInsets.symmetric(horizontal: 18), shape: RoundedRectangleBorder( @@ -214,20 +227,22 @@ class _ProductCatalogScreenState extends State { child: Column( mainAxisSize: MainAxisSize.min, children: [ + // Filter section header. Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( - "Filter", + "Filter", // Filter title. style: GoogleFonts.poppins( fontWeight: FontWeight.bold, fontSize: 16, ), ), + // Button to clear filters. TextButton( onPressed: _clearFilters, child: Text( - "Clear Filter", + "Clear Filter", // Clear filters button text. style: GoogleFonts.poppins( color: Colors.red, fontSize: 14, @@ -236,35 +251,37 @@ class _ProductCatalogScreenState extends State { ), ], ), + + // Dropdowns for filtering options. SizedBox( height: Get.height * 0.05, child: ListView.builder( shrinkWrap: true, scrollDirection: Axis.horizontal, - itemCount: 3, + itemCount: 3, // Number of filters (categories, etc.). itemBuilder: (context, index) => - _buildFilterDropdown(index), + _buildFilterDropdown(index), // Build filter dropdowns. ), ), + + // List of products. SizedBox( height: Get.height * 0.6, child: ListView.builder( - controller: _scrollController, + controller: _scrollController, // Attach scroll controller. padding: EdgeInsets.zero, - itemCount: _products.length + - (_isLoading ? 1 : 0), + itemCount: _products.length + (_isLoading ? 1 : 0), // Show loading indicator if more products are being fetched. itemBuilder: (context, index) { if (index >= _products.length) { print("Product length $_products"); return const Center( - child: CircularProgressIndicator()); + child: CircularProgressIndicator()); // Show loading indicator. } + // Display each product card. return Padding( - padding: const EdgeInsets.symmetric( - vertical: 8), + padding: const EdgeInsets.symmetric(vertical: 8), child: ProductCard( - productModel: _products[index], - // Use ProductModel here + productModel: _products[index], // Use ProductModel here. ), ); }, @@ -283,6 +300,7 @@ class _ProductCatalogScreenState extends State { ); } + // Build a dropdown for filtering based on the index. Widget _buildFilterDropdown(int index) { switch (index) { case 0: @@ -295,57 +313,14 @@ class _ProductCatalogScreenState extends State { child: Text(category['categoryName']), ); }).toList(), - hint: "Category", + hint: "Category", // Dropdown hint for category selection. ); - // case 1: - // return _buildStyledDropdown( - // value: _selectedPriceRange, - // onChanged: (value) { - // setState(() { - // _selectedPriceRange = value; - // }); - // }, - // items: [ - // const DropdownMenuItem( - // value: "Under ₹50", - // child: Text("Under ₹50"), - // ), - // const DropdownMenuItem( - // value: "₹50 - ₹100", - // child: Text("₹50 - ₹100"), - // ), - // const DropdownMenuItem( - // value: "Above ₹100", - // child: Text("Above ₹100"), - // ), - // ], - // hint: "Price Range", - // ); - // case 2: - // return _buildStyledDropdown( - // value: _selectedAvailability, - // onChanged: (value) { - // setState(() { - // _selectedAvailability = value; - // }); - // }, - // items: [ - // const DropdownMenuItem( - // value: "In Stock", - // child: Text("In Stock"), - // ), - // const DropdownMenuItem( - // value: "Out of Stock", - // child: Text("Out of Stock"), - // ), - // ], - // hint: "Availability", - // ); default: - return const SizedBox(); + return const SizedBox(); // Return empty widget for unsupported indices. } } + // Build a styled dropdown widget. Widget _buildStyledDropdown({ required String? value, required ValueChanged onChanged, @@ -365,10 +340,9 @@ class _ProductCatalogScreenState extends State { onChanged: onChanged, items: items, hint: Text(hint), - icon: const Icon(Icons.arrow_drop_down), + icon: const Icon(Icons.arrow_drop_down), // Dropdown arrow icon. ), ), ); } - } diff --git a/lib/screens/product/product_detail_screen.dart b/lib/screens/product/product_detail_screen.dart index 17a0441..6e98494 100644 --- a/lib/screens/product/product_detail_screen.dart +++ b/lib/screens/product/product_detail_screen.dart @@ -9,9 +9,10 @@ import 'package:google_fonts/google_fonts.dart'; import '../../controller/cart_controller.dart'; +// ProductDetailScreen displays detailed information about a selected product. class ProductDetailScreen extends StatefulWidget { - Product? productModel; - ProductModel? product; + Product? productModel; // Holds the product model data for display. + ProductModel? product; // Another product model (not used in this code). ProductDetailScreen({super.key, this.product, this.productModel}); @@ -20,27 +21,30 @@ class ProductDetailScreen extends StatefulWidget { } class _ProductDetailScreenState extends State { - final CartController _cartController = Get.put(CartController()); + final CartController _cartController = Get.put(CartController()); // Controller for managing cart state. + + // Capitalizes the first letter of the given text. String capitalizeFirstLetter(String text) { - if (text.isEmpty) return text; - return text[0].toUpperCase() + text.substring(1).toLowerCase(); + if (text.isEmpty) return text; // Return empty if the text is empty. + return text[0].toUpperCase() + text.substring(1).toLowerCase(); // Capitalize the first letter. } + @override Widget build(BuildContext context) { return Scaffold( - extendBodyBehindAppBar: true, + extendBodyBehindAppBar: true, // Extend the body behind the AppBar. appBar: AppBar( - centerTitle: true, - backgroundColor: Colors.transparent, - elevation: 0, + centerTitle: true, // Center the title. + backgroundColor: Colors.transparent, // Make AppBar transparent. + elevation: 0, // Remove shadow from AppBar. leading: Builder( builder: (context) { return GestureDetector( - onTap: () => Scaffold.of(context).openDrawer(), + onTap: () => Scaffold.of(context).openDrawer(), // Open drawer on tap. child: Padding( padding: const EdgeInsets.all(16.0), child: SvgPicture.asset( - 'assets/svg/menu.svg', + 'assets/svg/menu.svg', // Menu icon. ), ), ); @@ -48,111 +52,115 @@ class _ProductDetailScreenState extends State { ), actions: [ GestureDetector( - onTap: () => Get.back(), + onTap: () => Get.back(), // Navigate back on tap. child: Padding( padding: const EdgeInsets.all(8.0), child: SvgPicture.asset( - 'assets/svg/back_arrow.svg', + 'assets/svg/back_arrow.svg', // Back arrow icon. ), ), ), ], title: const Text( - "Product Detail", + "Product Detail", // Title of the screen. ), ), - drawer: const MyDrawer(), + drawer: const MyDrawer(), // Navigation drawer. body: Stack( - fit: StackFit.expand, + fit: StackFit.expand, // Expand the stack to fit the screen. children: [ Image.asset( - 'assets/images/image_1.png', + 'assets/images/image_1.png', // Background image. fit: BoxFit.cover, ), SafeArea( child: Column( - mainAxisAlignment: MainAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.start, // Align items to start. children: [ - SizedBox( - height: Get.height * 0.02, - ), + SizedBox(height: Get.height * 0.02), // Spacing above the card. + + // Card to display product details. Card( - margin: const EdgeInsets.symmetric(horizontal: 16), + margin: const EdgeInsets.symmetric(horizontal: 16), // Horizontal margin. shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(19), - side: const BorderSide(color: Color(0xFFFDFDFD)), + borderRadius: BorderRadius.circular(19), // Rounded corners. + side: const BorderSide(color: Color(0xFFFDFDFD)), // Border color. ), - color: const Color(0xFFB4D1E5).withOpacity(0.9), + color: const Color(0xFFB4D1E5).withOpacity(0.9), // Background color of the card. child: Padding( - padding: const EdgeInsets.all(12.0), + padding: const EdgeInsets.all(12.0), // Padding inside the card. child: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, // Minimize the size of the column. + crossAxisAlignment: CrossAxisAlignment.start, // Align items to start. children: [ + // Product image section. Padding( padding: const EdgeInsets.all(8.0), child: Container( - height: Get.height * 0.4, - width: Get.width * 0.8, + height: Get.height * 0.4, // Height of the image container. + width: Get.width * 0.8, // Width of the image container. decoration: BoxDecoration( border: Border.all( width: 4, - color: Colors.white, + color: Colors.white, // Border color. ), - borderRadius: BorderRadius.circular(15), + borderRadius: BorderRadius.circular(15), // Rounded corners for the container. ), child: ClipRRect( - borderRadius: BorderRadius.circular(10), - child: Image.asset("assets/images/product.png", fit: BoxFit.cover,), - // Image.asset( - // widget.product.image, - // fit: BoxFit.cover, - // ), + borderRadius: BorderRadius.circular(10), // Clip the corners of the image. + child: Image.asset( + "assets/images/product.png", // Product image. + fit: BoxFit.cover, // Cover the container. + ), ), ), ), + + // Product name row. Padding( padding: const EdgeInsets.symmetric(horizontal: 8), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - const Text("Product Name ",style: TextStyle(fontWeight: FontWeight.bold,fontSize: 16),), + const Text("Product Name ", style: TextStyle(fontWeight: FontWeight.bold, fontSize: 16)), // Label for product name. Text( - capitalizeFirstLetter(widget.productModel!.name), + capitalizeFirstLetter(widget.productModel!.name), // Display product name. style: GoogleFonts.roboto( fontSize: 16, - // fontWeight: FontWeight.w600, color: Colors.black, ), ), ], ), ), + + // Product price row. Padding( padding: const EdgeInsets.symmetric(horizontal: 8), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - const Text("Price ",style: TextStyle(fontSize: 16,fontWeight: FontWeight.bold),), + const Text("Price ", style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold)), // Label for product price. Text( - "₹ ${widget.productModel!.price.toString()}", + "₹ ${widget.productModel!.price.toString()}", // Display product price. style: GoogleFonts.roboto( fontSize: 16, - //fontWeight: FontWeight.w800, color: Colors.black, ), ), ], ), ), + + // Product category row. Padding( padding: const EdgeInsets.symmetric(horizontal: 8), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - const Text("Category ",style: TextStyle(fontWeight: FontWeight.bold,fontSize: 16),), + const Text("Category ", style: TextStyle(fontWeight: FontWeight.bold, fontSize: 16)), // Label for category. Text( - capitalizeFirstLetter(widget.productModel!.category.categoryName), + capitalizeFirstLetter(widget.productModel!.category.categoryName), // Display category name. style: GoogleFonts.roboto( fontSize: 16, fontWeight: FontWeight.w400, @@ -162,14 +170,17 @@ class _ProductDetailScreenState extends State { ], ), ), - const SizedBox(height: 8), + + const SizedBox(height: 8), // Spacing between rows. + + // Product description row. Padding( padding: const EdgeInsets.symmetric(horizontal: 8), child: Row( children: [ - const Text("Description",style: TextStyle(fontWeight: FontWeight.bold,fontSize: 16)), + const Text("Description", style: TextStyle(fontWeight: FontWeight.bold, fontSize: 16)), // Label for description. Text( - capitalizeFirstLetter(widget.productModel!.description), + capitalizeFirstLetter(widget.productModel!.description), // Display product description. style: GoogleFonts.roboto( fontSize: 16, fontWeight: FontWeight.w400, @@ -183,25 +194,28 @@ class _ProductDetailScreenState extends State { ), ), ), - SizedBox(height: Get.height * 0.04), + + SizedBox(height: Get.height * 0.04), // Spacing below the card. + + // Add to cart button. SizedBox( width: Get.width * 0.9, height: Get.height * 0.06, child: ElevatedButton( onPressed: () { - // Pass the product data to the CartScreen + // Add the product to the cart and show a snackbar notification. _cartController.addToCart(widget.productModel!); - showSnackbar("Product successfully added to your cart"); + showSnackbar("Product successfully added to your cart"); // Display success message. }, style: ElevatedButton.styleFrom( - foregroundColor: Colors.white, - backgroundColor: const Color(0xFF00784C), + foregroundColor: Colors.white, // Text color. + backgroundColor: const Color(0xFF00784C), // Button background color. shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(10), + borderRadius: BorderRadius.circular(10), // Rounded corners for the button. ), ), child: Text( - "Add To Cart", + "Add To Cart", // Button text. style: GoogleFonts.roboto( fontSize: 16, fontWeight: FontWeight.w600, diff --git a/lib/services/api_service.dart b/lib/services/api_service.dart new file mode 100644 index 0000000..c715702 --- /dev/null +++ b/lib/services/api_service.dart @@ -0,0 +1,32 @@ +import 'package:cheminova/services/app_interceptor.dart'; +import 'package:dio/dio.dart'; +import 'package:pretty_dio_logger/pretty_dio_logger.dart'; + +class ApiService { + final Dio _dio; + + ApiService() : _dio = Dio(BaseOptions(baseUrl: 'https://api.cnapp.co.in')) { + _dio.interceptors.add(AuthInterceptor()); + _dio.interceptors.add(PrettyDioLogger()); + } + + Future get(String path) async { + try { + return await _dio.get(path); + } on DioException catch (e) { + return _handleError(e); + } + } + + Future _handleError(DioException e) async { + if (e.response != null) { + return e.response!; + } else { + return Response( + requestOptions: RequestOptions(path: ''), + statusCode: 500, + data: {'message': 'An unexpected error occurred'}, + ); + } + } +} diff --git a/lib/services/app_interceptor.dart b/lib/services/app_interceptor.dart new file mode 100644 index 0000000..5197dac --- /dev/null +++ b/lib/services/app_interceptor.dart @@ -0,0 +1,29 @@ +import 'package:dio/dio.dart'; +import 'package:shared_preferences/shared_preferences.dart'; + +class AuthInterceptor extends Interceptor { + @override + void onRequest( + RequestOptions options, RequestInterceptorHandler handler) async { + final prefs = await SharedPreferences.getInstance(); + final token = prefs.getString('token'); + + if (token != null) { + options.headers['Authorization'] = 'Bearer $token'; + } + + handler.next(options); + } + + @override + void onResponse(Response response, ResponseInterceptorHandler handler) { + // Handle the response if needed + handler.next(response); + } + + @override + void onError(DioException err, ErrorInterceptorHandler handler) async { + if (err.response?.statusCode == 401) {} + return handler.next(err); + } +} diff --git a/lib/widgets/my_drawer.dart b/lib/widgets/my_drawer.dart index d000a08..e03de30 100644 --- a/lib/widgets/my_drawer.dart +++ b/lib/widgets/my_drawer.dart @@ -15,7 +15,8 @@ class MyDrawer extends StatefulWidget { } class _MyDrawerState extends State { - final homeController = Get.put(HomeController()); + final HomeController _homeController = Get.find(); + @override Widget build(BuildContext context) { return Drawer( @@ -24,38 +25,40 @@ class _MyDrawerState extends State { children: [ SizedBox( height: 150, - child:( - DrawerHeader( + child: Obx(() { + if (_homeController.isLoading.value) { + return const Center(child: CircularProgressIndicator()); + } else if (_homeController.error.value.isNotEmpty) { + return Center(child: Text(_homeController.error.value)); + } else { + final user = _homeController.userProfile.value; + return DrawerHeader( decoration: const BoxDecoration( color: Colors.black87, ), - child: GetBuilder( - init: homeController, - builder: (controller) { - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisAlignment: MainAxisAlignment.start, - children: [ - Text( - controller.user?.name?? "username", - style: const TextStyle( - color: Colors.white, - fontSize: 18, - ), - ), - Text( - controller.user?.uniqueId?? 'Employee ID', - style: const TextStyle( - color: Colors.white, - fontSize: 20, - ), - ), - ], - ); - } + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Text( + user!.myData!.name ?? "username", + style: const TextStyle( + color: Colors.white, + fontSize: 18, + ), + ), + Text( + user!.myData!.uniqueId ?? 'Employee ID', + style: const TextStyle( + color: Colors.white, + fontSize: 20, + ), + ), + ], ), - ) - ), + ); + } + }), ), ListTile( leading: const Icon(Icons.home), @@ -65,15 +68,13 @@ class _MyDrawerState extends State { ListTile( leading: const Icon(Icons.account_circle), title: const Text('Profile'), - onTap: () { - Get.to(ProfileScreen()); - }, + onTap: () => Get.to(() => const ProfileScreen()), ), ListTile( leading: const Icon(Icons.settings), title: const Text('Change Password'), onTap: () { - Get.to(ChangePasswordScreen()); + Get.to(const ChangePasswordScreen()); }, ), ListTile(