import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:lucide_icons/lucide_icons.dart'; import 'package:intl/intl.dart'; import '../providers/providers.dart'; import '../../dashboard/presentation/maturity_dialog.dart'; import '../providers/paginated_transaction_provider.dart'; import '../../../core/widgets/shimmer_loading.dart'; import '../../../core/widgets/empty_state.dart'; import 'add_transaction_screen.dart'; import '../presentation/widgets/transaction_filter_sheet.dart'; import '../../../core/theme/nature_colors.dart'; class AllTransactionsScreen extends ConsumerStatefulWidget { const AllTransactionsScreen({super.key}); @override ConsumerState createState() => _AllTransactionsScreenState(); } class _AllTransactionsScreenState extends ConsumerState { final ScrollController _scrollController = ScrollController(); final TextEditingController _searchController = TextEditingController(); @override void initState() { super.initState(); _scrollController.addListener(() { if (_scrollController.position.pixels >= _scrollController.position.maxScrollExtent - 200) { ref.read(paginatedTransactionProvider.notifier).loadMore(); } }); } @override void dispose() { _scrollController.dispose(); _searchController.dispose(); super.dispose(); } void _onSearchChanged(String value) { // Basic debounce for text search Future.delayed(const Duration(milliseconds: 500), () { if (_searchController.text == value) { ref.read(paginatedTransactionProvider.notifier).updateFilters(search: value); } }); } void _openFilterSheet() { showModalBottomSheet( context: context, isScrollControlled: true, builder: (context) => const TransactionFilterSheet(), ); } @override Widget build(BuildContext context) { final paginatedState = ref.watch(paginatedTransactionProvider); final transactions = paginatedState.transactions; final categoriesState = ref.watch(categoryProvider); final walletsState = ref.watch(walletProvider); return Scaffold( appBar: AppBar( title: const Text('Transactions'), actions: [ IconButton( icon: const Icon(LucideIcons.filter), onPressed: _openFilterSheet, ), IconButton( icon: const Icon(LucideIcons.plus), onPressed: () { Navigator.push(context, MaterialPageRoute(builder: (_) => const AddTransactionScreen())); }, ), ], bottom: PreferredSize( preferredSize: const Size.fromHeight(60), child: Padding( padding: const EdgeInsets.fromLTRB(16, 0, 16, 12), child: TextField( controller: _searchController, onChanged: _onSearchChanged, decoration: InputDecoration( hintText: 'Search transactions...', prefixIcon: const Icon(LucideIcons.search), suffixIcon: _searchController.text.isNotEmpty ? IconButton( icon: const Icon(LucideIcons.x), onPressed: () { _searchController.clear(); _onSearchChanged(''); }, ) : null, filled: true, fillColor: Colors.grey.shade100, border: OutlineInputBorder( borderRadius: BorderRadius.circular(16), borderSide: BorderSide.none, ), focusedBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(16), borderSide: const BorderSide(color: Color(0xFF6C63FF), width: 2) ), contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), ), ), ), ), ), body: Builder( builder: (context) { if (transactions.isEmpty && paginatedState.isLoading) { return ListView.builder( padding: const EdgeInsets.all(16), itemCount: 8, itemBuilder: (context, index) => const ShimmerCard(), ); } if (transactions.isEmpty) { return const EmptyStateWidget( icon: LucideIcons.fileText, title: 'No Transactions', message: 'No transactions found matching your criteria.', ); } return RefreshIndicator( onRefresh: () async { ref.read(paginatedTransactionProvider.notifier).clearFilters(); _searchController.clear(); }, child: ListView.builder( controller: _scrollController, physics: const AlwaysScrollableScrollPhysics(), padding: const EdgeInsets.all(16.0), itemCount: transactions.length + (paginatedState.hasMore ? 1 : 0), itemBuilder: (context, index) { if (index == transactions.length) { return const Padding( padding: EdgeInsets.all(16.0), child: Center(child: CircularProgressIndicator()), ); } final t = transactions[index]; final isIncome = t.type == 'INCOME' || (t.type == 'TRANSFER' && t.fromWalletId == null); final isInvestment = t.type == 'INVESTMENT'; final isExpense = t.type == 'EXPENSE' || (t.type == 'TRANSFER' && t.toWalletId == null); final isTransfer = t.type == 'TRANSFER' && t.fromWalletId != null && t.toWalletId != null; Color typeColor = Colors.grey; IconData typeIcon = LucideIcons.arrowRightLeft; if (isIncome) { typeColor = NatureColors.getColor('INCOME'); typeIcon = LucideIcons.arrowDownCircle; } else if (isExpense) { typeColor = NatureColors.getColor('EXPENSE'); typeIcon = LucideIcons.arrowUpCircle; } else if (isInvestment) { typeColor = NatureColors.getColor('INVESTMENTS'); typeIcon = LucideIcons.trendingUp; } else if (isTransfer) { typeColor = NatureColors.getColor('TRANSFER'); if (walletsState.hasValue) { final toW = walletsState.value!.where((w) => w.id == t.toWalletId).firstOrNull; final fromW = walletsState.value!.where((w) => w.id == t.fromWalletId).firstOrNull; if (toW != null && (toW.nature == 'PAYABLES' || toW.nature == 'LOAN')) { typeColor = NatureColors.getColor('PAYABLES'); typeIcon = LucideIcons.alertCircle; } else if (toW != null && (toW.nature == 'RECEIVABLES' || toW.nature == 'LENDING')) { typeColor = NatureColors.getColor('RECEIVABLES'); typeIcon = LucideIcons.arrowDownLeft; } else if (fromW != null && (fromW.nature == 'PAYABLES' || fromW.nature == 'LOAN')) { typeColor = NatureColors.getColor('PAYABLES'); typeIcon = LucideIcons.alertCircle; } else if (fromW != null && (fromW.nature == 'RECEIVABLES' || fromW.nature == 'LENDING')) { typeColor = NatureColors.getColor('RECEIVABLES'); typeIcon = LucideIcons.arrowDownLeft; } } } String categoryName = 'Unknown'; if (categoriesState.hasValue) { final match = categoriesState.value!.where((c) => c.id == t.categoryId); if (match.isNotEmpty) categoryName = match.first.name; } String accountName = 'Unknown'; if (walletsState.hasValue) { if (isExpense && t.toWalletId != null) { final match = walletsState.value!.where((w) => w.id == t.toWalletId); if (match.isNotEmpty) accountName = match.first.name; } else if (isIncome && t.toWalletId != null) { final match = walletsState.value!.where((w) => w.id == t.toWalletId); if (match.isNotEmpty) accountName = match.first.name; } else if (isTransfer && t.toWalletId != null) { final match = walletsState.value!.where((w) => w.id == t.toWalletId); if (match.isNotEmpty) accountName = 'To ${match.first.name}'; } } String fromName = ''; if (t.fromWalletId != null && walletsState.hasValue) { final match = walletsState.value!.where((w) => w.id == t.fromWalletId); if (match.isNotEmpty) fromName = match.first.name; } String toName = ''; if (t.toWalletId != null && walletsState.hasValue) { final match = walletsState.value!.where((w) => w.id == t.toWalletId); if (match.isNotEmpty) toName = match.first.name; } String subtitleText = ''; if (categoryName == 'Unknown' && fromName.isNotEmpty && toName.isNotEmpty) { subtitleText = '$fromName → $toName • ${DateFormat('MMM dd, yyyy').format(t.date)}'; } else if (categoryName == 'Unknown' && toName.isNotEmpty) { subtitleText = 'To $toName • ${DateFormat('MMM dd, yyyy').format(t.date)}'; } else if (categoryName == 'Unknown' && fromName.isNotEmpty) { subtitleText = 'From $fromName • ${DateFormat('MMM dd, yyyy').format(t.date)}'; } else { subtitleText = '$categoryName • ${DateFormat('MMM dd, yyyy').format(t.date)}${fromName.isNotEmpty ? ' • 💼 $fromName' : ''}'; } return Dismissible( key: Key(t.id.toString()), direction: DismissDirection.endToStart, background: Container( color: Colors.red, alignment: Alignment.centerRight, padding: const EdgeInsets.only(right: 20), child: const Icon(LucideIcons.trash2, color: Colors.white), ), confirmDismiss: (dir) async { return await showDialog( context: context, builder: (ctx) => AlertDialog( title: const Text('Delete Transaction?'), content: const Text('Are you sure you want to delete this transaction?'), actions: [ TextButton(onPressed: () => Navigator.pop(ctx, false), child: const Text('Cancel')), TextButton( onPressed: () => Navigator.pop(ctx, true), child: const Text('Delete', style: TextStyle(color: Colors.red)) ), ], ), ); }, onDismissed: (dir) { ref.read(transactionProvider.notifier).deleteTransaction(t.id); ref.read(paginatedTransactionProvider.notifier).removeTransactionFromState(t.id); }, child: Card( margin: const EdgeInsets.only(bottom: 12), child: Column( children: [ ListTile( onTap: () { Navigator.push(context, MaterialPageRoute(builder: (_) => AddTransactionScreen(transaction: t))); }, leading: CircleAvatar( backgroundColor: typeColor.withValues(alpha: 0.1), child: Icon(typeIcon, color: typeColor), ), title: Text((t.description != null && t.description!.isNotEmpty) ? t.description! : accountName, style: const TextStyle(fontWeight: FontWeight.bold)), subtitle: Text(subtitleText), trailing: Column( mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.end, children: [ Text( '${isIncome ? '+' : (isTransfer ? '' : '-')}Rs. ${t.amount.toStringAsFixed(0)}', style: TextStyle(fontWeight: FontWeight.bold, color: typeColor), ), if (t.investmentStatus == 'OPEN' && (typeColor == NatureColors.getColor('INVESTMENTS') || typeColor == NatureColors.getColor('RECEIVABLES'))) GestureDetector( onTap: () { showDialog(context: context, builder: (_) => MaturityDialog(transaction: t)); }, child: const Padding( padding: EdgeInsets.only(top: 4.0), child: Text('Close', style: TextStyle(color: Colors.blue, fontWeight: FontWeight.bold, fontSize: 12)), ), ), ], ), ), ], ), ), ); }, ), ); } ), ); } }