sc-android-app/lib/screens/Add_products_screen.dart
2024-09-29 22:09:36 +05:30

404 lines
20 KiB
Dart

// Import necessary packages and widgets for the application.
import 'package:cheminova/widgets/common_app_bar.dart';
import 'package:cheminova/widgets/common_background.dart';
import 'package:cheminova/widgets/common_drawer.dart';
import 'package:cheminova/widgets/common_elevated_button.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:provider/provider.dart';
import '../provider/products_provider.dart';
// Stateful widget for the Add Products screen.
class AddProductsScreen extends StatefulWidget {
final String distributorType; // Type of distributor (e.g., Retailer, Wholesaler)
final String tradeName; // Trade name of the distributor
final String pdRdId; // Principal distributor/retailer ID
final String? inventoryId; // Optional inventory ID
// Constructor for AddProductsScreen, with required parameters.
const AddProductsScreen({
super.key,
required this.distributorType,
required this.tradeName,
required this.pdRdId,
this.inventoryId,
});
@override
State<AddProductsScreen> createState() => _AddProductsScreenState();
}
class _AddProductsScreenState extends State<AddProductsScreen> {
// Controller for the search input field.
final searchController = TextEditingController();
// ProductProvider for managing product-related logic.
late ProductProvider productProvider;
@override
void initState() {
// Initialize the productProvider and fetch the products after the widget is built.
productProvider = Provider.of<ProductProvider>(context, listen: false);
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
productProvider.getProducts();
});
super.initState();
}
@override
void dispose() {
// Reset the products list when the screen is disposed.
if (context.mounted) {
productProvider.resetProducts();
}
super.dispose();
}
@override
Widget build(BuildContext context) {
// Build the main UI for the screen.
return CommonBackground(
child: Scaffold(
backgroundColor: Colors.transparent,
appBar: CommonAppBar(
actions: [
IconButton(
onPressed: () => Navigator.pop(context),
icon: Image.asset('assets/Back_attendance.png'),
padding: const EdgeInsets.only(right: 20)
)
],
title: Text(
'${widget.distributorType}\n${widget.tradeName}',
textAlign: TextAlign.center,
style: const TextStyle(
fontSize: 20,
color: Colors.black,
fontWeight: FontWeight.w400,
fontFamily: 'Anek'
)
),
backgroundColor: Colors.transparent,
elevation: 0
),
drawer: const CommonDrawer(),
// BottomNavigationBar which holds the "Add Products" and "Submit" button.
bottomNavigationBar: Consumer<ProductProvider>(
builder: (context, value, child) => Column(
mainAxisSize: MainAxisSize.min,
children: [
Align(
alignment: value.selectedProducts.isEmpty
? Alignment.center
: Alignment.bottomCenter,
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
// Floating Action Button to add products
FloatingActionButton.extended(
onPressed: () {
showModalBottomSheet(
isScrollControlled: true,
constraints: BoxConstraints(
maxHeight: MediaQuery.of(context).size.height * 0.9
),
context: context,
builder: (BuildContext context) {
// Display list of products with a search bar in the modal sheet.
return Consumer<ProductProvider>(
builder: (context, value, child) => StatefulBuilder(
builder: (context, setState) {
return Column(
children: [
// Search bar to filter products
Padding(
padding: const EdgeInsets.all(18.0),
child: TextField(
controller: searchController,
decoration: const InputDecoration(
labelText: 'Search by name or SKU',
border: OutlineInputBorder(),
prefixIcon: Icon(Icons.search)
),
onChanged: (val) {
value.filterProducts(val);
setState(() {});
}
)
),
// List of products (filtered or all)
Expanded(
child: ListView.builder(
itemCount: searchController.text.isEmpty
? value.productList.length
: value.searchList.length,
itemBuilder: (context, index) {
// Check if the product is already selected.
bool isAlreadySelected = value.selectedProducts.any(
(selectedProduct) => selectedProduct.sku == value.productList[index].sku
);
final data = searchController.text.isEmpty
? value.productList[index]
: value.searchList[index];
// Product tile with name, SKU, and selection functionality.
return Card(
child: ListTile(
title: Text(
data.productName,
style: TextStyle(color: isAlreadySelected ? Colors.grey : Colors.black)
),
subtitle: Text(
data.sku,
style: TextStyle(color: isAlreadySelected ? Colors.grey : Colors.black)
),
onTap: isAlreadySelected
? null
: () {
setState(() => value.selectedProducts.add(data));
Navigator.pop(context);
}
)
);
}
)
)
]
);
}
)
);
},
).whenComplete(() => setState(() {}));
},
backgroundColor: Colors.white,
icon: const Icon(Icons.add, color: Colors.black),
label: const Text('Add Products', style: TextStyle(color: Colors.black))
),
// Display Submit button if products are selected.
if (value.selectedProducts.isNotEmpty) ...[
const SizedBox(height: 16.0),
Consumer<ProductProvider>(
builder: (context, value, child) => CommonElevatedButton(
borderRadius: 30,
width: double.infinity,
height: kToolbarHeight - 10,
text: 'SUBMIT',
backgroundColor: const Color(0xff004791),
// Submit selected products, but first validate the data.
onPressed: () {
if (value.selectedProducts.isNotEmpty &&
value.selectedProducts.every((product) =>
product.sku.isNotEmpty &&
product.productName.isNotEmpty &&
product.sale != null &&
product.inventory != null)) {
value.submitProducts(
distributorType: widget.distributorType,
pdRdId: widget.pdRdId, inventoryId: widget.inventoryId
);
} else {
// Show error message if data is incomplete.
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Please fill out all product details, including sale and inventory.')
),
);
}
}
)
)
]
]
)
)
),
],
),
),
// Main body of the screen that shows the selected products and the loader.
body: Consumer<ProductProvider>(
builder: (context, value, child) {
return Stack(
children: [
Column(
children: [
// Display the selected products in a list.
if (value.selectedProducts.isNotEmpty)
Expanded(
child: ListView.builder(
itemCount: value.selectedProducts.length,
itemBuilder: (context, index) {
// Display individual product blocks.
return ProductBlock(
onUpdate: (updatedProduct) {
debugPrint('selected product length: ${value.selectedProducts.length}');
setState(() {
value.selectedProducts[index] = updatedProduct;
});
},
onRemove: () {
setState(() {
value.selectedProducts.removeAt(index);
});
},
product: value.selectedProducts[index]
);
}
)
)
]
),
// Show loader when products are being fetched.
(value.isLoading)
? Container(
color: Colors.black12,
child: const Center(child: CircularProgressIndicator())
)
: const SizedBox()
]
);
}
)
)
);
}
}
// Widget to represent individual product blocks in the selected products list.
class ProductBlock extends StatefulWidget {
final ProductModel product; // The product model to be displayed.
final ValueChanged<ProductModel> onUpdate; // Callback when product data is updated.
final VoidCallback onRemove; // Callback to remove the product.
const ProductBlock({
super.key,
required this.product,
required this.onUpdate,
required this.onRemove,
});
@override
State<ProductBlock> createState() => _ProductBlockState();
}
class _ProductBlockState extends State<ProductBlock> {
// Controllers for sale and inventory text fields.
final saleController = TextEditingController();
final inventoryController = TextEditingController();
String? errorMessage; // Error message for input validation.
@override
void initState() {
super.initState();
// Initialize controllers with existing product data.
saleController.text = (widget.product.sale ?? '').toString();
inventoryController.text = (widget.product.inventory ?? '').toString();
}
// Validate the inputs for sale and inventory fields.
void validateInput() {
setState(() {
String? saleError;
String? inventoryError;
if (saleController.text.isEmpty) {
saleError = 'Sale cannot be empty.';
}
if (inventoryController.text.isEmpty) {
inventoryError = 'Inventory cannot be empty.';
}
errorMessage = null;
if (saleError == null && inventoryError == null) {
int sale = int.parse(saleController.text);
int inventory = int.parse(inventoryController.text);
// Update the product with validated data.
widget.onUpdate(ProductModel(
sku: widget.product.sku,
productName: widget.product.productName,
sale: sale,
inventory: inventory,
));
} else {
errorMessage = saleError ?? inventoryError;
}
});
}
@override
Widget build(BuildContext context) {
return Card(
color: Colors.white,
margin: const EdgeInsets.all(8),
child: Stack(
children: [
Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('Product: ${widget.product.productName}',
style: const TextStyle(fontSize: 16)),
Text('SKU: ${widget.product.sku}',
style: const TextStyle(fontSize: 15)),
const SizedBox(height: 8),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Text field for sale input.
TextField(
controller: saleController,
onTapOutside: (event) => FocusScope.of(context).unfocus(),
inputFormatters: [FilteringTextInputFormatter.digitsOnly],
decoration: InputDecoration(
labelText: 'Sale',
errorText: saleController.text.isEmpty
? 'Sale cannot be empty.'
: null
),
keyboardType: TextInputType.number,
enabled: true,
onChanged: (_) => validateInput()
),
// Text field for inventory input.
TextField(
controller: inventoryController,
onTapOutside: (event) => FocusScope.of(context).unfocus(),
inputFormatters: [FilteringTextInputFormatter.digitsOnly],
decoration: InputDecoration(
labelText: 'Inventory',
errorText: inventoryController.text.isEmpty
? 'Inventory cannot be empty.'
: null
),
keyboardType: TextInputType.number,
enabled: true,
onChanged: (_) => validateInput()
)
]
)
]
),
),
// Remove button for the product block.
Positioned(
top: 4,
right: 4,
child: IconButton(
icon: const Icon(Icons.delete_outline, color: Colors.red),
onPressed: widget.onRemove,
),
),
],
)
);
}
}