mirror of
https://github.com/chenasraf/pantry-flutter.git
synced 2026-05-18 01:28:58 +00:00
203 lines
6.2 KiB
Dart
203 lines
6.2 KiB
Dart
import 'package:flutter/material.dart';
|
|
import 'package:pantry/i18n.dart';
|
|
import 'package:pantry/models/category.dart';
|
|
import 'package:pantry/services/category_service.dart';
|
|
import 'package:pantry/utils/category_icons.dart';
|
|
|
|
const categoryColors = [
|
|
'#ef4444',
|
|
'#f97316',
|
|
'#eab308',
|
|
'#22c55e',
|
|
'#14b8a6',
|
|
'#0ea5e9',
|
|
'#6366f1',
|
|
'#a855f7',
|
|
'#ec4899',
|
|
'#78716c',
|
|
];
|
|
|
|
class CreateCategoryDialog extends StatefulWidget {
|
|
final int houseId;
|
|
|
|
/// If non-null, we're editing this category instead of creating a new one.
|
|
final Category? existing;
|
|
|
|
const CreateCategoryDialog({super.key, required this.houseId, this.existing});
|
|
|
|
@override
|
|
State<CreateCategoryDialog> createState() => _CreateCategoryDialogState();
|
|
}
|
|
|
|
class _CreateCategoryDialogState extends State<CreateCategoryDialog> {
|
|
late final TextEditingController _nameController;
|
|
late String _selectedIcon;
|
|
late String _selectedColor;
|
|
bool _saving = false;
|
|
|
|
bool get _isEditing => widget.existing != null;
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
final e = widget.existing;
|
|
_nameController = TextEditingController(text: e?.name ?? '');
|
|
_selectedIcon = e?.icon ?? 'tag';
|
|
_selectedColor = e?.color ?? categoryColors.first;
|
|
}
|
|
|
|
@override
|
|
void dispose() {
|
|
_nameController.dispose();
|
|
super.dispose();
|
|
}
|
|
|
|
Future<void> _save() async {
|
|
final name = _nameController.text.trim();
|
|
if (name.isEmpty) return;
|
|
|
|
setState(() => _saving = true);
|
|
try {
|
|
final category = _isEditing
|
|
? await CategoryService.instance.updateCategory(
|
|
widget.houseId,
|
|
widget.existing!.id,
|
|
name: name,
|
|
icon: _selectedIcon,
|
|
color: _selectedColor,
|
|
)
|
|
: await CategoryService.instance.createCategory(
|
|
widget.houseId,
|
|
name: name,
|
|
icon: _selectedIcon,
|
|
color: _selectedColor,
|
|
);
|
|
if (mounted) Navigator.of(context).pop(category);
|
|
} catch (e) {
|
|
if (mounted) {
|
|
ScaffoldMessenger.of(
|
|
context,
|
|
).showSnackBar(SnackBar(content: Text(m.categories.saveFailed)));
|
|
}
|
|
} finally {
|
|
if (mounted) setState(() => _saving = false);
|
|
}
|
|
}
|
|
|
|
Color _parseHex(String hex) {
|
|
hex = hex.replaceFirst('#', '');
|
|
if (hex.length == 6) hex = 'FF$hex';
|
|
return Color(int.parse(hex, radix: 16));
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final theme = Theme.of(context);
|
|
final f = m.checklists.itemForm;
|
|
|
|
return AlertDialog(
|
|
title: Text(_isEditing ? m.categories.editTitle : m.categories.addTitle),
|
|
content: SingleChildScrollView(
|
|
child: Column(
|
|
mainAxisSize: MainAxisSize.min,
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
TextField(
|
|
controller: _nameController,
|
|
autofocus: true,
|
|
textCapitalization: TextCapitalization.sentences,
|
|
decoration: InputDecoration(
|
|
labelText: f.categoryName,
|
|
border: const OutlineInputBorder(),
|
|
),
|
|
),
|
|
const SizedBox(height: 16),
|
|
Text(f.categoryIcon, style: theme.textTheme.bodyMedium),
|
|
const SizedBox(height: 8),
|
|
Wrap(
|
|
spacing: 4,
|
|
runSpacing: 4,
|
|
children: categoryIconMap.entries.map((entry) {
|
|
final isSelected = _selectedIcon == entry.key;
|
|
return GestureDetector(
|
|
onTap: () => setState(() => _selectedIcon = entry.key),
|
|
child: Container(
|
|
width: 40,
|
|
height: 40,
|
|
decoration: BoxDecoration(
|
|
color: isSelected
|
|
? theme.colorScheme.primaryContainer
|
|
: null,
|
|
borderRadius: BorderRadius.circular(8),
|
|
border: isSelected
|
|
? Border.all(
|
|
color: theme.colorScheme.primary,
|
|
width: 2,
|
|
)
|
|
: null,
|
|
),
|
|
child: Icon(
|
|
entry.value,
|
|
size: 20,
|
|
color: isSelected
|
|
? theme.colorScheme.primary
|
|
: theme.colorScheme.onSurfaceVariant,
|
|
),
|
|
),
|
|
);
|
|
}).toList(),
|
|
),
|
|
const SizedBox(height: 16),
|
|
Text(f.categoryColor, style: theme.textTheme.bodyMedium),
|
|
const SizedBox(height: 8),
|
|
Wrap(
|
|
spacing: 8,
|
|
runSpacing: 8,
|
|
children: categoryColors.map((hex) {
|
|
final color = _parseHex(hex);
|
|
final isSelected = _selectedColor == hex;
|
|
return GestureDetector(
|
|
onTap: () => setState(() => _selectedColor = hex),
|
|
child: Container(
|
|
width: 36,
|
|
height: 36,
|
|
decoration: BoxDecoration(
|
|
color: color,
|
|
shape: BoxShape.circle,
|
|
border: isSelected
|
|
? Border.all(
|
|
color: theme.colorScheme.primary,
|
|
width: 3,
|
|
)
|
|
: Border.all(color: theme.colorScheme.outlineVariant),
|
|
),
|
|
child: isSelected
|
|
? const Icon(Icons.check, size: 18, color: Colors.white)
|
|
: null,
|
|
),
|
|
);
|
|
}).toList(),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
actions: [
|
|
TextButton(
|
|
onPressed: () => Navigator.pop(context),
|
|
child: Text(m.common.cancel),
|
|
),
|
|
FilledButton(
|
|
onPressed: _saving ? null : _save,
|
|
child: _saving
|
|
? const SizedBox(
|
|
width: 16,
|
|
height: 16,
|
|
child: CircularProgressIndicator(strokeWidth: 2),
|
|
)
|
|
: Text(m.common.save),
|
|
),
|
|
],
|
|
);
|
|
}
|
|
}
|