FBLA24/fbla_ui/lib/pages/export_data.dart

575 lines
22 KiB
Dart

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';
bool isDataTypesFiltered = false;
bool isBusinessesFiltered = true;
bool _isLoading = false;
class ExportData extends StatefulWidget {
final List<Business> businesses;
const ExportData({super.key, required this.businesses});
@override
State<ExportData> createState() => _ExportDataState();
}
class _ExportDataState extends State<ExportData> {
late Future refreshBusinessDataFuture;
@override
void initState() {
super.initState();
refreshBusinessDataFuture = fetchBusinessData();
_isLoading = false;
selectedBusinesses = <Business>{};
}
@override
Widget build(BuildContext context) {
return Scaffold(
floatingActionButton: _FAB(businesses: widget.businesses),
body: CustomScrollView(
slivers: [
SliverAppBar(
forceMaterialTransparency: false,
title: const Text('Export Data'),
toolbarHeight: 70,
pinned: true,
centerTitle: true,
expandedHeight: 120,
backgroundColor: Theme.of(context).colorScheme.background,
actions: [
IconButton(
icon: const Icon(Icons.settings),
onPressed: () {
showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
// DO NOT MOVE TO SEPARATE WIDGET, setState is needed in main tree
backgroundColor:
Theme.of(context).colorScheme.background,
title: const Text('Data Types'),
content: const SizedBox(
width: 400,
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Text(
'Data Columns you would like to show on the datasheet'),
FilterDataTypeChips(),
],
),
),
actions: [
TextButton(
child: const Text('Reset'),
onPressed: () {
setState(() {
dataTypeFilters = <DataType>{};
selectedDataTypes = <DataType>{};
isDataTypesFiltered = false;
});
Navigator.of(context).pop();
}),
TextButton(
child: const Text('Cancel'),
onPressed: () {
selectedDataTypes = Set.from(dataTypeFilters);
Navigator.of(context).pop();
}),
TextButton(
child: const Text('Apply'),
onPressed: () {
setState(() {
selectedDataTypes =
sortDataTypes(selectedDataTypes);
dataTypeFilters =
Set.from(selectedDataTypes);
if (dataTypeFilters.isNotEmpty) {
isDataTypesFiltered = true;
} else {
isDataTypesFiltered = false;
}
});
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: TextField(
onChanged: (query) {
setState(() {
searchFilter = query;
});
},
decoration: InputDecoration(
labelText: 'Search',
hintText: 'Search',
prefixIcon: const Padding(
padding: EdgeInsets.only(left: 8.0),
child: Icon(Icons.search),
),
border: const OutlineInputBorder(
borderRadius: BorderRadius.all(Radius.circular(25.0)),
),
suffixIcon: Padding(
padding: const EdgeInsets.only(right: 8.0),
child: IconButton(
icon: Icon(Icons.filter_list,
color: isFiltered
? Theme.of(context).colorScheme.primary
: Theme.of(context).colorScheme.onBackground),
onPressed: () {
showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
// DO NOT MOVE TO SEPARATE WIDGET, setState is needed in main tree
backgroundColor: Theme.of(context)
.colorScheme
.background,
title: const Text('Filter Options'),
content: const FilterChips(),
actions: [
TextButton(
child: const Text('Reset'),
onPressed: () {
setState(() {
filters = <BusinessType>{};
selectedChips = <BusinessType>{};
isFiltered = false;
});
Navigator.of(context).pop();
}),
TextButton(
child: const Text('Cancel'),
onPressed: () {
selectedChips = Set.from(filters);
Navigator.of(context).pop();
}),
TextButton(
child: const Text('Apply'),
onPressed: () {
setState(() {
filters = selectedChips;
if (filters.isNotEmpty) {
isFiltered = true;
} else {
isFiltered = false;
}
});
Navigator.of(context).pop();
}),
],
);
});
},
),
),
),
),
),
),
),
),
BusinessDisplayPanel(
businesses: widget.businesses,
widescreen: MediaQuery.sizeOf(context).width >= 1000,
selectable: true),
const SliverToBoxAdapter(
child: SizedBox(
height: 100,
),
),
],
),
);
}
}
class _FAB extends StatefulWidget {
final List<Business> businesses;
const _FAB({required this.businesses});
@override
State<_FAB> createState() => _FABState();
}
class _FABState extends State<_FAB> {
@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;
});
try {
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 <= 13
? '${dateTime.hour}:${minute}AM'
: '${dateTime.hour - 12}:${minute}PM';
String fileName =
'Business Data - ${dateTime.month}-${dateTime.day}-${dateTime.year} $time.pdf';
final pdf = pw.Document();
var svgBytes = await marinoDevLogo();
selectedDataTypes = sortDataTypes(selectedDataTypes);
List<pw.Padding> headers = [];
if (selectedDataTypes.isEmpty) {
dataTypeFilters.addAll(DataType.values);
} else {
for (var filter in selectedDataTypes) {
dataTypeFilters.add(filter);
}
}
for (var filter in dataTypeFilters) {
headers.add(pw.Padding(
child: pw.Text(dataTypeFriendly[filter]!,
style: const pw.TextStyle(fontSize: 10)),
padding: const pw.EdgeInsets.all(4.0)));
}
List<pw.TableRow> rows = [];
if (selectedBusinesses.isEmpty) {
selectedBusinesses.addAll(widget.businesses);
isBusinessesFiltered = false;
} else {
isBusinessesFiltered = true;
}
double remainingSpace = 744;
if (dataTypeFilters.contains(DataType.logo)) {
remainingSpace -= 32;
}
if (dataTypeFilters.contains(DataType.type)) {
remainingSpace -= 56;
}
if (dataTypeFilters.contains(DataType.contactName)) {
remainingSpace -= 72;
}
if (dataTypeFilters.contains(DataType.contactPhone)) {
remainingSpace -= 76;
}
double nameWidth = 0;
double websiteWidth = 0;
double contactEmailWidth = 0;
double notesWidth = 0;
double descriptionWidth = 0;
if (dataTypeFilters.contains(DataType.name)) {
nameWidth = (remainingSpace / 6);
}
if (dataTypeFilters.contains(DataType.website)) {
websiteWidth = (remainingSpace / 5);
}
if (dataTypeFilters.contains(DataType.contactEmail)) {
contactEmailWidth = (remainingSpace / 5);
}
if (dataTypeFilters.contains(DataType.notes)) {
notesWidth = (remainingSpace / 7);
}
remainingSpace -=
(nameWidth + websiteWidth + contactEmailWidth + notesWidth);
if (dataTypeFilters.contains(DataType.description)) {
descriptionWidth = remainingSpace;
}
Map<int, pw.TableColumnWidth> columnWidths = {};
int columnNum = -1;
for (var dataType in dataTypeFilters) {
pw.TableColumnWidth width = const pw.FixedColumnWidth(0);
if (dataType == DataType.logo) {
width = const pw.FixedColumnWidth(32);
columnNum++;
} else if (dataType == DataType.name) {
width = pw.FixedColumnWidth(nameWidth);
columnNum++;
} else if (dataType == DataType.description) {
width = pw.FixedColumnWidth(descriptionWidth);
columnNum++;
} else if (dataType == DataType.type) {
width = const pw.FixedColumnWidth(56);
columnNum++;
} else if (dataType == DataType.website) {
width = pw.FixedColumnWidth(websiteWidth);
columnNum++;
} else if (dataType == DataType.contactName) {
width = const pw.FixedColumnWidth(72);
columnNum++;
} else if (dataType == DataType.contactEmail) {
width = pw.FixedColumnWidth(contactEmailWidth);
columnNum++;
} else if (dataType == DataType.contactPhone) {
width = const pw.FixedColumnWidth(76);
columnNum++;
} else if (dataType == DataType.notes) {
width = pw.FixedColumnWidth(notesWidth);
columnNum++;
}
columnWidths.addAll({columnNum: width});
}
for (var business in selectedBusinesses) {
List<pw.Padding> data = [];
bool hasLogo = false;
Uint8List businessLogo = Uint8List(0);
if (dataTypeFilters.contains(DataType.logo)) {
try {
var apiLogo = await getLogo(business.id);
if (apiLogo.runtimeType != String) {
businessLogo = apiLogo;
hasLogo = true;
}
} catch (e) {
if (kDebugMode) {
print('Logo not available! $e');
}
}
}
if (dataTypeFilters.contains(DataType.name)) {
data.add(pw.Padding(
child: pw.Text(
business.name,
// style: const pw.TextStyle(fontSize: 10)
),
padding: const pw.EdgeInsets.all(4.0)));
}
if (dataTypeFilters.contains(DataType.description)) {
pw.TextStyle style = const pw.TextStyle(fontSize: 9);
if (business.description.length >= 200) {
style = const pw.TextStyle(fontSize: 8);
}
if (business.description.length >= 400) {
style = const pw.TextStyle(fontSize: 7);
}
data.add(pw.Padding(
child: pw.Text(
business.description,
style: style,
),
padding: const pw.EdgeInsets.all(4.0)));
}
if (dataTypeFilters.contains(DataType.type)) {
data.add(pw.Padding(
child: pw.Text(
business.type.name,
// style: const pw.TextStyle(fontSize: 10)
),
padding: const pw.EdgeInsets.all(4.0)));
}
if (dataTypeFilters.contains(DataType.website)) {
data.add(pw.Padding(
child: pw.Text(
business.website,
// style: const pw.TextStyle(fontSize: 10)
),
padding: const pw.EdgeInsets.all(4.0)));
}
if (dataTypeFilters.contains(DataType.contactName)) {
data.add(pw.Padding(
child: pw.Text(
business.contactName,
// style: const pw.TextStyle(fontSize: 10)
),
padding: const pw.EdgeInsets.all(4.0)));
}
if (dataTypeFilters.contains(DataType.contactEmail)) {
data.add(pw.Padding(
child: pw.Text(
business.contactEmail,
// style: const pw.TextStyle(fontSize: 10)
),
padding: const pw.EdgeInsets.all(4.0)));
}
if (dataTypeFilters.contains(DataType.contactPhone)) {
data.add(pw.Padding(
child: pw.Text(
business.contactPhone,
// style: const pw.TextStyle(fontSize: 10)
),
padding: const pw.EdgeInsets.all(4.0)));
}
if (dataTypeFilters.contains(DataType.notes)) {
pw.TextStyle style = const pw.TextStyle(fontSize: 9);
if (business.description.length >= 200) {
style = const pw.TextStyle(fontSize: 8);
}
data.add(pw.Padding(
child: pw.Text(business.notes, style: style),
padding: const pw.EdgeInsets.all(4.0)));
}
if (dataTypeFilters.contains(DataType.logo)) {
if (hasLogo) {
rows.add(pw.TableRow(
children: [
pw.Padding(
child: pw.ClipRRect(
child: pw.Image(pw.MemoryImage(businessLogo),
height: 24, width: 24),
horizontalRadius: 4,
verticalRadius: 4),
padding: const pw.EdgeInsets.all(4.0)),
...data
],
));
} else {
rows.add(pw.TableRow(
children: [
pw.Padding(
child: getPwIconFromType(
business.type, 24, PdfColors.black),
padding: const pw.EdgeInsets.all(4.0)),
...data
],
));
}
} else {
rows.add(pw.TableRow(
children: data,
));
}
}
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,
// theme: pw.ThemeData(
// tableCell: const pw.TextStyle(fontSize: 4),
// defaultTextStyle: const pw.TextStyle(fontSize: 4),
// header0: const pw.TextStyle(fontSize: 4),
// paragraphStyle: const pw.TextStyle(fontSize: 4),
// ),
// theme: pw.ThemeData.withFont(
// icons: await PdfGoogleFonts.materialIcons()),
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('Business 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: columnWidths,
// defaultColumnWidth: pw.IntrinsicColumnWidth(),
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: headers,
repeat: true,
),
...rows,
]),
];
}));
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);
}
if (!isBusinessesFiltered) {
selectedBusinesses = <Business>{};
}
setState(() {
_isLoading = false;
});
} catch (e) {
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
content: Text('Error generating PDF! $e'),
width: 300,
behavior: SnackBarBehavior.floating,
duration: const Duration(seconds: 2),
));
}
},
);
}
}