From f40d572fae9a7f63812a20695e947938fb517c1b Mon Sep 17 00:00:00 2001 From: AlexisBirling3 Date: Thu, 12 Feb 2026 10:48:12 -0500 Subject: [PATCH 1/4] improves the pop up after a scan gives no result --- lib/l10n/app_en.arb | 10 +++++++++- lib/widgets/nutrition/ingredient_dialogs.dart | 19 +++++++++++++++++++ 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index ff1506dcf..8e94bea3d 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -1128,5 +1128,13 @@ "themeMode": "Theme mode", "darkMode": "Always dark mode", "lightMode": "Always light mode", - "systemMode": "System settings" + "systemMode": "System settings", + "productNotFoundOpenFoodFacts": "You can add this product to Open Food Facts to help the community!", + "@productNotFoundOpenFoodFacts": { + "description": "Label shown when product is not found to encourage users to go on Open Food Facts and add it themselves" + }, + "addToOpenFoodFacts": "Add to Open Food Facts", + "@addToOpenFoodFacts": { + "description": "Label shown as the clickable link to go on Open Food Facts" + } } diff --git a/lib/widgets/nutrition/ingredient_dialogs.dart b/lib/widgets/nutrition/ingredient_dialogs.dart index b918b3607..1800094b6 100644 --- a/lib/widgets/nutrition/ingredient_dialogs.dart +++ b/lib/widgets/nutrition/ingredient_dialogs.dart @@ -198,6 +198,14 @@ class IngredientScanResultDialog extends StatelessWidget { AppLocalizations.of(context).productNotFoundDescription(barcode), ), ), + Padding( + padding: const EdgeInsets.only(bottom: 8.0), + child: Text( + AppLocalizations.of(context).productNotFoundOpenFoodFacts, + style: TextStyle(fontSize: 14, color: Colors.grey[700]), + ), + ), + if (ingredient != null) Padding( padding: const EdgeInsets.only(bottom: 8.0), @@ -241,6 +249,17 @@ class IngredientScanResultDialog extends StatelessWidget { Navigator.of(context).pop(); }, ), + // if didn't find a result after scanning + if (snapshot.connectionState == ConnectionState.done && ingredient == null) + TextButton.icon( + key: const Key('ingredient-scan-result-dialog-open-food-facts-button'), + icon: const Icon(Icons.add_circle_outline), + label: Text(AppLocalizations.of(context).addToOpenFoodFacts), + onPressed: () { + launchURL('https://world.openfoodfacts.org/cgi/product.pl', context); + Navigator.of(context).pop(); + }, + ), // if didn't match, or we're still waiting TextButton( key: const Key('ingredient-scan-result-dialog-close-button'), From 65a7e5dd43c45cbf14e062f5c0d1de677a6755e9 Mon Sep 17 00:00:00 2001 From: AlexisBirling3 Date: Tue, 17 Feb 2026 15:12:02 -0500 Subject: [PATCH 2/4] adds the tests for the modified widget in this issue --- .../nutrition/ingredient_dialogs_test.dart | 136 ++++++++++++++++++ 1 file changed, 136 insertions(+) create mode 100644 test/widgets/nutrition/ingredient_dialogs_test.dart diff --git a/test/widgets/nutrition/ingredient_dialogs_test.dart b/test/widgets/nutrition/ingredient_dialogs_test.dart new file mode 100644 index 000000000..c93b5f76c --- /dev/null +++ b/test/widgets/nutrition/ingredient_dialogs_test.dart @@ -0,0 +1,136 @@ +/* + * This file is part of wger Workout Manager . + * Copyright (c) 2026 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. + * + * This program 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 . + */ + +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:wger/l10n/generated/app_localizations.dart'; +import 'package:wger/models/nutrition/ingredient.dart'; +import 'package:wger/widgets/nutrition/ingredient_dialogs.dart'; + +Future pumpIngredientScanDialog( + WidgetTester tester, { + required AsyncSnapshot snapshot, + required String barcode, + }) async { + await tester.pumpWidget( + MaterialApp( + locale: const Locale('en'), + localizationsDelegates: AppLocalizations.localizationsDelegates, + supportedLocales: AppLocalizations.supportedLocales, + home: Scaffold( + body: Builder( + builder: (context) => ElevatedButton( + onPressed: () { + showDialog( + context: context, + builder: (_) => IngredientScanResultDialog( + snapshot, + barcode, + (int id, String name, num? amount) {}, // Mock callback + ), + ); + }, + child: const Text('Show Dialog'), + ), + ), + ), + ), + ); +} + +void main() { + group('IngredientScanResultDialog tests', () { + const testBarcode = '1234567890123'; + + testWidgets( + 'shows Open Food Facts button when product not found', + (WidgetTester tester) async { + // Arrange + const snapshot = AsyncSnapshot.withData( + ConnectionState.done, + null, // null = product not found + ); + + // Act + await pumpIngredientScanDialog( + tester, + snapshot: snapshot, + barcode: testBarcode, + ); + + // Open dialog + await tester.tap(find.text('Show Dialog')); + await tester.pumpAndSettle(); + + // Assert + expect(find.byType(AlertDialog), findsOneWidget); + + // Verify the Open Food Facts button exists + expect( + find.byKey(const Key('ingredient-scan-result-dialog-open-food-facts-button')), + findsOneWidget, + ); + + // Verify the close button exists + expect( + find.byKey(const Key('ingredient-scan-result-dialog-close-button')), + findsOneWidget, + ); + + // Verify the continue button does not exist + expect( + find.byKey(const Key('ingredient-scan-result-dialog-confirm-button')), + findsNothing, + ); + }, + ); + + testWidgets( + 'tapping Open Food Facts button closes the dialog', + (WidgetTester tester) async { + // Arrange + const snapshot = AsyncSnapshot.withData( + ConnectionState.done, + null, + ); + + await pumpIngredientScanDialog( + tester, + snapshot: snapshot, + barcode: testBarcode, + ); + + // Open dialog + await tester.tap(find.text('Show Dialog')); + await tester.pumpAndSettle(); + + // Verify dialog is open + expect(find.byType(AlertDialog), findsOneWidget); + + // Act: Tap the Open Food Facts button + await tester.tap( + find.byKey(const Key('ingredient-scan-result-dialog-open-food-facts-button')), + ); + await tester.pumpAndSettle(); + + // Assert: Dialog should be closed + expect(find.byType(AlertDialog), findsNothing); + }, + ); + }); +} \ No newline at end of file From bcbcd1a2dc5f412f4cef16c4b6152b7006a71ea2 Mon Sep 17 00:00:00 2001 From: AlexisBirling3 Date: Tue, 17 Feb 2026 15:27:18 -0500 Subject: [PATCH 3/4] apply dart format --- lib/widgets/nutrition/ingredient_dialogs.dart | 12 ++++++------ .../nutrition/ingredient_dialogs_test.dart | 16 ++++++++-------- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/lib/widgets/nutrition/ingredient_dialogs.dart b/lib/widgets/nutrition/ingredient_dialogs.dart index 1800094b6..2849772e9 100644 --- a/lib/widgets/nutrition/ingredient_dialogs.dart +++ b/lib/widgets/nutrition/ingredient_dialogs.dart @@ -198,13 +198,13 @@ class IngredientScanResultDialog extends StatelessWidget { AppLocalizations.of(context).productNotFoundDescription(barcode), ), ), - Padding( - padding: const EdgeInsets.only(bottom: 8.0), - child: Text( - AppLocalizations.of(context).productNotFoundOpenFoodFacts, - style: TextStyle(fontSize: 14, color: Colors.grey[700]), - ), + Padding( + padding: const EdgeInsets.only(bottom: 8.0), + child: Text( + AppLocalizations.of(context).productNotFoundOpenFoodFacts, + style: TextStyle(fontSize: 14, color: Colors.grey[700]), ), + ), if (ingredient != null) Padding( diff --git a/test/widgets/nutrition/ingredient_dialogs_test.dart b/test/widgets/nutrition/ingredient_dialogs_test.dart index c93b5f76c..5a51220e4 100644 --- a/test/widgets/nutrition/ingredient_dialogs_test.dart +++ b/test/widgets/nutrition/ingredient_dialogs_test.dart @@ -23,10 +23,10 @@ import 'package:wger/models/nutrition/ingredient.dart'; import 'package:wger/widgets/nutrition/ingredient_dialogs.dart'; Future pumpIngredientScanDialog( - WidgetTester tester, { - required AsyncSnapshot snapshot, - required String barcode, - }) async { + WidgetTester tester, { + required AsyncSnapshot snapshot, + required String barcode, +}) async { await tester.pumpWidget( MaterialApp( locale: const Locale('en'), @@ -41,7 +41,7 @@ Future pumpIngredientScanDialog( builder: (_) => IngredientScanResultDialog( snapshot, barcode, - (int id, String name, num? amount) {}, // Mock callback + (int id, String name, num? amount) {}, // Mock callback ), ); }, @@ -59,7 +59,7 @@ void main() { testWidgets( 'shows Open Food Facts button when product not found', - (WidgetTester tester) async { + (WidgetTester tester) async { // Arrange const snapshot = AsyncSnapshot.withData( ConnectionState.done, @@ -102,7 +102,7 @@ void main() { testWidgets( 'tapping Open Food Facts button closes the dialog', - (WidgetTester tester) async { + (WidgetTester tester) async { // Arrange const snapshot = AsyncSnapshot.withData( ConnectionState.done, @@ -133,4 +133,4 @@ void main() { }, ); }); -} \ No newline at end of file +} From da4c0e7af39790b0030c6f0770f774074bdbf2b7 Mon Sep 17 00:00:00 2001 From: AlexisBirling3 Date: Tue, 24 Feb 2026 14:40:55 -0500 Subject: [PATCH 4/4] Puts the dialog in a new column and changes style to default --- lib/widgets/nutrition/ingredient_dialogs.dart | 20 ++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/lib/widgets/nutrition/ingredient_dialogs.dart b/lib/widgets/nutrition/ingredient_dialogs.dart index 2849772e9..580541422 100644 --- a/lib/widgets/nutrition/ingredient_dialogs.dart +++ b/lib/widgets/nutrition/ingredient_dialogs.dart @@ -194,17 +194,19 @@ class IngredientScanResultDialog extends StatelessWidget { if (snapshot.connectionState == ConnectionState.done && ingredient == null) Padding( padding: const EdgeInsets.only(bottom: 8.0), - child: Text( - AppLocalizations.of(context).productNotFoundDescription(barcode), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Text( + AppLocalizations.of(context).productNotFoundDescription(barcode), + ), + const SizedBox(height: 8), + Text( + AppLocalizations.of(context).productNotFoundOpenFoodFacts, + ), + ], ), ), - Padding( - padding: const EdgeInsets.only(bottom: 8.0), - child: Text( - AppLocalizations.of(context).productNotFoundOpenFoodFacts, - style: TextStyle(fontSize: 14, color: Colors.grey[700]), - ), - ), if (ingredient != null) Padding(