moved to a wrapped scaffold, to get some consistency and one place to add the nav drawer

This commit is contained in:
broodjeaap89 2021-09-05 13:35:56 +02:00
parent 44cedd1a29
commit af0be6fe8b
10 changed files with 577 additions and 580 deletions

View file

@ -7,6 +7,7 @@ import 'package:fluttericon/font_awesome5_icons.dart';
import 'package:pizzaplanner/entities/pizza_event.dart'; import 'package:pizzaplanner/entities/pizza_event.dart';
import 'package:pizzaplanner/entities/PizzaRecipe/pizza_recipe.dart'; import 'package:pizzaplanner/entities/PizzaRecipe/pizza_recipe.dart';
import 'package:pizzaplanner/main.dart'; import 'package:pizzaplanner/main.dart';
import 'package:pizzaplanner/pages/scaffold.dart';
import 'package:pizzaplanner/util.dart'; import 'package:pizzaplanner/util.dart';
import 'package:hive/hive.dart'; import 'package:hive/hive.dart';
@ -31,178 +32,172 @@ class AddPizzaEventPageState extends State<AddPizzaEventPage> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return PizzaPlannerScaffold(
appBar: AppBar( title: const Text("Add Pizza Event"),
title: const Text("Add Pizza Event"), body: Column(
), children: <Widget>[
resizeToAvoidBottomInset: false, Expanded(
body: Container( flex: 40,
padding: const EdgeInsets.all(16), child: Column(
child: Column( children: <Widget>[
children: <Widget>[ Row(
Expanded( children: <Widget>[
flex: 40, const Icon(Icons.title),
child: Column( Container(width: 25),
children: <Widget>[ Expanded(
Row( child: TextField(
children: <Widget>[ decoration: InputDecoration(
const Icon(Icons.title), hintText: "Event Name",
Container(width: 25), errorText: nameValidation ? """Name can't be empty""" : null
Expanded( ),
child: TextField( onChanged: (String newName) {
decoration: InputDecoration( setState(() {
hintText: "Event Name", name = newName;
errorText: nameValidation ? """Name can't be empty""" : null });
), },
onChanged: (String newName) { ),
setState(() { )
name = newName; ]
}); ),
}, Row(
), children: <Widget>[
) const Icon(FontAwesome5.hashtag),
] Expanded(
), child: Slider(
Row(
children: <Widget>[
const Icon(FontAwesome5.hashtag),
Expanded(
child: Slider(
value: pizzaCount.toDouble(), value: pizzaCount.toDouble(),
min: 1, min: 1,
max: 20, max: 20,
divisions: 19, divisions: 19,
label: pizzaCount.toString(), label: pizzaCount.toString(),
onChanged: (double newPizzaCount) { onChanged: (double newPizzaCount) {
setState(() {pizzaCount = newPizzaCount.round();}); setState(() {pizzaCount = newPizzaCount.round();});
}, },
)
),
SizedBox(
width: 25,
child: Text(pizzaCount.toString())
) )
] ),
), SizedBox(
Row( width: 25,
children: <Widget>[ child: Text(pizzaCount.toString())
const Icon(FontAwesome5.weight_hanging), )
Expanded( ]
child: Slider( ),
value: doughBallSize.toDouble(), Row(
min: 100, children: <Widget>[
max: 400, const Icon(FontAwesome5.weight_hanging),
divisions: 30, Expanded(
label: doughBallSize.toString(), child: Slider(
onChanged: (double newDoughBallSize) { value: doughBallSize.toDouble(),
setState(() {doughBallSize = newDoughBallSize.round();}); min: 100,
}, max: 400,
) divisions: 30,
), label: doughBallSize.toString(),
SizedBox( onChanged: (double newDoughBallSize) {
width: 25, setState(() {doughBallSize = newDoughBallSize.round();});
child: Text(doughBallSize.toString()) },
) )
] ),
), SizedBox(
widget.pizzaRecipe.getIngredientsTable(pizzaCount, doughBallSize), width: 25,
] child: Text(doughBallSize.toString())
) )
), ]
const Divider(), ),
Expanded( widget.pizzaRecipe.getIngredientsTable(pizzaCount, doughBallSize),
flex: 45, ]
child: ListView( )
children: <Widget>[ ),
Column( const Divider(),
children: widget.pizzaRecipe.recipeSteps.where((recipeStep) => recipeStep.waitDescription.isNotEmpty).map((recipeStep) { Expanded(
return <Widget>[ flex: 45,
Text(recipeStep.waitDescription), child: ListView(
Row( children: <Widget>[
children: <Widget>[ Column(
Expanded( children: widget.pizzaRecipe.recipeSteps.where((recipeStep) => recipeStep.waitDescription.isNotEmpty).map((recipeStep) {
child: Slider( return <Widget>[
value: recipeStep.waitValue!.toDouble(), Text(recipeStep.waitDescription),
min: recipeStep.waitMin.toDouble(), Row(
max: recipeStep.waitMax.toDouble(), children: <Widget>[
divisions: recipeStep.waitMax - recipeStep.waitMin, Expanded(
label: recipeStep.waitValue.toString(), child: Slider(
onChanged: (newValue) => setState(() => recipeStep.waitValue = newValue.toInt()), value: recipeStep.waitValue!.toDouble(),
) min: recipeStep.waitMin.toDouble(),
), max: recipeStep.waitMax.toDouble(),
SizedBox( divisions: recipeStep.waitMax - recipeStep.waitMin,
width: 25, label: recipeStep.waitValue.toString(),
child: Text(recipeStep.waitValue.toString()) onChanged: (newValue) => setState(() => recipeStep.waitValue = newValue.toInt()),
) )
] ),
) SizedBox(
]; width: 25,
}).expand((option) => option).toList() child: Text(recipeStep.waitValue.toString())
) )
] ]
) )
), ];
const Divider(), }).expand((option) => option).toList()
const Spacer(), )
SizedBox( ]
width: double.infinity, )
height: 70, ),
child: Container( const Divider(),
color: Colors.blue, const Spacer(),
child: TextButton( SizedBox(
onPressed: () async { width: double.infinity,
height: 70,
child: Container(
color: Colors.blue,
child: TextButton(
onPressed: () async {
if (name.isEmpty){ if (name.isEmpty){
setState(() { nameValidation = true; }); setState(() { nameValidation = true; });
return; return;
} }
setState(() { nameValidation = false; }); setState(() { nameValidation = false; });
FocusScope.of(context).unfocus(); FocusScope.of(context).unfocus();
DateTime? eventTime = await showDialog( DateTime? eventTime = await showDialog(
context: context, context: context,
builder: (context) { builder: (context) {
return ConfirmPizzaEventDialog(name: name, pizzaRecipe: widget.pizzaRecipe, pizzaCount: pizzaCount, doughBallSize: doughBallSize); return ConfirmPizzaEventDialog(name: name, pizzaRecipe: widget.pizzaRecipe, pizzaCount: pizzaCount, doughBallSize: doughBallSize);
}
);
if (eventTime == null){
return;
} }
);
if (eventTime == null){
return;
}
// if the user waited to long on the confirmation dialog that the first step time is now in the past // if the user waited to long on the confirmation dialog that the first step time is now in the past
final durationUntilFirstStep = Duration(seconds: widget.pizzaRecipe.getCurrentDuration().inSeconds); final durationUntilFirstStep = Duration(seconds: widget.pizzaRecipe.getCurrentDuration().inSeconds);
final firstStepDateTime = eventTime.subtract(durationUntilFirstStep); final firstStepDateTime = eventTime.subtract(durationUntilFirstStep);
if (firstStepDateTime.isBefore(DateTime.now())){ if (firstStepDateTime.isBefore(DateTime.now())){
eventTime = DateTime.now() eventTime = DateTime.now()
.add(durationUntilFirstStep) .add(durationUntilFirstStep)
.add(const Duration(minutes: 1)); .add(const Duration(minutes: 1));
} }
final pizzaEventsBox = Hive.box<PizzaEvent>("PizzaEvents"); final pizzaEventsBox = Hive.box<PizzaEvent>("PizzaEvents");
final PizzaEvent pizzaEvent = PizzaEvent( final PizzaEvent pizzaEvent = PizzaEvent(
name, name,
widget.pizzaRecipe, widget.pizzaRecipe,
pizzaCount, pizzaCount,
doughBallSize, doughBallSize,
eventTime eventTime
); );
await pizzaEventsBox.add(pizzaEvent); await pizzaEventsBox.add(pizzaEvent);
pizzaEvent.createPizzaEventNotifications(); pizzaEvent.createPizzaEventNotifications();
if(!mounted) { if(!mounted) {
return; //https://dart-lang.github.io/linter/lints/use_build_context_synchronously.html return; //https://dart-lang.github.io/linter/lints/use_build_context_synchronously.html
} }
Navigator.of(context).pop(); Navigator.of(context).pop();
Navigator.of(context).pop(); // two times because of the pick recipe page Navigator.of(context).pop(); // two times because of the pick recipe page
}, },
child: const Text("Review", style: TextStyle(color: Colors.white)), child: const Text("Review", style: TextStyle(color: Colors.white)),
)
) )
) )
] )
) ]
) ),
); );
} }
} }

