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