import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import '../models/category_model.dart'; import '../providers/expense_provider.dart'; class CategoryManagementScreen extends StatelessWidget { const CategoryManagementScreen({super.key}); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text('分类管理'), ), body: Consumer( builder: (context, provider, child) { final rootCategories = provider.rootCategories; return ListView.builder( itemCount: rootCategories.length, itemBuilder: (context, index) { return _buildCategoryTile(context, rootCategories[index], provider); }, ); }, ), floatingActionButton: FloatingActionButton( onPressed: () => _showAddEditDialog(context, null, null), child: const Icon(Icons.add), ), ); } Widget _buildCategoryTile(BuildContext context, Category category, ExpenseProvider provider) { final subCategories = provider.getSubCategories(category.id!); if (subCategories.isEmpty) { return ListTile( leading: CircleAvatar( backgroundColor: Color(category.color), child: Icon(_getIconData(category.icon), color: Colors.white, size: 20), ), title: Text(category.name), trailing: _buildActionButtons(context, category), ); } return ExpansionTile( leading: CircleAvatar( backgroundColor: Color(category.color), child: Icon(_getIconData(category.icon), color: Colors.white, size: 20), ), title: Text(category.name), trailing: Row( mainAxisSize: MainAxisSize.min, children: [ // This trailing overrides the expansion arrow, so maybe we put actions in leading or long press? // ExpansionTile doesn't easily support custom trailing + expansion arrow. // Let's use PopupMenuButton for actions instead. _buildActionButtons(context, category), const Icon(Icons.expand_more), // Visual cue, though functionality is tricky if trailing is used ], ), children: subCategories.map((sub) => _buildCategoryTile(context, sub, provider)).toList(), ); } // Adjusted to use PopupMenu for cleaner UI on tiles Widget _buildActionButtons(BuildContext context, Category category) { return PopupMenuButton( onSelected: (value) { if (value == 'edit') { _showAddEditDialog(context, category, category.parentId); } else if (value == 'delete') { _confirmDelete(context, category); } else if (value == 'add_sub') { _showAddEditDialog(context, null, category.id); } }, itemBuilder: (BuildContext context) => >[ const PopupMenuItem( value: 'edit', child: Text('编辑'), ), const PopupMenuItem( value: 'add_sub', child: Text('添加子分类'), ), const PopupMenuItem( value: 'delete', child: Text('删除', style: TextStyle(color: Colors.red)), ), ], ); } void _confirmDelete(BuildContext context, Category category) { showDialog( context: context, builder: (ctx) => AlertDialog( title: const Text('确认删除'), content: Text('确定要删除分类 "${category.name}" 吗?'), actions: [ TextButton( onPressed: () => Navigator.of(ctx).pop(), child: const Text('取消'), ), TextButton( onPressed: () { Provider.of(context, listen: false).deleteCategory(category.id!); Navigator.of(ctx).pop(); }, child: const Text('删除', style: TextStyle(color: Colors.red)), ), ], ), ); } void _showAddEditDialog(BuildContext context, Category? category, int? parentId) { final isEditing = category != null; final nameController = TextEditingController(text: isEditing ? category.name : ''); String selectedType = isEditing ? category.type : 'expense'; String selectedIcon = isEditing ? category.icon : 'category'; int selectedColor = isEditing ? category.color : 0xFF2196F3; showDialog( context: context, builder: (ctx) { return StatefulBuilder( builder: (context, setState) { return AlertDialog( title: Text(isEditing ? '编辑分类' : '添加分类'), content: SingleChildScrollView( child: Column( mainAxisSize: MainAxisSize.min, children: [ TextField( controller: nameController, decoration: const InputDecoration(labelText: '分类名称'), ), const SizedBox(height: 16), DropdownButtonFormField( key: ValueKey(selectedType), initialValue: selectedType, items: const [ DropdownMenuItem(value: 'expense', child: Text('支出')), DropdownMenuItem(value: 'income', child: Text('收入')), ], onChanged: (val) { setState(() => selectedType = val!); }, decoration: const InputDecoration(labelText: '类型'), ), const SizedBox(height: 16), // Icon Picker (Simple version) DropdownButtonFormField( key: ValueKey(selectedIcon), initialValue: selectedIcon, items: const [ DropdownMenuItem(value: 'restaurant', child: Icon(Icons.restaurant)), DropdownMenuItem(value: 'directions_bus', child: Icon(Icons.directions_bus)), DropdownMenuItem(value: 'shopping_cart', child: Icon(Icons.shopping_cart)), DropdownMenuItem(value: 'movie', child: Icon(Icons.movie)), DropdownMenuItem(value: 'local_hospital', child: Icon(Icons.local_hospital)), DropdownMenuItem(value: 'attach_money', child: Icon(Icons.attach_money)), DropdownMenuItem(value: 'trending_up', child: Icon(Icons.trending_up)), DropdownMenuItem(value: 'category', child: Icon(Icons.category)), DropdownMenuItem(value: 'breakfast_dining', child: Icon(Icons.breakfast_dining)), DropdownMenuItem(value: 'lunch_dining', child: Icon(Icons.lunch_dining)), DropdownMenuItem(value: 'dinner_dining', child: Icon(Icons.dinner_dining)), DropdownMenuItem(value: 'subway', child: Icon(Icons.subway)), DropdownMenuItem(value: 'local_taxi', child: Icon(Icons.local_taxi)), ], onChanged: (val) { setState(() => selectedIcon = val!); }, decoration: const InputDecoration(labelText: '图标'), ), ], ), ), actions: [ TextButton( onPressed: () => Navigator.of(ctx).pop(), child: const Text('取消'), ), ElevatedButton( onPressed: () { if (nameController.text.isEmpty) return; final newCategory = Category( id: category?.id, name: nameController.text, type: selectedType, icon: selectedIcon, color: selectedColor, // Fixed color for now or add picker parentId: parentId, ); if (isEditing) { Provider.of(context, listen: false).updateCategory(newCategory); } else { Provider.of(context, listen: false).addCategory(newCategory); } Navigator.of(ctx).pop(); }, child: const Text('保存'), ), ], ); }, ); }, ); } 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; } } }