diff --git a/packages/smooth_app/lib/cards/data_cards/image_upload_card.dart b/packages/smooth_app/lib/cards/data_cards/image_upload_card.dart index 6ae3246a6a0..f3413898282 100644 --- a/packages/smooth_app/lib/cards/data_cards/image_upload_card.dart +++ b/packages/smooth_app/lib/cards/data_cards/image_upload_card.dart @@ -114,8 +114,7 @@ class _ImageUploadCardState extends State { context, MaterialPageRoute( builder: (BuildContext context) => ProductImageGalleryView( - imagesData: widget.allProductImagesData, - barcode: widget.product.barcode, + product: widget.product, ), ), ); diff --git a/packages/smooth_app/lib/pages/product/edit_product_page.dart b/packages/smooth_app/lib/pages/product/edit_product_page.dart index 45124bf3954..9c97a252729 100644 --- a/packages/smooth_app/lib/pages/product/edit_product_page.dart +++ b/packages/smooth_app/lib/pages/product/edit_product_page.dart @@ -6,7 +6,6 @@ import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:openfoodfacts/openfoodfacts.dart'; import 'package:provider/provider.dart'; -import 'package:smooth_app/data_models/product_image_data.dart'; import 'package:smooth_app/data_models/up_to_date_product_provider.dart'; import 'package:smooth_app/database/local_database.dart'; import 'package:smooth_app/generic_lib/design_constants.dart'; @@ -160,16 +159,13 @@ class _EditProductPageState extends State { if (!await ProductRefresher().checkIfLoggedIn(context)) { return; } - final List allProductImagesData = - getProductMainImagesData(_product, appLocalizations); // TODO(monsieurtanuki): careful, waiting for pop'ed value final bool? refreshed = await Navigator.push( context, MaterialPageRoute( builder: (BuildContext context) => ProductImageGalleryView( - imagesData: allProductImagesData, - barcode: _product.barcode, + product: _product, ), fullscreenDialog: true, ), diff --git a/packages/smooth_app/lib/pages/product/product_image_gallery_view.dart b/packages/smooth_app/lib/pages/product/product_image_gallery_view.dart index 310f9072555..12c4333effc 100644 --- a/packages/smooth_app/lib/pages/product/product_image_gallery_view.dart +++ b/packages/smooth_app/lib/pages/product/product_image_gallery_view.dart @@ -4,13 +4,19 @@ import 'package:collection/collection.dart'; import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:openfoodfacts/openfoodfacts.dart'; +import 'package:provider/provider.dart'; import 'package:smooth_app/data_models/product_image_data.dart'; +import 'package:smooth_app/data_models/up_to_date_product_provider.dart'; +import 'package:smooth_app/database/local_database.dart'; import 'package:smooth_app/generic_lib/design_constants.dart'; +import 'package:smooth_app/generic_lib/duration_constants.dart'; import 'package:smooth_app/generic_lib/widgets/images/smooth_images_sliver_grid.dart'; import 'package:smooth_app/generic_lib/widgets/images/smooth_images_sliver_list.dart'; import 'package:smooth_app/generic_lib/widgets/smooth_back_button.dart'; import 'package:smooth_app/helpers/picture_capture_helper.dart'; +import 'package:smooth_app/helpers/product_cards_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/pages/product/product_image_viewer.dart'; import 'package:smooth_app/query/product_query.dart'; import 'package:smooth_app/widgets/smooth_scaffold.dart'; @@ -22,12 +28,10 @@ import 'package:smooth_app/widgets/smooth_scaffold.dart'; /// class ProductImageGalleryView extends StatefulWidget { const ProductImageGalleryView({ - required this.imagesData, - this.barcode, + required this.product, }); - final String? barcode; - final List imagesData; + final Product product; @override State createState() => @@ -35,42 +39,15 @@ class ProductImageGalleryView extends StatefulWidget { } class _ProductImageGalleryViewState extends State { - late final Map selectedImages; + Map _selectedImages = + ?>{}; - final Map unselectedImages = + final Map _unselectedImages = {}; bool _isRefreshed = false; bool _isLoadingMore = true; - @override - void initState() { - selectedImages = Map.fromIterables( - widget.imagesData, - widget.imagesData.map(_provideImage), - ); - - _getProductImages(widget.barcode!) - .then((Iterable? loadedData) { - if (loadedData == null) { - return; - } - - final Map?> newMap = - Map.fromIterables( - loadedData, - loadedData.map(_provideImage), - ); - - setState(() { - unselectedImages.addAll(newMap); - _isLoadingMore = false; - }); - }); - - super.initState(); - } - ImageProvider? _provideImage(ProductImageData imageData) => imageData.imageUrl == null ? null : NetworkImage(imageData.imageUrl!); @@ -78,40 +55,87 @@ class _ProductImageGalleryViewState extends State { Widget build(BuildContext context) { final AppLocalizations appLocalizations = AppLocalizations.of(context); final ThemeData theme = Theme.of(context); + return Consumer( + builder: ( + BuildContext context, + UpToDateProductProvider provider, + Widget? child, + ) { + Product product = widget.product; - // When there is no data there should be no way to get to this page. - if (selectedImages.isEmpty) { - return SmoothScaffold( - body: Center( - child: Text(appLocalizations.error), - ), - ); - } - return SmoothScaffold( - appBar: AppBar( - title: Text(appLocalizations.edit_product_form_item_photos_title), - leading: SmoothBackButton( - onPressed: () => Navigator.maybePop(context, _isRefreshed), - ), - ), - body: Scrollbar( - child: CustomScrollView( - slivers: [ - _buildTitle(appLocalizations.selected_images, theme: theme), - SmoothImagesSliverList( - imagesData: selectedImages, - onTap: (ProductImageData data, _) => - data.imageUrl != null ? _openImage(data) : _newImage(data), + final Product? refreshedProduct = provider.get(product); + if (refreshedProduct != null) { + product = refreshedProduct; + } + final List allProductImagesData = + getProductMainImagesData(product, appLocalizations); + _selectedImages = Map.fromIterables( + allProductImagesData, + allProductImagesData.map(_provideImage), + ); + + _getProductImages().then( + (Iterable? loadedData) { + if (loadedData == null) { + return; + } + + final Map?> newMap = + Map.fromIterables( + loadedData, + loadedData.map(_provideImage), + ); + if (mounted) { + setState( + () { + _unselectedImages.clear(); + _unselectedImages.addAll(newMap); + _isLoadingMore = false; + }, + ); + } + }, + ); + if (_selectedImages.isEmpty) { + return SmoothScaffold( + body: Center( + child: Text(appLocalizations.error), ), - _buildTitle(appLocalizations.all_images, theme: theme), - SmoothImagesSliverGrid( - imagesData: unselectedImages, - loading: _isLoadingMore, - onTap: (ProductImageData data, _) => _openImage(data), + ); + } + return SmoothScaffold( + appBar: AppBar( + title: Text(appLocalizations.edit_product_form_item_photos_title), + leading: SmoothBackButton( + onPressed: () => Navigator.maybePop(context, _isRefreshed), ), - ], - ), - ), + ), + body: RefreshIndicator( + onRefresh: () async { + _refreshProduct(context); + }, + child: Scrollbar( + child: CustomScrollView( + slivers: [ + _buildTitle(appLocalizations.selected_images, theme: theme), + SmoothImagesSliverList( + imagesData: _selectedImages, + onTap: (ProductImageData data, _) => data.imageUrl != null + ? _openImage(data) + : _newImage(data), + ), + _buildTitle(appLocalizations.all_images, theme: theme), + SmoothImagesSliverGrid( + imagesData: _unselectedImages, + loading: _isLoadingMore, + onTap: (ProductImageData data, _) => _openImage(data), + ), + ], + ), + ), + ), + ); + }, ); } @@ -126,37 +150,59 @@ class _ProductImageGalleryViewState extends State { ), ); + Future _refreshProduct(BuildContext context) async { + final LocalDatabase localDatabase = context.read(); + final bool success = await ProductRefresher().fetchAndRefresh( + context: context, + localDatabase: localDatabase, + barcode: widget.product.barcode!, + ); + if (mounted && success) { + final AppLocalizations appLocalizations = AppLocalizations.of(context); + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text(appLocalizations.product_refreshed), + duration: SnackBarDuration.short, + ), + ); + } + return success; + } + Future _openImage(ProductImageData imageData) async => Navigator.push( - context, - MaterialPageRoute( - builder: (_) => ProductImageViewer( - barcode: widget.barcode!, - imageData: imageData, - ), - )); + context, + MaterialPageRoute( + builder: (_) => ProductImageViewer( + barcode: widget.product.barcode!, + imageData: imageData, + ), + ), + ); Future _newImage(ProductImageData data) async { final File? croppedImageFile = await startImageCropping(context); if (croppedImageFile == null) { return; } - setState(() { - final FileImage fileImage = FileImage(croppedImageFile); - if (selectedImages.containsKey(data)) { - selectedImages[data] = fileImage; - } else if (unselectedImages.containsKey(data)) { - unselectedImages[data] = fileImage; - } else { - throw ArgumentError('Could not find the type of $data'); - } - }); + if (mounted) { + setState(() { + final FileImage fileImage = FileImage(croppedImageFile); + if (_selectedImages.containsKey(data)) { + _selectedImages[data] = fileImage; + } else if (_unselectedImages.containsKey(data)) { + _unselectedImages[data] = fileImage; + } else { + throw ArgumentError('Could not find the type of $data'); + } + }); + } if (!mounted) { return; } final bool isUploaded = await uploadCapturedPicture( context, - barcode: widget.barcode!, + barcode: widget.product.barcode!, imageField: data.imageField, imageUri: croppedImageFile.uri, ); @@ -174,15 +220,15 @@ class _ProductImageGalleryViewState extends State { ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text(message), - duration: const Duration(seconds: 3), + duration: SnackBarDuration.medium, ), ); } } - Future?> _getProductImages(String barcode) async { + Future?> _getProductImages() async { final ProductQueryConfiguration configuration = ProductQueryConfiguration( - barcode, + widget.product.barcode!, fields: [ProductField.IMAGES], language: ProductQuery.getLanguage(), country: ProductQuery.getCountry(), @@ -199,12 +245,12 @@ class _ProductImageGalleryViewState extends State { return null; } - final Product? product = result.product; - if (product == null || product.images == null) { + final Product? resultProduct = result.product; + if (resultProduct == null || resultProduct.images == null) { return null; } - return _deduplicateImages(product.images!).map(_getProductImageData); + return _deduplicateImages(resultProduct.images!).map(_getProductImageData); } /// Groups the list of [ProductImage] by [ProductImage.imgid] @@ -222,6 +268,6 @@ class _ProductImageGalleryViewState extends State { // TODO(VaiTon): i18n title: image.imgid ?? '', buttonText: image.imgid ?? '', - imageUrl: ImageHelper.buildUrl(widget.barcode, image), + imageUrl: ImageHelper.buildUrl(widget.product.barcode, image), ); }