diff --git a/lib/main.dart b/lib/main.dart index 5eb4bce..ff9a9b0 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -8,6 +8,8 @@ import 'package:pizzaplanner/entities/PizzaRecipe/recipe_step.dart'; import 'package:pizzaplanner/entities/PizzaRecipe/recipe_substep.dart'; import 'package:pizzaplanner/pages/add_pizza_event_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/pizza_event_notification_page.dart'; import 'package:pizzaplanner/pages/pizza_event_page.dart'; @@ -171,6 +173,20 @@ class RouteGenerator { case "/recipes/add": { 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: { return MaterialPageRoute(builder: (context) => PizzaEventsPage()); } diff --git a/lib/pages/add_recipe_page.dart b/lib/pages/add_recipe_page.dart index c7f393d..85aa8c0 100644 --- a/lib/pages/add_recipe_page.dart +++ b/lib/pages/add_recipe_page.dart @@ -1,8 +1,10 @@ import 'package:flutter/material.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/pizza_recipe.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:url_launcher/url_launcher.dart'; @@ -18,32 +20,43 @@ class AddRecipePage extends StatefulWidget { class AddRecipePageState extends State { late PizzaRecipe pizzaRecipe; - AddRecipePageState(){ + bool nameValidation = false; + bool descriptionValidation = false; + + @override + void initState() { + super.initState(); if (widget.pizzaRecipe == null){ pizzaRecipe = PizzaRecipe( "", "", - [], - [], + [ + Ingredient("Flour", "g", 1.0) + ], + [ + RecipeStep("Step 1", "", "", "", 0, 1, []) + ], ); } else { pizzaRecipe = widget.pizzaRecipe!; } + } - bool nameValidation = false; @override Widget build(BuildContext context){ return PizzaPlannerScaffold( title: const Text("Add Recipe"), + resizeToAvoidBottomInset: true, body: ListView( children: [ - TextField( + TextFormField( decoration: InputDecoration( hintText: "Recipe Name", errorText: nameValidation ? """Name can't be empty""" : null ), + initialValue: widget.pizzaRecipe?.name, onChanged: (String newName) { setState(() { pizzaRecipe.name = newName; @@ -51,11 +64,12 @@ class AddRecipePageState extends State { }, ), const Divider(), - TextField( + TextFormField( decoration: InputDecoration( 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, onChanged: (String newDescription) { setState(() { @@ -105,7 +119,6 @@ class AddRecipePageState extends State { const Divider(), const Center( child: Text("Ingredients") - ), const Divider(), Container( @@ -114,11 +127,7 @@ class AddRecipePageState extends State { onPressed: () { setState(() { pizzaRecipe.ingredients.add( - Ingredient( - "", - "", - 0.0 - ) + Ingredient("", "", 0.0) ); }); }, @@ -127,7 +136,41 @@ class AddRecipePageState extends State { ), const Divider(), ] + 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, []) + ); + }); + }, + 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("PizzaRecipes"); + pizzaRecipesBox.add(pizzaRecipe); + } + }, + child: const Text("Save", style: TextStyle(color: Colors.white)), + ) + ) ], ), ); @@ -137,12 +180,12 @@ class AddRecipePageState extends State { return Row( children: [ Expanded( - flex: 4, - child: TextField( + flex: 8, + child: TextFormField( decoration: const InputDecoration( hintText: "Name", ), - controller: TextEditingController(text: ingredient.name), + initialValue: ingredient.name, onChanged: (String newName) { setState(() { ingredient.name = newName; @@ -151,13 +194,13 @@ class AddRecipePageState extends State { ), ), Expanded( - flex: 2, - child: TextField( + flex: 4, + child: TextFormField( decoration: const InputDecoration( hintText: "Value", ), keyboardType: TextInputType.number, - controller: TextEditingController(text: ingredient.value.toString()), + initialValue: ingredient.value.toString(), onChanged: (String newValue) { setState(() { final newDouble = double.tryParse(newValue); @@ -171,11 +214,11 @@ class AddRecipePageState extends State { ), Expanded( flex: 2, - child: TextField( + child: TextFormField( decoration: const InputDecoration( hintText: "Unit", ), - controller: TextEditingController(text: ingredient.unit), + initialValue: ingredient.unit, onChanged: (String newUnit) { setState(() { ingredient.unit = newUnit; @@ -196,6 +239,44 @@ class AddRecipePageState extends State { ] ); } + + Widget buildRecipeStepRow(RecipeStep recipeStep){ + return Row( + children: [ + 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 { diff --git a/lib/pages/edit_recipe_step_page.dart b/lib/pages/edit_recipe_step_page.dart new file mode 100644 index 0000000..a3b3702 --- /dev/null +++ b/lib/pages/edit_recipe_step_page.dart @@ -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 { + + bool nameValidation = false; + bool descriptionValidation = false; + + final waitUnits = ["minutes", "hours","days"].map>((String unit) => + DropdownMenuItem( + value: unit, + child: Text(unit) + ) + ).toList(); + + @override + Widget build(BuildContext context){ + return PizzaPlannerScaffold( + title: Text("Edit: ${widget.recipeStep.name}"), + body: ListView( + children: [ + 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: [ + 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: [ + 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( + 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: [ + 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)), + ) + ), + ], + ); + } +} \ No newline at end of file diff --git a/lib/pages/edit_recipe_sub_step_page.dart b/lib/pages/edit_recipe_sub_step_page.dart new file mode 100644 index 0000000..39d7425 --- /dev/null +++ b/lib/pages/edit_recipe_sub_step_page.dart @@ -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 { + + bool nameValidation = false; + bool descriptionValidation = false; + + @override + Widget build(BuildContext context){ + return PizzaPlannerScaffold( + title: Text("Edit: ${widget.subStep.name}"), + resizeToAvoidBottomInset: true, + body: Column( + children: [ + 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: [ + 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)), + ) + ) + ) + ], + ), + ], + ) + ); + } +} \ No newline at end of file diff --git a/todo.md b/todo.md index 33c1eb6..dddc40a 100644 --- a/todo.md +++ b/todo.md @@ -1,8 +1,7 @@ # TODO ## Feature -- add a page to create your own recipe - - longpress recipe to edit it? +- longpress recipe to edit it on recipes page? - add search to recipes page - add directory structure to recipes? - share to/export of yaml of recipes