PDF Edited
This commit is contained in:
@@ -1,7 +1,10 @@
|
||||
import 'dart:io';
|
||||
import 'dart:typed_data';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:material_symbols_icons/material_symbols_icons.dart';
|
||||
import 'package:pdf/pdf.dart';
|
||||
import 'package:shimmer/shimmer.dart';
|
||||
import 'package:kmobile/data/models/transaction.dart';
|
||||
import 'package:kmobile/data/repositories/transaction_repository.dart';
|
||||
@@ -9,13 +12,9 @@ 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 {
|
||||
@@ -451,220 +450,583 @@ class _AccountStatementScreen extends State<AccountStatementScreen> {
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _exportToPdf() async {
|
||||
if (_transactions.isEmpty) {
|
||||
if (mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(
|
||||
content: Text('No transactions to export.'),
|
||||
Future<void> _exportToPdf() async {
|
||||
if (_transactions.isEmpty) {
|
||||
if (mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(content: Text('No transactions to export.')),
|
||||
);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
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,
|
||||
ongoing: true,
|
||||
icon: 'notification_icon',
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
// --- 1. LOAD ASSETS ---
|
||||
final logoImage =
|
||||
pw.MemoryImage((await rootBundle.load('assets/images/logo.png')).buffer.asUint8List());
|
||||
final timesFont = await rootBundle.load("assets/fonts/Rubik-Regular.ttf");
|
||||
final timesBoldFont = await rootBundle.load("assets/fonts/Rubik-Bold.ttf");
|
||||
final ttf = pw.Font.ttf(timesFont);
|
||||
final ttfBold = pw.Font.ttf(timesBoldFont);
|
||||
|
||||
// --- 2. DEFINE COLORS ---
|
||||
final primaryColor = PdfColor.fromHex("#1a5f3a");
|
||||
final secondaryColor = PdfColor.fromHex("#2e7d32");
|
||||
final debitColor = PdfColor.fromHex("#d32f2f");
|
||||
final lightGreyColor = PdfColor.fromHex("#666");
|
||||
final tableBorderColor = PdfColor.fromHex("#d0d0d0");
|
||||
final lightBgColor = PdfColor.fromHex("#f9f9f9");
|
||||
final warningBgColor = PdfColor.fromHex("#f8d7da");
|
||||
final warningBorderColor = PdfColor.fromHex("#f5c6cb");
|
||||
final warningTextColor = PdfColor.fromHex("#721c24");
|
||||
|
||||
// --- 3. CREATE PDF ---
|
||||
final pdf = pw.Document(
|
||||
theme: pw.ThemeData.withFont(base: ttf, bold: ttfBold),
|
||||
);
|
||||
|
||||
// --- 4. BUILD PAGES ---
|
||||
pdf.addPage(
|
||||
pw.MultiPage(
|
||||
pageFormat: PdfPageFormat.a4.copyWith(
|
||||
marginTop: 15 * PdfPageFormat.mm,
|
||||
marginLeft: 10 * PdfPageFormat.mm,
|
||||
marginRight: 10 * PdfPageFormat.mm,
|
||||
marginBottom: 20 * PdfPageFormat.mm,
|
||||
),
|
||||
header: (context) =>
|
||||
_buildHeader(logoImage, primaryColor, lightGreyColor),
|
||||
footer: (context) {
|
||||
return pw.Center(
|
||||
child: pw.Text(
|
||||
'** This is only for information purpose and not for legal use **',
|
||||
style: pw.TextStyle(
|
||||
fontSize: 9,
|
||||
color: lightGreyColor,
|
||||
fontStyle: pw.FontStyle.italic)));
|
||||
},
|
||||
build: (context) => [
|
||||
_buildAccountDetails(
|
||||
customerName: selectedUser.name ?? '',
|
||||
branchCode: selectedUser.branchId ?? '',
|
||||
accountNo: selectedUser.accountNo ?? '',
|
||||
cifNumber: selectedUser.cifNumber ?? '',
|
||||
address: selectedUser.address ?? '',
|
||||
lightGreyColor: lightGreyColor,
|
||||
tableBorderColor: tableBorderColor,
|
||||
lightBgColor: lightBgColor,
|
||||
),
|
||||
_buildWarning(warningBgColor, warningBorderColor, debitColor,
|
||||
warningTextColor),
|
||||
_buildPeriodHeader(
|
||||
primaryColor: primaryColor,
|
||||
fromDate: fromDate,
|
||||
toDate: toDate,
|
||||
),
|
||||
_buildTransactionsTable(
|
||||
transactions: _transactions,
|
||||
primaryColor: primaryColor,
|
||||
secondaryColor: secondaryColor,
|
||||
debitColor: debitColor,
|
||||
tableBorderColor: tableBorderColor,
|
||||
),
|
||||
pw.SizedBox(height: 20),
|
||||
pw.Text('END OF STATEMENT',
|
||||
style: const pw.TextStyle(fontSize: 12)),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
pdf.addPage(
|
||||
pw.Page(
|
||||
pageFormat: PdfPageFormat.a4.copyWith(
|
||||
marginTop: 15 * PdfPageFormat.mm,
|
||||
marginLeft: 10 * PdfPageFormat.mm,
|
||||
marginRight: 10 * PdfPageFormat.mm,
|
||||
marginBottom: 20 * PdfPageFormat.mm,
|
||||
),
|
||||
build: (context) => _buildLastPage(),
|
||||
),
|
||||
);
|
||||
|
||||
// --- 5. SAVE AND NOTIFY ---
|
||||
try {
|
||||
final Uint8List pdfBytes = await pdf.save();
|
||||
final String timestamp =
|
||||
DateFormat("ddMMyyyy_HHmm").format(DateTime.now());
|
||||
final String fileName = 'Statement_${selectedUser.accountNo}_$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 {
|
||||
final tempDir = await getTemporaryDirectory();
|
||||
final file = await File('${tempDir.path}/$fileName').create();
|
||||
await file.writeAsBytes(pdfBytes);
|
||||
filePath = file.path;
|
||||
}
|
||||
|
||||
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'),
|
||||
),
|
||||
);
|
||||
}
|
||||
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',
|
||||
|
||||
pw.Widget _buildHeader(pw.MemoryImage logoImage, PdfColor primaryColor,
|
||||
PdfColor lightGreyColor) {
|
||||
return pw.Container(
|
||||
margin: const pw.EdgeInsets.only(bottom: 15),
|
||||
padding: const pw.EdgeInsets.only(bottom: 12),
|
||||
decoration: pw.BoxDecoration(
|
||||
border: pw.Border(bottom: pw.BorderSide(color: primaryColor, width: 2)),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
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,
|
||||
child: pw.Row(
|
||||
children: [
|
||||
pw.Image(logoImage, height: 55, width: 55),
|
||||
pw.SizedBox(width: 12),
|
||||
pw.Expanded(
|
||||
child: pw.Column(
|
||||
crossAxisAlignment: pw.CrossAxisAlignment.start,
|
||||
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')),
|
||||
pw.Text(
|
||||
'THE KANGRA CENTRAL CO-OPERATIVE BANK LTD.',
|
||||
style: pw.TextStyle(
|
||||
fontSize: 18,
|
||||
color: primaryColor,
|
||||
fontWeight: pw.FontWeight.bold,
|
||||
letterSpacing: 0.3),
|
||||
),
|
||||
pw.Text(
|
||||
'Head Office: Dharmsala, District Kangra (H.P.), Pin. 176215',
|
||||
style: pw.TextStyle(fontSize: 10, color: lightGreyColor),
|
||||
),
|
||||
],
|
||||
),
|
||||
..._transactions.map<pw.TableRow>((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),
|
||||
))),
|
||||
]);
|
||||
}),
|
||||
])
|
||||
];
|
||||
),
|
||||
pw.Column(
|
||||
crossAxisAlignment: pw.CrossAxisAlignment.end,
|
||||
children: [
|
||||
pw.Text(
|
||||
'e-Statement Service',
|
||||
style: pw.TextStyle(
|
||||
fontSize: 11,
|
||||
color: primaryColor,
|
||||
fontWeight: pw.FontWeight.bold),
|
||||
),
|
||||
pw.SizedBox(height: 2),
|
||||
pw.Text(
|
||||
'Generated: ${DateFormat("dd/MM/yyyy HH:mm").format(DateTime.now())}',
|
||||
style: pw.TextStyle(fontSize: 9, color: lightGreyColor),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
pw.Widget _buildAccountDetails({
|
||||
required String customerName,
|
||||
required String branchCode,
|
||||
required String accountNo,
|
||||
required String cifNumber,
|
||||
required String address,
|
||||
required PdfColor lightGreyColor,
|
||||
required PdfColor tableBorderColor,
|
||||
required PdfColor lightBgColor,
|
||||
}) {
|
||||
const cellPadding = pw.EdgeInsets.symmetric(horizontal: 12, vertical: 8);
|
||||
final border = pw.BorderSide(color: tableBorderColor, width: 1);
|
||||
|
||||
return pw.Table(
|
||||
border: pw.TableBorder(
|
||||
top: border,
|
||||
bottom: border,
|
||||
left: border,
|
||||
right: border,
|
||||
horizontalInside: border,
|
||||
verticalInside: border,
|
||||
),
|
||||
columnWidths: {
|
||||
0: const pw.FlexColumnWidth(1),
|
||||
1: const pw.FlexColumnWidth(1),
|
||||
},
|
||||
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,
|
||||
children: [
|
||||
pw.TableRow(
|
||||
children: [
|
||||
_buildDetailCell('Customer Name', customerName, cellPadding, lightGreyColor),
|
||||
_buildDetailCell('CIF Number', cifNumber, cellPadding, lightGreyColor),
|
||||
],
|
||||
),
|
||||
pw.TableRow(
|
||||
children: [
|
||||
_buildDetailCell('Account Number', accountNo, cellPadding, lightGreyColor),
|
||||
_buildDetailCell('Branch Code', branchCode, cellPadding, lightGreyColor),
|
||||
],
|
||||
),
|
||||
pw.TableRow(
|
||||
children: [
|
||||
pw.Container(
|
||||
padding: cellPadding,
|
||||
// Using a Column inside a single cell to potentially wrap long address
|
||||
child: pw.Column(
|
||||
crossAxisAlignment: pw.CrossAxisAlignment.start,
|
||||
children: [
|
||||
pw.Text(
|
||||
'Customer Address',
|
||||
style: pw.TextStyle(fontSize: 10, color: lightGreyColor),
|
||||
),
|
||||
pw.SizedBox(height: 2),
|
||||
pw.Text(
|
||||
address,
|
||||
style: pw.TextStyle(fontSize: 11, fontWeight: pw.FontWeight.bold),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
// Empty container for the second column in this row, as it's a single spanning column
|
||||
pw.Container(),
|
||||
],
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
pw.Widget _buildDetailCell(String label, String value, pw.EdgeInsets padding,
|
||||
PdfColor lightGreyColor, {bool vertical = false}) {
|
||||
final children = [
|
||||
pw.Text(
|
||||
label,
|
||||
style: pw.TextStyle(fontSize: 10, color: lightGreyColor),
|
||||
),
|
||||
if (vertical) pw.SizedBox(height: 2),
|
||||
pw.Text(
|
||||
value,
|
||||
style: pw.TextStyle(fontSize: 11, fontWeight: pw.FontWeight.bold),
|
||||
),
|
||||
];
|
||||
return pw.Padding(
|
||||
padding: padding,
|
||||
child: vertical
|
||||
? pw.Column(
|
||||
crossAxisAlignment: pw.CrossAxisAlignment.start,
|
||||
mainAxisAlignment: pw.MainAxisAlignment.start,
|
||||
children: children)
|
||||
: pw.Row(
|
||||
mainAxisAlignment: pw.MainAxisAlignment.spaceBetween,
|
||||
children: children),
|
||||
);
|
||||
}
|
||||
|
||||
pw.Widget _buildWarning(PdfColor warningBgColor, PdfColor warningBorderColor,
|
||||
PdfColor debitColor, PdfColor warningTextColor) {
|
||||
return pw.Container(
|
||||
margin: const pw.EdgeInsets.symmetric(vertical: 15),
|
||||
padding: const pw.EdgeInsets.all(10),
|
||||
decoration: pw.BoxDecoration(
|
||||
color: warningBgColor,
|
||||
border: pw.Border.all(color: warningBorderColor, width: 1),
|
||||
borderRadius: pw.BorderRadius.circular(4),
|
||||
),
|
||||
child: pw.Row(
|
||||
crossAxisAlignment: pw.CrossAxisAlignment.start,
|
||||
children: [
|
||||
pw.SizedBox(width: 10),
|
||||
pw.Expanded(
|
||||
child: pw.RichText(
|
||||
text: pw.TextSpan(
|
||||
style: pw.TextStyle(
|
||||
fontSize: 10,
|
||||
color: warningTextColor,
|
||||
lineSpacing: 1.5),
|
||||
children: [
|
||||
pw.TextSpan(
|
||||
text: 'NEVER SHARE ',
|
||||
style: pw.TextStyle(fontWeight: pw.FontWeight.bold)),
|
||||
const pw.TextSpan(
|
||||
text:
|
||||
'your Card number, CVV, PIN, OTP, Internet Banking User ID, Password or URB with anyone even if the caller claims to be a bank employee. Sharing these details can lead to unauthorized access to your account.'),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}));
|
||||
|
||||
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,
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
pw.Widget _buildPeriodHeader(
|
||||
{required PdfColor primaryColor, DateTime? fromDate, DateTime? toDate}) {
|
||||
String from = fromDate != null
|
||||
? DateFormat('dd/MM/yyyy').format(fromDate)
|
||||
: 'the beginning';
|
||||
String to =
|
||||
toDate != null ? DateFormat('dd/MM/yyyy').format(toDate) : 'today';
|
||||
|
||||
return pw.Container(
|
||||
margin: const pw.EdgeInsets.only(bottom: 12),
|
||||
padding: const pw.EdgeInsets.all(10),
|
||||
decoration: pw.BoxDecoration(
|
||||
border: pw.Border.symmetric(
|
||||
horizontal: pw.BorderSide(color: primaryColor, width: 2)),
|
||||
color: PdfColor.fromHex("#f5f5f5"),
|
||||
),
|
||||
alignment: pw.Alignment.center,
|
||||
child: pw.RichText(
|
||||
text: pw.TextSpan(
|
||||
style: pw.TextStyle(
|
||||
fontSize: 13,
|
||||
color: primaryColor,
|
||||
fontWeight: pw.FontWeight.bold),
|
||||
children: [
|
||||
const pw.TextSpan(text: 'Account statement from '),
|
||||
pw.TextSpan(
|
||||
text: from,
|
||||
style: pw.TextStyle(fontWeight: pw.FontWeight.bold)),
|
||||
const pw.TextSpan(text: ' to '),
|
||||
pw.TextSpan(
|
||||
text: to,
|
||||
style: pw.TextStyle(fontWeight: pw.FontWeight.bold)),
|
||||
],
|
||||
),
|
||||
),
|
||||
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',
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
pw.Widget _buildTransactionsTable({
|
||||
required List<Transaction> transactions,
|
||||
required PdfColor primaryColor,
|
||||
required PdfColor secondaryColor,
|
||||
required PdfColor debitColor,
|
||||
required PdfColor tableBorderColor,
|
||||
}) {
|
||||
//final border = pw.BorderSide(color: tableBorderColor, width: 1);
|
||||
|
||||
return pw.Table(
|
||||
border: pw.TableBorder.all(color: tableBorderColor, width: 1),
|
||||
columnWidths: {
|
||||
0: const pw.FlexColumnWidth(1.5),
|
||||
1: const pw.FlexColumnWidth(4),
|
||||
2: const pw.FlexColumnWidth(2.2),
|
||||
3: const pw.FlexColumnWidth(2.2),
|
||||
},
|
||||
children: [
|
||||
pw.TableRow(
|
||||
decoration: pw.BoxDecoration(color: secondaryColor),
|
||||
children: [
|
||||
pw.Padding(
|
||||
padding: const pw.EdgeInsets.all(10),
|
||||
child: pw.Text('Date',
|
||||
textAlign: pw.TextAlign.center,
|
||||
style: pw.TextStyle(
|
||||
fontSize: 11,
|
||||
fontWeight: pw.FontWeight.bold,
|
||||
color: PdfColors.white)),
|
||||
),
|
||||
pw.Padding(
|
||||
padding: const pw.EdgeInsets.all(10),
|
||||
child: pw.Text('Mode / Particulars',
|
||||
textAlign: pw.TextAlign.left,
|
||||
style: pw.TextStyle(
|
||||
fontSize: 11,
|
||||
fontWeight: pw.FontWeight.bold,
|
||||
color: PdfColors.white)),
|
||||
),
|
||||
pw.Padding(
|
||||
padding: const pw.EdgeInsets.all(10),
|
||||
child: pw.Text('Withdrawals / Deposits',
|
||||
textAlign: pw.TextAlign.center,
|
||||
style: pw.TextStyle(
|
||||
fontSize: 11,
|
||||
fontWeight: pw.FontWeight.bold,
|
||||
color: PdfColors.white)),
|
||||
),
|
||||
pw.Padding(
|
||||
padding: const pw.EdgeInsets.all(10),
|
||||
child: pw.Text('Balance',
|
||||
textAlign: pw.TextAlign.center,
|
||||
style: pw.TextStyle(
|
||||
fontSize: 11,
|
||||
fontWeight: pw.FontWeight.bold,
|
||||
color: PdfColors.white)),
|
||||
),
|
||||
],
|
||||
),
|
||||
...transactions.map((tx) {
|
||||
final amount = double.tryParse(tx.amount ?? '0') ?? 0;
|
||||
final isDebit = tx.type == 'DR';
|
||||
return pw.TableRow(
|
||||
decoration: const pw.BoxDecoration(color: PdfColors.white),
|
||||
children: [
|
||||
pw.Padding(
|
||||
padding: const pw.EdgeInsets.all(8),
|
||||
child: pw.Text(tx.date ?? '',
|
||||
textAlign: pw.TextAlign.center,
|
||||
style: const pw.TextStyle(fontSize: 11)),
|
||||
),
|
||||
pw.Padding(
|
||||
padding: const pw.EdgeInsets.all(8),
|
||||
child: pw.Text(tx.name ?? '',
|
||||
textAlign: pw.TextAlign.left,
|
||||
style: const pw.TextStyle(fontSize: 11)),
|
||||
),
|
||||
pw.Padding(
|
||||
padding: const pw.EdgeInsets.all(8),
|
||||
child: pw.Text(
|
||||
'${NumberFormat.currency(locale: 'en_IN', symbol: '₹').format(amount)} ${tx.type}',
|
||||
textAlign: pw.TextAlign.right,
|
||||
style: pw.TextStyle(
|
||||
fontSize: 11,
|
||||
color: isDebit ? debitColor : secondaryColor,
|
||||
fontWeight: pw.FontWeight.bold,
|
||||
),
|
||||
),
|
||||
),
|
||||
pw.Padding(
|
||||
padding: const pw.EdgeInsets.all(8),
|
||||
child: pw.Text(
|
||||
'${NumberFormat.currency(locale: 'en_IN', symbol: '₹').format(double.tryParse(tx.balance ?? '0') ?? 0)} ${tx.balanceType ?? ''}',
|
||||
textAlign: pw.TextAlign.right,
|
||||
style: pw.TextStyle(
|
||||
fontSize: 11, fontWeight: pw.FontWeight.bold),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget buildDateBox(String label, DateTime? date) {
|
||||
|
||||
pw.Widget _buildLastPage() {
|
||||
return pw.Container(
|
||||
padding: const pw.EdgeInsets.all(20),
|
||||
decoration: pw.BoxDecoration(
|
||||
border: pw.Border.all(color: PdfColors.black, width: 2),
|
||||
color: PdfColor.fromHex("#fafafa"),
|
||||
borderRadius: pw.BorderRadius.circular(4),
|
||||
),
|
||||
child: pw.Column(
|
||||
crossAxisAlignment: pw.CrossAxisAlignment.start,
|
||||
children: [
|
||||
pw.Text(
|
||||
'IMPORTANT INFORMATION:',
|
||||
style: pw.TextStyle(
|
||||
fontSize: 11,
|
||||
fontWeight: pw.FontWeight.bold,
|
||||
color: PdfColors.black,
|
||||
),
|
||||
),
|
||||
pw.SizedBox(height: 15),
|
||||
..._buildInfoPoints(PdfColors.black, [
|
||||
'The Kangra Central Cooperative Bank Officials or representatives will NEVER ask you for your personal information i.e. your card details, passwords, PIN, CVV, OTP etc. Do not share such details with anyone over phone, SMS or email.',
|
||||
'Always stay vigilant to suspicious emails. Do not open any suspicious emails.',
|
||||
'Always stay vigilant when giving out sensitive personal or account information.',
|
||||
'Beware of messages that instill a sense of urgency (e.g., account will expire unless you "verify" your information). Contact the Bank directly if unsure.',
|
||||
'Always log out of secondary devices and reset your passwords frequently.',
|
||||
'Use strong passwords: Create strong passwords that are difficult for hackers to guess.',
|
||||
'Use public Wi-Fi with caution: Be careful when using public Wi-Fi networks.',
|
||||
'Back up your data regularly to a secure, encrypted, off-site location.',
|
||||
'Follow corporate security policies: Adhere to your company\'s security guidelines.',
|
||||
'Assess third-party app permissions carefully before granting access.',
|
||||
'Lock your computer and mobile phone when not in use.',
|
||||
'Don\'t leave devices unattended. Keep all mobile devices, such as laptops and cell phones, physically secured.',
|
||||
'Don\'t leave Bluetooth / Wireless turned on when not in use. Enable them only when needed and in a safe environment.',
|
||||
]),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
List<pw.Widget> _buildInfoPoints(
|
||||
PdfColor primaryColor, List<String> points) {
|
||||
return points.map((point) {
|
||||
return pw.Padding(
|
||||
padding: const pw.EdgeInsets.only(bottom: 10),
|
||||
child: pw.Row(
|
||||
crossAxisAlignment: pw.CrossAxisAlignment.start,
|
||||
children: [
|
||||
pw.Container(
|
||||
width: 15,
|
||||
child: pw.Text('*',
|
||||
style: pw.TextStyle(
|
||||
color: primaryColor,
|
||||
fontSize: 12,
|
||||
fontWeight: pw.FontWeight.bold))),
|
||||
pw.Expanded(
|
||||
child: pw.Text(
|
||||
point,
|
||||
style: pw.TextStyle(
|
||||
fontSize: 11,
|
||||
lineSpacing: 1.6,
|
||||
fontWeight: pw.FontWeight.bold),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}).toList();
|
||||
} Widget buildDateBox(String label, DateTime? date) {
|
||||
return Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 16),
|
||||
decoration: BoxDecoration(
|
||||
|
||||
@@ -417,13 +417,25 @@ class _DashboardScreenState extends State<DashboardScreen>
|
||||
child: Scaffold(
|
||||
backgroundColor: theme.scaffoldBackgroundColor,
|
||||
appBar: AppBar(
|
||||
backgroundColor: theme.scaffoldBackgroundColor,
|
||||
leading: const Padding(
|
||||
padding: EdgeInsets.only(left: 10.0),
|
||||
child: CircleAvatar(
|
||||
radius: 15,
|
||||
backgroundImage: AssetImage('assets/images/logo.png'),
|
||||
),
|
||||
backgroundColor: const Color(0xFF01A04C),
|
||||
leading: Padding(
|
||||
padding: const EdgeInsets.only(left: 10.0),
|
||||
//
|
||||
child: CircleAvatar(
|
||||
radius: 20,
|
||||
backgroundColor: Colors.white,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(2.0),
|
||||
child: ClipOval(
|
||||
child: Image.asset(
|
||||
'assets/images/logo.png',
|
||||
width: 40,
|
||||
height: 40,
|
||||
fit: BoxFit.cover,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
title: Text(
|
||||
AppLocalizations.of(context).kccBankFull.replaceAll('-', '\u2011'),
|
||||
@@ -431,7 +443,7 @@ class _DashboardScreenState extends State<DashboardScreen>
|
||||
softWrap: true, // Explicitly allow wrapping
|
||||
maxLines: 2, // Allow text to wrap to a maximum of 2 lines
|
||||
style: TextStyle(
|
||||
color: theme.colorScheme.primary,
|
||||
color: theme.colorScheme.onPrimary,
|
||||
fontWeight: FontWeight.w700,
|
||||
fontSize: 20,
|
||||
),
|
||||
|
||||
Reference in New Issue
Block a user