Camara e chat IA
This commit is contained in:
@@ -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}"
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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(
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|
||||||
|
|||||||
@@ -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,
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -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',
|
||||||
|
|||||||
16
pubspec.lock
16
pubspec.lock
@@ -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:
|
||||||
|
|||||||
Reference in New Issue
Block a user