View file

@ -3,6 +3,7 @@ import 'package:flutter_markdown/flutter_markdown.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/pages/scaffold.dart';
import 'package:url_launcher/url_launcher.dart'; import 'package:url_launcher/url_launcher.dart';
class AddRecipePage extends StatefulWidget { class AddRecipePage extends StatefulWidget {
@ -34,107 +35,101 @@ class AddRecipePageState extends State<AddRecipePage> {
@override @override
Widget build(BuildContext context){ Widget build(BuildContext context){
return Scaffold( return PizzaPlannerScaffold(
appBar: AppBar( title: const Text("Add Recipe"),
title: const Text("Add Pizza Recipe"), body: ListView(
), children: <Widget>[
resizeToAvoidBottomInset: false, TextField(
body: Container( decoration: InputDecoration(
padding: const EdgeInsets.all(16), hintText: "Recipe Name",
child: ListView( errorText: nameValidation ? """Name can't be empty""" : null
children: <Widget>[
TextField(
decoration: InputDecoration(
hintText: "Recipe Name",
errorText: nameValidation ? """Name can't be empty""" : null
),
onChanged: (String newName) {
setState(() {
pizzaRecipe.name = newName;
});
},
), ),
const Divider(), onChanged: (String newName) {
TextField( setState(() {
decoration: InputDecoration( pizzaRecipe.name = newName;
hintText: "Recipe Description", });
errorText: nameValidation ? """Description can't be empty""" : null },
), ),
maxLines: 8, const Divider(),
onChanged: (String newDescription) { TextField(
setState(() { decoration: InputDecoration(
pizzaRecipe.description = newDescription; hintText: "Recipe Description",
}); errorText: nameValidation ? """Description can't be empty""" : null
},
), ),
const Divider(), maxLines: 8,
Row( onChanged: (String newDescription) {
children: <Widget>[ setState(() {
Expanded( pizzaRecipe.description = newDescription;
flex: 2, });
child: Container( },
color: Colors.blue, ),
width: double.infinity, const Divider(),
child: TextButton( Row(
onPressed: () { children: <Widget>[
showDialog( Expanded(
context: context, flex: 2,
builder: (context) { child: Container(
return PreviewMarkdownDescription(pizzaRecipe.description); color: Colors.blue,
} width: double.infinity,
); child: TextButton(
}, onPressed: () {
child: const Text("Preview", style: TextStyle(color: Colors.white)), showDialog(
) context: context,
) builder: (context) {
), return PreviewMarkdownDescription(pizzaRecipe.description);
const Expanded( }
);
},
child: const Text("Preview", style: TextStyle(color: Colors.white)),
)
)
),
const Expanded(
child: SizedBox() child: SizedBox()
), ),
Expanded( Expanded(
flex: 2, flex: 2,
child: Container( child: Container(
color: Colors.blue, color: Colors.blue,
width: double.infinity, width: double.infinity,
child: TextButton( child: TextButton(
onPressed: () { onPressed: () {
launch("https://guides.github.com/features/mastering-markdown/"); launch("https://guides.github.com/features/mastering-markdown/");
}, },
child: const Text("Markdown?", style: TextStyle(color: Colors.white)), child: const Text("Markdown?", style: TextStyle(color: Colors.white)),
) )
) )
) )
], ],
), ),
const Divider(), const Divider(),
const Center( const Center(
child: Text("Ingredients") child: Text("Ingredients")
), ),
const Divider(), const Divider(),
Container( Container(
color: Colors.blue, color: Colors.blue,
child: TextButton( child: TextButton(
onPressed: () { onPressed: () {
setState(() { setState(() {
pizzaRecipe.ingredients.add( pizzaRecipe.ingredients.add(
Ingredient( Ingredient(
"", "",
"", "",
0.0 0.0
) )
); );
}); });
}, },
child: const Text("Add Ingredient", style: TextStyle(color: Colors.white)), child: const Text("Add Ingredient", style: TextStyle(color: Colors.white)),
) )
), ),
const Divider(), const Divider(),
] + pizzaRecipe.ingredients.map((ingredient) => buildIngredientRow(ingredient)).toList() + [ ] + pizzaRecipe.ingredients.map((ingredient) => buildIngredientRow(ingredient)).toList() + [
], ],
) ),
)
); );
} }

View file

@ -2,42 +2,36 @@ import 'package:flutter/material.dart';
import 'package:hive_flutter/adapters.dart'; import 'package:hive_flutter/adapters.dart';
import 'package:pizzaplanner/entities/PizzaRecipe/pizza_recipe.dart'; import 'package:pizzaplanner/entities/PizzaRecipe/pizza_recipe.dart';
import 'package:pizzaplanner/pages/nav_drawer.dart'; import 'package:pizzaplanner/pages/nav_drawer.dart';
import 'package:pizzaplanner/pages/scaffold.dart';
import 'package:pizzaplanner/widgets/pizza_recipe_widget.dart'; import 'package:pizzaplanner/widgets/pizza_recipe_widget.dart';
class PickPizzaRecipePage extends StatelessWidget { class PickPizzaRecipePage extends StatelessWidget {
@override @override
Widget build(BuildContext context){ Widget build(BuildContext context){
return Scaffold( return PizzaPlannerScaffold(
drawer: NavDrawer(), title: const Text("Pick Recipe"),
appBar: AppBar( body: ValueListenableBuilder(
title: const Text("Pick Pizza Recipe"), valueListenable: Hive.box<PizzaRecipe>("PizzaRecipes").listenable(),
), builder: (context, Box<PizzaRecipe> pizzaRecipesBox, widget) {
resizeToAvoidBottomInset: false, return ListView.separated(
body: Container( padding: const EdgeInsets.all(8),
padding: const EdgeInsets.all(16), itemCount: pizzaRecipesBox.length,
child: ValueListenableBuilder( itemBuilder: (context, i) {
valueListenable: Hive.box<PizzaRecipe>("PizzaRecipes").listenable(), final pizzaRecipe = pizzaRecipesBox.get(i);
builder: (context, Box<PizzaRecipe> pizzaRecipesBox, widget) { if (pizzaRecipe == null){
return ListView.separated( return const SizedBox();
padding: const EdgeInsets.all(8), }
itemCount: pizzaRecipesBox.length, return InkWell(
itemBuilder: (context, i) { onTap: () {
final pizzaRecipe = pizzaRecipesBox.get(i); Navigator.pushNamed(context, "/event/add", arguments: pizzaRecipe);
if (pizzaRecipe == null){ },
return const SizedBox(); child: PizzaRecipeWidget(pizzaRecipe),
} );
return InkWell( },
onTap: () { separatorBuilder: (BuildContext context, int i) => const Divider(),
Navigator.pushNamed(context, "/event/add", arguments: pizzaRecipe); );
}, }
child: PizzaRecipeWidget(pizzaRecipe), ),
);
},
separatorBuilder: (BuildContext context, int i) => const Divider(),
);
}
)
)
); );
} }
} }

View file

@ -9,6 +9,7 @@ import 'package:pizzaplanner/entities/pizza_event.dart';
import 'package:pizzaplanner/entities/PizzaRecipe/recipe_step.dart'; import 'package:pizzaplanner/entities/PizzaRecipe/recipe_step.dart';
import 'package:pizzaplanner/main.dart'; import 'package:pizzaplanner/main.dart';
import 'package:pizzaplanner/pages/recipe_step_instruction_page.dart'; import 'package:pizzaplanner/pages/recipe_step_instruction_page.dart';
import 'package:pizzaplanner/pages/scaffold.dart';
import 'package:timezone/timezone.dart' as tz; import 'package:timezone/timezone.dart' as tz;
import 'package:vibration/vibration.dart'; import 'package:vibration/vibration.dart';
@ -56,98 +57,92 @@ class PizzaEventNotificationState extends State<PizzaEventNotificationPage> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return PizzaPlannerScaffold(
appBar: AppBar( title: Text(recipeStep.name),
title: const Text("From notification"), body: Column(
), children: <Widget>[
resizeToAvoidBottomInset: false, Expanded(
body: Container( flex: 10,
padding: const EdgeInsets.all(16), child: Center(
child: Column( child: Text(pizzaEvent.name),
children: <Widget>[ ),
Expanded( ),
flex: 10, Expanded(
child: Center( flex: 10,
child: Text(pizzaEvent.name), child: Center(
), child: Text(recipeStep.name)
), ),
Expanded( ),
flex: 10, const Divider(),
child: Center( Expanded(
child: Text(recipeStep.name) flex: 10,
), child: Container(
), color: Colors.blue,
const Divider(), width: double.infinity,
Expanded( child: TextButton(
flex: 10, onPressed: () async {
child: Container( showDialog(context: context, builder: (BuildContext context) {
color: Colors.blue, return buildIgnoreDialog();
width: double.infinity, });
child: TextButton( },
onPressed: () async { child: const Text("Ignore", style: TextStyle(color: Colors.white)),
showDialog(context: context, builder: (BuildContext context) {
return buildIgnoreDialog();
});
},
child: const Text("Ignore", style: TextStyle(color: Colors.white)),
)
) )
), )
const Divider(), ),
Expanded( const Divider(),
flex: 30, Expanded(
child: Container( flex: 30,
color: Colors.blue, child: Container(
width: double.infinity, color: Colors.blue,
child: TextButton( width: double.infinity,
onPressed: () async { child: TextButton(
setRecipeStepNotification(DateTime.now().add(const Duration(minutes: 15))); onPressed: () async {
Navigator.pop(context); setRecipeStepNotification(DateTime.now().add(const Duration(minutes: 15)));
}, Navigator.pop(context);
onLongPress: () async { },
final future5Min = DateTime.now().add(const Duration(minutes: 5)); onLongPress: () async {
DatePicker.showDateTimePicker(context, final future5Min = DateTime.now().add(const Duration(minutes: 5));
minTime: future5Min, DatePicker.showDateTimePicker(context,
currentTime: future5Min, minTime: future5Min,
maxTime: DateTime.now().add(const Duration(days: 365*10)), currentTime: future5Min,
onConfirm: (newEventTime) { maxTime: DateTime.now().add(const Duration(days: 365*10)),
setState((){ onConfirm: (newEventTime) {
setRecipeStepNotification(newEventTime); setState((){
Navigator.pop(context); setRecipeStepNotification(newEventTime);
}); Navigator.pop(context);
} });
); }
);
}, },
child: const Text("Snooze 15 minutes", style: TextStyle(color: Colors.white)), child: const Text("Snooze 15 minutes", style: TextStyle(color: Colors.white)),
)
) )
), )
const Divider(), ),
Expanded( const Divider(),
flex: 40, Expanded(
child: Container( flex: 40,
color: Colors.blue, child: Container(
width: double.infinity, color: Colors.blue,
child: TextButton( width: double.infinity,
onPressed: () async { child: TextButton(
Navigator.pop(context); onPressed: () async {
Navigator.pushNamed( Navigator.pop(context);
context, Navigator.pushNamed(
"/event/recipe_step", context,
arguments: RecipeStepInstructionPageArguments( "/event/recipe_step",
pizzaEvent, arguments: RecipeStepInstructionPageArguments(
recipeStep pizzaEvent,
) recipeStep
); )
}, );
child: const Text("Start!", style: TextStyle(color: Colors.white)), },
) child: const Text("Start!", style: TextStyle(color: Colors.white)),
) )
), )
] ),
) ]
) ),
); );
} }

