import 'dart:io'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:material_symbols_icons/material_symbols_icons.dart'; import 'package:shimmer/shimmer.dart'; import 'package:kmobile/data/models/transaction.dart'; import 'package:kmobile/data/repositories/transaction_repository.dart'; import 'package:kmobile/di/injection.dart'; import '../../../l10n/app_localizations.dart'; import 'transaction_details_screen.dart'; import 'package:pdf/widgets.dart' as pw; import 'package:permission_handler/permission_handler.dart'; import 'package:device_info_plus/device_info_plus.dart'; import 'package:path_provider/path_provider.dart'; import 'package:share_plus/share_plus.dart'; import 'package:flutter_local_notifications/flutter_local_notifications.dart'; import 'package:open_filex/open_filex.dart'; import 'package:kmobile/data/models/user.dart'; class AccountStatementScreen extends StatefulWidget { final List users; final int selectedIndex; const AccountStatementScreen({ super.key, required this.users, required this.selectedIndex, }); @override State createState() => _AccountStatementScreen(); } class _AccountStatementScreen extends State { late User selectedUser; DateTime? fromDate; DateTime? toDate; bool _txLoading = true; List _transactions = []; final _minAmountController = TextEditingController(); final _maxAmountController = TextEditingController(); late FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin; @override void initState() { super.initState(); selectedUser = widget.users[widget.selectedIndex]; _loadTransactions(); _initializeNotifications(); } void _initializeNotifications() async { flutterLocalNotificationsPlugin = FlutterLocalNotificationsPlugin(); const AndroidInitializationSettings initializationSettingsAndroid = AndroidInitializationSettings('notification_icon'); const InitializationSettings initializationSettings = InitializationSettings(android: initializationSettingsAndroid); await flutterLocalNotificationsPlugin.initialize( initializationSettings, onDidReceiveNotificationResponse: _onDidReceiveNotificationResponse, ); _requestNotificationPermission(); } void _onDidReceiveNotificationResponse( NotificationResponse notificationResponse) async { final String? payload = notificationResponse.payload; if (payload != null && payload.isNotEmpty) { await OpenFilex.open(payload); } } void _requestNotificationPermission() async { if (Platform.isAndroid) { final AndroidFlutterLocalNotificationsPlugin? androidImplementation = flutterLocalNotificationsPlugin.resolvePlatformSpecificImplementation< AndroidFlutterLocalNotificationsPlugin>(); if (androidImplementation != null) { await androidImplementation.requestNotificationsPermission(); } } } Future _loadTransactions() async { setState(() { _txLoading = true; _transactions = []; }); try { final repo = getIt(); final txs = await repo.fetchTransactions( selectedUser.accountNo ?? '', fromDate: fromDate, toDate: toDate, ); setState(() => _transactions = txs); } catch (e) { if (!mounted) return; ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text( '${AppLocalizations.of(context).failedToLoadTransactions} $e', ), ), ); } finally { setState(() => _txLoading = false); } } Future _selectFromDate(BuildContext context) async { final now = DateTime.now(); final picked = await showDatePicker( context: context, initialDate: fromDate ?? now, firstDate: DateTime(2020), lastDate: now, ); if (picked != null) { setState(() { fromDate = picked; toDate = null; }); } } Future _selectToDate(BuildContext context) async { if (fromDate == null) { ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text(AppLocalizations.of(context).pleaseSelectDateFirst), ), ); return; } final now = DateTime.now(); final maxToDate = fromDate!.add(const Duration(days: 183)).isBefore(now) ? fromDate!.add(const Duration(days: 183)) : now; final initialToDate = toDate ?? now; final clampedInitialToDate = initialToDate.isBefore(fromDate!) ? fromDate! : initialToDate; final picked = await showDatePicker( context: context, initialDate: clampedInitialToDate, firstDate: fromDate!, lastDate: maxToDate, ); if (picked != null) { setState(() => toDate = picked); } } String _formatDate(DateTime date) { return "${date.day.toString().padLeft(2, '0')}-" "${date.month.toString().padLeft(2, '0')}-" "${date.year}"; } @override void dispose() { _minAmountController.dispose(); _maxAmountController.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text( AppLocalizations.of(context).accountStatement, ), centerTitle: false, ), body: Stack( children: [ Padding( padding: const EdgeInsets.all(12.0), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Card( margin: const EdgeInsets.only(bottom: 10), child: Padding( padding: const EdgeInsets.all(16.0), child: Row( mainAxisAlignment: MainAxisAlignment.start, children: [ Text( AppLocalizations.of(context).accountNumber, style: const TextStyle( fontWeight: FontWeight.w500, fontSize: 17), ), const VerticalDivider( width: 20, thickness: 1, indent: 5, endIndent: 5, color: Colors.grey), DropdownButton( value: selectedUser, onChanged: (User? newUser) { if (newUser != null) { setState(() { selectedUser = newUser; }); _loadTransactions(); } }, items: widget.users.map((user) { return DropdownMenuItem( value: user, child: Text(user.accountNo.toString()), ); }).toList(), underline: Container(), // Remove the underline ), Spacer(), ], ), ), ), Card( margin: const EdgeInsets.only(bottom: 10), child: Padding( padding: const EdgeInsets.all(16.0), child: Row( children: [ Text( "${AppLocalizations.of(context).availableBalance}: ", style: const TextStyle( fontSize: 17, ), ), Text(' ₹ ${selectedUser.availableBalance}', style: const TextStyle(fontSize: 17)), ], ), ), ), Text( AppLocalizations.of(context).filters, style: const TextStyle(fontSize: 17), ), const SizedBox(height: 15), Row( children: [ Expanded( child: GestureDetector( onTap: () => _selectFromDate(context), child: buildDateBox( AppLocalizations.of(context).fromDate, fromDate, ), ), ), const SizedBox(width: 10), Expanded( child: GestureDetector( onTap: () => _selectToDate(context), child: buildDateBox( AppLocalizations.of(context).toDate, toDate, ), ), ), ], ), const SizedBox(height: 20), SizedBox( width: double.infinity, child: ElevatedButton( onPressed: _loadTransactions, style: ElevatedButton.styleFrom( backgroundColor: Theme.of(context).colorScheme.primaryContainer, padding: const EdgeInsets.symmetric(vertical: 16), ), child: Text( AppLocalizations.of(context).search, style: TextStyle( color: Theme.of(context).colorScheme.onPrimaryContainer, fontSize: 16, ), ), ), ), const SizedBox(height: 15), if (!_txLoading && _transactions.isNotEmpty && fromDate == null && toDate == null) Padding( padding: const EdgeInsets.only(bottom: 12.0), child: Text( AppLocalizations.of(context).lastTenTransactions, style: TextStyle( fontSize: 16, fontWeight: FontWeight.w500, color: Theme.of(context).colorScheme.onSurfaceVariant, ), ), ), Expanded( child: _txLoading ? ListView.builder( itemCount: 3, itemBuilder: (_, __) => ListTile( leading: Shimmer.fromColors( baseColor: Theme.of(context).colorScheme.surfaceVariant, highlightColor: Theme.of(context) .colorScheme .onSurfaceVariant, child: CircleAvatar( radius: 12, backgroundColor: Theme.of(context).scaffoldBackgroundColor, ), ), title: Shimmer.fromColors( baseColor: Theme.of(context).colorScheme.surfaceVariant, highlightColor: Theme.of(context) .colorScheme .onSurfaceVariant, child: Container( height: 10, width: 100, color: Theme.of(context).scaffoldBackgroundColor, ), ), subtitle: Shimmer.fromColors( baseColor: Theme.of(context).colorScheme.surfaceVariant, highlightColor: Theme.of(context) .colorScheme .onSurfaceVariant, child: Container( height: 8, width: 60, color: Theme.of(context).scaffoldBackgroundColor, ), ), ), ) : _transactions.isEmpty ? Center( child: Text( AppLocalizations.of(context).noTransactions, style: TextStyle( fontSize: 16, color: Theme.of(context).colorScheme.onSurface, )), ) : ListView.builder( itemCount: _transactions.length, itemBuilder: (context, index) { final tx = _transactions[index]; return Card( margin: const EdgeInsets.symmetric( horizontal: 0, vertical: 4), child: ListTile( leading: Icon( tx.type == 'CR' ? Symbols.call_received : Symbols.call_made, color: tx.type == 'CR' ? const Color(0xFF10BB10) : Theme.of(context).colorScheme.error, ), title: Text( tx.date ?? '', style: const TextStyle(fontSize: 15), ), subtitle: Text( tx.name != null ? (tx.name!.length > 22 ? tx.name!.substring(0, 22) : tx.name!) : '', style: const TextStyle(fontSize: 12), ), trailing: Column( crossAxisAlignment: CrossAxisAlignment.end, mainAxisAlignment: MainAxisAlignment.center, children: [ Text( "₹${tx.amount}", style: const TextStyle(fontSize: 17), ), Text( "Bal: ₹${tx.balance}", style: const TextStyle( fontSize: 12), // Style matches tx.name ), ], ), onTap: () { Navigator.push( context, MaterialPageRoute( builder: (_) => TransactionDetailsScreen( transaction: tx), ), ); }, ), ); }, ), ), ], ), ), IgnorePointer( child: Center( child: Opacity( opacity: 0.07, // Reduced opacity child: ClipOval( child: Image.asset( 'assets/images/logo.png', width: 200, // Adjust size as needed height: 200, // Adjust size as needed ), ), ), ), ), ], ), floatingActionButton: FloatingActionButton( onPressed: () { _exportToPdf(); }, child: const Icon(Icons.download), ), ); } Future _exportToPdf() async { if (_transactions.isEmpty) { if (mounted) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar( content: Text('No transactions to export.'), ), ); } return; } // Show downloading notification await flutterLocalNotificationsPlugin.show( 0, 'Downloading PDF', 'Your account statement is being downloaded...', const NotificationDetails( android: AndroidNotificationDetails( 'download_channel', 'Download Notifications', channelDescription: 'Notifications for PDF downloads', importance: Importance.low, priority: Priority.low, showProgress: true, maxProgress: 0, // Indeterminate progress ongoing: true, icon: 'notification_icon', ), ), ); var logo = await rootBundle.load('assets/images/logo.png'); var rubik = await rootBundle.load("assets/fonts/Rubik-Regular.ttf"); final pdf = pw.Document(); pdf.addPage(pw.MultiPage( margin: const pw.EdgeInsets.all(20), build: (pw.Context context) { return [ pw.Row( mainAxisAlignment: pw.MainAxisAlignment.start, crossAxisAlignment: pw.CrossAxisAlignment.center, children: [ pw.Image(pw.MemoryImage(logo.buffer.asUint8List()), width: 50, height: 50), pw.SizedBox(width: 20), pw.Text('Account Statement - KCCB', style: pw.TextStyle( fontSize: 24, fontWeight: pw.FontWeight.bold)), ]), pw.SizedBox(height: 20), pw.Row( mainAxisAlignment: pw.MainAxisAlignment.spaceBetween, children: [ pw.Text('Account Number: ${selectedUser.accountNo}', style: pw.TextStyle(font: pw.Font.ttf(rubik), fontSize: 15)), pw.Text('Account Type: ${selectedUser.productType}', style: pw.TextStyle( fontSize: 15, font: pw.Font.ttf(rubik), )), ]), pw.SizedBox(height: 20), pw.Table(border: pw.TableBorder.all(), columnWidths: { 0: const pw.FractionColumnWidth(0.2), 1: const pw.FractionColumnWidth(0.45), 2: const pw.FractionColumnWidth(0.15), 3: const pw.FractionColumnWidth(0.20), }, children: [ pw.TableRow( children: [ pw.Padding( padding: const pw.EdgeInsets.all(4), child: pw.Text('Date')), pw.Padding( padding: const pw.EdgeInsets.all(4), child: pw.Text('Description', softWrap: true)), pw.Padding( padding: const pw.EdgeInsets.all(4), child: pw.Text('Amount')), pw.Padding( padding: const pw.EdgeInsets.all(4), child: pw.Text('Balance')), ], ), ..._transactions.map((tx) { return pw.TableRow(children: [ pw.Padding( padding: const pw.EdgeInsets.all(10), child: pw.Text(tx.date ?? '', style: pw.TextStyle( fontSize: 12, font: pw.Font.ttf(rubik), ))), pw.Padding( padding: const pw.EdgeInsets.all(10), child: pw.Text(tx.name ?? '', style: pw.TextStyle( fontSize: 12, font: pw.Font.ttf(rubik)))), pw.Padding( padding: const pw.EdgeInsets.all(10), child: pw.Text("₹${tx.amount} ${tx.type}", style: pw.TextStyle( fontSize: 12, font: pw.Font.ttf(rubik), ))), pw.Padding( padding: const pw.EdgeInsets.all(10), child: pw.Text("₹${tx.balance} ${tx.balanceType}", style: pw.TextStyle( fontSize: 12, font: pw.Font.ttf(rubik), ))), ]); }), ]) ]; }, footer: (pw.Context context) { return pw.Container( alignment: pw.Alignment.centerRight, margin: const pw.EdgeInsets.only(top: 10), child: pw.Text( 'Kangra Central Co-Operative Bank Pvt Ltd. ©. All rights reserved.', style: pw.TextStyle( font: pw.Font.ttf(rubik), fontSize: 8, ), ), ); })); try { final Uint8List pdfBytes = await pdf.save(); final String timestamp = DateTime.now().millisecondsSinceEpoch.toString(); final String fileName = 'account_statement_$timestamp.pdf'; String? filePath; if (Platform.isAndroid) { final directory = Directory('/storage/emulated/0/Download'); if (!await directory.exists()) { await directory.create(recursive: true); } final file = File('${directory.path}/$fileName'); await file.writeAsBytes(pdfBytes); filePath = file.path; } else if (Platform.isIOS) { final tempDir = await getTemporaryDirectory(); final file = await File('${tempDir.path}/$fileName').create(); await file.writeAsBytes(pdfBytes); filePath = file.path; } else { // For other platforms, we might just save to temporary directory final tempDir = await getTemporaryDirectory(); final file = await File('${tempDir.path}/$fileName').create(); await file.writeAsBytes(pdfBytes); filePath = file.path; } // Update notification to download complete await flutterLocalNotificationsPlugin.show( 0, 'PDF Download Complete', 'Your account statement has been saved.', const NotificationDetails( android: AndroidNotificationDetails( 'download_channel', 'Download Notifications', channelDescription: 'Notifications for PDF downloads', importance: Importance.high, priority: Priority.high, showProgress: false, ongoing: false, autoCancel: true, icon: 'notification_icon', actions: [ AndroidNotificationAction( 'open_pdf', 'Open PDF', showsUserInterface: true, ) ], ), ), payload: filePath, ); } catch (e) { await flutterLocalNotificationsPlugin.show( 0, 'PDF Download Failed', 'Error saving PDF: $e', const NotificationDetails( android: AndroidNotificationDetails( 'download_channel', 'Download Notifications', channelDescription: 'Notifications for PDF downloads', importance: Importance.high, priority: Priority.high, showProgress: false, ongoing: false, icon: 'notification_icon', ), ), ); } } Widget buildDateBox(String label, DateTime? date) { return Container( padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 16), decoration: BoxDecoration( border: Border.all(color: Theme.of(context).colorScheme.onSurfaceVariant), borderRadius: BorderRadius.circular(6), ), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( date != null ? _formatDate(date) : label, style: TextStyle( fontSize: 16, color: date != null ? Theme.of(context).colorScheme.onSurface : Theme.of(context).colorScheme.onSurfaceVariant, ), ), const Icon(Icons.arrow_drop_down), ], ), ); } }