Skip to content

Commit

Permalink
CHORE: Run Dart formatter
Browse files Browse the repository at this point in the history
  • Loading branch information
KOOKIIEStudios committed Nov 12, 2023
1 parent 4bd1fef commit 00c19a1
Show file tree
Hide file tree
Showing 46 changed files with 685 additions and 404 deletions.
83 changes: 56 additions & 27 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
# CastFORM

![GitHub release (latest by date)](https://img.shields.io/github/v/release/BAA-Studios/CastFORM?display_name=tag&label=latest%20version)
[![Code Style: Google](https://img.shields.io/badge/code%20style-google-blueviolet.svg)](https://dart.dev/guides/language/effective-dart/style)
![platform | windows](https://img.shields.io/badge/platform-windows-lightgrey)
Expand All @@ -7,9 +8,14 @@
![GitHub all releases](https://img.shields.io/github/downloads/BAA-Studios/CastFORM/total)

**CastFORM** is an easy-to-use Pokémon TCG Deck Registration Sheet generator.
**CastFORM** offers an elegant graphical interface for filling in Pokémon deck lists for tournaments, and creates beautiful PDFs from user input. This reduces the likelihood of human errors associated with filling out of forms by hand, and also integrates well with existing players' workflows, given the popularity of deck strings (of various formats) in the Pokémon TCG community.
**CastFORM** offers an elegant graphical interface for filling in Pokémon deck lists for tournaments, and creates
beautiful PDFs from user input. This reduces the likelihood of human errors associated with filling out of forms by
hand, and also integrates well with existing players' workflows, given the popularity of deck strings (of various
formats) in the Pokémon TCG community.

This project is based on [Brandon Nguyen's](https://github.com/Bratah123) CLI Python script that served a similar purpose. **CastFORM** was spearheaded in response to the high demand for a user-friendly program in his local TCG community, most of whom were not comfortable with CLI tools.
This project is based on [Brandon Nguyen's](https://github.com/Bratah123) CLI Python script that served a similar
purpose. **CastFORM** was spearheaded in response to the high demand for a user-friendly program in his local TCG
community, most of whom were not comfortable with CLI tools.

## Gallery

Expand All @@ -20,71 +26,86 @@ This project is based on [Brandon Nguyen's](https://github.com/Bratah123) CLI Py
![dark mode screenshot](https://user-images.githubusercontent.com/25145447/232040394-cb3da909-e51b-4736-9497-ee7be42641ce.png)

## Installation & Usage Instructions

*Note: Only 64-bit Windows 11 machines are officially supported.*

### ZIP Archive

1. Download `castform.zip` from the releases page
- Note: actual file name may vary
- Note: actual file name may vary
2. Unzip it into your desired install location
3. Run `CastFORM.exe` from that location

### Windows Installer

1. Download `CastFORM_x64.exe` from the releases page
2. Run it **as administrator** to install CastFORM to `C:\Program Files\CastFORM`
3. Run `CastFORM.exe` from `C:\Program Files\CastFORM\CastFORM.exe`, and/or create a shortcut for it

### Video Instructions

- A video of the installation and usage steps: https://youtu.be/a4peYpnhHg0

---

## Technical Information
**CastFORM** is built with **Flutter 3.13.9** for **64-bit Windows 11**, and targets [**Google's Dart style guide**](https://dart.dev/guides/language/effective-dart/style).

**CastFORM** is built with **Flutter 3.13.9** for **64-bit Windows 11**, and targets [**Google's Dart style guide
**](https://dart.dev/guides/language/effective-dart/style).
For testing, we aim to provide complete coverage for API behaviour internally by release.

### Development Environment Set-up

1. [Install Flutter](https://docs.flutter.dev/get-started/install) and add it to PATH
2. Clone the repository
3. Open the repository in IntelliJ
- You may use your IDE of choice, but we prefer IntelliJ here at BAA Studios
- Make sure you install the Dart and Flutter Plugin for IntelliJ
4. Make sure `Windows (desktop)` is selected in the list of devices
- The location of the button differs depending on IntelliJ version
- For IntelliJ 2022.3 use the drop-down menu at the top right:
![illustration of where the menu is found](https://i.imgur.com/kqMsy3g.png)
- The location of the button differs depending on IntelliJ version
- For IntelliJ 2022.3 use the drop-down menu at the top right:
![illustration of where the menu is found](https://i.imgur.com/kqMsy3g.png)
5. Hit the `Run` button at the top right of the window
- The location of the button differs depending on IntelliJ version
- For IntelliJ 2022.3 click here:
![illustration of where to click](https://i.imgur.com/0FGpLNN.png)
![illustration of where to click](https://i.imgur.com/0FGpLNN.png)

### Internal Package API

API docs for the business logic are included as static HTML files in the repository.
After cloning the repository, navigate to the respective package to open them:
After cloning the repository, navigate to the respective package to open them:

- `packages/deck_string_parser/doc/api/index.html`
- `packages/pokemon_pdf_builder/doc/api/index.html`

### Toolchain

A number of standalone Python scripts have been make to automate metadata fetching and template creation:

- [ForeCAST](https://github.com/KOOKIIEStudios/Forecast)
- Uses Bulbapedia's REST APIs to get all known set abbreviations
- Uses Bulbapedia's REST APIs to get all known set abbreviations
- [WEATHERBall](https://github.com/KOOKIIEStudios/Weather-Ball)
- Remote mode: Scrapes the official Pokémon website for the registration form templates
- Local mode: Converts PDF files to WebP
- Added as a fallback option upon discovery that Pokémon uses Incapsula as an anti-scraping measure
- Remote mode: Scrapes the official Pokémon website for the registration form templates
- Local mode: Converts PDF files to WebP
- Added as a fallback option upon discovery that Pokémon uses Incapsula as an anti-scraping measure

### Build Instructions

1. Run `flutter build windows`
2. Navigate to the output folder `/build/windows/runner/Release`
3. Add the following `dlls` (which can be found in a Visual Studio 2022 installation folder, e.g. `C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Redist\MSVC\14.32.31326\x64\Microsoft.VC143.CRT`)
- msvcp140.dll
- vcruntime140.dll
- vcruntime140_1.dll
3. Add the following `dlls` (which can be found in a Visual Studio 2022 installation folder,
e.g. `C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Redist\MSVC\14.32.31326\x64\Microsoft.VC143.CRT`)
- msvcp140.dll
- vcruntime140.dll
- vcruntime140_1.dll
4. Package the contents of this folder as a ZIP file
- Users can unzip to their desired location and run `CastFORM.exe` from there
- Users can unzip to their desired location and run `CastFORM.exe` from there

### Bundle with Windows Installer (Optional)

*Note: This assumes you've already run through steps 1-4 of the build instructions*
**First time bundling:**
**First time bundling:**

1. Add a batch script `setup.bat` (refer to `/sample_setup.bat` for template)
2. Run `iexpress.exe` as administrator
3. Select `Create new Self Extraction Directive file`, and hit `Next`
Expand All @@ -93,13 +114,14 @@ A number of standalone Python scripts have been make to automate metadata fetchi
6. Select `No prompt`, and hit `Next`
7. Select `Do not display a license`, and hit `Next`
8. Use the `Add` button to add all the files in the output folder from above, and hit `Next`
- The file picker dialog box allows `CTRL + A` to select all files
- The file picker dialog box ignores folders, and does not add them recursively
- You may use `CTRL + A` on the output folder and repeat for all nested folders to quickly add everything
- The file picker dialog box allows `CTRL + A` to select all files
- The file picker dialog box ignores folders, and does not add them recursively
- You may use `CTRL + A` on the output folder and repeat for all nested folders to quickly add everything
9. In the `Install Program` field, click on it and manually replace with `cmd /c setup.bat`, and hit `Next`
- You are able to type in this drop down box
- The `cmd /c` part is **REQUIRED**
- IExpress simply bundles/extracts as well as wrap around a batch script, so all installation logic should be written in `setup.bat`
- You are able to type in this drop down box
- The `cmd /c` part is **REQUIRED**
- IExpress simply bundles/extracts as well as wrap around a batch script, so all installation logic should be
written in `setup.bat`
10. Select `Default`, and hit `Next`
11. Input a message if you like, and hit `Next`
12. Click `Browse` and select an output folder for the resulting installer EXE, and give it a name
Expand All @@ -111,12 +133,19 @@ A number of standalone Python scripts have been make to automate metadata fetchi
16. On the `Create package` page, hit `Next` to start the bundling process, and then `Finish` once it's done

**For subsequent bundling:**

1. Run `iexpress.exe` as administrator
2. Select `Open existing Self Extraction Directive file`, and select the SED file with `Browse`
3. Select `Create Package`
4. On the `Create package` page, hit `Next` to start the bundling process, and then `Finish` once it's done
4. On the `Create package` page, hit `Next` to start the bundling process, and then `Finish` once it's done

---

## Disclaimer
**CastFORM** is an open-source program for a generating a niche type of PDF documents. **CastFORM** is not affiliated with Nintendo, The Pokémon Company, or any Pokémon-related organisations fan-driven or otherwise. **CastFORM** is non-monetised, and provided as is. Every reasonable effort has been taken to ensure correctness and reliability of **CastFORM**. We will not be liable for any special, direct, indirect, or consequential damages or any damages whatsoever resulting from loss of use, data or profits, whether in an action if contract, negligence or other tortious action, arising out of or in connection with the use of **CastFORM** (in part or in whole).

**CastFORM** is an open-source program for a generating a niche type of PDF documents. **CastFORM** is not affiliated
with Nintendo, The Pokémon Company, or any Pokémon-related organisations fan-driven or otherwise. **CastFORM** is
non-monetised, and provided as is. Every reasonable effort has been taken to ensure correctness and reliability of *
*CastFORM**. We will not be liable for any special, direct, indirect, or consequential damages or any damages whatsoever
resulting from loss of use, data or profits, whether in an action if contract, negligence or other tortious action,
arising out of or in connection with the use of **CastFORM** (in part or in whole).
4 changes: 2 additions & 2 deletions analysis_options.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@ linter:
# `// ignore_for_file: name_of_lint` syntax on the line or in the file
# producing the lint.
rules:
# avoid_print: false # Uncomment to disable the `avoid_print` rule
# prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule
# avoid_print: false # Uncomment to disable the `avoid_print` rule
# prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule

# Additional information about this file can be found at
# https://dart.dev/guides/language/analysis-options
6 changes: 3 additions & 3 deletions lib/app.dart
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import 'package:flutter/material.dart';
import 'package:castform/constants.dart' show initPackageInfo, initPdfConstants;
import 'package:castform/screens/home_material.dart';
import 'package:castform/style.dart';
import 'package:flutter/material.dart';

class App extends StatelessWidget {
const App({super.key});
Expand All @@ -14,8 +14,8 @@ class App extends StatelessWidget {
return MaterialApp(
theme: theme,
darkTheme: darkTheme,
home: const HomeMaterial(), // Google-style for Windows
home: const HomeMaterial(), // Google-style for Windows
// home: HomeCupertino(), // Apple-style for macOS
);
}
}
}
18 changes: 7 additions & 11 deletions lib/constants.dart
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:pdf/widgets.dart' as pw;
import 'package:package_info_plus/package_info_plus.dart';
import 'package:pdf/widgets.dart' as pw;
import 'package:printing/printing.dart';

enum PaperType { a4, letter }
Expand All @@ -15,7 +15,6 @@ const defaultBorder = OutlineInputBorder(
borderRadius: BorderRadius.all(Radius.circular(8.0)),
);


pw.TextStyle? formTextStyle;
pw.TextStyle? unicodeTextStyle;
pw.Image? a4FormTemplate;
Expand All @@ -25,9 +24,7 @@ void initPdfConstants() {
PdfGoogleFonts.mPLUS1pMedium().then((value) {
unicodeTextStyle = pw.TextStyle(font: value, fontSize: 15);
});
rootBundle
.load("assets/fonts/RobotoSlab-Regular.ttf")
.then((value) {
rootBundle.load("assets/fonts/RobotoSlab-Regular.ttf").then((value) {
pw.Font font = pw.Font.ttf(value);
formTextStyle = pw.TextStyle(font: font, fontSize: 10.0);
});
Expand Down Expand Up @@ -55,9 +52,8 @@ void initPackageInfo() {

const aboutText = Text(
"CastFORM is a free and easy to use tool for automatic filling out of Pokemon "
"tournament registration sheets!\n"
"\n"
"Put together with love from Brandon Nguyen and Amos Chua of BAA Studios!\n"
"If you enjoy our work, do follow us on GitHub; if you're feeling generous, you can "
"buy us a coffee on ko-fi too!"
);
"tournament registration sheets!\n"
"\n"
"Put together with love from Brandon Nguyen and Amos Chua of BAA Studios!\n"
"If you enjoy our work, do follow us on GitHub; if you're feeling generous, you can "
"buy us a coffee on ko-fi too!");
4 changes: 2 additions & 2 deletions lib/main.dart
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import 'package:flutter/material.dart';
import 'package:castform/app.dart';
import 'package:flutter/material.dart';

void main() => runApp(const App());
void main() => runApp(const App());
1 change: 0 additions & 1 deletion lib/models/save_feedback.dart
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,3 @@ class SaveResponse {
);
}
}

22 changes: 14 additions & 8 deletions lib/models/user.dart
Original file line number Diff line number Diff line change
Expand Up @@ -31,14 +31,16 @@ class User {
// Deck validation
String cachedDeckString = deckString ?? "";
if (!isValidDeckString(cachedDeckString)) {
return const SaveResponse(notificationText: "Deck does not contain 60 cards!", isError: true);
return const SaveResponse(
notificationText: "Deck does not contain 60 cards!", isError: true);
}

// Open save-as dialog, which gives us the full save path as string
var dateTime = DateTime.now();
String? outputFilePath = await FilePicker.platform.saveFile(
dialogTitle: "Please select an output file:",
fileName: "pokemon_registration_sheet_${dateTime.month}${dateTime.day}${dateTime.second}.pdf",
fileName:
"pokemon_registration_sheet_${dateTime.month}${dateTime.day}${dateTime.second}.pdf",
);

if (outputFilePath == null) {
Expand Down Expand Up @@ -78,28 +80,32 @@ class User {
// Export as PDF
try {
await File(outputFilePath).writeAsBytes(await formHandler.buildPdf());
} catch(_) {
return const SaveResponse(notificationText: "Unable to export as PDF!", isError: true);
} catch (_) {
return const SaveResponse(
notificationText: "Unable to export as PDF!", isError: true);
}
// show in Explorer
if (openInExplorer ?? false) {
// strip the trailing file name
var temp = outputFilePath.split("\\");
var directoryPath = "file:/${temp.sublist(0, temp.length - 1).join("\\")}";
var directoryPath =
"file:/${temp.sublist(0, temp.length - 1).join("\\")}";
final Uri uri = Uri.parse(directoryPath);

if (!await launchUrl(uri)) {
return const SaveResponse(notificationText: "Unable to open in Explorer!", isError: true);
return const SaveResponse(
notificationText: "Unable to open in Explorer!", isError: true);
}
}

// show in PDF viewer
if (openInViewer ?? false) {
final Uri uri = Uri.file(outputFilePath);
if (!await launchUrl(uri)) {
return const SaveResponse(notificationText: "Unable to open output file!", isError: true);
return const SaveResponse(
notificationText: "Unable to open output file!", isError: true);
}
}
return const SaveResponse(notificationText: "Successfully saved as PDF!");
}
}
}
4 changes: 2 additions & 2 deletions lib/providers/user_provider.dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import 'package:flutter/foundation.dart';
import 'package:castform/constants.dart';
import 'package:castform/models/user.dart';
import 'package:flutter/foundation.dart';
import 'package:pokemon_pdf_builder/pokemon_pdf.dart';

class UserProvider extends User with ChangeNotifier {
Expand Down Expand Up @@ -43,4 +43,4 @@ class UserProvider extends User with ChangeNotifier {
openInViewer = !(openInViewer ?? false);
notifyListeners();
}
}
}
1 change: 1 addition & 0 deletions lib/screens/buttons.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import 'package:castform/providers/user_provider.dart';

class SaveButton extends StatelessWidget {
final GlobalKey<FormState> formKey;

const SaveButton({super.key, required this.formKey});

@override
Expand Down
21 changes: 13 additions & 8 deletions lib/screens/date_field.dart
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:castform/constants.dart';
import 'package:castform/providers/user_provider.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';

class DateField extends StatefulWidget {
const DateField({super.key});
Expand All @@ -11,8 +11,9 @@ class DateField extends StatefulWidget {
}

class _DateFieldState extends State<DateField> {
final _dateController = TextEditingController(); // Overriding for custom date handling

final _dateController =
TextEditingController(); // Overriding for custom date handling

@override
Widget build(BuildContext context) {
return TextFormField(
Expand All @@ -21,22 +22,26 @@ class _DateFieldState extends State<DateField> {
decoration: const InputDecoration(
labelText: "Date of Birth (Optional)",
),
onTap: () async { // valid date from now to 125 years ago
onTap: () async {
// valid date from now to 125 years ago
await showDatePicker(
context: context,
initialDate: DateTime.now(),
firstDate: DateTime.now().subtract(maximumHumanLifespan),
lastDate: DateTime.now(),
).then((DateTime? value) {
if (value != null) { // Fill the field if input is valid
if (value != null) {
// Fill the field if input is valid
_dateController.text = "${value.month}/${value.day}/${value.year}";
} else { // Clear the field, if "cancel" is clicked
} else {
// Clear the field, if "cancel" is clicked
_dateController.text = "";
}
context.read<UserProvider>().setDate(_dateController.text);
});
},
onSaved: (_) => context.read<UserProvider>().setDate(_dateController.text),
onSaved: (_) =>
context.read<UserProvider>().setDate(_dateController.text),
);
}
}
Loading

0 comments on commit 00c19a1

Please sign in to comment.