added page(s) to create a recipe

This commit is contained in:
broodjeaap89 2021-09-05 17:42:20 +02:00
parent c9c4798e87
commit de9e5f8ba6
5 changed files with 432 additions and 24 deletions

View file

@ -8,6 +8,8 @@ import 'package:pizzaplanner/entities/PizzaRecipe/recipe_step.dart';
import 'package:pizzaplanner/entities/PizzaRecipe/recipe_substep.dart'; import 'package:pizzaplanner/entities/PizzaRecipe/recipe_substep.dart';
import 'package:pizzaplanner/pages/add_pizza_event_page.dart'; import 'package:pizzaplanner/pages/add_pizza_event_page.dart';
import 'package:pizzaplanner/pages/add_recipe_page.dart'; import 'package:pizzaplanner/pages/add_recipe_page.dart';
import 'package:pizzaplanner/pages/edit_recipe_step_page.dart';
import 'package:pizzaplanner/pages/edit_recipe_sub_step_page.dart';
import 'package:pizzaplanner/pages/pick_pizza_recipe_page.dart'; import 'package:pizzaplanner/pages/pick_pizza_recipe_page.dart';
import 'package:pizzaplanner/pages/pizza_event_notification_page.dart'; import 'package:pizzaplanner/pages/pizza_event_notification_page.dart';
import 'package:pizzaplanner/pages/pizza_event_page.dart'; import 'package:pizzaplanner/pages/pizza_event_page.dart';
@ -171,6 +173,20 @@ class RouteGenerator {
case "/recipes/add": { case "/recipes/add": {
return MaterialPageRoute(builder: (context) => AddRecipePage()); return MaterialPageRoute(builder: (context) => AddRecipePage());
} }
case "/recipes/add/edit_step": {
final recipeStep = settings.arguments as RecipeStep?;
if(recipeStep == null){
break;
}
return MaterialPageRoute(builder: (context) => EditRecipeStepPage(recipeStep));
}
case "/recipes/add/edit_sub_step": {
final subStep = settings.arguments as RecipeSubStep?;
if(subStep == null){
break;
}
return MaterialPageRoute(builder: (context) => EditRecipeSubStepPage(subStep));
}
default: { default: {
return MaterialPageRoute(builder: (context) => PizzaEventsPage()); return MaterialPageRoute(builder: (context) => PizzaEventsPage());
} }

View file

@ -1,8 +1,10 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_markdown/flutter_markdown.dart'; import 'package:flutter_markdown/flutter_markdown.dart';
import 'package:hive/hive.dart';
import 'package:pizzaplanner/entities/PizzaRecipe/ingredient.dart'; import 'package:pizzaplanner/entities/PizzaRecipe/ingredient.dart';
import 'package:pizzaplanner/entities/PizzaRecipe/pizza_recipe.dart'; import 'package:pizzaplanner/entities/PizzaRecipe/pizza_recipe.dart';
import 'package:pizzaplanner/entities/PizzaRecipe/recipe_step.dart'; import 'package:pizzaplanner/entities/PizzaRecipe/recipe_step.dart';
import 'package:pizzaplanner/entities/PizzaRecipe/recipe_substep.dart';
import 'package:pizzaplanner/pages/scaffold.dart'; import 'package:pizzaplanner/pages/scaffold.dart';
import 'package:url_launcher/url_launcher.dart'; import 'package:url_launcher/url_launcher.dart';
@ -18,32 +20,43 @@ class AddRecipePage extends StatefulWidget {
class AddRecipePageState extends State<AddRecipePage> { class AddRecipePageState extends State<AddRecipePage> {
late PizzaRecipe pizzaRecipe; late PizzaRecipe pizzaRecipe;
AddRecipePageState(){ bool nameValidation = false;
bool descriptionValidation = false;
@override
void initState() {
super.initState();
if (widget.pizzaRecipe == null){ if (widget.pizzaRecipe == null){
pizzaRecipe = PizzaRecipe( pizzaRecipe = PizzaRecipe(
"", "",
"", "",
<Ingredient>[], <Ingredient>[
<RecipeStep>[], Ingredient("Flour", "g", 1.0)
],
<RecipeStep>[
RecipeStep("Step 1", "", "", "", 0, 1, <RecipeSubStep>[])
],
); );
} else { } else {
pizzaRecipe = widget.pizzaRecipe!; pizzaRecipe = widget.pizzaRecipe!;
} }
} }
bool nameValidation = false;
@override @override
Widget build(BuildContext context){ Widget build(BuildContext context){
return PizzaPlannerScaffold( return PizzaPlannerScaffold(
title: const Text("Add Recipe"), title: const Text("Add Recipe"),
resizeToAvoidBottomInset: true,
body: ListView( body: ListView(
children: <Widget>[ children: <Widget>[
TextField( TextFormField(
decoration: InputDecoration( decoration: InputDecoration(
hintText: "Recipe Name", hintText: "Recipe Name",
errorText: nameValidation ? """Name can't be empty""" : null errorText: nameValidation ? """Name can't be empty""" : null
), ),
initialValue: widget.pizzaRecipe?.name,
onChanged: (String newName) { onChanged: (String newName) {
setState(() { setState(() {
pizzaRecipe.name = newName; pizzaRecipe.name = newName;
@ -51,11 +64,12 @@ class AddRecipePageState extends State<AddRecipePage> {
}, },
), ),
const Divider(), const Divider(),
TextField( TextFormField(
decoration: InputDecoration( decoration: InputDecoration(
hintText: "Recipe Description", hintText: "Recipe Description",
errorText: nameValidation ? """Description can't be empty""" : null errorText: descriptionValidation ? """Description can't be empty""" : null
), ),
initialValue: widget.pizzaRecipe?.description,
maxLines: 8, maxLines: 8,
onChanged: (String newDescription) { onChanged: (String newDescription) {
setState(() { setState(() {
@ -105,7 +119,6 @@ class AddRecipePageState extends State<AddRecipePage> {
const Divider(), const Divider(),
const Center( const Center(
child: Text("Ingredients") child: Text("Ingredients")
), ),
const Divider(), const Divider(),
Container( Container(
@ -114,11 +127,7 @@ class AddRecipePageState extends State<AddRecipePage> {
onPressed: () { onPressed: () {
setState(() { setState(() {
pizzaRecipe.ingredients.add( pizzaRecipe.ingredients.add(
Ingredient( Ingredient("", "", 0.0)
"",
"",
0.0
)
); );
}); });
}, },
@ -127,7 +136,41 @@ class AddRecipePageState extends State<AddRecipePage> {
), ),
const Divider(), const Divider(),
] + pizzaRecipe.ingredients.map((ingredient) => buildIngredientRow(ingredient)).toList() + [ ] + pizzaRecipe.ingredients.map((ingredient) => buildIngredientRow(ingredient)).toList() + [
const Divider(),
const Center(
child: Text("Steps")
),
Container(
color: Colors.blue,
child: TextButton(
onPressed: () {
setState(() {
pizzaRecipe.recipeSteps.add(
RecipeStep("Step ${pizzaRecipe.recipeSteps.length+1}", "", "", "minutes", 0, 1, <RecipeSubStep>[])
);
});
},
child: const Text("Add Step", style: TextStyle(color: Colors.white)),
)
),
const Divider()
] + pizzaRecipe.recipeSteps.map((recipeStep) => buildRecipeStepRow(recipeStep)).toList() + [
const Divider(),
Container(
color: Colors.blue,
width: double.infinity,
child: TextButton(
onPressed: () async {
if (pizzaRecipe.isInBox){
pizzaRecipe.save();
} else {
final pizzaRecipesBox = await Hive.openBox<PizzaRecipe>("PizzaRecipes");
pizzaRecipesBox.add(pizzaRecipe);
}
},
child: const Text("Save", style: TextStyle(color: Colors.white)),
)
)
], ],
), ),
); );
@ -137,12 +180,12 @@ class AddRecipePageState extends State<AddRecipePage> {
return Row( return Row(
children: <Widget>[ children: <Widget>[
Expanded( Expanded(
flex: 4, flex: 8,
child: TextField( child: TextFormField(
decoration: const InputDecoration( decoration: const InputDecoration(
hintText: "Name", hintText: "Name",
), ),
controller: TextEditingController(text: ingredient.name), initialValue: ingredient.name,
onChanged: (String newName) { onChanged: (String newName) {
setState(() { setState(() {
ingredient.name = newName; ingredient.name = newName;
@ -151,13 +194,13 @@ class AddRecipePageState extends State<AddRecipePage> {
), ),
), ),
Expanded( Expanded(
flex: 2, flex: 4,
child: TextField( child: TextFormField(
decoration: const InputDecoration( decoration: const InputDecoration(
hintText: "Value", hintText: "Value",
), ),
keyboardType: TextInputType.number, keyboardType: TextInputType.number,
controller: TextEditingController(text: ingredient.value.toString()), initialValue: ingredient.value.toString(),
onChanged: (String newValue) { onChanged: (String newValue) {
setState(() { setState(() {
final newDouble = double.tryParse(newValue); final newDouble = double.tryParse(newValue);
@ -171,11 +214,11 @@ class AddRecipePageState extends State<AddRecipePage> {
), ),
Expanded( Expanded(
flex: 2, flex: 2,
child: TextField( child: TextFormField(
decoration: const InputDecoration( decoration: const InputDecoration(
hintText: "Unit", hintText: "Unit",
), ),
controller: TextEditingController(text: ingredient.unit), initialValue: ingredient.unit,
onChanged: (String newUnit) { onChanged: (String newUnit) {
setState(() { setState(() {
ingredient.unit = newUnit; ingredient.unit = newUnit;
@ -196,6 +239,44 @@ class AddRecipePageState extends State<AddRecipePage> {
] ]
); );
} }
Widget buildRecipeStepRow(RecipeStep recipeStep){
return Row(
children: <Widget>[
Expanded(
flex: 10,
child: Text(recipeStep.name)
),
Expanded(
flex: 4,
child: Container(
color: Colors.blue,
child: TextButton(
onPressed: () {
FocusScope.of(context).unfocus();
Navigator.pushNamed(context, "/recipes/add/edit_step", arguments: recipeStep).then(
(_) {
setState((){});
}
);
},
child: const Text("Edit", style: TextStyle(color: Colors.white)),
)
)
),
Expanded(
child: TextButton(
onPressed: () {
setState(() {
pizzaRecipe.recipeSteps.remove(recipeStep);
});
},
child: const Text("X", style: TextStyle(color: Colors.red)),
)
),
],
);
}
} }
class PreviewMarkdownDescription extends StatelessWidget { class PreviewMarkdownDescription extends StatelessWidget {

View file

@ -0,0 +1,215 @@
import 'package:flutter/material.dart';
import 'package:pizzaplanner/entities/PizzaRecipe/pizza_recipe.dart';
import 'package:pizzaplanner/entities/PizzaRecipe/recipe_step.dart';
import 'package:pizzaplanner/entities/PizzaRecipe/recipe_substep.dart';
import 'package:pizzaplanner/pages/add_recipe_page.dart';
import 'package:pizzaplanner/pages/nav_drawer.dart';
import 'package:pizzaplanner/pages/scaffold.dart';
import 'package:url_launcher/url_launcher.dart';
class EditRecipeStepPage extends StatefulWidget {
final RecipeStep recipeStep;
const EditRecipeStepPage(this.recipeStep);
@override
EditRecipeStepPageState createState() => EditRecipeStepPageState();
}
class EditRecipeStepPageState extends State<EditRecipeStepPage> {
bool nameValidation = false;
bool descriptionValidation = false;
final waitUnits = ["minutes", "hours","days"].map<DropdownMenuItem<String>>((String unit) =>
DropdownMenuItem<String>(
value: unit,
child: Text(unit)
)
).toList();
@override
Widget build(BuildContext context){
return PizzaPlannerScaffold(
title: Text("Edit: ${widget.recipeStep.name}"),
body: ListView(
children: <Widget>[
TextFormField(
decoration: InputDecoration(
hintText: "Recipe Name",
errorText: nameValidation ? """Name can't be empty""" : null
),
initialValue: widget.recipeStep.name,
onChanged: (String newName) {
setState(() {
widget.recipeStep.name = newName;
});
},
),
const Divider(),
TextFormField(
decoration: InputDecoration(
hintText: "Recipe Description",
errorText: descriptionValidation ? """Description can't be empty""" : null
),
initialValue: widget.recipeStep.description,
maxLines: 8,
onChanged: (String newDescription) {
setState(() {
widget.recipeStep.description = newDescription;
});
},
),
const Divider(),
Row(
children: <Widget>[
Expanded(
flex: 2,
child: Container(
color: Colors.blue,
width: double.infinity,
child: TextButton(
onPressed: () {
showDialog(
context: context,
builder: (context) {
return PreviewMarkdownDescription(widget.recipeStep.description);
}
);
},
child: const Text("Preview", style: TextStyle(color: Colors.white)),
)
)
),
const Expanded(
child: SizedBox()
),
Expanded(
flex: 2,
child: Container(
color: Colors.blue,
width: double.infinity,
child: TextButton(
onPressed: () {
launch("https://guides.github.com/features/mastering-markdown/");
},
child: const Text("Markdown?", style: TextStyle(color: Colors.white)),
)
)
)
],
),
const Divider(),
const Center(child: Text("Next step after:")),
Row(
children: <Widget>[
Expanded(
flex: 2,
child: TextFormField(
decoration: const InputDecoration(
hintText: "Min",
),
keyboardType: TextInputType.number,
initialValue: widget.recipeStep.waitMin.toString(),
onChanged: (String newMin) {
setState(() {
widget.recipeStep.waitMin = int.tryParse(newMin) ?? 0;
});
},
),
),
const Expanded(
flex: 2,
child: Center(child: Text("To")),
),
Expanded(
flex: 2,
child: TextFormField(
decoration: const InputDecoration(
hintText: "Max",
),
keyboardType: TextInputType.number,
initialValue: widget.recipeStep.waitMax.toString(),
onChanged: (String newMax) {
setState(() {
widget.recipeStep.waitMax = int.tryParse(newMax) ?? 0;
});
},
),
),
Expanded(
flex: 4,
child: DropdownButton<String>(
value: widget.recipeStep.waitUnit,
items: waitUnits,
onChanged: (String? newUnit){
if (newUnit == null){
return;
}
setState(() {
widget.recipeStep.waitUnit = newUnit;
});
},
)
),
],
),
const Divider(),
const Center(child: Text("Sub Steps")),
Container(
color: Colors.blue,
child: TextButton(
onPressed: () {
setState(() {
widget.recipeStep.subSteps.add(
RecipeSubStep("Sub step ${widget.recipeStep.subSteps.length+1}", "")
);
});
},
child: const Text("Add Sub Step", style: TextStyle(color: Colors.white)),
)
),
const Divider()
] + widget.recipeStep.subSteps.map((subStep) => buildSubStepRow(subStep)).toList()
)
);
}
Widget buildSubStepRow(RecipeSubStep subStep){
return Row(
children: <Widget>[
Expanded(
flex: 10,
child: Text(subStep.name)
),
Expanded(
flex: 4,
child: Container(
color: Colors.blue,
child: TextButton(
onPressed: () {
FocusScope.of(context).unfocus();
Navigator.pushNamed(context, "/recipes/add/edit_sub_step", arguments: subStep).then(
(_) {
setState((){});
}
);
},
child: const Text("Edit", style: TextStyle(color: Colors.white)),
)
)
),
Expanded(
child: TextButton(
onPressed: () {
setState(() {
widget.recipeStep.subSteps.remove(subStep);
});
},
child: const Text("X", style: TextStyle(color: Colors.red)),
)
),
],
);
}
}

View file

@ -0,0 +1,97 @@
import 'package:flutter/material.dart';
import 'package:pizzaplanner/entities/PizzaRecipe/recipe_substep.dart';
import 'package:pizzaplanner/pages/add_recipe_page.dart';
import 'package:pizzaplanner/pages/scaffold.dart';
import 'package:url_launcher/url_launcher.dart';
class EditRecipeSubStepPage extends StatefulWidget {
final RecipeSubStep subStep;
const EditRecipeSubStepPage(this.subStep);
@override
EditRecipeSubStepPageState createState() => EditRecipeSubStepPageState();
}
class EditRecipeSubStepPageState extends State<EditRecipeSubStepPage> {
bool nameValidation = false;
bool descriptionValidation = false;
@override
Widget build(BuildContext context){
return PizzaPlannerScaffold(
title: Text("Edit: ${widget.subStep.name}"),
resizeToAvoidBottomInset: true,
body: Column(
children: <Widget>[
TextFormField(
decoration: InputDecoration(
hintText: "Recipe Name",
errorText: nameValidation ? """Name can't be empty""" : null
),
initialValue: widget.subStep.name,
onChanged: (String newName) {
setState(() {
widget.subStep.name = newName;
});
},
),
const Divider(),
TextFormField(
decoration: InputDecoration(
hintText: "Recipe Description",
errorText: descriptionValidation ? """Description can't be empty""" : null
),
initialValue: widget.subStep.description,
maxLines: 12,
onChanged: (String newDescription) {
setState(() {
widget.subStep.description = newDescription;
});
},
),
const Divider(),
Row(
children: <Widget>[
Expanded(
flex: 2,
child: Container(
color: Colors.blue,
width: double.infinity,
child: TextButton(
onPressed: () {
showDialog(
context: context,
builder: (context) {
return PreviewMarkdownDescription(widget.subStep.description);
}
);
},
child: const Text("Preview", style: TextStyle(color: Colors.white)),
)
)
),
const Expanded(
child: SizedBox()
),
Expanded(
flex: 2,
child: Container(
color: Colors.blue,
width: double.infinity,
child: TextButton(
onPressed: () {
launch("https://guides.github.com/features/mastering-markdown/");
},
child: const Text("Markdown?", style: TextStyle(color: Colors.white)),
)
)
)
],
),
],
)
);
}
}

View file

@ -1,8 +1,7 @@
# TODO # TODO
## Feature ## Feature
- add a page to create your own recipe - longpress recipe to edit it on recipes page?
- longpress recipe to edit it?
- add search to recipes page - add search to recipes page
- add directory structure to recipes? - add directory structure to recipes?
- share to/export of yaml of recipes - share to/export of yaml of recipes