Camara e chat IA

This commit is contained in:
2026-05-17 17:05:01 +01:00
parent e8fa39c594
commit 5158d140ca
9 changed files with 196 additions and 121 deletions

View File

@@ -1,4 +1,8 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"> <manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" android:maxSdkVersion="32" />
<application <application
android:label="daymaker_lp" android:label="daymaker_lp"
android:name="${applicationName}" android:name="${applicationName}"

View File

@@ -24,6 +24,10 @@
<string>$(FLUTTER_BUILD_NUMBER)</string> <string>$(FLUTTER_BUILD_NUMBER)</string>
<key>LSRequiresIPhoneOS</key> <key>LSRequiresIPhoneOS</key>
<true/> <true/>
<key>NSCameraUsageDescription</key>
<string>O DayMaker precisa da câmara para tirares fotos dos itens que queres registar.</string>
<key>NSPhotoLibraryUsageDescription</key>
<string>O DayMaker precisa da galeria para escolheres fotos dos itens que queres registar.</string>
<key>UILaunchStoryboardName</key> <key>UILaunchStoryboardName</key>
<string>LaunchScreen</string> <string>LaunchScreen</string>
<key>UIMainStoryboardFile</key> <key>UIMainStoryboardFile</key>

View File

@@ -22,10 +22,10 @@ class _AddItemScreenState extends State<AddItemScreen> {
bool _isLoading = false; bool _isLoading = false;
Future<void> _pickImage() async { Future<void> _pickImage(ImageSource source) async {
final picker = ImagePicker(); final picker = ImagePicker();
final image = await picker.pickImage( final image = await picker.pickImage(
source: ImageSource.gallery, source: source,
maxWidth: 1024, maxWidth: 1024,
imageQuality: 80, imageQuality: 80,
); );
@@ -34,6 +34,46 @@ class _AddItemScreenState extends State<AddItemScreen> {
} }
} }
Future<void> _showImageSourcePicker() async {
final source = await showModalBottomSheet<ImageSource>(
context: context,
backgroundColor: Colors.white,
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.vertical(top: Radius.circular(20)),
),
builder: (context) => SafeArea(
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 12),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
ListTile(
leading: const Icon(
Icons.photo_camera,
color: Color(0xFF0066CC),
),
title: const Text('Tirar foto'),
onTap: () => Navigator.pop(context, ImageSource.camera),
),
ListTile(
leading: const Icon(
Icons.photo_library,
color: Color(0xFF0066CC),
),
title: const Text('Escolher da galeria'),
onTap: () => Navigator.pop(context, ImageSource.gallery),
),
],
),
),
),
);
if (source != null) {
await _pickImage(source);
}
}
@override @override
void dispose() { void dispose() {
_nameController.dispose(); _nameController.dispose();
@@ -185,7 +225,7 @@ class _AddItemScreenState extends State<AddItemScreen> {
// Image picker // Image picker
Center( Center(
child: GestureDetector( child: GestureDetector(
onTap: _pickImage, onTap: _showImageSourcePicker,
child: Container( child: Container(
width: 140, width: 140,
height: 140, height: 140,
@@ -297,7 +337,7 @@ class _AddItemScreenState extends State<AddItemScreen> {
value: _selectedCategory, value: _selectedCategory,
hint: const Text('Selecione uma categoria'), hint: const Text('Selecione uma categoria'),
isExpanded: true, isExpanded: true,
items: ITEM_CATEGORIES.map((category) { items: itemCategories.map((category) {
return DropdownMenuItem<ItemCategory>( return DropdownMenuItem<ItemCategory>(
value: category, value: category,
child: Row( child: Row(
@@ -407,13 +447,13 @@ class _AddItemScreenState extends State<AddItemScreen> {
Wrap( Wrap(
spacing: 8, spacing: 8,
runSpacing: 8, runSpacing: 8,
children: CONTEXT_TAGS.map((tag) { children: contextTags.map((tag) {
final isSelected = _selectedTags.contains(tag.id); final isSelected = _selectedTags.contains(tag.id);
return FilterChip( return FilterChip(
label: Text(tag.name), label: Text(tag.name),
selected: isSelected, selected: isSelected,
onSelected: (selected) => _toggleTag(tag.id), onSelected: (selected) => _toggleTag(tag.id),
selectedColor: const Color(0xFF0066CC).withOpacity(0.2), selectedColor: const Color(0xFF0066CC).withValues(alpha: 0.2),
checkmarkColor: const Color(0xFF0066CC), checkmarkColor: const Color(0xFF0066CC),
backgroundColor: Colors.white, backgroundColor: Colors.white,
labelStyle: TextStyle( labelStyle: TextStyle(

View File

@@ -1,9 +1,10 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:supabase_flutter/supabase_flutter.dart'; import 'package:supabase_flutter/supabase_flutter.dart';
import 'perfil_screen.dart'; import 'perfil_screen.dart';
import 'add_item_screen.dart'; import 'add_item_screen.dart';
import 'item_screen.dart'; import 'item_screen.dart';
import 'week_screen.dart'; import 'week_screen.dart';
import 'ai_chat_screen.dart';
import '../constants/item_categories.dart'; import '../constants/item_categories.dart';
class HomeScreen extends StatefulWidget { class HomeScreen extends StatefulWidget {
@@ -20,6 +21,7 @@ class _HomeScreenState extends State<HomeScreen> {
const _HomeContent(), const _HomeContent(),
const _ItemsScreen(), const _ItemsScreen(),
const _WeekScreen(), const _WeekScreen(),
const _AiScreen(),
const _ProfileScreen(), const _ProfileScreen(),
]; ];
@@ -36,7 +38,10 @@ class _HomeScreenState extends State<HomeScreen> {
backgroundColor: Colors.white, backgroundColor: Colors.white,
type: BottomNavigationBarType.fixed, type: BottomNavigationBarType.fixed,
items: const [ items: const [
BottomNavigationBarItem(icon: Icon(Icons.home), label: 'Início'), BottomNavigationBarItem(
icon: Icon(Icons.home),
label: 'Início',
),
BottomNavigationBarItem( BottomNavigationBarItem(
icon: Icon(Icons.inventory_2_outlined), icon: Icon(Icons.inventory_2_outlined),
label: 'Itens', label: 'Itens',
@@ -45,6 +50,10 @@ class _HomeScreenState extends State<HomeScreen> {
icon: Icon(Icons.calendar_today_outlined), icon: Icon(Icons.calendar_today_outlined),
label: 'Semana', label: 'Semana',
), ),
BottomNavigationBarItem(
icon: Icon(Icons.auto_awesome_outlined),
label: 'IA',
),
BottomNavigationBarItem( BottomNavigationBarItem(
icon: Icon(Icons.person_outline), icon: Icon(Icons.person_outline),
label: 'Perfil', label: 'Perfil',
@@ -136,7 +145,7 @@ class _HomeContentState extends State<_HomeContent> {
_isLoading = false; _isLoading = false;
}); });
} catch (e) { } catch (e) {
print('Error loading home: $e'); debugPrint('Error loading home: $e');
setState(() => _isLoading = false); setState(() => _isLoading = false);
} }
} }
@@ -315,9 +324,9 @@ class _HomeContentState extends State<_HomeContent> {
final imageUrl = (images != null && images.isNotEmpty) final imageUrl = (images != null && images.isNotEmpty)
? images.first['image_url'] ? images.first['image_url']
: null; : null;
final category = ITEM_CATEGORIES.firstWhere( final category = itemCategories.firstWhere(
(c) => c.id == item['categoria'], (c) => c.id == item['categoria'],
orElse: () => ITEM_CATEGORIES.last, orElse: () => itemCategories.last,
); );
return Container( return Container(
width: 80, width: 80,
@@ -332,7 +341,8 @@ class _HomeContentState extends State<_HomeContent> {
width: 60, width: 60,
height: 60, height: 60,
fit: BoxFit.cover, fit: BoxFit.cover,
errorBuilder: (_, __, ___) => _placeholder(category.icon), errorBuilder: (context, error, stackTrace) =>
_placeholder(category.icon),
) )
: _placeholder(category.icon), : _placeholder(category.icon),
), ),
@@ -352,7 +362,7 @@ class _HomeContentState extends State<_HomeContent> {
return Container( return Container(
width: 60, width: 60,
height: 60, height: 60,
color: const Color(0xFF0066CC).withOpacity(0.1), color: const Color(0xFF0066CC).withValues(alpha: 0.1),
alignment: Alignment.center, alignment: Alignment.center,
child: Text(icon, style: const TextStyle(fontSize: 26)), child: Text(icon, style: const TextStyle(fontSize: 26)),
); );
@@ -391,9 +401,9 @@ class _HomeContentState extends State<_HomeContent> {
final imageUrl = (images != null && images.isNotEmpty) final imageUrl = (images != null && images.isNotEmpty)
? images.first['image_url'] ? images.first['image_url']
: null; : null;
final category = ITEM_CATEGORIES.firstWhere( final category = itemCategories.firstWhere(
(c) => c.id == item['categoria'], (c) => c.id == item['categoria'],
orElse: () => ITEM_CATEGORIES.last, orElse: () => itemCategories.last,
); );
return Container( return Container(
width: 110, width: 110,
@@ -414,13 +424,13 @@ class _HomeContentState extends State<_HomeContent> {
width: double.infinity, width: double.infinity,
height: 70, height: 70,
fit: BoxFit.cover, fit: BoxFit.cover,
errorBuilder: (_, __, ___) => errorBuilder: (context, error, stackTrace) =>
_placeholder(category.icon), _placeholder(category.icon),
) )
: Container( : Container(
width: double.infinity, width: double.infinity,
height: 70, height: 70,
color: const Color(0xFF0066CC).withOpacity(0.1), color: const Color(0xFF0066CC).withValues(alpha: 0.1),
alignment: Alignment.center, alignment: Alignment.center,
child: Text( child: Text(
category.icon, category.icon,
@@ -468,6 +478,13 @@ class _WeekScreen extends StatelessWidget {
Widget build(BuildContext context) => const WeekScreen(); Widget build(BuildContext context) => const WeekScreen();
} }
class _AiScreen extends StatelessWidget {
const _AiScreen();
@override
Widget build(BuildContext context) => const AiChatScreen();
}
class _ProfileScreen extends StatelessWidget { class _ProfileScreen extends StatelessWidget {
const _ProfileScreen(); const _ProfileScreen();

View File

@@ -56,7 +56,7 @@ class _ItemScreenState extends State<ItemScreen> {
_isLoading = false; _isLoading = false;
}); });
} catch (e) { } catch (e) {
print('Error loading items: $e'); debugPrint('Error loading items: $e');
setState(() => _isLoading = false); setState(() => _isLoading = false);
} }
} }
@@ -71,15 +71,15 @@ class _ItemScreenState extends State<ItemScreen> {
String _categoryName(String? id) { String _categoryName(String? id) {
if (id == null) return 'Outros'; if (id == null) return 'Outros';
return ITEM_CATEGORIES return itemCategories
.firstWhere((c) => c.id == id, orElse: () => ITEM_CATEGORIES.last) .firstWhere((c) => c.id == id, orElse: () => itemCategories.last)
.name; .name;
} }
String _categoryIcon(String? id) { String _categoryIcon(String? id) {
if (id == null) return '📦'; if (id == null) return '📦';
return ITEM_CATEGORIES return itemCategories
.firstWhere((c) => c.id == id, orElse: () => ITEM_CATEGORIES.last) .firstWhere((c) => c.id == id, orElse: () => itemCategories.last)
.icon; .icon;
} }
@@ -231,7 +231,7 @@ class _ItemScreenState extends State<ItemScreen> {
scrollDirection: Axis.horizontal, scrollDirection: Axis.horizontal,
children: [ children: [
_categoryChip(null, 'Todos', '🗂'), _categoryChip(null, 'Todos', '🗂'),
...ITEM_CATEGORIES.map( ...itemCategories.map(
(c) => _categoryChip(c.id, c.name, c.icon), (c) => _categoryChip(c.id, c.name, c.icon),
), ),
], ],
@@ -324,7 +324,7 @@ class _ItemScreenState extends State<ItemScreen> {
width: 72, width: 72,
height: 72, height: 72,
fit: BoxFit.cover, fit: BoxFit.cover,
errorBuilder: (_, __, ___) => errorBuilder: (context, error, stackTrace) =>
_iconPlaceholder(categoryIcon), _iconPlaceholder(categoryIcon),
) )
: _iconPlaceholder(categoryIcon), : _iconPlaceholder(categoryIcon),
@@ -362,7 +362,9 @@ class _ItemScreenState extends State<ItemScreen> {
vertical: 2, vertical: 2,
), ),
decoration: BoxDecoration( decoration: BoxDecoration(
color: const Color(0xFF0066CC).withOpacity(0.1), color: const Color(
0xFF0066CC,
).withValues(alpha: 0.1),
borderRadius: BorderRadius.circular(6), borderRadius: BorderRadius.circular(6),
), ),
child: Text( child: Text(
@@ -430,7 +432,7 @@ class _ItemScreenState extends State<ItemScreen> {
return Container( return Container(
width: 72, width: 72,
height: 72, height: 72,
color: const Color(0xFF0066CC).withOpacity(0.1), color: const Color(0xFF0066CC).withValues(alpha: 0.1),
alignment: Alignment.center, alignment: Alignment.center,
child: Text(icon, style: const TextStyle(fontSize: 32)), child: Text(icon, style: const TextStyle(fontSize: 32)),
); );
@@ -451,10 +453,10 @@ class ItemDetailScreen extends StatelessWidget {
final categoryId = item['categoria'] as String?; final categoryId = item['categoria'] as String?;
final categoryName = categoryId == null final categoryName = categoryId == null
? 'Outros' ? 'Outros'
: ITEM_CATEGORIES : itemCategories
.firstWhere( .firstWhere(
(c) => c.id == categoryId, (c) => c.id == categoryId,
orElse: () => ITEM_CATEGORIES.last, orElse: () => itemCategories.last,
) )
.name; .name;
final tags = List<String>.from(item['tags'] ?? []); final tags = List<String>.from(item['tags'] ?? []);
@@ -482,7 +484,8 @@ class ItemDetailScreen extends StatelessWidget {
width: double.infinity, width: double.infinity,
height: 250, height: 250,
fit: BoxFit.cover, fit: BoxFit.cover,
errorBuilder: (_, __, ___) => const SizedBox.shrink(), errorBuilder: (context, error, stackTrace) =>
const SizedBox.shrink(),
), ),
), ),
const SizedBox(height: 20), const SizedBox(height: 20),
@@ -504,7 +507,7 @@ class ItemDetailScreen extends StatelessWidget {
label: Text(t), label: Text(t),
backgroundColor: const Color( backgroundColor: const Color(
0xFF0066CC, 0xFF0066CC,
).withOpacity(0.1), ).withValues(alpha: 0.1),
labelStyle: const TextStyle(color: Color(0xFF0066CC)), labelStyle: const TextStyle(color: Color(0xFF0066CC)),
), ),
) )
@@ -561,9 +564,9 @@ class _EditItemScreenState extends State<EditItemScreen> {
_nameController = TextEditingController(text: widget.item['nome'] ?? ''); _nameController = TextEditingController(text: widget.item['nome'] ?? '');
final catId = widget.item['categoria'] as String?; final catId = widget.item['categoria'] as String?;
if (catId != null) { if (catId != null) {
_selectedCategory = ITEM_CATEGORIES.firstWhere( _selectedCategory = itemCategories.firstWhere(
(c) => c.id == catId, (c) => c.id == catId,
orElse: () => ITEM_CATEGORIES.last, orElse: () => itemCategories.last,
); );
} }
final tags = List<String>.from(widget.item['tags'] ?? []); final tags = List<String>.from(widget.item['tags'] ?? []);
@@ -653,7 +656,7 @@ class _EditItemScreenState extends State<EditItemScreen> {
child: DropdownButton<ItemCategory>( child: DropdownButton<ItemCategory>(
value: _selectedCategory, value: _selectedCategory,
isExpanded: true, isExpanded: true,
items: ITEM_CATEGORIES items: itemCategories
.map( .map(
(c) => DropdownMenuItem( (c) => DropdownMenuItem(
value: c, value: c,
@@ -674,7 +677,7 @@ class _EditItemScreenState extends State<EditItemScreen> {
Wrap( Wrap(
spacing: 8, spacing: 8,
runSpacing: 8, runSpacing: 8,
children: CONTEXT_TAGS.map((tag) { children: contextTags.map((tag) {
final selected = _selectedTags.contains(tag.id); final selected = _selectedTags.contains(tag.id);
return FilterChip( return FilterChip(
label: Text(tag.name), label: Text(tag.name),
@@ -688,7 +691,7 @@ class _EditItemScreenState extends State<EditItemScreen> {
} }
}); });
}, },
selectedColor: const Color(0xFF0066CC).withOpacity(0.2), selectedColor: const Color(0xFF0066CC).withValues(alpha: 0.2),
checkmarkColor: const Color(0xFF0066CC), checkmarkColor: const Color(0xFF0066CC),
backgroundColor: Colors.white, backgroundColor: Colors.white,
); );

View File

@@ -45,7 +45,7 @@ class _PerfilScreenState extends State<PerfilScreen> {
}); });
} }
} catch (e) { } catch (e) {
print('Error loading user data: $e'); debugPrint('Error loading user data: $e');
} finally { } finally {
setState(() => _isLoading = false); setState(() => _isLoading = false);
} }

View File

@@ -42,8 +42,11 @@ class _WeekScreenState extends State<WeekScreen> {
DateTime get _startOfWeek { DateTime get _startOfWeek {
final now = DateTime.now(); final now = DateTime.now();
return DateTime(now.year, now.month, now.day) return DateTime(
.subtract(Duration(days: now.weekday - 1)); now.year,
now.month,
now.day,
).subtract(Duration(days: now.weekday - 1));
} }
String _dateKey(DateTime d) => String _dateKey(DateTime d) =>
@@ -107,7 +110,7 @@ class _WeekScreenState extends State<WeekScreen> {
_isLoading = false; _isLoading = false;
}); });
} catch (e) { } catch (e) {
print('Error loading day items: $e'); debugPrint('Error loading day items: $e');
setState(() => _isLoading = false); setState(() => _isLoading = false);
} }
} }
@@ -123,8 +126,9 @@ class _WeekScreenState extends State<WeekScreen> {
final available = List<Map<String, dynamic>>.from(allItems); final available = List<Map<String, dynamic>>.from(allItems);
final existingIds = _dayItems.map((i) => i['id']).toSet(); final existingIds = _dayItems.map((i) => i['id']).toSet();
final toShow = final toShow = available
available.where((i) => !existingIds.contains(i['id'])).toList(); .where((i) => !existingIds.contains(i['id']))
.toList();
if (!mounted) return; if (!mounted) return;
final selected = await showModalBottomSheet<List<int>>( final selected = await showModalBottomSheet<List<int>>(
@@ -193,10 +197,12 @@ class _WeekScreenState extends State<WeekScreen> {
padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 8), padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 8),
child: Row( child: Row(
children: days.map((day) { children: days.map((day) {
final isSelected = day.year == _selectedDay.year && final isSelected =
day.year == _selectedDay.year &&
day.month == _selectedDay.month && day.month == _selectedDay.month &&
day.day == _selectedDay.day; day.day == _selectedDay.day;
final isToday = day.year == DateTime.now().year && final isToday =
day.year == DateTime.now().year &&
day.month == DateTime.now().month && day.month == DateTime.now().month &&
day.day == DateTime.now().day; day.day == DateTime.now().day;
return Expanded( return Expanded(
@@ -280,39 +286,39 @@ class _WeekScreenState extends State<WeekScreen> {
child: _isLoading child: _isLoading
? const Center(child: CircularProgressIndicator()) ? const Center(child: CircularProgressIndicator())
: _dayItems.isEmpty : _dayItems.isEmpty
? Center( ? Center(
child: Column( child: Column(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
Icon( Icon(
Icons.calendar_today_outlined, Icons.calendar_today_outlined,
size: 56, size: 56,
color: Colors.grey[400], color: Colors.grey[400],
),
const SizedBox(height: 12),
const Text(
'Nenhum item para este dia',
style: TextStyle(
color: Color(0xFF666666),
fontSize: 16,
),
),
const SizedBox(height: 4),
const Text(
'Toque em + para adicionar',
style: TextStyle(
color: Color(0xFF999999),
fontSize: 13,
),
),
],
), ),
) const SizedBox(height: 12),
: ListView.builder( const Text(
padding: const EdgeInsets.symmetric(horizontal: 16), 'Nenhum item para este dia',
itemCount: _dayItems.length, style: TextStyle(
itemBuilder: (_, i) => _buildItemTile(_dayItems[i]), color: Color(0xFF666666),
), fontSize: 16,
),
),
const SizedBox(height: 4),
const Text(
'Toque em + para adicionar',
style: TextStyle(
color: Color(0xFF999999),
fontSize: 13,
),
),
],
),
)
: ListView.builder(
padding: const EdgeInsets.symmetric(horizontal: 16),
itemCount: _dayItems.length,
itemBuilder: (_, i) => _buildItemTile(_dayItems[i]),
),
), ),
], ],
), ),
@@ -326,11 +332,12 @@ class _WeekScreenState extends State<WeekScreen> {
Widget _buildItemTile(Map<String, dynamic> item) { Widget _buildItemTile(Map<String, dynamic> item) {
final images = item['item_images'] as List?; final images = item['item_images'] as List?;
final imageUrl = final imageUrl = (images != null && images.isNotEmpty)
(images != null && images.isNotEmpty) ? images.first['image_url'] : null; ? images.first['image_url']
final category = ITEM_CATEGORIES.firstWhere( : null;
final category = itemCategories.firstWhere(
(c) => c.id == item['categoria'], (c) => c.id == item['categoria'],
orElse: () => ITEM_CATEGORIES.last, orElse: () => itemCategories.last,
); );
return Card( return Card(
@@ -346,7 +353,8 @@ class _WeekScreenState extends State<WeekScreen> {
width: 56, width: 56,
height: 56, height: 56,
fit: BoxFit.cover, fit: BoxFit.cover,
errorBuilder: (_, __, ___) => _icon(category.icon), errorBuilder: (context, error, stackTrace) =>
_icon(category.icon),
) )
: _icon(category.icon), : _icon(category.icon),
), ),
@@ -367,7 +375,7 @@ class _WeekScreenState extends State<WeekScreen> {
return Container( return Container(
width: 56, width: 56,
height: 56, height: 56,
color: const Color(0xFF0066CC).withOpacity(0.1), color: const Color(0xFF0066CC).withValues(alpha: 0.1),
alignment: Alignment.center, alignment: Alignment.center,
child: Text(icon, style: const TextStyle(fontSize: 26)), child: Text(icon, style: const TextStyle(fontSize: 26)),
); );
@@ -393,10 +401,9 @@ class _ItemPickerSheetState extends State<_ItemPickerSheet> {
Widget build(BuildContext context) { Widget build(BuildContext context) {
final filtered = widget.items final filtered = widget.items
.where( .where(
(i) => (i['nome'] ?? '') (i) => (i['nome'] ?? '').toString().toLowerCase().contains(
.toString() _query.toLowerCase(),
.toLowerCase() ),
.contains(_query.toLowerCase()),
) )
.toList(); .toList();
@@ -457,13 +464,12 @@ class _ItemPickerSheetState extends State<_ItemPickerSheet> {
final id = item['id'] as int; final id = item['id'] as int;
final selected = _selected.contains(id); final selected = _selected.contains(id);
final images = item['item_images'] as List?; final images = item['item_images'] as List?;
final imageUrl = final imageUrl = (images != null && images.isNotEmpty)
(images != null && images.isNotEmpty) ? images.first['image_url']
? images.first['image_url'] : null;
: null; final category = itemCategories.firstWhere(
final category = ITEM_CATEGORIES.firstWhere(
(c) => c.id == item['categoria'], (c) => c.id == item['categoria'],
orElse: () => ITEM_CATEGORIES.last, orElse: () => itemCategories.last,
); );
return CheckboxListTile( return CheckboxListTile(
value: selected, value: selected,
@@ -487,20 +493,24 @@ class _ItemPickerSheetState extends State<_ItemPickerSheet> {
width: 48, width: 48,
height: 48, height: 48,
fit: BoxFit.cover, fit: BoxFit.cover,
errorBuilder: (_, __, ___) => Container( errorBuilder:
width: 48, (context, error, stackTrace) =>
height: 48, Container(
color: const Color(0xFF0066CC) width: 48,
.withOpacity(0.1), height: 48,
alignment: Alignment.center, color: const Color(
child: Text(category.icon), 0xFF0066CC,
), ).withValues(alpha: 0.1),
alignment: Alignment.center,
child: Text(category.icon),
),
) )
: Container( : Container(
width: 48, width: 48,
height: 48, height: 48,
color: const Color(0xFF0066CC) color: const Color(
.withOpacity(0.1), 0xFF0066CC,
).withValues(alpha: 0.1),
alignment: Alignment.center, alignment: Alignment.center,
child: Text(category.icon), child: Text(category.icon),
), ),
@@ -527,10 +537,7 @@ class _ItemPickerSheetState extends State<_ItemPickerSheet> {
), ),
child: Text( child: Text(
'Adicionar (${_selected.length})', 'Adicionar (${_selected.length})',
style: const TextStyle( style: const TextStyle(color: Colors.white, fontSize: 16),
color: Colors.white,
fontSize: 16,
),
), ),
), ),
), ),

View File

@@ -37,11 +37,11 @@ class ContextTag {
} }
// Categorias principais // Categorias principais
final List<ItemCategory> ITEM_CATEGORIES = [ final List<ItemCategory> itemCategories = [
ItemCategory( ItemCategory(
id: 'clothing', id: 'clothing',
name: 'Roupa', name: 'Roupa',
icon: '', icon: '👕',
description: 'Peças de vestuário', description: 'Peças de vestuário',
subcategories: [ subcategories: [
Subcategory( Subcategory(
@@ -79,7 +79,7 @@ final List<ItemCategory> ITEM_CATEGORIES = [
ItemCategory( ItemCategory(
id: 'electronics', id: 'electronics',
name: 'Eletrónica', name: 'Eletrónica',
icon: '', icon: '💻',
description: 'Dispositivos e acessórios tecnológicos', description: 'Dispositivos e acessórios tecnológicos',
subcategories: [ subcategories: [
Subcategory( Subcategory(
@@ -117,7 +117,7 @@ final List<ItemCategory> ITEM_CATEGORIES = [
ItemCategory( ItemCategory(
id: 'footwear', id: 'footwear',
name: 'Calçado', name: 'Calçado',
icon: '', icon: '👟',
description: 'Sapatos, botas, sandálias', description: 'Sapatos, botas, sandálias',
subcategories: [ subcategories: [
Subcategory( Subcategory(
@@ -145,7 +145,7 @@ final List<ItemCategory> ITEM_CATEGORIES = [
ItemCategory( ItemCategory(
id: 'accessories', id: 'accessories',
name: 'Acessórios', name: 'Acessórios',
icon: '', icon: '🎒',
description: 'Bolsas, relógios, óculos, bijuteria', description: 'Bolsas, relógios, óculos, bijuteria',
subcategories: [ subcategories: [
Subcategory( Subcategory(
@@ -175,7 +175,7 @@ final List<ItemCategory> ITEM_CATEGORIES = [
ItemCategory( ItemCategory(
id: 'documents', id: 'documents',
name: 'Documentos', name: 'Documentos',
icon: '', icon: '📄',
description: 'Passaporte, cartões, papéis importantes', description: 'Passaporte, cartões, papéis importantes',
subcategories: [ subcategories: [
Subcategory( Subcategory(
@@ -203,14 +203,14 @@ final List<ItemCategory> ITEM_CATEGORIES = [
ItemCategory( ItemCategory(
id: 'other', id: 'other',
name: 'Outros', name: 'Outros',
icon: '', icon: '📦',
description: 'Tudo o resto', description: 'Tudo o resto',
subcategories: [], subcategories: [],
), ),
]; ];
// Tags de contexto // Tags de contexto
final List<ContextTag> CONTEXT_TAGS = [ final List<ContextTag> contextTags = [
ContextTag( ContextTag(
id: 'travel', id: 'travel',
name: 'Viagem', name: 'Viagem',

View File

@@ -61,10 +61,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: characters name: characters
sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803 sha256: faf38497bda5ead2a8c7615f4f7939df04333478bf32e4173fcb06d428b5716b
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.4.0" version: "1.4.1"
clock: clock:
dependency: transitive dependency: transitive
description: description:
@@ -404,18 +404,18 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: matcher name: matcher
sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2 sha256: dc0b7dc7651697ea4ff3e69ef44b0407ea32c487a39fff6a4004fa585e901861
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.12.17" version: "0.12.19"
material_color_utilities: material_color_utilities:
dependency: transitive dependency: transitive
description: description:
name: material_color_utilities name: material_color_utilities
sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec sha256: "9c337007e82b1889149c82ed242ed1cb24a66044e30979c44912381e9be4c48b"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.11.1" version: "0.13.0"
meta: meta:
dependency: transitive dependency: transitive
description: description:
@@ -713,10 +713,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: test_api name: test_api
sha256: ab2726c1a94d3176a45960b6234466ec367179b87dd74f1611adb1f3b5fb9d55 sha256: "8161c84903fd860b26bfdefb7963b3f0b68fee7adea0f59ef805ecca346f0c7a"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.7.7" version: "0.7.10"
typed_data: typed_data:
dependency: transitive dependency: transitive
description: description: