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'; import 'add_item_screen.dart'; class ItemScreen extends StatefulWidget { const ItemScreen({super.key}); @override State createState() => _ItemScreenState(); } class _ItemScreenState extends State { List> _items = []; bool _isLoading = true; String _searchQuery = ''; String? _selectedCategoryFilter; bool _gridView = true; List> get _filteredItems { return _items.where((item) { final name = (item['nome'] ?? '').toString().toLowerCase(); final tags = List.from( item['tags'] ?? [], ).join(' ').toLowerCase(); final matchesSearch = _searchQuery.isEmpty || name.contains(_searchQuery.toLowerCase()) || tags.contains(_searchQuery.toLowerCase()); final matchesCategory = _selectedCategoryFilter == null || item['categoria'] == _selectedCategoryFilter; return matchesSearch && matchesCategory; }).toList(); } @override void initState() { super.initState(); _loadItems(); } Future _loadItems() async { setState(() => _isLoading = true); try { final user = Supabase.instance.client.auth.currentUser; if (user == null) return; final response = await Supabase.instance.client .from('items') .select('*, item_images(image_url)') .eq('user_id', user.id) .order('id', ascending: false); if (!mounted) return; setState(() { _items = List>.from(response); _isLoading = false; }); } catch (e) { debugPrint('Error loading items: $e'); if (mounted) setState(() => _isLoading = false); } } String? _imageUrl(Map item) { final images = item['item_images'] as List?; if (images != null && images.isNotEmpty) { return images.first['image_url'] as String?; } return null; } Future _deleteItem(Map item) async { final confirmed = await showDialog( context: context, builder: (ctx) => AlertDialog( shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(AppRadius.lg), ), title: const Text('Apagar item?'), content: Text('"${item['nome']}" será removido permanentemente.'), actions: [ TextButton( onPressed: () => Navigator.pop(ctx, false), child: const Text('Cancelar'), ), TextButton( onPressed: () => Navigator.pop(ctx, true), style: TextButton.styleFrom(foregroundColor: AppColors.error), child: const Text('Apagar'), ), ], ), ); if (confirmed != true) return; try { await Supabase.instance.client .from('item_images') .delete() .eq('item_id', item['id']); await Supabase.instance.client .from('items') .delete() .eq('id', item['id']); _loadItems(); if (mounted) AppSnack.success(context, 'Item apagado'); } catch (e) { if (mounted) AppSnack.error(context, 'Erro: $e'); } } void _openItem(Map item) { Navigator.push( context, MaterialPageRoute( builder: (_) => ItemDetailScreen(item: item, imageUrl: _imageUrl(item)), ), ).then((_) => _loadItems()); } void _editItem(Map item) { Navigator.push( context, MaterialPageRoute(builder: (_) => EditItemScreen(item: item)), ).then((_) => _loadItems()); } @override Widget build(BuildContext context) { return Scaffold( backgroundColor: AppColors.background, body: SafeArea( bottom: false, child: Column( children: [ _buildHeader(), _buildSearchBar(), const SizedBox(height: 12), _buildCategoryChips(), const SizedBox(height: 16), Expanded( child: _isLoading ? const Center(child: CircularProgressIndicator()) : _items.isEmpty ? _buildEmpty() : _filteredItems.isEmpty ? _buildNoResults() : RefreshIndicator( onRefresh: _loadItems, color: AppColors.primary, child: _gridView ? _buildGrid() : _buildList(), ), ), ], ), ), floatingActionButton: _buildFab(), ); } Widget _buildHeader() { return Padding( padding: const EdgeInsets.fromLTRB(20, 12, 20, 16), child: Row( children: [ const Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text('Inventário', style: AppText.h2), SizedBox(height: 2), Text('Os seus itens guardados', style: AppText.caption), ], ), ), _toggleViewButton(), ], ), ); } Widget _toggleViewButton() { return Container( decoration: BoxDecoration( color: AppColors.surfaceAlt, borderRadius: BorderRadius.circular(AppRadius.pill), ), padding: const EdgeInsets.all(4), child: Row( children: [ _viewIcon(Icons.grid_view_rounded, _gridView), _viewIcon(Icons.view_list_rounded, !_gridView), ], ), ); } Widget _viewIcon(IconData icon, bool selected) { return Material( color: Colors.transparent, child: InkWell( borderRadius: BorderRadius.circular(AppRadius.pill), onTap: () => setState(() => _gridView = !_gridView), child: Container( padding: const EdgeInsets.all(6), decoration: BoxDecoration( color: selected ? AppColors.surface : Colors.transparent, borderRadius: BorderRadius.circular(AppRadius.pill), boxShadow: selected ? AppShadows.soft : null, ), child: Icon( icon, size: 18, color: selected ? AppColors.primary : AppColors.textSecondary, ), ), ), ); } Widget _buildSearchBar() { return Padding( padding: const EdgeInsets.symmetric(horizontal: 20), child: Container( decoration: AppDecorations.outlined(radius: AppRadius.pill), child: TextField( onChanged: (v) => setState(() => _searchQuery = v), style: AppText.body, decoration: InputDecoration( prefixIcon: const Icon( Icons.search_rounded, color: AppColors.textSecondary, ), hintText: 'Pesquisar por nome ou tag...', hintStyle: TextStyle(color: AppColors.textTertiary), border: InputBorder.none, contentPadding: const EdgeInsets.symmetric(vertical: 14), ), ), ), ); } Widget _buildCategoryChips() { return SizedBox( height: 38, child: ListView( scrollDirection: Axis.horizontal, padding: const EdgeInsets.symmetric(horizontal: 20), children: [ Padding( padding: const EdgeInsets.only(right: 8), child: AppChip( label: 'Todos', icon: Icons.apps_rounded, selected: _selectedCategoryFilter == null, onTap: () => setState(() => _selectedCategoryFilter = null), ), ), ...itemCategories.map( (c) => Padding( padding: const EdgeInsets.only(right: 8), child: AppChip( label: c.name, icon: c.icon, color: c.color, selected: _selectedCategoryFilter == c.id, onTap: () => setState(() => _selectedCategoryFilter = c.id), ), ), ), ], ), ); } Widget _buildEmpty() { return _emptyState( icon: Icons.inventory_2_outlined, title: 'Nenhum item ainda', subtitle: 'Toque no + para adicionar o primeiro', ); } Widget _buildNoResults() { return _emptyState( icon: Icons.search_off_rounded, title: 'Sem resultados', subtitle: 'Tente outra pesquisa ou categoria', ); } Widget _emptyState({ required IconData icon, required String title, required String subtitle, }) { return Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Container( width: 80, height: 80, decoration: BoxDecoration( color: AppColors.surfaceAlt, shape: BoxShape.circle, ), child: Icon(icon, size: 36, color: AppColors.textTertiary), ), const SizedBox(height: 16), Text(title, style: AppText.h3), const SizedBox(height: 4), Text(subtitle, style: AppText.caption), ], ), ); } Widget _buildGrid() { return GridView.builder( padding: const EdgeInsets.fromLTRB(20, 4, 20, 100), physics: const AlwaysScrollableScrollPhysics(), gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( crossAxisCount: 2, mainAxisSpacing: 12, crossAxisSpacing: 12, childAspectRatio: 0.70, ), itemCount: _filteredItems.length, itemBuilder: (_, i) => _buildGridCard(_filteredItems[i]), ); } Widget _buildList() { return ListView.builder( padding: const EdgeInsets.fromLTRB(20, 4, 20, 100), physics: const AlwaysScrollableScrollPhysics(), itemCount: _filteredItems.length, itemBuilder: (_, i) => _buildListCard(_filteredItems[i]), ); } Widget _buildGridCard(Map item) { final cat = categoryById(item['categoria'] as String?); final tags = List.from(item['tags'] ?? []); final imageUrl = _imageUrl(item); return Container( decoration: AppDecorations.card(), clipBehavior: Clip.antiAlias, child: Material( color: Colors.transparent, child: InkWell( onTap: () => _openItem(item), onLongPress: () => _showItemActions(item), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Expanded( child: Stack( fit: StackFit.expand, children: [ Container(color: cat.color.withValues(alpha: 0.15)), if (imageUrl != null) Image.network( imageUrl, fit: BoxFit.cover, errorBuilder: (_, __, ___) => Center( child: Icon(cat.icon, color: cat.color, size: 40), ), ) else Center(child: Icon(cat.icon, color: cat.color, size: 40)), Positioned(top: 8, right: 8, child: _moreButton(item)), ], ), ), Padding( padding: const EdgeInsets.fromLTRB(10, 8, 10, 10), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( item['nome'] ?? 'Sem nome', maxLines: 1, overflow: TextOverflow.ellipsis, style: const TextStyle( fontWeight: FontWeight.w600, fontSize: 14, color: AppColors.textPrimary, ), ), const SizedBox(height: 2), Row( children: [ Icon(cat.icon, size: 12, color: cat.color), const SizedBox(width: 4), Expanded( child: Text( cat.name, maxLines: 1, overflow: TextOverflow.ellipsis, style: TextStyle( fontSize: 11, color: cat.color, fontWeight: FontWeight.w600, ), ), ), ], ), if (tags.isNotEmpty) ...[ const SizedBox(height: 6), Text( tags.take(2).join(' • '), maxLines: 1, overflow: TextOverflow.ellipsis, style: AppText.caption, ), ], ], ), ), ], ), ), ), ); } Widget _buildListCard(Map item) { final cat = categoryById(item['categoria'] as String?); final tags = List.from(item['tags'] ?? []); final imageUrl = _imageUrl(item); return Container( margin: const EdgeInsets.only(bottom: 10), decoration: AppDecorations.card(), clipBehavior: Clip.antiAlias, child: Material( color: Colors.transparent, child: InkWell( onTap: () => _openItem(item), onLongPress: () => _showItemActions(item), child: Padding( padding: const EdgeInsets.all(10), child: Row( children: [ ClipRRect( borderRadius: BorderRadius.circular(AppRadius.md), child: Container( width: 64, height: 64, color: cat.color.withValues(alpha: 0.15), child: imageUrl != null ? Image.network( imageUrl, fit: BoxFit.cover, errorBuilder: (_, __, ___) => Icon(cat.icon, color: cat.color, size: 28), ) : Icon(cat.icon, color: cat.color, size: 28), ), ), const SizedBox(width: 12), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( item['nome'] ?? 'Sem nome', style: const TextStyle( fontWeight: FontWeight.w600, fontSize: 15, color: AppColors.textPrimary, ), maxLines: 1, overflow: TextOverflow.ellipsis, ), const SizedBox(height: 4), Row( children: [ Icon(cat.icon, size: 13, color: cat.color), const SizedBox(width: 4), Text( cat.name, style: TextStyle( fontSize: 12, color: cat.color, fontWeight: FontWeight.w600, ), ), ], ), if (tags.isNotEmpty) ...[ const SizedBox(height: 4), Text( tags.take(3).join(' • '), maxLines: 1, overflow: TextOverflow.ellipsis, style: AppText.caption, ), ], ], ), ), _moreButton(item), ], ), ), ), ), ); } Widget _moreButton(Map item) { return Material( color: Colors.white.withValues(alpha: 0.75), shape: const CircleBorder(), child: InkWell( customBorder: const CircleBorder(), onTap: () => _showItemActions(item), child: const Padding( padding: EdgeInsets.all(6), child: Icon( Icons.more_horiz_rounded, size: 18, color: AppColors.textPrimary, ), ), ), ); } void _showItemActions(Map item) { showModalBottomSheet( context: context, backgroundColor: Colors.transparent, builder: (ctx) => Container( margin: const EdgeInsets.all(12), decoration: BoxDecoration( color: AppColors.surface, borderRadius: BorderRadius.circular(AppRadius.lg), ), child: SafeArea( child: Column( mainAxisSize: MainAxisSize.min, children: [ const SizedBox(height: 8), Container( width: 40, height: 4, decoration: BoxDecoration( color: AppColors.border, borderRadius: BorderRadius.circular(2), ), ), const SizedBox(height: 8), _actionTile( Icons.visibility_outlined, 'Ver detalhes', AppColors.primary, () { Navigator.pop(ctx); _openItem(item); }, ), _actionTile(Icons.edit_outlined, 'Editar', AppColors.primary, () { Navigator.pop(ctx); _editItem(item); }), _actionTile( Icons.delete_outline_rounded, 'Apagar', AppColors.error, () { Navigator.pop(ctx); _deleteItem(item); }, ), const SizedBox(height: 8), ], ), ), ), ); } Widget _actionTile( IconData icon, String label, Color color, VoidCallback onTap, ) { return ListTile( leading: Container( width: 40, height: 40, decoration: BoxDecoration( color: color.withValues(alpha: 0.12), borderRadius: BorderRadius.circular(AppRadius.md), ), child: Icon(icon, color: color), ), title: Text( label, style: TextStyle(color: color, fontWeight: FontWeight.w600), ), onTap: onTap, ); } Widget _buildFab() { return Container( decoration: BoxDecoration( gradient: AppColors.brandGradient, borderRadius: BorderRadius.circular(AppRadius.lg), boxShadow: AppShadows.brand, ), child: Material( color: Colors.transparent, child: InkWell( borderRadius: BorderRadius.circular(AppRadius.lg), onTap: () { Navigator.push( context, MaterialPageRoute(builder: (_) => const AddItemScreen()), ).then((_) => _loadItems()); }, child: const Padding( padding: EdgeInsets.symmetric(horizontal: 18, vertical: 14), child: Row( mainAxisSize: MainAxisSize.min, children: [ Icon(Icons.add_rounded, color: Colors.white), SizedBox(width: 6), Text( 'Adicionar', style: TextStyle( color: Colors.white, fontWeight: FontWeight.w600, ), ), ], ), ), ), ), ); } } // ============================================================ // Detalhe do item // ============================================================ class ItemDetailScreen extends StatelessWidget { final Map item; final String? imageUrl; const ItemDetailScreen({super.key, required this.item, this.imageUrl}); @override Widget build(BuildContext context) { final cat = categoryById(item['categoria'] as String?); final tags = List.from(item['tags'] ?? []); final notes = item['nota'] ?? item['notes']; return Scaffold( backgroundColor: AppColors.background, body: CustomScrollView( slivers: [ SliverAppBar( expandedHeight: 320, pinned: true, backgroundColor: cat.color, iconTheme: const IconThemeData(color: Colors.white), actions: [ IconButton( icon: const Icon(Icons.edit_rounded, color: Colors.white), onPressed: () { Navigator.push( context, MaterialPageRoute( builder: (_) => EditItemScreen(item: item), ), ).then((_) { if (context.mounted) Navigator.pop(context); }); }, ), ], flexibleSpace: FlexibleSpaceBar( background: Stack( fit: StackFit.expand, children: [ Container(color: cat.color.withValues(alpha: 0.25)), if (imageUrl != null) Image.network( imageUrl!, fit: BoxFit.cover, errorBuilder: (_, __, ___) => Center( child: Icon(cat.icon, color: cat.color, size: 80), ), ) else Center(child: Icon(cat.icon, color: cat.color, size: 80)), Container( decoration: BoxDecoration( gradient: LinearGradient( begin: Alignment.topCenter, end: Alignment.bottomCenter, colors: [ Colors.black.withValues(alpha: 0.4), Colors.transparent, Colors.black.withValues(alpha: 0.2), ], ), ), ), ], ), ), ), SliverToBoxAdapter( child: Padding( padding: const EdgeInsets.all(20), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text(item['nome'] ?? '', style: AppText.h1), const SizedBox(height: 12), Container( padding: const EdgeInsets.symmetric( horizontal: 12, vertical: 6, ), decoration: BoxDecoration( color: cat.color.withValues(alpha: 0.12), borderRadius: BorderRadius.circular(AppRadius.pill), ), child: Row( mainAxisSize: MainAxisSize.min, children: [ Icon(cat.icon, size: 16, color: cat.color), const SizedBox(width: 6), Text( cat.name, style: TextStyle( color: cat.color, fontWeight: FontWeight.w600, ), ), ], ), ), const SizedBox(height: 24), if (tags.isNotEmpty) ...[ const Text('Tags', style: AppText.label), const SizedBox(height: 8), Wrap( spacing: 8, runSpacing: 8, children: tags .map( (t) => Container( padding: const EdgeInsets.symmetric( horizontal: 12, vertical: 6, ), decoration: BoxDecoration( color: AppColors.surface, borderRadius: BorderRadius.circular( AppRadius.pill, ), border: Border.all(color: AppColors.border), ), child: Text( t, style: const TextStyle( color: AppColors.textPrimary, fontWeight: FontWeight.w500, ), ), ), ) .toList(), ), ], const SizedBox(height: 24), if (notes != null && notes.toString().isNotEmpty) ...[ const Text('Notas', style: AppText.label), const SizedBox(height: 8), Container( width: double.infinity, padding: const EdgeInsets.all(16), decoration: BoxDecoration( color: AppColors.surface, borderRadius: BorderRadius.circular(AppRadius.lg), border: Border.all(color: AppColors.border), ), child: Text( notes.toString(), style: AppText.body.copyWith(height: 1.4), ), ), ], ], ), ), ), ], ), ); } } // ============================================================ // Editar item // ============================================================ class EditItemScreen extends StatefulWidget { final Map item; const EditItemScreen({super.key, required this.item}); @override State createState() => _EditItemScreenState(); } class _EditItemScreenState extends State { late TextEditingController _nameController; late TextEditingController _notesController; ItemCategory? _selectedCategory; final Set _selectedTags = {}; final _imagePicker = ImagePicker(); XFile? _pickedImage; String? _currentImageUrl; bool _isLoading = false; @override void initState() { super.initState(); _nameController = TextEditingController(text: widget.item['nome'] ?? ''); _notesController = TextEditingController( text: widget.item['nota'] ?? widget.item['notes'] ?? '', ); final catId = widget.item['categoria'] as String?; if (catId != null) { _selectedCategory = categoryById(catId); } _selectedTags.addAll(List.from(widget.item['tags'] ?? [])); final images = widget.item['item_images'] as List?; if (images != null && images.isNotEmpty) { _currentImageUrl = images.first['image_url'] as String?; } } @override void dispose() { _nameController.dispose(); _notesController.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 _showImageOptions() { showModalBottomSheet( context: context, shape: RoundedRectangleBorder( borderRadius: BorderRadius.vertical(top: Radius.circular(AppRadius.xl)), ), builder: (ctx) => SafeArea( child: Padding( padding: const EdgeInsets.symmetric(vertical: 20), child: Column( mainAxisSize: MainAxisSize.min, children: [ Text('Alterar foto', style: AppText.h3), const SizedBox(height: 20), ListTile( leading: const Icon(Icons.photo_library_outlined), title: const Text('Galeria'), onTap: () { Navigator.pop(ctx); _pickImage(); }, ), ListTile( leading: const Icon(Icons.camera_alt_outlined), title: const Text('Câmara'), onTap: () { Navigator.pop(ctx); _takePhoto(); }, ), if (_currentImageUrl != null || _pickedImage != null) ListTile( leading: const Icon( Icons.delete_outline, color: AppColors.error, ), title: Text( 'Remover foto', style: TextStyle(color: AppColors.error), ), onTap: () { Navigator.pop(ctx); setState(() { _pickedImage = null; _currentImageUrl = null; }); }, ), ], ), ), ), ); } Future _save() async { if (_nameController.text.trim().isEmpty) { AppSnack.error(context, 'Nome não pode ser vazio'); return; } setState(() => _isLoading = true); try { await Supabase.instance.client .from('items') .update({ 'nome': _nameController.text.trim(), 'categoria': _selectedCategory?.id, 'tags': _selectedTags.toList(), 'nota': _notesController.text.trim().isEmpty ? null : _notesController.text.trim(), }) .eq('id', widget.item['id']); if (_pickedImage != null) { final file = File(_pickedImage!.path); final fileName = 'item_${widget.item['id']}_${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': widget.item['id'], 'image_url': imageUrl, }); } if (mounted) { AppSnack.success(context, 'Item atualizado!'); Navigator.pop(context); } } catch (e) { if (mounted) AppSnack.error(context, 'Erro: $e'); } finally { if (mounted) setState(() => _isLoading = false); } } @override Widget build(BuildContext context) { return Scaffold( backgroundColor: AppColors.background, appBar: AppBar( backgroundColor: AppColors.background, elevation: 0, title: const Text('Editar Item', style: AppText.h3), iconTheme: const IconThemeData(color: AppColors.textPrimary), ), body: SingleChildScrollView( padding: const EdgeInsets.all(20), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // Foto do item Center( child: GestureDetector( onTap: _showImageOptions, child: Container( width: 140, height: 140, decoration: BoxDecoration( color: AppColors.surface, borderRadius: BorderRadius.circular(AppRadius.lg), border: Border.all(color: AppColors.border), image: _pickedImage != null ? DecorationImage( image: FileImage(File(_pickedImage!.path)), fit: BoxFit.cover, ) : _currentImageUrl != null ? DecorationImage( image: NetworkImage(_currentImageUrl!), fit: BoxFit.cover, ) : null, ), child: _pickedImage == null && _currentImageUrl == null ? Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon( Icons.add_photo_alternate_outlined, size: 40, color: AppColors.textSecondary, ), const SizedBox(height: 8), Text( 'Adicionar foto', style: AppText.caption.copyWith( color: AppColors.textSecondary, ), ), ], ) : Stack( alignment: Alignment.bottomRight, children: [ Positioned( bottom: 8, right: 8, child: Container( padding: const EdgeInsets.all(8), decoration: BoxDecoration( color: Colors.black54, borderRadius: BorderRadius.circular( AppRadius.md, ), ), child: const Icon( Icons.edit, color: Colors.white, size: 18, ), ), ), ], ), ), ), ), const SizedBox(height: 12), Center( child: TextButton.icon( onPressed: _showImageOptions, icon: const Icon(Icons.camera_alt_outlined, size: 18), label: const Text('Alterar foto'), style: TextButton.styleFrom(foregroundColor: AppColors.primary), ), ), const SizedBox(height: 24), const Text('Nome', style: AppText.label), const SizedBox(height: 8), Container( decoration: AppDecorations.outlined(), child: TextField( controller: _nameController, style: AppText.body, decoration: const InputDecoration( border: InputBorder.none, contentPadding: EdgeInsets.symmetric( horizontal: 16, vertical: 14, ), hintText: 'Nome do item', ), ), ), const SizedBox(height: 20), const Text('Notas / Descrição', style: AppText.label), const SizedBox(height: 8), Container( decoration: AppDecorations.outlined(), child: TextField( controller: _notesController, style: AppText.body, maxLines: 3, decoration: const InputDecoration( border: InputBorder.none, contentPadding: EdgeInsets.symmetric( horizontal: 16, vertical: 14, ), hintText: 'Detalhes, tamanho, cor, localização...', ), ), ), const SizedBox(height: 20), const Text('Categoria', style: AppText.label), const SizedBox(height: 8), Wrap( spacing: 8, runSpacing: 8, children: itemCategories .map( (c) => AppChip( label: c.name, icon: c.icon, color: c.color, selected: _selectedCategory?.id == c.id, onTap: () => setState(() => _selectedCategory = c), ), ) .toList(), ), const SizedBox(height: 20), 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 alterações', icon: Icons.check_rounded, loading: _isLoading, onPressed: _save, ), ], ), ), ); } }