diff --git a/assets/recipes/neapolitan_quick.yaml b/assets/recipes/neapolitan_quick.yaml new file mode 100644 index 0000000..9bcf977 --- /dev/null +++ b/assets/recipes/neapolitan_quick.yaml @@ -0,0 +1,54 @@ +recipe: + name: "Neapolitan Day Dough" + description: > + A Neapolitan Style pizza prepared in a day + ingredients: + method: ratio + items: + - name: flour + unit: g + value: 0.595 + - name: water + unit: ml + value: 0.386 + - name: salt + unit: g + value: 0.0178 + - name: yeast + unit: g + value: 0.0012 + steps: + - name: Make the dough + wait: + unit: hours + min: 4 + max: 6 + description: > + Combine all the ingredients to make the dough, we start with just the water and salt and add the yeast after adding some of the flour to not kill the yeast. + substeps: + - name: Salt+Water + description: > + Combine the salt and the water in a bowl, stir until the salt dissolves. + - name: +20% flour + description: > + Add ~20% of the flour to the water/salt mixture and mix everything together. + - name: Yeast + description: > + Add the yeast to the mixture. + - name: Flour + description: > + Add the rest of the flour to the mixture, knead the dough for 15-20 minutes. + - name: Rise + description: > + Split the dough into doughballs and place the dough in a sealed/covered container at room temperature. + - name: Preheat Oven + wait: + unit: minutes + min: 30 + max: 120 + description: > + Preheat the oven in advance to ensure it's as hot as it can be + A high (250 to 300 degrees celsius) is recommended) + - name: Pizza Time! + description: > + Time to make pizza! \ No newline at end of file diff --git a/assets/recipes/new_york.yaml b/assets/recipes/new_york.yaml new file mode 100644 index 0000000..641a948 --- /dev/null +++ b/assets/recipes/new_york.yaml @@ -0,0 +1,54 @@ +recipe: + name: "New York" + description: > + A Neapolitan Style pizza prepared in a day + ingredients: + method: ratio + items: + - name: flour + unit: g + value: 0.545 + - name: water + unit: ml + value: 0.436 + - name: salt + unit: g + value: 0.0178 + - name: yeast + unit: g + value: 0.0012 + steps: + - name: Make the dough + wait: + unit: hours + min: 4 + max: 6 + description: > + Combine all the ingredients to make the dough, we start with just the water and salt and add the yeast after adding some of the flour to not kill the yeast. + substeps: + - name: Salt+Water + description: > + Combine the salt and the water in a bowl, stir until the salt dissolves. + - name: +20% flour + description: > + Add ~20% of the flour to the water/salt mixture and mix everything together. + - name: Yeast + description: > + Add the yeast to the mixture. + - name: Flour + description: > + Add the rest of the flour to the mixture, knead the dough for 15-20 minutes. + - name: Rise + description: > + Split the dough into doughballs and place the dough in a sealed/covered container at room temperature. + - name: Preheat Oven + wait: + unit: minutes + min: 30 + max: 120 + description: > + Preheat the oven in advance to ensure it's as hot as it can be + A high (250 to 300 degrees celsius) is recommended) + - name: Pizza Time! + description: > + Time to make pizza! \ No newline at end of file diff --git a/lib/entities/PizzaEvent.dart b/lib/entities/PizzaEvent.dart index 199439a..a804e1f 100644 --- a/lib/entities/PizzaEvent.dart +++ b/lib/entities/PizzaEvent.dart @@ -1,14 +1,16 @@ +import 'package:pizzaplanner/entities/PizzaRecipe/PizzaRecipe.dart'; + class PizzaEvent { final String name; - final String type; + final PizzaRecipe recipe; final int pizzaCount; final int doughBallSize; final DateTime dateTime; PizzaEvent( this.name, - this.type, + this.recipe, this.pizzaCount, this.doughBallSize, this.dateTime diff --git a/lib/entities/PizzaRecipe/PizzaRecipe.dart b/lib/entities/PizzaRecipe/PizzaRecipe.dart index 4c49240..784c3c9 100644 --- a/lib/entities/PizzaRecipe/PizzaRecipe.dart +++ b/lib/entities/PizzaRecipe/PizzaRecipe.dart @@ -15,8 +15,8 @@ class PizzaRecipe { PizzaRecipe(this.name, this.description, this.ingredients, this.recipeSteps); - static Future fromYaml() async{ - String yamlString = await loadAsset("assets/recipes/neapolitan_cold.yaml"); + static Future fromYaml(yamlPath) async{ + String yamlString = await loadAsset(yamlPath); var yaml = loadYaml(yamlString); var recipe = yaml["recipe"]; @@ -38,11 +38,16 @@ class PizzaRecipe { String stepName = step["name"]; String stepDescription = step["description"]; - YamlMap waitMap = step.containsKey("wait") ? step["wait"] : YamlList(); - String waitUnit = waitMap["unit"]; - int waitMin = waitMap["min"]; - int waitMax = waitMap["max"]; - print(step); + String waitUnit = "none"; + int waitMin = 0; + int waitMax = 0; + + if (step.containsKey("wait")) { + YamlMap waitMap = step["wait"]; + waitUnit = waitMap["unit"]; + waitMin = waitMap["min"]; + waitMax = waitMap["max"]; + } YamlList subSteps = step.containsKey("substeps") ? step["substeps"] : YamlList(); var newSubSteps = List.generate(subSteps.length, (j) { @@ -66,5 +71,9 @@ class PizzaRecipe { newRecipeSteps ); } + + String toString() { + return "PizzaRecipe(${this.name}, ${this.ingredients.ingredients.length}, ${this.recipeSteps.length})"; + } } diff --git a/lib/pages/AddPizzaEventPage.dart b/lib/pages/AddPizzaEventPage.dart index eced963..a0600f5 100644 --- a/lib/pages/AddPizzaEventPage.dart +++ b/lib/pages/AddPizzaEventPage.dart @@ -5,6 +5,8 @@ import 'package:flutter_datetime_picker/flutter_datetime_picker.dart'; import 'package:fluttericon/font_awesome5_icons.dart'; import 'package:intl/intl.dart'; import 'package:pizzaplanner/entities/PizzaEvent.dart'; +import 'package:pizzaplanner/entities/PizzaRecipe/PizzaRecipe.dart'; +import 'package:pizzaplanner/util.dart'; class AddPizzaEventPage extends StatefulWidget { @override @@ -15,156 +17,167 @@ class AddPizzaEventPageState extends State { final DateFormat dateFormatter = DateFormat("yyyy-MM-dd hh:mm"); String name = ""; - String pizzaType = "Neapolitan"; + bool initialized = false; + late PizzaRecipe pizzaRecipe; + late List pizzaTypes; int pizzaCount = 1; int doughBallSize = 250; DateTime eventTime = DateTime.now(); bool nameValidation = false; + @override + void initState() { + super.initState(); + getRecipes().then((pTypes) { + this.pizzaTypes = pTypes; + this.pizzaRecipe = this.pizzaTypes.first; + setState(() {this.initialized = true;}); + }); + } + @override Widget build(BuildContext context) { return Scaffold( - appBar: AppBar( - title: Text("Add Pizza Event"), - ), - body: Container( - padding: EdgeInsets.fromLTRB(40, 10, 40, 10), - child: Column( - children: [ - Row( + appBar: AppBar( + title: Text("Add Pizza Event"), + ), + body: Container( + padding: EdgeInsets.fromLTRB(40, 10, 40, 10), + child: Column( children: [ - Icon(Icons.title), - Container(width: 25), - Expanded( - child: TextField( - decoration: InputDecoration( - hintText: "Event Name", - errorText: this.nameValidation ? "Name can\'t be empty" : null - ), - onChanged: (String newName) { - setState(() { - name = newName; - }); - }, - ), - ) - ] - ), - Row( - children: [ - Icon(FontAwesome5.pizza_slice), - Container(width: 25), - Expanded( - child: DropdownButton( - value: pizzaType, - onChanged: (String? newType) { - setState(() => pizzaType = newType!); - }, - items: ["Neapolitan", "New York", "Chicago"] - .map>((String v) { - return DropdownMenuItem( - value: v, - child: Text(v) - ); - }).toList() - ), - ) - ] - ), - Row( - children: [ - Icon(FontAwesome5.hashtag), - Expanded( - child: Slider( - value: pizzaCount.toDouble(), - min: 1, - max: 20, - divisions: 19, - label: this.pizzaCount.toString(), - onChanged: (double newPizzaCount) { - setState(() {this.pizzaCount = newPizzaCount.round();}); - }, - ) + Row( + children: [ + Icon(Icons.title), + Container(width: 25), + Expanded( + child: TextField( + decoration: InputDecoration( + hintText: "Event Name", + errorText: this.nameValidation ? "Name can\'t be empty" : null + ), + onChanged: (String newName) { + setState(() { + name = newName; + }); + }, + ), + ) + ] ), - Container( - width: 25, - child: Text(this.pizzaCount.toString()) - ) - ] - ), - - Row( - children: [ - Icon(FontAwesome5.weight_hanging), - Expanded( - child: Slider( - value: doughBallSize.toDouble(), - min: 100, - max: 400, - divisions: 30, - label: this.doughBallSize.toString(), - onChanged: (double newDoughBallSize) { - setState(() {this.doughBallSize = newDoughBallSize.round();}); - }, - ) + Row( + children: [ + Icon(FontAwesome5.pizza_slice), + Container(width: 25), + Expanded( + child: this.initialized ? // Only render the dropdown if the recipes have been loaded from storage + DropdownButton( + value: this.pizzaRecipe.name, + onChanged: (String? newType) { + setState(() => this.pizzaRecipe = this.pizzaTypes.firstWhere((pizzaRecipe) => pizzaRecipe.name == newType)); + }, + items: this.pizzaTypes.map((pizzaRecipe) { + return DropdownMenuItem( + value: pizzaRecipe.name, + child: Text(pizzaRecipe.name) + ); + }).toList() + ) : CircularProgressIndicator() + ) + ] ), - Container( - width: 25, - child: Text(this.doughBallSize.toString()) - ) - ] - ), - Row( - children: [ - Icon(FontAwesome5.calendar_alt), - Expanded( - child: InkWell( - child: Center( - child: Text(dateFormatter.format(this.eventTime)), + Row( + children: [ + Icon(FontAwesome5.hashtag), + Expanded( + child: Slider( + value: pizzaCount.toDouble(), + min: 1, + max: 20, + divisions: 19, + label: this.pizzaCount.toString(), + onChanged: (double newPizzaCount) { + setState(() {this.pizzaCount = newPizzaCount.round();}); + }, + ) ), - onTap: () { - DatePicker.showDateTimePicker(context, - showTitleActions: true, - minTime: DateTime.now(), - currentTime: this.eventTime.difference(DateTime.now()).isNegative ? DateTime.now() : this.eventTime, - maxTime: DateTime.now().add(Duration(days: 365*10)), - onConfirm: (newEventTime) { - setState((){ this.eventTime = newEventTime; }); - } - ); - } + Container( + width: 25, + child: Text(this.pizzaCount.toString()) + ) + ] + ), + Row( + children: [ + Icon(FontAwesome5.weight_hanging), + Expanded( + child: Slider( + value: doughBallSize.toDouble(), + min: 100, + max: 400, + divisions: 30, + label: this.doughBallSize.toString(), + onChanged: (double newDoughBallSize) { + setState(() {this.doughBallSize = newDoughBallSize.round();}); + }, + ) + ), + Container( + width: 25, + child: Text(this.doughBallSize.toString()) + ) + ] + ), + Row( + children: [ + Icon(FontAwesome5.calendar_alt), + Expanded( + child: InkWell( + child: Center( + child: Text(dateFormatter.format(this.eventTime)), + ), + onTap: () { + DatePicker.showDateTimePicker(context, + showTitleActions: true, + minTime: DateTime.now(), + currentTime: this.eventTime.difference(DateTime.now()).isNegative ? DateTime.now() : this.eventTime, + maxTime: DateTime.now().add(Duration(days: 365*10)), + onConfirm: (newEventTime) { + setState((){ this.eventTime = newEventTime; }); + } + ); + } + ) + ) + ] + ), + Spacer(), + SizedBox( + width: double.infinity, + height: 70, + child: Container( + color: Colors.blue, + child: TextButton( + child: Text("Add", style: TextStyle(color: Colors.white)), + onPressed: () { + if (this.name.length == 0){ + setState(() { this.nameValidation = true; }); + return; + } + Navigator.pop(context, PizzaEvent( + this.name, + this.pizzaRecipe, + this.pizzaCount, + this.doughBallSize, + this.eventTime + )); + }, + ) ) - ) - ] - ), - Spacer(), - SizedBox( - width: double.infinity, - height: 70, - child: Container( - color: Colors.blue, - child: TextButton( - child: Text("Add", style: TextStyle(color: Colors.white)), - onPressed: () { - if (this.name.length == 0){ - setState(() { this.nameValidation = true; }); - return; - } - Navigator.pop(context, PizzaEvent( - this.name, - this.pizzaType, - this.pizzaCount, - this.doughBallSize, - this.eventTime - )); - }, ) - ) - ) - ] + ] + ) ) - ) ); } } \ No newline at end of file diff --git a/lib/pages/PizzaEventsPage.dart b/lib/pages/PizzaEventsPage.dart index e1c9582..984c31b 100644 --- a/lib/pages/PizzaEventsPage.dart +++ b/lib/pages/PizzaEventsPage.dart @@ -1,6 +1,8 @@ import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:pizzaplanner/entities/PizzaEvent.dart'; +import 'package:pizzaplanner/entities/PizzaRecipe/PizzaRecipe.dart'; +import 'package:pizzaplanner/util.dart'; import 'package:pizzaplanner/widgets/PizzaEventWidget.dart'; class PizzaEventsPage extends StatefulWidget { @@ -30,6 +32,7 @@ class PizzaEventsState extends State { context, "/add", ); + if (newPizzaEvent != null){ this.addPizzaEvent(newPizzaEvent); } diff --git a/lib/util.dart b/lib/util.dart index 00c1874..285e5c2 100644 --- a/lib/util.dart +++ b/lib/util.dart @@ -1,4 +1,21 @@ +import 'dart:convert'; + import 'package:flutter/services.dart' show rootBundle; +import 'package:pizzaplanner/entities/PizzaRecipe/PizzaRecipe.dart'; + +Future> getRecipes() async { + final manifestContent = await rootBundle.loadString('AssetManifest.json'); + final Map manifestMap = json.decode(manifestContent); + final List fileList = manifestMap.keys.toList(); + final List pizzaRecipes = []; + for (var filePath in fileList) { + if (filePath.startsWith("assets/recipes") && filePath.endsWith(".yaml")) { + PizzaRecipe pizzaRecipe = await PizzaRecipe.fromYaml(filePath); + pizzaRecipes.add(pizzaRecipe); + } + } + return pizzaRecipes; +} Future loadAsset(String path) async { return await rootBundle.loadString(path); diff --git a/lib/widgets/PizzaEventWidget.dart b/lib/widgets/PizzaEventWidget.dart index 948a054..26676f5 100644 --- a/lib/widgets/PizzaEventWidget.dart +++ b/lib/widgets/PizzaEventWidget.dart @@ -70,7 +70,7 @@ class PizzaEventWidget extends StatelessWidget { mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text(dateFormatter.format(pizzaEvent.dateTime)), - Text(pizzaEvent.type) + Text(pizzaEvent.recipe.name) ], ),