View file

@ -7,6 +7,7 @@ import 'package:pizzaplanner/entities/PizzaRecipe/ingredient.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/entities/PizzaRecipe/recipe_substep.dart';
import 'package:pizzaplanner/main.dart'; import 'package:pizzaplanner/main.dart';
import 'package:pizzaplanner/pages/scaffold.dart';
import 'package:pizzaplanner/util.dart'; import 'package:pizzaplanner/util.dart';
import 'package:pizzaplanner/widgets/pizza_recipe_widget.dart'; import 'package:pizzaplanner/widgets/pizza_recipe_widget.dart';
import 'package:url_launcher/url_launcher.dart'; import 'package:url_launcher/url_launcher.dart';
@ -26,35 +27,30 @@ class PizzaEventPageState extends State<PizzaEventPage> {
Widget build(BuildContext context) { Widget build(BuildContext context) {
final recipeStepCount = widget.pizzaEvent.recipe.recipeSteps.length; final recipeStepCount = widget.pizzaEvent.recipe.recipeSteps.length;
final completedRecipeStepCount = widget.pizzaEvent.recipe.recipeSteps.where((recipeStep) => recipeStep.completed).length; final completedRecipeStepCount = widget.pizzaEvent.recipe.recipeSteps.where((recipeStep) => recipeStep.completed).length;
return Scaffold( return PizzaPlannerScaffold(
appBar: AppBar( title: Text(widget.pizzaEvent.name),
title: Text(widget.pizzaEvent.name), body: Column(
), children: <Widget>[
resizeToAvoidBottomInset: false, Expanded(
body: Container( flex: 15,
padding: const EdgeInsets.all(16), child: Column(
child: Column( children: <Widget>[
children: <Widget>[ Text(widget.pizzaEvent.name),
Expanded( Text(getTimeRemainingString(widget.pizzaEvent.dateTime)),
flex: 15, Container(
child: Column( color: Colors.blue,
children: <Widget>[ child: TextButton(
Text(widget.pizzaEvent.name), onPressed: () {
Text(getTimeRemainingString(widget.pizzaEvent.dateTime)), Navigator.pushNamed(context, "/recipe/view", arguments: widget.pizzaEvent.recipe);
Container( },
color: Colors.blue, child: Text(widget.pizzaEvent.recipe.name, style: const TextStyle(color: Colors.white)),
child: TextButton( )
onPressed: () { )
Navigator.pushNamed(context, "/recipe/view", arguments: widget.pizzaEvent.recipe); ],
},
child: Text(widget.pizzaEvent.recipe.name, style: const TextStyle(color: Colors.white)),
)
)
],
),
), ),
const Divider(), ),
Expanded( const Divider(),
Expanded(
flex: 80, flex: 80,
child: ListView( child: ListView(
children: <Widget>[ children: <Widget>[
@ -78,28 +74,27 @@ class PizzaEventPageState extends State<PizzaEventPage> {
] + widget.pizzaEvent.recipe.ingredients.map((ingredient) => buildIngredientWidget(ingredient)).toList(), ] + widget.pizzaEvent.recipe.ingredients.map((ingredient) => buildIngredientWidget(ingredient)).toList(),
), ),
Table( Table(
columnWidths: const <int, TableColumnWidth>{ columnWidths: const <int, TableColumnWidth>{
0: FlexColumnWidth(4), 0: FlexColumnWidth(4),
1: FlexColumnWidth(3), 1: FlexColumnWidth(3),
2: FlexColumnWidth(), 2: FlexColumnWidth(),
}, },
children: <TableRow>[ children: <TableRow>[
TableRow( TableRow(
children: <TableCell>[ children: <TableCell>[
const TableCell(child: Text("Recipe Step")), const TableCell(child: Text("Recipe Step")),
const TableCell(child: Text("When")), const TableCell(child: Text("When")),
TableCell(child: Text("$completedRecipeStepCount/$recipeStepCount")), TableCell(child: Text("$completedRecipeStepCount/$recipeStepCount")),
] ]
) )
] + widget.pizzaEvent.recipe.recipeSteps.map((recipeStep) => buildRecipeStepWhenWidget(recipeStep)).toList() ] + widget.pizzaEvent.recipe.recipeSteps.map((recipeStep) => buildRecipeStepWhenWidget(recipeStep)).toList()
), ),
const Divider(), const Divider(),
] ]
) )
), ),
], ],
) ),
)
); );
} }

