Atualização tela semanal |Adicoinar item com imagem
This commit is contained in:
@@ -1,4 +1,6 @@
|
||||
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';
|
||||
|
||||
@@ -12,13 +14,26 @@ class AddItemScreen extends StatefulWidget {
|
||||
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();
|
||||
@@ -31,7 +46,7 @@ class _AddItemScreenState extends State<AddItemScreen> {
|
||||
_selectedCategory = category;
|
||||
_selectedSubcategory = null;
|
||||
_selectedTags.clear();
|
||||
|
||||
|
||||
if (category != null && category.subcategories.isNotEmpty) {
|
||||
_selectedSubcategory = category.subcategories.first;
|
||||
_autoAssignTags(category.id, category.subcategories.first.id);
|
||||
@@ -43,7 +58,7 @@ class _AddItemScreenState extends State<AddItemScreen> {
|
||||
setState(() {
|
||||
_selectedSubcategory = subcategory;
|
||||
_selectedTags.clear();
|
||||
|
||||
|
||||
if (_selectedCategory != null && subcategory != null) {
|
||||
_autoAssignTags(_selectedCategory!.id, subcategory.id);
|
||||
}
|
||||
@@ -87,17 +102,49 @@ class _AddItemScreenState extends State<AddItemScreen> {
|
||||
return;
|
||||
}
|
||||
|
||||
await Supabase.instance.client.from('items').insert({
|
||||
'user_id': user.id,
|
||||
'name': _nameController.text.trim(),
|
||||
'description': _descriptionController.text.trim(),
|
||||
'category_id': _selectedCategory!.id,
|
||||
'subcategory_id': _selectedSubcategory?.id,
|
||||
'context_tags': _selectedTags.toList(),
|
||||
});
|
||||
// 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!');
|
||||
Navigator.pop(context);
|
||||
if (mounted) Navigator.pop(context);
|
||||
} catch (e) {
|
||||
_showErrorSnackBar('Erro ao salvar item: $e');
|
||||
} finally {
|
||||
@@ -135,6 +182,50 @@ class _AddItemScreenState extends State<AddItemScreen> {
|
||||
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(
|
||||
@@ -192,7 +283,7 @@ class _AddItemScreenState extends State<AddItemScreen> {
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
|
||||
|
||||
Container(
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12),
|
||||
@@ -211,7 +302,10 @@ class _AddItemScreenState extends State<AddItemScreen> {
|
||||
value: category,
|
||||
child: Row(
|
||||
children: [
|
||||
Text(category.icon, style: const TextStyle(fontSize: 24)),
|
||||
Text(
|
||||
category.icon,
|
||||
style: const TextStyle(fontSize: 24),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Text(category.name),
|
||||
],
|
||||
@@ -226,7 +320,8 @@ class _AddItemScreenState extends State<AddItemScreen> {
|
||||
const SizedBox(height: 16),
|
||||
|
||||
// Subcategory selection
|
||||
if (_selectedCategory != null && _selectedCategory!.subcategories.isNotEmpty) ...[
|
||||
if (_selectedCategory != null &&
|
||||
_selectedCategory!.subcategories.isNotEmpty) ...[
|
||||
const Text(
|
||||
'Subcategoria',
|
||||
style: TextStyle(
|
||||
@@ -236,7 +331,7 @@ class _AddItemScreenState extends State<AddItemScreen> {
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
|
||||
|
||||
SizedBox(
|
||||
height: 60,
|
||||
child: ListView.builder(
|
||||
@@ -245,25 +340,34 @@ class _AddItemScreenState extends State<AddItemScreen> {
|
||||
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),
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 16,
|
||||
vertical: 8,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color: isSelected ? const Color(0xFF0066CC) : Colors.white,
|
||||
color: isSelected
|
||||
? const Color(0xFF0066CC)
|
||||
: Colors.white,
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
border: Border.all(
|
||||
color: isSelected ? const Color(0xFF0066CC) : const Color(0xFFE0E0E0),
|
||||
color: isSelected
|
||||
? const Color(0xFF0066CC)
|
||||
: const Color(0xFFE0E0E0),
|
||||
),
|
||||
),
|
||||
child: Center(
|
||||
child: Text(
|
||||
subcategory.name,
|
||||
style: TextStyle(
|
||||
color: isSelected ? Colors.white : const Color(0xFF333333),
|
||||
color: isSelected
|
||||
? Colors.white
|
||||
: const Color(0xFF333333),
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
@@ -274,7 +378,7 @@ class _AddItemScreenState extends State<AddItemScreen> {
|
||||
},
|
||||
),
|
||||
),
|
||||
|
||||
|
||||
const SizedBox(height: 8),
|
||||
if (_selectedSubcategory != null)
|
||||
Text(
|
||||
@@ -299,7 +403,7 @@ class _AddItemScreenState extends State<AddItemScreen> {
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
|
||||
|
||||
Wrap(
|
||||
spacing: 8,
|
||||
runSpacing: 8,
|
||||
@@ -313,19 +417,18 @@ class _AddItemScreenState extends State<AddItemScreen> {
|
||||
checkmarkColor: const Color(0xFF0066CC),
|
||||
backgroundColor: Colors.white,
|
||||
labelStyle: TextStyle(
|
||||
color: isSelected ? const Color(0xFF0066CC) : const Color(0xFF333333),
|
||||
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),
|
||||
),
|
||||
style: const TextStyle(fontSize: 12, color: Color(0xFF666666)),
|
||||
),
|
||||
|
||||
const SizedBox(height: 32),
|
||||
@@ -349,7 +452,9 @@ class _AddItemScreenState extends State<AddItemScreen> {
|
||||
width: 20,
|
||||
child: CircularProgressIndicator(
|
||||
strokeWidth: 2,
|
||||
valueColor: AlwaysStoppedAnimation<Color>(Colors.white),
|
||||
valueColor: AlwaysStoppedAnimation<Color>(
|
||||
Colors.white,
|
||||
),
|
||||
),
|
||||
)
|
||||
: const Text(
|
||||
|
||||
Reference in New Issue
Block a user