Refactor: Separate UI screens into
This commit is contained in:
parent
dd623f1b04
commit
8c47eb9519
|
@ -1,6 +1,12 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:sqflite_common_ffi/sqflite_ffi.dart';
|
||||||
|
import 'screens/product_list_screen.dart';
|
||||||
|
import 'screens/add_edit_product_screen.dart';
|
||||||
|
|
||||||
void main() {
|
void main() async {
|
||||||
|
WidgetsFlutterBinding.ensureInitialized();
|
||||||
|
sqfliteFfiInit();
|
||||||
|
databaseFactory = databaseFactoryFfi;
|
||||||
runApp(const MyApp());
|
runApp(const MyApp());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -10,34 +16,12 @@ class MyApp extends StatelessWidget {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return MaterialApp(
|
return MaterialApp(
|
||||||
title: 'Flutter Demo',
|
title: 'ابزار ساده ثبت کالا',
|
||||||
theme: ThemeData(
|
theme: ThemeData(
|
||||||
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
|
primarySwatch: Colors.blueGrey,
|
||||||
|
visualDensity: VisualDensity.adaptivePlatformDensity,
|
||||||
),
|
),
|
||||||
home: const MyHomePage(title: 'Flutter Demo Home Page'),
|
home: ProductListScreen(),
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class MyHomePage extends StatefulWidget {
|
|
||||||
const MyHomePage({super.key, required this.title});
|
|
||||||
|
|
||||||
final String title;
|
|
||||||
|
|
||||||
@override
|
|
||||||
State<MyHomePage> createState() => _MyHomePageState();
|
|
||||||
}
|
|
||||||
|
|
||||||
class _MyHomePageState extends State<MyHomePage> {
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return Scaffold(
|
|
||||||
appBar: AppBar(
|
|
||||||
backgroundColor: Theme.of(context).colorScheme.inversePrimary,
|
|
||||||
|
|
||||||
title: Text(widget.title),
|
|
||||||
),
|
|
||||||
body: Center(),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,162 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import '../models/product.dart';
|
||||||
|
import '../services/database_helper.dart';
|
||||||
|
|
||||||
|
class AddEditProductScreen extends StatefulWidget {
|
||||||
|
final Product? product;
|
||||||
|
|
||||||
|
const AddEditProductScreen({super.key, this.product});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<AddEditProductScreen> createState() => _AddEditProductScreenState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _AddEditProductScreenState extends State<AddEditProductScreen> {
|
||||||
|
final _formKey = GlobalKey<FormState>();
|
||||||
|
late TextEditingController _nameController;
|
||||||
|
late TextEditingController _codeController;
|
||||||
|
late TextEditingController _quantityController;
|
||||||
|
late TextEditingController _priceController;
|
||||||
|
late TextEditingController _descriptionController;
|
||||||
|
late DatabaseHelper _databaseHelper;
|
||||||
|
|
||||||
|
bool get isEditing => widget.product != null;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_databaseHelper = DatabaseHelper();
|
||||||
|
_nameController = TextEditingController(text: widget.product?.name);
|
||||||
|
_codeController = TextEditingController(text: widget.product?.code);
|
||||||
|
_quantityController = TextEditingController(
|
||||||
|
text: widget.product?.quantity.toString(),
|
||||||
|
);
|
||||||
|
_priceController = TextEditingController(
|
||||||
|
text: widget.product?.price.toString(),
|
||||||
|
);
|
||||||
|
_descriptionController = TextEditingController(
|
||||||
|
text: widget.product?.description,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_nameController.dispose();
|
||||||
|
_codeController.dispose();
|
||||||
|
_quantityController.dispose();
|
||||||
|
_priceController.dispose();
|
||||||
|
_descriptionController.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _saveProduct() async {
|
||||||
|
if (_formKey.currentState!.validate()) {
|
||||||
|
_formKey.currentState!.save();
|
||||||
|
|
||||||
|
final product = Product(
|
||||||
|
id: isEditing ? widget.product!.id : null,
|
||||||
|
name: _nameController.text,
|
||||||
|
code: _codeController.text,
|
||||||
|
quantity: int.parse(_quantityController.text),
|
||||||
|
price: double.parse(_priceController.text),
|
||||||
|
description:
|
||||||
|
_descriptionController.text.isEmpty
|
||||||
|
? null
|
||||||
|
: _descriptionController.text,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (isEditing) {
|
||||||
|
await _databaseHelper.updateProduct(product);
|
||||||
|
} else {
|
||||||
|
await _databaseHelper.insertProduct(product);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!mounted) return;
|
||||||
|
Navigator.pop(context, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Scaffold(
|
||||||
|
appBar: AppBar(
|
||||||
|
title: Text(isEditing ? 'ویرایش کالا' : 'افزودن کالا'),
|
||||||
|
centerTitle: true,
|
||||||
|
),
|
||||||
|
body: Padding(
|
||||||
|
padding: const EdgeInsets.all(16.0),
|
||||||
|
child: Form(
|
||||||
|
key: _formKey,
|
||||||
|
child: ListView(
|
||||||
|
children: [
|
||||||
|
TextFormField(
|
||||||
|
controller: _nameController,
|
||||||
|
decoration: const InputDecoration(labelText: 'نام کالا'),
|
||||||
|
validator: (value) {
|
||||||
|
if (value == null || value.isEmpty) {
|
||||||
|
return 'لطفا نام کالا را وارد کنید';
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
),
|
||||||
|
const SizedBox(height: 10),
|
||||||
|
TextFormField(
|
||||||
|
controller: _codeController,
|
||||||
|
decoration: const InputDecoration(labelText: 'کد کالا'),
|
||||||
|
validator: (value) {
|
||||||
|
if (value == null || value.isEmpty) {
|
||||||
|
return 'لطفا کد کالا را وارد کنید';
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
),
|
||||||
|
const SizedBox(height: 10),
|
||||||
|
TextFormField(
|
||||||
|
controller: _quantityController,
|
||||||
|
decoration: const InputDecoration(labelText: 'تعداد'),
|
||||||
|
keyboardType: TextInputType.number,
|
||||||
|
validator: (value) {
|
||||||
|
if (value == null || value.isEmpty) {
|
||||||
|
return 'لطفا تعداد را وارد کنید';
|
||||||
|
}
|
||||||
|
if (int.tryParse(value) == null) {
|
||||||
|
return 'تعداد باید عدد باشد';
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
),
|
||||||
|
const SizedBox(height: 10),
|
||||||
|
TextFormField(
|
||||||
|
controller: _priceController,
|
||||||
|
decoration: const InputDecoration(labelText: 'قیمت'),
|
||||||
|
keyboardType: TextInputType.number,
|
||||||
|
validator: (value) {
|
||||||
|
if (value == null || value.isEmpty) {
|
||||||
|
return 'لطفا قیمت را وارد کنید';
|
||||||
|
}
|
||||||
|
if (double.tryParse(value) == null) {
|
||||||
|
return 'قیمت باید عدد باشد';
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
),
|
||||||
|
const SizedBox(height: 10),
|
||||||
|
TextFormField(
|
||||||
|
controller: _descriptionController,
|
||||||
|
decoration: const InputDecoration(
|
||||||
|
labelText: 'توضیحات (اختیاری)',
|
||||||
|
),
|
||||||
|
maxLines: 3,
|
||||||
|
),
|
||||||
|
const SizedBox(height: 20),
|
||||||
|
ElevatedButton(
|
||||||
|
onPressed: _saveProduct,
|
||||||
|
child: Text(isEditing ? 'ذخیره تغییرات' : 'افزودن کالا'),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,139 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import '../services/database_helper.dart';
|
||||||
|
import '../models/product.dart';
|
||||||
|
import 'add_edit_product_screen.dart';
|
||||||
|
|
||||||
|
class ProductListScreen extends StatefulWidget {
|
||||||
|
const ProductListScreen({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<ProductListScreen> createState() => _ProductListScreenState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _ProductListScreenState extends State<ProductListScreen> {
|
||||||
|
late DatabaseHelper _databaseHelper;
|
||||||
|
List<Product> _products = [];
|
||||||
|
bool _isLoading = true;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_databaseHelper = DatabaseHelper();
|
||||||
|
_loadProducts();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _loadProducts() async {
|
||||||
|
setState(() {
|
||||||
|
_isLoading = true;
|
||||||
|
});
|
||||||
|
final products = await _databaseHelper.getProducts();
|
||||||
|
if (!mounted) return;
|
||||||
|
setState(() {
|
||||||
|
_products = products;
|
||||||
|
_isLoading = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _deleteProduct(int id) async {
|
||||||
|
await _databaseHelper.deleteProduct(id);
|
||||||
|
_loadProducts();
|
||||||
|
}
|
||||||
|
|
||||||
|
void _navigateToAddEditProduct([Product? product]) async {
|
||||||
|
final result = await Navigator.push(
|
||||||
|
context,
|
||||||
|
MaterialPageRoute(
|
||||||
|
builder: (context) => AddEditProductScreen(product: product),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!mounted) return;
|
||||||
|
if (result == true) {
|
||||||
|
_loadProducts();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Scaffold(
|
||||||
|
appBar: AppBar(title: const Text('لیست کالاها'), centerTitle: true),
|
||||||
|
body:
|
||||||
|
_isLoading
|
||||||
|
? const Center(child: CircularProgressIndicator())
|
||||||
|
: _products.isEmpty
|
||||||
|
? const Center(
|
||||||
|
child: Text(
|
||||||
|
'هیچ کالایی یافت نشد. برای شروع، یک کالا اضافه کنید.',
|
||||||
|
),
|
||||||
|
)
|
||||||
|
: ListView.builder(
|
||||||
|
itemCount: _products.length,
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
final product = _products[index];
|
||||||
|
return Card(
|
||||||
|
margin: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 10,
|
||||||
|
vertical: 5,
|
||||||
|
),
|
||||||
|
child: ListTile(
|
||||||
|
leading: CircleAvatar(
|
||||||
|
child: Text(product.quantity.toString()),
|
||||||
|
),
|
||||||
|
title: Text(product.name),
|
||||||
|
subtitle: Text(
|
||||||
|
'کد: ${product.code} | قیمت: ${product.price} تومان',
|
||||||
|
),
|
||||||
|
trailing: Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
IconButton(
|
||||||
|
icon: const Icon(Icons.edit, color: Colors.blue),
|
||||||
|
onPressed: () => _navigateToAddEditProduct(product),
|
||||||
|
),
|
||||||
|
IconButton(
|
||||||
|
icon: const Icon(Icons.delete, color: Colors.red),
|
||||||
|
onPressed: () {
|
||||||
|
showDialog(
|
||||||
|
context: context,
|
||||||
|
builder:
|
||||||
|
(context) => AlertDialog(
|
||||||
|
title: const Text('حذف کالا'),
|
||||||
|
content: Text(
|
||||||
|
'آیا مطمئنید که میخواهید ${product.name} را حذف کنید؟',
|
||||||
|
),
|
||||||
|
actions: [
|
||||||
|
TextButton(
|
||||||
|
onPressed:
|
||||||
|
() => Navigator.pop(context),
|
||||||
|
child: const Text('خیر'),
|
||||||
|
),
|
||||||
|
TextButton(
|
||||||
|
onPressed: () {
|
||||||
|
_deleteProduct(product.id!);
|
||||||
|
if (!mounted)
|
||||||
|
return; // Add this check
|
||||||
|
Navigator.pop(context);
|
||||||
|
},
|
||||||
|
child: const Text('بله'),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
onTap: () {
|
||||||
|
_navigateToAddEditProduct(product);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
floatingActionButton: FloatingActionButton(
|
||||||
|
onPressed: () => _navigateToAddEditProduct(),
|
||||||
|
child: const Icon(Icons.add),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue