Files
FinanceApp2/lib/widgets/add_category_dialog.dart
2025-05-12 19:08:22 +03:00

269 lines
10 KiB
Dart

import 'package:drift/drift.dart' show Value;
import 'package:flutter/material.dart';
import '../database/database.dart' as db; // Import database with prefix 'db'
import '../utils/category_utils.dart'; // Import CategoryUtils
class AddCategoryDialog extends StatefulWidget {
final db.AppDatabase database;
const AddCategoryDialog({Key? key, required this.database}) : super(key: key);
@override
State<AddCategoryDialog> createState() => _AddCategoryDialogState();
}
class _AddCategoryDialogState extends State<AddCategoryDialog> {
final _formKey = GlobalKey<FormState>();
final _nameController = TextEditingController();
// State for selected icon
String _selectedIconName = 'label_outline'; // Default icon name (string)
IconData _selectedIconData = Icons.label_outline; // Default icon data
// State for selected color
Color _selectedColor = CategoryUtils.availableColors[9]; // Default to green
@override
void initState() {
super.initState();
// Initialize icon data based on the default name
_selectedIconData = CategoryUtils.getIconFromString(_selectedIconName);
}
@override
void dispose() {
_nameController.dispose();
super.dispose();
}
// --- Function to show the icon picker dialog ---
Future<void> _showIconPicker() async {
final Map<String, IconData> availableIcons = CategoryUtils.getAvailableIcons();
final List<String> iconNames = availableIcons.keys.toList();
final List<IconData> iconDatas = availableIcons.values.toList();
print("Number of available icons: ${availableIcons.length}"); // Debug print
final String? chosenIconName = await showDialog<String>(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: const Text('Выберите иконку'),
contentPadding: const EdgeInsets.all(10.0), // Adjust padding
content: SizedBox( // Constrain the size of the dialog content
width: double.maxFinite, // Use maximum width available
height: 300, // <--- ЗАДАЕМ ВЫСОТУ ДЛЯ ОБЛАСТИ ПРОКРУТКИ
child: GridView.builder(
// shrinkWrap: true, // <--- УБИРАЕМ SHRINKWRAP
itemCount: iconNames.length,
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 5, // Adjust number of columns
crossAxisSpacing: 10.0,
mainAxisSpacing: 10.0,
),
itemBuilder: (context, index) {
final bool isSelected = _selectedIconName == iconNames[index];
return InkWell(
onTap: () {
Navigator.pop(context, iconNames[index]); // Return the selected icon name (String)
},
borderRadius: BorderRadius.circular(8.0), // Ripple effect matches border
child: Container(
decoration: BoxDecoration(
border: Border.all(
color: isSelected
? Theme.of(context).colorScheme.primary // Highlight selected
: Colors.grey.withOpacity(0.3), // Subtle border for all
width: isSelected ? 2.0 : 1.0, // Thicker border if selected
),
borderRadius: BorderRadius.circular(8.0),
color: isSelected ? Theme.of(context).colorScheme.primary.withOpacity(0.1) : null,
),
child: Icon(
iconDatas[index],
size: 30.0, // Adjust icon size
color: Theme.of(context).brightness == Brightness.dark
? Colors.white70
: Colors.black87,
),
),
);
},
),
),
actions: [
TextButton(
onPressed: () => Navigator.pop(context, null), // Close without selection
child: const Text('Отмена'),
),
],
);
},
);
// Update state if an icon was chosen
if (chosenIconName != null) {
setState(() {
_selectedIconName = chosenIconName;
_selectedIconData = CategoryUtils.getIconFromString(chosenIconName);
});
}
}
// --- End of Icon Picker Function ---
Future<void> _saveCategory() async {
if (_formKey.currentState!.validate()) {
final name = _nameController.text;
// Use the selected icon name
final icon = _selectedIconName;
// Use the selected color value
final color = _selectedColor.value;
final newCategoryCompanion = db.CategoriesCompanion(
name: Value(name),
icon: Value(icon), // Use selected icon name
color: Value(color), // Use selected color value
);
try {
// TODO: Add check for duplicate category name before inserting (more robustly)
final newCategoryId = await widget.database.addCategory(newCategoryCompanion);
final newCategory = await widget.database.getCategoryById(newCategoryId);
if (mounted) {
Navigator.pop(context, newCategory); // Return the newly created CategoryDb object
}
} catch (e) {
print('Error adding category: $e');
// Handle potential duplicate name error from database (depends on DB constraints)
String errorMessage = 'Произошла ошибка при добавлении категории.';
if (e.toString().toLowerCase().contains('unique constraint failed')) {
errorMessage = 'Категория с таким именем уже существует.';
}
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(errorMessage),
backgroundColor: Colors.red,
),
);
}
}
}
}
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
// Determine icon color based on the selected background color's brightness
final iconColor = ThemeData.estimateBrightnessForColor(_selectedColor) == Brightness.dark
? Colors.white70
: Colors.black87;
return AlertDialog(
title: const Text('Создать категорию'),
content: Form(
key: _formKey,
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start, // Align children to the start
children: [
// --- Icon and Name Row ---
Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
// --- Icon Picker ---
Padding(
padding: const EdgeInsets.only(right: 16.0, bottom: 10.0), // Add padding
child: InkWell(
onTap: _showIconPicker, // Show picker on tap
customBorder: const CircleBorder(), // Make ripple circular
child: CircleAvatar(
radius: 24,
backgroundColor: _selectedColor, // Use selected color for background
child: Icon(
_selectedIconData, // Display selected icon
color: iconColor, // Adjust icon color based on background
size: 26,
),
),
),
),
// --- Name Field ---
Expanded(
child: TextFormField(
controller: _nameController,
decoration: const InputDecoration(
labelText: 'Название категории',
),
validator: (value) {
if (value == null || value.isEmpty) {
return 'Пожалуйста, введите название';
}
// Basic length check
if (value.length > 50) {
return 'Название слишком длинное';
}
return null;
},
textCapitalization: TextCapitalization.sentences,
),
),
],
),
const SizedBox(height: 20), // Space before color picker
// --- Color Picker ---
const Text('Выберите цвет:', style: TextStyle(fontSize: 16)),
const SizedBox(height: 8),
Wrap( // Use Wrap for horizontal layout with wrapping
spacing: 8.0, // Horizontal space between circles
runSpacing: 8.0, // Vertical space between rows
children: CategoryUtils.availableColors.map((color) {
final bool isSelected = _selectedColor == color;
return InkWell(
onTap: () {
setState(() {
_selectedColor = color; // Update selected color on tap
});
},
customBorder: const CircleBorder(),
child: Container(
width: 32, // Size of the color circle
height: 32,
decoration: BoxDecoration(
color: color,
shape: BoxShape.circle,
border: Border.all(
color: isSelected
? theme.colorScheme.onSurface // Highlight selected
: theme.dividerColor, // Border for others
width: isSelected ? 3.0 : 1.0,
),
boxShadow: isSelected ? [
BoxShadow(
color: Colors.black.withOpacity(0.3),
blurRadius: 3,
offset: const Offset(0, 1),
)
] : null,
),
),
);
}).toList(),
),
const SizedBox(height: 16), // Add some space at the bottom
],
),
),
actions: [
TextButton(
onPressed: () => Navigator.pop(context, null), // Return null if cancelled
child: const Text('Отмена'),
),
ElevatedButton(
onPressed: _saveCategory,
child: const Text('Сохранить'),
),
],
);
}
}