1)getProduct api Integration

2)getCategory wise product api integration
3)Category wise product filter added
This commit is contained in:
saritabirare 2024-08-30 11:26:38 +05:30
parent 7aa59ca145
commit 43413ac168
6 changed files with 485 additions and 173 deletions

View File

@ -0,0 +1,64 @@
import 'package:cheminova/controller/product_service.dart';
import 'package:get/get.dart';
class ProductController extends GetxController {
final ProductService productService = ProductService();
var products = <Map<String, dynamic>>[].obs;
var categories = <String>[].obs; // Holds the list of categories
var selectedCategory = Rxn<String>(); // Holds the selected category
int _currentPage = 1;
bool isLoading = false;
@override
void onInit() {
super.onInit();
getCategory();
getUser();
}
Future<void> getUser() async {
if (isLoading) return;
isLoading = true;
try {
final category = selectedCategory.value; // Get the selected category
final fetchedProducts = await productService.getProduct(
_currentPage,
category: category,
);
if (fetchedProducts != null) {
products.addAll(fetchedProducts as Iterable<Map<String, dynamic>>);
}
} catch (e) {
print("Error fetching products: $e");
} finally {
isLoading = false;
update();
}
}
Future<void> getCategory() async {
try {
final fetchedCategories = await productService.getCategory();
if (fetchedCategories != null) {
categories.assignAll(fetchedCategories.map((category) => category['categoryName'] as String));
categories.insert(0, 'All'); // Add "All" option
}
} catch (e) {
print("Error fetching categories: $e");
}
}
void setCategory(String category) {
selectedCategory.value = category == 'All' ? null : category;
_currentPage = 1;
products.clear();
getUser();
}
void loadMoreProducts() {
_currentPage++;
getUser();
}
}

View File

@ -0,0 +1,60 @@
import '../utils/common_api_service.dart';
import '../utils/show_snackbar.dart';
class ProductService {
Future<List<Map<String, dynamic>>?> getProduct(int page, {String? category}) async {
try {
String url;
if (category != null && category.isNotEmpty) {
url = "/api/product/getAll/user?page=$page&category=$category";
} else {
url = "/api/product/getAll/user?page=$page"; // URL without category filter
}
final response = await commonApiService<List<Map<String, dynamic>>>(
method: "GET",
url: url,
fromJson: (json) {
if (json['products'] != null) {
final List<Map<String, dynamic>> products = (json['products'] as List)
.map((productJson) => productJson as Map<String, dynamic>)
.toList();
return products;
} else {
return [];
}
},
);
return response;
} catch (e) {
showSnackbar(e.toString());
//print("Error: $e");
}
return null;
}
Future<List<Map<String, dynamic>>?> getCategory() async {
try {
final response = await commonApiService<List<Map<String, dynamic>>>(
method: "GET",
url: "/api/category/getCategories",
fromJson: (json) {
if (json['categories'] != null) {
final List<Map<String, dynamic>> category = (json['categories'] as List)
.map((productJson) => productJson as Map<String, dynamic>)
.toList();
return category;
} else {
return [];
}
},
);
return response;
} catch (e) {
showSnackbar(e.toString());
print("Error: $e");
}
return null;
}
}

View File

