Download PDF #1
This commit is contained in:
@@ -2,7 +2,6 @@ import 'dart:io';
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:material_symbols_icons/material_symbols_icons.dart';
|
import 'package:material_symbols_icons/material_symbols_icons.dart';
|
||||||
import 'package:path_provider/path_provider.dart';
|
|
||||||
import 'package:shimmer/shimmer.dart';
|
import 'package:shimmer/shimmer.dart';
|
||||||
import 'package:kmobile/data/models/transaction.dart';
|
import 'package:kmobile/data/models/transaction.dart';
|
||||||
import 'package:kmobile/data/repositories/transaction_repository.dart';
|
import 'package:kmobile/data/repositories/transaction_repository.dart';
|
||||||
@@ -12,14 +11,19 @@ import 'transaction_details_screen.dart';
|
|||||||
import 'package:pdf/widgets.dart' as pw;
|
import 'package:pdf/widgets.dart' as pw;
|
||||||
import 'package:permission_handler/permission_handler.dart';
|
import 'package:permission_handler/permission_handler.dart';
|
||||||
import 'package:device_info_plus/device_info_plus.dart';
|
import 'package:device_info_plus/device_info_plus.dart';
|
||||||
|
import 'package:flutter/foundation.dart' show kIsWeb;
|
||||||
|
import 'dart:html' as html; // Import for web-specific code
|
||||||
|
import 'dart:typed_data';
|
||||||
|
|
||||||
class AccountStatementScreen extends StatefulWidget {
|
class AccountStatementScreen extends StatefulWidget {
|
||||||
final String accountNo;
|
final String accountNo;
|
||||||
final String balance;
|
final String balance;
|
||||||
|
final String accountType;
|
||||||
const AccountStatementScreen({
|
const AccountStatementScreen({
|
||||||
super.key,
|
super.key,
|
||||||
required this.accountNo,
|
required this.accountNo,
|
||||||
required this.balance,
|
required this.balance,
|
||||||
|
required this.accountType,
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -33,7 +37,7 @@ class _AccountStatementScreen extends State<AccountStatementScreen> {
|
|||||||
List<Transaction> _transactions = [];
|
List<Transaction> _transactions = [];
|
||||||
final _minAmountController = TextEditingController();
|
final _minAmountController = TextEditingController();
|
||||||
final _maxAmountController = TextEditingController();
|
final _maxAmountController = TextEditingController();
|
||||||
Future<Map<String, dynamic>?>? accountStatementsFuture;
|
//Future<Map<String, dynamic>?>? accountStatementsFuture;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
@@ -325,35 +329,155 @@ class _AccountStatementScreen extends State<AccountStatementScreen> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _exportToPdf() async {
|
Future<void> _exportToPdf() async {
|
||||||
if (accountStatementsFuture == null) return;
|
// Step 1: Check if there are any transactions to export.
|
||||||
if (Platform.isAndroid) {
|
if (_transactions.isEmpty) {
|
||||||
final androidInfo = await DeviceInfoPlugin().androidInfo;
|
if (mounted) {
|
||||||
if (androidInfo.version.sdkInt < 29) {
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
final status = await Permission.storage.status;
|
const SnackBar(
|
||||||
if (status.isDenied) {
|
content: Text('No transactions to export.'),
|
||||||
final result = await Permission.storage.request();
|
),
|
||||||
if (result.isDenied) {
|
);
|
||||||
if (mounted) {
|
}
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
return;
|
||||||
const SnackBar(
|
}
|
||||||
content: Text('Storage permission is required to save PDF'),
|
|
||||||
duration: Duration(seconds: 3),
|
// Step 2: Handle storage permissions for Android.
|
||||||
),
|
/*if (Platform.isAndroid) {
|
||||||
);
|
final androidInfo = await DeviceInfoPlugin().androidInfo;
|
||||||
}
|
if (androidInfo.version.sdkInt < 33) { // Target Android 12 & below
|
||||||
return;
|
final status = await Permission.storage.status;
|
||||||
|
if (status.isDenied) {
|
||||||
|
final result = await Permission.storage.request();
|
||||||
|
if (result.isDenied) {
|
||||||
|
if (mounted) {
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
const SnackBar(
|
||||||
|
content: Text('Storage permission is required to save PDF'),
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
String logoSvg = await rootBundle.loadString('assets/logos/logo.svg');
|
// Step 3: Load assets for the PDF.
|
||||||
var rubik = await rootBundle.load("assets/fonts/Rubik-Regular.ttf");
|
// IMPORTANT: The original path 'assets/logos/logo.svg' is incorrect.
|
||||||
try {
|
// I've corrected it to 'assets/images/icon.svg' based on your project structure.
|
||||||
final statements = await accountStatementsFuture;
|
String logoSvg = await rootBundle.loadString('assets/images/icon.svg');
|
||||||
if (statements == null) return;
|
var rubik = await rootBundle.load("assets/fonts/Rubik-Regular.ttf");
|
||||||
final pdf = pw.Document();
|
|
||||||
pdf.addPage(pw.MultiPage(
|
try {
|
||||||
|
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.SvgImage(svg: logoSvg, 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: ${widget.accountNo}',
|
||||||
|
style: pw.TextStyle(font: pw.Font.ttf(rubik), fontSize: 15),
|
||||||
|
),
|
||||||
|
pw.Text(
|
||||||
|
'Account Type: ${widget.accountType}',
|
||||||
|
style: pw.TextStyle(font: pw.Font.ttf(rubik), fontSize: 15),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
pw.SizedBox(height: 20),
|
||||||
|
// Step 4: Build the table from the _transactions list.
|
||||||
|
pw.Table.fromTextArray(
|
||||||
|
border: pw.TableBorder.all(),
|
||||||
|
headerStyle: pw.TextStyle(fontWeight: pw.FontWeight.bold),
|
||||||
|
headerDecoration: const pw.BoxDecoration(
|
||||||
|
color: PdfColors.grey300,
|
||||||
|
),
|
||||||
|
cellHeight: 30,
|
||||||
|
cellAlignments: {
|
||||||
|
0: pw.Alignment.centerLeft,
|
||||||
|
1: pw.Alignment.centerLeft,
|
||||||
|
2: pw.Alignment.centerRight,
|
||||||
|
3: pw.Alignment.center,
|
||||||
|
},
|
||||||
|
headers: ['Date', 'Description', 'Amount', 'Type'],
|
||||||
|
data: _transactions.map((tx) => [
|
||||||
|
tx.date ?? 'N/A',
|
||||||
|
tx.name ?? 'N/A',
|
||||||
|
'₹${tx.amount}',
|
||||||
|
tx.type ?? 'N/A',
|
||||||
|
]).toList(),
|
||||||
|
),
|
||||||
|
];
|
||||||
|
},
|
||||||
|
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,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Step 5: Save the PDF to the device.
|
||||||
|
Directory? directory = await getDownloadsDirectory();
|
||||||
|
if (directory == null) {
|
||||||
|
throw Exception('Could not access downloads directory');
|
||||||
|
}
|
||||||
|
final String timestamp = DateTime.now().millisecondsSinceEpoch.toString();
|
||||||
|
final file = File('${directory.path}/account_statement_$timestamp.pdf');
|
||||||
|
|
||||||
|
await file.writeAsBytes(await pdf.save());
|
||||||
|
|
||||||
|
if (mounted) {
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
SnackBar(
|
||||||
|
content: Text('PDF saved to: ${file.path}'),
|
||||||
|
duration: const Duration(seconds: 3),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
if (mounted) {
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
SnackBar(
|
||||||
|
content: Text('Error saving PDF: $e'),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}*/
|
||||||
|
String logoSvg = await rootBundle.loadString('assets/images/kccb_logo.svg'); // Corrected asset path
|
||||||
|
var rubik = await rootBundle.load("assets/fonts/Rubik-Regular.ttf");
|
||||||
|
final pdf = pw.Document();
|
||||||
|
|
||||||
|
pdf.addPage(pw.MultiPage(
|
||||||
margin: const pw.EdgeInsets.all(20),
|
margin: const pw.EdgeInsets.all(20),
|
||||||
build: (pw.Context context) {
|
build: (pw.Context context) {
|
||||||
return [
|
return [
|
||||||
@@ -363,7 +487,7 @@ class _AccountStatementScreen extends State<AccountStatementScreen> {
|
|||||||
children: [
|
children: [
|
||||||
pw.SvgImage(svg: logoSvg, width: 50, height: 50),
|
pw.SvgImage(svg: logoSvg, width: 50, height: 50),
|
||||||
pw.SizedBox(width: 20),
|
pw.SizedBox(width: 20),
|
||||||
pw.Text(AppLocalizations.of(context as BuildContext).kccBankFull,
|
pw.Text('Account Statement - KCCB',
|
||||||
style: pw.TextStyle(
|
style: pw.TextStyle(
|
||||||
fontSize: 24, fontWeight: pw.FontWeight.bold)),
|
fontSize: 24, fontWeight: pw.FontWeight.bold)),
|
||||||
]),
|
]),
|
||||||
@@ -374,7 +498,7 @@ class _AccountStatementScreen extends State<AccountStatementScreen> {
|
|||||||
pw.Text('Account Number: ${widget.accountNo}',
|
pw.Text('Account Number: ${widget.accountNo}',
|
||||||
style: pw.TextStyle(
|
style: pw.TextStyle(
|
||||||
font: pw.Font.ttf(rubik), fontSize: 15)),
|
font: pw.Font.ttf(rubik), fontSize: 15)),
|
||||||
pw.Text('Account Type: selectedAccountType',
|
pw.Text('Account Type: ${widget.accountType}',
|
||||||
style: pw.TextStyle(
|
style: pw.TextStyle(
|
||||||
fontSize: 15,
|
fontSize: 15,
|
||||||
font: pw.Font.ttf(rubik),
|
font: pw.Font.ttf(rubik),
|
||||||
@@ -403,40 +527,28 @@ class _AccountStatementScreen extends State<AccountStatementScreen> {
|
|||||||
child: pw.Text('Balance')),
|
child: pw.Text('Balance')),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
...statements['accountStatement'].map<pw.TableRow>((statement) {
|
..._transactions.map<pw.TableRow>((tx) {
|
||||||
return pw.TableRow(children: [
|
return pw.TableRow(children: [
|
||||||
pw.Padding(
|
pw.Padding(
|
||||||
padding: const pw.EdgeInsets.all(10),
|
padding: const pw.EdgeInsets.all(10),
|
||||||
child: pw.Text(
|
child: pw.Text(tx.date ?? '',
|
||||||
'${statement['postDate']} ${statement['postTime']}',
|
style: pw.TextStyle(
|
||||||
style: pw.TextStyle(
|
fontSize: 12,
|
||||||
fontSize: 12,
|
font: pw.Font.ttf(rubik),
|
||||||
font: pw.Font.ttf(rubik),
|
))),
|
||||||
))),
|
pw.Padding(
|
||||||
pw.Padding(
|
padding: const pw.EdgeInsets.all(10),
|
||||||
padding: const pw.EdgeInsets.all(10),
|
child: pw.Text(tx.name ?? '',
|
||||||
child: pw.Text(statement['narration'],
|
style: pw.TextStyle(
|
||||||
style: pw.TextStyle(
|
fontSize: 12, font: pw.Font.ttf(rubik)))),
|
||||||
fontSize: 12, font: pw.Font.ttf(rubik)))),
|
pw.Padding(
|
||||||
pw.Padding(
|
padding: const pw.EdgeInsets.all(10),
|
||||||
padding: const pw.EdgeInsets.all(10),
|
child: pw.Text("₹${tx.amount}",
|
||||||
child: pw.Text("₹${statement['transactionAmount']}",
|
style: pw.TextStyle(
|
||||||
style: pw.TextStyle(
|
fontSize: 12,
|
||||||
fontSize: 12,
|
font: pw.Font.ttf(rubik),
|
||||||
font: pw.Font.ttf(rubik),
|
))),
|
||||||
// color:
|
]);
|
||||||
// statement['transactionAmount'].contains('-')
|
|
||||||
// ? const PdfColor.fromInt(0xFFB00020)
|
|
||||||
// : const PdfColor.fromInt(0xFF007B0D),
|
|
||||||
))),
|
|
||||||
pw.Padding(
|
|
||||||
padding: const pw.EdgeInsets.all(10),
|
|
||||||
child: pw.Text("₹${statement['endBalance']}",
|
|
||||||
style: pw.TextStyle(
|
|
||||||
fontSize: 12,
|
|
||||||
font: pw.Font.ttf(rubik),
|
|
||||||
))),
|
|
||||||
]);
|
|
||||||
}).toList(),
|
}).toList(),
|
||||||
])
|
])
|
||||||
];
|
];
|
||||||
@@ -446,7 +558,7 @@ class _AccountStatementScreen extends State<AccountStatementScreen> {
|
|||||||
alignment: pw.Alignment.centerRight,
|
alignment: pw.Alignment.centerRight,
|
||||||
margin: const pw.EdgeInsets.only(top: 10),
|
margin: const pw.EdgeInsets.only(top: 10),
|
||||||
child: pw.Text(
|
child: pw.Text(
|
||||||
'Kangra Central Co-Operative bank Pvt Ltd. ©. All rights reserved.',
|
'Kangra Central Co-Operative Bank Pvt Ltd. ©. All rights reserved.',
|
||||||
style: pw.TextStyle(
|
style: pw.TextStyle(
|
||||||
font: pw.Font.ttf(rubik),
|
font: pw.Font.ttf(rubik),
|
||||||
fontSize: 8,
|
fontSize: 8,
|
||||||
@@ -455,41 +567,81 @@ class _AccountStatementScreen extends State<AccountStatementScreen> {
|
|||||||
);
|
);
|
||||||
}));
|
}));
|
||||||
|
|
||||||
Directory? directory;
|
//Logic For all platforms
|
||||||
if (Platform.isAndroid) {
|
try {
|
||||||
directory = Directory('/storage/emulated/0/Download');
|
final Uint8List pdfBytes = await pdf.save();
|
||||||
} else {
|
final String timestamp = DateTime.now().millisecondsSinceEpoch.toString();
|
||||||
directory = await getDownloadsDirectory();
|
final String fileName = 'account_statement_$timestamp.pdf';
|
||||||
}
|
|
||||||
|
|
||||||
if (directory == null) {
|
// For Web
|
||||||
throw Exception('Could not access downloads directory');
|
if (kIsWeb) {
|
||||||
}
|
final blob = html.Blob([pdfBytes], 'application/pdf');
|
||||||
final String timestamp = DateTime.now().millisecondsSinceEpoch.toString();
|
final url = html.Url.createObjectUrlFromBlob(blob);
|
||||||
final file = File('${directory.path}/account_statement_$timestamp.pdf');
|
html.Url.revokeObjectUrl(url);
|
||||||
|
print('Generated PDF Blob URL for web: $url');
|
||||||
|
|
||||||
|
final anchor = html.document.createElement('a') as html.AnchorElement
|
||||||
|
..href = url
|
||||||
|
..style.display = 'none'
|
||||||
|
..download = fileName;
|
||||||
|
|
||||||
await file.writeAsBytes(await pdf.save());
|
html.document.body!.children.add(anchor);
|
||||||
|
anchor.click();
|
||||||
|
html.document.body!.children.remove(anchor);
|
||||||
|
|
||||||
if (mounted) {
|
html.Url.revokeObjectUrl(url);
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
if (mounted) {
|
||||||
SnackBar(
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
content: Text('PDF saved to: ${file.path}'),
|
SnackBar(
|
||||||
duration: const Duration(seconds: 3),
|
content: Text('PDF download started: $fileName'),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} catch (e) {
|
}
|
||||||
if (mounted) {
|
// For Android
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
else if (Platform.isAndroid) {
|
||||||
SnackBar(
|
final androidInfo = await DeviceInfoPlugin().androidInfo;
|
||||||
content: Text('Error saving PDF: $e'),
|
if (androidInfo.version.sdkInt < 29) {
|
||||||
duration: const Duration(seconds: 3),
|
final status = await Permission.storage.status;
|
||||||
),
|
if (status.isDenied) {
|
||||||
);
|
final result = await Permission.storage.request();
|
||||||
}
|
if (result.isDenied) {
|
||||||
}
|
if (mounted) {
|
||||||
}
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
const SnackBar(
|
||||||
|
content: Text('Storage permission is required to save PDF'),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final directory = Directory('/storage/emulated/0/Download');
|
||||||
|
final file = File('${directory.path}/$fileName');
|
||||||
|
await file.writeAsBytes(pdfBytes);
|
||||||
|
|
||||||
|
if (mounted) {
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
SnackBar(
|
||||||
|
content: Text('PDF saved to: ${file.path}'),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Add for IOS
|
||||||
|
|
||||||
|
} catch (e) {
|
||||||
|
if (mounted) {
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
SnackBar(
|
||||||
|
content: Text('Error saving PDF: $e'),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Widget buildDateBox(String label, DateTime? date) {
|
Widget buildDateBox(String label, DateTime? date) {
|
||||||
return Container(
|
return Container(
|
||||||
|
@@ -521,6 +521,8 @@ class _DashboardScreenState extends State<DashboardScreen> {
|
|||||||
.accountNo!,
|
.accountNo!,
|
||||||
balance: users[selectedAccountIndex]
|
balance: users[selectedAccountIndex]
|
||||||
.availableBalance!,
|
.availableBalance!,
|
||||||
|
accountType: users[selectedAccountIndex]
|
||||||
|
.accountType!,
|
||||||
)));
|
)));
|
||||||
}),
|
}),
|
||||||
_buildQuickLink(Symbols.checkbook,
|
_buildQuickLink(Symbols.checkbook,
|
||||||
|
@@ -40,7 +40,13 @@ class ProfileScreen extends StatelessWidget {
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
// You can add more profile options here later
|
ListTile(
|
||||||
|
leading: const Icon(Icons.password),
|
||||||
|
title: Text(loc.changePassword),
|
||||||
|
onTap: () {
|
||||||
|
|
||||||
|
},
|
||||||
|
),
|
||||||
ListTile(
|
ListTile(
|
||||||
leading: const Icon(Icons.logout),
|
leading: const Icon(Icons.logout),
|
||||||
title: Text(AppLocalizations.of(context).logout),
|
title: Text(AppLocalizations.of(context).logout),
|
||||||
@@ -55,6 +61,7 @@ class ProfileScreen extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
// You can add more profile options here later
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
@@ -291,5 +291,6 @@
|
|||||||
"search": "Search",
|
"search": "Search",
|
||||||
"viewCardDeatils": "View Card Details",
|
"viewCardDeatils": "View Card Details",
|
||||||
"logout": "Logout",
|
"logout": "Logout",
|
||||||
"logoutCheck": "Are you sure you want to logout?"
|
"logoutCheck": "Are you sure you want to logout?",
|
||||||
|
"changePassword": "Change Password"
|
||||||
}
|
}
|
||||||
|
@@ -292,5 +292,6 @@
|
|||||||
"search": "खोजें",
|
"search": "खोजें",
|
||||||
"viewCardDeatils": "कार्ड विवरण देखें",
|
"viewCardDeatils": "कार्ड विवरण देखें",
|
||||||
"logout": "लॉग आउट",
|
"logout": "लॉग आउट",
|
||||||
"logoutCheck": "क्या आप लॉग आउट करना चाहते हैं?"
|
"logoutCheck": "क्या आप लॉग आउट करना चाहते हैं?",
|
||||||
|
"changePassword": "पासवर्ड बदलें"
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user