import 'dart:async'; import 'dart:convert'; import 'dart:io'; import 'package:connectivity_plus/connectivity_plus.dart'; import 'package:flutter/services.dart'; import 'package:flutter_local_notifications/flutter_local_notifications.dart'; import 'package:generp/Utils/commonServices.dart'; import 'package:http/http.dart' as http; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_download_manager/flutter_download_manager.dart'; import 'package:flutter_downloader/flutter_downloader.dart'; import 'package:flutter_inappwebview/flutter_inappwebview.dart'; import 'package:generp/Utils/app_colors.dart'; import 'package:generp/Utils/commonWidgets.dart'; import 'package:path_provider/path_provider.dart'; import 'package:permission_handler/permission_handler.dart'; import 'dart:math'; import 'package:flutter/widgets.dart'; import 'package:url_launcher/url_launcher.dart'; import '../services/api_calling.dart'; // const MAX_PROGRESS = 100; Future runErpScreenApp() async { await FlutterDownloader.initialize( debug: true, // optional: set false to disable printing logs to console ); await Permission.storage.request(); } class WebErpScreen extends StatefulWidget { final String erp_url; const WebErpScreen({super.key, required this.erp_url}); @override State createState() => _WebErpScreenState(); } class _WebErpScreenState extends State { Map _source = {ConnectivityResult.mobile: true}; final MyConnectivity _connectivity = MyConnectivity.instance; final Completer _controller = Completer(); var empId = ""; var sessionId = ""; bool isLoading = true; InAppWebViewController? _webViewController; PullToRefreshController? pullToRefreshController; PullToRefreshSettings pullToRefreshSettings = PullToRefreshSettings( color: AppColors.app_blue, ); bool pullToRefreshEnabled = true; final FlutterLocalNotificationsPlugin _notificationsPlugin = FlutterLocalNotificationsPlugin(); static const platform = MethodChannel('in.webgrid.generp/download'); final GlobalKey webViewKey = GlobalKey(); var dl = DownloadManager(); @override void initState() { // loadData(); super.initState(); _connectivity.initialise(); _connectivity.myStream.listen((source) { setState(() => _source = source); }); pullToRefreshController = kIsWeb ? null : PullToRefreshController( settings: pullToRefreshSettings, onRefresh: () async { if (defaultTargetPlatform == TargetPlatform.android) { _webViewController?.reload(); } else if (defaultTargetPlatform == TargetPlatform.iOS) { _webViewController?.loadUrl( urlRequest: URLRequest( url: await _webViewController?.getUrl(), ), ); } }, ); // print("URL:${widget.url}"); _initializeNotifications(); } Future _initializeNotifications() async { const AndroidInitializationSettings initializationSettingsAndroid = AndroidInitializationSettings('@mipmap/ic_launcher'); final InitializationSettings initializationSettings = InitializationSettings(android: initializationSettingsAndroid); await _notificationsPlugin.initialize(initializationSettings); // Create a notification channel for Android const AndroidNotificationChannel channel = AndroidNotificationChannel( 'download_channel', 'Downloads', description: 'Notifications for file downloads', importance: Importance.high, ); await _notificationsPlugin .resolvePlatformSpecificImplementation< AndroidFlutterLocalNotificationsPlugin >() ?.createNotificationChannel(channel); } @override void dispose() { super.dispose(); _connectivity.disposeStream(); } @override Widget build(BuildContext context) { switch (_source.keys.toList()[0]) { case ConnectivityResult.mobile: connection = 'Online'; break; case ConnectivityResult.wifi: connection = 'Online'; break; case ConnectivityResult.none: default: connection = 'Offline'; } return WillPopScope( onWillPop: () async { if (await _webViewController!.canGoBack()) { _webViewController!.goBack(); return false; // Prevent default back button behavior } return true; // Allow default back button behavior }, child: connection == "Online" ? SafeArea( top: false, bottom: Platform.isIOS ? false : true, child: Scaffold( resizeToAvoidBottomInset: true, appBar: appbar(context, "ERP"), body: Container( child: Column( children: [ Expanded( child: Stack( children: [ InAppWebView( initialUrlRequest: URLRequest( url: WebUri(widget.erp_url), allowsCellularAccess: true, allowsConstrainedNetworkAccess: true, allowsExpensiveNetworkAccess: true, ), androidOnGeolocationPermissionsShowPrompt: ( InAppWebViewController controller, String origin, ) async { return GeolocationPermissionShowPromptResponse( origin: origin, allow: true, retain: true, ); }, initialOptions: InAppWebViewGroupOptions( android: AndroidInAppWebViewOptions( useWideViewPort: true, loadWithOverviewMode: true, allowContentAccess: true, geolocationEnabled: true, allowFileAccess: true, databaseEnabled: true, // Enables the WebView database domStorageEnabled: true, // Enables DOM storage builtInZoomControls: true, // Enables the built-in zoom controls displayZoomControls: false, // Disables displaying zoom controls safeBrowsingEnabled: true, // Enables Safe Browsing clearSessionCache: true, loadsImagesAutomatically: true, thirdPartyCookiesEnabled: true, blockNetworkImage: false, supportMultipleWindows: true, blockNetworkLoads: false, networkAvailable: true, useShouldInterceptRequest: true, hardwareAcceleration: true, // Enable camera access ), ios: IOSInAppWebViewOptions( allowsInlineMediaPlayback: true, allowsLinkPreview: true, allowsBackForwardNavigationGestures: true, ), crossPlatform: InAppWebViewOptions( javaScriptEnabled: true, useOnDownloadStart: true, allowFileAccessFromFileURLs: true, allowUniversalAccessFromFileURLs: true, mediaPlaybackRequiresUserGesture: true, ), ), androidOnPermissionRequest: ( InAppWebViewController controller, String origin, List resources, ) async { return PermissionRequestResponse( resources: resources, action: PermissionRequestResponseAction.GRANT, ); }, onPermissionRequest: ( controller, request, ) async { return PermissionResponse( resources: request.resources, action: PermissionResponseAction.GRANT, ); }, keepAlive: InAppWebViewKeepAlive(), onWebViewCreated: (controller) { _webViewController = controller; _controller.complete(controller); // _webViewController!.addJavaScriptHandler( // handlerName: 'downloadBlobHandler', // callback: (args) async { // String base64Data = args[0]; // String mimeType = args[1]; // String filename = args[2]; // // // Save the file // await saveBase64File(base64Data, filename, mimeType); // }, // ); _webViewController!.addJavaScriptHandler( handlerName: 'MobileAppJavascriptInterface', callback: (args) { print( "JavaScript called MobileAppJavascriptInterface with args: $args", ); return {'status': 'success'}; }, ); _webViewController!.addJavaScriptHandler( handlerName: 'downloadFile', callback: (args) async { if (Platform.isAndroid) { final url = args[0] as String; await _handleDownload( url, '', 'application/octet-stream', '', // controller,context ); } }, ); }, pullToRefreshController: pullToRefreshController, onLoadStart: (controller, url) { return setState(() { isLoading = true; }); }, initialSettings: InAppWebViewSettings( allowUniversalAccessFromFileURLs: true, allowFileAccessFromFileURLs: true, allowFileAccess: true, allowsInlineMediaPlayback: true, allowsPictureInPictureMediaPlayback: true, allowsBackForwardNavigationGestures: true, iframeAllow: "camera;microphone;files;media;", domStorageEnabled: true, allowContentAccess: true, javaScriptEnabled: true, supportZoom: true, builtInZoomControls: true, displayZoomControls: false, textZoom: 125, blockNetworkImage: false, loadsImagesAutomatically: true, safeBrowsingEnabled: true, useWideViewPort: true, loadWithOverviewMode: true, javaScriptCanOpenWindowsAutomatically: true, mediaPlaybackRequiresUserGesture: false, geolocationEnabled: true, useOnDownloadStart: true, allowsLinkPreview: true, databaseEnabled: true, // Enables the WebView database clearSessionCache: true, mediaType: "image/*,application/pdf", useShouldInterceptRequest: true, hardwareAcceleration: true, ), shouldInterceptRequest: ( controller, request, ) async { final url = request.url.toString(); print( 'Intercepting request: $url, Headers: ${request.headers}', ); if (url.endsWith('.pdf')) { final response = await http.get( Uri.parse(url), headers: {'Accept': 'application/pdf'}, ); if (response.statusCode == 200 && response.headers['content-type'] ?.contains('application/pdf') == true) { return WebResourceResponse( contentType: 'application/pdf', data: response.bodyBytes, ); } else { print( 'Failed to load PDF: Status ${response.statusCode}, Content-Type: ${response.headers['content-type']}', ); } } return null; }, shouldOverrideUrlLoading: ( controller, navigationAction, ) async { var uri = navigationAction.request.url!; print("urib scgefes"); print(uri); print(uri.scheme); if (uri.toString().contains( 'file_viewer_name.php', ) && uri.toString().contains('.pdf')) { final pdfPath = Uri.parse( uri.toString(), ).queryParameters['file_path']; if (pdfPath != null) { final pdfUrl = 'https://erp.gengroup.in/$pdfPath'; await controller.loadUrl( urlRequest: URLRequest( url: WebUri(pdfUrl), ), ); return NavigationActionPolicy.CANCEL; } } if (uri.scheme == "tel") { // Launch the phone dialer app with the specified phone number if (await canLaunch(uri.toString())) { await launch(uri.toString()); return NavigationActionPolicy.CANCEL; } } else if (uri.scheme == "mailto") { if (await canLaunch(uri.toString())) { await launch(uri.toString()); return NavigationActionPolicy.CANCEL; } } else if (uri.scheme == "whatsapp") { // Launch WhatsApp with the specified chat or phone number if (await canLaunch(uri.toString())) { await launch(uri.toString()); return NavigationActionPolicy.CANCEL; } } // // Check if the URL is trying to access the camera for image upload // if (uri.scheme == 'camera' && uri.path.contains('/camera/')) { // // Handle camera image upload here // // You might want to display a custom UI for image selection or directly trigger the camera // // You can use platform-specific plugins like image_picker for this purpose // // Once the image is selected, you can pass it to the web view using JavaScript injection // if (await canLaunch(uri.toString())) { // await launch(uri.toString()); // return NavigationActionPolicy.CANCEL; // } // } return NavigationActionPolicy.ALLOW; }, onLoadStop: (controller, url) async { if (url.toString().contains( 'file_viewer_name.php', ) && url.toString().contains('.pdf')) { final uri = Uri.parse(url.toString()); final pdfPath = uri.queryParameters['file_path']; if (pdfPath != null) { final pdfUrl = 'https://erp.gengroup.in/$pdfPath'; await controller.evaluateJavascript( source: ''' var pdfjsLib = window.pdfjsLib || document.createElement('script'); pdfjsLib.src = 'https://mozilla.github.io/pdf.js/build/pdf.js'; document.head.appendChild(pdfjsLib); pdfjsLib.onload = function() { pdfjsLib.getDocument('$pdfUrl').promise.then(function(pdf) { pdf.getPage(1).then(function(page) { var canvas = document.createElement('canvas'); document.body.appendChild(canvas); var context = canvas.getContext('2d'); var viewport = page.getViewport({ scale: 1.0 }); canvas.height = viewport.height; canvas.width = viewport.width; page.render({ canvasContext: context, viewport: viewport }); }); }).catch(function(error) { console.error('PDF.js error: ' + error); }); }; ''', ); } } pullToRefreshController?.endRefreshing(); return setState(() { isLoading = false; }); }, onReceivedError: (controller, request, error) { pullToRefreshController?.endRefreshing(); return setState(() { isLoading = false; }); }, onProgressChanged: (controller, progress) { if (progress == 100) { pullToRefreshController?.endRefreshing(); } }, onConsoleMessage: (controller, consoleMessage) { if (kDebugMode) { debugPrint("consoleMessage$consoleMessage"); } debugPrint( "JavaScript console message: ${consoleMessage.message}", ); }, // onDownloadStartRequest: (controller, url) async { // await ApiCalling.download_files( // empId, sessionId, "${url.url}", context) // .then((data) => {debugPrint(data)}); // // }, onDownloadStartRequest: ( controller, downloadStartRequest, ) async { // String url = downloadStartRequest.url.toString(); // // // Use url_launcher or another plugin to handle the download externally // if (await canLaunchUrl(Uri.parse(url))) { // await launchUrl( // Uri.parse(url), // mode: LaunchMode.externalApplication, // ); // } else { // print("Could not launch $url"); // } if (Platform.isAndroid) { await _handleDownload( downloadStartRequest.url.toString(), downloadStartRequest.suggestedFilename!, downloadStartRequest.mimeType!, downloadStartRequest.suggestedFilename ?? '', // controller,context ); } }, ), if (isLoading) ...[ Container( color: Colors.white.withOpacity(0.7), child: Column( crossAxisAlignment: CrossAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center, children: [ SpinKitRing( color: AppColors.app_blue, lineWidth: 4, // duration: Duration(seconds: 2), size: 50, ), const SizedBox(height: 15), SizedBox( width: 200, child: Text( "Please wait.......", textAlign: TextAlign.center, style: TextStyle( decorationThickness: 0, fontSize: 15, fontWeight: FontWeight.normal, color: AppColors.app_blue, ), ), ), // SvgPicture.asset("/assets/images/NutsLoader.gif") ], ), ), ], ], ), ), ], ), ), ), ) : NoNetwork(context), ); } // Future saveBase64File(String base64Data, String filename, String mimeType) async { // // Ask for permission // if (await Permission.storage.request().isGranted) { // final bytes = base64.decode(base64Data.split(',').last); // final directory = await getExternalStorageDirectory(); // or getApplicationDocumentsDirectory() // final path = "${directory!.path}/$filename"; // final file = File(path); // // await file.writeAsBytes(bytes); // print("File saved to: $path"); // } else { // print("Storage permission denied."); // } // } // Future _handleDownload( // String url, // String contentDisposition, // String mimeType, // String suggestedFilename, // InAppWebViewController controller, // BuildContext context, // Add context for toast // ) async { // print("URL: $url"); // print("MimeType: $mimeType"); // print("SuggestedFilename: $suggestedFilename"); // print("ContentDisposition: $contentDisposition"); // // if (Platform.isAndroid) { // if (await Permission.notification.request().isGranted) { // try { // final userAgent = 'Flutter InAppWebView'; // if (url.startsWith('blob:')) { // print("Attempting to convert blob URL..."); // final blobContent = await _convertBlobToDataUrl(url, controller); // if (blobContent != null) { // print("Blob converted to data URL: ${blobContent.substring(0, 50)}..."); // Log first 50 chars // await platform.invokeMethod('startDownload', { // 'url': blobContent, // 'userAgent': userAgent, // 'contentDisposition': contentDisposition, // 'mimeType': mimeType, // 'suggestedFilename': suggestedFilename, // 'isBase64': true, // }); // ScaffoldMessenger.of(context).showSnackBar( // SnackBar(content: Text("Download started: $suggestedFilename")), // ); // } else { // print("Failed to convert blob URL"); // ScaffoldMessenger.of(context).showSnackBar( // SnackBar(content: Text("Failed to resolve blob URL")), // ); // } // } else { // print("Handling non-blob URL"); // await platform.invokeMethod('startDownload', { // 'url': url, // 'userAgent': userAgent, // 'contentDisposition': contentDisposition, // 'mimeType': mimeType, // 'suggestedFilename': suggestedFilename, // 'isBase64': false, // }); // } // } catch (e, stackTrace) { // print("Download Error: $e"); // print("StackTrace: $stackTrace"); // ScaffoldMessenger.of(context).showSnackBar( // SnackBar(content: Text("Download failed: $e")), // ); // } // } else { // print("Notification Permission Denied"); // ScaffoldMessenger.of(context).showSnackBar( // SnackBar(content: Text("Notification Permission Denied")), // ); // } // } else if (Platform.isIOS) { // _handleIOSDownload(url, suggestedFilename); // } // } // // Future _convertBlobToDataUrl(String blobUrl, InAppWebViewController controller) async { // try { // final result = await controller.evaluateJavascript( // source: """ // (async function() { // try { // const response = await fetch('$blobUrl'); // if (!response.ok) { // console.error('Fetch failed with status: ' + response.status); // return null; // } // const blob = await response.blob(); // return new Promise((resolve) => { // const reader = new FileReader(); // reader.onloadend = () => resolve(reader.result); // reader.onerror = () => resolve(null); // reader.readAsDataURL(blob); // }); // } catch (e) { // console.error('Blob conversion error: ' + e.message); // return null; // } // })(); // """, // ); // if (result != null && result.toString().startsWith('data:')) { // print("Blob conversion successful, data URL length: ${result.toString().length}"); // return result.toString(); // } else { // print("Blob conversion failed, result: $result"); // return null; // } // } catch (e, stackTrace) { // print("Blob conversion error: $e"); // print("StackTrace: $stackTrace"); // return null; // } // } void handleBlobDownload(String blobUrl, String filename) async { final js = """ (async function() { const blobUrl = "$blobUrl"; const response = await fetch(blobUrl); const blob = await response.blob(); const reader = new FileReader(); reader.onloadend = function() { window.flutter_inappwebview.callHandler('downloadBlobHandler', reader.result, blob.type, "$filename"); }; reader.readAsDataURL(blob); })(); """; _webViewController?.evaluateJavascript(source: js); } Future _handleDownload( String url, String contentDisposition, String mimeType, String suggestedFilename, ) async { print("mimeType4: $mimeType"); print("mimeType4: $suggestedFilename"); // Request notification permission for Android 13+ if (Platform.isIOS) { _handleIOSDownload(url, suggestedFilename); } else if (Platform.isAndroid) { if (await Permission.notification.request().isGranted) { try { // Show custom notification (optional, since DownloadManager shows its own) if (Platform.isAndroid) { // Call native Android Download Manager final userAgent = 'Flutter InAppWebView'; await platform.invokeMethod('startDownload', { 'url': url, 'userAgent': userAgent, 'contentDisposition': contentDisposition, 'mimeType': mimeType, 'suggestedFilename': suggestedFilename, }); } else if (Platform.isIOS) { _handleIOSDownload(url, suggestedFilename); } } catch (e) { print("Download Error $e"); } } else { toast(context, "Notification Permission Denied"); } } } Future _handleIOSDownload(String url, String suggestedFilename) async { try { // Show initial download notification await _showDownloadNotification(0, suggestedFilename, isComplete: false); // Get the temporary directory for iOS final tempDir = await getTemporaryDirectory(); final fileName = suggestedFilename.isNotEmpty ? suggestedFilename : url.split('/').last; final filePath = '${tempDir.path}/$fileName'; // Download the file using http final response = await http.get(Uri.parse(url)); if (response.statusCode == 200) { // Save the file final file = File(filePath); await file.writeAsBytes(response.bodyBytes); // Show completion notification await _showDownloadNotification(100, fileName, isComplete: true); // Optionally, open the file or notify the user toast(context, "File downloaded to $filePath"); } else { throw Exception("Failed to download file: HTTP ${response.statusCode}"); } } catch (e) { print("iOS Download Error: $e"); await _showDownloadNotification( 0, suggestedFilename, isComplete: false, isError: true, ); toast(context, "Failed to download file: $e"); } } Future _showDownloadNotification( int progress, String fileName, { bool isComplete = false, bool isError = false, }) async { final androidDetails = AndroidNotificationDetails( 'download_channel', 'Downloads', channelDescription: 'Notifications for file downloads', importance: Importance.high, priority: Priority.high, showProgress: !isComplete && !isError, maxProgress: 100, progress: progress, ongoing: !isComplete && !isError, playSound: isComplete || isError, styleInformation: BigTextStyleInformation( isError ? 'Download failed for $fileName' : isComplete ? 'Download complete: $fileName' : 'Downloading $fileName...', ), ); final iosDetails = DarwinNotificationDetails( presentAlert: true, presentBadge: true, presentSound: isComplete || isError, subtitle: isError ? 'Download failed' : isComplete ? 'Download complete' : 'Downloading...', threadIdentifier: 'download_thread', ); final notificationDetails = NotificationDetails( android: androidDetails, iOS: iosDetails, ); await _notificationsPlugin.show( fileName.hashCode, // Unique ID for the notification isError ? 'Download Failed' : isComplete ? 'Download Complete' : 'Downloading File', isError ? 'Failed to download $fileName' : isComplete ? 'Successfully downloaded $fileName' : 'Downloading $fileName ($progress%)', notificationDetails, ); } } class SpinKitRing extends StatefulWidget { const SpinKitRing({ super.key, required this.color, this.lineWidth = 7.0, this.size = 50.0, this.duration = const Duration(milliseconds: 1200), this.controller, }); final Color color; final double size; final double lineWidth; final Duration duration; final AnimationController? controller; @override State createState() => _SpinKitRingState(); } class _SpinKitRingState extends State with SingleTickerProviderStateMixin { late AnimationController _controller; late Animation _animation1; late Animation _animation2; late Animation _animation3; @override void initState() { super.initState(); _controller = (widget.controller ?? AnimationController(vsync: this, duration: widget.duration)) ..addListener(() { if (mounted) { setState(() {}); } }) ..repeat(); _animation1 = Tween(begin: 0.0, end: 1.0).animate( CurvedAnimation( parent: _controller, curve: const Interval(0.0, 1.0, curve: Curves.linear), ), ); _animation2 = Tween(begin: -2 / 3, end: 1 / 2).animate( CurvedAnimation( parent: _controller, curve: const Interval(0.5, 1.0, curve: Curves.linear), ), ); _animation3 = Tween(begin: 0.25, end: 5 / 6).animate( CurvedAnimation( parent: _controller, curve: const Interval(0.0, 1.0, curve: SpinKitRingCurve()), ), ); } @override void dispose() { if (widget.controller == null) { _controller.dispose(); } super.dispose(); } @override Widget build(BuildContext context) { return Center( child: Transform( transform: Matrix4.identity()..rotateZ((_animation1.value) * 5 * pi / 6), alignment: FractionalOffset.center, child: SizedBox.fromSize( size: Size.square(widget.size), child: CustomPaint( foregroundPainter: RingPainter( paintWidth: widget.lineWidth, trackColor: widget.color, progressPercent: _animation3.value, startAngle: pi * _animation2.value, ), ), ), ), ); } } class RingPainter extends CustomPainter { RingPainter({ required this.paintWidth, this.progressPercent, this.startAngle, required this.trackColor, }) : trackPaint = Paint() ..color = trackColor ..style = PaintingStyle.stroke ..strokeWidth = paintWidth ..strokeCap = StrokeCap.square; final double paintWidth; final Paint trackPaint; final Color trackColor; final double? progressPercent; final double? startAngle; @override void paint(Canvas canvas, Size size) { final center = Offset(size.width / 2, size.height / 2); final radius = (min(size.width, size.height) - paintWidth) / 2; canvas.drawArc( Rect.fromCircle(center: center, radius: radius), startAngle!, 2 * pi * progressPercent!, false, trackPaint, ); } @override bool shouldRepaint(CustomPainter oldDelegate) => true; } class SpinKitRingCurve extends Curve { const SpinKitRingCurve(); @override double transform(double t) => (t <= 0.5) ? 2 * t : 2 * (1 - t); }