various ingredient form UX issues

* upon scan completion, give more useful preview of the found ingredient
* use ID field to track whether the form is "loaded" with a valid
  ingredient or not
* don't trigger search if an ingredient is loaded, unless user changes
  the pattern
* always show meal item preview, whether loaded from recent items, or
  from scan result
This commit is contained in:
Dieter Plaetinck
2024-05-26 21:05:10 +02:00
parent f00dc88678
commit b21c19ae65
2 changed files with 47 additions and 13 deletions

View File

@@ -169,7 +169,6 @@ class IngredientFormState extends State<IngredientForm> {
final _timeController = TextEditingController(); // optional
final _mealItem = MealItem.empty();
bool validIngredientId = false;
@override
void initState() {
super.initState();
@@ -287,7 +286,7 @@ class IngredientFormState extends State<IngredientForm> {
),
],
),
if (validIngredientId)
if (ingredientIdController.text.isNotEmpty)
Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
@@ -302,10 +301,9 @@ class IngredientFormState extends State<IngredientForm> {
builder: (BuildContext context, AsyncSnapshot<Ingredient> snapshot) {
if (snapshot.hasData) {
_mealItem.ingredient = snapshot.data!;
return ListTile(
leading: IngredientAvatar(ingredient: _mealItem.ingredient),
title:
Text(getShortNutritionValues(_mealItem.nutritionalValues, context)),
return MealItemTile(
ingredient: _mealItem.ingredient,
nutritionalValues: _mealItem.nutritionalValues,
);
} else if (snapshot.hasError) {
return Padding(
@@ -365,14 +363,13 @@ class IngredientFormState extends State<IngredientForm> {
return Card(
child: ListTile(
onTap: () {
_ingredientController.text = widget.recent[index].ingredient.name;
_ingredientIdController.text =
widget.recent[index].ingredient.id.toString();
_amountController.text = widget.recent[index].amount.toStringAsFixed(0);
setState(() {
_ingredientController.text = widget.recent[index].ingredient.name;
_ingredientIdController.text =
widget.recent[index].ingredient.id.toString();
_amountController.text = widget.recent[index].amount.toStringAsFixed(0);
_mealItem.ingredientId = widget.recent[index].ingredientId;
_mealItem.amount = widget.recent[index].amount;
validIngredientId = true;
});
},
title: Text(

View File

@@ -32,6 +32,7 @@ import 'package:wger/models/exercises/ingredient_api.dart';
import 'package:wger/models/nutrition/ingredient.dart';
import 'package:wger/models/nutrition/log.dart';
import 'package:wger/models/nutrition/nutritional_plan.dart';
import 'package:wger/models/nutrition/nutritional_values.dart';
import 'package:wger/providers/nutrition.dart';
import 'package:wger/widgets/core/core.dart';
import 'package:wger/widgets/nutrition/helpers.dart';
@@ -118,6 +119,11 @@ class _IngredientTypeaheadState extends State<IngredientTypeahead> {
}
return null;
},
onChanged: (value) {
// if user changes the pattern, it means they want to drop the
// currently loaded ingredient (if any) and start a new search
widget._ingredientIdController.text = '';
},
decoration: InputDecoration(
prefixIcon: const Icon(Icons.search),
labelText: AppLocalizations.of(context).searchIngredient,
@@ -126,7 +132,8 @@ class _IngredientTypeaheadState extends State<IngredientTypeahead> {
);
},
suggestionsCallback: (pattern) {
if (pattern == '') {
// don't do search if user has already loaded a specific item
if (pattern == '' || widget._ingredientIdController.text.isNotEmpty) {
return null;
}
@@ -188,6 +195,7 @@ class _IngredientTypeaheadState extends State<IngredientTypeahead> {
}
final result = await Provider.of<NutritionPlansProvider>(context, listen: false)
.searchIngredientWithCode(barcode);
// TODO: show spinner...
if (!mounted) {
return;
}
@@ -198,7 +206,16 @@ class _IngredientTypeaheadState extends State<IngredientTypeahead> {
builder: (ctx) => AlertDialog(
key: const Key('found-dialog'),
title: Text(AppLocalizations.of(context).productFound),
content: Text(AppLocalizations.of(context).productFoundDescription(result.name)),
content: Column(
mainAxisSize: MainAxisSize.min,
children: [
Text(AppLocalizations.of(context).productFoundDescription(result.name)),
MealItemTile(
ingredient: result,
nutritionalValues: result.nutritionalValues,
),
],
),
actions: [
TextButton(
key: const Key('found-dialog-confirm-button'),
@@ -357,3 +374,23 @@ class IngredientAvatar extends StatelessWidget {
: const CircleIconAvatar(Icon(Icons.image, color: Colors.grey));
}
}
class MealItemTile extends StatelessWidget {
final Ingredient ingredient;
final NutritionalValues nutritionalValues;
const MealItemTile({
super.key,
required this.ingredient,
required this.nutritionalValues,
});
@override
Widget build(BuildContext context) {
return ListTile(
leading: IngredientAvatar(ingredient: ingredient),
title: Text(getShortNutritionValues(nutritionalValues, context)),
subtitle: Text(ingredient.id.toString()),
);
}
}