feat:"初始化仓库,并更新基础代码。"
This commit is contained in:
228
lib/screens/add_transaction_screen.dart
Normal file
228
lib/screens/add_transaction_screen.dart
Normal file
@@ -0,0 +1,228 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:uuid/uuid.dart';
|
||||
import '../models/transaction_model.dart';
|
||||
import '../providers/expense_provider.dart';
|
||||
|
||||
class AddTransactionScreen extends StatefulWidget {
|
||||
const AddTransactionScreen({super.key});
|
||||
|
||||
@override
|
||||
State<AddTransactionScreen> createState() => _AddTransactionScreenState();
|
||||
}
|
||||
|
||||
class _AddTransactionScreenState extends State<AddTransactionScreen> {
|
||||
final _formKey = GlobalKey<FormState>();
|
||||
final _amountController = TextEditingController();
|
||||
final _noteController = TextEditingController();
|
||||
DateTime _selectedDate = DateTime.now();
|
||||
String _transactionType = 'expense';
|
||||
int? _selectedCategoryId;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final provider = Provider.of<ExpenseProvider>(context);
|
||||
|
||||
// Flatten categories with hierarchy logic for display
|
||||
final List<DropdownMenuItem<int>> categoryItems = [];
|
||||
final rootCategories = provider.rootCategories.where((c) => c.type == _transactionType).toList();
|
||||
|
||||
for (var root in rootCategories) {
|
||||
// Add Root
|
||||
categoryItems.add(
|
||||
DropdownMenuItem<int>(
|
||||
value: root.id,
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(_getIconData(root.icon), color: Color(root.color), size: 20),
|
||||
const SizedBox(width: 8),
|
||||
Text(root.name, style: const TextStyle(fontWeight: FontWeight.bold)),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
// Add Children
|
||||
final children = provider.getSubCategories(root.id!);
|
||||
for (var child in children) {
|
||||
categoryItems.add(
|
||||
DropdownMenuItem<int>(
|
||||
value: child.id,
|
||||
child: Row(
|
||||
children: [
|
||||
const SizedBox(width: 24), // Indent
|
||||
Icon(_getIconData(child.icon), color: Color(child.color), size: 16),
|
||||
const SizedBox(width: 8),
|
||||
Text(child.name),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text('记一笔'),
|
||||
),
|
||||
body: Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Form(
|
||||
key: _formKey,
|
||||
child: ListView(
|
||||
children: [
|
||||
// Type Selection
|
||||
RadioGroup<String>(
|
||||
groupValue: _transactionType,
|
||||
onChanged: (value) {
|
||||
setState(() {
|
||||
_transactionType = value!;
|
||||
_selectedCategoryId = null;
|
||||
});
|
||||
},
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: RadioListTile<String>(
|
||||
title: const Text('支出'),
|
||||
value: 'expense',
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: RadioListTile<String>(
|
||||
title: const Text('收入'),
|
||||
value: 'income',
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
// Amount
|
||||
TextFormField(
|
||||
controller: _amountController,
|
||||
keyboardType: TextInputType.number,
|
||||
decoration: const InputDecoration(
|
||||
labelText: '金额',
|
||||
prefixText: '¥ ',
|
||||
),
|
||||
validator: (value) {
|
||||
if (value == null || value.isEmpty) {
|
||||
return '请输入金额';
|
||||
}
|
||||
if (double.tryParse(value) == null) {
|
||||
return '请输入有效的数字';
|
||||
}
|
||||
return null;
|
||||
},
|
||||
),
|
||||
|
||||
const SizedBox(height: 16),
|
||||
|
||||
// Category
|
||||
DropdownButtonFormField<int>(
|
||||
key: ValueKey(_selectedCategoryId),
|
||||
initialValue: _selectedCategoryId,
|
||||
decoration: const InputDecoration(labelText: '分类'),
|
||||
items: categoryItems,
|
||||
onChanged: (value) {
|
||||
setState(() {
|
||||
_selectedCategoryId = value;
|
||||
});
|
||||
},
|
||||
validator: (value) {
|
||||
if (value == null) {
|
||||
return '请选择分类';
|
||||
}
|
||||
return null;
|
||||
},
|
||||
),
|
||||
|
||||
const SizedBox(height: 16),
|
||||
|
||||
// Date Picker
|
||||
Row(
|
||||
children: [
|
||||
Text('日期: ${DateFormat('yyyy-MM-dd').format(_selectedDate)}'),
|
||||
const Spacer(),
|
||||
TextButton(
|
||||
onPressed: () async {
|
||||
final pickedDate = await showDatePicker(
|
||||
context: context,
|
||||
initialDate: _selectedDate,
|
||||
firstDate: DateTime(2000),
|
||||
lastDate: DateTime(2100),
|
||||
helpText: '选择日期',
|
||||
);
|
||||
if (pickedDate != null) {
|
||||
setState(() {
|
||||
_selectedDate = pickedDate;
|
||||
});
|
||||
}
|
||||
},
|
||||
child: const Text('选择日期'),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
const SizedBox(height: 16),
|
||||
|
||||
// Note
|
||||
TextFormField(
|
||||
controller: _noteController,
|
||||
decoration: const InputDecoration(labelText: '备注'),
|
||||
),
|
||||
|
||||
const SizedBox(height: 32),
|
||||
|
||||
ElevatedButton(
|
||||
onPressed: _saveTransaction,
|
||||
style: ElevatedButton.styleFrom(
|
||||
minimumSize: const Size(double.infinity, 50),
|
||||
),
|
||||
child: const Text('保存', style: TextStyle(fontSize: 18)),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _saveTransaction() {
|
||||
if (_formKey.currentState!.validate()) {
|
||||
final newTransaction = TransactionModel(
|
||||
id: const Uuid().v4(),
|
||||
amount: double.parse(_amountController.text),
|
||||
type: _transactionType,
|
||||
categoryId: _selectedCategoryId!,
|
||||
date: _selectedDate,
|
||||
note: _noteController.text,
|
||||
createdAt: DateTime.now(),
|
||||
updatedAt: DateTime.now(),
|
||||
);
|
||||
|
||||
Provider.of<ExpenseProvider>(context, listen: false).addTransaction(newTransaction);
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
}
|
||||
|
||||
IconData _getIconData(String iconName) {
|
||||
switch (iconName) {
|
||||
case 'restaurant': return Icons.restaurant;
|
||||
case 'directions_bus': return Icons.directions_bus;
|
||||
case 'shopping_cart': return Icons.shopping_cart;
|
||||
case 'movie': return Icons.movie;
|
||||
case 'local_hospital': return Icons.local_hospital;
|
||||
case 'attach_money': return Icons.attach_money;
|
||||
case 'trending_up': return Icons.trending_up;
|
||||
case 'breakfast_dining': return Icons.breakfast_dining;
|
||||
case 'lunch_dining': return Icons.lunch_dining;
|
||||
case 'dinner_dining': return Icons.dinner_dining;
|
||||
case 'subway': return Icons.subway;
|
||||
case 'local_taxi': return Icons.local_taxi;
|
||||
default: return Icons.category;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user