From e4fe16ac063265556e5cad53fe535bab3f315bbe Mon Sep 17 00:00:00 2001 From: SomnusVeritas Date: Mon, 13 Nov 2023 19:45:23 +0100 Subject: [PATCH] reimplemented how create recipe page works --- lib/main.dart | 2 +- .../create_recipe/create_recipe_page.dart | 189 ------------------ lib/pages/create_recipe_page.dart | 87 ++++++++ lib/pages/dashboard_page.dart | 2 +- lib/services/providers/recipe_provider.dart | 53 ----- .../create_recipe_page/add_info_widget.dart | 70 +++++++ .../add_ingredients_widget.dart | 124 ++++++++++++ .../create_recipe_page/add_steps_widget.dart | 10 + pubspec.yaml | 2 +- 9 files changed, 294 insertions(+), 245 deletions(-) delete mode 100644 lib/pages/create_recipe/create_recipe_page.dart create mode 100644 lib/pages/create_recipe_page.dart create mode 100644 lib/widgets/create_recipe_page/add_info_widget.dart create mode 100644 lib/widgets/create_recipe_page/add_ingredients_widget.dart create mode 100644 lib/widgets/create_recipe_page/add_steps_widget.dart diff --git a/lib/main.dart b/lib/main.dart index 3cfd574..d625ea2 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; -import 'package:rezepte/pages/create_recipe/create_recipe_page.dart'; +import 'package:rezepte/pages/create_recipe_page.dart'; import 'package:rezepte/pages/dashboard_page.dart'; import 'package:rezepte/services/providers/recipe_list_provider.dart'; import 'package:rezepte/services/providers/recipe_provider.dart'; diff --git a/lib/pages/create_recipe/create_recipe_page.dart b/lib/pages/create_recipe/create_recipe_page.dart deleted file mode 100644 index 9063e39..0000000 --- a/lib/pages/create_recipe/create_recipe_page.dart +++ /dev/null @@ -1,189 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:provider/provider.dart'; -import 'package:rezepte/services/providers/db/dbhelper.dart'; -import 'package:rezepte/widgets/ingredients_bottomsheet.dart'; -import 'package:rezepte/widgets/will_pop_scope.dart'; -import '../../models/difficulty.dart'; -import '../../models/ingredient_list_entry.dart'; -import '../../models/recipe.dart'; -import '../../services/providers/recipe_list_provider.dart'; -import '../../services/providers/recipe_provider.dart'; - -class CreateRecipe extends StatefulWidget { - const CreateRecipe({super.key}); - static const routeName = '/createRecipe'; - - @override - State createState() => _CreateRecipeState(); -} - -class _CreateRecipeState extends State { - late RecipeListProvider recipeListProvider; - late RecipeProvider recipeProvider; - late Recipe recipe; - - @override - void initState() { - super.initState(); - } - - @override - void dispose() { - recipeProvider.disposeRecipe(); - super.dispose(); - } - - @override - Widget build(BuildContext context) { - recipeProvider = Provider.of(context, listen: true); - recipeListProvider = - Provider.of(context, listen: false); - - if (recipeProvider.recipe == null) { - recipe = Recipe(id: DbHelper.nextRecipeId, title: ''); - WidgetsBinding.instance.addPostFrameCallback((timeStamp) { - recipeProvider.recipe = recipe; - }); - } - - return CustomWillPopScope( - context, - ignore: recipe.isEmpty, - child: Scaffold( - appBar: AppBar( - title: const Text('Create Recipe'), - ), - floatingActionButton: recipe.isNotEmpty - ? FloatingActionButton( - onPressed: _onRecipeSubmitted, - child: const Icon(Icons.save), - ) - : null, - body: Form( - child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 20), - child: Column( - children: [ - TextFormField( - onTapOutside: (event) => FocusScope.of(context).unfocus(), - onChanged: (value) => recipe.title = value, - decoration: const InputDecoration( - label: Text('Title'), - ), - style: TextStyle( - color: Theme.of(context).colorScheme.onBackground), - ), - TextFormField( - onTapOutside: (event) => FocusScope.of(context).unfocus(), - minLines: 1, - maxLines: 4, - onChanged: (value) => recipe.description = value, - decoration: const InputDecoration( - label: Text('Description'), - ), - style: TextStyle( - color: Theme.of(context).colorScheme.onBackground), - ), - DropdownMenu( - dropdownMenuEntries: DifficultyUtil.getDropdownList(), - onSelected: (value) => - recipe.difficulty = value ?? Difficulty.notSelected, - label: const Text('Difficulty'), - textStyle: TextStyle( - color: Theme.of(context).colorScheme.onBackground), - ), - ElevatedButton( - onPressed: _openIngredientBottomSheet, - child: const Text('Add Ingredient'), - ), - Expanded( - child: ListView.separated( - itemCount: recipe.ingredients.length, - itemBuilder: _ingredientListBuilder, - separatorBuilder: (context, index) => const Divider(), - ), - ) - ], - ), - ), - ), - ), - ); - } - - void _openIngredientBottomSheet() { - showModalBottomSheet( - context: context, - builder: (context) => IngredientsBottomsheet( - onSubmitted: _onIngredientSubmitted, - ), - ); - } - - void _onIngredientSubmitted(IngredientListEntry ingredient) => setState(() { - recipe.ingredients.add(ingredient); - }); - - void _onIngredientRemoveTapped(int index) { - final removedIngredient = recipe.ingredients.elementAt(index); - - recipe.removeIngredientAt(index); - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: const Text('Ingredient Removed'), - action: SnackBarAction( - label: 'Undo', - onPressed: () { - recipe.addIngredient(removedIngredient); - }), - ), - ); - } - - Widget? _ingredientListBuilder(BuildContext context, int index) { - final ingredient = recipe.ingredients.elementAt(index); - - return ListTile( - contentPadding: EdgeInsets.zero, - title: Text(ingredient.ingredient.title), - subtitle: ingredient.optional ? const Text('optional') : null, - trailing: Row( - mainAxisSize: MainAxisSize.min, - children: [ - Text( - ingredient.amount.toString(), - style: Theme.of(context) - .textTheme - .bodyLarge! - .copyWith(color: Theme.of(context).colorScheme.onBackground), - ), - const Padding(padding: EdgeInsets.symmetric(horizontal: 2)), - Text( - ingredient.unit.name, - style: Theme.of(context) - .textTheme - .bodyLarge! - .copyWith(color: Theme.of(context).colorScheme.onBackground), - ), - const Padding(padding: EdgeInsets.symmetric(horizontal: 5)), - SizedBox( - width: 30, - height: 30, - child: IconButton( - padding: EdgeInsets.zero, - onPressed: () => _onIngredientRemoveTapped(index), - icon: const Icon(Icons.delete), - ), - ), - ], - ), - ); - } - - void _onRecipeSubmitted() { - // TODO implement onRecipeSubmitted - if (recipe.isEmpty) return; - recipeListProvider.addRecipe(recipe); - Navigator.of(context).pop(); - } -} diff --git a/lib/pages/create_recipe_page.dart b/lib/pages/create_recipe_page.dart new file mode 100644 index 0000000..ee2d0f5 --- /dev/null +++ b/lib/pages/create_recipe_page.dart @@ -0,0 +1,87 @@ +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; +import 'package:rezepte/services/providers/db/dbhelper.dart'; +import 'package:rezepte/widgets/will_pop_scope.dart'; +import '../models/recipe.dart'; +import '../services/providers/recipe_list_provider.dart'; +import '../services/providers/recipe_provider.dart'; +import '../widgets/create_recipe_page/add_info_widget.dart'; +import '../widgets/create_recipe_page/add_ingredients_widget.dart'; +import '../widgets/create_recipe_page/add_steps_widget.dart'; + +class CreateRecipe extends StatefulWidget { + const CreateRecipe({super.key}); + static const routeName = '/createRecipe'; + + @override + State createState() => _CreateRecipeState(); +} + +class _CreateRecipeState extends State { + late RecipeListProvider recipeListProvider; + late RecipeProvider recipeProvider; + late Recipe recipe; + final List pages = [ + const AddRecipeInfoWidget(), + const AddIngredientsWidget(), + const AddCookingStepsWidget(), + ]; + int selectedTabIndex = 0; + + @override + void dispose() { + recipeProvider.disposeRecipe(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + recipeProvider = Provider.of(context, listen: true); + recipeListProvider = + Provider.of(context, listen: false); + + if (recipeProvider.recipe == null) { + recipe = Recipe(id: DbHelper.nextRecipeId, title: ''); + WidgetsBinding.instance.addPostFrameCallback((timeStamp) { + recipeProvider.recipe = recipe; + }); + } + + return CustomWillPopScope( + context, + ignore: recipe.isEmpty, + child: Scaffold( + appBar: AppBar( + title: const Text('Create Recipe'), + ), + bottomNavigationBar: BottomNavigationBar( + currentIndex: selectedTabIndex, + showSelectedLabels: false, + items: const [ + BottomNavigationBarItem(icon: Icon(Icons.add_task), label: ''), + BottomNavigationBarItem(icon: Icon(Icons.add_task), label: ''), + BottomNavigationBarItem(icon: Icon(Icons.add_task), label: ''), + ], + onTap: (value) => setState(() => selectedTabIndex = value), + ), + body: Padding( + padding: const EdgeInsets.symmetric(horizontal: 20), + child: pages.elementAt(selectedTabIndex), + ), + // floatingActionButton: recipe.isNotEmpty + // ? FloatingActionButton( + // onPressed: _onRecipeSubmitted, + // child: const Icon(Icons.save), + // ) + // : null, + ), + ); + } + + void _onRecipeSubmitted() { + // TODO implement onRecipeSubmitted + if (recipe.isEmpty) return; + recipeListProvider.addRecipe(recipe); + Navigator.of(context).pop(); + } +} diff --git a/lib/pages/dashboard_page.dart b/lib/pages/dashboard_page.dart index febfe0c..5348354 100644 --- a/lib/pages/dashboard_page.dart +++ b/lib/pages/dashboard_page.dart @@ -1,5 +1,5 @@ import 'package:flutter/material.dart'; -import 'package:rezepte/pages/create_recipe/create_recipe_page.dart'; +import 'package:rezepte/pages/create_recipe_page.dart'; import 'package:rezepte/widgets/recipe_list.dart'; class Dashboard extends StatelessWidget { diff --git a/lib/services/providers/recipe_provider.dart b/lib/services/providers/recipe_provider.dart index 108dd24..6e41601 100644 --- a/lib/services/providers/recipe_provider.dart +++ b/lib/services/providers/recipe_provider.dart @@ -15,57 +15,4 @@ class RecipeProvider extends ChangeNotifier { void disposeRecipe() { _recipe = null; } - - // set description(String description) { - // _description = description; - // notifyListeners(); - // } - - // String get title => _title; - - // set title(String title) { - // _title = title; - // notifyListeners(); - // } - - // Difficulty? get difficulty => _difficulty; - - // set difficulty(Difficulty? difficulty) { - // _difficulty = difficulty; - // notifyListeners(); - // } - - // List get ingredients => _ingredients; - - // void addIngredient(IngredientListEntry ingredient) { - // _ingredients.add(ingredient); - // notifyListeners(); - // } - - // void clearIngredients({silent = false}) { - // ingredients.clear(); - // if (!silent) notifyListeners(); - // } - - // void removeIngredientAt(int index, {silent = false}) { - // ingredients.removeAt(index); - // if (!silent) notifyListeners(); - // } - - // void removeIngredient(IngredientListEntry ingredient, {silent = false}) { - // ingredients.removeWhere((element) => element == ingredient); - // if (!silent) notifyListeners(); - // } - - // List get steps => _steps; - - // void addStep(CookingStep step, {silent = false}) { - // steps.add(step); - // if (!silent) notifyListeners(); - // } - - // void removeStepAt(int index, {silent = false}) { - // steps.removeAt(index); - // if (!silent) notifyListeners(); - // } } diff --git a/lib/widgets/create_recipe_page/add_info_widget.dart b/lib/widgets/create_recipe_page/add_info_widget.dart new file mode 100644 index 0000000..bebca1f --- /dev/null +++ b/lib/widgets/create_recipe_page/add_info_widget.dart @@ -0,0 +1,70 @@ +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; + +import '../../models/difficulty.dart'; +import '../../models/recipe.dart'; +import '../../services/providers/recipe_provider.dart'; + +class AddRecipeInfoWidget extends StatefulWidget { + const AddRecipeInfoWidget({super.key}); + + @override + State createState() => _AddRecipeInfoWidgetState(); +} + +class _AddRecipeInfoWidgetState extends State { + late Recipe? recipe; + + @override + Widget build(BuildContext context) { + recipe = Provider.of(context, listen: true).recipe; + + if (recipe == null) { + return const Center( + child: CircularProgressIndicator(), + ); + } + + return Column( + children: [ + TextFormField( + onTapOutside: (event) => FocusScope.of(context).unfocus(), + onChanged: (value) => recipe!.title = value, + decoration: const InputDecoration( + label: Text('Title'), + ), + style: TextStyle(color: Theme.of(context).colorScheme.onBackground), + ), + TextFormField( + onTapOutside: (event) => FocusScope.of(context).unfocus(), + minLines: 1, + maxLines: 4, + onChanged: (value) => recipe!.description = value, + decoration: const InputDecoration( + label: Text('Description'), + ), + style: TextStyle(color: Theme.of(context).colorScheme.onBackground), + ), + DropdownMenu( + dropdownMenuEntries: DifficultyUtil.getDropdownList(), + onSelected: (value) => + recipe!.difficulty = value ?? Difficulty.notSelected, + label: const Text('Difficulty'), + textStyle: + TextStyle(color: Theme.of(context).colorScheme.onBackground), + ), + // ElevatedButton( + // onPressed: _openIngredientBottomSheet, + // child: const Text('Add Ingredient'), + // ), + // Expanded( + // child: ListView.separated( + // itemCount: recipe.ingredients.length, + // itemBuilder: _ingredientListBuilder, + // separatorBuilder: (context, index) => const Divider(), + // ), + // ) + ], + ); + } +} diff --git a/lib/widgets/create_recipe_page/add_ingredients_widget.dart b/lib/widgets/create_recipe_page/add_ingredients_widget.dart new file mode 100644 index 0000000..6c3830c --- /dev/null +++ b/lib/widgets/create_recipe_page/add_ingredients_widget.dart @@ -0,0 +1,124 @@ +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; + +import '../../models/ingredient_list_entry.dart'; +import '../../models/recipe.dart'; +import '../../services/providers/recipe_provider.dart'; +import '../../widgets/ingredients_bottomsheet.dart'; + +class AddIngredientsWidget extends StatefulWidget { + const AddIngredientsWidget({super.key}); + + @override + State createState() => _AddIngredientsWidgetState(); +} + +class _AddIngredientsWidgetState extends State { + late RecipeProvider recipeProvider; + + void _onIngredientRemoveTapped(int index) { + final removedIngredient = + recipeProvider.recipe!.ingredients.elementAt(index); + + setState(() { + recipeProvider.recipe!.removeIngredientAt(index); + }); + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: const Text('Ingredient Removed'), + action: SnackBarAction( + label: 'Undo', + onPressed: () { + setState(() { + recipeProvider.recipe!.addIngredient(removedIngredient); + }); + }, + ), + ), + ); + } + + Widget? _ingredientListBuilder(BuildContext context, int index) { + final ingredient = recipeProvider.recipe!.ingredients.elementAt(index); + + return ListTile( + contentPadding: EdgeInsets.zero, + title: Text(ingredient.ingredient.title), + subtitle: ingredient.optional ? const Text('optional') : null, + trailing: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Text( + ingredient.amount.toString(), + style: Theme.of(context) + .textTheme + .bodyLarge! + .copyWith(color: Theme.of(context).colorScheme.onBackground), + ), + const Padding(padding: EdgeInsets.symmetric(horizontal: 2)), + Text( + ingredient.unit.name, + style: Theme.of(context) + .textTheme + .bodyLarge! + .copyWith(color: Theme.of(context).colorScheme.onBackground), + ), + const Padding(padding: EdgeInsets.symmetric(horizontal: 5)), + SizedBox( + width: 30, + height: 30, + child: IconButton( + padding: EdgeInsets.zero, + onPressed: () => _onIngredientRemoveTapped(index), + icon: const Icon(Icons.delete), + ), + ), + ], + ), + ); + } + + void _openIngredientBottomSheet() { + showModalBottomSheet( + context: context, + builder: (context) => IngredientsBottomsheet( + onSubmitted: _onIngredientSubmitted, + ), + ); + } + + void _onIngredientSubmitted(IngredientListEntry ingredient) => setState(() { + recipeProvider.recipe!.ingredients.add(ingredient); + }); + + @override + Widget build(BuildContext context) { + recipeProvider = Provider.of(context, listen: true); + if (recipeProvider.recipe == null) { + return const Center( + child: CircularProgressIndicator(), + ); + } + + return Stack( + children: [ + ListView.separated( + padding: const EdgeInsets.only(bottom: 88), + itemCount: recipeProvider.recipe!.ingredients.length, + itemBuilder: _ingredientListBuilder, + separatorBuilder: (context, index) => const Divider(), + ), + Align( + alignment: Alignment.bottomRight, + child: Padding( + padding: const EdgeInsets.only(bottom: 16, right: 16), + child: FloatingActionButton( + onPressed: _openIngredientBottomSheet, + child: const Icon(Icons.add), + ), + ), + ), + ], + ); + } +} diff --git a/lib/widgets/create_recipe_page/add_steps_widget.dart b/lib/widgets/create_recipe_page/add_steps_widget.dart new file mode 100644 index 0000000..03ac1c2 --- /dev/null +++ b/lib/widgets/create_recipe_page/add_steps_widget.dart @@ -0,0 +1,10 @@ +import 'package:flutter/material.dart'; + +class AddCookingStepsWidget extends StatelessWidget { + const AddCookingStepsWidget({super.key}); + + @override + Widget build(BuildContext context) { + return const Placeholder(); + } +} diff --git a/pubspec.yaml b/pubspec.yaml index 4deb5ab..146a75f 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -19,7 +19,7 @@ dev_dependencies: flutter_test: sdk: flutter - flutter_lints: ^2.0.0 + flutter_lints: ^3.0.1 build_runner: ^2.4.6 flutter: