Files
2026-05-27 10:35:09 +05:30

331 lines
12 KiB
Dart
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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 havent added any products yet.',
color: ColorConstants.blackColor,
fontSize: 14,
fontWeight: FontWeight.normal,
maxLines: 2,
textAlign: TextAlign.center,
),
],
),
);
}