View file

@ -1,6 +1,7 @@
import 'package:flutter/cupertino.dart'; import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:pizzaplanner/entities/pizza_event.dart'; import 'package:pizzaplanner/entities/pizza_event.dart';
import 'package:pizzaplanner/pages/scaffold.dart';
import 'package:pizzaplanner/widgets/pizza_event_widget.dart'; import 'package:pizzaplanner/widgets/pizza_event_widget.dart';
import 'package:hive/hive.dart'; import 'package:hive/hive.dart';
@ -17,47 +18,42 @@ class PizzaEventsState extends State<PizzaEventsPage> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return PizzaPlannerScaffold(
appBar: AppBar( title: const Text("Pizza Events"),
title: const Text("Pizza Events"), body: ValueListenableBuilder(
), valueListenable: Hive.box<PizzaEvent>("PizzaEvents").listenable(),
body: Container( builder: (context, Box<PizzaEvent> box, widget) {
padding: const EdgeInsets.all(16), if (box.isEmpty){
child: ValueListenableBuilder( return Container();
valueListenable: Hive.box<PizzaEvent>("PizzaEvents").listenable(),
builder: (context, Box<PizzaEvent> box, widget) {
if (box.isEmpty){
return Container();
}
return ListView.separated(
padding: const EdgeInsets.all(8),
itemCount: box.length,
itemBuilder: (BuildContext context, int i) => PizzaEventWidget(box.getAt(i)!),
separatorBuilder: (BuildContext context, int i) => const Divider(),
);
} }
), return ListView.separated(
padding: const EdgeInsets.all(8),
itemCount: box.length,
itemBuilder: (BuildContext context, int i) => PizzaEventWidget(box.getAt(i)!),
separatorBuilder: (BuildContext context, int i) => const Divider(),
);
}
), ),
floatingActionButton: FloatingActionButton( floatingActionButton: FloatingActionButton(
onPressed: () async { onPressed: () async {
final dynamic newPizzaEvent = await Navigator.pushNamed( final dynamic newPizzaEvent = await Navigator.pushNamed(
context, context,
"/event/pick_recipe", "/event/pick_recipe",
); );
if (newPizzaEvent != null){ if (newPizzaEvent != null){
addPizzaEvent(newPizzaEvent as PizzaEvent); addPizzaEvent(newPizzaEvent as PizzaEvent);
} }
}, },
tooltip: "Add Pizza Plans", tooltip: "Add Pizza Plans",
child: Center( child: Center(
child: Row( child: Row(
children: const <Widget>[ children: const <Widget>[
Icon(Icons.add), Icon(Icons.add),
Icon(Icons.local_pizza_rounded), Icon(Icons.local_pizza_rounded),
] ]
)
) )
)
), ),
); );
} }

