feat: Refactor category dialog to improve UI and functionality for creating and editing categories

This commit is contained in:
2026-05-20 19:16:00 +02:00
parent b1ab4235bd
commit 2d76dd2a43
+258 -200
View File
@@ -292,210 +292,18 @@ class _HomeScreenState extends State<HomeScreen> {
}
Future<void> _showCreateCategoryDialog([Category? category]) async {
final TextEditingController controller = TextEditingController(text: category?.name ?? '');
Color? selectedColor = category != null && category.colorValue != null
? Color(category.colorValue!)
: null;
IconData? selectedIcon;
final List<Color> palette = [
Colors.amber,
Colors.blue,
Colors.green,
Colors.purple,
Colors.red,
Colors.teal,
Colors.orange,
Colors.grey,
];
final List<IconData> icons = [
Icons.folder,
Icons.work,
Icons.star,
Icons.home,
Icons.school,
Icons.book,
Icons.music_note,
Icons.lightbulb,
];
if (category != null && category.iconCodePoint != null) {
selectedIcon = icons.firstWhere(
(i) => i.codePoint == category.iconCodePoint,
orElse: () => icons.first,
);
}
final bool? result = await showDialog<bool>(
await showDialog<void>(
context: context,
builder: (BuildContext context) {
return StatefulBuilder(
builder: (BuildContext ctx, StateSetter setState) {
return AlertDialog(
title: Text(category == null ? 'Crear categoría' : 'Editar categoría'),
content: SingleChildScrollView(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
TextField(
controller: controller,
decoration: const InputDecoration(
hintText: 'Nombre de la categoría',
),
),
const SizedBox(height: 12),
Align(
alignment: Alignment.centerLeft,
child: Text(
'Color',
style: const TextStyle(
fontSize: 14,
color: Colors.white70,
),
),
),
const SizedBox(height: 8),
Wrap(
spacing: 8,
children: palette.map((Color color) {
final bool isSelected = selectedColor == color;
return GestureDetector(
onTap: () => setState(() => selectedColor = color),
child: Container(
width: 36,
height: 36,
decoration: BoxDecoration(
color: color,
borderRadius: BorderRadius.circular(6),
border: isSelected
? Border.all(color: Colors.white, width: 2)
: null,
),
),
);
}).toList(),
),
const SizedBox(height: 12),
Align(
alignment: Alignment.centerLeft,
child: Text(
'Icono',
style: const TextStyle(
fontSize: 14,
color: Colors.white70,
),
),
),
const SizedBox(height: 8),
Wrap(
spacing: 8,
children: icons.map((IconData icon) {
final bool isSelected = selectedIcon == icon;
return GestureDetector(
onTap: () => setState(() => selectedIcon = icon),
child: Container(
width: 40,
height: 40,
decoration: BoxDecoration(
color: isSelected
? Colors.white10
: Colors.transparent,
borderRadius: BorderRadius.circular(6),
),
child: Icon(
icon,
color: isSelected ? Colors.white : Colors.white70,
),
),
);
}).toList(),
),
],
),
),
actions: [
if (category != null)
TextButton(
onPressed: () async {
final bool? confirm = await showDialog<bool>(
context: context,
builder: (context) => AlertDialog(
title: const Text('Borrar categoría'),
content: const Text('¿Seguro que quieres borrar esta categoría?'),
actions: [
TextButton(onPressed: () => Navigator.pop(context, false), child: const Text('Cancelar')),
TextButton(onPressed: () => Navigator.pop(context, true), child: const Text('Borrar')),
],
),
);
if (confirm == true) {
try {
await widget.repository.deleteCategory(category.id);
await _loadCategories();
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text('Categoría borrada')));
}
try {
await widget.onRequestSync();
} catch (_) {}
} catch (e) {
if (mounted) ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text('Error al borrar categoría: $e')));
}
Navigator.pop(context, false);
}
},
child: const Text('Borrar', style: TextStyle(color: Colors.red)),
),
TextButton(
onPressed: () => Navigator.pop(context, false),
child: const Text('Cancelar'),
),
TextButton(
onPressed: () => Navigator.pop(context, true),
child: Text(category == null ? 'Crear' : 'Guardar'),
),
],
return _CategoryDialog(
category: category,
repository: widget.repository,
onCategoriesChanged: _loadCategories,
onRequestSync: widget.onRequestSync,
onCategoryDeleted: () => _handleMenuItemTapped('all_notes'),
);
},
);
},
);
if (result != true || controller.text.trim().isEmpty) {
controller.dispose();
return;
}
try {
final Category newCategory = Category(
id: category?.id,
name: controller.text.trim(),
updatedAt: DateTime.now(),
colorValue: selectedColor?.toARGB32(),
iconCodePoint: selectedIcon?.codePoint,
);
if (category == null) {
debugPrint('Creating category: ${newCategory.name}, color: ${newCategory.colorValue}, icon: ${newCategory.iconCodePoint}');
} else {
debugPrint('Updating category: ${newCategory.name}, color: ${newCategory.colorValue}, icon: ${newCategory.iconCodePoint}');
}
await widget.repository.createCategory(newCategory);
await _loadCategories();
try {
await widget.onRequestSync();
} catch (_) {}
} catch (e) {
debugPrint('ERROR creating category: $e');
if (mounted) {
ScaffoldMessenger.of(
context,
).showSnackBar(SnackBar(content: Text('Error al crear categoría: $e')));
}
}
controller.dispose();
}
@override
@@ -919,7 +727,8 @@ class _HomeScreenState extends State<HomeScreen> {
? 'deleted_notes'
: 'all_notes'),
categories: _categories,
onEditCategory: (Category c) => _showCreateCategoryDialog(c),
onEditCategory: (Category c) =>
_showCreateCategoryDialog(c),
onCreateCategory: _showCreateCategoryDialog,
),
),
@@ -995,3 +804,252 @@ class _EmptyState extends StatelessWidget {
);
}
}
class _CategoryDialog extends StatefulWidget {
const _CategoryDialog({
required this.category,
required this.repository,
required this.onCategoriesChanged,
required this.onRequestSync,
required this.onCategoryDeleted,
});
final Category? category;
final NoteRepository repository;
final Future<void> Function() onCategoriesChanged;
final Future<void> Function() onRequestSync;
final Future<void> Function() onCategoryDeleted;
@override
State<_CategoryDialog> createState() => _CategoryDialogState();
}
class _CategoryDialogState extends State<_CategoryDialog> {
late final TextEditingController _controller;
Color? _selectedColor;
IconData? _selectedIcon;
final List<Color> _palette = [
Colors.amber,
Colors.blue,
Colors.green,
Colors.purple,
Colors.red,
Colors.teal,
Colors.orange,
Colors.grey,
];
final List<IconData> _icons = [
Icons.folder,
Icons.work,
Icons.star,
Icons.home,
Icons.school,
Icons.book,
Icons.music_note,
Icons.lightbulb,
];
@override
void initState() {
super.initState();
_controller = TextEditingController(text: widget.category?.name ?? '');
_selectedColor =
widget.category != null && widget.category!.colorValue != null
? Color(widget.category!.colorValue!)
: null;
if (widget.category != null && widget.category!.iconCodePoint != null) {
_selectedIcon = _icons.firstWhere(
(IconData icon) => icon.codePoint == widget.category!.iconCodePoint,
orElse: () => _icons.first,
);
}
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
Future<void> _saveCategory() async {
final String name = _controller.text.trim();
if (name.isEmpty) {
return;
}
try {
final Category newCategory = Category(
id: widget.category?.id,
name: name,
updatedAt: DateTime.now(),
colorValue: _selectedColor?.toARGB32(),
iconCodePoint: _selectedIcon?.codePoint,
);
if (widget.category == null) {
debugPrint(
'Creating category: ${newCategory.name}, color: ${newCategory.colorValue}, icon: ${newCategory.iconCodePoint}',
);
} else {
debugPrint(
'Updating category: ${newCategory.name}, color: ${newCategory.colorValue}, icon: ${newCategory.iconCodePoint}',
);
}
await widget.repository.createCategory(newCategory);
await widget.onCategoriesChanged();
try {
await widget.onRequestSync();
} catch (_) {}
if (mounted) {
await widget.onCategoryDeleted();
Navigator.pop(context);
}
} catch (e) {
debugPrint('ERROR creating category: $e');
if (mounted) {
ScaffoldMessenger.of(
context,
).showSnackBar(SnackBar(content: Text('Error al crear categoría: $e')));
}
}
}
Future<void> _deleteCategory() async {
final bool? confirm = await showDialog<bool>(
context: context,
builder: (BuildContext context) => AlertDialog(
title: const Text('Borrar categoría'),
content: const Text('¿Seguro que quieres borrar esta categoría?'),
actions: [
TextButton(
onPressed: () => Navigator.pop(context, false),
child: const Text('Cancelar'),
),
TextButton(
onPressed: () => Navigator.pop(context, true),
child: const Text('Borrar'),
),
],
),
);
if (confirm != true) {
return;
}
try {
await widget.repository.deleteCategory(widget.category!.id);
await widget.onCategoriesChanged();
try {
await widget.onRequestSync();
} catch (_) {}
if (mounted) {
Navigator.pop(context);
}
} catch (e) {
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Error al borrar categoría: $e')),
);
}
}
}
@override
Widget build(BuildContext context) {
return AlertDialog(
title: Text(
widget.category == null ? 'Crear categoría' : 'Editar categoría',
),
content: SingleChildScrollView(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
TextField(
controller: _controller,
decoration: const InputDecoration(
hintText: 'Nombre de la categoría',
),
),
const SizedBox(height: 12),
Align(
alignment: Alignment.centerLeft,
child: Text(
'Color',
style: const TextStyle(fontSize: 14, color: Colors.white70),
),
),
const SizedBox(height: 8),
Wrap(
spacing: 8,
children: _palette.map((Color color) {
final bool isSelected = _selectedColor == color;
return GestureDetector(
onTap: () => setState(() => _selectedColor = color),
child: Container(
width: 36,
height: 36,
decoration: BoxDecoration(
color: color,
borderRadius: BorderRadius.circular(6),
border: isSelected
? Border.all(color: Colors.white, width: 2)
: null,
),
),
);
}).toList(),
),
const SizedBox(height: 12),
Align(
alignment: Alignment.centerLeft,
child: Text(
'Icono',
style: const TextStyle(fontSize: 14, color: Colors.white70),
),
),
const SizedBox(height: 8),
Wrap(
spacing: 8,
children: _icons.map((IconData icon) {
final bool isSelected = _selectedIcon == icon;
return GestureDetector(
onTap: () => setState(() => _selectedIcon = icon),
child: Container(
width: 40,
height: 40,
decoration: BoxDecoration(
color: isSelected ? Colors.white10 : Colors.transparent,
borderRadius: BorderRadius.circular(6),
),
child: Icon(
icon,
color: isSelected ? Colors.white : Colors.white70,
),
),
);
}).toList(),
),
],
),
),
actions: [
if (widget.category != null)
TextButton(
onPressed: _deleteCategory,
child: const Text('Borrar', style: TextStyle(color: Colors.red)),
),
TextButton(
onPressed: () => Navigator.pop(context),
child: const Text('Cancelar'),
),
TextButton(
onPressed: _saveCategory,
child: Text(widget.category == null ? 'Crear' : 'Guardar'),
),
],
);
}
}