feat: Implement color and icon selection tabs in CategoryDialog for improved user experience
This commit is contained in:
+151
-55
@@ -784,6 +784,7 @@ class _CategoryDialogState extends State<_CategoryDialog> {
|
|||||||
late final TextEditingController _controller;
|
late final TextEditingController _controller;
|
||||||
Color? _selectedColor;
|
Color? _selectedColor;
|
||||||
IconData? _selectedIcon;
|
IconData? _selectedIcon;
|
||||||
|
int _selectedSection = 0;
|
||||||
|
|
||||||
final List<Color> _palette = [
|
final List<Color> _palette = [
|
||||||
Colors.amber,
|
Colors.amber,
|
||||||
@@ -920,9 +921,11 @@ class _CategoryDialogState extends State<_CategoryDialog> {
|
|||||||
title: Text(
|
title: Text(
|
||||||
widget.category == null ? 'Crear categoría' : 'Editar categoría',
|
widget.category == null ? 'Crear categoría' : 'Editar categoría',
|
||||||
),
|
),
|
||||||
content: SingleChildScrollView(
|
content: SizedBox(
|
||||||
|
width: 380,
|
||||||
child: Column(
|
child: Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
children: [
|
children: [
|
||||||
TextField(
|
TextField(
|
||||||
controller: _controller,
|
controller: _controller,
|
||||||
@@ -930,65 +933,119 @@ class _CategoryDialogState extends State<_CategoryDialog> {
|
|||||||
hintText: 'Nombre de la categoría',
|
hintText: 'Nombre de la categoría',
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 12),
|
const SizedBox(height: 16),
|
||||||
Align(
|
Container(
|
||||||
alignment: Alignment.centerLeft,
|
decoration: BoxDecoration(
|
||||||
child: Text(
|
color: Colors.white.withValues(alpha: 0.04),
|
||||||
'Color',
|
borderRadius: BorderRadius.circular(14),
|
||||||
style: const TextStyle(fontSize: 14, color: Colors.white70),
|
border: Border.all(color: Colors.white12),
|
||||||
),
|
),
|
||||||
),
|
child: Column(
|
||||||
const SizedBox(height: 8),
|
mainAxisSize: MainAxisSize.min,
|
||||||
Wrap(
|
children: [
|
||||||
spacing: 8,
|
Row(
|
||||||
children: _palette.map((Color color) {
|
children: [
|
||||||
final bool isSelected = _selectedColor == color;
|
Expanded(
|
||||||
return GestureDetector(
|
child: _PickerTabButton(
|
||||||
onTap: () => setState(() => _selectedColor = color),
|
label: 'Color',
|
||||||
child: Container(
|
selected: _selectedSection == 0,
|
||||||
width: 36,
|
borderRadius: const BorderRadius.only(
|
||||||
height: 36,
|
topLeft: Radius.circular(13),
|
||||||
decoration: BoxDecoration(
|
),
|
||||||
color: color,
|
onTap: () => setState(() => _selectedSection = 0),
|
||||||
borderRadius: BorderRadius.circular(6),
|
),
|
||||||
border: isSelected
|
),
|
||||||
? Border.all(color: Colors.white, width: 2)
|
Expanded(
|
||||||
: null,
|
child: _PickerTabButton(
|
||||||
|
label: 'Icono',
|
||||||
|
selected: _selectedSection == 1,
|
||||||
|
borderRadius: const BorderRadius.only(
|
||||||
|
topRight: Radius.circular(13),
|
||||||
|
),
|
||||||
|
onTap: () => setState(() => _selectedSection = 1),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const Divider(height: 1, color: Colors.white12),
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.all(12),
|
||||||
|
child: AnimatedSwitcher(
|
||||||
|
duration: const Duration(milliseconds: 180),
|
||||||
|
child: _selectedSection == 0
|
||||||
|
? Wrap(
|
||||||
|
key: const ValueKey<String>('colors'),
|
||||||
|
spacing: 10,
|
||||||
|
runSpacing: 10,
|
||||||
|
children: _palette.map((Color color) {
|
||||||
|
final bool isSelected =
|
||||||
|
_selectedColor?.value == color.value;
|
||||||
|
return GestureDetector(
|
||||||
|
onTap: () => setState(() {
|
||||||
|
_selectedColor = color;
|
||||||
|
_selectedSection = 0;
|
||||||
|
}),
|
||||||
|
child: Container(
|
||||||
|
width: 42,
|
||||||
|
height: 42,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: color,
|
||||||
|
borderRadius: BorderRadius.circular(10),
|
||||||
|
border: isSelected
|
||||||
|
? Border.all(
|
||||||
|
color: Colors.white,
|
||||||
|
width: 2,
|
||||||
|
)
|
||||||
|
: Border.all(
|
||||||
|
color: Colors.white12,
|
||||||
|
width: 1,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}).toList(),
|
||||||
|
)
|
||||||
|
: Wrap(
|
||||||
|
key: const ValueKey<String>('icons'),
|
||||||
|
spacing: 10,
|
||||||
|
runSpacing: 10,
|
||||||
|
children: _icons.map((IconData icon) {
|
||||||
|
final bool isSelected = _selectedIcon == icon;
|
||||||
|
return GestureDetector(
|
||||||
|
onTap: () => setState(() {
|
||||||
|
_selectedIcon = icon;
|
||||||
|
_selectedSection = 1;
|
||||||
|
}),
|
||||||
|
child: Container(
|
||||||
|
width: 42,
|
||||||
|
height: 42,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: isSelected
|
||||||
|
? Colors.white10
|
||||||
|
: Colors.transparent,
|
||||||
|
borderRadius: BorderRadius.circular(10),
|
||||||
|
border: Border.all(
|
||||||
|
color: isSelected
|
||||||
|
? Colors.white
|
||||||
|
: Colors.white12,
|
||||||
|
width: 1,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: Icon(
|
||||||
|
icon,
|
||||||
|
color: isSelected
|
||||||
|
? Colors.white
|
||||||
|
: Colors.white70,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}).toList(),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
],
|
||||||
}).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(),
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -1010,3 +1067,42 @@ class _CategoryDialogState extends State<_CategoryDialog> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class _PickerTabButton extends StatelessWidget {
|
||||||
|
const _PickerTabButton({
|
||||||
|
required this.label,
|
||||||
|
required this.selected,
|
||||||
|
required this.borderRadius,
|
||||||
|
required this.onTap,
|
||||||
|
});
|
||||||
|
|
||||||
|
final String label;
|
||||||
|
final bool selected;
|
||||||
|
final BorderRadius borderRadius;
|
||||||
|
final VoidCallback onTap;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Material(
|
||||||
|
borderRadius: borderRadius,
|
||||||
|
clipBehavior: Clip.antiAlias,
|
||||||
|
color: selected ? Colors.white10 : Colors.transparent,
|
||||||
|
child: InkWell(
|
||||||
|
borderRadius: borderRadius,
|
||||||
|
onTap: onTap,
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(vertical: 14),
|
||||||
|
child: Center(
|
||||||
|
child: Text(
|
||||||
|
label,
|
||||||
|
style: TextStyle(
|
||||||
|
color: selected ? Colors.white : Colors.white54,
|
||||||
|
fontWeight: selected ? FontWeight.w600 : FontWeight.w400,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user