View file

@ -3,6 +3,7 @@ import 'package:flutter_markdown/flutter_markdown.dart';
import 'package:pizzaplanner/entities/PizzaRecipe/pizza_recipe.dart'; import 'package:pizzaplanner/entities/PizzaRecipe/pizza_recipe.dart';
import 'package:pizzaplanner/entities/pizza_event.dart'; import 'package:pizzaplanner/entities/pizza_event.dart';
import 'package:pizzaplanner/entities/PizzaRecipe/recipe_step.dart'; import 'package:pizzaplanner/entities/PizzaRecipe/recipe_step.dart';
import 'package:pizzaplanner/pages/scaffold.dart';
import 'package:url_launcher/url_launcher.dart'; import 'package:url_launcher/url_launcher.dart';
class RecipePage extends StatefulWidget { class RecipePage extends StatefulWidget {
@ -42,37 +43,34 @@ class RecipePageState extends State<RecipePage> {
} }
} }
return Scaffold( return PizzaPlannerScaffold(
appBar: AppBar(
title: Text(widget.pizzaRecipe.name), title: Text(widget.pizzaRecipe.name),
), body: Column(
resizeToAvoidBottomInset: false, children: <Widget>[
body: Column( Expanded(
children: <Widget>[ flex: 95,
Expanded( child: PageView(
flex: 95, onPageChanged: (newPage) => setState(() {page = newPage;}),
child: PageView( controller: controller,
onPageChanged: (newPage) => setState(() {page = newPage;}), children: <Widget>[
controller: controller, Markdown(
children: <Widget>[ data: widget.pizzaRecipe.description,
Markdown( onTapLink: (text, url, title) {
data: widget.pizzaRecipe.description, launch(url!);
onTapLink: (text, url, title) { },
launch(url!); ),
}, ] + widget.pizzaRecipe.recipeSteps.map((recipeStep) => buildRecipeStep(recipeStep)).toList()
), )
] + widget.pizzaRecipe.recipeSteps.map((recipeStep) => buildRecipeStep(recipeStep)).toList() ),
) Expanded(
), flex: 5,
Expanded( child: Row(
flex: 5, mainAxisAlignment: MainAxisAlignment.center,
child: Row( children: pageIndex
mainAxisAlignment: MainAxisAlignment.center, )
children: pageIndex )
) ],
) ),
],
)
); );
} }

