import 'dart:io'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:hive_flutter/hive_flutter.dart'; // Added for Hive.box import 'package:image_picker/image_picker.dart'; import 'package:uuid/uuid.dart'; import 'package:path_provider/path_provider.dart'; import 'package:path/path.dart' as path; import '../models/sake_item.dart'; import '../theme/app_theme.dart'; import 'package:lucide_icons/lucide_icons.dart'; class AddSetItemDialog extends ConsumerStatefulWidget { const AddSetItemDialog({super.key}); @override ConsumerState createState() => _AddSetItemDialogState(); } class _AddSetItemDialogState extends ConsumerState { final _formKey = GlobalKey(); final _nameController = TextEditingController(); final _descriptionController = TextEditingController(); final _priceController = TextEditingController(); bool _useDefaultImage = true; String? _pickedImagePath; final ImagePicker _picker = ImagePicker(); @override void dispose() { _nameController.dispose(); _descriptionController.dispose(); _priceController.dispose(); super.dispose(); } Future _pickImage(ImageSource source) async { try { final XFile? image = await _picker.pickImage( source: source, maxWidth: 1024, maxHeight: 1024, imageQuality: 85, ); if (image != null) { setState(() { _pickedImagePath = image.path; _useDefaultImage = false; }); } } catch (e) { // Handle error } } Future _save() async { if (!_formKey.currentState!.validate()) return; // Save Logic final name = _nameController.text; final description = _descriptionController.text; final price = int.tryParse(_priceController.text) ?? 0; // Image handling List imagePaths = []; if (!_useDefaultImage && _pickedImagePath != null) { // Copy to app doc dir final appDir = await getApplicationDocumentsDirectory(); final fileName = '${const Uuid().v4()}.jpg'; final savedImage = await File(_pickedImagePath!).copy(path.join(appDir.path, fileName)); imagePaths.add(savedImage.path); } // Note: If using default image, we leave imagePaths empty. // The UI will show asset if imagePaths is empty AND itemType is set. final newItem = SakeItem( id: const Uuid().v4(), itemType: ItemType.set, displayData: DisplayData( name: name, brewery: 'Set Product', // Hidden in UI prefecture: 'Set', // Hidden in UI catchCopy: description, // Use catchCopy for description imagePaths: imagePaths, ), userData: UserData( price: price, isUserEdited: true, // It is manually created markup: 1.0, // Default for set ), hiddenSpecs: HiddenSpecs(description: description), metadata: Metadata(createdAt: DateTime.now()), ); // Save to Hive Directly (since sakeListProvider is read-only) final box = Hive.box('sake_items'); await box.add(newItem); if (mounted) { Navigator.of(context).pop(); } } @override Widget build(BuildContext context) { return Dialog( shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)), child: Container( padding: const EdgeInsets.all(24), constraints: const BoxConstraints(maxWidth: 500), child: SingleChildScrollView( child: Form( key: _formKey, child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.stretch, children: [ Row( children: [ Icon(LucideIcons.package, color: Theme.of(context).colorScheme.primary), // Box icon const SizedBox(width: 8), Text( 'セット商品の登録', style: Theme.of(context).textTheme.headlineSmall?.copyWith(fontWeight: FontWeight.bold), ), ], ), const SizedBox(height: 24), // Image Section Center( child: GestureDetector( onTap: () { // Toggle or show picker _showImageSourceDialog(); }, child: Container( width: 120, height: 120, decoration: BoxDecoration( color: Colors.grey[200], borderRadius: BorderRadius.circular(12), border: Border.all(color: Colors.grey[300]!), image: _useDefaultImage ? const DecorationImage(image: AssetImage('assets/images/set_placeholder.png'), fit: BoxFit.cover) : (_pickedImagePath != null ? DecorationImage(image: FileImage(File(_pickedImagePath!)), fit: BoxFit.cover) : null), ), child: !_useDefaultImage && _pickedImagePath == null ? const Icon(LucideIcons.camera, size: 40, color: Colors.grey) : null, ), ), ), Center( child: TextButton( onPressed: _showImageSourceDialog, child: const Text('画像を変更'), ), ), const SizedBox(height: 16), // Name TextFormField( controller: _nameController, decoration: const InputDecoration( labelText: '商品名', hintText: '例: 3種飲み比べセット', border: OutlineInputBorder(), filled: true, ), validator: (val) => val == null || val.isEmpty ? '必須項目です' : null, ), const SizedBox(height: 16), // Price TextFormField( controller: _priceController, decoration: const InputDecoration( labelText: '価格 (税込)', suffixText: '円', border: OutlineInputBorder(), filled: true, ), keyboardType: TextInputType.number, validator: (val) => val == null || val.isEmpty ? '必須項目です' : null, ), const SizedBox(height: 16), // Description TextFormField( controller: _descriptionController, decoration: const InputDecoration( labelText: '説明文', hintText: '例: 当店おすすめの辛口3種です。', border: OutlineInputBorder(), filled: true, ), maxLines: 3, ), const SizedBox(height: 24), // Buttons Row( mainAxisAlignment: MainAxisAlignment.end, children: [ TextButton( onPressed: () => Navigator.pop(context), child: const Text('キャンセル'), ), const SizedBox(width: 8), ElevatedButton( style: ElevatedButton.styleFrom( backgroundColor: AppTheme.posimaiBlue, foregroundColor: Colors.white, elevation: 0, ), onPressed: _save, child: const Text('登録'), ), ], ), ], ), ), ), ), ); } void _showImageSourceDialog() { showModalBottomSheet( context: context, builder: (_) => SafeArea( child: Column( mainAxisSize: MainAxisSize.min, children: [ ListTile( leading: const Icon(LucideIcons.image), title: const Text('プリセット画像を使用'), onTap: () { setState(() { _useDefaultImage = true; }); Navigator.pop(context); }, ), ListTile( leading: const Icon(LucideIcons.camera), title: const Text('カメラで撮影'), onTap: () { Navigator.pop(context); _pickImage(ImageSource.camera); }, ), ListTile( leading: const Icon(LucideIcons.image), title: const Text('アルバムから選択'), onTap: () { Navigator.pop(context); _pickImage(ImageSource.gallery); }, ), ], ), ), ); } }