diff --git a/lib/models/nutrition/nutritional_plan.dart b/lib/models/nutrition/nutritional_plan.dart index fe453dd2e..a4527ec1d 100644 --- a/lib/models/nutrition/nutritional_plan.dart +++ b/lib/models/nutrition/nutritional_plan.dart @@ -16,6 +16,7 @@ * along with this program. If not, see . */ +import 'package:collection/collection.dart'; import 'package:flutter/widgets.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:json_annotation/json_annotation.dart'; @@ -201,16 +202,16 @@ class NutritionalPlan { return out; } - /// Helper that returns all meal items for the current plan - /// - /// Duplicated ingredients are removed - List get allMealItems { + /// returns meal items across all meals + /// deduped by the combination of amount and ingredient ID + List get dedupMealItems { final List out = []; for (final meal in meals) { for (final mealItem in meal.mealItems) { - final ingredientInList = out.where((e) => e.ingredientId == mealItem.ingredientId); + final found = out.firstWhereOrNull( + (e) => e.amount == mealItem.amount && e.ingredientId == mealItem.ingredientId); - if (ingredientInList.isEmpty) { + if (found == null) { out.add(mealItem); } } @@ -218,6 +219,20 @@ class NutritionalPlan { return out; } + /// returns diary entries + /// deduped by the combination of amount and ingredient ID + List get dedupDiaryEntries { + final out = []; + for (final log in diaryEntries) { + final found = + out.firstWhereOrNull((e) => e.amount == log.amount && e.ingredientId == log.ingredientId); + if (found == null) { + out.add(log); + } + } + return out; + } + Meal pseudoMealOthers(String name) { return Meal( id: PSEUDO_MEAL_ID, diff --git a/lib/widgets/nutrition/forms.dart b/lib/widgets/nutrition/forms.dart index da61afc64..ba96b5cd1 100644 --- a/lib/widgets/nutrition/forms.dart +++ b/lib/widgets/nutrition/forms.dart @@ -131,7 +131,7 @@ Widget MealItemForm(Meal meal, List recent, [String? barcode, bool? te Widget IngredientLogForm(NutritionalPlan plan) { return IngredientForm( - recent: plan.diaryEntries, + recent: plan.dedupDiaryEntries, onSave: (BuildContext context, MealItem mealItem, DateTime? dt) { Provider.of(context, listen: false) .logIngredientToDiary(mealItem, plan.id!, dt); @@ -168,6 +168,7 @@ class IngredientFormState extends State { final _dateController = TextEditingController(); // optional final _timeController = TextEditingController(); // optional final _mealItem = MealItem.empty(); + var _searchQuery = ''; // copy from typeahead. for filtering suggestions @override void initState() { @@ -201,10 +202,18 @@ class IngredientFormState extends State { }); } + void updateSearchQuery(String query) { + setState(() { + _searchQuery = query; + }); + } + @override Widget build(BuildContext context) { final String unit = AppLocalizations.of(context).g; - + final queryLower = _searchQuery.toLowerCase(); + final suggestions = + widget.recent.where((e) => e.ingredient.name.toLowerCase().contains(queryLower)).toList(); return Container( margin: const EdgeInsets.all(20), child: Form( @@ -218,6 +227,7 @@ class IngredientFormState extends State { test: widget.test, selectIngredient: selectIngredient, unSelectIngredient: unSelectIngredient, + updateSearchQuery: updateSearchQuery, ), Row( children: [ @@ -370,27 +380,26 @@ class IngredientFormState extends State { Navigator.of(context).pop(); }, ), - if (widget.recent.isNotEmpty) const SizedBox(height: 10.0), + if (suggestions.isNotEmpty) const SizedBox(height: 10.0), Container( padding: const EdgeInsets.all(10.0), child: Text(AppLocalizations.of(context).recentlyUsedIngredients), ), Expanded( child: ListView.builder( - itemCount: widget.recent.length, + itemCount: suggestions.length, shrinkWrap: true, itemBuilder: (context, index) { return Card( child: ListTile( onTap: () { - final ingredient = widget.recent[index].ingredient; - selectIngredient( - ingredient.id, ingredient.name, widget.recent[index].amount); + final ingredient = suggestions[index].ingredient; + selectIngredient(ingredient.id, ingredient.name, suggestions[index].amount); }, title: Text( - '${widget.recent[index].ingredient.name} (${widget.recent[index].amount.toStringAsFixed(0)}$unit)'), + '${suggestions[index].ingredient.name} (${suggestions[index].amount.toStringAsFixed(0)}$unit)'), subtitle: Text(getShortNutritionValues( - widget.recent[index].ingredient.nutritionalValues, context)), + suggestions[index].ingredient.nutritionalValues, context)), trailing: const Icon(Icons.copy), ), ); diff --git a/lib/widgets/nutrition/meal.dart b/lib/widgets/nutrition/meal.dart index 769f991fd..e11b6e7c0 100644 --- a/lib/widgets/nutrition/meal.dart +++ b/lib/widgets/nutrition/meal.dart @@ -39,11 +39,11 @@ enum viewMode { class MealWidget extends StatefulWidget { final Meal _meal; - final List _listMealItems; + final List _recentMealItems; const MealWidget( this._meal, - this._listMealItems, + this._recentMealItems, ); @override @@ -108,7 +108,7 @@ class _MealWidgetState extends State { FormScreen.routeName, arguments: FormScreenArguments( AppLocalizations.of(context).addIngredient, - MealItemForm(widget._meal, widget._listMealItems), + MealItemForm(widget._meal, widget._recentMealItems), hasListView: true, ), ); diff --git a/lib/widgets/nutrition/nutritional_plan_detail.dart b/lib/widgets/nutrition/nutritional_plan_detail.dart index 9feb631c4..cf8739040 100644 --- a/lib/widgets/nutrition/nutritional_plan_detail.dart +++ b/lib/widgets/nutrition/nutritional_plan_detail.dart @@ -56,11 +56,11 @@ class NutritionalPlanDetailWidget extends StatelessWidget { const SizedBox(height: 10), ..._nutritionalPlan.meals.map((meal) => MealWidget( meal, - _nutritionalPlan.allMealItems, + _nutritionalPlan.dedupMealItems, )), MealWidget( _nutritionalPlan.pseudoMealOthers('Other logs'), - _nutritionalPlan.allMealItems, + _nutritionalPlan.dedupMealItems, ), if (!_nutritionalPlan.onlyLogging) Padding( diff --git a/lib/widgets/nutrition/widgets.dart b/lib/widgets/nutrition/widgets.dart index 35d335c58..460c15a7e 100644 --- a/lib/widgets/nutrition/widgets.dart +++ b/lib/widgets/nutrition/widgets.dart @@ -65,6 +65,7 @@ class IngredientTypeahead extends StatefulWidget { final Function(int id, String name, num? amount) selectIngredient; final Function() unSelectIngredient; + final Function(String query) updateSearchQuery; const IngredientTypeahead( this._ingredientIdController, @@ -74,6 +75,7 @@ class IngredientTypeahead extends StatefulWidget { this.barcode = '', required this.selectIngredient, required this.unSelectIngredient, + required this.updateSearchQuery, }); @override @@ -125,6 +127,7 @@ class _IngredientTypeaheadState extends State { return null; }, onChanged: (value) { + widget.updateSearchQuery(value); // unselect to start a new search widget.unSelectIngredient(); }, diff --git a/test/nutrition/nutritional_plan_model_test.dart b/test/nutrition/nutritional_plan_model_test.dart index 860e47dc8..a48fa4683 100644 --- a/test/nutrition/nutritional_plan_model_test.dart +++ b/test/nutrition/nutritional_plan_model_test.dart @@ -101,7 +101,7 @@ void main() { }); test('Test that the getter returns all meal items for a plan', () { - expect(plan.allMealItems, plan.meals[0].mealItems + plan.meals[1].mealItems); + expect(plan.dedupMealItems, plan.meals[0].mealItems + plan.meals[1].mealItems); }); }); }