Skip to content

Commit

Permalink
Extract ingredients/packaging: loading / loaded / extracting states
Browse files Browse the repository at this point in the history
  • Loading branch information
g123k committed Jun 15, 2024
1 parent 9f7d9d4 commit fb2d1ee
Show file tree
Hide file tree
Showing 5 changed files with 277 additions and 19 deletions.
22 changes: 19 additions & 3 deletions packages/smooth_app/lib/l10n/app_en.arb
Original file line number Diff line number Diff line change
Expand Up @@ -1448,18 +1448,34 @@
"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": {
"edit_ingredients_extract_ingredients_btn_text": "Extract ingredients from the photo",
"@edit_ingredients_extract_ingredients_btn_text": {
"description": "Ingredients edition - Extract ingredients"
},
"edit_ingredients_extracting_ingredients_btn_text": "Extracting ingredients\nfrom the photo",
"@edit_ingredients_extracting_ingredients_btn_text": {
"description": "Ingredients edition - Extracting ingredients"
},
"edit_ingredients_loading_photo_btn_text": "Loading photo…",
"@edit_ingredients_loading_photo_btn_text": {
"description": "Ingredients edition - Loading photo from the server"
},
"edit_ingredients_refresh_photo_btn_text": "Refresh photo",
"@edit_ingredients_refresh_photo_btn_text": {
"description": "Ingredients edition - Refresh photo"
},
"edit_packaging_extract_btn_text": "Extract packaging",
"edit_packaging_extract_btn_text": "Extract packaging\nfrom the photo",
"@edit_packaging_extract_btn_text": {
"description": "Packaging edition - OCR-Extract packaging"
},
"edit_packaging_extracting_btn_text": "Extracting packaging from the photo",
"@edit_packaging_extracting_btn_text": {
"description": "Packaging edition - OCR-Extracting packaging"
},
"edit_packaging_loading_photo_btn_text": "Loading photo…",
"@edit_packaging_loading_photo_btn_text": {
"description": "Packaging edition - Loading photo from the server"
},
"edit_packaging_refresh_photo_btn_text": "Refresh photo",
"@edit_packaging_refresh_photo_btn_text": {
"description": "Packaging edition - Refresh photo"
Expand Down
250 changes: 235 additions & 15 deletions packages/smooth_app/lib/pages/product/edit_ocr_page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ 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:shimmer/shimmer.dart';
import 'package:smooth_app/background/background_task_details.dart';
import 'package:smooth_app/data_models/up_to_date_mixin.dart';
import 'package:smooth_app/database/local_database.dart';
Expand All @@ -18,6 +19,7 @@ import 'package:smooth_app/pages/product/explanation_widget.dart';
import 'package:smooth_app/pages/product/multilingual_helper.dart';
import 'package:smooth_app/pages/product/ocr_helper.dart';
import 'package:smooth_app/pages/product/product_image_button.dart';
import 'package:smooth_app/themes/smooth_theme_colors.dart';
import 'package:smooth_app/widgets/smooth_scaffold.dart';

/// Editing with OCR a product field and the corresponding image.
Expand All @@ -43,6 +45,7 @@ class _EditOcrPageState extends State<EditOcrPage> with UpToDateMixin {
late final MultilingualHelper _multilingualHelper;

OcrHelper get _helper => widget.helper;
bool extractingData = false;

@override
void initState() {
Expand All @@ -62,7 +65,8 @@ class _EditOcrPageState extends State<EditOcrPage> with UpToDateMixin {
///
/// When done, populates the related page field.
Future<void> _extractData() async {
// TODO(monsieurtanuki): hide the "extract" button while extracting, or display a loading dialog on top
setState(() => extractingData = true);

try {
final String? extractedText = await _helper.getExtractedText(
widget.product,
Expand All @@ -72,6 +76,8 @@ class _EditOcrPageState extends State<EditOcrPage> with UpToDateMixin {
return;
}

setState(() => extractingData = false);

if (extractedText == null || extractedText.isEmpty) {
await LoadingDialog.error(
context: context,
Expand All @@ -83,9 +89,7 @@ class _EditOcrPageState extends State<EditOcrPage> with UpToDateMixin {
if (_controller.text != extractedText) {
setState(() => _controller.text = extractedText);
}
} catch (e) {
//
}
} catch (_) {}
}

/// Updates the product field on the server.
Expand Down Expand Up @@ -282,17 +286,11 @@ class _EditOcrPageState extends State<EditOcrPage> with UpToDateMixin {
setState: setState,
product: upToDateProduct,
),
if (transientFile.isServerImage())
SmoothActionButtonsBar.single(
action: SmoothActionButton(
text:
_helper.getActionExtractText(appLocalizations),
onPressed: () async => _extractData(),
),
)
else if (transientFile.isImageAvailable())
// TODO(monsieurtanuki): what if slow upload? text instead?
const CircularProgressIndicator.adaptive(),
_EditOcrMainAction(
onPressed: _extractData,
helper: _helper,
state: _extractState(transientFile),
),
const SizedBox(height: MEDIUM_SPACE),
TextField(
controller: _controller,
Expand Down Expand Up @@ -377,4 +375,226 @@ class _EditOcrPageState extends State<EditOcrPage> with UpToDateMixin {
}
return result;
}

OcrState _extractState(TransientFile transientFile) {
if (extractingData) {
return OcrState.EXTRACTING_DATA;
} else if (transientFile.isServerImage()) {
return OcrState.IMAGE_LOADED;
} else if (transientFile.isImageAvailable()) {
return OcrState.IMAGE_LOADING;
} else {
return OcrState.OTHER;
}
}
}

class _EditOcrMainAction extends StatelessWidget {
const _EditOcrMainAction({
required this.onPressed,
required this.helper,
required this.state,
});

final VoidCallback onPressed;
final OcrHelper helper;
final OcrState state;

@override
Widget build(BuildContext context) {
final AppLocalizations appLocalizations = AppLocalizations.of(context);

final Widget? child = switch (state) {
OcrState.IMAGE_LOADING => _EditOcrActionLoadingContent(
helper: helper,
appLocalizations: appLocalizations,
),
OcrState.IMAGE_LOADED => _ExtractMainActionContentLoaded(
helper: helper,
appLocalizations: appLocalizations,
onPressed: onPressed,
),
OcrState.EXTRACTING_DATA => _EditOcrActionExtractingContent(
helper: helper,
appLocalizations: appLocalizations,
),
OcrState.OTHER => null,
};

final SmoothColorsThemeExtension theme =
Theme.of(context).extension<SmoothColorsThemeExtension>()!;

if (child == null) {
return EMPTY_WIDGET;
} else {
return SizedBox(
height: 45.0 * (_computeFontScaleFactor(context)),
width: double.infinity,
child: DecoratedBox(
decoration: BoxDecoration(
borderRadius: ANGULAR_BORDER_RADIUS,
color: theme.primarySemiDark,
border: Border.all(
color: theme.primaryBlack,
width: 2.0,
),
),
child: Material(
type: MaterialType.transparency,
child: ProgressIndicatorTheme(
data: const ProgressIndicatorThemeData(
color: Colors.white,
),
child: IconTheme(
data: const IconThemeData(color: Colors.white),
child: DefaultTextStyle(
style: const TextStyle(
fontWeight: FontWeight.bold,
fontSize: 15.5,
color: Colors.white,
),
child: child,
),
),
),
),
),
);
}
}

double _computeFontScaleFactor(BuildContext context) {
final double fontSize = DefaultTextStyle.of(context).style.fontSize ?? 15.0;
final double scaledFontSize =
MediaQuery.textScalerOf(context).scale(fontSize);

return scaledFontSize / fontSize;
}
}

class _EditOcrActionExtractingContent extends StatelessWidget {
const _EditOcrActionExtractingContent({
required this.helper,
required this.appLocalizations,
});

final OcrHelper helper;
final AppLocalizations appLocalizations;

@override
Widget build(BuildContext context) {
return Semantics(
label: helper.getActionLoadingPhoto(appLocalizations),
excludeSemantics: true,
child: Shimmer(
gradient: const LinearGradient(
begin: Alignment.centerLeft,
end: Alignment.centerRight,
colors: <Color>[
Colors.black,
Colors.white,
Colors.black,
],
),
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: MEDIUM_SPACE),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
const CircularProgressIndicator.adaptive(),
Expanded(
child: Text(
helper.getActionExtractingData(appLocalizations),
textAlign: TextAlign.center,
),
),
const CircularProgressIndicator.adaptive(),
],
),
),
),
);
}
}

class _ExtractMainActionContentLoaded extends StatelessWidget {
const _ExtractMainActionContentLoaded({
super.key,
required this.helper,
required this.appLocalizations,
required this.onPressed,
});

final OcrHelper helper;
final AppLocalizations appLocalizations;
final VoidCallback onPressed;

@override
Widget build(BuildContext context) {
return Semantics(
excludeSemantics: true,
value: helper.getActionExtractText(appLocalizations),
button: true,
child: InkWell(
onTap: onPressed,
borderRadius: ANGULAR_BORDER_RADIUS,
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: MEDIUM_SPACE),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Expanded(
child: Text(
helper.getActionExtractText(appLocalizations),
),
),
const Icon(
Icons.download,
semanticLabel: '',
),
],
),
),
),
);
}
}

