import 'dart:io'; import 'package:fbla_ui/shared/api_logic.dart'; import 'package:fbla_ui/shared/utils.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:open_filex/open_filex.dart'; import 'package:path_provider/path_provider.dart'; import 'package:pdf/pdf.dart'; import 'package:pdf/widgets.dart' as pw; import 'package:printing/printing.dart'; Map dataTypePriorityBusiness = { DataTypeBusiness.logo: 0, DataTypeBusiness.name: 1, DataTypeBusiness.description: 2, DataTypeBusiness.type: 3, DataTypeBusiness.website: 4, DataTypeBusiness.contactName: 5, DataTypeBusiness.contactEmail: 6, DataTypeBusiness.contactPhone: 7, DataTypeBusiness.notes: 8 }; Map dataTypeFriendlyBusiness = { DataTypeBusiness.logo: 'Logo', DataTypeBusiness.name: 'Name', DataTypeBusiness.description: 'Description', DataTypeBusiness.type: 'Type', DataTypeBusiness.website: 'Website', DataTypeBusiness.contactName: 'Contact Name', DataTypeBusiness.contactEmail: 'Contact Email', DataTypeBusiness.contactPhone: 'Contact Phone', DataTypeBusiness.notes: 'Notes' }; Map dataTypePriorityJob = { DataTypeJob.businessName: 1, DataTypeJob.name: 2, DataTypeJob.description: 3, DataTypeJob.type: 4, DataTypeJob.offerType: 5, DataTypeJob.wage: 6, DataTypeJob.link: 7, }; Map dataTypeFriendlyJob = { DataTypeJob.businessName: 'Business Name', DataTypeJob.name: 'Job Listing Name', DataTypeJob.description: 'Description', DataTypeJob.type: 'Job Type', DataTypeJob.offerType: 'Offer Type', DataTypeJob.wage: 'Wage Information', DataTypeJob.link: 'Additional Info Link', }; Set sortDataTypesBusiness(Set set) { List list = set.toList(); list.sort((a, b) { return dataTypePriorityBusiness[a]!.compareTo(dataTypePriorityBusiness[b]!); }); set = list.toSet(); return set; } Set sortDataTypesJob(Set set) { List list = set.toList(); list.sort((a, b) { return dataTypePriorityJob[a]!.compareTo(dataTypePriorityJob[b]!); }); set = list.toSet(); return set; } class _FilterBusinessDataTypeChips extends StatefulWidget { final Set selectedDataTypesBusiness; const _FilterBusinessDataTypeChips({required this.selectedDataTypesBusiness}); @override State<_FilterBusinessDataTypeChips> createState() => _FilterBusinessDataTypeChipsState(); } class _FilterBusinessDataTypeChipsState extends State<_FilterBusinessDataTypeChips> { @override Widget build(BuildContext context) { List chips = []; for (var type in DataTypeBusiness.values) { chips.add(Padding( padding: const EdgeInsets.only(left: 3.0, right: 3.0, bottom: 3.0, top: 3.0), child: FilterChip( shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(20), side: BorderSide(color: Theme.of(context).colorScheme.secondary)), label: Text(dataTypeFriendlyBusiness[type]!), showCheckmark: false, selected: widget.selectedDataTypesBusiness.contains(type), onSelected: (bool selected) { setState(() { if (selected) { widget.selectedDataTypesBusiness.add(type); } else { widget.selectedDataTypesBusiness.remove(type); } }); }), )); } return Wrap( children: chips, ); } } class _FilterJobDataTypeChips extends StatefulWidget { final Set selectedDataTypesJob; const _FilterJobDataTypeChips({required this.selectedDataTypesJob}); @override State<_FilterJobDataTypeChips> createState() => _FilterJobDataTypeChipsState(); } class _FilterJobDataTypeChipsState extends State<_FilterJobDataTypeChips> { @override Widget build(BuildContext context) { List chips = []; for (var type in DataTypeJob.values) { chips.add(Padding( padding: const EdgeInsets.only(left: 3.0, right: 3.0, bottom: 3.0, top: 3.0), child: FilterChip( shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(20), side: BorderSide(color: Theme.of(context).colorScheme.secondary)), label: Text(dataTypeFriendlyJob[type]!), showCheckmark: false, selected: widget.selectedDataTypesJob.contains(type), onSelected: (bool selected) { setState(() { if (selected) { widget.selectedDataTypesJob.add(type); } else { widget.selectedDataTypesJob.remove(type); } }); }), )); } return Wrap( children: chips, ); } } Future generatePDF( {required BuildContext context, required int documentTypeIndex, Set? selectedBusinesses, Set? selectedJobs}) async { List headerColumns = []; List tableRows = []; Set dataTypesBusiness = {}; Set dataTypesJob = {}; showDialog( context: context, builder: (BuildContext context) { return AlertDialog( contentPadding: const EdgeInsets.all(16), scrollable: true, title: const Text('Export Settings'), content: SizedBox( width: 400, child: Column( mainAxisSize: MainAxisSize.min, children: [ const Padding( padding: EdgeInsets.all(8.0), child: Text('Data columns you want to export:'), ), documentTypeIndex == 0 ? _FilterBusinessDataTypeChips( selectedDataTypesBusiness: dataTypesBusiness, ) : _FilterJobDataTypeChips( selectedDataTypesJob: dataTypesJob) ], ), ), actions: [ TextButton( child: const Text('Cancel'), onPressed: () { Navigator.of(context).pop(); }), TextButton( child: const Text('Generate'), onPressed: () async { if (documentTypeIndex == 0) { List businesses = await fetchBusinesses( selectedBusinesses! .map((business) => business.id) .toList()); if (dataTypesBusiness.isEmpty) { dataTypesBusiness.addAll(DataTypeBusiness.values); } dataTypesBusiness = sortDataTypesBusiness(dataTypesBusiness); for (Business business in businesses) { List businessRow = []; if (dataTypesBusiness.contains(DataTypeBusiness.logo)) { var apiLogo = await getLogo(business.id); if (apiLogo.runtimeType != String) { businessRow.add(pw.Padding( child: pw.ClipRRect( child: pw.Image(pw.MemoryImage(apiLogo), height: 24, width: 24), horizontalRadius: 4, verticalRadius: 4), padding: const pw.EdgeInsets.all(4.0))); } else { businessRow.add(pw.Padding( child: pw.Icon( getPwIconFromBusinessType(business.type!), size: 24), padding: const pw.EdgeInsets.all(4.0))); } } for (DataTypeBusiness dataType in dataTypesBusiness) { if (dataType != DataTypeBusiness.logo) { var currentValue = businessValueFromDataType(business, dataType); if (currentValue != null) { businessRow.add(pw.Padding( child: pw.Text(businessValueFromDataType( business, dataType)), padding: const pw.EdgeInsets.all(4.0))); } else { businessRow.add(pw.Container()); } } } tableRows.add(pw.TableRow(children: businessRow)); } for (var filter in dataTypesBusiness) { headerColumns.add(pw.Padding( child: pw.Text(dataTypeFriendlyBusiness[filter]!, style: const pw.TextStyle(fontSize: 10)), padding: const pw.EdgeInsets.all(4.0))); } } else { if (dataTypesJob.isEmpty) { dataTypesJob.addAll(DataTypeJob.values); } dataTypesJob = sortDataTypesJob(dataTypesJob); // List> nameMapping = // await fetchBusinessNames(); for (Business business in selectedJobs!) { for (JobListing job in business.listings!) { List jobRow = []; for (DataTypeJob dataType in dataTypesJob) { switch (dataType) { case DataTypeJob.businessName: jobRow.add(pw.Padding( child: pw.Text(business.name!), padding: const pw.EdgeInsets.all(4.0))); case DataTypeJob.type: jobRow.add(pw.Padding( child: pw.Text(getNameFromJobType(job.type!)), padding: const pw.EdgeInsets.all(4.0))); case DataTypeJob.offerType: jobRow.add(pw.Padding( child: pw.Text( getNameFromOfferType(job.offerType!)), padding: const pw.EdgeInsets.all(4.0))); default: jobRow.add(pw.Padding( child: pw.Text( jobValueFromDataType(job, dataType) ?? ''), padding: const pw.EdgeInsets.all(4.0))); } // if (dataType != DataTypeJob.businessName) { // var currentValue = // jobValueFromDataType(job, dataType); // if (currentValue != null) { // jobRow.add(pw.Padding( // child: pw.Text(currentValue), // padding: const pw.EdgeInsets.all(4.0))); // } else { // jobRow.add(pw.Container()); // } // } else { // jobRow.add(pw.Padding( // child: pw.Text(business.name!), // padding: const pw.EdgeInsets.all(4.0))); // } } tableRows.add(pw.TableRow(children: jobRow)); } } for (var filter in dataTypesJob) { headerColumns.add(pw.Padding( child: pw.Text(dataTypeFriendlyJob[filter]!, style: const pw.TextStyle(fontSize: 10)), padding: const pw.EdgeInsets.all(4.0))); } } // Final Generation DateTime dateTime = DateTime.now(); String minute = '00'; if (dateTime.minute.toString().length < 2) { minute = '0${dateTime.minute}'; } else { minute = dateTime.minute.toString(); } String time = dateTime.hour <= 12 ? '${dateTime.hour}:${minute}AM' : '${dateTime.hour - 12}:${minute}PM'; String fileName = '${documentTypeIndex == 0 ? 'Business' : 'Job Listing'} Data - ${dateTime.month}-${dateTime.day}-${dateTime.year} $time.pdf'; final pdf = pw.Document(); var svg = await rootBundle.loadString('assets/MarinoDev.svg'); var themeIcon = pw.ThemeData.withFont( base: await PdfGoogleFonts.notoSansDisplayMedium(), icons: await PdfGoogleFonts.materialIcons()); var finalTheme = themeIcon.copyWith( defaultTextStyle: const pw.TextStyle(fontSize: 9), ); pdf.addPage(pw.MultiPage( theme: finalTheme, pageFormat: PdfPageFormat.letter, orientation: pw.PageOrientation.landscape, margin: const pw.EdgeInsets.all(24), build: (pw.Context context) { return [ pw.Row( mainAxisAlignment: pw.MainAxisAlignment.spaceBetween, children: [ pw.SvgImage(svg: svg, height: 40), pw.Padding( padding: const pw.EdgeInsets.all(8.0), child: pw.Text( '${documentTypeIndex == 0 ? 'Business' : 'Job Listing'} Datasheet', style: pw.TextStyle( fontSize: 32, fontWeight: pw.FontWeight.bold)), ), pw.Text( 'Generated on ${dateTime.month}/${dateTime.day}/${dateTime.year} at $time', style: const pw.TextStyle(fontSize: 12), textAlign: pw.TextAlign.right), // ]), pw.Table( columnWidths: documentTypeIndex == 0 ? _businessColumnSizes(dataTypesBusiness) : _jobColumnSizes(dataTypesJob), border: const pw.TableBorder( bottom: pw.BorderSide(), left: pw.BorderSide(), right: pw.BorderSide(), top: pw.BorderSide(), horizontalInside: pw.BorderSide(), verticalInside: pw.BorderSide()), children: [ pw.TableRow( decoration: const pw.BoxDecoration( color: PdfColors.blue400), children: headerColumns, repeat: true, ), ...tableRows, ]) ]; })); Uint8List pdfBytes = await pdf.save(); if (kIsWeb) { await Printing.sharePdf( bytes: await pdf.save(), filename: fileName, ); } else { var dir = await getTemporaryDirectory(); var tempDir = dir.path; File pdfFile = File('$tempDir/$fileName'); pdfFile.writeAsBytesSync(pdfBytes); OpenFilex.open(pdfFile.path); } Navigator.of(context).pop(); }), ], ); }); } Map _businessColumnSizes( Set dataTypes) { double space = 744.0; List sorted = sortDataTypesBusiness(dataTypes).toList(); Map map = {}; if (sorted.contains(DataTypeBusiness.logo)) { space -= 32; map.addAll( {sorted.indexOf(DataTypeBusiness.logo): const pw.FixedColumnWidth(32)}); } if (sorted.contains(DataTypeBusiness.type)) { space -= 68; map.addAll( {sorted.indexOf(DataTypeBusiness.type): const pw.FixedColumnWidth(68)}); } if (dataTypes.contains(DataTypeBusiness.contactName)) { space -= 72; map.addAll({ sorted.indexOf(DataTypeBusiness.contactName): const pw.FixedColumnWidth(72) }); } if (dataTypes.contains(DataTypeBusiness.contactPhone)) { space -= 76; map.addAll({ sorted.indexOf(DataTypeBusiness.contactPhone): const pw.FixedColumnWidth(76) }); } double leftNum = 0; if (dataTypes.contains(DataTypeBusiness.name)) { leftNum += 1; } if (dataTypes.contains(DataTypeBusiness.website)) { leftNum += 1; } if (dataTypes.contains(DataTypeBusiness.contactEmail)) { leftNum += 1; } if (dataTypes.contains(DataTypeBusiness.notes)) { leftNum += 1; } if (dataTypes.contains(DataTypeBusiness.description)) { leftNum += 3; } leftNum = space / leftNum; if (dataTypes.contains(DataTypeBusiness.name)) { map.addAll( {sorted.indexOf(DataTypeBusiness.name): pw.FixedColumnWidth(leftNum)}); } if (dataTypes.contains(DataTypeBusiness.website)) { map.addAll({ sorted.indexOf(DataTypeBusiness.website): pw.FixedColumnWidth(leftNum) }); } if (dataTypes.contains(DataTypeBusiness.contactEmail)) { map.addAll({ sorted.indexOf(DataTypeBusiness.contactEmail): pw.FixedColumnWidth(leftNum) }); } if (dataTypes.contains(DataTypeBusiness.notes)) { map.addAll( {sorted.indexOf(DataTypeBusiness.notes): pw.FixedColumnWidth(leftNum)}); } if (dataTypes.contains(DataTypeBusiness.description)) { map.addAll({ sorted.indexOf(DataTypeBusiness.description): pw.FixedColumnWidth(leftNum * 3) }); } return map; } Map _jobColumnSizes(Set dataTypes) { Map map = {}; List sortedDataTypes = sortDataTypesJob(dataTypes).toList(); if (dataTypes.contains(DataTypeJob.businessName)) { map.addAll({ sortedDataTypes.indexOf(sortedDataTypes .where((element) => element == DataTypeJob.businessName) .first): const pw.FractionColumnWidth(0.2) }); } if (dataTypes.contains(DataTypeJob.type)) { map.addAll({ sortedDataTypes.indexOf(sortedDataTypes .where((element) => element == DataTypeJob.type) .first): const pw.FractionColumnWidth(0.1) }); } if (dataTypes.contains(DataTypeJob.name)) { map.addAll({ sortedDataTypes.indexOf(sortedDataTypes .where((element) => element == DataTypeJob.name) .first): const pw.FractionColumnWidth(0.2) }); } if (dataTypes.contains(DataTypeJob.description)) { map.addAll({ sortedDataTypes.indexOf(sortedDataTypes .where((element) => element == DataTypeJob.description) .first): const pw.FractionColumnWidth(0.3) }); } if (dataTypes.contains(DataTypeJob.offerType)) { map.addAll({ sortedDataTypes.indexOf(sortedDataTypes .where((element) => element == DataTypeJob.offerType) .first): const pw.FractionColumnWidth(0.1) }); } if (dataTypes.contains(DataTypeJob.wage)) { map.addAll({ sortedDataTypes.indexOf(sortedDataTypes .where((element) => element == DataTypeJob.wage) .first): const pw.FractionColumnWidth(0.15) }); } if (dataTypes.contains(DataTypeJob.link)) { map.addAll({ sortedDataTypes.indexOf(sortedDataTypes .where((element) => element == DataTypeJob.link) .first): const pw.FractionColumnWidth(0.2) }); } return map; } dynamic businessValueFromDataType( Business business, DataTypeBusiness dataType) { switch (dataType) { case DataTypeBusiness.name: return business.name; case DataTypeBusiness.description: return business.description; case DataTypeBusiness.type: return getNameFromBusinessType(business.type!); case DataTypeBusiness.website: return business.website; case DataTypeBusiness.contactName: return business.contactName; case DataTypeBusiness.contactEmail: return business.contactEmail; case DataTypeBusiness.contactPhone: return business.contactPhone; case DataTypeBusiness.notes: return business.notes; case DataTypeBusiness.logo: return null; } } dynamic jobValueFromDataType(JobListing job, DataTypeJob dataType) { switch (dataType) { case DataTypeJob.name: return job.name; case DataTypeJob.description: return job.description; case DataTypeJob.type: return job.type; case DataTypeJob.offerType: return job.offerType; case DataTypeJob.wage: return job.wage; case DataTypeJob.link: return job.link; case DataTypeJob.businessName: return null; } }