@ -110,7 +110,7 @@ class _ForgetPasswordScreenState extends State<ForgetPasswordScreen> {
InputField( InputField(
hintText: "Email", hintText: "Email",
labelText: "Email", labelText: "Email",
controller: userNameController, controller: authController.emailController,
keyboardType: TextInputType.emailAddress, keyboardType: TextInputType.emailAddress,
), ),
const SizedBox(height: 30), const SizedBox(height: 30),

View File

@ -1,10 +1,10 @@
import 'package:cheminova/models/product_model.dart';
import 'package:cheminova/widgets/my_drawer.dart';
import 'package:cheminova/widgets/product_card.dart';
import 'package:flutter/material.dart'; 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 'package:google_fonts/google_fonts.dart'; import 'package:google_fonts/google_fonts.dart';
import '../../widgets/my_drawer.dart';
import '../../widgets/product_card.dart';
import '../../controller/product_service.dart';
class ProductCatalogScreen extends StatefulWidget { class ProductCatalogScreen extends StatefulWidget {
const ProductCatalogScreen({super.key}); const ProductCatalogScreen({super.key});
@ -14,22 +14,101 @@ class ProductCatalogScreen extends StatefulWidget {
} }
class _ProductCatalogScreenState extends State<ProductCatalogScreen> { class _ProductCatalogScreenState extends State<ProductCatalogScreen> {
final List<ProductModel> _productList = [ final ProductService _productService = ProductService();
ProductModel( final ScrollController _scrollController = ScrollController();
id: '1', List<Map<String, dynamic>> _products = [];
name: 'Product 1', List<Map<String, dynamic>> _categories = [];
price: 100,
description: 'Description 1', int _currentPage = 1;
category: ProductCategory.food, bool _isLoading = false;
image: 'assets/images/product.png', bool _hasMoreData = true;
)
]; String? _selectedCategory; // Default to null to handle 'All' explicitly
String? _selectedPriceRange;
String? _selectedAvailability;
@override
void initState() {
super.initState();
_fetchCategories(); // Fetch categories first to set initial filter
_fetchProducts(); // Fetch products after setting initial filters
_scrollController.addListener(() {
if (_scrollController.position.pixels == _scrollController.position.maxScrollExtent &&
!_isLoading &&
_hasMoreData) {
_fetchMoreProducts();
}
});
}
Future<void> _fetchProducts() async {
setState(() {
_isLoading = true;
});
// Adjust the category parameter based on the selected category
final category = _selectedCategory == 'All' ? null : _selectedCategory;
final products = await _productService.getProduct(_currentPage, category: category);
setState(() {
if (products != null) {
_products.addAll(products);
_hasMoreData = products.isNotEmpty;
}
_isLoading = false;
});
}
Future<void> _fetchCategories() async {
final categories = await _productService.getCategory();
if (categories != null) {
setState(() {
_categories = categories;
// Add "All" option
_categories.insert(1, {'categoryName': 'All'});
_selectedCategory = 'All'; // Set initial selected category to "All"
});
}
}
void _onCategoryChanged(String? newCategory) {
if (newCategory != null) {
setState(() {
_selectedCategory = newCategory;
_products.clear();
_currentPage = 1;
});
_fetchProducts();
}
}
Future<void> _fetchMoreProducts() async {
if (!_isLoading && _hasMoreData) {
setState(() {
_isLoading = true;
_currentPage++;
});
await _fetchProducts();
}
}
void _clearFilters() {
setState(() {
_selectedCategory = null;
_selectedPriceRange = null;
_selectedAvailability = null;
_products.clear();
_currentPage = 1;
});
_fetchProducts();
}
@override
void dispose() {
_scrollController.dispose();
super.dispose();
}
final List<String> _filterList = [
"Category",
"Price Range",
"Availability",
];
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
@ -74,11 +153,25 @@ class _ProductCatalogScreenState extends State<ProductCatalogScreen> {
fit: BoxFit.cover, fit: BoxFit.cover,
), ),
SafeArea( SafeArea(
child: SingleChildScrollView(
child: Column( child: Column(
children: [ children: [
SizedBox( SizedBox(height: Get.height * 0.02),
height: Get.height * 0.02, // Search Bar
Padding(
padding: const EdgeInsets.symmetric(horizontal: 18.0),
child: TextField(
decoration: InputDecoration(
hintText: "Search Products",
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(15),
), ),
filled: true,
fillColor: Colors.white.withOpacity(0.9),
),
),
),
SizedBox(height: Get.height * 0.02),
Card( Card(
margin: const EdgeInsets.symmetric(horizontal: 18), margin: const EdgeInsets.symmetric(horizontal: 18),
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(
@ -91,41 +184,60 @@ class _ProductCatalogScreenState extends State<ProductCatalogScreen> {
child: Column( child: Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
"Filters",
style: GoogleFonts.poppins(
fontWeight: FontWeight.bold,
fontSize: 16,
),
),
TextButton(
onPressed: _clearFilters,
child: Text(
"Clear Filters",
style: GoogleFonts.poppins(
color: Colors.red,
fontSize: 14,
),
),
),
],
),
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: _filterList.length, itemCount: 3,
itemBuilder: (context, index) => Padding( itemBuilder: (context, index) => Padding(
padding: padding: const EdgeInsets.symmetric(horizontal: 4),
const EdgeInsets.symmetric(horizontal: 4), child: _buildFilterDropdown(index),
child: Chip(
label: Text(
_filterList[index],
style: GoogleFonts.roboto(
fontSize: 14,
fontWeight: FontWeight.w500,
),
),
),
), ),
), ),
), ),
SizedBox( SizedBox(
height: Get.height * 0.6, height: Get.height * 0.6,
child: ListView.builder( child: ListView.builder(
controller: _scrollController,
padding: EdgeInsets.zero, padding: EdgeInsets.zero,
shrinkWrap: true, itemCount: _products.length + (_isLoading ? 1 : 0),
itemCount: 10, itemBuilder: (context, index) {
itemBuilder: (context, index) => Padding( if (index >= _products.length) {
print("Product length $_products");
return const Center(child: CircularProgressIndicator());
}
return Padding(
padding: const EdgeInsets.symmetric(vertical: 8), padding: const EdgeInsets.symmetric(vertical: 8),
child: ProductCard( child: ProductCard(
product: _productList[0], productModel: _products[index],
),
);
},
), ),
), ),
),
)
], ],
), ),
), ),
@ -133,8 +245,66 @@ class _ProductCatalogScreenState extends State<ProductCatalogScreen> {
], ],
), ),
), ),
),
], ],
), ),
); );
} }
Widget _buildFilterDropdown(int index) {
switch (index) {
case 0:
return DropdownButton<String>(
value: _selectedCategory,
hint: const Text("Category"),
onChanged: (value) {
if (value != null) {
_onCategoryChanged(value);
}
},
items: _categories.map<DropdownMenuItem<String>>((category) {
return DropdownMenuItem<String>(
value: category['categoryName'],
child: Text(category['categoryName']),
);
}).toList(),
);
case 1:
return DropdownButton<String>(
value: _selectedPriceRange,
hint: const Text("Price Range"),
onChanged: (value) {
setState(() {
_selectedPriceRange = value;
});
},
items: <String>['\$0-\$50', '\$51-\$100', '\$101-\$150']
.map<DropdownMenuItem<String>>((String value) {
return DropdownMenuItem<String>(
value: value,
child: Text(value),
);
}).toList(),
);
case 2:
return DropdownButton<String>(
value: _selectedAvailability,
hint: const Text("Availability"),
onChanged: (value) {
setState(() {
_selectedAvailability = value;
});
},
items: <String>['In Stock', 'Out of Stock']
.map<DropdownMenuItem<String>>((String value) {
return DropdownMenuItem<String>(
value: value,
child: Text(value),
);
}).toList(),
);
default:
return Container();
}
}
} }

