PDF Edited
This commit is contained in:
@@ -1,7 +1,10 @@
|
|||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
import 'dart:typed_data';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
|
import 'package:intl/intl.dart';
|
||||||
import 'package:material_symbols_icons/material_symbols_icons.dart';
|
import 'package:material_symbols_icons/material_symbols_icons.dart';
|
||||||
|
import 'package:pdf/pdf.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';
|
||||||
@@ -9,13 +12,9 @@ import 'package:kmobile/di/injection.dart';
|
|||||||
import '../../../l10n/app_localizations.dart';
|
import '../../../l10n/app_localizations.dart';
|
||||||
import 'transaction_details_screen.dart';
|
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:device_info_plus/device_info_plus.dart';
|
|
||||||
import 'package:path_provider/path_provider.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:flutter_local_notifications/flutter_local_notifications.dart';
|
||||||
import 'package:open_filex/open_filex.dart';
|
import 'package:open_filex/open_filex.dart';
|
||||||
|
|
||||||
import 'package:kmobile/data/models/user.dart';
|
import 'package:kmobile/data/models/user.dart';
|
||||||
|
|
||||||
class AccountStatementScreen extends StatefulWidget {
|
class AccountStatementScreen extends StatefulWidget {
|
||||||
@@ -455,15 +454,12 @@ class _AccountStatementScreen extends State<AccountStatementScreen> {
|
|||||||
if (_transactions.isEmpty) {
|
if (_transactions.isEmpty) {
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
const SnackBar(
|
const SnackBar(content: Text('No transactions to export.')),
|
||||||
content: Text('No transactions to export.'),
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Show downloading notification
|
|
||||||
await flutterLocalNotificationsPlugin.show(
|
await flutterLocalNotificationsPlugin.show(
|
||||||
0,
|
0,
|
||||||
'Downloading PDF',
|
'Downloading PDF',
|
||||||
@@ -472,124 +468,111 @@ class _AccountStatementScreen extends State<AccountStatementScreen> {
|
|||||||
android: AndroidNotificationDetails(
|
android: AndroidNotificationDetails(
|
||||||
'download_channel',
|
'download_channel',
|
||||||
'Download Notifications',
|
'Download Notifications',
|
||||||
channelDescription:
|
channelDescription: 'Notifications for PDF downloads',
|
||||||
'Notifications for PDF downloads',
|
|
||||||
importance: Importance.low,
|
importance: Importance.low,
|
||||||
priority: Priority.low,
|
priority: Priority.low,
|
||||||
showProgress: true,
|
showProgress: true,
|
||||||
maxProgress: 0, // Indeterminate progress
|
maxProgress: 0,
|
||||||
ongoing: true,
|
ongoing: true,
|
||||||
icon: 'notification_icon',
|
icon: 'notification_icon',
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
var logo = await rootBundle.load('assets/images/logo.png');
|
// --- 1. LOAD ASSETS ---
|
||||||
var rubik = await rootBundle.load("assets/fonts/Rubik-Regular.ttf");
|
final logoImage =
|
||||||
final pdf = pw.Document();
|
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);
|
||||||
|
|
||||||
pdf.addPage(pw.MultiPage(
|
// --- 2. DEFINE COLORS ---
|
||||||
margin: const pw.EdgeInsets.all(20),
|
final primaryColor = PdfColor.fromHex("#1a5f3a");
|
||||||
build: (pw.Context context) {
|
final secondaryColor = PdfColor.fromHex("#2e7d32");
|
||||||
return [
|
final debitColor = PdfColor.fromHex("#d32f2f");
|
||||||
pw.Row(
|
final lightGreyColor = PdfColor.fromHex("#666");
|
||||||
mainAxisAlignment: pw.MainAxisAlignment.start,
|
final tableBorderColor = PdfColor.fromHex("#d0d0d0");
|
||||||
crossAxisAlignment: pw.CrossAxisAlignment.center,
|
final lightBgColor = PdfColor.fromHex("#f9f9f9");
|
||||||
children: [
|
final warningBgColor = PdfColor.fromHex("#f8d7da");
|
||||||
pw.Image(pw.MemoryImage(logo.buffer.asUint8List()),
|
final warningBorderColor = PdfColor.fromHex("#f5c6cb");
|
||||||
width: 50, height: 50),
|
final warningTextColor = PdfColor.fromHex("#721c24");
|
||||||
pw.SizedBox(width: 20),
|
|
||||||
pw.Text('Account Statement - KCCB',
|
// --- 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(
|
style: pw.TextStyle(
|
||||||
fontSize: 24, fontWeight: pw.FontWeight.bold)),
|
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.SizedBox(height: 20),
|
||||||
pw.Row(
|
pw.Text('END OF STATEMENT',
|
||||||
mainAxisAlignment: pw.MainAxisAlignment.spaceBetween,
|
style: const pw.TextStyle(fontSize: 12)),
|
||||||
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<pw.TableRow>((tx) {
|
);
|
||||||
return pw.TableRow(children: [
|
|
||||||
pw.Padding(
|
pdf.addPage(
|
||||||
padding: const pw.EdgeInsets.all(10),
|
pw.Page(
|
||||||
child: pw.Text(tx.date ?? '',
|
pageFormat: PdfPageFormat.a4.copyWith(
|
||||||
style: pw.TextStyle(
|
marginTop: 15 * PdfPageFormat.mm,
|
||||||
fontSize: 12,
|
marginLeft: 10 * PdfPageFormat.mm,
|
||||||
font: pw.Font.ttf(rubik),
|
marginRight: 10 * PdfPageFormat.mm,
|
||||||
))),
|
marginBottom: 20 * PdfPageFormat.mm,
|
||||||
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,
|
|
||||||
),
|
),
|
||||||
|
build: (context) => _buildLastPage(),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}));
|
|
||||||
|
|
||||||
|
// --- 5. SAVE AND NOTIFY ---
|
||||||
try {
|
try {
|
||||||
final Uint8List pdfBytes = await pdf.save();
|
final Uint8List pdfBytes = await pdf.save();
|
||||||
final String timestamp = DateTime.now().millisecondsSinceEpoch.toString();
|
final String timestamp =
|
||||||
final String fileName = 'account_statement_$timestamp.pdf';
|
DateFormat("ddMMyyyy_HHmm").format(DateTime.now());
|
||||||
|
final String fileName = 'Statement_${selectedUser.accountNo}_$timestamp.pdf';
|
||||||
|
|
||||||
String? filePath;
|
String? filePath;
|
||||||
|
|
||||||
@@ -601,20 +584,13 @@ class _AccountStatementScreen extends State<AccountStatementScreen> {
|
|||||||
final file = File('${directory.path}/$fileName');
|
final file = File('${directory.path}/$fileName');
|
||||||
await file.writeAsBytes(pdfBytes);
|
await file.writeAsBytes(pdfBytes);
|
||||||
filePath = file.path;
|
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 {
|
} else {
|
||||||
// For other platforms, we might just save to temporary directory
|
|
||||||
final tempDir = await getTemporaryDirectory();
|
final tempDir = await getTemporaryDirectory();
|
||||||
final file = await File('${tempDir.path}/$fileName').create();
|
final file = await File('${tempDir.path}/$fileName').create();
|
||||||
await file.writeAsBytes(pdfBytes);
|
await file.writeAsBytes(pdfBytes);
|
||||||
filePath = file.path;
|
filePath = file.path;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update notification to download complete
|
|
||||||
await flutterLocalNotificationsPlugin.show(
|
await flutterLocalNotificationsPlugin.show(
|
||||||
0,
|
0,
|
||||||
'PDF Download Complete',
|
'PDF Download Complete',
|
||||||
@@ -623,8 +599,7 @@ class _AccountStatementScreen extends State<AccountStatementScreen> {
|
|||||||
android: AndroidNotificationDetails(
|
android: AndroidNotificationDetails(
|
||||||
'download_channel',
|
'download_channel',
|
||||||
'Download Notifications',
|
'Download Notifications',
|
||||||
channelDescription:
|
channelDescription: 'Notifications for PDF downloads',
|
||||||
'Notifications for PDF downloads',
|
|
||||||
importance: Importance.high,
|
importance: Importance.high,
|
||||||
priority: Priority.high,
|
priority: Priority.high,
|
||||||
showProgress: false,
|
showProgress: false,
|
||||||
@@ -632,11 +607,8 @@ class _AccountStatementScreen extends State<AccountStatementScreen> {
|
|||||||
autoCancel: true,
|
autoCancel: true,
|
||||||
icon: 'notification_icon',
|
icon: 'notification_icon',
|
||||||
actions: [
|
actions: [
|
||||||
AndroidNotificationAction(
|
AndroidNotificationAction('open_pdf', 'Open PDF',
|
||||||
'open_pdf',
|
showsUserInterface: true)
|
||||||
'Open PDF',
|
|
||||||
showsUserInterface: true,
|
|
||||||
)
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -648,23 +620,413 @@ class _AccountStatementScreen extends State<AccountStatementScreen> {
|
|||||||
'PDF Download Failed',
|
'PDF Download Failed',
|
||||||
'Error saving PDF: $e',
|
'Error saving PDF: $e',
|
||||||
const NotificationDetails(
|
const NotificationDetails(
|
||||||
android: AndroidNotificationDetails(
|
android: AndroidNotificationDetails('download_channel', 'Download Notifications',
|
||||||
'download_channel',
|
channelDescription: 'Notifications for PDF downloads',
|
||||||
'Download Notifications',
|
|
||||||
channelDescription:
|
|
||||||
'Notifications for PDF downloads',
|
|
||||||
importance: Importance.high,
|
importance: Importance.high,
|
||||||
priority: Priority.high,
|
priority: Priority.high,
|
||||||
showProgress: false,
|
showProgress: false,
|
||||||
ongoing: false,
|
ongoing: false,
|
||||||
icon: 'notification_icon',
|
icon: 'notification_icon'),
|
||||||
),
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget buildDateBox(String label, DateTime? date) {
|
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)),
|
||||||
|
),
|
||||||
|
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.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),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
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),
|
||||||
|
},
|
||||||
|
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.'),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
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)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
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),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
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(
|
return Container(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 16),
|
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 16),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
|
|||||||
@@ -417,12 +417,24 @@ class _DashboardScreenState extends State<DashboardScreen>
|
|||||||
child: Scaffold(
|
child: Scaffold(
|
||||||
backgroundColor: theme.scaffoldBackgroundColor,
|
backgroundColor: theme.scaffoldBackgroundColor,
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
backgroundColor: theme.scaffoldBackgroundColor,
|
backgroundColor: const Color(0xFF01A04C),
|
||||||
leading: const Padding(
|
leading: Padding(
|
||||||
padding: EdgeInsets.only(left: 10.0),
|
padding: const EdgeInsets.only(left: 10.0),
|
||||||
|
//
|
||||||
child: CircleAvatar(
|
child: CircleAvatar(
|
||||||
radius: 15,
|
radius: 20,
|
||||||
backgroundImage: AssetImage('assets/images/logo.png'),
|
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(
|
title: Text(
|
||||||
@@ -431,7 +443,7 @@ class _DashboardScreenState extends State<DashboardScreen>
|
|||||||
softWrap: true, // Explicitly allow wrapping
|
softWrap: true, // Explicitly allow wrapping
|
||||||
maxLines: 2, // Allow text to wrap to a maximum of 2 lines
|
maxLines: 2, // Allow text to wrap to a maximum of 2 lines
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: theme.colorScheme.primary,
|
color: theme.colorScheme.onPrimary,
|
||||||
fontWeight: FontWeight.w700,
|
fontWeight: FontWeight.w700,
|
||||||
fontSize: 20,
|
fontSize: 20,
|
||||||
),
|
),
|
||||||
|
|||||||
Reference in New Issue
Block a user