v0.2.0 beta - Major screen changes
This commit is contained in:
@@ -1,850 +0,0 @@
|
||||
// import 'dart:html' as html;
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:fbla_ui/api_logic.dart';
|
||||
import 'package:fbla_ui/shared.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.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';
|
||||
import 'package:rive/rive.dart';
|
||||
|
||||
class ExportData extends StatefulWidget {
|
||||
final Map<JobType, List<Business>> groupedBusinesses;
|
||||
|
||||
const ExportData({super.key, required this.groupedBusinesses});
|
||||
|
||||
@override
|
||||
State<ExportData> createState() => _ExportDataState();
|
||||
}
|
||||
|
||||
class _ExportDataState extends State<ExportData> {
|
||||
String documentType = 'Business';
|
||||
late Future refreshBusinessDataFuture;
|
||||
bool _isPreviousData = false;
|
||||
late Map<JobType, List<Business>> overviewBusinesses;
|
||||
Set<JobType> jobTypeFilters = <JobType>{};
|
||||
String searchQuery = '';
|
||||
Set<DataTypeJob> selectedDataTypesJob = <DataTypeJob>{};
|
||||
Set<DataTypeBusiness> selectedDataTypesBusiness = <DataTypeBusiness>{};
|
||||
|
||||
Future<void> _setFilters(Set<JobType> filters) async {
|
||||
setState(() {
|
||||
jobTypeFilters = filters;
|
||||
});
|
||||
_updateOverviewBusinesses();
|
||||
}
|
||||
|
||||
Future<void> _updateOverviewBusinesses() async {
|
||||
var refreshedData =
|
||||
fetchBusinessDataOverview(typeFilters: jobTypeFilters.toList());
|
||||
await refreshedData;
|
||||
setState(() {
|
||||
refreshBusinessDataFuture = refreshedData;
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> _setSearch(String search) async {
|
||||
setState(() {
|
||||
searchQuery = search;
|
||||
});
|
||||
_updateOverviewBusinesses();
|
||||
}
|
||||
|
||||
Map<JobType, List<Business>> _filterBySearch(
|
||||
Map<JobType, List<Business>> businesses) {
|
||||
Map<JobType, List<Business>> filteredBusinesses = businesses;
|
||||
|
||||
for (JobType jobType in businesses.keys) {
|
||||
filteredBusinesses[jobType]!.removeWhere((tmpBusiness) => !tmpBusiness
|
||||
.name
|
||||
.replaceAll(RegExp(r'[^a-zA-Z]'), '')
|
||||
.toLowerCase()
|
||||
.contains(searchQuery
|
||||
.replaceAll(RegExp(r'[^a-zA-Z]'), '')
|
||||
.toLowerCase()
|
||||
.trim()));
|
||||
}
|
||||
filteredBusinesses.removeWhere((key, value) => value.isEmpty);
|
||||
return filteredBusinesses;
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
refreshBusinessDataFuture = fetchBusinessDataOverview();
|
||||
selectedBusinesses = <Business>{};
|
||||
}
|
||||
|
||||
void _setStateCallbackReset() {
|
||||
setState(() {
|
||||
selectedDataTypesBusiness = <DataTypeBusiness>{};
|
||||
selectedDataTypesJob = <DataTypeJob>{};
|
||||
documentType = 'Business';
|
||||
});
|
||||
}
|
||||
|
||||
void _setStateCallbackApply(String docType, Set<DataTypeJob> dataFiltersJob,
|
||||
Set<DataTypeBusiness> dataFiltersBusiness) {
|
||||
setState(() {
|
||||
selectedDataTypesBusiness = dataFiltersBusiness;
|
||||
selectedDataTypesJob = dataFiltersJob;
|
||||
documentType = docType;
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
bool widescreen = MediaQuery.sizeOf(context).width >= 1000;
|
||||
return Scaffold(
|
||||
floatingActionButton: _FAB(
|
||||
groupedBusinesses: widget.groupedBusinesses,
|
||||
documentType: documentType,
|
||||
selectedDataTypesBusiness: selectedDataTypesBusiness,
|
||||
selectedDataTypesJob: selectedDataTypesJob,
|
||||
),
|
||||
body: CustomScrollView(
|
||||
slivers: [
|
||||
SliverAppBar(
|
||||
forceMaterialTransparency: false,
|
||||
title: const Text('Export Data'),
|
||||
toolbarHeight: 70,
|
||||
pinned: true,
|
||||
centerTitle: true,
|
||||
expandedHeight: 120,
|
||||
backgroundColor: Theme.of(context).colorScheme.surface,
|
||||
actions: [
|
||||
IconButton(
|
||||
icon: const Icon(Icons.settings),
|
||||
onPressed: () {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
Set<DataTypeBusiness> dataFiltersBusinessTmp =
|
||||
Set<DataTypeBusiness>.from(
|
||||
selectedDataTypesBusiness);
|
||||
Set<DataTypeJob> dataFiltersJobTmp =
|
||||
Set<DataTypeJob>.from(selectedDataTypesJob);
|
||||
String docTypeTmp = documentType;
|
||||
return StatefulBuilder(builder: (context, setState) {
|
||||
void segmentedCallback(String docType) {
|
||||
setState(() {
|
||||
docTypeTmp = docType;
|
||||
});
|
||||
}
|
||||
|
||||
void chipsCallback(
|
||||
{Set<DataTypeJob>? selectedDataTypesJob,
|
||||
Set<DataTypeBusiness>?
|
||||
selectedDataTypesBusiness}) {
|
||||
if (selectedDataTypesJob != null) {
|
||||
dataFiltersJobTmp = selectedDataTypesJob;
|
||||
}
|
||||
if (selectedDataTypesBusiness != null) {
|
||||
dataFiltersBusinessTmp =
|
||||
selectedDataTypesBusiness;
|
||||
}
|
||||
}
|
||||
|
||||
return AlertDialog(
|
||||
// DO NOT MOVE TO SEPARATE WIDGET, setState is needed in main tree
|
||||
backgroundColor:
|
||||
Theme.of(context).colorScheme.surface,
|
||||
title: const Text('Export Settings'),
|
||||
content: SizedBox(
|
||||
width: 450,
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const Text('Document Type:'),
|
||||
_SegmentedButton(
|
||||
callback: segmentedCallback,
|
||||
docType: docTypeTmp,
|
||||
),
|
||||
const Text(
|
||||
'Data Columns you would like to show on the datasheet:'),
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: _FilterDataTypeChips(
|
||||
docTypeTmp,
|
||||
dataFiltersJobTmp,
|
||||
dataFiltersBusinessTmp,
|
||||
chipsCallback),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
child: const Text('Reset'),
|
||||
onPressed: () {
|
||||
_setStateCallbackReset();
|
||||
Navigator.of(context).pop();
|
||||
}),
|
||||
TextButton(
|
||||
child: const Text('Cancel'),
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
}),
|
||||
TextButton(
|
||||
child: const Text('Apply'),
|
||||
onPressed: () {
|
||||
_setStateCallbackApply(
|
||||
docTypeTmp,
|
||||
dataFiltersJobTmp,
|
||||
dataFiltersBusinessTmp);
|
||||
Navigator.of(context).pop();
|
||||
}),
|
||||
],
|
||||
);
|
||||
});
|
||||
});
|
||||
},
|
||||
),
|
||||
],
|
||||
bottom: PreferredSize(
|
||||
preferredSize: const Size.fromHeight(0),
|
||||
child: SizedBox(
|
||||
height: 70,
|
||||
width: 1000,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(10),
|
||||
child: BusinessSearchBar(
|
||||
filters: jobTypeFilters,
|
||||
setFiltersCallback: _setFilters,
|
||||
setSearchCallback: _setSearch),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
FutureBuilder(
|
||||
future: refreshBusinessDataFuture,
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.connectionState == ConnectionState.done) {
|
||||
if (snapshot.hasData) {
|
||||
if (snapshot.data.runtimeType == String) {
|
||||
_isPreviousData = false;
|
||||
return SliverToBoxAdapter(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Column(children: [
|
||||
Center(
|
||||
child: Text(snapshot.data,
|
||||
textAlign: TextAlign.center)),
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: FilledButton(
|
||||
child: const Text('Retry'),
|
||||
onPressed: () {
|
||||
_updateOverviewBusinesses();
|
||||
},
|
||||
),
|
||||
),
|
||||
]),
|
||||
));
|
||||
}
|
||||
|
||||
overviewBusinesses = snapshot.data;
|
||||
_isPreviousData = true;
|
||||
|
||||
return BusinessDisplayPanel(
|
||||
groupedBusinesses: _filterBySearch(overviewBusinesses),
|
||||
widescreen: widescreen,
|
||||
selectable: true);
|
||||
} else if (snapshot.hasError) {
|
||||
return SliverToBoxAdapter(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(left: 16.0, right: 16.0),
|
||||
child: Text(
|
||||
'Error when loading data! Error: ${snapshot.error}'),
|
||||
));
|
||||
}
|
||||
} else if (snapshot.connectionState ==
|
||||
ConnectionState.waiting) {
|
||||
if (_isPreviousData) {
|
||||
return BusinessDisplayPanel(
|
||||
groupedBusinesses: _filterBySearch(overviewBusinesses),
|
||||
widescreen: widescreen,
|
||||
selectable: true);
|
||||
} else {
|
||||
return SliverToBoxAdapter(
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
alignment: Alignment.center,
|
||||
child: const SizedBox(
|
||||
width: 75,
|
||||
height: 75,
|
||||
child: RiveAnimation.asset(
|
||||
'assets/mdev_triangle_loading.riv'),
|
||||
),
|
||||
));
|
||||
}
|
||||
}
|
||||
return SliverToBoxAdapter(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: Text(
|
||||
'\nError: ${snapshot.error}',
|
||||
style: const TextStyle(fontSize: 18),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
),
|
||||
);
|
||||
}),
|
||||
const SliverToBoxAdapter(
|
||||
child: SizedBox(
|
||||
height: 100,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _SegmentedButton extends StatefulWidget {
|
||||
final void Function(String) callback;
|
||||
final String docType;
|
||||
|
||||
const _SegmentedButton({required this.callback, required this.docType});
|
||||
|
||||
@override
|
||||
State<_SegmentedButton> createState() => _SegmentedButtonState();
|
||||
}
|
||||
|
||||
class _SegmentedButtonState extends State<_SegmentedButton> {
|
||||
Set<String> _selected = {};
|
||||
|
||||
void updateSelected(Set<String> newSelection) {
|
||||
setState(() {
|
||||
_selected = newSelection;
|
||||
});
|
||||
widget.callback(newSelection.first);
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_selected = {widget.docType};
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: SegmentedButton(
|
||||
segments: const <ButtonSegment<String>>[
|
||||
ButtonSegment<String>(
|
||||
value: 'Business',
|
||||
label: Text('Businesses'),
|
||||
icon: Icon(Icons.business)),
|
||||
ButtonSegment<String>(
|
||||
value: 'Job Listing',
|
||||
label: Text('Job Listings'),
|
||||
icon: Icon(Icons.work))
|
||||
],
|
||||
selected: _selected,
|
||||
onSelectionChanged: updateSelected,
|
||||
style: SegmentedButton.styleFrom(
|
||||
side: BorderSide(color: Theme.of(context).colorScheme.secondary),
|
||||
)),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _FAB extends StatefulWidget {
|
||||
final String documentType;
|
||||
final Map<JobType, List<Business>> groupedBusinesses;
|
||||
final Set<DataTypeJob> selectedDataTypesJob;
|
||||
final Set<DataTypeBusiness> selectedDataTypesBusiness;
|
||||
|
||||
const _FAB(
|
||||
{required this.groupedBusinesses,
|
||||
required this.documentType,
|
||||
required this.selectedDataTypesJob,
|
||||
required this.selectedDataTypesBusiness});
|
||||
|
||||
@override
|
||||
State<_FAB> createState() => _FABState();
|
||||
}
|
||||
|
||||
class _FABState extends State<_FAB> {
|
||||
List<Business> allBusinesses = [];
|
||||
bool _isLoading = false;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
for (JobType type in widget.groupedBusinesses.keys) {
|
||||
allBusinesses.addAll(widget.groupedBusinesses[type]!);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return FloatingActionButton(
|
||||
child: _isLoading
|
||||
? const Padding(
|
||||
padding: EdgeInsets.all(16.0),
|
||||
child: CircularProgressIndicator(
|
||||
color: Colors.white,
|
||||
strokeWidth: 3.0,
|
||||
),
|
||||
)
|
||||
: const Icon(Icons.save_alt),
|
||||
onPressed: () async {
|
||||
setState(() {
|
||||
_isLoading = true;
|
||||
});
|
||||
|
||||
Set<Business> generateBusinesses = <Business>{};
|
||||
if (selectedBusinesses.isEmpty) {
|
||||
generateBusinesses = Set<Business>.from(allBusinesses);
|
||||
} else {
|
||||
generateBusinesses = selectedBusinesses;
|
||||
}
|
||||
|
||||
await _generatePDF(context, widget.documentType, generateBusinesses,
|
||||
widget.selectedDataTypesBusiness, widget.selectedDataTypesJob);
|
||||
|
||||
setState(() {
|
||||
_isLoading = false;
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
class _FilterDataTypeChips extends StatefulWidget {
|
||||
final String documentType;
|
||||
final Set<DataTypeJob> selectedDataTypesJob;
|
||||
final Set<DataTypeBusiness> selectedDataTypesBusiness;
|
||||
final void Function(
|
||||
{Set<DataTypeJob>? selectedDataTypesJob,
|
||||
Set<DataTypeBusiness>? selectedDataTypesBusiness}) updateCallback;
|
||||
|
||||
const _FilterDataTypeChips(this.documentType, this.selectedDataTypesJob,
|
||||
this.selectedDataTypesBusiness, this.updateCallback);
|
||||
|
||||
@override
|
||||
State<_FilterDataTypeChips> createState() => _FilterDataTypeChipsState();
|
||||
}
|
||||
|
||||
class _FilterDataTypeChipsState extends State<_FilterDataTypeChips> {
|
||||
List<Padding> filterDataTypeChips() {
|
||||
List<Padding> chips = [];
|
||||
|
||||
if (widget.documentType == 'Business') {
|
||||
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);
|
||||
}
|
||||
});
|
||||
widget.updateCallback(
|
||||
selectedDataTypesBusiness:
|
||||
widget.selectedDataTypesBusiness);
|
||||
}),
|
||||
));
|
||||
}
|
||||
} else if (widget.documentType == 'Job Listing') {
|
||||
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);
|
||||
}
|
||||
});
|
||||
widget.updateCallback(
|
||||
selectedDataTypesJob: widget.selectedDataTypesJob);
|
||||
}),
|
||||
));
|
||||
}
|
||||
}
|
||||
return chips;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Wrap(
|
||||
children: filterDataTypeChips(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _generatePDF(
|
||||
BuildContext context,
|
||||
String documentType,
|
||||
Set<Business>? selectedBusinesses,
|
||||
Set<DataTypeBusiness>? dataTypesBusinessInput,
|
||||
Set<DataTypeJob>? dataTypesJobInput) async {
|
||||
Set<DataTypeBusiness> dataTypesBusiness = {};
|
||||
Set<DataTypeJob> dataTypesJob = {};
|
||||
List<pw.Widget> headerColumns = [];
|
||||
List<pw.TableRow> tableRows = [];
|
||||
List<Business> businesses = await fetchBusinesses(
|
||||
selectedBusinesses!.map((business) => business.id).toList());
|
||||
|
||||
if (documentType == 'Business') {
|
||||
dataTypesBusiness = Set.from(dataTypesBusinessInput!);
|
||||
if (dataTypesBusiness.isEmpty) {
|
||||
dataTypesBusiness.addAll(DataTypeBusiness.values);
|
||||
}
|
||||
dataTypesBusiness = sortDataTypesBusiness(dataTypesBusiness);
|
||||
|
||||
for (Business business in businesses) {
|
||||
List<pw.Widget> 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(const pw.IconData(0xe0af), size: 24),
|
||||
padding: const pw.EdgeInsets.all(4.0)));
|
||||
}
|
||||
}
|
||||
for (DataTypeBusiness dataType in dataTypesBusiness) {
|
||||
if (dataType != DataTypeBusiness.logo) {
|
||||
businessRow.add(pw.Padding(
|
||||
child: pw.Text(businessValueFromDataType(business, dataType)),
|
||||
padding: const pw.EdgeInsets.all(4.0)));
|
||||
}
|
||||
}
|
||||
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 (documentType == 'Job Listing') {
|
||||
dataTypesJob = Set.from(dataTypesJobInput!);
|
||||
if (dataTypesJob.isEmpty) {
|
||||
dataTypesJob.addAll(DataTypeJob.values);
|
||||
}
|
||||
List<DataTypeJob> dataTypesJobList =
|
||||
sortDataTypesJob(dataTypesJob).toList();
|
||||
|
||||
List<Map<String, dynamic>> nameMapping = await fetchBusinessNames();
|
||||
|
||||
for (Business business in businesses) {
|
||||
for (JobListing job in business.listings!) {
|
||||
List<pw.Widget> jobRow = [];
|
||||
for (DataTypeJob dataType in dataTypesJobList) {
|
||||
jobRow.add(pw.Padding(
|
||||
child: pw.Text(jobValueFromDataType(job, dataType, nameMapping)),
|
||||
padding: const pw.EdgeInsets.all(4.0)));
|
||||
}
|
||||
tableRows.add(pw.TableRow(children: jobRow));
|
||||
}
|
||||
}
|
||||
|
||||
for (var filter in dataTypesJobList) {
|
||||
headerColumns.add(pw.Padding(
|
||||
child: pw.Text(dataTypeFriendlyJob[filter]!,
|
||||
style: const pw.TextStyle(fontSize: 10)),
|
||||
padding: const pw.EdgeInsets.all(4.0)));
|
||||
}
|
||||
} else {
|
||||
ScaffoldMessenger.of(context).showSnackBar(const SnackBar(
|
||||
content: Text(
|
||||
'Could not identify document type! Please select a type in the generation settings.')));
|
||||
return;
|
||||
}
|
||||
|
||||
// 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 =
|
||||
'$documentType Data - ${dateTime.month}-${dateTime.day}-${dateTime.year} $time.pdf';
|
||||
|
||||
final pdf = pw.Document();
|
||||
var svgBytes = await marinoDevLogo();
|
||||
|
||||
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: utf8.decode(svgBytes), height: 40),
|
||||
pw.Padding(
|
||||
padding: const pw.EdgeInsets.all(8.0),
|
||||
child: pw.Text('$documentType 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: documentType == 'Business'
|
||||
? _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) {
|
||||
// List<int> fileInts = List.from(pdfBytes);
|
||||
// html.AnchorElement(
|
||||
// href:
|
||||
// 'data:application/octet-stream;charset=utf-16le;base64,${base64.encode(fileInts)}')
|
||||
// ..setAttribute('download', fileName)
|
||||
// ..click();
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
Map<int, pw.TableColumnWidth> _businessColumnSizes(
|
||||
Set<DataTypeBusiness> dataTypes) {
|
||||
double space = 744.0;
|
||||
Map<int, pw.TableColumnWidth> map = {};
|
||||
|
||||
if (dataTypes.contains(DataTypeBusiness.logo)) {
|
||||
space -= 28;
|
||||
map.addAll({
|
||||
dataTypePriorityBusiness[DataTypeBusiness.logo]!:
|
||||
const pw.FixedColumnWidth(28)
|
||||
});
|
||||
}
|
||||
if (dataTypes.contains(DataTypeBusiness.contactName)) {
|
||||
space -= 72;
|
||||
map.addAll({
|
||||
dataTypePriorityBusiness[DataTypeBusiness.contactName]!:
|
||||
const pw.FixedColumnWidth(72)
|
||||
});
|
||||
}
|
||||
if (dataTypes.contains(DataTypeBusiness.contactPhone)) {
|
||||
space -= 76;
|
||||
map.addAll({
|
||||
dataTypePriorityBusiness[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 += 2;
|
||||
}
|
||||
if (dataTypes.contains(DataTypeBusiness.description)) {
|
||||
leftNum += 3;
|
||||
}
|
||||
leftNum = space / leftNum;
|
||||
if (dataTypes.contains(DataTypeBusiness.name)) {
|
||||
map.addAll({
|
||||
dataTypePriorityBusiness[DataTypeBusiness.name]!:
|
||||
pw.FixedColumnWidth(leftNum)
|
||||
});
|
||||
}
|
||||
if (dataTypes.contains(DataTypeBusiness.website)) {
|
||||
map.addAll({
|
||||
dataTypePriorityBusiness[DataTypeBusiness.website]!:
|
||||
pw.FixedColumnWidth(leftNum)
|
||||
});
|
||||
}
|
||||
if (dataTypes.contains(DataTypeBusiness.contactEmail)) {
|
||||
map.addAll({
|
||||
dataTypePriorityBusiness[DataTypeBusiness.contactEmail]!:
|
||||
pw.FixedColumnWidth(leftNum)
|
||||
});
|
||||
}
|
||||
if (dataTypes.contains(DataTypeBusiness.notes)) {
|
||||
map.addAll({
|
||||
dataTypePriorityBusiness[DataTypeBusiness.notes]!:
|
||||
pw.FixedColumnWidth(leftNum * 2)
|
||||
});
|
||||
}
|
||||
if (dataTypes.contains(DataTypeBusiness.description)) {
|
||||
map.addAll({
|
||||
dataTypePriorityBusiness[DataTypeBusiness.description]!:
|
||||
pw.FixedColumnWidth(leftNum * 3)
|
||||
});
|
||||
}
|
||||
|
||||
return map;
|
||||
}
|
||||
|
||||
Map<int, pw.TableColumnWidth> _jobColumnSizes(Set<DataTypeJob> dataTypes) {
|
||||
Map<int, pw.TableColumnWidth> map = {};
|
||||
List<DataTypeJob> 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.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.4)
|
||||
});
|
||||
}
|
||||
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.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,
|
||||
List<Map<String, dynamic>> nameMapping) {
|
||||
switch (dataType) {
|
||||
case DataTypeJob.name:
|
||||
return job.name;
|
||||
case DataTypeJob.description:
|
||||
return job.description;
|
||||
case DataTypeJob.wage:
|
||||
return job.wage;
|
||||
case DataTypeJob.link:
|
||||
return job.link;
|
||||
case DataTypeJob.businessName:
|
||||
return nameMapping
|
||||
.where((element) => element['id'] == job.businessId)
|
||||
.first['name'];
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user