Skip to content

Commit

Permalink
feat: 3919 - select languages for new product images (#4255)
Browse files Browse the repository at this point in the history
* feat: 3919 - select languages for new product images

New file:
* `add_new_product_helper.dart`: Helper classes for `AddNewProductPage` that was getting too big.

Impacted files:
* `add_basic_details_page.dart`: minor refactoring
* `add_new_product_page.dart`: moved code to new file `add_new_product_helper.dart`; refactored the trackers with new class `AnalyticsProductTracker`; refactored the access to image edit
* `add_simple_input_button.dart`: minor refactoring
* `crop_page.dart`: minor refactoring
* `edit_new_packagings.dart`: minor refactoring
* `edit_ocr_page.dart`: minor refactoring
* `edit_product_page.dart`: minor refactoring
* `image_crop_page.dart`: minor refactoring
* `image_field_extension.dart`: minor refactoring
* `nutrition_page_loaded.dart`: minor refactoring
* `product_cards_helper.dart`: minor refactoring
* `product_field_editor.dart`: minor refactoring
* `product_image_carousel.dart`: minor refactoring
* `product_image_carousel_item.dart`: renamed from `image_upload_card.dart`
* `product_image_gallery_view.dart`: minor refactoring
* `product_image_local_button.dart`: minor refactoring
* `product_image_server_button.dart`: minor refactoring
* `product_image_swipeable_view.dart`: minor refactoring
* `product_image_viewer.dart`: minor refactoring
* `product_refresher.dart`: minor refactoring
* `uploaded_image_gallery.dart`: minor refactoring

* feat: 3919 - merge conflict fixes

* feat: 3919 - conflict fix

---------

Co-authored-by: Pierre Slamich <pierre@openfoodfacts.org>
  • Loading branch information
monsieurtanuki and teolemon committed Aug 24, 2023
1 parent b84cd3c commit 75683db
Show file tree
Hide file tree
Showing 22 changed files with 508 additions and 331 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,12 @@ import 'package:smooth_app/pages/image_crop_page.dart';
import 'package:smooth_app/pages/product/product_image_gallery_view.dart';
import 'package:smooth_app/query/product_query.dart';

// TODO(monsieurtanuki): rename that class, like `ProductImageCarouselItem`
/// Displays a product image in the carousel: access to gallery, or new image.
///
/// If the image exists, it's displayed and a tap gives access to the gallery.
/// If not, a "add image" button is displayed.
class ImageUploadCard extends StatefulWidget {
const ImageUploadCard({
class ProductImageCarouselItem extends StatefulWidget {
const ProductImageCarouselItem({
required this.product,
required this.productImageData,
});
Expand All @@ -25,10 +24,11 @@ class ImageUploadCard extends StatefulWidget {
final ProductImageData productImageData;

@override
State<ImageUploadCard> createState() => _ImageUploadCardState();
State<ProductImageCarouselItem> createState() =>
_ProductImageCarouselItemState();
}

class _ImageUploadCardState extends State<ImageUploadCard> {
class _ProductImageCarouselItemState extends State<ProductImageCarouselItem> {
@override
Widget build(BuildContext context) {
final Size screenSize = MediaQuery.of(context).size;
Expand All @@ -47,6 +47,7 @@ class _ImageUploadCardState extends State<ImageUploadCard> {
barcode: widget.product.barcode!,
imageField: widget.productImageData.imageField,
language: ProductQuery.getLanguage(),
isLoggedInMandatory: true,
),
icon: const Icon(Icons.add_a_photo),
label: Text(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import 'package:flutter/material.dart';
import 'package:openfoodfacts/openfoodfacts.dart';
import 'package:smooth_app/cards/data_cards/image_upload_card.dart';
import 'package:smooth_app/cards/data_cards/product_image_carousel_item.dart';
import 'package:smooth_app/data_models/product_image_data.dart';
import 'package:smooth_app/helpers/product_cards_helper.dart';
import 'package:smooth_app/query/product_query.dart';
Expand All @@ -22,6 +22,7 @@ class ProductImageCarousel extends StatelessWidget {
final List<ProductImageData> productImagesData = getProductMainImagesData(
product,
ProductQuery.getLanguage(),
includeOther: true,
);
return SizedBox(
height: height,
Expand All @@ -35,7 +36,7 @@ class ProductImageCarousel extends StatelessWidget {
return Container(
margin: const EdgeInsets.fromLTRB(0, 0, 5, 0),
decoration: const BoxDecoration(color: Colors.black12),
child: ImageUploadCard(
child: ProductImageCarouselItem(
product: product,
productImageData: data,
),
Expand Down
2 changes: 2 additions & 0 deletions packages/smooth_app/lib/helpers/image_field_extension.dart
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,7 @@ extension ImageFieldSmoothieExtension on ImageField {
Widget getPhotoButton(
final BuildContext context,
final Product product,
final bool isLoggedInMandatory,
) =>
SmoothLargeButtonWithIcon(
onPressed: () async => Navigator.push(
Expand All @@ -127,6 +128,7 @@ extension ImageFieldSmoothieExtension on ImageField {
builder: (_) => ProductImageSwipeableView.imageField(
imageField: this,
product: product,
isLoggedInMandatory: isLoggedInMandatory,
),
),
),
Expand Down
2 changes: 1 addition & 1 deletion packages/smooth_app/lib/helpers/product_cards_helper.dart
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,7 @@ Widget addPanelButton(
List<ProductImageData> getProductMainImagesData(
final Product product,
final OpenFoodFactsLanguage language, {
final bool includeOther = true,
required final bool includeOther,
}) {
final List<ImageField> imageFields = List<ImageField>.of(
ImageFieldSmoothieExtension.orderedMain,
Expand Down
11 changes: 11 additions & 0 deletions packages/smooth_app/lib/pages/crop_page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import 'package:smooth_app/helpers/analytics_helper.dart';
import 'package:smooth_app/helpers/database_helper.dart';
import 'package:smooth_app/helpers/image_compute_container.dart';
import 'package:smooth_app/helpers/image_field_extension.dart';
import 'package:smooth_app/pages/product/common/product_refresher.dart';
import 'package:smooth_app/pages/product/edit_image_button.dart';
import 'package:smooth_app/pages/product/may_exit_page_helper.dart';
import 'package:smooth_app/widgets/smooth_app_bar.dart';
Expand All @@ -34,6 +35,7 @@ class CropPage extends StatefulWidget {
required this.imageField,
required this.language,
required this.initiallyDifferent,
required this.isLoggedInMandatory,
this.imageId,
this.initialCropRect,
this.initialRotation,
Expand All @@ -56,6 +58,8 @@ class CropPage extends StatefulWidget {

final CropRotation? initialRotation;

final bool isLoggedInMandatory;

@override
State<CropPage> createState() => _CropPageState();
}
Expand Down Expand Up @@ -380,6 +384,13 @@ class _CropPageState extends State<CropPage> {
}

Future<bool> _saveFileAndExit() async {
if (!await ProductRefresher().checkIfLoggedIn(
context,
isLoggedInMandatory: widget.isLoggedInMandatory,
)) {
return false;
}

setState(
() => _progress = AppLocalizations.of(context).crop_page_action_saving,
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,13 @@ class UploadedImageGallery extends StatelessWidget {
required this.imageIds,
required this.imageField,
required this.language,
required this.isLoggedInMandatory,
});

final String barcode;
final List<int> imageIds;
final ImageField imageField;
final bool isLoggedInMandatory;

/// Language for which we'll save the cropped image.
final OpenFoodFactsLanguage language;
Expand Down Expand Up @@ -83,6 +85,7 @@ class UploadedImageGallery extends StatelessWidget {
imageId: imageId,
initiallyDifferent: true,
language: language,
isLoggedInMandatory: isLoggedInMandatory,
),
fullscreenDialog: true,
),
Expand Down
2 changes: 2 additions & 0 deletions packages/smooth_app/lib/pages/image_crop_page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,7 @@ Future<File?> confirmAndUploadNewPicture(
required final ImageField imageField,
required final String barcode,
required final OpenFoodFactsLanguage language,
required final bool isLoggedInMandatory,
}) async {
XFile? croppedPhoto;
try {
Expand Down Expand Up @@ -262,6 +263,7 @@ Future<File?> confirmAndUploadNewPicture(
inputFile: File(croppedPhoto!.path),
initiallyDifferent: true,
language: language,
isLoggedInMandatory: isLoggedInMandatory,
),
fullscreenDialog: true,
),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import 'package:smooth_app/widgets/smooth_scaffold.dart';
class AddBasicDetailsPage extends StatefulWidget {
const AddBasicDetailsPage(
this.product, {
this.isLoggedInMandatory = true,
required this.isLoggedInMandatory,
});

final Product product;
Expand Down Expand Up @@ -200,14 +200,14 @@ class _AddBasicDetailsPageState extends State<AddBasicDetailsPage> {
}
}

if (widget.isLoggedInMandatory) {
if (!mounted) {
return false;
}
final bool loggedIn = await ProductRefresher().checkIfLoggedIn(context);
if (!loggedIn) {
return false;
}
if (!mounted) {
return false;
}
if (!await ProductRefresher().checkIfLoggedIn(
context,
isLoggedInMandatory: widget.isLoggedInMandatory,
)) {
return false;
}

AnalyticsHelper.trackProductEdit(
Expand Down
202 changes: 202 additions & 0 deletions packages/smooth_app/lib/pages/product/add_new_product_helper.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,202 @@
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/database/transient_file.dart';
import 'package:smooth_app/generic_lib/buttons/smooth_large_button_with_icon.dart';
import 'package:smooth_app/generic_lib/design_constants.dart';
import 'package:smooth_app/generic_lib/svg_icon_chip.dart';
import 'package:smooth_app/helpers/analytics_helper.dart';
import 'package:smooth_app/helpers/product_cards_helper.dart';
import 'package:smooth_app/pages/product/product_field_editor.dart';
import 'package:smooth_app/query/product_query.dart';

/// Tracks (only the first time) when a [check] is true.
class AnalyticsProductTracker {
AnalyticsProductTracker({
required this.analyticsEvent,
required this.barcode,
required this.check,
});

final AnalyticsEvent analyticsEvent;
final String barcode;
final bool Function() check;

bool _already = false;

void track() {
if (_already) {
return;
}
if (!check()) {
return;
}
_already = true;
AnalyticsHelper.trackEvent(analyticsEvent, barcode: barcode);
}
}

/// Card title for "Add new product" page.
class AddNewProductTitle extends StatelessWidget {
const AddNewProductTitle(
this.label, {
this.maxLines,
});

final String label;
final int? maxLines;

@override
Widget build(BuildContext context) => Text(
label,
style: Theme.of(context).textTheme.displaySmall,
maxLines: maxLines,
);
}

/// Card subtitle for "Add new product" page.
class AddNewProductSubTitle extends StatelessWidget {
const AddNewProductSubTitle(this.label);

final String label;

@override
Widget build(BuildContext context) => Text(label);
}

/// Standard button in the "Add new product" page.
class AddNewProductButton extends StatelessWidget {
const AddNewProductButton(
this.label,
this.iconData,
this.onPressed, {
required this.done,
});

final String label;
final IconData iconData;
final VoidCallback? onPressed;
final bool done;

static const IconData doneIconData = Icons.check;
static const IconData todoIconData = Icons.add;
static const IconData cameraIconData = Icons.camera_alt;

@override
Widget build(BuildContext context) {
final ThemeData themeData = Theme.of(context);
final bool dark = themeData.brightness == Brightness.dark;
final Color? darkGrey = Colors.grey[700];
final Color? lightGrey = Colors.grey[300];
return Padding(
padding: const EdgeInsets.symmetric(vertical: SMALL_SPACE),
child: SmoothLargeButtonWithIcon(
text: label,
icon: iconData,
onPressed: onPressed,
trailing: Icons.edit,
backgroundColor: onPressed == null
? (dark ? darkGrey : lightGrey)
: done
? Colors.green[700]
: themeData.colorScheme.secondary,
foregroundColor: onPressed == null
? (dark ? lightGrey : darkGrey)
: done
? Colors.white
: themeData.colorScheme.onSecondary,
),
);
}
}

/// Standard "editor" button in the "Add new product" page.
class AddNewProductEditorButton extends StatelessWidget {
const AddNewProductEditorButton(
this.product,
this.editor, {
this.forceIconData,
this.disabled = false,
required this.isLoggedInMandatory,
});

final Product product;
final ProductFieldEditor editor;
final IconData? forceIconData;
final bool disabled;
final bool isLoggedInMandatory;

@override
Widget build(BuildContext context) {
final bool done = editor.isPopulated(product);
return AddNewProductButton(
editor.getLabel(AppLocalizations.of(context)),
forceIconData ??
(done
? AddNewProductButton.doneIconData
: AddNewProductButton.todoIconData),
disabled
? null
: () async => editor.edit(
context: context,
product: product,
isLoggedInMandatory: isLoggedInMandatory,
),
done: done,
);
}
}

class AddNewProductScoreIcon extends StatelessWidget {
const AddNewProductScoreIcon({
required this.iconUrl,
required this.defaultIconUrl,
});

final String? iconUrl;
final String defaultIconUrl;

@override
Widget build(BuildContext context) => SvgIconChip(
iconUrl ?? defaultIconUrl,
height: MediaQuery.of(context).size.height * .2,
);
}

/// Helper for the "Add new product" page.
class AddNewProductHelper {
bool isMainImagePopulated(
final ProductImageData productImageData,
final String barcode,
) =>
TransientFile.fromProductImageData(
productImageData,
barcode,
ProductQuery.getLanguage(),
).getImageProvider() !=
null;

bool isOneMainImagePopulated(final Product product) {
final List<ProductImageData> productImagesData = getProductMainImagesData(
product,
ProductQuery.getLanguage(),
includeOther: false,
);
for (final ProductImageData productImageData in productImagesData) {
if (isMainImagePopulated(productImageData, product.barcode!)) {
return true;
}
}
return false;
}
}

/// Possible actions on that page.
enum EditProductAction {
openPage,
leaveEmpty,
ingredients,
category,
nutritionFacts;
}
Loading

0 comments on commit 75683db

Please sign in to comment.