diff --git a/lib/screens/roster/pdf/pdf.dart b/lib/screens/roster/pdf/pdf.dart index 02e11ac6..d0e9dfb0 100644 --- a/lib/screens/roster/pdf/pdf.dart +++ b/lib/screens/roster/pdf/pdf.dart @@ -6,6 +6,7 @@ import 'package:gearforce/screens/roster/pdf/record_sheet/record_sheet.dart'; import 'package:gearforce/screens/roster/pdf/record_sheet/rules_sheet.dart'; import 'package:gearforce/screens/roster/pdf/record_sheet/traits_sheet.dart'; import 'package:gearforce/screens/roster/pdf/unit_cards/unit_cards.dart'; +import 'package:gearforce/widgets/pdf_settings.dart'; import 'package:pdf/pdf.dart'; import 'package:pdf/widgets.dart' as pw; import 'package:printing/printing.dart'; @@ -19,82 +20,132 @@ const double _bottomPageMargins = PdfPageFormat.inch / 28; const double _unitCardMargins = 5.0 / 2.0; const pw.EdgeInsets _footerMargin = const pw.EdgeInsets.only(top: 0); -Future printPDF(UnitRoster roster, {required String version}) async { +Future printPDF( + UnitRoster roster, + PDFSettings settings, { + required String version, +}) async { // This is where we print the document return Printing.layoutPdf( // [onLayout] will be called multiple times // when the user changes the printer or printer settings onLayout: (PdfPageFormat format) { // Any valid Pdf document can be returned here as a list of int - return buildPdf(format, roster, version: version); + return buildPdf(format, roster, settings, version: version); }, ); } +Future downloadPDF( + UnitRoster roster, + PDFSettings settings, { + required String version, +}) async { + final pdf = await buildPdf( + PdfPageFormat.letter, + roster, + settings, + version: version, + ); + + final String fileName = '${roster.name ?? _defaultRosterFileName}.pdf'; + final saveLocation = await getSaveLocation(suggestedName: fileName); + if (saveLocation == null) { + // Operation was canceled by the user. + return; + } + + final XFile textFile = XFile.fromData( + pdf, + mimeType: 'application/pdf', + name: fileName, + ); + + await textFile.saveTo(saveLocation.path); +} + /// This method takes a page format and generates the Pdf file data -Future buildPdf(PdfPageFormat format, UnitRoster roster, - {required String version}) async { +Future buildPdf( + PdfPageFormat format, + UnitRoster roster, + PDFSettings settings, { + required String version, +}) async { // Create the Pdf document final pw.Document doc = pw.Document(); final font = await PdfGoogleFonts.nunitoRegular(); final pageTheme = await _myPageTheme(format); - // Add record sheet summary - doc.addPage( - pw.MultiPage( - pageTheme: pageTheme, - build: (pw.Context context) { - return buildRecordSheet(font, roster); - }, - footer: (pw.Context context) { - return _buildFooter(context, version, roster.rulesVersion); - }), - ); + if (settings.sections.recordSheet) { + // Add record sheet summary + doc.addPage( + pw.MultiPage( + pageTheme: pageTheme, + build: (pw.Context context) { + return buildRecordSheet(font, roster); + }, + footer: (pw.Context context) { + return _buildFooter(context, version, roster.rulesVersion); + }), + ); + } - final unitCards = buildUnitCards(font, roster, version: version); - - // Add unit cards - doc.addPage(pw.MultiPage( - pageTheme: pageTheme, - mainAxisAlignment: pw.MainAxisAlignment.start, - crossAxisAlignment: pw.CrossAxisAlignment.center, - build: (pw.Context context) { - return [ - pw.Padding( - padding: pw.EdgeInsets.only(), - child: pw.Wrap( - children: unitCards, - spacing: _unitCardMargins * 2, - runSpacing: _unitCardMargins, - ), - ) - ]; - }, - footer: (pw.Context context) { - return _buildFooter(context, version, roster.rulesVersion); - }, - )); + if (settings.sections.unitCards) { + final unitCards = buildUnitCards(font, roster, version: version); - // Add Trait reference page - doc.addPage(pw.MultiPage( + // Add unit cards + doc.addPage(pw.MultiPage( pageTheme: pageTheme, - build: (context) { - return [buildTraitSheet(font, roster.getAllUnits())]; + mainAxisAlignment: pw.MainAxisAlignment.start, + crossAxisAlignment: pw.CrossAxisAlignment.center, + build: (pw.Context context) { + return [ + pw.Padding( + padding: pw.EdgeInsets.only(), + child: pw.Wrap( + children: unitCards, + spacing: _unitCardMargins * 2, + runSpacing: _unitCardMargins, + ), + ) + ]; }, footer: (pw.Context context) { return _buildFooter(context, version, roster.rulesVersion); - })); - - // Add Rules reference page - doc.addPage(pw.MultiPage( - pageTheme: pageTheme, - build: (context) { - return [buildRulesSheet(font, roster)]; }, - footer: (pw.Context context) { - return _buildFooter(context, version, roster.rulesVersion); - })); + )); + } + if (settings.sections.traitReference) { + // Add Trait reference page + doc.addPage(pw.MultiPage( + pageTheme: pageTheme, + build: (context) { + return [buildTraitSheet(font, roster.getAllUnits())]; + }, + footer: (pw.Context context) { + return _buildFooter(context, version, roster.rulesVersion); + })); + } + + if (settings.sections.factionRules || settings.sections.subFactionRules) { + // Add Rules reference page + doc.addPage(pw.MultiPage( + pageTheme: pageTheme, + build: (context) { + return [ + buildRulesSheet( + font, + roster, + includeFactionRules: settings.sections.factionRules, + includeSubFactionRules: settings.sections.subFactionRules, + ) + ]; + }, + footer: (pw.Context context) { + return _buildFooter(context, version, roster.rulesVersion); + })); + } // Build and return the final Pdf file data return doc.save(); } @@ -131,29 +182,6 @@ pw.Widget _buildFooter( ); } -Future downloadPDF(UnitRoster roster, {required String version}) async { - final pdf = await buildPdf( - PdfPageFormat.letter, - roster, - version: version, - ); - - final String fileName = '${roster.name ?? _defaultRosterFileName}.pdf'; - final saveLocation = await getSaveLocation(suggestedName: fileName); - if (saveLocation == null) { - // Operation was canceled by the user. - return; - } - - final XFile textFile = XFile.fromData( - pdf, - mimeType: 'application/pdf', - name: fileName, - ); - - await textFile.saveTo(saveLocation.path); -} - Future _myPageTheme(PdfPageFormat format) async { final updatedFormat = format.copyWith( marginLeft: _leftRightPageMargins, diff --git a/lib/screens/roster/pdf/pdf_settings_dialog.dart b/lib/screens/roster/pdf/pdf_settings_dialog.dart new file mode 100644 index 00000000..6b4365f1 --- /dev/null +++ b/lib/screens/roster/pdf/pdf_settings_dialog.dart @@ -0,0 +1,102 @@ +import 'package:flutter/material.dart'; +import 'package:gearforce/widgets/pdf_settings.dart'; + +class PDFSettingsDialog extends StatelessWidget { + final String type; + + const PDFSettingsDialog(this.type, {super.key}); + @override + Widget build(BuildContext context) { + PDFSettings settings = PDFSettings(); + + return SimpleDialog( + title: Align( + child: Text('$type Settings', style: TextStyle(fontSize: 24)), + ), + children: [ + Text('Select the sections to include', + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w500, + )), + SettingsOptionLine( + text: 'Record Sheet', + onChanged: (bool? newValue) { + settings.sections.recordSheet = newValue!; + }), + SettingsOptionLine( + text: 'Unit Cards', + onChanged: (bool? newValue) { + settings.sections.unitCards = newValue!; + }), + SettingsOptionLine( + text: 'Traits', + onChanged: (bool? newValue) { + settings.sections.traitReference = newValue!; + }), + SettingsOptionLine( + text: 'Faction Rules', + onChanged: (bool? newValue) { + settings.sections.factionRules = newValue!; + }), + SettingsOptionLine( + text: 'Sub-Faction Rules', + onChanged: (bool? newValue) { + settings.sections.subFactionRules = newValue!; + }), + SimpleDialogOption( + onPressed: () { + Navigator.pop(context, settings); + }, + child: Center( + child: Text( + type, + style: TextStyle( + fontSize: 24, + fontWeight: FontWeight.bold, + color: Colors.green), + ), + ), + ), + ], + contentPadding: EdgeInsets.only(top: 10, bottom: 5, left: 10, right: 10), + titlePadding: EdgeInsets.only(top: 10, bottom: 0, left: 10, right: 10), + ); + } +} + +class SettingsOptionLine extends StatefulWidget { + final String text; + final ValueChanged onChanged; + + SettingsOptionLine({ + required this.text, + required this.onChanged, + }); + + @override + State createState() => _SettingsOptionLineState(); +} + +class _SettingsOptionLineState extends State { + bool value = true; + + @override + Widget build(BuildContext context) { + return Row( + children: [ + Text(widget.text), + Spacer(), + Checkbox( + value: value, + onChanged: (bool? newValue) { + setState(() { + value = newValue!; + }); + widget.onChanged(newValue); + }, + ), + ], + ); + } +} diff --git a/lib/screens/roster/pdf/record_sheet/header.dart b/lib/screens/roster/pdf/record_sheet/header.dart index 3cb7370f..ce49c88b 100644 --- a/lib/screens/roster/pdf/record_sheet/header.dart +++ b/lib/screens/roster/pdf/record_sheet/header.dart @@ -17,12 +17,15 @@ pw.Widget buildRosterHeader(pw.Font font, UnitRoster roster) { return pw.Column( children: [ - pw.Center( - child: pw.Text( - _recordSheetHeadingText, - style: pw.TextStyle( - font: font, - fontSize: _recordSheetHeadingFontSize, + pw.Padding( + padding: pw.EdgeInsets.only(bottom: 5.0), + child: pw.Center( + child: pw.Text( + _recordSheetHeadingText, + style: pw.TextStyle( + font: font, + fontSize: _recordSheetHeadingFontSize, + ), ), ), ), diff --git a/lib/screens/roster/pdf/record_sheet/rules_sheet.dart b/lib/screens/roster/pdf/record_sheet/rules_sheet.dart index 6d7dfb6d..35daaf24 100644 --- a/lib/screens/roster/pdf/record_sheet/rules_sheet.dart +++ b/lib/screens/roster/pdf/record_sheet/rules_sheet.dart @@ -5,19 +5,27 @@ import 'package:pdf/widgets.dart' as pw; const double _headerTextSize = 12; const double _standardTextSize = 10; -pw.Widget buildRulesSheet(pw.Font font, UnitRoster roster) { - // Faction rules +pw.Widget buildRulesSheet( + pw.Font font, + UnitRoster roster, { + bool includeFactionRules = true, + bool includeSubFactionRules = true, +}) { final List<(String, String)> factionRules = []; - FactionRule.enabledRules(roster.rulesetNotifer.value.factionRules) - .forEach((fr) { - factionRules.add((fr.name, fr.description)); - }); + if (includeFactionRules) { + FactionRule.enabledRules(roster.rulesetNotifer.value.factionRules) + .forEach((fr) { + factionRules.add((fr.name, fr.description)); + }); + } final List<(String, String)> subFactionRules = []; - FactionRule.enabledRules(roster.rulesetNotifer.value.subFactionRules) - .forEach((fr) { - subFactionRules.add((fr.name, fr.description)); - }); + if (includeSubFactionRules) { + FactionRule.enabledRules(roster.rulesetNotifer.value.subFactionRules) + .forEach((fr) { + subFactionRules.add((fr.name, fr.description)); + }); + } final sheet = pw.Column(children: [ _buildRuleTable(font, ['Faction Rule', 'Description'], factionRules), diff --git a/lib/screens/roster/roster.dart b/lib/screens/roster/roster.dart index c525c1e7..1cb71a11 100644 --- a/lib/screens/roster/roster.dart +++ b/lib/screens/roster/roster.dart @@ -7,11 +7,13 @@ import 'package:gearforce/screens/roster/filehandler/uploader.dart'; import 'package:gearforce/screens/roster/input_roster_id.dart'; import 'package:gearforce/screens/roster/markdown.dart'; import 'package:gearforce/screens/roster/pdf/pdf.dart'; +import 'package:gearforce/screens/roster/pdf/pdf_settings_dialog.dart'; import 'package:gearforce/screens/roster/roster_display.dart'; import 'package:gearforce/screens/roster/show_roster_id.dart'; import 'package:gearforce/screens/unitSelector/unit_selection.dart'; import 'package:gearforce/widgets/api/api_service.dart'; import 'package:gearforce/widgets/confirmation_dialog.dart'; +import 'package:gearforce/widgets/pdf_settings.dart'; import 'package:gearforce/widgets/roster_id.dart'; import 'package:provider/provider.dart'; import 'package:url_launcher/url_launcher_string.dart'; @@ -19,7 +21,7 @@ import 'package:url_launcher/url_launcher_string.dart'; const double _leftPanelWidth = 670.0; const double _titleHeight = 40.0; const double _menuTitleHeight = 50.0; -const String _version = '1.1.0'; +const String _version = '1.2.0'; const String _bugEmailAddress = 'gearforce@metadiversions.com'; const String _dp9URL = 'https://www.dp9.com/'; const String _sourceCodeURL = 'https://github.com/Ariemeth/gearforce-flutter'; @@ -184,7 +186,14 @@ class _RosterWidgetState extends State { style: TextStyle(fontSize: 16), ), onTap: () async { - printPDF(roster, version: _version); + final pdfSettings = await showDialog( + context: context, + builder: (context) => PDFSettingsDialog('Print'), + ); + + if (pdfSettings != null) { + printPDF(roster, pdfSettings, version: _version); + } }, ), ListTile( @@ -193,7 +202,14 @@ class _RosterWidgetState extends State { style: TextStyle(fontSize: 16), ), onTap: () async { - downloadPDF(roster, version: _version); + final pdfSettings = await showDialog( + context: context, + builder: (context) => PDFSettingsDialog('Export to PDF'), + ); + + if (pdfSettings != null) { + downloadPDF(roster, pdfSettings, version: _version); + } }, ), ListTile( diff --git a/lib/widgets/pdf_settings.dart b/lib/widgets/pdf_settings.dart new file mode 100644 index 00000000..0f4ee4a6 --- /dev/null +++ b/lib/widgets/pdf_settings.dart @@ -0,0 +1,21 @@ +class PDFSettings { + Sections sections = Sections(); + + @override + String toString() { + return 'Sections: ' + + 'recordSheet = ${sections.recordSheet}, ' + + 'unitCards = ${sections.unitCards}, ' + + 'traitReference = ${sections.traitReference}, ' + + 'factionRules = ${sections.factionRules}, ' + + 'subFactionRules = ${sections.subFactionRules}'; + } +} + +class Sections { + bool recordSheet = true; + bool unitCards = true; + bool traitReference = true; + bool factionRules = true; + bool subFactionRules = true; +} diff --git a/pubspec.yaml b/pubspec.yaml index 6ad60c14..e95f4e09 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -16,7 +16,7 @@ publish_to: "none" # Remove this line if you wish to publish to pub.dev # Read more about iOS versioning at # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html -version: 1.1.0 +version: 1.2.0 environment: sdk: ">=3.0.0"