import 'dart:io'; import 'package:flutter/material.dart'; import 'package:image_picker/image_picker.dart'; import 'package:supabase_flutter/supabase_flutter.dart'; import '../constants/item_categories.dart'; import '../theme/app_theme.dart'; class AddItemScreen extends StatefulWidget { const AddItemScreen({super.key}); @override State createState() => _AddItemScreenState(); } class _AddItemScreenState extends State { final _nameController = TextEditingController(); final _descController = TextEditingController(); final _imagePicker = ImagePicker(); XFile? _pickedImage; ItemCategory? _selectedCategory; Subcategory? _selectedSubcategory; final Set _selectedTags = {}; bool _isLoading = false; @override void dispose() { _nameController.dispose(); _descController.dispose(); super.dispose(); } Future _pickImage() async { try { final image = await _imagePicker.pickImage( source: ImageSource.gallery, maxWidth: 1080, maxHeight: 1080, imageQuality: 80, ); if (image != null) { setState(() => _pickedImage = image); } } catch (e) { if (mounted) AppSnack.error(context, 'Erro ao selecionar foto: $e'); } } Future _takePhoto() async { try { final image = await _imagePicker.pickImage( source: ImageSource.camera, maxWidth: 1080, maxHeight: 1080, imageQuality: 80, ); if (image != null) { setState(() => _pickedImage = image); } } catch (e) { if (mounted) AppSnack.error(context, 'Erro ao abrir câmara: $e'); } } void _toggleSubcategoryTags( Subcategory? oldSub, Subcategory? newSub, ) { if (oldSub != null && _selectedCategory != null) { for (final t in getAutoContextTags(_selectedCategory!.id, oldSub.id)) { _selectedTags.remove(t); } } if (newSub != null && _selectedCategory != null) { _selectedTags.addAll( getAutoContextTags(_selectedCategory!.id, newSub.id), ); } } Future _saveItem() async { if (_nameController.text.trim().isEmpty) { AppSnack.error(context, 'Indique um nome'); return; } if (_selectedCategory == null) { AppSnack.error(context, 'Selecione uma categoria'); return; } setState(() => _isLoading = true); try { final user = Supabase.instance.client.auth.currentUser; if (user == null) { AppSnack.error(context, 'Utilizador não autenticado'); return; } await Supabase.instance.client.from('users').upsert({ 'id': user.id, 'nome': user.userMetadata?['username'] ?? user.email?.split('@').first ?? 'Utilizador', }, onConflict: 'id'); final inserted = await Supabase.instance.client .from('items') .insert({ 'user_id': user.id, 'nome': _nameController.text.trim(), 'categoria': _selectedCategory!.id, 'tags': [ if (_selectedSubcategory != null) _selectedSubcategory!.id, ..._selectedTags, ], }) .select() .single(); final itemId = inserted['id']; if (_pickedImage != null) { final file = File(_pickedImage!.path); final fileName = 'item_${itemId}_${DateTime.now().millisecondsSinceEpoch}.jpg'; await Supabase.instance.client.storage .from('avatars') .upload(fileName, file); final imageUrl = Supabase.instance.client.storage .from('avatars') .getPublicUrl(fileName); await Supabase.instance.client.from('item_images').insert({ 'item_id': itemId, 'image_url': imageUrl, }); } if (mounted) { AppSnack.success(context, 'Item adicionado!'); Navigator.pop(context); } } catch (e) { if (mounted) AppSnack.error(context, 'Erro ao guardar: $e'); } finally { if (mounted) setState(() => _isLoading = false); } } @override Widget build(BuildContext context) { return Scaffold( backgroundColor: AppColors.background, appBar: AppBar( backgroundColor: AppColors.background, elevation: 0, iconTheme: const IconThemeData(color: AppColors.textPrimary), title: const Text('Adicionar Item', style: AppText.h3), ), body: SingleChildScrollView( padding: const EdgeInsets.fromLTRB(20, 8, 20, 32), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ _buildImagePicker(), const SizedBox(height: 24), const Text('Nome', style: AppText.label), const SizedBox(height: 8), _textField( controller: _nameController, hint: 'ex: Camisa azul', icon: Icons.label_outline_rounded, ), const SizedBox(height: 20), const Text('Descrição (opcional)', style: AppText.label), const SizedBox(height: 8), _textField( controller: _descController, hint: 'detalhes do item', icon: Icons.notes_rounded, maxLines: 3, ), const SizedBox(height: 24), const Text('Categoria', style: AppText.label), const SizedBox(height: 12), _buildCategoryGrid(), if (_selectedCategory != null && _selectedCategory!.subcategories.isNotEmpty) ...[ const SizedBox(height: 24), const Text('Subcategoria', style: AppText.label), const SizedBox(height: 8), Wrap( spacing: 8, runSpacing: 8, children: _selectedCategory!.subcategories.map((sub) { final selected = _selectedSubcategory?.id == sub.id; return AppChip( label: sub.name, selected: selected, color: _selectedCategory!.color, onTap: () { setState(() { final oldSub = _selectedSubcategory; _selectedSubcategory = selected ? null : sub; _toggleSubcategoryTags( oldSub, _selectedSubcategory, ); }); }, ); }).toList(), ), if (_selectedSubcategory != null) ...[ const SizedBox(height: 6), Text( _selectedSubcategory!.description, style: AppText.caption.copyWith( fontStyle: FontStyle.italic, ), ), ], ], const SizedBox(height: 24), const Text('Tags de Contexto', style: AppText.label), const SizedBox(height: 8), Wrap( spacing: 8, runSpacing: 8, children: contextTags.map((tag) { final selected = _selectedTags.contains(tag.id); return AppChip( label: tag.name, selected: selected, onTap: () { setState(() { if (selected) { _selectedTags.remove(tag.id); } else { _selectedTags.add(tag.id); } }); }, ); }).toList(), ), const SizedBox(height: 32), AppButton( label: 'Guardar Item', icon: Icons.check_rounded, loading: _isLoading, onPressed: _saveItem, ), ], ), ), ); } Widget _buildImagePicker() { return Container( decoration: BoxDecoration( color: AppColors.surface, borderRadius: BorderRadius.circular(AppRadius.lg), border: Border.all(color: AppColors.border), ), clipBehavior: Clip.antiAlias, child: Column( children: [ AspectRatio( aspectRatio: 16 / 11, child: _pickedImage != null ? Stack( fit: StackFit.expand, children: [ Image.file(File(_pickedImage!.path), fit: BoxFit.cover), Positioned( top: 8, right: 8, child: Material( color: Colors.black.withValues(alpha: 0.55), shape: const CircleBorder(), child: InkWell( customBorder: const CircleBorder(), onTap: () => setState(() => _pickedImage = null), child: const Padding( padding: EdgeInsets.all(8), child: Icon( Icons.close_rounded, color: Colors.white, size: 18, ), ), ), ), ), ], ) : Container( color: AppColors.surfaceAlt, alignment: Alignment.center, child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon( Icons.image_outlined, size: 48, color: AppColors.textTertiary, ), const SizedBox(height: 8), Text( 'Adicione uma foto', style: AppText.caption, ), ], ), ), ), Row( children: [ Expanded( child: _imageBtn( icon: Icons.photo_camera_rounded, label: 'Câmara', onTap: _takePhoto, ), ), Container(width: 1, height: 44, color: AppColors.border), Expanded( child: _imageBtn( icon: Icons.photo_library_rounded, label: 'Galeria', onTap: _pickImage, ), ), ], ), ], ), ); } Widget _imageBtn({ required IconData icon, required String label, required VoidCallback onTap, }) { return Material( color: Colors.transparent, child: InkWell( onTap: onTap, child: Padding( padding: const EdgeInsets.symmetric(vertical: 12), child: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon(icon, size: 18, color: AppColors.primary), const SizedBox(width: 8), Text( label, style: const TextStyle( color: AppColors.primary, fontWeight: FontWeight.w600, ), ), ], ), ), ), ); } Widget _textField({ required TextEditingController controller, required String hint, required IconData icon, int maxLines = 1, }) { return Container( decoration: AppDecorations.outlined(), child: TextField( controller: controller, maxLines: maxLines, style: AppText.body, decoration: InputDecoration( prefixIcon: maxLines == 1 ? Icon(icon, color: AppColors.textSecondary) : Padding( padding: const EdgeInsets.only(left: 12, top: 14), child: Icon(icon, color: AppColors.textSecondary), ), prefixIconConstraints: maxLines == 1 ? null : const BoxConstraints(minWidth: 40, minHeight: 40), hintText: hint, hintStyle: TextStyle(color: AppColors.textTertiary), border: InputBorder.none, contentPadding: const EdgeInsets.symmetric( horizontal: 12, vertical: 14, ), ), ), ); } Widget _buildCategoryGrid() { return GridView.builder( shrinkWrap: true, physics: const NeverScrollableScrollPhysics(), itemCount: itemCategories.length, gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( crossAxisCount: 3, crossAxisSpacing: 10, mainAxisSpacing: 10, childAspectRatio: 1, ), itemBuilder: (_, i) { final c = itemCategories[i]; final selected = _selectedCategory?.id == c.id; return AnimatedContainer( duration: const Duration(milliseconds: 180), decoration: BoxDecoration( color: selected ? c.color : AppColors.surface, borderRadius: BorderRadius.circular(AppRadius.lg), border: Border.all( color: selected ? c.color : AppColors.border, width: 1.5, ), boxShadow: selected ? [ BoxShadow( color: c.color.withValues(alpha: 0.3), blurRadius: 12, offset: const Offset(0, 6), ) ] : null, ), child: Material( color: Colors.transparent, child: InkWell( borderRadius: BorderRadius.circular(AppRadius.lg), onTap: () { setState(() { if (_selectedCategory?.id != c.id) { _selectedCategory = c; _selectedSubcategory = null; } }); }, child: Padding( padding: const EdgeInsets.all(8), child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon( c.icon, size: 28, color: selected ? Colors.white : c.color, ), const SizedBox(height: 6), Text( c.name, textAlign: TextAlign.center, style: TextStyle( fontSize: 12, fontWeight: FontWeight.w600, color: selected ? Colors.white : AppColors.textPrimary, ), ), ], ), ), ), ), ); }, ); } }