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 3608b7bab72..a77108b43d4 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 @@ -1,27 +1,23 @@ import 'dart:io'; import 'package:flutter/material.dart'; -import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:openfoodfacts/openfoodfacts.dart'; +import 'package:smooth_app/data_models/product_image_data.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/product_image_page.dart'; +import 'package:smooth_app/pages/product/product_image_gallery_view.dart'; class ImageUploadCard extends StatefulWidget { const ImageUploadCard({ required this.product, - required this.imageField, - this.imageUrl, - this.title, - required this.buttonText, + required this.productImageData, + required this.allProductImagesData, required this.onUpload, }); final Product product; - final ImageField imageField; - final String? imageUrl; - final String? title; - final String buttonText; + final ProductImageData productImageData; + final List allProductImagesData; final Function(BuildContext) onUpload; @override @@ -53,7 +49,7 @@ class _ImageUploadCardState extends State { context, barcode: widget.product .barcode!, //Probably throws an error, but this is not a big problem when we got a product without a barcode - imageField: widget.imageField, + imageField: widget.productImageData.imageField, imageUri: croppedImageFile.uri, ); croppedImageFile.delete(); @@ -68,10 +64,9 @@ class _ImageUploadCardState extends State { // We can already have an _imageProvider for a file that is going to be uploaded // or an imageUrl for a network image // or no image yet - final AppLocalizations appLocalizations = AppLocalizations.of(context)!; - - if ((_imageProvider == null) && (widget.imageUrl != null)) { - _imageProvider = NetworkImage(widget.imageUrl!); + if ((_imageProvider == null) && + (widget.productImageData.imageUrl != null)) { + _imageProvider = NetworkImage(widget.productImageData.imageUrl!); } if (_imageProvider != null) { @@ -85,19 +80,18 @@ class _ImageUploadCardState extends State { if (_imageFullProvider == null) { final String _imageFullUrl = - widget.imageUrl!.replaceAll('.400.', '.full.'); + widget.productImageData.imageUrl!.replaceAll('.400.', '.full.'); _imageFullProvider = NetworkImage(_imageFullUrl); } Navigator.push( context, MaterialPageRoute( - builder: (BuildContext context) => ProductImagePage( - product: widget.product, - imageField: widget.imageField, - imageProvider: _imageFullProvider!, - title: widget.title ?? appLocalizations.image, - buttonText: widget.buttonText), + builder: (BuildContext context) => ProductImageGalleryView( + productImageData: widget.productImageData, + allProductImagesData: widget.allProductImagesData, + title: widget.productImageData.title, + ), ), ); }, @@ -106,7 +100,7 @@ class _ImageUploadCardState extends State { return ElevatedButton.icon( onPressed: _getImage, icon: const Icon(Icons.add_a_photo), - label: Text(widget.buttonText), + label: Text(widget.productImageData.buttonText), ); } } diff --git a/packages/smooth_app/lib/cards/product_cards/product_image_carousel.dart b/packages/smooth_app/lib/cards/product_cards/product_image_carousel.dart index 5300f5acf62..63e9117065a 100644 --- a/packages/smooth_app/lib/cards/product_cards/product_image_carousel.dart +++ b/packages/smooth_app/lib/cards/product_cards/product_image_carousel.dart @@ -3,6 +3,7 @@ import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:openfoodfacts/model/Product.dart'; import 'package:openfoodfacts/model/ProductImage.dart'; import 'package:smooth_app/cards/data_cards/image_upload_card.dart'; +import 'package:smooth_app/data_models/product_image_data.dart'; class ProductImageCarousel extends StatelessWidget { const ProductImageCarousel( @@ -18,46 +19,36 @@ class ProductImageCarousel extends StatelessWidget { @override Widget build(BuildContext context) { final AppLocalizations appLocalizations = AppLocalizations.of(context)!; - final List carouselItems = [ - ImageUploadCard( - product: product, + final List allProductImagesData = [ + ProductImageData( imageField: ImageField.FRONT, imageUrl: product.imageFrontUrl, title: appLocalizations.product, buttonText: appLocalizations.front_photo, - onUpload: onUpload, ), - ImageUploadCard( - product: product, + ProductImageData( imageField: ImageField.INGREDIENTS, imageUrl: product.imageIngredientsUrl, title: appLocalizations.ingredients, buttonText: appLocalizations.ingredients_photo, - onUpload: onUpload, ), - ImageUploadCard( - product: product, + ProductImageData( imageField: ImageField.NUTRITION, imageUrl: product.imageNutritionUrl, title: appLocalizations.nutrition, buttonText: appLocalizations.nutrition_facts_photo, - onUpload: onUpload, ), - ImageUploadCard( - product: product, + ProductImageData( imageField: ImageField.PACKAGING, imageUrl: product.imagePackagingUrl, title: appLocalizations.packaging_information, buttonText: appLocalizations.packaging_information_photo, - onUpload: onUpload, ), - ImageUploadCard( - product: product, + ProductImageData( imageField: ImageField.OTHER, imageUrl: null, title: appLocalizations.more_photos, buttonText: appLocalizations.more_photos, - onUpload: onUpload, ), ]; @@ -66,12 +57,17 @@ class ProductImageCarousel extends StatelessWidget { child: ListView( // This next line does the trick. scrollDirection: Axis.horizontal, - children: carouselItems + children: allProductImagesData .map( - (ImageUploadCard item) => Container( + (ProductImageData item) => Container( margin: const EdgeInsets.fromLTRB(0, 0, 5, 0), decoration: const BoxDecoration(color: Colors.black12), - child: item, + child: ImageUploadCard( + product: product, + productImageData: item, + allProductImagesData: allProductImagesData, + onUpload: onUpload, + ), ), ) .toList(), diff --git a/packages/smooth_app/lib/data_models/product_image_data.dart b/packages/smooth_app/lib/data_models/product_image_data.dart new file mode 100644 index 00000000000..1615c4402dc --- /dev/null +++ b/packages/smooth_app/lib/data_models/product_image_data.dart @@ -0,0 +1,15 @@ +import 'package:openfoodfacts/model/ProductImage.dart'; + +class ProductImageData { + const ProductImageData({ + required this.imageField, + required this.title, + required this.buttonText, + this.imageUrl, + }); + + final ImageField imageField; + final String title; + final String buttonText; + final String? imageUrl; +} 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 new file mode 100644 index 00000000000..0098a5b5ef6 --- /dev/null +++ b/packages/smooth_app/lib/pages/product/product_image_gallery_view.dart @@ -0,0 +1,106 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; +import 'package:photo_view/photo_view.dart'; +import 'package:photo_view/photo_view_gallery.dart'; +import 'package:smooth_app/data_models/product_image_data.dart'; +import 'package:smooth_app/generic_lib/widgets/smooth_gauge.dart'; + +class ProductImageGalleryView extends StatefulWidget { + const ProductImageGalleryView({ + required this.title, + required this.productImageData, + required this.allProductImagesData, + }); + + final String title; + final ProductImageData productImageData; + final List allProductImagesData; + + @override + State createState() => + _ProductImageGalleryViewState(); +} + +class _ProductImageGalleryViewState extends State { + late final PageController _controller; + late final List images = []; + late String title; + + @override + void initState() { + title = widget.title; + + for (final ProductImageData element in widget.allProductImagesData) { + if (element.imageUrl != null) { + images.add(element); + } + } + + _controller = PageController( + initialPage: widget.allProductImagesData.indexOf( + images.firstWhere((ProductImageData element) => + element.imageUrl == widget.productImageData.imageUrl), + ), + ); + super.initState(); + } + + @override + Widget build(BuildContext context) { + final AppLocalizations appLocalizations = AppLocalizations.of(context)!; + + //When all are empty there shouldn't be a way to access this page + if (images.isEmpty) { + return Scaffold( + body: Center( + child: Text(appLocalizations.error), + ), + ); + } + return Scaffold( + extendBodyBehindAppBar: true, + appBar: AppBar( + backgroundColor: Colors.transparent, + elevation: 0, + title: Text(title), + ), + backgroundColor: Colors.black, + body: PhotoViewGallery.builder( + pageController: _controller, + scrollPhysics: const BouncingScrollPhysics(), + builder: (BuildContext context, int index) { + return PhotoViewGalleryPageOptions( + imageProvider: NetworkImage(images[index].imageUrl!), + initialScale: PhotoViewComputedScale.contained * 0.8, + minScale: PhotoViewComputedScale.contained * 0.8, + maxScale: PhotoViewComputedScale.covered * 1.1, + heroAttributes: + PhotoViewHeroAttributes(tag: images[index].imageUrl!), + ); + }, + itemCount: images.length, + loadingBuilder: + (final BuildContext context, final ImageChunkEvent? event) { + return Center( + child: SmoothGauge( + color: Theme.of(context).colorScheme.onBackground, + value: event == null || + event.expectedTotalBytes == null || + event.expectedTotalBytes == 0 + ? 0 + : event.cumulativeBytesLoaded / event.expectedTotalBytes!, + ), + ); + }, + backgroundDecoration: const BoxDecoration( + color: Colors.black, + ), + onPageChanged: (int index) { + setState(() { + title = images[index].title; + }); + }, + ), + ); + } +} diff --git a/packages/smooth_app/lib/pages/product/product_image_page.dart b/packages/smooth_app/lib/pages/product/product_image_page.dart deleted file mode 100644 index 87e6a18ae7a..00000000000 --- a/packages/smooth_app/lib/pages/product/product_image_page.dart +++ /dev/null @@ -1,44 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:openfoodfacts/openfoodfacts.dart'; -import 'package:photo_view/photo_view.dart'; -import 'package:smooth_app/generic_lib/widgets/smooth_gauge.dart'; - -class ProductImagePage extends StatelessWidget { - const ProductImagePage({ - required this.product, - required this.imageField, - required this.imageProvider, - required this.title, - required this.buttonText, - }); - - final Product product; - final ImageField imageField; - final ImageProvider imageProvider; - final String title; - final String buttonText; - - @override - Widget build(BuildContext context) => Scaffold( - appBar: AppBar( - title: Text(title), - ), - body: PhotoView( - loadingBuilder: - (final BuildContext context, final ImageChunkEvent? event) => - Center( - child: SmoothGauge( - color: Theme.of(context).colorScheme.onBackground, - value: event == null || - event.expectedTotalBytes == null || - event.expectedTotalBytes == 0 - ? 0 - : event.cumulativeBytesLoaded / event.expectedTotalBytes!, - ), - ), - imageProvider: imageProvider, - minScale: PhotoViewComputedScale - .contained, // Makes it easy to dezoom until the photo is contained in the screen - ), - ); -}