mirror of
https://github.com/wger-project/flutter.git
synced 2026-02-18 00:17:48 +01:00
Refactor ingredient search widget
This commit is contained in:
@@ -242,6 +242,12 @@
|
||||
"@logMeal": {},
|
||||
"addIngredient": "Add ingredient",
|
||||
"@addIngredient": {},
|
||||
"logIngredient": "Save to nutrition diary",
|
||||
"@logIngredient": {},
|
||||
"searchIngredient": "Search ingredient",
|
||||
"@searchIngredient": {
|
||||
"description": "Label on ingredient search form"
|
||||
},
|
||||
"nutritionalPlan": "Nutritional plan",
|
||||
"@nutritionalPlan": {},
|
||||
"nutritionalDiary": "Nutritional diary",
|
||||
|
||||
@@ -339,11 +339,11 @@ class NutritionPlansProvider extends WgerBaseProvider with ChangeNotifier {
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
// Log custom ingredient to nutrition diary
|
||||
Future<void> logIngredentToDiary(MealItem mealItem, int planId) async {
|
||||
/// Log custom ingredient to nutrition diary
|
||||
Future<void> logIngredentToDiary(MealItem mealItem, int planId, [DateTime? dateTime]) async {
|
||||
final plan = findById(planId);
|
||||
mealItem.ingredientObj = await fetchIngredient(mealItem.ingredientId);
|
||||
final Log log = Log.fromMealItem(mealItem, plan.id!, null);
|
||||
final Log log = Log.fromMealItem(mealItem, plan.id!, null, dateTime);
|
||||
|
||||
final data = await post(log.toJson(), makeUrl(_nutritionDiaryPath));
|
||||
log.id = data['id'];
|
||||
|
||||
@@ -44,7 +44,19 @@ class NutritionalPlanScreen extends StatelessWidget {
|
||||
|
||||
return Scaffold(
|
||||
//appBar: getAppBar(nutritionalPlan),
|
||||
//drawer: AppDrawer(),
|
||||
floatingActionButton: FloatingActionButton(
|
||||
child: const Icon(Icons.history_edu),
|
||||
onPressed: () {
|
||||
Navigator.pushNamed(
|
||||
context,
|
||||
FormScreen.routeName,
|
||||
arguments: FormScreenArguments(
|
||||
AppLocalizations.of(context).logIngredient,
|
||||
IngredientLogForm(_nutritionalPlan),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
body: CustomScrollView(
|
||||
slivers: <Widget>[
|
||||
SliverAppBar(
|
||||
|
||||
@@ -18,7 +18,6 @@
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||
import 'package:flutter_typeahead/flutter_typeahead.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:wger/exceptions/http_exception.dart';
|
||||
import 'package:wger/helpers/consts.dart';
|
||||
@@ -29,6 +28,7 @@ import 'package:wger/models/nutrition/meal_item.dart';
|
||||
import 'package:wger/models/nutrition/nutritional_plan.dart';
|
||||
import 'package:wger/providers/nutrition.dart';
|
||||
import 'package:wger/screens/nutritional_plan_screen.dart';
|
||||
import 'package:wger/widgets/nutrition/widgets.dart';
|
||||
|
||||
class MealForm extends StatelessWidget {
|
||||
late final Meal _meal;
|
||||
@@ -115,18 +115,18 @@ class MealForm extends StatelessWidget {
|
||||
class MealItemForm extends StatelessWidget {
|
||||
final Meal _meal;
|
||||
late final MealItem _mealItem;
|
||||
|
||||
final List<MealItem> _listMealItems;
|
||||
|
||||
final _form = GlobalKey<FormState>();
|
||||
final _ingredientIdController = TextEditingController();
|
||||
final _ingredientController = TextEditingController();
|
||||
final _amountController = TextEditingController();
|
||||
|
||||
MealItemForm(this._meal, this._listMealItems, [mealItem]) {
|
||||
_mealItem = mealItem ?? MealItem.empty();
|
||||
_mealItem.mealId = _meal.id!;
|
||||
}
|
||||
|
||||
final _form = GlobalKey<FormState>();
|
||||
final _ingredientController = TextEditingController();
|
||||
final _amountController = TextEditingController();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final String unit = AppLocalizations.of(context).g;
|
||||
@@ -136,37 +136,7 @@ class MealItemForm extends StatelessWidget {
|
||||
key: _form,
|
||||
child: Column(
|
||||
children: [
|
||||
TypeAheadFormField(
|
||||
textFieldConfiguration: TextFieldConfiguration(
|
||||
controller: _ingredientController,
|
||||
decoration: InputDecoration(labelText: AppLocalizations.of(context).ingredient),
|
||||
),
|
||||
suggestionsCallback: (pattern) async {
|
||||
return Provider.of<NutritionPlansProvider>(context, listen: false).searchIngredient(
|
||||
pattern,
|
||||
Localizations.localeOf(context).languageCode,
|
||||
);
|
||||
},
|
||||
itemBuilder: (context, dynamic suggestion) {
|
||||
return ListTile(
|
||||
title: Text(suggestion['value']),
|
||||
subtitle: Text(suggestion['data']['id'].toString()),
|
||||
);
|
||||
},
|
||||
transitionBuilder: (context, suggestionsBox, controller) {
|
||||
return suggestionsBox;
|
||||
},
|
||||
onSuggestionSelected: (dynamic suggestion) {
|
||||
_mealItem.ingredientId = suggestion['data']['id'];
|
||||
_ingredientController.text = suggestion['value'];
|
||||
},
|
||||
validator: (value) {
|
||||
if (value!.isEmpty) {
|
||||
return AppLocalizations.of(context).selectIngredient;
|
||||
}
|
||||
return null;
|
||||
},
|
||||
),
|
||||
IngredientTypeahead(_ingredientIdController, _ingredientController),
|
||||
TextFormField(
|
||||
decoration: InputDecoration(labelText: AppLocalizations.of(context).weight),
|
||||
controller: _amountController,
|
||||
@@ -191,6 +161,7 @@ class MealItemForm extends StatelessWidget {
|
||||
return;
|
||||
}
|
||||
_form.currentState!.save();
|
||||
_mealItem.ingredientId = int.parse(_ingredientIdController.text);
|
||||
|
||||
try {
|
||||
Provider.of<NutritionPlansProvider>(context, listen: false)
|
||||
@@ -217,6 +188,8 @@ class MealItemForm extends StatelessWidget {
|
||||
child: ListTile(
|
||||
onTap: () {
|
||||
_ingredientController.text = _listMealItems[index].ingredientObj.name;
|
||||
_ingredientIdController.text =
|
||||
_listMealItems[index].ingredientObj.id.toString();
|
||||
_amountController.text = _listMealItems[index].amount.toStringAsFixed(0);
|
||||
_mealItem.ingredientId = _listMealItems[index].ingredientId;
|
||||
_mealItem.amount = _listMealItems[index].amount;
|
||||
@@ -236,63 +209,32 @@ class MealItemForm extends StatelessWidget {
|
||||
}
|
||||
}
|
||||
|
||||
class IngredientForm extends StatelessWidget {
|
||||
class IngredientLogForm extends StatelessWidget {
|
||||
late MealItem _mealItem;
|
||||
final int _planId;
|
||||
|
||||
IngredientForm(this._planId) {
|
||||
_mealItem = MealItem.empty();
|
||||
}
|
||||
final NutritionalPlan _plan;
|
||||
|
||||
final _form = GlobalKey<FormState>();
|
||||
final _ingredientController = TextEditingController();
|
||||
final _ingredientIdController = TextEditingController();
|
||||
final _amountController = TextEditingController();
|
||||
final _dateController = TextEditingController();
|
||||
|
||||
IngredientLogForm(this._plan) {
|
||||
_mealItem = MealItem.empty();
|
||||
_dateController.text = toDate(DateTime.now())!;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
margin: EdgeInsets.all(20),
|
||||
margin: const EdgeInsets.all(20),
|
||||
child: Form(
|
||||
key: _form,
|
||||
child: Column(
|
||||
children: [
|
||||
TypeAheadFormField(
|
||||
textFieldConfiguration: TextFieldConfiguration(
|
||||
controller: _ingredientController,
|
||||
decoration: InputDecoration(
|
||||
labelText: AppLocalizations.of(context).ingredient),
|
||||
),
|
||||
suggestionsCallback: (pattern) async {
|
||||
return Provider.of<NutritionPlansProvider>(context,
|
||||
listen: false)
|
||||
.searchIngredient(
|
||||
pattern,
|
||||
Localizations.localeOf(context).languageCode,
|
||||
);
|
||||
},
|
||||
itemBuilder: (context, dynamic suggestion) {
|
||||
return ListTile(
|
||||
title: Text(suggestion['value']),
|
||||
subtitle: Text(suggestion['data']['id'].toString()),
|
||||
);
|
||||
},
|
||||
transitionBuilder: (context, suggestionsBox, controller) {
|
||||
return suggestionsBox;
|
||||
},
|
||||
onSuggestionSelected: (dynamic suggestion) {
|
||||
_mealItem.ingredientId = suggestion['data']['id'];
|
||||
_ingredientController.text = suggestion['value'];
|
||||
},
|
||||
validator: (value) {
|
||||
if (value!.isEmpty) {
|
||||
return AppLocalizations.of(context).selectIngredient;
|
||||
}
|
||||
return null;
|
||||
},
|
||||
),
|
||||
IngredientTypeahead(_ingredientIdController, _ingredientController),
|
||||
TextFormField(
|
||||
decoration: InputDecoration(
|
||||
labelText: AppLocalizations.of(context).weight),
|
||||
decoration: InputDecoration(labelText: AppLocalizations.of(context).weight),
|
||||
controller: _amountController,
|
||||
keyboardType: TextInputType.number,
|
||||
onFieldSubmitted: (_) {},
|
||||
@@ -308,6 +250,31 @@ class IngredientForm extends StatelessWidget {
|
||||
return null;
|
||||
},
|
||||
),
|
||||
TextFormField(
|
||||
readOnly: true, // Stop keyboard from appearing
|
||||
decoration: InputDecoration(
|
||||
labelText: AppLocalizations.of(context).date,
|
||||
suffixIcon: const Icon(Icons.calendar_today_outlined),
|
||||
),
|
||||
enableInteractiveSelection: false,
|
||||
controller: _dateController,
|
||||
onTap: () async {
|
||||
// Show Date Picker Here
|
||||
final pickedDate = await showDatePicker(
|
||||
context: context,
|
||||
initialDate: DateTime.now(),
|
||||
firstDate: DateTime(DateTime.now().year - 10),
|
||||
lastDate: DateTime.now(),
|
||||
);
|
||||
|
||||
if (pickedDate != null) {
|
||||
_dateController.text = toDate(pickedDate)!;
|
||||
}
|
||||
},
|
||||
onSaved: (newValue) {
|
||||
_dateController.text = newValue!;
|
||||
},
|
||||
),
|
||||
ElevatedButton(
|
||||
child: Text(AppLocalizations.of(context).save),
|
||||
onPressed: () async {
|
||||
@@ -315,10 +282,11 @@ class IngredientForm extends StatelessWidget {
|
||||
return;
|
||||
}
|
||||
_form.currentState!.save();
|
||||
_mealItem.ingredientId = int.parse(_ingredientIdController.text);
|
||||
|
||||
try {
|
||||
Provider.of<NutritionPlansProvider>(context, listen: false)
|
||||
.logIngredentToDiary(_mealItem, _planId);
|
||||
Provider.of<NutritionPlansProvider>(context, listen: false).logIngredentToDiary(
|
||||
_mealItem, _plan.id!, DateTime.parse(_dateController.text));
|
||||
} on WgerHttpException catch (error) {
|
||||
showHttpExceptionErrorDialog(error, context);
|
||||
} catch (error) {
|
||||
|
||||
@@ -67,22 +67,6 @@ class NutritionalPlanDetailWidget extends StatelessWidget {
|
||||
},
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: ElevatedButton(
|
||||
child: Text(AppLocalizations.of(context).addIngredient),
|
||||
onPressed: () {
|
||||
Navigator.pushNamed(
|
||||
context,
|
||||
FormScreen.routeName,
|
||||
arguments: FormScreenArguments(
|
||||
AppLocalizations.of(context).addIngredient,
|
||||
IngredientForm(_nutritionalPlan.id!),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
Container(
|
||||
padding: const EdgeInsets.all(15),
|
||||
height: 220,
|
||||
|
||||
74
lib/widgets/nutrition/widgets.dart
Normal file
74
lib/widgets/nutrition/widgets.dart
Normal file
@@ -0,0 +1,74 @@
|
||||
/*
|
||||
* This file is part of wger Workout Manager <https://github.com/wger-project>.
|
||||
* Copyright (C) 2020, 2021 wger Team
|
||||
*
|
||||
* wger Workout Manager is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* wger Workout Manager is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||
import 'package:flutter_typeahead/flutter_typeahead.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:wger/providers/nutrition.dart';
|
||||
|
||||
class IngredientTypeahead extends StatefulWidget {
|
||||
final TextEditingController _ingredientController;
|
||||
final TextEditingController _ingredientIdController;
|
||||
|
||||
IngredientTypeahead(this._ingredientIdController, this._ingredientController);
|
||||
|
||||
@override
|
||||
_IngredientTypeaheadState createState() => _IngredientTypeaheadState();
|
||||
}
|
||||
|
||||
class _IngredientTypeaheadState extends State<IngredientTypeahead> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return TypeAheadFormField(
|
||||
textFieldConfiguration: TextFieldConfiguration(
|
||||
controller: widget._ingredientController,
|
||||
decoration: InputDecoration(
|
||||
labelText: AppLocalizations.of(context).searchIngredient,
|
||||
suffixIcon: const Icon(Icons.search),
|
||||
),
|
||||
),
|
||||
suggestionsCallback: (pattern) async {
|
||||
return Provider.of<NutritionPlansProvider>(context, listen: false).searchIngredient(
|
||||
pattern,
|
||||
Localizations.localeOf(context).languageCode,
|
||||
);
|
||||
},
|
||||
itemBuilder: (context, dynamic suggestion) {
|
||||
return ListTile(
|
||||
title: Text(suggestion['value']),
|
||||
subtitle: Text(suggestion['data']['id'].toString()),
|
||||
);
|
||||
},
|
||||
transitionBuilder: (context, suggestionsBox, controller) {
|
||||
return suggestionsBox;
|
||||
},
|
||||
onSuggestionSelected: (dynamic suggestion) {
|
||||
widget._ingredientIdController.text = suggestion['data']['id'].toString();
|
||||
widget._ingredientController.text = suggestion['value'];
|
||||
},
|
||||
validator: (value) {
|
||||
if (value!.isEmpty) {
|
||||
return AppLocalizations.of(context).selectIngredient;
|
||||
}
|
||||
return null;
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -692,7 +692,7 @@ packages:
|
||||
name: rive
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.7.28"
|
||||
version: "0.7.33"
|
||||
shared_preferences:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
||||
@@ -47,7 +47,7 @@ dependencies:
|
||||
version: ^2.0.0
|
||||
package_info: ^2.0.2
|
||||
provider: ^5.0.0
|
||||
rive: 0.7.28
|
||||
rive: ^0.7.33
|
||||
shared_preferences: ^2.0.7
|
||||
table_calendar: ^3.0.2
|
||||
url_launcher: ^6.0.10
|
||||
|
||||
Reference in New Issue
Block a user