View File

@ -1,14 +1,19 @@
import 'package:cheminova/models/product_model.dart'; import 'package:cheminova/models/product_model.dart';
import 'package:cheminova/screens/product/cart_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:flutter/material.dart'; 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 'package:google_fonts/google_fonts.dart'; import 'package:google_fonts/google_fonts.dart';
import 'cart_screen.dart';
class ProductDetailScreen extends StatefulWidget { class ProductDetailScreen extends StatefulWidget {
final ProductModel product; final Map<String, dynamic>? productModel;
const ProductDetailScreen({super.key, required this.product}); ProductModel? product;
ProductDetailScreen({super.key, this.product, this.productModel});
@override @override
State<ProductDetailScreen> createState() => _ProductDetailScreenState(); State<ProductDetailScreen> createState() => _ProductDetailScreenState();
@ -93,17 +98,18 @@ class _ProductDetailScreenState extends State<ProductDetailScreen> {
), ),
child: ClipRRect( child: ClipRRect(
borderRadius: BorderRadius.circular(10), borderRadius: BorderRadius.circular(10),
child: Image.asset( child: Image.asset("assets/images/product.png", fit: BoxFit.cover,),
widget.product.image, // Image.asset(
fit: BoxFit.cover, // widget.product.image,
), // fit: BoxFit.cover,
// ),
), ),
), ),
), ),
Padding( Padding(
padding: const EdgeInsets.symmetric(horizontal: 8), padding: const EdgeInsets.symmetric(horizontal: 8),
child: Text( child: Text(
widget.product.name, widget.productModel!['name'],
style: GoogleFonts.roboto( style: GoogleFonts.roboto(
fontSize: 20, fontSize: 20,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
@ -114,7 +120,7 @@ class _ProductDetailScreenState extends State<ProductDetailScreen> {
Padding( Padding(
padding: const EdgeInsets.symmetric(horizontal: 8), padding: const EdgeInsets.symmetric(horizontal: 8),
child: Text( child: Text(
"${widget.product.price.toString()}", "${widget.productModel!['price'].toString()}",
style: GoogleFonts.roboto( style: GoogleFonts.roboto(
fontSize: 24, fontSize: 24,
fontWeight: FontWeight.w800, fontWeight: FontWeight.w800,
@ -125,7 +131,7 @@ class _ProductDetailScreenState extends State<ProductDetailScreen> {
Padding( Padding(
padding: const EdgeInsets.symmetric(horizontal: 8), padding: const EdgeInsets.symmetric(horizontal: 8),
child: Text( child: Text(
widget.product.category.name, widget.productModel!['category']['categoryName'],
style: GoogleFonts.roboto( style: GoogleFonts.roboto(
fontSize: 16, fontSize: 16,
fontWeight: FontWeight.w400, fontWeight: FontWeight.w400,
@ -137,7 +143,7 @@ class _ProductDetailScreenState extends State<ProductDetailScreen> {
Padding( Padding(
padding: const EdgeInsets.symmetric(horizontal: 8), padding: const EdgeInsets.symmetric(horizontal: 8),
child: Text( child: Text(
widget.product.description, widget.productModel!['description'],
style: GoogleFonts.roboto( style: GoogleFonts.roboto(
fontSize: 16, fontSize: 16,
fontWeight: FontWeight.w400, fontWeight: FontWeight.w400,
@ -154,7 +160,12 @@ class _ProductDetailScreenState extends State<ProductDetailScreen> {
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(() => const CartScreen()), onPressed: () {
// Pass the product data to the CartScreen
Get.to(() => CartScreen(
// Pass the product in a list
));
},
style: ElevatedButton.styleFrom( style: ElevatedButton.styleFrom(
foregroundColor: Colors.white, foregroundColor: Colors.white,
backgroundColor: const Color(0xFF00784C), backgroundColor: const Color(0xFF00784C),

View File

@ -5,12 +5,15 @@ import 'package:get/get.dart';
import 'package:google_fonts/google_fonts.dart'; import 'package:google_fonts/google_fonts.dart';
class ProductCard extends StatelessWidget { class ProductCard extends StatelessWidget {
final ProductModel product; final Map<String, dynamic>? productModel;
ProductModel? product;
final bool isInCart; final bool isInCart;
final bool isCheckout; final bool isCheckout;
const ProductCard({
ProductCard({
super.key, super.key,
required this.product, this.product,
this.productModel,
this.isInCart = false, this.isInCart = false,
this.isCheckout = false, this.isCheckout = false,
}); });
@ -20,7 +23,8 @@ class ProductCard extends StatelessWidget {
return GestureDetector( return GestureDetector(
onTap: () => isInCart || isCheckout onTap: () => isInCart || isCheckout
? null ? null
: Get.to(() => ProductDetailScreen(product: product)), : Get.to(() =>
ProductDetailScreen(productModel: productModel)),
child: Card( child: Card(
child: Row( child: Row(
children: [ children: [
@ -33,7 +37,8 @@ class ProductCard extends StatelessWidget {
width: Get.width * 0.30, width: Get.width * 0.30,
decoration: BoxDecoration( decoration: BoxDecoration(
image: DecorationImage( image: DecorationImage(
image: Image.asset(product.image).image, image: AssetImage("assets/images/product.png"),
// Image.asset(productModel!['image']).image,
fit: BoxFit.cover, fit: BoxFit.cover,
), ),
), ),
@ -47,21 +52,21 @@ class ProductCard extends StatelessWidget {
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text( Text(
product.name, productModel!['name'],
style: GoogleFonts.roboto( style: GoogleFonts.roboto(
fontSize: 16, fontSize: 16,
fontWeight: FontWeight.w500, fontWeight: FontWeight.w500,
), ),
), ),
Text( Text(
product.category.name, productModel!['category']['categoryName'],
style: GoogleFonts.roboto( style: GoogleFonts.roboto(
fontSize: 14, fontSize: 14,
fontWeight: FontWeight.w400, fontWeight: FontWeight.w400,
), ),
), ),
Text( Text(
"${product.price.toString()}0", "${ productModel!['price'].toString()}0",
style: GoogleFonts.roboto( style: GoogleFonts.roboto(
fontSize: 22, fontSize: 22,
fontWeight: FontWeight.w700, fontWeight: FontWeight.w700,
@ -106,7 +111,7 @@ class ProductCard extends StatelessWidget {
), ),
), ),
Text( Text(
product.quantity.toString(), productModel!['brand']['brandName'],
style: const TextStyle( style: const TextStyle(
color: Colors.white, color: Colors.white,
fontSize: 16, fontSize: 16,
@ -146,7 +151,9 @@ class ProductCard extends StatelessWidget {
], ],
) )
: ElevatedButton( : ElevatedButton(
onPressed: () {}, onPressed: () {
},
style: ElevatedButton.styleFrom( style: ElevatedButton.styleFrom(
foregroundColor: Colors.white, foregroundColor: Colors.white,
backgroundColor: const Color(0xFF00784C), backgroundColor: const Color(0xFF00784C),