View file

@ -4,6 +4,7 @@ import 'package:pizzaplanner/entities/PizzaRecipe/recipe_step.dart';
import 'package:flutter_markdown/flutter_markdown.dart'; import 'package:flutter_markdown/flutter_markdown.dart';
import 'package:pizzaplanner/entities/PizzaRecipe/recipe_substep.dart'; import 'package:pizzaplanner/entities/PizzaRecipe/recipe_substep.dart';
import 'package:pizzaplanner/pages/scaffold.dart';
import 'package:url_launcher/url_launcher.dart'; import 'package:url_launcher/url_launcher.dart';
class RecipeStepInstructionPageArguments { class RecipeStepInstructionPageArguments {
@ -49,41 +50,37 @@ class RecipeStepInstructionState extends State<RecipeStepInstructionPage> {
} else if (page > 0){ } else if (page > 0){
nextButtonText = "Next step"; nextButtonText = "Next step";
} }
return Scaffold(
appBar: AppBar( return PizzaPlannerScaffold(
title: Text(widget.recipeStep.name), title: Text(widget.recipeStep.name),
), body: Column(
resizeToAvoidBottomInset: false,
body: Container(
padding: const EdgeInsets.all(16),
child: Column(
children: <Widget>[ children: <Widget>[
Expanded( Expanded(
flex: 90, flex: 90,
child: PageView( child: PageView(
controller: controller, controller: controller,
onPageChanged: (newPage) => setState(() { onPageChanged: (newPage) => setState(() {
page = newPage; page = newPage;
if (widget.recipeStep.subSteps.isNotEmpty && page != 0) { if (widget.recipeStep.subSteps.isNotEmpty && page != 0) {
currentSubStep = widget.recipeStep.subSteps.elementAt(newPage-1); currentSubStep = widget.recipeStep.subSteps.elementAt(newPage-1);
} }
}), }),
children: <Widget>[ children: <Widget>[
Markdown( Markdown(
data: widget.recipeStep.description, data: widget.recipeStep.description,
onTapLink: (text, url, title) { onTapLink: (text, url, title) {
launch(url!); launch(url!);
}, },
) )
] + widget.recipeStep.subSteps.map((subStep) { ] + widget.recipeStep.subSteps.map((subStep) {
return Markdown( return Markdown(
data: subStep.description, data: subStep.description,
onTapLink: (text, url, title) { onTapLink: (text, url, title) {
launch(url!); launch(url!);
}, },
); );
}).toList() }).toList()
) )
), ),
Expanded( Expanded(
flex: 10, flex: 10,
@ -139,8 +136,7 @@ class RecipeStepInstructionState extends State<RecipeStepInstructionPage> {
), ),
) )
] ]
) ),
)
); );
} }
} }

