Skip to content

Commit

Permalink
feat: 3771 - ingredient page now uses the same local/server buttons a…
Browse files Browse the repository at this point in the history
…s gallery (#3852)

New files:
* `product_image_local_button.dart`: Button asking for a "local" photo (new from camera, existing from gallery). The code used to be in `product_image_view.dart`.
* `product_image_server_button.dart`: Button asking for a "server" photo (taken from what was already uploaded). The code used to be in `product_image_view.dart`.

Impacted files:
* `edit_ingredients_page.dart`: minor refactoring
* `ocr_widget.dart`: now using new buttons `ProductImageLocalButton` and `ProductImageServerButton`
* `product_image_view.dart`: now using new buttons `ProductImageLocalButton` and `ProductImageServerButton`, whose code used to be there.
  • Loading branch information
monsieurtanuki committed Apr 6, 2023
1 parent d5a4d27 commit d2191ee
Show file tree
Hide file tree
Showing 5 changed files with 179 additions and 102 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ import 'package:smooth_app/generic_lib/design_constants.dart';
import 'package:smooth_app/generic_lib/loading_dialog.dart';
import 'package:smooth_app/helpers/analytics_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/ocr_helper.dart';
import 'package:smooth_app/pages/product/ocr_widget.dart';
import 'package:smooth_app/widgets/smooth_app_bar.dart';
Expand Down Expand Up @@ -61,13 +60,6 @@ class _EditOcrPageState extends State<EditOcrPage> {
Future<void> _onSubmitField(ImageField imageField) async =>
_updateText(_controller.text, imageField);

/// Opens a page to upload a new image.
Future<void> _newImage() async => confirmAndUploadNewPicture(
this,
barcode: _product.barcode!,
imageField: _helper.getImageField(),
);

/// Extracts data with OCR from the image stored on the server.
///
/// When done, populates the related page field.
Expand Down Expand Up @@ -147,7 +139,6 @@ class _EditOcrPageState extends State<EditOcrPage> {
_getImageWidget(productImageData),
OcrWidget(
controller: _controller,
onTapNewImage: _newImage,
onTapExtractData: _extractData,
onSubmitField: _onSubmitField,
productImageData: productImageData,
Expand Down
44 changes: 32 additions & 12 deletions packages/smooth_app/lib/pages/product/ocr_widget.dart
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import 'package:smooth_app/helpers/product_cards_helper.dart';
import 'package:smooth_app/pages/image_crop_page.dart';
import 'package:smooth_app/pages/product/explanation_widget.dart';
import 'package:smooth_app/pages/product/ocr_helper.dart';
import 'package:smooth_app/pages/product/product_image_local_button.dart';
import 'package:smooth_app/pages/product/product_image_server_button.dart';

/// Widget dedicated to OCR, with 3 actions: upload image, extract data, save.
///
Expand All @@ -17,15 +19,13 @@ class OcrWidget extends StatefulWidget {
const OcrWidget({
required this.controller,
required this.onSubmitField,
required this.onTapNewImage,
required this.onTapExtractData,
required this.productImageData,
required this.product,
required this.helper,
});

final TextEditingController controller;
final Future<void> Function() onTapNewImage;
final Future<void> Function() onTapExtractData;
final Future<void> Function(ImageField) onSubmitField;
final ProductImageData productImageData;
Expand Down Expand Up @@ -55,16 +55,36 @@ class _OcrWidgetState extends State<OcrWidget> {
start: LARGE_SPACE,
end: LARGE_SPACE,
),
child: SmoothActionButtonsBar(
positiveAction: SmoothActionButton(
text: (TransientFile.isImageAvailable(
widget.productImageData,
widget.product.barcode!,
))
? widget.helper.getActionRefreshPhoto(appLocalizations)
: appLocalizations.upload_image,
onPressed: () async => widget.onTapNewImage(),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisSize: MainAxisSize.max,
children: <Widget>[
Expanded(
child: Padding(
padding:
const EdgeInsets.symmetric(horizontal: SMALL_SPACE),
child: ProductImageServerButton(
barcode: widget.product.barcode!,
imageField: widget.helper.getImageField(),
),
),
),
Expanded(
child: Padding(
padding:
const EdgeInsets.symmetric(horizontal: SMALL_SPACE),
child: ProductImageLocalButton(
firstPhoto: !TransientFile.isImageAvailable(
widget.productImageData,
widget.product.barcode!,
),
barcode: widget.product.barcode!,
imageField: widget.helper.getImageField(),
),
),
),
],
),
),
),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:openfoodfacts/openfoodfacts.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/edit_image_button.dart';

/// Button asking for a "local" photo (new from camera, existing from gallery).
class ProductImageLocalButton extends StatefulWidget {
const ProductImageLocalButton({
required this.firstPhoto,
required this.barcode,
required this.imageField,
});

final bool firstPhoto;
final String barcode;
final ImageField imageField;

@override
State<ProductImageLocalButton> createState() =>
_ProductImageLocalButtonState();
}

class _ProductImageLocalButtonState extends State<ProductImageLocalButton> {
@override
Widget build(BuildContext context) {
final AppLocalizations appLocalizations = AppLocalizations.of(context);
return EditImageButton(
iconData: widget.firstPhoto ? Icons.add : Icons.add_a_photo,
label:
widget.firstPhoto ? appLocalizations.add : appLocalizations.capture,
onPressed: () async => _actionNewImage(context),
);
}

Future<void> _actionNewImage(final BuildContext context) async {
final bool loggedIn = await ProductRefresher().checkIfLoggedIn(context);
if (!loggedIn) {
return;
}
if (context.mounted) {
} else {
return;
}
await confirmAndUploadNewPicture(
this,
imageField: widget.imageField,
barcode: widget.barcode,
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:openfoodfacts/openfoodfacts.dart';
import 'package:smooth_app/generic_lib/dialogs/smooth_alert_dialog.dart';
import 'package:smooth_app/generic_lib/loading_dialog.dart';
import 'package:smooth_app/pages/image/uploaded_image_gallery.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/query/product_query.dart';

/// Button asking for a "server" photo (taken from what was already uploaded).
class ProductImageServerButton extends StatelessWidget {
const ProductImageServerButton({
required this.barcode,
required this.imageField,
});

final String barcode;
final ImageField imageField;

@override
Widget build(BuildContext context) => EditImageButton(
iconData: Icons.image_search,
label: AppLocalizations.of(context)
.edit_photo_select_existing_button_label,
onPressed: () async => _actionGallery(context),
);

Future<void> _actionGallery(final BuildContext context) async {
final AppLocalizations appLocalizations = AppLocalizations.of(context);
if (!context.mounted) {
return;
}
final bool loggedIn = await ProductRefresher().checkIfLoggedIn(context);
if (!loggedIn) {
return;
}
if (context.mounted) {
} else {
return;
}
final List<int>? result = await LoadingDialog.run<List<int>>(
future: OpenFoodAPIClient.getProductImageIds(
barcode,
user: ProductQuery.getUser(),
),
context: context,
title: appLocalizations.edit_photo_select_existing_download_label,
);
if (result == null) {
return;
}
if (context.mounted) {
} else {
return;
}
if (result.isEmpty) {
await showDialog<void>(
context: context,
builder: (BuildContext context) => SmoothAlertDialog(
body:
Text(appLocalizations.edit_photo_select_existing_downloaded_none),
actionsAxis: Axis.vertical,
positiveAction: SmoothActionButton(
text: appLocalizations.okay,
onPressed: () => Navigator.of(context).pop(),
),
),
);
return;
}
await Navigator.push<void>(
context,
MaterialPageRoute<void>(
builder: (BuildContext context) => UploadedImageGallery(
barcode: barcode,
imageIds: result,
imageField: imageField,
),
),
);
}
}
93 changes: 12 additions & 81 deletions packages/smooth_app/lib/pages/product/product_image_viewer.dart
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,13 @@ import 'package:smooth_app/database/local_database.dart';
import 'package:smooth_app/database/transient_file.dart';
import 'package:smooth_app/generic_lib/design_constants.dart';
import 'package:smooth_app/generic_lib/dialogs/smooth_alert_dialog.dart';
import 'package:smooth_app/generic_lib/loading_dialog.dart';
import 'package:smooth_app/generic_lib/widgets/picture_not_found.dart';
import 'package:smooth_app/helpers/product_cards_helper.dart';
import 'package:smooth_app/pages/image/uploaded_image_gallery.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/edit_image_button.dart';
import 'package:smooth_app/pages/product/product_image_local_button.dart';
import 'package:smooth_app/pages/product/product_image_server_button.dart';
import 'package:smooth_app/query/product_query.dart';
import 'package:smooth_app/tmp_crop_image/new_crop_page.dart';
import 'package:smooth_app/tmp_crop_image/rotation.dart';
Expand Down Expand Up @@ -104,15 +104,19 @@ class _ProductImageViewerState extends State<ProductImageViewer> {
Expanded(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: SMALL_SPACE),
child: _getGalleryButton(appLocalizations),
child: ProductImageServerButton(
barcode: _barcode,
imageField: widget.imageField,
),
),
),
Expanded(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: SMALL_SPACE),
child: _getCameraImageButton(
appLocalizations,
imageProvider == null,
child: ProductImageLocalButton(
firstPhoto: imageProvider == null,
barcode: _barcode,
imageField: widget.imageField,
),
),
),
Expand Down Expand Up @@ -145,95 +149,22 @@ class _ProductImageViewerState extends State<ProductImageViewer> {
);
}

// TODO(monsieurtanuki): refactor as ProductImageCropButton
Widget _getEditImageButton(final AppLocalizations appLocalizations) =>
EditImageButton(
iconData: Icons.edit,
label: appLocalizations.edit_photo_button_label,
onPressed: _actionEditImage,
);

Widget _getCameraImageButton(
final AppLocalizations appLocalizations,
final bool firstPhoto,
) =>
EditImageButton(
iconData: firstPhoto ? Icons.add : Icons.add_a_photo,
label: firstPhoto ? appLocalizations.add : appLocalizations.capture,
onPressed: _actionNewImage,
);

// TODO(monsieurtanuki): refactor as ProductImageUnselectButton
Widget _getUnselectImageButton(final AppLocalizations appLocalizations) =>
EditImageButton(
iconData: Icons.do_disturb_on,
label: appLocalizations.edit_photo_unselect_button_label,
onPressed: () => _actionUnselect(appLocalizations),
);

Widget _getGalleryButton(final AppLocalizations appLocalizations) =>
EditImageButton(
iconData: Icons.image_search,
label: appLocalizations.edit_photo_select_existing_button_label,
onPressed: _actionGallery,
);

// TODO(monsieurtanuki): we should also suggest the existing image gallery
Future<File?> _actionNewImage() async {
if (!await ProductRefresher().checkIfLoggedIn(context)) {
return null;
}
return confirmAndUploadNewPicture(
this,
imageField: _imageData.imageField,
barcode: _barcode,
);
}

Future<void> _actionGallery() async {
final AppLocalizations appLocalizations = AppLocalizations.of(context);
if (!await ProductRefresher().checkIfLoggedIn(context)) {
return;
}
final List<int>? result = await LoadingDialog.run<List<int>>(
future: OpenFoodAPIClient.getProductImageIds(
_barcode,
user: ProductQuery.getUser(),
),
context: context,
title: appLocalizations.edit_photo_select_existing_download_label,
);
if (result == null) {
return;
}
if (!mounted) {
return;
}
if (result.isEmpty) {
await showDialog<void>(
context: context,
builder: (BuildContext context) => SmoothAlertDialog(
body:
Text(appLocalizations.edit_photo_select_existing_downloaded_none),
actionsAxis: Axis.vertical,
positiveAction: SmoothActionButton(
text: appLocalizations.okay,
onPressed: () => Navigator.of(context).pop(),
),
),
);
return;
}
await Navigator.push<void>(
context,
MaterialPageRoute<void>(
builder: (BuildContext context) => UploadedImageGallery(
barcode: _barcode,
imageIds: result,
imageField: widget.imageField,
),
),
);
}

Future<File?> _actionEditImage() async {
final NavigatorState navigatorState = Navigator.of(context);
if (!await ProductRefresher().checkIfLoggedIn(context)) {
Expand Down

0 comments on commit d2191ee

Please sign in to comment.