import 'dart:ui'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../../providers/auth_provider.dart'; import '../../providers/bill_provider.dart'; import '../../providers/family_provider.dart'; import '../../models/bill.dart'; import '../../models/family.dart'; import '../../theme/colors.dart'; import '../../widgets/glass_card.dart'; import '../../widgets/category_icon.dart'; import '../../widgets/stat_card.dart'; class HomeScreen extends ConsumerStatefulWidget { const HomeScreen({super.key}); @override ConsumerState createState() => _HomeScreenState(); } class _HomeScreenState extends ConsumerState { int _selectedTab = 0; @override void initState() { super.initState(); Future.microtask(() { ref.read(billProvider.notifier).loadBills(); ref.read(familyProvider.notifier).loadFamilies(); }); } String _getCurrentMonth() { final now = DateTime.now(); final months = ['一月', '二月', '三月', '四月', '五月', '六月', '七月', '八月', '九月', '十月', '十一月', '十二月']; return '${now.year}年${months[now.month - 1]}'; } List _calculateMonthlyStats(List bills) { final now = DateTime.now(); final currentMonth = now.month; final currentYear = now.year; double totalExpense = 0; double totalIncome = 0; for (final bill in bills) { try { final billDate = DateTime.parse(bill.date); if (billDate.month == currentMonth && billDate.year == currentYear) { if (bill.isExpense) { totalExpense += bill.amount; } else { totalIncome += bill.amount; } } } catch (_) {} } final balance = totalIncome - totalExpense; return [ StatCardData(label: '本月支出', value: '¥${totalExpense.toStringAsFixed(0)}', color: AppColors.expense), StatCardData(label: '本月收入', value: '¥${totalIncome.toStringAsFixed(0)}', color: AppColors.income), StatCardData(label: '本月结余', value: '¥${balance.toStringAsFixed(0)}', color: AppColors.brand), ]; } @override Widget build(BuildContext context) { final authState = ref.watch(authProvider); final billsAsync = ref.watch(billProvider); final familiesAsync = ref.watch(familyProvider); final user = authState.user; final nickname = user?.nickname ?? '用户'; final firstLetter = nickname.isNotEmpty ? nickname[0] : 'U'; final personalBills = billsAsync.valueOrNull ?? []; final stats = _calculateMonthlyStats(personalBills); return Scaffold( backgroundColor: AppColors.pageBg, body: Column( children: [ // OrangeHeader Container( padding: EdgeInsets.only( top: MediaQuery.of(context).padding.top + 16, left: 20, right: 20, bottom: 20, ), decoration: const BoxDecoration( gradient: LinearGradient( begin: Alignment.topLeft, end: Alignment.bottomRight, colors: [AppColors.brand, AppColors.brandLight], ), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // User info row Row( children: [ // Avatar with first letter Container( width: 56, height: 56, decoration: BoxDecoration( color: Colors.white.withOpacity(0.2), shape: BoxShape.circle, border: Border.all(color: Colors.white.withOpacity(0.5), width: 2), ), child: Center( child: Text( firstLetter, style: const TextStyle( fontSize: 24, fontWeight: FontWeight.bold, color: Colors.white, ), ), ), ), const SizedBox(width: 14), Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( '你好,$nickname', style: const TextStyle( fontSize: 22, fontWeight: FontWeight.bold, color: Colors.white, ), ), const SizedBox(height: 4), Text( _getCurrentMonth(), style: TextStyle( fontSize: 14, color: Colors.white.withOpacity(0.85), ), ), ], ), ], ), const SizedBox(height: 20), // 3 StatCards Row( children: [ Expanded(child: StatCard(label: stats[0].label, value: stats[0].value, color: stats[0].color)), const SizedBox(width: 10), Expanded(child: StatCard(label: stats[1].label, value: stats[1].value, color: stats[1].color)), const SizedBox(width: 10), Expanded(child: StatCard(label: stats[2].label, value: stats[2].value, color: stats[2].color)), ], ), ], ), ), // TabSwitcher Padding( padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 16), child: ClipRRect( borderRadius: BorderRadius.circular(16), child: BackdropFilter( filter: ImageFilter.blur(sigmaX: 30, sigmaY: 30), child: Container( height: 50, decoration: BoxDecoration( color: Colors.white.withOpacity(0.7), borderRadius: BorderRadius.circular(16), border: Border.all(color: Colors.white.withOpacity(0.8)), ), child: Row( children: [ Expanded( child: _TabButton( label: '个人账本', isSelected: _selectedTab == 0, onTap: () => setState(() => _selectedTab = 0), ), ), Expanded( child: _TabButton( label: '家庭账本', isSelected: _selectedTab == 1, onTap: () => setState(() => _selectedTab = 1), ), ), ], ), ), ), ), ), // Tab content Expanded( child: _selectedTab == 0 ? _PersonalTab(bills: personalBills) : _FamilyTab(familiesAsync: familiesAsync, bills: personalBills), ), ], ), ); } } class StatCardData { final String label; final String value; final Color color; StatCardData({required this.label, required this.value, required this.color}); } class _TabButton extends StatelessWidget { final String label; final bool isSelected; final VoidCallback onTap; const _TabButton({ required this.label, required this.isSelected, required this.onTap, }); @override Widget build(BuildContext context) { return GestureDetector( onTap: onTap, child: AnimatedContainer( duration: const Duration(milliseconds: 200), margin: const EdgeInsets.all(4), decoration: BoxDecoration( gradient: isSelected ? const LinearGradient(colors: [AppColors.brand, AppColors.brandLight]) : null, borderRadius: BorderRadius.circular(12), ), child: Center( child: Text( label, style: TextStyle( fontSize: 15, fontWeight: isSelected ? FontWeight.w600 : FontWeight.w500, color: isSelected ? Colors.white : AppColors.textSecondary, ), ), ), ), ); } } class _PersonalTab extends StatelessWidget { final List bills; const _PersonalTab({required this.bills}); @override Widget build(BuildContext context) { if (bills.isEmpty) { return Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon(Icons.receipt_long_outlined, size: 64, color: AppColors.textSecondary.withOpacity(0.5)), const SizedBox(height: 16), Text( '暂无账单记录', style: TextStyle(fontSize: 16, color: AppColors.textSecondary.withOpacity(0.7)), ), ], ), ); } // Sort by date descending final sortedBills = List.from(bills) ..sort((a, b) => b.date.compareTo(a.date)); return ListView.builder( padding: const EdgeInsets.symmetric(horizontal: 20), itemCount: sortedBills.length, itemBuilder: (context, index) { final bill = sortedBills[index]; return Padding( padding: const EdgeInsets.only(bottom: 12), child: GlassCard( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), child: Row( children: [ CategoryIcon(emoji: bill.emoji, category: bill.category), const SizedBox(width: 12), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( bill.category, style: const TextStyle( fontSize: 15, fontWeight: FontWeight.w600, color: AppColors.textPrimary, ), ), if (bill.note.isNotEmpty) Text( bill.note, style: TextStyle( fontSize: 13, color: AppColors.textSecondary.withOpacity(0.8), ), maxLines: 1, overflow: TextOverflow.ellipsis, ), ], ), ), Column( crossAxisAlignment: CrossAxisAlignment.end, children: [ Text( '${bill.isExpense ? '-' : '+'}¥${bill.amount.toStringAsFixed(2)}', style: TextStyle( fontSize: 16, fontWeight: FontWeight.bold, color: bill.isExpense ? AppColors.expense : AppColors.income, ), ), Text( bill.time, style: TextStyle( fontSize: 12, color: AppColors.textSecondary.withOpacity(0.7), ), ), ], ), ], ), ), ); }, ); } } class _FamilyTab extends StatelessWidget { final AsyncValue> familiesAsync; final List bills; const _FamilyTab({required this.familiesAsync, required this.bills}); @override Widget build(BuildContext context) { return familiesAsync.when( loading: () => const Center(child: CircularProgressIndicator(color: AppColors.brand)), error: (e, _) => Center(child: Text('加载失败: $e')), data: (families) { if (families.isEmpty) { return Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon(Icons.family_restroom_outlined, size: 64, color: AppColors.textSecondary.withOpacity(0.5)), const SizedBox(height: 16), Text( '暂无家庭', style: TextStyle(fontSize: 16, color: AppColors.textSecondary.withOpacity(0.7)), ), const SizedBox(height: 8), Text( '创建或加入家庭后即可查看', style: TextStyle(fontSize: 14, color: AppColors.textSecondary.withOpacity(0.5)), ), ], ), ); } // Filter family bills final familyBills = bills.where((b) => b.familyId != null).toList(); final sortedFamilyBills = List.from(familyBills) ..sort((a, b) => b.date.compareTo(a.date)); // Calculate member rankings (mock data based on members) final family = families.first; final memberStats = >[]; for (final member in family.members) { double total = 0; for (final bill in familyBills) { if (bill.userId == member.userId) { total += bill.amount; } } memberStats.add({'member': member, 'total': total}); } memberStats.sort((a, b) => (b['total'] as double).compareTo(a['total'] as double)); return ListView( padding: const EdgeInsets.symmetric(horizontal: 20), children: [ // Family bills section if (familyBills.isNotEmpty) ...[ const Padding( padding: EdgeInsets.only(bottom: 12), child: Text( '家庭账单', style: TextStyle( fontSize: 18, fontWeight: FontWeight.bold, color: AppColors.textPrimary, ), ), ), ...sortedFamilyBills.take(5).map((bill) => Padding( padding: const EdgeInsets.only(bottom: 12), child: GlassCard( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), child: Row( children: [ CategoryIcon(emoji: bill.emoji, category: bill.category), const SizedBox(width: 12), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( bill.category, style: const TextStyle( fontSize: 15, fontWeight: FontWeight.w600, color: AppColors.textPrimary, ), ), if (bill.note.isNotEmpty) Text( bill.note, style: TextStyle( fontSize: 13, color: AppColors.textSecondary.withOpacity(0.8), ), maxLines: 1, overflow: TextOverflow.ellipsis, ), ], ), ), Text( '${bill.isExpense ? '-' : '+'}¥${bill.amount.toStringAsFixed(2)}', style: TextStyle( fontSize: 16, fontWeight: FontWeight.bold, color: bill.isExpense ? AppColors.expense : AppColors.income, ), ), ], ), ), )), const SizedBox(height: 16), ], // Member rankings section if (memberStats.isNotEmpty) ...[ const Padding( padding: EdgeInsets.only(bottom: 12), child: Text( '成员排行', style: TextStyle( fontSize: 18, fontWeight: FontWeight.bold, color: AppColors.textPrimary, ), ), ), ...memberStats.asMap().entries().map((entry) { final idx = entry.key; final data = entry.value; final member = data['member'] as FamilyMember; final total = data['total'] as double; final medals = ['🥇', '🥈', '🥉']; return Padding( padding: const EdgeInsets.only(bottom: 10), child: GlassCard( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 14), child: Row( children: [ Container( width: 28, height: 28, decoration: BoxDecoration( color: idx < 3 ? AppColors.brand.withOpacity(0.1) : AppColors.textSecondary.withOpacity(0.1), shape: BoxShape.circle, ), child: Center( child: Text( idx < 3 ? medals[idx] : '${idx + 1}', style: const TextStyle(fontSize: 14), ), ), ), const SizedBox(width: 12), Expanded( child: Text( member.nickname, style: const TextStyle( fontSize: 15, fontWeight: FontWeight.w600, color: AppColors.textPrimary, ), ), ), Text( '¥${total.toStringAsFixed(0)}', style: const TextStyle( fontSize: 16, fontWeight: FontWeight.bold, color: AppColors.brand, ), ), ], ), ), ); }), ], ], ); }, ); } }