331 lines
12 KiB
Dart
331 lines
12 KiB
Dart
import 'package:cached_network_image/cached_network_image.dart';
|
||
import 'package:flutter/material.dart';
|
||
import 'package:get/get.dart';
|
||
|
||
import '../../Controller/products/product_controller.dart';
|
||
import '../../Globalwidgets/textwidget.dart';
|
||
import '../../Helper/Constants/AssetConstants.dart';
|
||
import '../../Helper/Constants/Colorconstants.dart';
|
||
import '../../Helper/Logger.dart';
|
||
import '../../Model/Response/products/product_response.dart';
|
||
import '../Dashboard/Dashboardview.dart';
|
||
|
||
class ProductView extends StatelessWidget {
|
||
ProductView({super.key});
|
||
|
||
final ProductController controller = Get.put(ProductController());
|
||
final FocusNode searchFocusNode = FocusNode();
|
||
|
||
@override
|
||
Widget build(BuildContext context) {
|
||
return GetBuilder<ProductController>(
|
||
initState: (_) {
|
||
controller.getProducts();
|
||
},
|
||
builder: (controller) {
|
||
return Scaffold(
|
||
appBar: AppBar(
|
||
automaticallyImplyLeading: false,
|
||
title: AnimatedSwitcher(
|
||
duration: const Duration(milliseconds: 300),
|
||
transitionBuilder: (Widget child, Animation<double> animation) {
|
||
return SizeTransition(
|
||
sizeFactor: animation,
|
||
axis: Axis.horizontal,
|
||
child: FadeTransition(opacity: animation, child: child),
|
||
);
|
||
},
|
||
child: controller.isSearchModeEnable.value
|
||
? TextField(
|
||
key: const ValueKey('searchField'),
|
||
focusNode: searchFocusNode,
|
||
controller: controller.productSearchController,
|
||
cursorColor: ColorConstants.primaryColor,
|
||
decoration: const InputDecoration(
|
||
hintText: 'Search Products',
|
||
border: UnderlineInputBorder(
|
||
borderSide: BorderSide(color: Colors.white),
|
||
),
|
||
enabledBorder: UnderlineInputBorder(
|
||
borderSide: BorderSide(color: Colors.white),
|
||
),
|
||
focusedBorder: UnderlineInputBorder(
|
||
borderSide:
|
||
BorderSide(color: Colors.white, width: 2),
|
||
),
|
||
isDense: false,
|
||
contentPadding: EdgeInsets.zero,
|
||
),
|
||
style: const TextStyle(color: Colors.black),
|
||
|
||
// 🔥 FIXED HERE — USE LOCAL SEARCH
|
||
onChanged: (value) {
|
||
controller.applySearch(value);
|
||
},
|
||
|
||
autofocus: true,
|
||
)
|
||
: TextWidget(
|
||
key: const ValueKey('titleText'),
|
||
text: 'Products',
|
||
fontWeight: FontWeight.w700,
|
||
fontSize: 20,
|
||
),
|
||
),
|
||
actions: [
|
||
Padding(
|
||
padding: const EdgeInsets.only(right: 12),
|
||
child: InkWell(
|
||
onTap: () {
|
||
if (controller.isSearchModeEnable.value) {
|
||
controller.productSearchController.clear();
|
||
searchFocusNode.unfocus();
|
||
|
||
// 🔥 FIXED HERE — RESTORE LIST
|
||
controller.applySearch('');
|
||
} else {
|
||
Future.delayed(const Duration(milliseconds: 100), () {
|
||
searchFocusNode.requestFocus();
|
||
});
|
||
}
|
||
|
||
controller.isSearchModeEnable.value =
|
||
!controller.isSearchModeEnable.value;
|
||
controller.update();
|
||
},
|
||
child: Icon(
|
||
controller.isSearchModeEnable.value
|
||
? Icons.cancel
|
||
: Icons.search,
|
||
color: ColorConstants.primaryColor,
|
||
),
|
||
),
|
||
),
|
||
],
|
||
),
|
||
|
||
body: Obx(() {
|
||
if (controller.isProductLoading.value) {
|
||
return const Center(
|
||
child: Padding(
|
||
padding: EdgeInsets.only(top: 10),
|
||
child: ShimmerListView(height: 100),
|
||
),
|
||
);
|
||
}
|
||
|
||
if (controller.product.isEmpty) {
|
||
return emptyProductsWidget();
|
||
}
|
||
|
||
return ListView.builder(
|
||
cacheExtent: 1000,
|
||
itemCount: controller.product.length,
|
||
itemBuilder: (context, index) {
|
||
final product = controller.product[index];
|
||
return ProductCard(
|
||
key: ValueKey(product.productid),
|
||
product: product,
|
||
index: index,
|
||
controller: controller,
|
||
isLoading: controller.loadingIndices.contains(index),
|
||
);
|
||
},
|
||
);
|
||
}),
|
||
);
|
||
},
|
||
);
|
||
}
|
||
}
|
||
|
||
/// Product Card Widget
|
||
class ProductCard extends StatelessWidget {
|
||
final ProductData product;
|
||
final int index;
|
||
final ProductController controller;
|
||
final bool isLoading;
|
||
|
||
const ProductCard({
|
||
super.key,
|
||
required this.product,
|
||
required this.index,
|
||
required this.controller,
|
||
required this.isLoading,
|
||
});
|
||
|
||
@override
|
||
Widget build(BuildContext context) {
|
||
final isAvailable = product.status == 'Active';
|
||
|
||
return Padding(
|
||
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 0),
|
||
child: Padding(
|
||
padding: const EdgeInsets.only(top: 5),
|
||
child: Card(
|
||
elevation: 0,
|
||
shadowColor: Colors.grey.shade100,
|
||
color: Colors.white,
|
||
shape:
|
||
RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
|
||
child: Padding(
|
||
padding: const EdgeInsets.only(top: 12, left: 10, right: 10),
|
||
child: Row(
|
||
crossAxisAlignment: CrossAxisAlignment.start,
|
||
children: [
|
||
ClipRRect(
|
||
borderRadius: BorderRadius.circular(12),
|
||
child: CachedNetworkImage(
|
||
imageUrl: product.productimage ?? '',
|
||
width: 80,
|
||
height: 80,
|
||
fit: BoxFit.cover,
|
||
memCacheHeight: 160,
|
||
memCacheWidth: 160,
|
||
maxHeightDiskCache: 160,
|
||
maxWidthDiskCache: 160,
|
||
placeholder: (context, url) => Container(
|
||
width: 80,
|
||
height: 80,
|
||
color: Colors.grey.shade100,
|
||
child: Icon(Icons.image,
|
||
color: Colors.grey.shade400, size: 40),
|
||
),
|
||
errorWidget: (context, url, error) => Container(
|
||
width: 80,
|
||
height: 80,
|
||
color: Colors.grey.shade100,
|
||
child: Icon(Icons.broken_image_outlined,
|
||
color: Colors.grey.shade400, size: 40),
|
||
),
|
||
),
|
||
),
|
||
const SizedBox(width: 12),
|
||
|
||
Expanded(
|
||
child: Column(
|
||
crossAxisAlignment: CrossAxisAlignment.start,
|
||
children: [
|
||
TextWidget(
|
||
text: product.productname ?? 'Unknown',
|
||
maxLines: 1,
|
||
fontSize: 15,
|
||
fontWeight: FontWeight.w700,
|
||
color: isAvailable
|
||
? Colors.black87
|
||
: Colors.grey.shade600,
|
||
),
|
||
const SizedBox(height: 8),
|
||
Row(
|
||
children: [
|
||
Icon(
|
||
Icons.inventory_2_outlined,
|
||
size: 16,
|
||
color: Colors.grey.shade500,
|
||
),
|
||
const SizedBox(width: 4),
|
||
Text(
|
||
"Qty: ${product.productstock ?? 0}",
|
||
style: TextStyle(
|
||
fontSize: 14,
|
||
color: Colors.grey.shade700,
|
||
fontWeight: FontWeight.w500,
|
||
),
|
||
),
|
||
const Spacer(),
|
||
Text(
|
||
product.productcost != null
|
||
? '₹${product.productcost!.toStringAsFixed(2)}'
|
||
: 'N/A',
|
||
style: TextStyle(
|
||
fontSize: 14,
|
||
color: Colors.grey.shade700,
|
||
fontWeight: FontWeight.w500,
|
||
),
|
||
),
|
||
],
|
||
),
|
||
const SizedBox(height: 8),
|
||
Row(
|
||
children: [
|
||
Container(
|
||
padding: const EdgeInsets.symmetric(
|
||
horizontal: 12, vertical: 6),
|
||
decoration: BoxDecoration(
|
||
color: isAvailable
|
||
? Colors.green.shade500
|
||
: Colors.red.shade400,
|
||
borderRadius: BorderRadius.circular(10),
|
||
),
|
||
child: Text(
|
||
isAvailable ? "Active" : "Out of Stock",
|
||
style: const TextStyle(
|
||
fontSize: 11,
|
||
color: Colors.white,
|
||
fontWeight: FontWeight.w600,
|
||
),
|
||
),
|
||
),
|
||
const Spacer(),
|
||
AnimatedOpacity(
|
||
opacity: isLoading ? 0.6 : 1.0,
|
||
duration: const Duration(milliseconds: 300),
|
||
child: Switch.adaptive(
|
||
value: isAvailable,
|
||
activeColor: Colors.green.shade500,
|
||
inactiveThumbColor: Colors.grey.shade400,
|
||
inactiveTrackColor: Colors.grey.shade200,
|
||
onChanged: isLoading
|
||
? null
|
||
: (_) => controller.toggleAvailability(index),
|
||
),
|
||
),
|
||
],
|
||
),
|
||
],
|
||
),
|
||
),
|
||
],
|
||
),
|
||
),
|
||
),
|
||
),
|
||
);
|
||
}
|
||
}
|
||
|
||
Widget emptyProductsWidget() {
|
||
return Center(
|
||
child: Column(
|
||
mainAxisAlignment: MainAxisAlignment.center,
|
||
crossAxisAlignment: CrossAxisAlignment.center,
|
||
children: [
|
||
const SizedBox(height: 10),
|
||
Image.asset(
|
||
AssetConstants.noProductsFound,
|
||
height: 200,
|
||
width: 200,
|
||
fit: BoxFit.fill,
|
||
),
|
||
TextWidget(
|
||
text: 'No Products Found!',
|
||
color: ColorConstants.blackColor,
|
||
fontSize: 18,
|
||
fontWeight: FontWeight.w700,
|
||
maxLines: 2,
|
||
textAlign: TextAlign.center,
|
||
),
|
||
const SizedBox(height: 10),
|
||
TextWidget(
|
||
text: 'You haven’t added any products yet.',
|
||
color: ColorConstants.blackColor,
|
||
fontSize: 14,
|
||
fontWeight: FontWeight.normal,
|
||
maxLines: 2,
|
||
textAlign: TextAlign.center,
|
||
),
|
||
],
|
||
),
|
||
);
|
||
}
|