Files
dayMaker_lp/lib/Screens/home_screen.dart
2026-05-15 12:39:29 +01:00

477 lines
14 KiB
Dart
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import 'package:flutter/material.dart';
import 'package:supabase_flutter/supabase_flutter.dart';
import 'perfil_screen.dart';
import 'add_item_screen.dart';
import 'item_screen.dart';
import 'week_screen.dart';
import '../constants/item_categories.dart';
class HomeScreen extends StatefulWidget {
const HomeScreen({super.key});
@override
State<HomeScreen> createState() => _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen> {
int _selectedIndex = 0;
final List<Widget> _screens = [
const _HomeContent(),
const _ItemsScreen(),
const _WeekScreen(),
const _ProfileScreen(),
];
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: const Color(0xFFFFE5CC),
body: _screens[_selectedIndex],
bottomNavigationBar: BottomNavigationBar(
currentIndex: _selectedIndex,
onTap: (index) => setState(() => _selectedIndex = index),
selectedItemColor: const Color(0xFF0066CC),
unselectedItemColor: const Color(0xFF666666),
backgroundColor: Colors.white,
type: BottomNavigationBarType.fixed,
items: const [
BottomNavigationBarItem(icon: Icon(Icons.home), label: 'Início'),
BottomNavigationBarItem(
icon: Icon(Icons.inventory_2_outlined),
label: 'Itens',
),
BottomNavigationBarItem(
icon: Icon(Icons.calendar_today_outlined),
label: 'Semana',
),
BottomNavigationBarItem(
icon: Icon(Icons.person_outline),
label: 'Perfil',
),
],
),
);
}
}
class _HomeContent extends StatefulWidget {
const _HomeContent();
@override
State<_HomeContent> createState() => _HomeContentState();
}
class _HomeContentState extends State<_HomeContent> {
int _itemCount = 0;
List<Map<String, dynamic>> _todayItems = [];
List<Map<String, dynamic>> _recentItems = [];
bool _isLoading = true;
static const _weekdayLong = [
'Segunda',
'Terça',
'Quarta',
'Quinta',
'Sexta',
'Sábado',
'Domingo',
];
@override
void initState() {
super.initState();
_loadData();
}
String _dateKey(DateTime d) =>
'${d.year.toString().padLeft(4, '0')}-'
'${d.month.toString().padLeft(2, '0')}-'
'${d.day.toString().padLeft(2, '0')}';
Future<void> _loadData() async {
setState(() => _isLoading = true);
try {
final user = Supabase.instance.client.auth.currentUser;
if (user == null) return;
// total item count
final all = await Supabase.instance.client
.from('items')
.select('id')
.eq('user_id', user.id);
// recent 5 items
final recent = await Supabase.instance.client
.from('items')
.select('*, item_images(image_url)')
.eq('user_id', user.id)
.order('id', ascending: false)
.limit(5);
// today's plan
final today = DateTime.now();
final plan = await Supabase.instance.client
.from('plans')
.select('plan_items(items(*, item_images(image_url)))')
.eq('user_id', user.id)
.eq('data', _dateKey(today))
.maybeSingle();
List<Map<String, dynamic>> todayItems = [];
if (plan != null) {
final planItems = plan['plan_items'] as List? ?? [];
todayItems = planItems
.where((pi) => pi['items'] != null)
.map<Map<String, dynamic>>(
(pi) => Map<String, dynamic>.from(pi['items']),
)
.toList();
}
setState(() {
_itemCount = all.length;
_recentItems = List<Map<String, dynamic>>.from(recent);
_todayItems = todayItems;
_isLoading = false;
});
} catch (e) {
print('Error loading home: $e');
setState(() => _isLoading = false);
}
}
@override
Widget build(BuildContext context) {
return SafeArea(
child: Stack(
children: [
Column(
children: [
// App Bar
Container(
color: const Color(0xFF0066CC),
padding: const EdgeInsets.symmetric(
horizontal: 20,
vertical: 16,
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'DayMaker',
style: TextStyle(
color: Colors.white,
fontSize: 24,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 4),
Text(
'$_itemCount itens',
style: const TextStyle(
color: Colors.white70,
fontSize: 14,
),
),
],
),
IconButton(
icon: const Icon(
Icons.notifications_outlined,
color: Colors.white,
),
onPressed: () {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Notificações'),
backgroundColor: Color(0xFF0066CC),
),
);
},
),
],
),
),
Expanded(
child: RefreshIndicator(
onRefresh: _loadData,
child: SingleChildScrollView(
physics: const AlwaysScrollableScrollPhysics(),
padding: const EdgeInsets.all(20),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_buildTodaySection(),
const SizedBox(height: 24),
const Text(
'Itens Recentes',
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
color: Color(0xFF333333),
),
),
const SizedBox(height: 12),
_buildRecentItems(),
],
),
),
),
),
],
),
// Floating Action Button
Positioned(
bottom: 80,
right: 20,
child: FloatingActionButton(
onPressed: () {
Navigator.of(context)
.push(
MaterialPageRoute(builder: (_) => const AddItemScreen()),
)
.then((_) => _loadData());
},
backgroundColor: const Color(0xFF0066CC),
child: const Icon(Icons.add, color: Colors.white),
),
),
],
),
);
}
Widget _buildTodaySection() {
final today = DateTime.now();
final dayName = _weekdayLong[today.weekday - 1];
return Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(16),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
'Hoje - $dayName',
style: const TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
color: Color(0xFF0066CC),
),
),
Text(
'${today.day}/${today.month}',
style: const TextStyle(fontSize: 14, color: Color(0xFF666666)),
),
],
),
const SizedBox(height: 6),
Text(
'${_todayItems.length} ${_todayItems.length == 1 ? "item planejado" : "itens planejados"}',
style: const TextStyle(fontSize: 14, color: Color(0xFF666666)),
),
const SizedBox(height: 12),
if (_isLoading)
const Padding(
padding: EdgeInsets.symmetric(vertical: 16),
child: Center(child: CircularProgressIndicator()),
)
else if (_todayItems.isEmpty)
Container(
padding: const EdgeInsets.symmetric(vertical: 20),
alignment: Alignment.center,
child: const Text(
'Nada planejado para hoje',
style: TextStyle(color: Color(0xFF999999)),
),
)
else
SizedBox(
height: 90,
child: ListView.builder(
scrollDirection: Axis.horizontal,
itemCount: _todayItems.length,
itemBuilder: (_, i) => _buildTodayChip(_todayItems[i]),
),
),
],
),
);
}
Widget _buildTodayChip(Map<String, dynamic> item) {
final images = item['item_images'] as List?;
final imageUrl = (images != null && images.isNotEmpty)
? images.first['image_url']
: null;
final category = ITEM_CATEGORIES.firstWhere(
(c) => c.id == item['categoria'],
orElse: () => ITEM_CATEGORIES.last,
);
return Container(
width: 80,
margin: const EdgeInsets.only(right: 10),
child: Column(
children: [
ClipRRect(
borderRadius: BorderRadius.circular(10),
child: imageUrl != null
? Image.network(
imageUrl,
width: 60,
height: 60,
fit: BoxFit.cover,
errorBuilder: (_, __, ___) => _placeholder(category.icon),
)
: _placeholder(category.icon),
),
const SizedBox(height: 4),
Text(
item['nome'] ?? '',
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: const TextStyle(fontSize: 11, color: Color(0xFF333333)),
),
],
),
);
}
Widget _placeholder(String icon) {
return Container(
width: 60,
height: 60,
color: const Color(0xFF0066CC).withOpacity(0.1),
alignment: Alignment.center,
child: Text(icon, style: const TextStyle(fontSize: 26)),
);
}
Widget _buildRecentItems() {
if (_isLoading) {
return const SizedBox(
height: 100,
child: Center(child: CircularProgressIndicator()),
);
}
if (_recentItems.isEmpty) {
return Container(
height: 100,
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(12),
),
child: const Center(
child: Text(
'Sem itens ainda',
style: TextStyle(color: Color(0xFF999999)),
),
),
);
}
return SizedBox(
height: 130,
child: ListView.builder(
scrollDirection: Axis.horizontal,
itemCount: _recentItems.length,
itemBuilder: (_, i) {
final item = _recentItems[i];
final images = item['item_images'] as List?;
final imageUrl = (images != null && images.isNotEmpty)
? images.first['image_url']
: null;
final category = ITEM_CATEGORIES.firstWhere(
(c) => c.id == item['categoria'],
orElse: () => ITEM_CATEGORIES.last,
);
return Container(
width: 110,
margin: const EdgeInsets.only(right: 10),
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(12),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
ClipRRect(
borderRadius: BorderRadius.circular(8),
child: imageUrl != null
? Image.network(
imageUrl,
width: double.infinity,
height: 70,
fit: BoxFit.cover,
errorBuilder: (_, __, ___) =>
_placeholder(category.icon),
)
: Container(
width: double.infinity,
height: 70,
color: const Color(0xFF0066CC).withOpacity(0.1),
alignment: Alignment.center,
child: Text(
category.icon,
style: const TextStyle(fontSize: 30),
),
),
),
const SizedBox(height: 6),
Text(
item['nome'] ?? '',
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: const TextStyle(
fontWeight: FontWeight.bold,
fontSize: 12,
),
),
Text(
category.name,
style: const TextStyle(
fontSize: 10,
color: Color(0xFF666666),
),
),
],
),
);
},
),
);
}
}
class _ItemsScreen extends StatelessWidget {
const _ItemsScreen();
@override
Widget build(BuildContext context) => const ItemScreen();
}
class _WeekScreen extends StatelessWidget {
const _WeekScreen();
@override
Widget build(BuildContext context) => const WeekScreen();
}
class _ProfileScreen extends StatelessWidget {
const _ProfileScreen();
@override
Widget build(BuildContext context) => const PerfilScreen();
}