import 'dart:io'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_download_manager/flutter_download_manager.dart'; import 'package:flutter_local_notifications/flutter_local_notifications.dart'; import 'package:generp/Models/crmModels/crmLeadDetailsGenerateQuotationViewResponse.dart'; import 'package:generp/Models/crmModels/crmSelectedProductDetailsResponse.dart'; import 'package:generp/screens/LoginScreen.dart'; import 'package:http/http.dart' as http; import 'package:intl/intl.dart'; import 'package:path_provider/path_provider.dart'; import 'package:permission_handler/permission_handler.dart'; import 'package:provider/provider.dart'; import 'package:url_launcher/url_launcher.dart'; import '../../Utils/commonServices.dart'; import '../../services/api_calling.dart'; import '../HomeScreenNotifier.dart'; class Crmgeneratequotationprovider extends ChangeNotifier { final FlutterLocalNotificationsPlugin _notificationsPlugin = FlutterLocalNotificationsPlugin(); static const platform = MethodChannel('in.webgrid.generp/download'); final GlobalKey webViewKey = GlobalKey(); var dl = DownloadManager(); ProductsData? _selectedProductsDeatilsData; TextEditingController addEditProductPriceController = TextEditingController(); TextEditingController addEditQuantityController = TextEditingController(); TextEditingController addEditTotalAmountController = TextEditingController(); TextEditingController mailIdController = TextEditingController(); TextEditingController mobileController = TextEditingController(); TextEditingController subjectsController = TextEditingController(); TextEditingController taxesController = TextEditingController(); TextEditingController SpecialNoteController = TextEditingController(); TextEditingController forController = TextEditingController(); TextEditingController paymentTermsController = TextEditingController(); List> _productRows = []; // For backend List> get productRows => _productRows; set productRows(value) { _productRows = value; notifyListeners(); } String? _selectedTaxes; String? mailIdError = ""; String? mobileError = ""; String? subjectsError = ""; String? taxesError = ""; String? SpecialNoteError = ""; String? forError = ""; String? paymentTermsError = ""; QuoteDetails _quoteDetails = QuoteDetails(); List _leadProductsList = []; List _productsList = []; bool _isLoading = true; String? _quotationFilePath; LeadProducts? _selectedLeadProducts; Products? _selectedProducts; String? _selectedAddEditLeadProductId; String? _selectedAddEditLeadProductName; String? _selectedAddEditProductName; String? _selectedAddEditProductId; List editProductPriceControllers = []; List editQuantityControllers = []; List editTotalAmountControllers = []; ProductsData? get selectedProductsDetailsData => _selectedProductsDeatilsData; String? get selectedTaxes => _selectedTaxes; List _selectedProductIds = []; List _selectedValues = []; bool _submitLoading = false; String? get quotationFilePath => _quotationFilePath; QuoteDetails get quoteDetails => _quoteDetails; List get leadProductsList => _leadProductsList; List get productsList => _productsList; set leadProductsList(List value) { _leadProductsList = value; notifyListeners(); } set productsList(List value) { _productsList = value; notifyListeners(); } set selectedTaxes(String? value){ _selectedTaxes = value; notifyListeners(); } LeadProducts? get selectedLeadProducts => _selectedLeadProducts; Products? get selectedProducts => _selectedProducts; bool get isLoading => _isLoading; List get selectedProductIds => _selectedProductIds; String? get selectedAddEditLeadProductId => _selectedAddEditLeadProductId; String? get selectedAddEditLeadProductName => _selectedAddEditLeadProductName; String? get selectedAddEditProductId => _selectedAddEditProductId; String? get selectedAddEditProductName => _selectedAddEditProductName; List get selectedValues => _selectedValues; bool get submitLoading => _submitLoading; set selectedLeadProducts(LeadProducts? value) { _selectedLeadProducts = value; _selectedAddEditLeadProductId = value?.productId!; _selectedAddEditLeadProductName = value?.productName; notifyListeners(); } set selectedProducts(Products? value){ _selectedProducts = value; _selectedAddEditProductId = value!.id!; _selectedAddEditProductName = value!.name; notifyListeners(); } set selectedAddEditProductId(String? value){ _selectedAddEditProductId = value; notifyListeners(); } set selectedAddEditProductName(String? value){ _selectedAddEditProductName= value; notifyListeners(); } set selectedAddEditLeadProductId(String? value) { _selectedAddEditLeadProductId = value; notifyListeners(); } set selectedAddEditLeadProductName(String? value) { _selectedAddEditLeadProductName = value; notifyListeners(); } set submitLoading(bool value) { _submitLoading = value; notifyListeners(); } void addEditUpdateTotalAmount(value) { final price = double.tryParse(addEditProductPriceController.text) ?? 0; final qty = int.tryParse(addEditQuantityController.text) ?? 0; addEditTotalAmountController.text = (price * qty).toString(); notifyListeners(); } void editProduct(int index, LeadProducts updatedProduct) { if (index >= 0 && index < leadProductsList.length) { leadProductsList[index] = updatedProduct; _productRows[index] = { "product_id": updatedProduct.productId!, "price": updatedProduct.price, "qty": updatedProduct.qty, "net_price": updatedProduct.prodTotalPrice, }; notifyListeners(); } } void addEditInitializeForm(BuildContext context) { checkDropdownReset(); addEditProductPriceController.clear(); addEditQuantityController.clear(); addEditTotalAmountController.clear(); selectedLeadProducts = null; selectedAddEditLeadProductId = null; selectedAddEditLeadProductName = null; notifyListeners(); } void preFillFormForEdit(LeadProducts product) { checkDropdownReset(); selectedLeadProducts = product; selectedAddEditLeadProductId = product.id; selectedAddEditLeadProductName = product.productName; addEditProductPriceController.text = product.price?.toString() ?? ''; addEditQuantityController.text = product.qty?.toString() ?? ''; addEditTotalAmountController.text = product.prodTotalPrice?.toString() ?? ''; notifyListeners(); } void initializeForm(BuildContext context) { // Clear existing controllers editProductPriceControllers.clear(); editQuantityControllers.clear(); editTotalAmountControllers.clear(); _selectedProductIds.clear(); _selectedValues.clear(); // Initialize controllers for each lead product for (var product in _leadProductsList) { editProductPriceControllers.add( TextEditingController(text: product.price?.toString() ?? ''), ); editQuantityControllers.add( TextEditingController(text: product.qty?.toString() ?? ''), ); editTotalAmountControllers.add( TextEditingController( text: (double.parse(product.price?.toString() ?? '0') * int.parse(product.qty?.toString() ?? '0')) .toString(), ), ); _selectedProductIds.add(product.productId); _selectedValues.add(product.productName); } // Add one empty row if the list is empty if (_leadProductsList.isEmpty) { editAddNewRow(); } notifyListeners(); } Future crmSelectedProductDetailsApiFunction( BuildContext context, String productId, ) async { try { final prov = Provider.of(context, listen: false); final data = await ApiCalling.crmSelectedProductDetailsApi( prov.empId, prov.session, productId, ); if (data != null && data.error == "0") { _selectedProductsDeatilsData = data.productsData!; addEditProductPriceController.text= data.productsData!.price!; notifyListeners(); } } catch (e, s) { print("Error: $e, Stack: $s"); } } void editAddNewRow() { editProductPriceControllers.add(TextEditingController()); editQuantityControllers.add(TextEditingController()); editTotalAmountControllers.add(TextEditingController()); _selectedProductIds.add(null); _selectedValues.add(null); notifyListeners(); } void editRemoveRow(int index) { editProductPriceControllers[index].dispose(); editQuantityControllers[index].dispose(); editTotalAmountControllers[index].dispose(); editProductPriceControllers.removeAt(index); editQuantityControllers.removeAt(index); editTotalAmountControllers.removeAt(index); _selectedProductIds.removeAt(index); _selectedValues.removeAt(index); notifyListeners(); } void updateSelectedProductIds(int index, LeadProducts value) { _selectedProductIds[index] = value.id; _selectedValues[index] = value.productName; notifyListeners(); } void updateTotalAmount(int index) { final price = double.tryParse(editProductPriceControllers[index].text) ?? 0; final qty = int.tryParse(editQuantityControllers[index].text) ?? 0; editTotalAmountControllers[index].text = (price * qty).toString(); notifyListeners(); } //[{"product_id":"32","price":"40000","qty":"1","net_price":"40000"}, // {"product_id":"7","price":"318437.52","qty":"1","net_price":"318437.52"}] List> getFormData() { final List> insertData = []; for (int i = 0; i < editProductPriceControllers.length; i++) { if (_selectedProductIds[i] != null) { final rowData = { "product_id": _selectedProductIds[i]!, "price": editProductPriceControllers[i].text, "qty": editQuantityControllers[i].text, "net_price": editTotalAmountControllers[i].text, }; insertData.add(rowData); } } return insertData; } Future crmLeadDetailsGenerateQuoteViewAPIFunction( BuildContext context, String leadID, ) async { try { final prov = Provider.of(context, listen: false); final data = await ApiCalling.crmLeadDetailsGenerateQuotationViewAPI( prov.empId, prov.session, leadID, ); if (data != null) { if (data.sessionExists == 1) { if (data.error == "0") { _isLoading = false; _leadProductsList = data.leadProducts ?? []; _productsList = data.products ?? []; _quoteDetails = data.quoteDetails ?? QuoteDetails( accountId: "", email: "", forText: "", mobile: "", name: "", paymentTerms: "", subject: "", ); mailIdController.text = data.quoteDetails!.email ?? "-"; mobileController.text = data.quoteDetails!.mobile ?? "-"; subjectsController.text = data.quoteDetails!.subject ?? "-"; // taxesController.text = data.quoteDetails!1; // SpecialNoteController.text = data.quoteDetails; forController.text = data.quoteDetails!.forText ?? "-"; paymentTermsController.text = data.quoteDetails!.paymentTerms ?? "-"; for (var product in data.leadProducts!) { _productRows.add({ "product_id": product.productId!, "price": product.price, "qty": product.qty, "net_price": product.prodTotalPrice, }); } print("_productRows : ${_productRows}"); notifyListeners(); } else { _isLoading = false; notifyListeners(); } } else { sessionDoesNotExist(context); } } else { _isLoading = false; notifyListeners(); } } catch (e, s) { print("Error: $e, Stack: $s"); } } Future crmLeadDetailsGenerateQuoteSubmitAPIFunction( BuildContext context, String leadID, quote_products, quotation_type, ) async { try { final prov = Provider.of(context, listen: false); final data = await ApiCalling.crmLeadDetailsGenerateQuotationSubmitAPI( prov.empId, prov.session, leadID, mobileController.text, subjectsController.text, taxesController.text, SpecialNoteController.text, forController.text, paymentTermsController.text, mailIdController.text, quote_products, quotation_type, ); if (data != null) { if (data.error == "0") { _quotationFilePath = data.quoteFilepath!; if (quotation_type == "genquotedown") { String suggestedFilename = getUniqueFilename('quotation', 'pdf'); String contentDisposition = 'attachment; filename="$suggestedFilename"'; // openWhatsApp(data.quoteFilepath!); _handleDownload( context, data.quoteFilepath!, contentDisposition, 'application/octet-stream', '', ); } else if (quotation_type == "genquotewhatsappbymynum") { openWhatsApp(data.quoteFilepath!); } Navigator.pop(context, true); toast(context, data.message); resetForm(); notifyListeners(); } else {} } else {} } catch (e, s) { print("Error: $e, Stack: $s"); } } String getUniqueFilename(String baseName, [String ext = 'pdf']) { final now = DateTime.now(); final formattedDate = DateFormat('yyyyMMdd_HHmmss').format(now); return '${baseName}_$formattedDate.$ext'; } void openWhatsApp(String path) async { final Uri url = Uri.parse(path); // Example: 919876543210 if (await canLaunchUrl(url)) { await launchUrl(url, mode: LaunchMode.externalApplication); } else { throw 'Could not launch $url'; } } Future _handleDownload( context, String url, String contentDisposition, String mimeType, String suggestedFilename, ) async { // Request notification permission for Android 13+ if (Platform.isIOS) { _handleIOSDownload(context, 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, }); await launchUrl( Uri.parse(url), mode: LaunchMode.externalApplication, ); } else if (Platform.isIOS) { _handleIOSDownload(context, url, suggestedFilename); } } catch (e) { print("Download Error $e"); } } else { toast(context, "Notification Permission Denied"); } } } Future _handleIOSDownload( context, 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, ); } onChangemailId(value) { mailIdError = ""; notifyListeners(); } onChangemobile(value) { mobileError = ""; notifyListeners(); } onChangesubjects(value) { subjectsError = ""; notifyListeners(); } onChangetaxes(value) { taxesError = ""; notifyListeners(); } onChangeSpecialNote(value) { SpecialNoteError = ""; notifyListeners(); } onChangefor(value) { forError = ""; notifyListeners(); } onChangepaymentTerms(value) { paymentTermsError = ""; notifyListeners(); } void resetForm() { checkDropdownReset(); _productRows.clear(); selectedTaxes = null; addEditProductPriceController.clear(); addEditQuantityController.clear(); addEditTotalAmountController.clear(); editProductPriceControllers.clear(); editQuantityControllers.clear(); editTotalAmountControllers.clear(); _selectedLeadProducts = null; _selectedAddEditLeadProductId = null; _selectedAddEditLeadProductName = null; _selectedAddEditProductName = null; _selectedAddEditProductId = null; _selectedProducts = null; notifyListeners(); } void checkDropdownReset() { if (!_leadProductsList.contains(_selectedLeadProducts) && _selectedLeadProducts != null) { _selectedAddEditLeadProductId = null; _selectedAddEditLeadProductName = null; } notifyListeners(); } resetForm2() { _selectedAddEditProductName = null; _selectedAddEditProductId = null; _selectedProducts = null; mailIdController.clear(); mobileController.clear(); subjectsController.clear(); taxesController.clear(); SpecialNoteController.clear(); forController.clear(); paymentTermsController.clear(); editProductPriceControllers.clear(); editQuantityControllers.clear(); editTotalAmountControllers.clear(); _selectedProductIds.clear(); _selectedValues.clear(); _leadProductsList.clear(); mailIdError = ""; mobileError = ""; subjectsError = ""; taxesError = ""; SpecialNoteError = ""; forError = ""; paymentTermsError = ""; notifyListeners(); } }