476 lines
16 KiB
Dart
476 lines
16 KiB
Dart
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';
|
|
|
|
class AddItemScreen extends StatefulWidget {
|
|
const AddItemScreen({super.key});
|
|
|
|
@override
|
|
State<AddItemScreen> createState() => _AddItemScreenState();
|
|
}
|
|
|
|
class _AddItemScreenState extends State<AddItemScreen> {
|
|
final TextEditingController _nameController = TextEditingController();
|
|
final TextEditingController _descriptionController = TextEditingController();
|
|
|
|
ItemCategory? _selectedCategory;
|
|
Subcategory? _selectedSubcategory;
|
|
final Set<String> _selectedTags = {};
|
|
XFile? _selectedImage;
|
|
|
|
bool _isLoading = false;
|
|
|
|
Future<void> _pickImage() async {
|
|
final picker = ImagePicker();
|
|
final image = await picker.pickImage(
|
|
source: ImageSource.gallery,
|
|
maxWidth: 1024,
|
|
imageQuality: 80,
|
|
);
|
|
if (image != null) {
|
|
setState(() => _selectedImage = image);
|
|
}
|
|
}
|
|
|
|
@override
|
|
void dispose() {
|
|
_nameController.dispose();
|
|
_descriptionController.dispose();
|
|
super.dispose();
|
|
}
|
|
|
|
void _onCategoryChanged(ItemCategory? category) {
|
|
setState(() {
|
|
_selectedCategory = category;
|
|
_selectedSubcategory = null;
|
|
_selectedTags.clear();
|
|
|
|
if (category != null && category.subcategories.isNotEmpty) {
|
|
_selectedSubcategory = category.subcategories.first;
|
|
_autoAssignTags(category.id, category.subcategories.first.id);
|
|
}
|
|
});
|
|
}
|
|
|
|
void _onSubcategoryChanged(Subcategory? subcategory) {
|
|
setState(() {
|
|
_selectedSubcategory = subcategory;
|
|
_selectedTags.clear();
|
|
|
|
if (_selectedCategory != null && subcategory != null) {
|
|
_autoAssignTags(_selectedCategory!.id, subcategory.id);
|
|
}
|
|
});
|
|
}
|
|
|
|
void _autoAssignTags(String categoryId, String subcategoryId) {
|
|
final autoTags = getAutoContextTags(categoryId, subcategoryId);
|
|
setState(() {
|
|
_selectedTags.addAll(autoTags);
|
|
});
|
|
}
|
|
|
|
void _toggleTag(String tagId) {
|
|
setState(() {
|
|
if (_selectedTags.contains(tagId)) {
|
|
_selectedTags.remove(tagId);
|
|
} else if (_selectedTags.length < 10) {
|
|
_selectedTags.add(tagId);
|
|
}
|
|
});
|
|
}
|
|
|
|
Future<void> _saveItem() async {
|
|
if (_nameController.text.trim().isEmpty) {
|
|
_showErrorSnackBar('Por favor, insira o nome do item');
|
|
return;
|
|
}
|
|
|
|
if (_selectedCategory == null) {
|
|
_showErrorSnackBar('Por favor, selecione uma categoria');
|
|
return;
|
|
}
|
|
|
|
setState(() => _isLoading = true);
|
|
|
|
try {
|
|
final user = Supabase.instance.client.auth.currentUser;
|
|
if (user == null) {
|
|
_showErrorSnackBar('Usuário não autenticado');
|
|
return;
|
|
}
|
|
|
|
// Ensure user profile row exists in public.users (FK requirement)
|
|
await Supabase.instance.client.from('users').upsert({
|
|
'id': user.id,
|
|
'nome':
|
|
user.userMetadata?['username'] ??
|
|
user.email?.split('@').first ??
|
|
'Usuário',
|
|
}, 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();
|
|
|
|
// Upload image if selected
|
|
if (_selectedImage != null) {
|
|
final itemId = inserted['id'];
|
|
final file = File(_selectedImage!.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,
|
|
});
|
|
}
|
|
|
|
_showSuccessSnackBar('Item adicionado com sucesso!');
|
|
if (mounted) Navigator.pop(context);
|
|
} catch (e) {
|
|
_showErrorSnackBar('Erro ao salvar item: $e');
|
|
} finally {
|
|
setState(() => _isLoading = false);
|
|
}
|
|
}
|
|
|
|
void _showErrorSnackBar(String message) {
|
|
ScaffoldMessenger.of(context).showSnackBar(
|
|
SnackBar(content: Text(message), backgroundColor: Colors.red),
|
|
);
|
|
}
|
|
|
|
void _showSuccessSnackBar(String message) {
|
|
ScaffoldMessenger.of(context).showSnackBar(
|
|
SnackBar(content: Text(message), backgroundColor: Colors.green),
|
|
);
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return Scaffold(
|
|
backgroundColor: const Color(0xFFFFE5CC),
|
|
appBar: AppBar(
|
|
backgroundColor: const Color(0xFF0066CC),
|
|
elevation: 0,
|
|
title: const Text(
|
|
'Adicionar Item',
|
|
style: TextStyle(color: Colors.white, fontSize: 20),
|
|
),
|
|
centerTitle: true,
|
|
),
|
|
body: SingleChildScrollView(
|
|
padding: const EdgeInsets.all(20),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
// Image picker
|
|
Center(
|
|
child: GestureDetector(
|
|
onTap: _pickImage,
|
|
child: Container(
|
|
width: 140,
|
|
height: 140,
|
|
decoration: BoxDecoration(
|
|
color: Colors.white,
|
|
borderRadius: BorderRadius.circular(16),
|
|
border: Border.all(color: const Color(0xFFE0E0E0)),
|
|
),
|
|
child: _selectedImage != null
|
|
? ClipRRect(
|
|
borderRadius: BorderRadius.circular(16),
|
|
child: Image.file(
|
|
File(_selectedImage!.path),
|
|
fit: BoxFit.cover,
|
|
),
|
|
)
|
|
: const Column(
|
|
mainAxisAlignment: MainAxisAlignment.center,
|
|
children: [
|
|
Icon(
|
|
Icons.add_a_photo_outlined,
|
|
size: 40,
|
|
color: Color(0xFF0066CC),
|
|
),
|
|
SizedBox(height: 8),
|
|
Text(
|
|
'Adicionar foto',
|
|
style: TextStyle(
|
|
color: Color(0xFF666666),
|
|
fontSize: 12,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
),
|
|
|
|
const SizedBox(height: 20),
|
|
|
|
// Name field
|
|
Container(
|
|
decoration: BoxDecoration(
|
|
color: Colors.white,
|
|
borderRadius: BorderRadius.circular(12),
|
|
border: Border.all(color: const Color(0xFFE0E0E0)),
|
|
),
|
|
child: TextField(
|
|
controller: _nameController,
|
|
decoration: const InputDecoration(
|
|
labelText: 'Nome do item',
|
|
hintText: 'Ex: Camiseta Azul',
|
|
border: InputBorder.none,
|
|
contentPadding: EdgeInsets.symmetric(
|
|
horizontal: 16,
|
|
vertical: 16,
|
|
),
|
|
),
|
|
),
|
|
),
|
|
|
|
const SizedBox(height: 16),
|
|
|
|
// Description field
|
|
Container(
|
|
decoration: BoxDecoration(
|
|
color: Colors.white,
|
|
borderRadius: BorderRadius.circular(12),
|
|
border: Border.all(color: const Color(0xFFE0E0E0)),
|
|
),
|
|
child: TextField(
|
|
controller: _descriptionController,
|
|
maxLines: 3,
|
|
decoration: const InputDecoration(
|
|
labelText: 'Descrição (opcional)',
|
|
hintText: 'Detalhes sobre o item',
|
|
border: InputBorder.none,
|
|
contentPadding: EdgeInsets.symmetric(
|
|
horizontal: 16,
|
|
vertical: 16,
|
|
),
|
|
),
|
|
),
|
|
),
|
|
|
|
const SizedBox(height: 24),
|
|
|
|
// Category selection
|
|
const Text(
|
|
'Categoria',
|
|
style: TextStyle(
|
|
fontSize: 16,
|
|
fontWeight: FontWeight.bold,
|
|
color: Color(0xFF333333),
|
|
),
|
|
),
|
|
const SizedBox(height: 8),
|
|
|
|
Container(
|
|
width: double.infinity,
|
|
padding: const EdgeInsets.symmetric(horizontal: 12),
|
|
decoration: BoxDecoration(
|
|
color: Colors.white,
|
|
borderRadius: BorderRadius.circular(12),
|
|
border: Border.all(color: const Color(0xFFE0E0E0)),
|
|
),
|
|
child: DropdownButtonHideUnderline(
|
|
child: DropdownButton<ItemCategory>(
|
|
value: _selectedCategory,
|
|
hint: const Text('Selecione uma categoria'),
|
|
isExpanded: true,
|
|
items: ITEM_CATEGORIES.map((category) {
|
|
return DropdownMenuItem<ItemCategory>(
|
|
value: category,
|
|
child: Row(
|
|
children: [
|
|
Text(
|
|
category.icon,
|
|
style: const TextStyle(fontSize: 24),
|
|
),
|
|
const SizedBox(width: 12),
|
|
Text(category.name),
|
|
],
|
|
),
|
|
);
|
|
}).toList(),
|
|
onChanged: _onCategoryChanged,
|
|
),
|
|
),
|
|
),
|
|
|
|
const SizedBox(height: 16),
|
|
|
|
// Subcategory selection
|
|
if (_selectedCategory != null &&
|
|
_selectedCategory!.subcategories.isNotEmpty) ...[
|
|
const Text(
|
|
'Subcategoria',
|
|
style: TextStyle(
|
|
fontSize: 16,
|
|
fontWeight: FontWeight.bold,
|
|
color: Color(0xFF333333),
|
|
),
|
|
),
|
|
const SizedBox(height: 8),
|
|
|
|
SizedBox(
|
|
height: 60,
|
|
child: ListView.builder(
|
|
scrollDirection: Axis.horizontal,
|
|
itemCount: _selectedCategory!.subcategories.length,
|
|
itemBuilder: (context, index) {
|
|
final subcategory = _selectedCategory!.subcategories[index];
|
|
final isSelected = _selectedSubcategory == subcategory;
|
|
|
|
return Padding(
|
|
padding: const EdgeInsets.only(right: 8),
|
|
child: GestureDetector(
|
|
onTap: () => _onSubcategoryChanged(subcategory),
|
|
child: Container(
|
|
padding: const EdgeInsets.symmetric(
|
|
horizontal: 16,
|
|
vertical: 8,
|
|
),
|
|
decoration: BoxDecoration(
|
|
color: isSelected
|
|
? const Color(0xFF0066CC)
|
|
: Colors.white,
|
|
borderRadius: BorderRadius.circular(20),
|
|
border: Border.all(
|
|
color: isSelected
|
|
? const Color(0xFF0066CC)
|
|
: const Color(0xFFE0E0E0),
|
|
),
|
|
),
|
|
child: Center(
|
|
child: Text(
|
|
subcategory.name,
|
|
style: TextStyle(
|
|
color: isSelected
|
|
? Colors.white
|
|
: const Color(0xFF333333),
|
|
fontWeight: FontWeight.w500,
|
|
),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
},
|
|
),
|
|
),
|
|
|
|
const SizedBox(height: 8),
|
|
if (_selectedSubcategory != null)
|
|
Text(
|
|
_selectedSubcategory!.examples,
|
|
style: const TextStyle(
|
|
fontSize: 12,
|
|
color: Color(0xFF666666),
|
|
fontStyle: FontStyle.italic,
|
|
),
|
|
),
|
|
],
|
|
|
|
const SizedBox(height: 24),
|
|
|
|
// Context tags
|
|
const Text(
|
|
'Tags de Contexto',
|
|
style: TextStyle(
|
|
fontSize: 16,
|
|
fontWeight: FontWeight.bold,
|
|
color: Color(0xFF333333),
|
|
),
|
|
),
|
|
const SizedBox(height: 8),
|
|
|
|
Wrap(
|
|
spacing: 8,
|
|
runSpacing: 8,
|
|
children: CONTEXT_TAGS.map((tag) {
|
|
final isSelected = _selectedTags.contains(tag.id);
|
|
return FilterChip(
|
|
label: Text(tag.name),
|
|
selected: isSelected,
|
|
onSelected: (selected) => _toggleTag(tag.id),
|
|
selectedColor: const Color(0xFF0066CC).withOpacity(0.2),
|
|
checkmarkColor: const Color(0xFF0066CC),
|
|
backgroundColor: Colors.white,
|
|
labelStyle: TextStyle(
|
|
color: isSelected
|
|
? const Color(0xFF0066CC)
|
|
: const Color(0xFF333333),
|
|
),
|
|
);
|
|
}).toList(),
|
|
),
|
|
|
|
const SizedBox(height: 8),
|
|
Text(
|
|
'Tags selecionadas: ${_selectedTags.length}/10',
|
|
style: const TextStyle(fontSize: 12, color: Color(0xFF666666)),
|
|
),
|
|
|
|
const SizedBox(height: 32),
|
|
|
|
// Save button
|
|
SizedBox(
|
|
width: double.infinity,
|
|
height: 56,
|
|
child: ElevatedButton(
|
|
onPressed: _isLoading ? null : _saveItem,
|
|
style: ElevatedButton.styleFrom(
|
|
backgroundColor: const Color(0xFF0066CC),
|
|
shape: RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.circular(12),
|
|
),
|
|
elevation: 0,
|
|
),
|
|
child: _isLoading
|
|
? const SizedBox(
|
|
height: 20,
|
|
width: 20,
|
|
child: CircularProgressIndicator(
|
|
strokeWidth: 2,
|
|
valueColor: AlwaysStoppedAnimation<Color>(
|
|
Colors.white,
|
|
),
|
|
),
|
|
)
|
|
: const Text(
|
|
'Salvar Item',
|
|
style: TextStyle(
|
|
color: Colors.white,
|
|
fontSize: 18,
|
|
fontWeight: FontWeight.w600,
|
|
),
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|