class _EditOcrActionLoadingContent extends StatelessWidget {
const _EditOcrActionLoadingContent({
super.key,
required this.helper,
required this.appLocalizations,
});

final OcrHelper helper;
final AppLocalizations appLocalizations;

@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.symmetric(horizontal: MEDIUM_SPACE),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Expanded(
child: Text(
helper.getActionLoadingPhoto(appLocalizations),
),
),
const CircularProgressIndicator.adaptive(),
],
),
);
}
}

enum OcrState {
IMAGE_LOADING,
IMAGE_LOADED,
EXTRACTING_DATA,
OTHER,
}
6 changes: 6 additions & 0 deletions packages/smooth_app/lib/pages/product/ocr_helper.dart
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,12 @@ abstract class OcrHelper {
/// Returns the "extract text" button label
String getActionExtractText(final AppLocalizations appLocalizations);

/// Returns the "loading photo" button label
String getActionLoadingPhoto(final AppLocalizations appLocalizations);

/// Returns the "Extracting data…" button label
String getActionExtractingData(final AppLocalizations appLocalizations);

/// Returns the "refresh photo" button label
String getActionRefreshPhoto(final AppLocalizations appLocalizations);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,15 @@ class OcrIngredientsHelper extends OcrHelper {

@override
String getActionExtractText(final AppLocalizations appLocalizations) =>
appLocalizations.edit_ingredients_extrait_ingredients_btn_text;
appLocalizations.edit_ingredients_extract_ingredients_btn_text;

@override
String getActionExtractingData(AppLocalizations appLocalizations) =>
appLocalizations.edit_ingredients_extracting_ingredients_btn_text;

@override
String getActionLoadingPhoto(AppLocalizations appLocalizations) =>
appLocalizations.edit_ingredients_loading_photo_btn_text;

@override
String getActionRefreshPhoto(final AppLocalizations appLocalizations) =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,14 @@ class OcrPackagingHelper extends OcrHelper {
String getActionExtractText(final AppLocalizations appLocalizations) =>
appLocalizations.edit_packaging_extract_btn_text;

@override
String getActionExtractingData(AppLocalizations appLocalizations) =>
appLocalizations.edit_packaging_extracting_btn_text;

@override
String getActionLoadingPhoto(AppLocalizations appLocalizations) =>
appLocalizations.edit_packaging_loading_photo_btn_text;

@override
String getActionRefreshPhoto(final AppLocalizations appLocalizations) =>
appLocalizations.edit_packaging_refresh_photo_btn_text;
Expand Down

0 comments on commit fb2d1ee

Please sign in to comment.