diff --git a/android/app/build.gradle b/android/app/build.gradle index de387d6..791a4bb 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -34,6 +34,7 @@ android { ndkVersion "27.0.12077973" compileOptions { + coreLibraryDesugaringEnabled true sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 } @@ -80,4 +81,6 @@ flutter { source '../..' } -dependencies {} +dependencies { + coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.1.5' +} diff --git a/android/app/src/main/res/drawable/notification_icon.png b/android/app/src/main/res/drawable/notification_icon.png new file mode 100644 index 0000000..d4ff893 Binary files /dev/null and b/android/app/src/main/res/drawable/notification_icon.png differ diff --git a/lib/features/accounts/screens/account_statement_screen.dart b/lib/features/accounts/screens/account_statement_screen.dart index 39ce2e1..a1c472a 100644 --- a/lib/features/accounts/screens/account_statement_screen.dart +++ b/lib/features/accounts/screens/account_statement_screen.dart @@ -13,6 +13,8 @@ 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'; @@ -38,13 +40,50 @@ class _AccountStatementScreen extends State { List _transactions = []; final _minAmountController = TextEditingController(); final _maxAmountController = TextEditingController(); - //Future?>? accountStatementsFuture; + late FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin; @override void initState() { super.initState(); selectedUser = widget.users[widget.selectedIndex]; _loadTransactions(); + _initializeNotifications(); + } + + void _initializeNotifications() async { + flutterLocalNotificationsPlugin = FlutterLocalNotificationsPlugin(); + + const AndroidInitializationSettings initializationSettingsAndroid = + AndroidInitializationSettings('notification_icon'); + + const InitializationSettings initializationSettings = + InitializationSettings(android: initializationSettingsAndroid); + + await flutterLocalNotificationsPlugin.initialize( + initializationSettings, + onDidReceiveNotificationResponse: _onDidReceiveNotificationResponse, + ); + _requestNotificationPermission(); + } + + void _onDidReceiveNotificationResponse( + NotificationResponse notificationResponse) async { + final String? payload = notificationResponse.payload; + if (payload != null && payload.isNotEmpty) { + await OpenFilex.open(payload); + } + } + + void _requestNotificationPermission() async { + if (Platform.isAndroid) { + final AndroidFlutterLocalNotificationsPlugin? androidImplementation = + flutterLocalNotificationsPlugin.resolvePlatformSpecificImplementation< + AndroidFlutterLocalNotificationsPlugin>(); + + if (androidImplementation != null) { + await androidImplementation.requestNotificationsPermission(); + } + } } Future _loadTransactions() async { @@ -413,7 +452,6 @@ class _AccountStatementScreen extends State { } Future _exportToPdf() async { - // Step 1: Check if there are any transactions to export. if (_transactions.isEmpty) { if (mounted) { ScaffoldMessenger.of(context).showSnackBar( @@ -425,6 +463,27 @@ class _AccountStatementScreen extends State { return; } + // Show downloading notification + await flutterLocalNotificationsPlugin.show( + 0, + 'Downloading PDF', + 'Your account statement is being downloaded...', + const NotificationDetails( + android: AndroidNotificationDetails( + 'download_channel', + 'Download Notifications', + channelDescription: + 'Notifications for PDF downloads', + importance: Importance.low, + priority: Priority.low, + showProgress: true, + maxProgress: 0, // Indeterminate progress + ongoing: true, + icon: 'notification_icon', + ), + ), + ); + var logo = await rootBundle.load('assets/images/logo.png'); var rubik = await rootBundle.load("assets/fonts/Rubik-Regular.ttf"); final pdf = pw.Document(); @@ -527,64 +586,81 @@ class _AccountStatementScreen extends State { ); })); - //Logic For all platforms try { final Uint8List pdfBytes = await pdf.save(); final String timestamp = DateTime.now().millisecondsSinceEpoch.toString(); final String fileName = 'account_statement_$timestamp.pdf'; - // For Android - 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'), - ), - ); - } - return; - } - } - } + 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); - - if (mounted) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text('PDF saved to: ${file.path}'), - ), - ); - } - } - // Add for IOS - else if (Platform.isIOS) { - // On iOS, we save to a temporary directory and then open the share sheet. + 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; + } - // Use share_plus to open the iOS share dialog - await Share.shareXFiles( - [XFile(file.path)], - ); - } - } catch (e) { - if (mounted) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text('Error saving PDF: $e'), + // Update notification to download complete + await flutterLocalNotificationsPlugin.show( + 0, + 'PDF Download Complete', + 'Your account statement has been saved.', + const NotificationDetails( + android: AndroidNotificationDetails( + 'download_channel', + 'Download Notifications', + channelDescription: + 'Notifications for PDF downloads', + importance: Importance.high, + priority: Priority.high, + showProgress: false, + ongoing: false, + autoCancel: true, + icon: 'notification_icon', + actions: [ + AndroidNotificationAction( + 'open_pdf', + 'Open PDF', + showsUserInterface: true, + ) + ], ), - ); - } + ), + payload: filePath, + ); + } catch (e) { + await flutterLocalNotificationsPlugin.show( + 0, + 'PDF Download Failed', + 'Error saving PDF: $e', + const NotificationDetails( + android: AndroidNotificationDetails( + 'download_channel', + 'Download Notifications', + channelDescription: + 'Notifications for PDF downloads', + importance: Importance.high, + priority: Priority.high, + showProgress: false, + ongoing: false, + icon: 'notification_icon', + ), + ), + ); } } diff --git a/lib/features/dashboard/screens/dashboard_screen.dart b/lib/features/dashboard/screens/dashboard_screen.dart index 94647ae..764ab5d 100644 --- a/lib/features/dashboard/screens/dashboard_screen.dart +++ b/lib/features/dashboard/screens/dashboard_screen.dart @@ -388,6 +388,21 @@ class _DashboardScreenState extends State @override Widget build(BuildContext context) { final theme = Theme.of(context); + final authState = context.read().state; + String mobileNumberToPass = ''; + String customerNo = ''; + String customerName = ''; + if (authState is Authenticated) { + if (selectedAccountIndex >= 0 && + selectedAccountIndex < authState.users.length) { + mobileNumberToPass = + authState.users[selectedAccountIndex].mobileNo ?? ''; + customerNo = + authState.users[selectedAccountIndex].cifNumber ?? ''; + customerName = + authState.users[selectedAccountIndex].name ?? ''; + } + } return BlocListener( listener: (context, state) async { if (state is Authenticated && !_biometricPromptShown) { @@ -429,21 +444,7 @@ class _DashboardScreenState extends State child: InkWell( borderRadius: BorderRadius.circular(20), onTap: () { - final authState = context.read().state; - String mobileNumberToPass = ''; - String customerNo = ''; - String customerName = ''; - if (authState is Authenticated) { - if (selectedAccountIndex >= 0 && - selectedAccountIndex < authState.users.length) { - mobileNumberToPass = - authState.users[selectedAccountIndex].mobileNo ?? ''; - customerNo = - authState.users[selectedAccountIndex].cifNumber ?? ''; - customerName = - authState.users[selectedAccountIndex].name ?? ''; - } - } + Navigator.push( context, @@ -677,18 +678,21 @@ class _DashboardScreenState extends State const EnquiryScreen())); }), _buildQuickLink( - Symbols.checkbook, - AppLocalizations.of(context).chequeManagement, + Symbols.person, + AppLocalizations.of(context).profile, () { Navigator.push( context, MaterialPageRoute( builder: (context) => - const ChequeManagementScreen(), - ), + ProfileScreen( + mobileNumber: mobileNumberToPass, + customerNo: customerNo, + customerName: customerName), + ), ); }, - disable: true, + disable: false, ), ], ), diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index d8b913c..c03b8b6 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -6,6 +6,7 @@ import FlutterMacOS import Foundation import device_info_plus +import flutter_local_notifications import flutter_secure_storage_macos import local_auth_darwin import package_info_plus @@ -16,6 +17,7 @@ import url_launcher_macos func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { DeviceInfoPlusMacosPlugin.register(with: registry.registrar(forPlugin: "DeviceInfoPlusMacosPlugin")) + FlutterLocalNotificationsPlugin.register(with: registry.registrar(forPlugin: "FlutterLocalNotificationsPlugin")) FlutterSecureStoragePlugin.register(with: registry.registrar(forPlugin: "FlutterSecureStoragePlugin")) FLALocalAuthPlugin.register(with: registry.registrar(forPlugin: "FLALocalAuthPlugin")) FPPPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FPPPackageInfoPlusPlugin")) diff --git a/pubspec.lock b/pubspec.lock index 6e600bd..d9ada43 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -137,6 +137,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.8" + dbus: + dependency: transitive + description: + name: dbus + sha256: "79e0c23480ff85dc68de79e2cd6334add97e48f7f4865d17686dd6ea81a47e8c" + url: "https://pub.dev" + source: hosted + version: "0.7.11" device_info_plus: dependency: "direct main" description: @@ -238,6 +246,38 @@ packages: url: "https://pub.dev" source: hosted version: "3.0.2" + flutter_local_notifications: + dependency: "direct main" + description: + name: flutter_local_notifications + sha256: "19ffb0a8bb7407875555e5e98d7343a633bb73707bae6c6a5f37c90014077875" + url: "https://pub.dev" + source: hosted + version: "19.5.0" + flutter_local_notifications_linux: + dependency: transitive + description: + name: flutter_local_notifications_linux + sha256: e3c277b2daab8e36ac5a6820536668d07e83851aeeb79c446e525a70710770a5 + url: "https://pub.dev" + source: hosted + version: "6.0.0" + flutter_local_notifications_platform_interface: + dependency: transitive + description: + name: flutter_local_notifications_platform_interface + sha256: "277d25d960c15674ce78ca97f57d0bae2ee401c844b6ac80fcd972a9c99d09fe" + url: "https://pub.dev" + source: hosted + version: "9.1.0" + flutter_local_notifications_windows: + dependency: transitive + description: + name: flutter_local_notifications_windows + sha256: "8d658f0d367c48bd420e7cf2d26655e2d1130147bca1eea917e576ca76668aaf" + url: "https://pub.dev" + source: hosted + version: "1.0.3" flutter_localizations: dependency: "direct main" description: flutter @@ -541,6 +581,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.0" + open_filex: + dependency: "direct main" + description: + name: open_filex + sha256: "9976da61b6a72302cf3b1efbce259200cd40232643a467aac7370addf94d6900" + url: "https://pub.dev" + source: hosted + version: "4.7.0" package_info_plus: dependency: "direct main" description: @@ -874,6 +922,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.7.6" + timezone: + dependency: transitive + description: + name: timezone + sha256: dd14a3b83cfd7cb19e7888f1cbc20f258b8d71b54c06f79ac585f14093a287d1 + url: "https://pub.dev" + source: hosted + version: "0.10.1" typed_data: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index f013478..ca2b318 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -61,6 +61,8 @@ dependencies: device_info_plus: ^11.3.0 showcaseview: ^2.0.3 package_info_plus: ^4.2.0 + flutter_local_notifications: ^19.5.0 + open_filex: ^4.7.0 # jailbreak_root_detection: "^1.1.6" diff --git a/windows/flutter/generated_plugins.cmake b/windows/flutter/generated_plugins.cmake index 3bb2d90..2fd5c53 100644 --- a/windows/flutter/generated_plugins.cmake +++ b/windows/flutter/generated_plugins.cmake @@ -11,6 +11,7 @@ list(APPEND FLUTTER_PLUGIN_LIST ) list(APPEND FLUTTER_FFI_PLUGIN_LIST + flutter_local_notifications_windows ) set(PLUGIN_BUNDLED_LIBRARIES)