diff --git a/packages/smooth_app/lib/cards/product_cards/knowledge_panels/knowledge_panels_builder.dart b/packages/smooth_app/lib/cards/product_cards/knowledge_panels/knowledge_panels_builder.dart index b103ce20f72..7898a00aa75 100644 --- a/packages/smooth_app/lib/cards/product_cards/knowledge_panels/knowledge_panels_builder.dart +++ b/packages/smooth_app/lib/cards/product_cards/knowledge_panels/knowledge_panels_builder.dart @@ -17,11 +17,17 @@ import 'package:smooth_app/pages/user_preferences_dev_mode.dart'; /// /// Panels display large data like all health data or environment data. class KnowledgePanelsBuilder { - const KnowledgePanelsBuilder({this.setState}); + const KnowledgePanelsBuilder({ + this.setState, + this.refreshProductCallback, + }); /// Would for instance refresh the product page. final VoidCallback? setState; + /// Callback to refresh the product when necessary. + final Function(BuildContext)? refreshProductCallback; + /// Builds all panels. /// /// Typical use case: product page. @@ -140,12 +146,12 @@ class KnowledgePanelsBuilder { knowledgePanelElementWidgets.add( addPanelButton( appLocalizations.score_add_missing_ingredients, - onPressed: () async => Navigator.push( + onPressed: () async => Navigator.push( context, - MaterialPageRoute( + MaterialPageRoute( builder: (BuildContext context) => EditIngredientsPage( product: product, - imageIngredientsUrl: product.imageIngredientsUrl, + refreshProductCallback: refreshProductCallback, ), ), ), diff --git a/packages/smooth_app/lib/database/product_query.dart b/packages/smooth_app/lib/database/product_query.dart index 1efd7bd1238..fca313a0e1f 100644 --- a/packages/smooth_app/lib/database/product_query.dart +++ b/packages/smooth_app/lib/database/product_query.dart @@ -100,6 +100,7 @@ abstract class ProductQuery { ProductField.NUTRIMENT_ENERGY_UNIT, ProductField.ADDITIVES, ProductField.INGREDIENTS_ANALYSIS_TAGS, + ProductField.INGREDIENTS_TEXT, ProductField.LABELS_TAGS, ProductField.LABELS_TAGS_IN_LANGUAGES, ProductField.ENVIRONMENT_IMPACT_LEVELS, diff --git a/packages/smooth_app/lib/l10n/app_en.arb b/packages/smooth_app/lib/l10n/app_en.arb index 2382b0c6654..cbe0c9da5b9 100644 --- a/packages/smooth_app/lib/l10n/app_en.arb +++ b/packages/smooth_app/lib/l10n/app_en.arb @@ -837,6 +837,14 @@ "completed_basic_details_btn_text": "Complete basic details", "not_implemented_snackbar_text": "Not implemented yet", "category_picker_page_appbar_text": "Categories", + "edit_ingredients_extrait_ingredients_btn_text": "Extract ingredients", + "@edit_ingredients_extrait_ingredients_btn_text": { + "description": "Ingredients edition - Extract ingredients" + }, + "edit_ingredients_refresh_photo_btn_text": "Refresh photo", + "@edit_ingredients_refresh_photo_btn_text": { + "description": "Ingredients edition - Refresh photo" + }, "user_list_dialog_new_title": "New list of products", "@user_list_dialog_new_title": { "description": "Title of the 'new user list' dialog" @@ -881,4 +889,4 @@ "@user_list_name_error_same": { "description": "Validation error about the renamed name that is the same as the initial list name" } -} \ No newline at end of file +} diff --git a/packages/smooth_app/lib/pages/product/edit_ingredients_page.dart b/packages/smooth_app/lib/pages/product/edit_ingredients_page.dart index e09701d834a..21b3df3991a 100644 --- a/packages/smooth_app/lib/pages/product/edit_ingredients_page.dart +++ b/packages/smooth_app/lib/pages/product/edit_ingredients_page.dart @@ -8,24 +8,23 @@ import 'package:openfoodfacts/openfoodfacts.dart'; import 'package:provider/provider.dart'; import 'package:smooth_app/database/local_database.dart'; import 'package:smooth_app/database/product_query.dart'; +import 'package:smooth_app/generic_lib/buttons/smooth_action_button.dart'; import 'package:smooth_app/generic_lib/design_constants.dart'; import 'package:smooth_app/helpers/picture_capture_helper.dart'; import 'package:smooth_app/pages/image_crop_page.dart'; import 'package:smooth_app/pages/product/common/product_refresher.dart'; -import 'package:smooth_app/themes/smooth_theme.dart'; -import 'package:smooth_app/themes/theme_provider.dart'; /// Page for editing the ingredients of a product and the image of the /// ingredients. class EditIngredientsPage extends StatefulWidget { const EditIngredientsPage({ Key? key, - this.imageIngredientsUrl, required this.product, + this.refreshProductCallback, }) : super(key: key); final Product product; - final String? imageIngredientsUrl; + final Function(BuildContext)? refreshProductCallback; @override State createState() => _EditIngredientsPageState(); @@ -37,27 +36,13 @@ class _EditIngredientsPageState extends State { bool _updatingImage = false; bool _updatingIngredients = false; - static String _getIngredientsString(List? ingredients) { - return ingredients == null ? '' : ingredients.join(', '); - } - @override void initState() { super.initState(); - _controller.text = _getIngredientsString(widget.product.ingredients); + _controller.text = widget.product.ingredientsText ?? ''; } - @override - void didUpdateWidget(EditIngredientsPage oldWidget) { - super.didUpdateWidget(oldWidget); - final String productIngredients = - _getIngredientsString(widget.product.ingredients); - if (productIngredients != _controller.text) { - _controller.text = productIngredients; - } - } - - Future _onSubmitField(String string) async { + Future _onSubmitField() async { final User user = ProductQuery.getUser(); setState(() { @@ -65,7 +50,7 @@ class _EditIngredientsPageState extends State { }); try { - await _updateIngredientsText(string, user); + await _updateIngredientsText(_controller.text, user); } catch (error) { final AppLocalizations appLocalizations = AppLocalizations.of(context)!; _showError(appLocalizations.ingredients_editing_error); @@ -76,13 +61,13 @@ class _EditIngredientsPageState extends State { }); } - Future _onTapGetImage() async { + Future _onTapGetImage(bool isNewImage) async { setState(() { _updatingImage = true; }); try { - await _getImage(); + await _getImage(isNewImage); } catch (error) { final AppLocalizations appLocalizations = AppLocalizations.of(context)!; _showError(appLocalizations.ingredients_editing_image_error); @@ -108,26 +93,30 @@ class _EditIngredientsPageState extends State { // // Returns a Future that resolves successfully only if everything succeeds, // otherwise it will resolve with the relevant error. - Future _getImage() async { - final File? croppedImageFile = await startImageCropping(context); + Future _getImage(bool isNewImage) async { + bool isUploaded = true; + if (isNewImage) { + final File? croppedImageFile = await startImageCropping(context); + + // If the user cancels. + if (croppedImageFile == null) { + return; + } - // If the user cancels. - if (croppedImageFile == null) { - return; - } + // Update the image to load the new image file. + setState(() { + _imageProvider = FileImage(croppedImageFile); + }); - // Update the image to load the new image file. - setState(() { - _imageProvider = FileImage(croppedImageFile); - }); + isUploaded = await uploadCapturedPicture( + context, + barcode: widget.product.barcode!, + imageField: ImageField.INGREDIENTS, + imageUri: croppedImageFile.uri, + ); - final bool isUploaded = await uploadCapturedPicture( - context, - barcode: widget.product.barcode!, - imageField: ImageField.INGREDIENTS, - imageUri: croppedImageFile.uri, - ); - croppedImageFile.delete(); + croppedImageFile.delete(); + } if (!isUploaded) { throw Exception('Image could not be uploaded.'); @@ -152,8 +141,6 @@ class _EditIngredientsPageState extends State { setState(() { _controller.text = nextIngredients; }); - - await _updateIngredientsText(nextIngredients, user); } } @@ -165,7 +152,9 @@ class _EditIngredientsPageState extends State { localDatabase: localDatabase, product: widget.product, ); - if (!savedAndRefreshed) { + if (savedAndRefreshed) { + await widget.refreshProductCallback?.call(context); + } else { throw Exception("Couldn't save the product."); } } @@ -184,10 +173,11 @@ class _EditIngredientsPageState extends State { ), ); } else { - if (widget.imageIngredientsUrl != null) { + if (widget.product.imageIngredientsUrl != null) { children.add(ConstrainedBox( constraints: const BoxConstraints.expand(), - child: _buildZoomableImage(NetworkImage(widget.imageIngredientsUrl!)), + child: _buildZoomableImage( + NetworkImage(widget.product.imageIngredientsUrl!)), )); } else { children.add(Container(color: Colors.white)); @@ -201,7 +191,7 @@ class _EditIngredientsPageState extends State { } else { children.add(_EditIngredientsBody( controller: _controller, - imageIngredientsUrl: widget.imageIngredientsUrl, + imageIngredientsUrl: widget.product.imageIngredientsUrl, onTapGetImage: _onTapGetImage, onSubmitField: _onSubmitField, updatingIngredients: _updatingIngredients, @@ -251,125 +241,96 @@ class _EditIngredientsBody extends StatelessWidget { final TextEditingController controller; final bool updatingIngredients; final String? imageIngredientsUrl; - final Future Function() onTapGetImage; - final Future Function(String) onSubmitField; + final Future Function(bool) onTapGetImage; + final Future Function() onSubmitField; @override Widget build(BuildContext context) { - final ThemeProvider themeProvider = context.watch(); - final ThemeData darkTheme = SmoothTheme.getThemeData( - Brightness.dark, - themeProvider.colorTag, - ); final AppLocalizations appLocalizations = AppLocalizations.of(context)!; return Align( alignment: Alignment.bottomLeft, - child: Padding( - padding: const EdgeInsets.symmetric(horizontal: LARGE_SPACE), - child: Column( - mainAxisAlignment: MainAxisAlignment.end, - children: [ - Flexible( - flex: 1, - child: Align( - alignment: Alignment.bottomRight, - child: Padding( - padding: const EdgeInsets.only(bottom: LARGE_SPACE), - child: _ActionButtons( - getImage: onTapGetImage, - hasImage: imageIngredientsUrl != null, - ), + child: Column( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + Flexible( + flex: 1, + child: Align( + alignment: Alignment.bottomRight, + child: Padding( + padding: const EdgeInsets.only( + bottom: LARGE_SPACE, right: SMALL_SPACE), + child: SmoothActionButton( + text: + appLocalizations.edit_ingredients_refresh_photo_btn_text, + onPressed: () => onTapGetImage(true), ), ), ), - Flexible( - flex: 1, + ), + Flexible( + flex: 1, + child: SingleChildScrollView( child: Container( - color: Colors.black, - child: Theme( - data: darkTheme, - child: DefaultTextStyle( - style: const TextStyle(color: Colors.white), - child: Padding( - padding: const EdgeInsets.all(LARGE_SPACE), - child: Column( - children: [ - TextField( - enabled: !updatingIngredients, - controller: controller, - decoration: const InputDecoration( - border: OutlineInputBorder( - borderRadius: ANGULAR_BORDER_RADIUS, - ), - ), - maxLines: null, - textInputAction: TextInputAction.done, - onSubmitted: onSubmitField, + decoration: BoxDecoration( + color: Theme.of(context).colorScheme.background, + borderRadius: const BorderRadius.only( + topLeft: ANGULAR_RADIUS, + topRight: ANGULAR_RADIUS, + )), + child: Padding( + padding: const EdgeInsets.all(LARGE_SPACE), + child: Column( + children: [ + SmoothActionButton( + text: appLocalizations + .edit_ingredients_extrait_ingredients_btn_text, + onPressed: () => onTapGetImage(false), + ), + const SizedBox(height: MEDIUM_SPACE), + TextField( + enabled: !updatingIngredients, + controller: controller, + decoration: const InputDecoration( + border: OutlineInputBorder( + borderRadius: ANGULAR_BORDER_RADIUS, ), - Text(appLocalizations - .ingredients_editing_instructions), - ], + ), + maxLines: null, + textInputAction: TextInputAction.done, + onSubmitted: (_) => onSubmitField, ), - ), + const SizedBox(height: SMALL_SPACE), + Text(appLocalizations.ingredients_editing_instructions, + style: Theme.of(context).textTheme.caption), + const SizedBox(height: MEDIUM_SPACE), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + SmoothActionButton( + text: appLocalizations.cancel, + onPressed: () { + Navigator.pop(context); + }, + ), + const SizedBox(width: LARGE_SPACE), + SmoothActionButton( + text: appLocalizations.save, + onPressed: () async { + await onSubmitField(); + Navigator.pop(context); + }, + ), + ]), + const SizedBox(height: MEDIUM_SPACE), + ], ), ), ), ), - ], - ), + ), + ], ), ); } } - -/// The actions for the page in a row of FloatingActionButtons. -class _ActionButtons extends StatelessWidget { - const _ActionButtons({ - Key? key, - required this.hasImage, - required this.getImage, - }) : super(key: key); - - final bool hasImage; - final VoidCallback getImage; - - @override - Widget build(BuildContext context) { - final ColorScheme colorScheme = Theme.of(context).buttonTheme.colorScheme!; - final List children = hasImage - ? [ - FloatingActionButton.small( - tooltip: 'Retake photo', - backgroundColor: colorScheme.background, - foregroundColor: colorScheme.onBackground, - onPressed: getImage, - child: const Icon(Icons.refresh), - ), - const SizedBox(width: MEDIUM_SPACE), - FloatingActionButton.small( - tooltip: 'Confirm', - backgroundColor: colorScheme.primary, - foregroundColor: colorScheme.onPrimary, - onPressed: () { - Navigator.pop(context); - }, - child: const Icon(Icons.check), - ), - ] - : [ - FloatingActionButton.small( - tooltip: 'Take photo', - backgroundColor: colorScheme.background, - foregroundColor: colorScheme.onBackground, - onPressed: getImage, - child: const Icon(Icons.camera_alt), - ), - ]; - - return Row( - mainAxisAlignment: MainAxisAlignment.end, - children: children, - ); - } -} diff --git a/packages/smooth_app/lib/pages/product/new_product_page.dart b/packages/smooth_app/lib/pages/product/new_product_page.dart index b50d30bb522..79f7c2d6b52 100644 --- a/packages/smooth_app/lib/pages/product/new_product_page.dart +++ b/packages/smooth_app/lib/pages/product/new_product_page.dart @@ -238,9 +238,10 @@ class _ProductPageState extends State { List knowledgePanelWidgets = []; if (snapshot.hasData) { // Render all KnowledgePanels - knowledgePanelWidgets = - KnowledgePanelsBuilder(setState: () => setState(() {})) - .buildAll( + knowledgePanelWidgets = KnowledgePanelsBuilder( + setState: () => setState(() {}), + refreshProductCallback: _refreshProduct, + ).buildAll( snapshot.data!, context: context, product: _product,