ecard download for android
This commit is contained in:
parent
653316dfa1
commit
cbb2f9f33d
@ -41,6 +41,7 @@ android {
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_21
|
||||
targetCompatibility JavaVersion.VERSION_21
|
||||
coreLibraryDesugaringEnabled true
|
||||
}
|
||||
|
||||
kotlinOptions {
|
||||
@ -133,4 +134,5 @@ dependencies {
|
||||
implementation "com.google.firebase:firebase-messaging:24.1.1"
|
||||
implementation("com.google.firebase:firebase-appcheck-playintegrity")
|
||||
implementation 'androidx.browser:browser:1.3.0'
|
||||
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.1.5'
|
||||
}
|
||||
|
||||
@ -1,9 +1,11 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="nh.ind.nhance.benefits">
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" package="nh.ind.nhance.benefits">
|
||||
<uses-permission android:name="android.permission.INTERNET"/>
|
||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
|
||||
<uses-permission android:name="android.permission.VIBRATE" />
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
|
||||
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" tools:ignore="ScopedStorage" />
|
||||
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
|
||||
<uses-permission android:name="android.permission.USE_BIOMETRIC"/>
|
||||
<uses-permission android:name="android.permission.CALL_PHONE"/>
|
||||
<uses-permission android:name="android.permission.USE_FINGERPRINT"/>
|
||||
|
||||
@ -1,5 +1,32 @@
|
||||
package nh.ind.nhance.benefits
|
||||
|
||||
import android.media.MediaScannerConnection
|
||||
import io.flutter.embedding.engine.FlutterEngine
|
||||
import io.flutter.embedding.android.FlutterFragmentActivity
|
||||
import io.flutter.plugin.common.MethodChannel
|
||||
|
||||
class MainActivity: FlutterFragmentActivity()
|
||||
class MainActivity: FlutterFragmentActivity() {
|
||||
private val mediaScanChannel = "nhance/media_scan"
|
||||
|
||||
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
|
||||
super.configureFlutterEngine(flutterEngine)
|
||||
MethodChannel(flutterEngine.dartExecutor.binaryMessenger, mediaScanChannel)
|
||||
.setMethodCallHandler { call, result ->
|
||||
if (call.method == "scanFile") {
|
||||
val path = call.argument<String>("path")
|
||||
if (path.isNullOrBlank()) {
|
||||
result.error("invalid_path", "Path is required", null)
|
||||
return@setMethodCallHandler
|
||||
}
|
||||
MediaScannerConnection.scanFile(
|
||||
applicationContext,
|
||||
arrayOf(path),
|
||||
null
|
||||
) { _, _ -> }
|
||||
result.success(true)
|
||||
} else {
|
||||
result.notImplemented()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -40,6 +40,7 @@ import 'package:nhance_app_pwa/pages/session/SetPinBiometric.dart';
|
||||
import 'package:nhance_app_pwa/pages/session/changePin.dart';
|
||||
import 'package:nhance_app_pwa/pages/session/settingUpPinAndBiometric.dart';
|
||||
import 'package:nhance_app_pwa/pages/setPassword.dart';
|
||||
import 'package:nhance_app_pwa/pages/helpers/app_permission_service.dart';
|
||||
|
||||
import 'package:nhance_app_pwa/pages/verify.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
@ -207,6 +208,7 @@ void _applyAppFlavorFromDartDefine() {
|
||||
Future<void> startApp() async {
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
_applyAppFlavorFromDartDefine();
|
||||
await AppPermissionService.requestInitialPermissions();
|
||||
|
||||
await TokenService.clearLegacyWebAuthFromSharedPreferences();
|
||||
|
||||
|
||||
16
lib/pages/helpers/app_permission_service.dart
Normal file
16
lib/pages/helpers/app_permission_service.dart
Normal file
@ -0,0 +1,16 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:permission_handler/permission_handler.dart';
|
||||
|
||||
class AppPermissionService {
|
||||
AppPermissionService._();
|
||||
|
||||
static Future<void> requestInitialPermissions() async {
|
||||
if (kIsWeb || !Platform.isAndroid) return;
|
||||
|
||||
await Permission.notification.request();
|
||||
// Keep app-open flow lightweight: no redirection to Android settings page.
|
||||
await Permission.storage.request();
|
||||
}
|
||||
}
|
||||
34
lib/pages/helpers/custom_download_snackbar.dart
Normal file
34
lib/pages/helpers/custom_download_snackbar.dart
Normal file
@ -0,0 +1,34 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class CustomDownloadSnackbar {
|
||||
static void show(
|
||||
BuildContext context, {
|
||||
required String message,
|
||||
Duration duration = const Duration(seconds: 2),
|
||||
}) {
|
||||
final messenger = ScaffoldMessenger.of(context);
|
||||
messenger
|
||||
..hideCurrentSnackBar()
|
||||
..showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(
|
||||
message,
|
||||
style: const TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
behavior: SnackBarBehavior.floating,
|
||||
margin: const EdgeInsets.fromLTRB(16, 0, 16, 24),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 14),
|
||||
duration: duration,
|
||||
backgroundColor: const Color(0xFF1F1F1F),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(14),
|
||||
),
|
||||
dismissDirection: DismissDirection.down,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
23
lib/pages/helpers/download_result.dart
Normal file
23
lib/pages/helpers/download_result.dart
Normal file
@ -0,0 +1,23 @@
|
||||
class DownloadResult {
|
||||
final bool success;
|
||||
final String? message;
|
||||
final String? savedPath;
|
||||
|
||||
const DownloadResult({
|
||||
required this.success,
|
||||
this.message,
|
||||
this.savedPath,
|
||||
});
|
||||
|
||||
factory DownloadResult.success({String? message, String? savedPath}) {
|
||||
return DownloadResult(
|
||||
success: true,
|
||||
message: message ?? 'Download complete',
|
||||
savedPath: savedPath,
|
||||
);
|
||||
}
|
||||
|
||||
factory DownloadResult.failure(String message) {
|
||||
return DownloadResult(success: false, message: message);
|
||||
}
|
||||
}
|
||||
72
lib/pages/helpers/ecard_download_notification_service.dart
Normal file
72
lib/pages/helpers/ecard_download_notification_service.dart
Normal file
@ -0,0 +1,72 @@
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
|
||||
import 'package:open_filex/open_filex.dart';
|
||||
|
||||
class EcardDownloadNotificationService {
|
||||
EcardDownloadNotificationService._();
|
||||
|
||||
static final FlutterLocalNotificationsPlugin _plugin =
|
||||
FlutterLocalNotificationsPlugin();
|
||||
static bool _initialized = false;
|
||||
|
||||
static Future<void> ensureInitialized() async {
|
||||
if (_initialized || kIsWeb) return;
|
||||
|
||||
const androidSettings =
|
||||
AndroidInitializationSettings('@mipmap/ic_launcher');
|
||||
const iosSettings = DarwinInitializationSettings();
|
||||
const settings = InitializationSettings(
|
||||
android: androidSettings,
|
||||
iOS: iosSettings,
|
||||
);
|
||||
|
||||
await _plugin.initialize(
|
||||
settings,
|
||||
onDidReceiveNotificationResponse: (NotificationResponse response) async {
|
||||
final filePath = response.payload;
|
||||
if (filePath == null || filePath.isEmpty) return;
|
||||
await OpenFilex.open(filePath);
|
||||
},
|
||||
);
|
||||
|
||||
await _plugin
|
||||
.resolvePlatformSpecificImplementation<
|
||||
AndroidFlutterLocalNotificationsPlugin>()
|
||||
?.requestNotificationsPermission();
|
||||
await _plugin
|
||||
.resolvePlatformSpecificImplementation<
|
||||
IOSFlutterLocalNotificationsPlugin>()
|
||||
?.requestPermissions(alert: true, badge: true, sound: true);
|
||||
|
||||
_initialized = true;
|
||||
}
|
||||
|
||||
static Future<void> showDownloadCompleted({
|
||||
required String filePath,
|
||||
required String fileName,
|
||||
}) async {
|
||||
if (kIsWeb) return;
|
||||
await ensureInitialized();
|
||||
|
||||
const androidDetails = AndroidNotificationDetails(
|
||||
'ecard_downloads_channel',
|
||||
'E-Card Downloads',
|
||||
channelDescription: 'Notifications for completed eCard downloads',
|
||||
importance: Importance.max,
|
||||
priority: Priority.high,
|
||||
styleInformation: DefaultStyleInformation(true, true),
|
||||
);
|
||||
const iosDetails = DarwinNotificationDetails();
|
||||
|
||||
await _plugin.show(
|
||||
DateTime.now().millisecondsSinceEpoch ~/ 1000,
|
||||
'eCard downloaded',
|
||||
'Tap to open $fileName',
|
||||
const NotificationDetails(
|
||||
android: androidDetails,
|
||||
iOS: iosDetails,
|
||||
),
|
||||
payload: filePath,
|
||||
);
|
||||
}
|
||||
}
|
||||
24
lib/pages/helpers/ecard_download_service.dart
Normal file
24
lib/pages/helpers/ecard_download_service.dart
Normal file
@ -0,0 +1,24 @@
|
||||
import 'download_result.dart';
|
||||
import 'ecard_download_service_stub.dart'
|
||||
if (dart.library.io) 'ecard_download_service_io.dart'
|
||||
if (dart.library.html) 'ecard_download_service_web.dart' as impl;
|
||||
|
||||
class EcardDownloadService {
|
||||
const EcardDownloadService();
|
||||
|
||||
Future<bool> ensureStorageAccess() {
|
||||
return impl.ensureStorageAccessImpl();
|
||||
}
|
||||
|
||||
Future<DownloadResult> downloadEcard({
|
||||
required String url,
|
||||
required String fileName,
|
||||
Map<String, String>? headers,
|
||||
}) {
|
||||
return impl.downloadEcardImpl(
|
||||
url: url,
|
||||
fileName: fileName,
|
||||
headers: headers,
|
||||
);
|
||||
}
|
||||
}
|
||||
116
lib/pages/helpers/ecard_download_service_io.dart
Normal file
116
lib/pages/helpers/ecard_download_service_io.dart
Normal file
@ -0,0 +1,116 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:permission_handler/permission_handler.dart';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
import 'download_result.dart';
|
||||
|
||||
final Dio _dio = Dio();
|
||||
const MethodChannel _mediaScanChannel = MethodChannel('nhance/media_scan');
|
||||
|
||||
Future<bool> ensureStorageAccessImpl() async {
|
||||
if (Platform.isAndroid) {
|
||||
// Request notification permission (best-effort for Android 13+).
|
||||
await Permission.notification.request();
|
||||
|
||||
// Prefer broad access for writing to public Download directory.
|
||||
if (await Permission.manageExternalStorage.isGranted) return true;
|
||||
final manage = await Permission.manageExternalStorage.request();
|
||||
if (manage.isGranted) return true;
|
||||
|
||||
// Fallback for older devices.
|
||||
final storage = await Permission.storage.request();
|
||||
return storage.isGranted;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
Future<DownloadResult> downloadEcardImpl({
|
||||
required String url,
|
||||
required String fileName,
|
||||
Map<String, String>? headers,
|
||||
}) async {
|
||||
try {
|
||||
final hasPermission = await ensureStorageAccessImpl();
|
||||
if (!hasPermission) {
|
||||
return DownloadResult.failure(
|
||||
'Storage permission required',
|
||||
);
|
||||
}
|
||||
|
||||
final Directory downloadDir = await _resolveDownloadDirectory();
|
||||
if (!await downloadDir.exists()) {
|
||||
await downloadDir.create(recursive: true);
|
||||
}
|
||||
|
||||
final safeFileName = _sanitizeFileName(fileName);
|
||||
final filePath = '${downloadDir.path}/$safeFileName';
|
||||
final file = File(filePath);
|
||||
if (await file.exists()) {
|
||||
await file.delete();
|
||||
}
|
||||
|
||||
await _dio.download(
|
||||
url,
|
||||
filePath,
|
||||
options: Options(
|
||||
headers: headers,
|
||||
responseType: ResponseType.bytes,
|
||||
),
|
||||
deleteOnError: true,
|
||||
);
|
||||
|
||||
if (!await file.exists()) {
|
||||
return DownloadResult.failure('Download failed');
|
||||
}
|
||||
final length = await file.length();
|
||||
if (length == 0) {
|
||||
await file.delete();
|
||||
return DownloadResult.failure('Download failed');
|
||||
}
|
||||
await _scanFileForAndroidMediaIndex(filePath);
|
||||
|
||||
return DownloadResult.success(
|
||||
savedPath: filePath,
|
||||
message: 'File saved under Download/$safeFileName',
|
||||
);
|
||||
} on FileSystemException {
|
||||
return DownloadResult.failure('Storage permission issue');
|
||||
} on DioException catch (e) {
|
||||
final hasStatusCode = e.response?.statusCode != null;
|
||||
final message = hasStatusCode
|
||||
? 'Download failed (${e.response!.statusCode})'
|
||||
: 'Download failed';
|
||||
return DownloadResult.failure(message);
|
||||
} catch (_) {
|
||||
return DownloadResult.failure('Download failed');
|
||||
}
|
||||
}
|
||||
|
||||
String _sanitizeFileName(String fileName) {
|
||||
final replaced = fileName.replaceAll(RegExp(r'[\\/:*?"<>|]'), '_').trim();
|
||||
if (replaced.isEmpty) return 'ecard.pdf';
|
||||
return replaced.toLowerCase().endsWith('.pdf') ? replaced : '$replaced.pdf';
|
||||
}
|
||||
|
||||
Future<Directory> _resolveDownloadDirectory() async {
|
||||
if (Platform.isAndroid) {
|
||||
final dir = Directory('/storage/emulated/0/Download');
|
||||
if (!await dir.exists()) {
|
||||
await dir.create(recursive: true);
|
||||
}
|
||||
return dir;
|
||||
}
|
||||
return getApplicationDocumentsDirectory();
|
||||
}
|
||||
|
||||
Future<void> _scanFileForAndroidMediaIndex(String filePath) async {
|
||||
if (!Platform.isAndroid) return;
|
||||
try {
|
||||
await _mediaScanChannel.invokeMethod<void>('scanFile', {'path': filePath});
|
||||
} catch (_) {
|
||||
// Non-fatal: file is already saved even if scan fails.
|
||||
}
|
||||
}
|
||||
11
lib/pages/helpers/ecard_download_service_stub.dart
Normal file
11
lib/pages/helpers/ecard_download_service_stub.dart
Normal file
@ -0,0 +1,11 @@
|
||||
import 'download_result.dart';
|
||||
|
||||
Future<bool> ensureStorageAccessImpl() async => true;
|
||||
|
||||
Future<DownloadResult> downloadEcardImpl({
|
||||
required String url,
|
||||
required String fileName,
|
||||
Map<String, String>? headers,
|
||||
}) async {
|
||||
return DownloadResult.failure('Download is not supported on this platform');
|
||||
}
|
||||
18
lib/pages/helpers/ecard_download_service_web.dart
Normal file
18
lib/pages/helpers/ecard_download_service_web.dart
Normal file
@ -0,0 +1,18 @@
|
||||
import 'download_result.dart';
|
||||
import 'file_download_helper.dart';
|
||||
|
||||
Future<bool> ensureStorageAccessImpl() async => true;
|
||||
|
||||
Future<DownloadResult> downloadEcardImpl({
|
||||
required String url,
|
||||
required String fileName,
|
||||
Map<String, String>? headers,
|
||||
}) async {
|
||||
// Keep existing web flow unchanged.
|
||||
return downloadFileFromUrl(
|
||||
url,
|
||||
fileName: fileName,
|
||||
headers: headers,
|
||||
openAfterDownload: false,
|
||||
);
|
||||
}
|
||||
10
lib/pages/helpers/file_download_common.dart
Normal file
10
lib/pages/helpers/file_download_common.dart
Normal file
@ -0,0 +1,10 @@
|
||||
String fileNameFromUrl(String url, {String defaultName = 'ecard.pdf'}) {
|
||||
try {
|
||||
final segment = Uri.parse(url).pathSegments.lastWhere(
|
||||
(s) => s.isNotEmpty,
|
||||
orElse: () => '',
|
||||
);
|
||||
if (segment.contains('.')) return Uri.decodeComponent(segment);
|
||||
} catch (_) {}
|
||||
return defaultName;
|
||||
}
|
||||
25
lib/pages/helpers/file_download_helper.dart
Normal file
25
lib/pages/helpers/file_download_helper.dart
Normal file
@ -0,0 +1,25 @@
|
||||
import 'package:flutter/foundation.dart' show kIsWeb;
|
||||
|
||||
import 'download_result.dart';
|
||||
import 'file_download_stub.dart'
|
||||
if (dart.library.io) 'file_download_io.dart'
|
||||
if (dart.library.html) 'file_download_web.dart' as impl;
|
||||
|
||||
export 'download_result.dart';
|
||||
|
||||
/// Downloads without opening a browser tab.
|
||||
/// Web: saves via browser download. Mobile: saves locally and opens the
|
||||
/// system file viewer (not an in-app WebView or browser tab).
|
||||
Future<DownloadResult> downloadFileFromUrl(
|
||||
String url, {
|
||||
String? fileName,
|
||||
Map<String, String>? headers,
|
||||
bool openAfterDownload = true,
|
||||
}) {
|
||||
return impl.downloadFileFromUrlImpl(
|
||||
url,
|
||||
fileName: fileName,
|
||||
headers: headers,
|
||||
openAfterDownload: kIsWeb ? false : openAfterDownload,
|
||||
);
|
||||
}
|
||||
52
lib/pages/helpers/file_download_io.dart
Normal file
52
lib/pages/helpers/file_download_io.dart
Normal file
@ -0,0 +1,52 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'package:open_filex/open_filex.dart';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
|
||||
import 'download_result.dart';
|
||||
import 'file_download_common.dart';
|
||||
|
||||
Future<DownloadResult> downloadFileFromUrlImpl(
|
||||
String url, {
|
||||
String? fileName,
|
||||
Map<String, String>? headers,
|
||||
bool openAfterDownload = false,
|
||||
}) async {
|
||||
try {
|
||||
final response = await http.get(Uri.parse(url), headers: headers);
|
||||
if (response.statusCode != 200) {
|
||||
return DownloadResult.failure(
|
||||
'Download failed (${response.statusCode})',
|
||||
);
|
||||
}
|
||||
|
||||
final name = fileName ?? fileNameFromUrl(url);
|
||||
final dir = await _resolveSaveDirectory();
|
||||
final file = File('${dir.path}/$name');
|
||||
await file.writeAsBytes(response.bodyBytes, flush: true);
|
||||
|
||||
if (openAfterDownload) {
|
||||
await OpenFilex.open(file.path);
|
||||
}
|
||||
|
||||
return DownloadResult.success(
|
||||
savedPath: file.path,
|
||||
message: openAfterDownload ? 'E-Card downloaded' : 'E-Card saved',
|
||||
);
|
||||
} catch (e) {
|
||||
return DownloadResult.failure('Could not download file');
|
||||
}
|
||||
}
|
||||
|
||||
Future<Directory> _resolveSaveDirectory() async {
|
||||
if (Platform.isAndroid) {
|
||||
final downloads = await getDownloadsDirectory();
|
||||
if (downloads != null) return downloads;
|
||||
}
|
||||
if (Platform.isIOS) {
|
||||
return getApplicationDocumentsDirectory();
|
||||
}
|
||||
final downloads = await getDownloadsDirectory();
|
||||
return downloads ?? await getApplicationDocumentsDirectory();
|
||||
}
|
||||
10
lib/pages/helpers/file_download_stub.dart
Normal file
10
lib/pages/helpers/file_download_stub.dart
Normal file
@ -0,0 +1,10 @@
|
||||
import 'download_result.dart';
|
||||
|
||||
Future<DownloadResult> downloadFileFromUrlImpl(
|
||||
String url, {
|
||||
String? fileName,
|
||||
Map<String, String>? headers,
|
||||
bool openAfterDownload = false,
|
||||
}) async {
|
||||
return DownloadResult.failure('Download is not supported on this platform');
|
||||
}
|
||||
48
lib/pages/helpers/file_download_web.dart
Normal file
48
lib/pages/helpers/file_download_web.dart
Normal file
@ -0,0 +1,48 @@
|
||||
import 'dart:html' as html;
|
||||
|
||||
import 'package:http/http.dart' as http;
|
||||
|
||||
import 'download_result.dart';
|
||||
import 'file_download_common.dart';
|
||||
|
||||
Future<DownloadResult> downloadFileFromUrlImpl(
|
||||
String url, {
|
||||
String? fileName,
|
||||
Map<String, String>? headers,
|
||||
bool openAfterDownload = false,
|
||||
}) async {
|
||||
try {
|
||||
final name = fileName ?? fileNameFromUrl(url);
|
||||
final response = await http.get(Uri.parse(url), headers: headers);
|
||||
if (response.statusCode != 200) {
|
||||
return DownloadResult.failure(
|
||||
'Download failed (${response.statusCode})',
|
||||
);
|
||||
}
|
||||
|
||||
final blob = html.Blob([response.bodyBytes]);
|
||||
final objectUrl = html.Url.createObjectUrlFromBlob(blob);
|
||||
final anchor = html.AnchorElement(href: objectUrl)
|
||||
..download = name
|
||||
..style.display = 'none';
|
||||
html.document.body?.append(anchor);
|
||||
anchor.click();
|
||||
anchor.remove();
|
||||
html.Url.revokeObjectUrl(objectUrl);
|
||||
|
||||
return DownloadResult.success(message: 'E-Card downloaded');
|
||||
} catch (_) {
|
||||
try {
|
||||
final name = fileName ?? fileNameFromUrl(url);
|
||||
final anchor = html.AnchorElement(href: url)
|
||||
..download = name
|
||||
..style.display = 'none';
|
||||
html.document.body?.append(anchor);
|
||||
anchor.click();
|
||||
anchor.remove();
|
||||
return DownloadResult.success(message: 'E-Card download started');
|
||||
} catch (e) {
|
||||
return DownloadResult.failure('Could not download file');
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -11,6 +11,9 @@ import 'package:nhance_app_pwa/pages/postEnrollment/service/svg_service.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
import '../../customAppBar/customFooter.dart';
|
||||
import '../helpers/custom_download_snackbar.dart';
|
||||
import '../helpers/ecard_download_service.dart';
|
||||
import '../helpers/ecard_download_notification_service.dart';
|
||||
import '../../customAppBar/responsive.dart';
|
||||
import '../../customAppBar/tabs.dart';
|
||||
import '../../customAppBar/toastHelper.dart';
|
||||
@ -33,6 +36,7 @@ class policies extends StatefulWidget {
|
||||
|
||||
class _policiesState extends State<policies> {
|
||||
late ApiService apiService;
|
||||
final EcardDownloadService _ecardDownloadService = const EcardDownloadService();
|
||||
final session = SessionManager();
|
||||
int _currentIndex = 0;
|
||||
bool isActive = true;
|
||||
@ -52,6 +56,7 @@ class _policiesState extends State<policies> {
|
||||
dynamic argumentsData;
|
||||
bool ECardHide = true;
|
||||
bool _isExpanded = false;
|
||||
bool _isDownloadingEcard = false;
|
||||
|
||||
void _onTabChanged(int index) {
|
||||
setState(() {
|
||||
@ -70,6 +75,12 @@ class _policiesState extends State<policies> {
|
||||
super.initState();
|
||||
apiService = ApiService(context); // Initialize ApiService here
|
||||
_loadToken();
|
||||
EcardDownloadNotificationService.ensureInitialized();
|
||||
if (!kIsWeb) {
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) async {
|
||||
await _ecardDownloadService.ensureStorageAccess();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
@ -80,6 +91,7 @@ class _policiesState extends State<policies> {
|
||||
Future<void> _loadToken() async {
|
||||
logDebug('_loadToken');
|
||||
final String? token = await TokenService.getPostToken();
|
||||
_token = token;
|
||||
final SharedPreferences prefs = await SharedPreferences.getInstance();
|
||||
if (token != null && token.isNotEmpty) {
|
||||
// Decode the JWT token received from the API response
|
||||
@ -94,37 +106,105 @@ class _policiesState extends State<policies> {
|
||||
}
|
||||
|
||||
Future<void> getEcardDownload(id) async {
|
||||
final clientPolicyID = widget.arguments?['client_policy_id'];
|
||||
final policyNo = widget.arguments?['policy_no'];
|
||||
final eCarDParams = {
|
||||
// 'id': empPrimaryId,
|
||||
'id': id,
|
||||
'emp_code': empCodeString,
|
||||
'client_policy_id': clientPolicyID,
|
||||
'policy_no': policyNo
|
||||
};
|
||||
final response = await apiService.getEcardRequest(eCarDParams);
|
||||
logDebug('check 1');
|
||||
final ecardDownloadUrl = response['data']['eCardDownload'];
|
||||
final message = response['data']['message'];
|
||||
if (ecardDownloadUrl != null) {
|
||||
logDebug('✅ Link: $ecardDownloadUrl');
|
||||
await _launchURL(ecardDownloadUrl,context); // Only launch if status is success
|
||||
// ToastHelper.showSuccessToast(context, message);
|
||||
} else {
|
||||
logDebug('❌ Error: $message');
|
||||
ToastHelper.showErrorToast(context, message);
|
||||
}
|
||||
}
|
||||
if (_isDownloadingEcard) return;
|
||||
if (!mounted) return;
|
||||
setState(() => _isDownloadingEcard = true);
|
||||
|
||||
|
||||
Future<void> _launchURL(String url, BuildContext context) async {
|
||||
logDebug('url $url');
|
||||
try {
|
||||
final Uri uri = Uri.parse(url);
|
||||
await launchUrl(uri, mode: LaunchMode.externalApplication);
|
||||
final clientPolicyID = widget.arguments?['client_policy_id'];
|
||||
final policyNo = widget.arguments?['policy_no'];
|
||||
final eCarDParams = {
|
||||
'id': id,
|
||||
'emp_code': empCodeString,
|
||||
'client_policy_id': clientPolicyID,
|
||||
'policy_no': policyNo,
|
||||
};
|
||||
final response = await apiService.getEcardRequest(eCarDParams);
|
||||
if (!mounted) return;
|
||||
|
||||
final data = response['data'];
|
||||
if (data is! Map) {
|
||||
ToastHelper.showErrorToast(context, 'Unable to download E-Card');
|
||||
return;
|
||||
}
|
||||
|
||||
final ecardDownloadUrl = data['eCardDownload'];
|
||||
final message = data['message']?.toString();
|
||||
if (ecardDownloadUrl == null || ecardDownloadUrl.toString().isEmpty) {
|
||||
logDebug('❌ Error: $message');
|
||||
ToastHelper.showErrorToast(
|
||||
context,
|
||||
message ?? 'E-Card is not available',
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
final url = ecardDownloadUrl.toString();
|
||||
logDebug('✅ E-Card URL: $url');
|
||||
final safePolicyNo = policyNo?.toString().replaceAll(RegExp(r'[^\w\-.]'), '_');
|
||||
final fileName = safePolicyNo == null || safePolicyNo.isEmpty
|
||||
? 'ecard.pdf'
|
||||
: 'ecard_$safePolicyNo.pdf';
|
||||
|
||||
if (!kIsWeb) {
|
||||
CustomDownloadSnackbar.show(
|
||||
context,
|
||||
message: 'Downloading eCard...',
|
||||
);
|
||||
}
|
||||
|
||||
final result = await _ecardDownloadService.downloadEcard(
|
||||
url: url,
|
||||
fileName: fileName,
|
||||
headers: {
|
||||
'APP-SIGNATURE': 'nhance-2025-signature-T8f6gV9k5fK2wf5V5c4vId3b0J8z6cAm2x8Y',
|
||||
if (_token != null && _token.toString().isNotEmpty)
|
||||
'Authorization': 'Bearer ${_token.toString()}',
|
||||
},
|
||||
);
|
||||
if (!mounted) return;
|
||||
|
||||
if (result.success) {
|
||||
if (kIsWeb) {
|
||||
ToastHelper.showSuccessToast(
|
||||
context,
|
||||
result.message ?? 'E-Card downloaded',
|
||||
);
|
||||
} else {
|
||||
CustomDownloadSnackbar.show(
|
||||
context,
|
||||
message: result.message ?? 'eCard downloaded successfully',
|
||||
);
|
||||
if (result.savedPath != null && result.savedPath!.isNotEmpty) {
|
||||
await EcardDownloadNotificationService.showDownloadCompleted(
|
||||
filePath: result.savedPath!,
|
||||
fileName: fileName,
|
||||
);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (kIsWeb) {
|
||||
ToastHelper.showErrorToast(
|
||||
context,
|
||||
result.message ?? 'Could not download E-Card',
|
||||
);
|
||||
} else {
|
||||
CustomDownloadSnackbar.show(
|
||||
context,
|
||||
message: result.message ?? 'Download failed',
|
||||
);
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
logDebug('Could not launch URL: $e');
|
||||
logDebug('E-Card download error: $e');
|
||||
if (mounted) {
|
||||
ToastHelper.showErrorToast(
|
||||
context,
|
||||
'Unable to download E-Card. Please try again.',
|
||||
);
|
||||
}
|
||||
} finally {
|
||||
if (mounted) setState(() => _isDownloadingEcard = false);
|
||||
}
|
||||
}
|
||||
|
||||
@ -151,12 +231,15 @@ class _policiesState extends State<policies> {
|
||||
}
|
||||
}
|
||||
|
||||
void _openUrl(String url) async {
|
||||
if (url.isNotEmpty && await canLaunch(url)) {
|
||||
await launch(url,
|
||||
webOnlyWindowName: '_blank'); // Opens in a new tab (web only)
|
||||
Future<void> _openUrl(String url) async {
|
||||
final uri = Uri.tryParse(url);
|
||||
if (uri == null) {
|
||||
logDebug('Could not launch URL');
|
||||
return;
|
||||
}
|
||||
if (await canLaunchUrl(uri)) {
|
||||
await launchUrl(uri, mode: LaunchMode.externalApplication);
|
||||
} else {
|
||||
// Handle invalid or null URL
|
||||
logDebug('Could not launch URL');
|
||||
}
|
||||
}
|
||||
@ -402,7 +485,9 @@ class _policiesState extends State<policies> {
|
||||
? 200
|
||||
: 180,
|
||||
child: TextButton(
|
||||
onPressed: () {
|
||||
onPressed: _isDownloadingEcard
|
||||
? null
|
||||
: () {
|
||||
getEcardDownload(empPrimaryId);
|
||||
// if (argumentsData['eCardDownload'] !=
|
||||
// null) {
|
||||
@ -703,7 +788,9 @@ class _policiesState extends State<policies> {
|
||||
child: MouseRegion(
|
||||
cursor: SystemMouseCursors.click, // Web pointer
|
||||
child: GestureDetector(
|
||||
onTap: () {
|
||||
onTap: _isDownloadingEcard
|
||||
? null
|
||||
: () {
|
||||
getEcardDownload(detail['id']);
|
||||
},
|
||||
child: Tooltip(
|
||||
|
||||
@ -8,6 +8,7 @@ import Foundation
|
||||
import file_picker
|
||||
import file_selector_macos
|
||||
import flutter_inappwebview_macos
|
||||
import flutter_local_notifications
|
||||
import flutter_secure_storage_macos
|
||||
import google_sign_in_ios
|
||||
import local_auth_darwin
|
||||
@ -24,6 +25,7 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
|
||||
FilePickerPlugin.register(with: registry.registrar(forPlugin: "FilePickerPlugin"))
|
||||
FileSelectorPlugin.register(with: registry.registrar(forPlugin: "FileSelectorPlugin"))
|
||||
InAppWebViewFlutterPlugin.register(with: registry.registrar(forPlugin: "InAppWebViewFlutterPlugin"))
|
||||
FlutterLocalNotificationsPlugin.register(with: registry.registrar(forPlugin: "FlutterLocalNotificationsPlugin"))
|
||||
FlutterSecureStoragePlugin.register(with: registry.registrar(forPlugin: "FlutterSecureStoragePlugin"))
|
||||
FLTGoogleSignInPlugin.register(with: registry.registrar(forPlugin: "FLTGoogleSignInPlugin"))
|
||||
LocalAuthPlugin.register(with: registry.registrar(forPlugin: "LocalAuthPlugin"))
|
||||
|
||||
@ -75,6 +75,10 @@ dependencies:
|
||||
ribbon_widget: ^1.0.5
|
||||
flutter_html: ^3.0.0
|
||||
video_player: ^2.10.1
|
||||
path_provider: ^2.1.5
|
||||
open_filex: ^4.7.0
|
||||
flutter_local_notifications: ^19.4.2
|
||||
permission_handler: ^12.0.1
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
|
||||
@ -10,6 +10,7 @@
|
||||
#include <flutter_inappwebview_windows/flutter_inappwebview_windows_plugin_c_api.h>
|
||||
#include <flutter_secure_storage_windows/flutter_secure_storage_windows_plugin.h>
|
||||
#include <local_auth_windows/local_auth_plugin.h>
|
||||
#include <permission_handler_windows/permission_handler_windows_plugin.h>
|
||||
#include <printing/printing_plugin.h>
|
||||
#include <url_launcher_windows/url_launcher_windows.h>
|
||||
|
||||
@ -22,6 +23,8 @@ void RegisterPlugins(flutter::PluginRegistry* registry) {
|
||||
registry->GetRegistrarForPlugin("FlutterSecureStorageWindowsPlugin"));
|
||||
LocalAuthPluginRegisterWithRegistrar(
|
||||
registry->GetRegistrarForPlugin("LocalAuthPlugin"));
|
||||
PermissionHandlerWindowsPluginRegisterWithRegistrar(
|
||||
registry->GetRegistrarForPlugin("PermissionHandlerWindowsPlugin"));
|
||||
PrintingPluginRegisterWithRegistrar(
|
||||
registry->GetRegistrarForPlugin("PrintingPlugin"));
|
||||
UrlLauncherWindowsRegisterWithRegistrar(
|
||||
|
||||
@ -7,11 +7,13 @@ list(APPEND FLUTTER_PLUGIN_LIST
|
||||
flutter_inappwebview_windows
|
||||
flutter_secure_storage_windows
|
||||
local_auth_windows
|
||||
permission_handler_windows
|
||||
printing
|
||||
url_launcher_windows
|
||||
)
|
||||
|
||||
list(APPEND FLUTTER_FFI_PLUGIN_LIST
|
||||
flutter_local_notifications_windows
|
||||
)
|
||||
|
||||
set(PLUGIN_BUNDLED_LIBRARIES)
|
||||
|
||||
Loading…
Reference in New Issue
Block a user