import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:intl/intl.dart'; import '../../core/constants.dart'; import '../../shared/models/transaction.dart'; import 'provider.dart'; final _currencyFmt = NumberFormat.currency(symbol: '\$', decimalDigits: 2); class DashboardScreen extends ConsumerWidget { const DashboardScreen({super.key}); @override Widget build(BuildContext context, WidgetRef ref) { final balance = ref.watch(totalBalanceProvider); final income = ref.watch(totalIncomeProvider); final expense = ref.watch(totalExpenseProvider); final recent = ref.watch(recentTransactionsProvider); return Scaffold( backgroundColor: AppColors.background, body: SafeArea( child: CustomScrollView( slivers: [ SliverToBoxAdapter( child: Padding( padding: const EdgeInsets.fromLTRB(20, 24, 20, 0), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( 'My Finances', style: Theme.of(context).textTheme.headlineSmall?.copyWith( fontWeight: FontWeight.w700, color: AppColors.textPrimary, ), ), Text( DateFormat('MMMM yyyy').format(DateTime.now()), style: Theme.of(context).textTheme.bodyMedium?.copyWith( color: AppColors.textSecondary, ), ), const SizedBox(height: 24), _BalanceCard(balance: balance), const SizedBox(height: 16), _SummaryRow(income: income, expense: expense), const SizedBox(height: 28), Text( 'Recent Transactions', style: Theme.of(context).textTheme.titleMedium?.copyWith( fontWeight: FontWeight.w600, color: AppColors.textPrimary, ), ), const SizedBox(height: 12), ], ), ), ), if (recent.isEmpty) const SliverFillRemaining( hasScrollBody: false, child: _EmptyState(), ) else SliverPadding( padding: const EdgeInsets.fromLTRB(20, 0, 20, 100), sliver: SliverList( delegate: SliverChildBuilderDelegate( (context, i) => Padding( padding: const EdgeInsets.only(bottom: 10), child: _TransactionTile(transaction: recent[i], ref: ref), ), childCount: recent.length, ), ), ), ], ), ), ); } } class _BalanceCard extends StatelessWidget { final double balance; const _BalanceCard({required this.balance}); @override Widget build(BuildContext context) { return Container( width: double.infinity, padding: const EdgeInsets.all(24), decoration: BoxDecoration( gradient: const LinearGradient( colors: [Color(0xFF7C6DED), Color(0xFF5A4FBF)], begin: Alignment.topLeft, end: Alignment.bottomRight, ), borderRadius: BorderRadius.circular(20), boxShadow: [ BoxShadow( color: AppColors.accent.withOpacity(0.3), blurRadius: 20, offset: const Offset(0, 8), ), ], ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( 'Total Balance', style: Theme.of(context).textTheme.bodyMedium?.copyWith( color: Colors.white70, ), ), const SizedBox(height: 8), Text( _currencyFmt.format(balance), style: Theme.of(context).textTheme.headlineMedium?.copyWith( color: Colors.white, fontWeight: FontWeight.w700, letterSpacing: -0.5, ), ), ], ), ); } } class _SummaryRow extends StatelessWidget { final double income; final double expense; const _SummaryRow({required this.income, required this.expense}); @override Widget build(BuildContext context) { return Row( children: [ Expanded(child: _SummaryCard(label: 'Income', amount: income, color: AppColors.income, icon: Icons.arrow_downward_rounded)), const SizedBox(width: 12), Expanded(child: _SummaryCard(label: 'Expenses', amount: expense, color: AppColors.expense, icon: Icons.arrow_upward_rounded)), ], ); } } class _SummaryCard extends StatelessWidget { final String label; final double amount; final Color color; final IconData icon; const _SummaryCard({required this.label, required this.amount, required this.color, required this.icon}); @override Widget build(BuildContext context) { return Container( padding: const EdgeInsets.all(16), decoration: BoxDecoration( color: AppColors.surface, borderRadius: BorderRadius.circular(16), border: Border.all(color: AppColors.divider), ), child: Row( children: [ Container( padding: const EdgeInsets.all(8), decoration: BoxDecoration( color: color.withOpacity(0.15), borderRadius: BorderRadius.circular(10), ), child: Icon(icon, color: color, size: 18), ), const SizedBox(width: 10), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text(label, style: Theme.of(context).textTheme.bodySmall?.copyWith(color: AppColors.textSecondary)), const SizedBox(height: 2), Text( _currencyFmt.format(amount), style: Theme.of(context).textTheme.bodyMedium?.copyWith( color: color, fontWeight: FontWeight.w600, ), overflow: TextOverflow.ellipsis, ), ], ), ), ], ), ); } } class _TransactionTile extends StatelessWidget { final Transaction transaction; final WidgetRef ref; const _TransactionTile({required this.transaction, required this.ref}); @override Widget build(BuildContext context) { final isIncome = transaction.type == TransactionType.income; final color = isIncome ? AppColors.income : AppColors.expense; final catColor = AppCategories.colors[transaction.category] ?? AppColors.accent; final catIcon = AppCategories.icons[transaction.category] ?? Icons.category_rounded; return Dismissible( key: Key(transaction.id), direction: DismissDirection.endToStart, background: Container( alignment: Alignment.centerRight, padding: const EdgeInsets.only(right: 20), decoration: BoxDecoration( color: AppColors.expense.withOpacity(0.15), borderRadius: BorderRadius.circular(16), ), child: const Icon(Icons.delete_outline_rounded, color: AppColors.expense), ), onDismissed: (_) => ref.read(transactionsProvider.notifier).delete(transaction.id), child: Container( padding: const EdgeInsets.all(14), decoration: BoxDecoration( color: AppColors.surface, borderRadius: BorderRadius.circular(16), border: Border.all(color: AppColors.divider), ), child: Row( children: [ Container( padding: const EdgeInsets.all(10), decoration: BoxDecoration( color: catColor.withOpacity(0.15), borderRadius: BorderRadius.circular(12), ), child: Icon(catIcon, color: catColor, size: 20), ), const SizedBox(width: 12), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( transaction.category, style: Theme.of(context).textTheme.bodyMedium?.copyWith( fontWeight: FontWeight.w600, color: AppColors.textPrimary, ), ), if (transaction.note != null && transaction.note!.isNotEmpty) Text( transaction.note!, style: Theme.of(context).textTheme.bodySmall?.copyWith( color: AppColors.textSecondary, ), overflow: TextOverflow.ellipsis, ) else Text( DateFormat('MMM d, yyyy').format(transaction.date), style: Theme.of(context).textTheme.bodySmall?.copyWith( color: AppColors.textSecondary, ), ), ], ), ), Text( '${isIncome ? '+' : '-'}${_currencyFmt.format(transaction.amount)}', style: Theme.of(context).textTheme.bodyMedium?.copyWith( color: color, fontWeight: FontWeight.w700, ), ), ], ), ), ); } } class _EmptyState extends StatelessWidget { const _EmptyState(); @override Widget build(BuildContext context) { return Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Container( padding: const EdgeInsets.all(24), decoration: BoxDecoration( color: AppColors.surface, shape: BoxShape.circle, ), child: const Icon( Icons.receipt_long_rounded, size: 48, color: AppColors.textSecondary, ), ), const SizedBox(height: 16), Text( 'No transactions yet', style: Theme.of(context).textTheme.titleMedium?.copyWith( color: AppColors.textPrimary, fontWeight: FontWeight.w600, ), ), const SizedBox(height: 6), Text( 'Tap + to add your first transaction', style: Theme.of(context).textTheme.bodyMedium?.copyWith( color: AppColors.textSecondary, ), ), ], ), ); } }