From d8d87e8da4dc75b68c0323ea7fc18272baa29eb8 Mon Sep 17 00:00:00 2001 From: Nilanjan Chakrabarti Date: Mon, 8 Sep 2025 13:07:35 +0530 Subject: [PATCH] Localization Changes #6 --- .../screens/account_statement_screen.dart | 180 ++++++++++++++++++ .../{preferences => }/logout_dialog.dart | 10 +- lib/features/profile/profile_screen.dart | 4 +- lib/l10n/app_en.arb | 4 +- lib/l10n/app_hi.arb | 4 +- lib/main.dart | 22 +-- macos/Flutter/GeneratedPluginRegistrant.swift | 2 + pubspec.lock | 104 ++++++++++ pubspec.yaml | 3 + .../flutter/generated_plugin_registrant.cc | 3 + windows/flutter/generated_plugins.cmake | 1 + 11 files changed, 317 insertions(+), 20 deletions(-) rename lib/features/profile/{preferences => }/logout_dialog.dart (60%) diff --git a/lib/features/accounts/screens/account_statement_screen.dart b/lib/features/accounts/screens/account_statement_screen.dart index 26dd65a..6e87cc2 100644 --- a/lib/features/accounts/screens/account_statement_screen.dart +++ b/lib/features/accounts/screens/account_statement_screen.dart @@ -1,11 +1,17 @@ +import 'dart:io'; import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; import 'package:material_symbols_icons/material_symbols_icons.dart'; +import 'package:path_provider/path_provider.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'; class AccountStatementScreen extends StatefulWidget { final String accountNo; @@ -27,6 +33,7 @@ class _AccountStatementScreen extends State { List _transactions = []; final _minAmountController = TextEditingController(); final _maxAmountController = TextEditingController(); + Future?>? accountStatementsFuture; @override void initState() { @@ -308,9 +315,182 @@ class _AccountStatementScreen extends State { ], ), ), + floatingActionButton: FloatingActionButton( + onPressed: () { + _exportToPdf(); + }, + child: const Icon(Icons.download), + ), ); } + Future _exportToPdf() async { + if (accountStatementsFuture == null) return; + if (Platform.isAndroid) { + final androidInfo = await DeviceInfoPlugin().androidInfo; + if (androidInfo.version.sdkInt < 29) { + 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'), + duration: Duration(seconds: 3), + ), + ); + } + return; + } + } + } + } + + String logoSvg = await rootBundle.loadString('assets/logos/logo.svg'); + var rubik = await rootBundle.load("assets/fonts/Rubik-Regular.ttf"); + try { + final statements = await accountStatementsFuture; + if (statements == null) return; + 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(AppLocalizations.of(context as BuildContext).kccBankFull, + 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: selectedAccountType', + 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.5), + 2: const pw.FractionColumnWidth(0.15), + 3: const pw.FractionColumnWidth(0.15), + }, 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')), + ], + ), + ...statements['accountStatement'].map((statement) { + return pw.TableRow(children: [ + pw.Padding( + padding: const pw.EdgeInsets.all(10), + child: pw.Text( + '${statement['postDate']} ${statement['postTime']}', + style: pw.TextStyle( + fontSize: 12, + font: pw.Font.ttf(rubik), + ))), + pw.Padding( + padding: const pw.EdgeInsets.all(10), + child: pw.Text(statement['narration'], + style: pw.TextStyle( + fontSize: 12, font: pw.Font.ttf(rubik)))), + pw.Padding( + padding: const pw.EdgeInsets.all(10), + child: pw.Text("₹${statement['transactionAmount']}", + style: pw.TextStyle( + fontSize: 12, + 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(), + ]) + ]; + }, + 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, + ), + ), + ); + })); + + Directory? directory; + if (Platform.isAndroid) { + directory = Directory('/storage/emulated/0/Download'); + } else { + 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'), + duration: const Duration(seconds: 3), + ), + ); + } + } + } + + Widget buildDateBox(String label, DateTime? date) { return Container( padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 16), diff --git a/lib/features/profile/preferences/logout_dialog.dart b/lib/features/profile/logout_dialog.dart similarity index 60% rename from lib/features/profile/preferences/logout_dialog.dart rename to lib/features/profile/logout_dialog.dart index 3924731..4b930d3 100644 --- a/lib/features/profile/preferences/logout_dialog.dart +++ b/lib/features/profile/logout_dialog.dart @@ -1,21 +1,23 @@ import 'package:flutter/material.dart'; +import '../../l10n/app_localizations.dart'; + class LogoutDialog extends StatelessWidget { const LogoutDialog({super.key}); @override Widget build(BuildContext context) { return AlertDialog( - title: const Text("Logout"), - content: const Text("Are you sure you want to logout?"), + title: Text(AppLocalizations.of(context).logout), + content: Text(AppLocalizations.of(context).logoutCheck), actions: [ TextButton( onPressed: () => Navigator.pop(context, false), // dismiss - child: const Text("No"), + child: Text(AppLocalizations.of(context).no), ), TextButton( onPressed: () => Navigator.pop(context, true), // confirm - child: const Text("Yes"), + child: Text(AppLocalizations.of(context).yes), ), ], ); diff --git a/lib/features/profile/profile_screen.dart b/lib/features/profile/profile_screen.dart index f41e142..9462610 100644 --- a/lib/features/profile/profile_screen.dart +++ b/lib/features/profile/profile_screen.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; import 'package:kmobile/data/repositories/auth_repository.dart'; -import 'package:kmobile/features/profile/preferences/logout_dialog.dart'; +import 'package:kmobile/features/profile/logout_dialog.dart'; import 'package:shared_preferences/shared_preferences.dart'; import '../../di/injection.dart'; import '../../l10n/app_localizations.dart'; @@ -43,7 +43,7 @@ class ProfileScreen extends StatelessWidget { // You can add more profile options here later ListTile( leading: const Icon(Icons.logout), - title: const Text("Logout"), + title: Text(AppLocalizations.of(context).logout), onTap: () async { final shouldLogout = await showDialog( context: context, diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 358dc80..c94772a 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -289,5 +289,7 @@ "beneficiarydetails": "Beneficiary Details", "delete": "Delete", "search": "Search", -"viewCardDeatils": "View Card Details" +"viewCardDeatils": "View Card Details", +"logout": "Logout", +"logoutCheck": "Are you sure you want to logout?" } diff --git a/lib/l10n/app_hi.arb b/lib/l10n/app_hi.arb index 6d493df..67977c9 100644 --- a/lib/l10n/app_hi.arb +++ b/lib/l10n/app_hi.arb @@ -290,5 +290,7 @@ "beneficiarydetails": "लाभार्थी विवरण", "delete": "मिटाओ", "search": "खोजें", -"viewCardDeatils": "कार्ड विवरण देखें" +"viewCardDeatils": "कार्ड विवरण देखें", +"logout": "लॉग आउट", +"logoutCheck": "क्या आप लॉग आउट करना चाहते हैं?" } diff --git a/lib/main.dart b/lib/main.dart index 90db376..a62f353 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,3 +1,5 @@ +// ignore_for_file: unused_import + import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:kmobile/features/security/security_error_screen.dart'; @@ -15,19 +17,15 @@ void main() async { ]); // Check for device compromise - final compromisedMessage = await SecurityService.deviceCompromisedMessage; - if (compromisedMessage != null) { - runApp(MaterialApp( - home: SecurityErrorScreen(message: compromisedMessage), - )); - return; - } + // final compromisedMessage = await SecurityService.deviceCompromisedMessage; + // if (compromisedMessage != null) { + // runApp(MaterialApp( + // home: SecurityErrorScreen(message: compromisedMessage), + // )); + // return; + // } // Initialize dependencies await setupDependencies(); runApp(const KMobile()); -} - - - - +} \ No newline at end of file diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index 2b90d63..1e2316e 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -5,6 +5,7 @@ import FlutterMacOS import Foundation +import device_info_plus import flutter_secure_storage_macos import local_auth_darwin import path_provider_foundation @@ -13,6 +14,7 @@ import shared_preferences_foundation import url_launcher_macos func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { + DeviceInfoPlusMacosPlugin.register(with: registry.registrar(forPlugin: "DeviceInfoPlusMacosPlugin")) FlutterSecureStoragePlugin.register(with: registry.registrar(forPlugin: "FlutterSecureStoragePlugin")) FLALocalAuthPlugin.register(with: registry.registrar(forPlugin: "FLALocalAuthPlugin")) PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) diff --git a/pubspec.lock b/pubspec.lock index 2dd6a96..10b95d9 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -25,6 +25,22 @@ packages: url: "https://pub.dev" source: hosted version: "2.11.0" + barcode: + dependency: transitive + description: + name: barcode + sha256: "7b6729c37e3b7f34233e2318d866e8c48ddb46c1f7ad01ff7bb2a8de1da2b9f4" + url: "https://pub.dev" + source: hosted + version: "2.2.9" + bidi: + dependency: transitive + description: + name: bidi + sha256: "77f475165e94b261745cf1032c751e2032b8ed92ccb2bf5716036db79320637d" + url: "https://pub.dev" + source: hosted + version: "2.0.13" bloc: dependency: "direct main" description: @@ -121,6 +137,22 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.8" + device_info_plus: + dependency: "direct main" + description: + name: device_info_plus + sha256: "72d146c6d7098689ff5c5f66bcf593ac11efc530095385356e131070333e64da" + url: "https://pub.dev" + source: hosted + version: "11.3.0" + device_info_plus_platform_interface: + dependency: transitive + description: + name: device_info_plus_platform_interface + sha256: "0b04e02b30791224b31969eb1b50d723498f402971bff3630bca2ba839bd1ed2" + url: "https://pub.dev" + source: hosted + version: "7.0.2" dio: dependency: "direct main" description: @@ -573,6 +605,62 @@ packages: url: "https://pub.dev" source: hosted version: "2.3.0" + pdf: + dependency: "direct main" + description: + name: pdf + sha256: "28eacad99bffcce2e05bba24e50153890ad0255294f4dd78a17075a2ba5c8416" + url: "https://pub.dev" + source: hosted + version: "3.11.3" + permission_handler: + dependency: "direct main" + description: + name: permission_handler + sha256: bc917da36261b00137bbc8896bf1482169cd76f866282368948f032c8c1caae1 + url: "https://pub.dev" + source: hosted + version: "12.0.1" + permission_handler_android: + dependency: transitive + description: + name: permission_handler_android + sha256: "1e3bc410ca1bf84662104b100eb126e066cb55791b7451307f9708d4007350e6" + url: "https://pub.dev" + source: hosted + version: "13.0.1" + permission_handler_apple: + dependency: transitive + description: + name: permission_handler_apple + sha256: f000131e755c54cf4d84a5d8bd6e4149e262cc31c5a8b1d698de1ac85fa41023 + url: "https://pub.dev" + source: hosted + version: "9.4.7" + permission_handler_html: + dependency: transitive + description: + name: permission_handler_html + sha256: "38f000e83355abb3392140f6bc3030660cfaef189e1f87824facb76300b4ff24" + url: "https://pub.dev" + source: hosted + version: "0.1.3+5" + permission_handler_platform_interface: + dependency: transitive + description: + name: permission_handler_platform_interface + sha256: eb99b295153abce5d683cac8c02e22faab63e50679b937fa1bf67d58bb282878 + url: "https://pub.dev" + source: hosted + version: "4.3.0" + permission_handler_windows: + dependency: transitive + description: + name: permission_handler_windows + sha256: "1a790728016f79a41216d88672dbc5df30e686e811ad4e698bfc51f76ad91f1e" + url: "https://pub.dev" + source: hosted + version: "0.2.1" petitparser: dependency: transitive description: @@ -605,6 +693,14 @@ packages: url: "https://pub.dev" source: hosted version: "6.1.5" + qr: + dependency: transitive + description: + name: qr + sha256: "5a1d2586170e172b8a8c8470bbbffd5eb0cd38a66c0d77155ea138d3af3a4445" + url: "https://pub.dev" + source: hosted + version: "3.0.2" screenshot: dependency: "direct main" description: @@ -890,6 +986,14 @@ packages: url: "https://pub.dev" source: hosted version: "5.10.1" + win32_registry: + dependency: transitive + description: + name: win32_registry + sha256: "21ec76dfc731550fd3e2ce7a33a9ea90b828fdf19a5c3bcf556fa992cfa99852" + url: "https://pub.dev" + source: hosted + version: "1.1.5" xdg_directories: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 9169af7..3336c02 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -60,6 +60,9 @@ dependencies: lottie: ^2.6.0 share_plus: ^7.2.1 confetti: ^0.7.0 + pdf: ^3.11.3 + permission_handler: ^12.0.1 + device_info_plus: ^11.3.0 # jailbreak_root_detection: "^1.1.6" diff --git a/windows/flutter/generated_plugin_registrant.cc b/windows/flutter/generated_plugin_registrant.cc index f4b698c..c21327d 100644 --- a/windows/flutter/generated_plugin_registrant.cc +++ b/windows/flutter/generated_plugin_registrant.cc @@ -8,6 +8,7 @@ #include #include +#include #include #include @@ -16,6 +17,8 @@ void RegisterPlugins(flutter::PluginRegistry* registry) { registry->GetRegistrarForPlugin("FlutterSecureStorageWindowsPlugin")); LocalAuthPluginRegisterWithRegistrar( registry->GetRegistrarForPlugin("LocalAuthPlugin")); + PermissionHandlerWindowsPluginRegisterWithRegistrar( + registry->GetRegistrarForPlugin("PermissionHandlerWindowsPlugin")); SharePlusWindowsPluginCApiRegisterWithRegistrar( registry->GetRegistrarForPlugin("SharePlusWindowsPluginCApi")); UrlLauncherWindowsRegisterWithRegistrar( diff --git a/windows/flutter/generated_plugins.cmake b/windows/flutter/generated_plugins.cmake index baf4018..3bb2d90 100644 --- a/windows/flutter/generated_plugins.cmake +++ b/windows/flutter/generated_plugins.cmake @@ -5,6 +5,7 @@ list(APPEND FLUTTER_PLUGIN_LIST flutter_secure_storage_windows local_auth_windows + permission_handler_windows share_plus url_launcher_windows )