diff --git a/lib/main.dart b/lib/main.dart index 18cf643..5eb4bce 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -7,6 +7,7 @@ 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_pizza_event_page.dart'; +import 'package:pizzaplanner/pages/add_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_page.dart'; @@ -16,6 +17,7 @@ import 'package:hive/hive.dart'; import 'package:hive_flutter/hive_flutter.dart'; import 'package:pizzaplanner/pages/recipe_page.dart'; import 'package:pizzaplanner/pages/recipe_step_instruction_page.dart'; +import 'package:pizzaplanner/pages/recipes_page.dart'; import 'package:pizzaplanner/recipes/neapolitan_cold.dart'; import 'package:pizzaplanner/util.dart'; import 'package:rxdart/subjects.dart'; @@ -163,6 +165,12 @@ class RouteGenerator { } return MaterialPageRoute(builder: (context) => RecipeStepInstructionPage(recipeStepInstructionArgument)); } + case "/recipes/view": { + return MaterialPageRoute(builder: (context) => RecipesPage()); + } + case "/recipes/add": { + return MaterialPageRoute(builder: (context) => AddRecipePage()); + } default: { return MaterialPageRoute(builder: (context) => PizzaEventsPage()); } diff --git a/lib/pages/add_recipe_page.dart b/lib/pages/add_recipe_page.dart new file mode 100644 index 0000000..c3429b8 --- /dev/null +++ b/lib/pages/add_recipe_page.dart @@ -0,0 +1,226 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_markdown/flutter_markdown.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:url_launcher/url_launcher.dart'; + +class AddRecipePage extends StatefulWidget { + final PizzaRecipe? pizzaRecipe; + + const AddRecipePage({this.pizzaRecipe}); + + @override + AddRecipePageState createState() => AddRecipePageState(); +} + +class AddRecipePageState extends State { + late PizzaRecipe pizzaRecipe; + + AddRecipePageState(){ + if (widget.pizzaRecipe == null){ + pizzaRecipe = PizzaRecipe( + "", + "", + [], + [], + ); + } else { + pizzaRecipe = widget.pizzaRecipe!; + } + } + + bool nameValidation = false; + + @override + Widget build(BuildContext context){ + return Scaffold( + appBar: AppBar( + title: const Text("Add Pizza Recipe"), + ), + resizeToAvoidBottomInset: false, + body: Container( + padding: const EdgeInsets.all(16), + child: ListView( + children: [ + TextField( + decoration: InputDecoration( + hintText: "Recipe Name", + errorText: nameValidation ? """Name can't be empty""" : null + ), + onChanged: (String newName) { + setState(() { + pizzaRecipe.name = newName; + }); + }, + ), + const Divider(), + TextField( + decoration: InputDecoration( + hintText: "Recipe Description", + errorText: nameValidation ? """Description can't be empty""" : null + ), + maxLines: 8, + onChanged: (String newDescription) { + setState(() { + pizzaRecipe.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(pizzaRecipe.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("Ingredients") + + ), + const Divider(), + Container( + color: Colors.blue, + child: TextButton( + onPressed: () { + setState(() { + pizzaRecipe.ingredients.add( + Ingredient( + "", + "", + 0.0 + ) + ); + }); + }, + child: const Text("Add Ingredient", style: TextStyle(color: Colors.white)), + ) + ), + const Divider(), + ] + pizzaRecipe.ingredients.map((ingredient) => buildIngredientRow(ingredient)).toList() + [ + + ], + ) + ) + ); + } + + Widget buildIngredientRow(Ingredient ingredient){ + return Row( + children: [ + Expanded( + flex: 4, + child: TextField( + decoration: const InputDecoration( + hintText: "Name", + ), + controller: TextEditingController(text: ingredient.name), + onChanged: (String newName) { + setState(() { + ingredient.name = newName; + }); + }, + ), + ), + Expanded( + flex: 2, + child: TextField( + decoration: const InputDecoration( + hintText: "Value", + ), + keyboardType: TextInputType.number, + controller: TextEditingController(text: ingredient.value.toString()), + onChanged: (String newValue) { + setState(() { + final newDouble = double.tryParse(newValue); + if (newDouble == null){ + return; + } + ingredient.value = newDouble; + }); + }, + ), + ), + Expanded( + flex: 2, + child: TextField( + decoration: const InputDecoration( + hintText: "Unit", + ), + controller: TextEditingController(text: ingredient.unit), + onChanged: (String newUnit) { + setState(() { + ingredient.unit = newUnit; + }); + }, + ), + ), + Expanded( + child: TextButton( + onPressed: () { + setState(() { + pizzaRecipe.ingredients.remove(ingredient); + }); + }, + child: const Text("X", style: TextStyle(color: Colors.red)), + ) + ) + ] + ); + } +} + +class PreviewMarkdownDescription extends StatelessWidget { + final String description; + + const PreviewMarkdownDescription(this.description); + + @override + Widget build(BuildContext context){ + return Dialog( + insetPadding: const EdgeInsets.all(10), + child: Container( + padding: const EdgeInsets.all(10), + child: Markdown( + data: description, + onTapLink: (text, url, title) { + launch(url!); + } + ) + ) + ); + } +} \ No newline at end of file diff --git a/lib/pages/nav_drawer.dart b/lib/pages/nav_drawer.dart new file mode 100644 index 0000000..5af1b06 --- /dev/null +++ b/lib/pages/nav_drawer.dart @@ -0,0 +1,35 @@ +import 'package:flutter/material.dart'; +import 'package:fluttericon/font_awesome5_icons.dart'; + +class NavDrawer extends StatelessWidget { + @override + Widget build(BuildContext context){ + return Drawer( + child: ListView( + padding: EdgeInsets.zero, + children: [ + const DrawerHeader( + child: Text( + "Pizza Planner", + style: TextStyle(color: Colors.black, fontSize: 30) + ), + /*decoration: BoxDecoration( + color: Colors.redAccent, + image: DecorationImage( + fit: BoxFit.fill, + image: AssetImage() + ) + ),*/ + ), + ListTile( + leading: const Icon(FontAwesome5.pizza_slice), + title: const Text("Recipes"), + onTap: () => { + Navigator.pushNamed(context, "/recipes/view") + }, + ) + ] + ) + ); + } +} \ No newline at end of file diff --git a/lib/pages/pick_pizza_recipe_page.dart b/lib/pages/pick_pizza_recipe_page.dart index 7f07d47..7db7f73 100644 --- a/lib/pages/pick_pizza_recipe_page.dart +++ b/lib/pages/pick_pizza_recipe_page.dart @@ -1,12 +1,14 @@ import 'package:flutter/material.dart'; import 'package:hive_flutter/adapters.dart'; import 'package:pizzaplanner/entities/PizzaRecipe/pizza_recipe.dart'; +import 'package:pizzaplanner/pages/nav_drawer.dart'; import 'package:pizzaplanner/widgets/pizza_recipe_widget.dart'; class PickPizzaRecipePage extends StatelessWidget { @override Widget build(BuildContext context){ return Scaffold( + drawer: NavDrawer(), appBar: AppBar( title: const Text("Pick Pizza Recipe"), ), diff --git a/lib/pages/recipes_page.dart b/lib/pages/recipes_page.dart new file mode 100644 index 0000000..fe999ec --- /dev/null +++ b/lib/pages/recipes_page.dart @@ -0,0 +1,64 @@ +import 'package:flutter/material.dart'; +import 'package:hive_flutter/hive_flutter.dart'; +import 'package:pizzaplanner/entities/PizzaRecipe/pizza_recipe.dart'; +import 'package:pizzaplanner/pages/nav_drawer.dart'; +import 'package:pizzaplanner/widgets/pizza_recipe_widget.dart'; + +class RecipesPage extends StatelessWidget { + @override + Widget build(BuildContext context){ + return Scaffold( + drawer: NavDrawer(), + appBar: AppBar( + title: const Text("Recipes"), + ), + resizeToAvoidBottomInset: false, + body: Container( + padding: const EdgeInsets.all(16), + child: Column( + children: [ + const Expanded( + flex: 5, + child: Text("Search here maybe") + ), + Container( + color: Colors.blue, + width: double.infinity, + child: TextButton( + onPressed: () async { + Navigator.pushNamed(context, "/recipes/add"); + }, + child: const Text("New Recipe", style: TextStyle(color: Colors.white)), + ) + ), + const Divider(), + Expanded( + flex: 50, + child: ValueListenableBuilder( + valueListenable: Hive.box("PizzaRecipes").listenable(), + builder: (context, Box pizzaRecipesBox, widget) { + return ListView.separated( + itemCount: pizzaRecipesBox.length, + itemBuilder: (context, i) { + final pizzaRecipe = pizzaRecipesBox.get(i); + if (pizzaRecipe == null){ + return const SizedBox(); + } + return InkWell( + onTap: () { + Navigator.pushNamed(context, "/recipe/view", arguments: pizzaRecipe); + }, + child: PizzaRecipeWidget(pizzaRecipe), + ); + }, + separatorBuilder: (BuildContext context, int i) => const Divider(), + ); + } + ), + ) + ] + ) + ) + ); + } +} \ No newline at end of file