View file

@ -2,63 +2,57 @@ import 'package:flutter/material.dart';
import 'package:hive_flutter/hive_flutter.dart'; import 'package:hive_flutter/hive_flutter.dart';
import 'package:pizzaplanner/entities/PizzaRecipe/pizza_recipe.dart'; import 'package:pizzaplanner/entities/PizzaRecipe/pizza_recipe.dart';
import 'package:pizzaplanner/pages/nav_drawer.dart'; import 'package:pizzaplanner/pages/nav_drawer.dart';
import 'package:pizzaplanner/pages/scaffold.dart';
import 'package:pizzaplanner/widgets/pizza_recipe_widget.dart'; import 'package:pizzaplanner/widgets/pizza_recipe_widget.dart';
class RecipesPage extends StatelessWidget { class RecipesPage extends StatelessWidget {
@override @override
Widget build(BuildContext context){ Widget build(BuildContext context){
return Scaffold( return PizzaPlannerScaffold(
drawer: NavDrawer(), title: const Text("Pizza Recipes"),
appBar: AppBar( body: Column(
title: const Text("Recipes"), children: <Widget>[
), const Expanded(
resizeToAvoidBottomInset: false, flex: 5,
body: Container( child: Text("Search here maybe")
padding: const EdgeInsets.all(16), ),
child: Column( Container(
children: <Widget>[ color: Colors.blue,
const Expanded( width: double.infinity,
flex: 5, child: TextButton(
child: Text("Search here maybe") onPressed: () async {
), Navigator.pushNamed(context, "/recipes/add");
Container( },
color: Colors.blue, child: const Text("New Recipe", style: TextStyle(color: Colors.white)),
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<PizzaRecipe>("PizzaRecipes").listenable(),
builder: (context, Box<PizzaRecipe> 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(),
);
}
),
) )
] ),
) const Divider(),
) Expanded(
flex: 50,
child: ValueListenableBuilder(
valueListenable: Hive.box<PizzaRecipe>("PizzaRecipes").listenable(),
builder: (context, Box<PizzaRecipe> 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(),
);
}
),
)
]
),
); );
} }
} }

39
lib/pages/scaffold.dart Normal file
View file

@ -0,0 +1,39 @@
import 'package:flutter/material.dart';
import 'package:pizzaplanner/pages/nav_drawer.dart';
class PizzaPlannerScaffold extends StatelessWidget {
late final Widget _body;
late final Widget _appBarTitle;
late final bool _resizeToAvoidBottomInset;
late final EdgeInsets _edgeInsets;
late final FloatingActionButton? _floatingActionButton;
PizzaPlannerScaffold({
required Widget body,
required Widget title,
bool resizeToAvoidBottomInset = false,
EdgeInsets edgeInsets = const EdgeInsets.all(16),
FloatingActionButton? floatingActionButton
}){
_body = body;
_appBarTitle = title;
_resizeToAvoidBottomInset = resizeToAvoidBottomInset;
_edgeInsets = edgeInsets;
_floatingActionButton = floatingActionButton;
}
@override
Widget build(BuildContext context){
return Scaffold(
body: Container(
padding: _edgeInsets,
child: _body
),
appBar: AppBar(
title: _appBarTitle,
),
resizeToAvoidBottomInset: _resizeToAvoidBottomInset,
drawer: NavDrawer(),
floatingActionButton: _floatingActionButton,
);
}
}