Skip to content

Commit

Permalink
feat: 2424 - new "select existing images" button (#3641)
Browse files Browse the repository at this point in the history
* feat: 2424 - new "select existing image" button

New files:
* `background_task_crop.dart`: Background task about product image crop from existing file.
* `uploaded_image_gallery.dart`: Gallery of all images already uploaded, about a given product.

Delete file:
* `smooth_images_sliver_grid.dart`

Impacted files:
* `abstract_background_task.dart`: added reference to "crop" task
* `app_en.arb`: added 4 labels about "existing images" (button, page title, downloading, nothing found)
* `image_crop_page.dart`: refactored moving code here from `ProductImageViewer`
* `new_crop_page.dart`: now we can handle "already existing" images, in addition to the current choice for e.g. NUTRITION_FR
* `operation_type.dart`: added reference to new "crop" task
* `product_image_swipeable_view.dart`: unrelated fix - the title of the first reached page was always the title of the first page to the left
* `product_image_viewer.dart`: added a "select existing image" button; moved code to `image_crop_page.dart`
* `pubspec.lock`: wtf
* `pubspec.yaml`: upgraded off-dart in order to user new methods

* feat: 2424 - upgraded to off-dart 2.2.0

Impacted files:
* `pubspec.lock`: wtf
* `pubspec.yaml`: upgraded off-dart in order to user new methods
  • Loading branch information
monsieurtanuki committed Jan 27, 2023
1 parent 1dbf3b5 commit 7121b0e
Show file tree
Hide file tree
Showing 12 changed files with 521 additions and 174 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:openfoodfacts/openfoodfacts.dart';
import 'package:smooth_app/background/background_task_crop.dart';
import 'package:smooth_app/background/background_task_details.dart';
import 'package:smooth_app/background/background_task_image.dart';
import 'package:smooth_app/background/background_task_manager.dart';
Expand Down Expand Up @@ -47,6 +48,7 @@ abstract class AbstractBackgroundTask {
BackgroundTaskDetails.fromJson(map) ??
BackgroundTaskImage.fromJson(map) ??
BackgroundTaskUnselect.fromJson(map) ??
BackgroundTaskCrop.fromJson(map) ??
BackgroundTaskRefreshLater.fromJson(map);

/// Executes the background task: upload, download, update locally.
Expand Down
228 changes: 228 additions & 0 deletions packages/smooth_app/lib/background/background_task_crop.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,228 @@
import 'dart:convert';
import 'dart:io';

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/background/abstract_background_task.dart';
import 'package:smooth_app/background/background_task_image.dart';
import 'package:smooth_app/background/background_task_refresh_later.dart';
import 'package:smooth_app/data_models/operation_type.dart';
import 'package:smooth_app/database/local_database.dart';
import 'package:smooth_app/database/transient_file.dart';
import 'package:smooth_app/query/product_query.dart';

/// Background task about product image crop from existing file.
class BackgroundTaskCrop extends AbstractBackgroundTask {
const BackgroundTaskCrop._({
required super.processName,
required super.uniqueId,
required super.barcode,
required super.languageCode,
required super.user,
required super.country,
required super.stamp,
required this.imageId,
required this.imageField,
required this.croppedPath,
required this.rotationDegrees,
required this.cropX1,
required this.cropY1,
required this.cropX2,
required this.cropY2,
});

BackgroundTaskCrop._fromJson(Map<String, dynamic> json)
: this._(
processName: json['processName'] as String,
uniqueId: json['uniqueId'] as String,
barcode: json['barcode'] as String,
languageCode: json['languageCode'] as String,
user: json['user'] as String,
country: json['country'] as String,
imageId: json['imageId'] as int,
imageField: json['imageField'] as String,
croppedPath: json['croppedPath'] as String,
rotationDegrees: json['rotation'] as int,
cropX1: json['x1'] as int? ?? 0,
cropY1: json['y1'] as int? ?? 0,
cropX2: json['x2'] as int? ?? 0,
cropY2: json['y2'] as int? ?? 0,
stamp: json['stamp'] as String,
);

/// Task ID.
static const String _PROCESS_NAME = 'IMAGE_CROP';

static const OperationType _operationType = OperationType.crop;

final int imageId;
final String imageField;
final String croppedPath;
final int rotationDegrees;
final int cropX1;
final int cropY1;
final int cropX2;
final int cropY2;

@override
Map<String, dynamic> toJson() => <String, dynamic>{
'processName': processName,
'uniqueId': uniqueId,
'barcode': barcode,
'languageCode': languageCode,
'user': user,
'country': country,
'imageId': imageId,
'imageField': imageField,
'croppedPath': croppedPath,
'stamp': stamp,
'rotation': rotationDegrees,
'x1': cropX1,
'y1': cropY1,
'x2': cropX2,
'y2': cropY2,
};

/// Returns the deserialized background task if possible, or null.
static AbstractBackgroundTask? fromJson(final Map<String, dynamic> map) {
try {
final AbstractBackgroundTask result = BackgroundTaskCrop._fromJson(map);
if (result.processName == _PROCESS_NAME) {
return result;
}
} catch (e) {
//
}
return null;
}

/// Adds the background task about uploading a product image.
static Future<void> addTask(
final String barcode, {
required final int imageId,
required final ImageField imageField,
required final File croppedFile,
required final int rotation,
required final int x1,
required final int y1,
required final int x2,
required final int y2,
required final State<StatefulWidget> widget,
}) async {
final LocalDatabase localDatabase = widget.context.read<LocalDatabase>();
final String uniqueId = await _operationType.getNewKey(
localDatabase,
barcode,
);
final AbstractBackgroundTask task = _getNewTask(
barcode,
imageId,
imageField,
croppedFile,
uniqueId,
rotation,
x1,
y1,
x2,
y2,
);
await task.addToManager(localDatabase, widget: widget);
}

@override
String? getSnackBarMessage(final AppLocalizations appLocalizations) =>
appLocalizations.product_task_background_schedule;

/// Returns a new background task about cropping an existing image.
static BackgroundTaskCrop _getNewTask(
final String barcode,
final int imageId,
final ImageField imageField,
final File croppedFile,
final String uniqueId,
final int rotationDegrees,
final int cropX1,
final int cropY1,
final int cropX2,
final int cropY2,
) =>
BackgroundTaskCrop._(
uniqueId: uniqueId,
barcode: barcode,
processName: _PROCESS_NAME,
imageId: imageId,
imageField: imageField.offTag,
croppedPath: croppedFile.path,
rotationDegrees: rotationDegrees,
cropX1: cropX1,
cropY1: cropY1,
cropX2: cropX2,
cropY2: cropY2,
languageCode: ProductQuery.getLanguage().code,
user: jsonEncode(ProductQuery.getUser().toJson()),
country: ProductQuery.getCountry()!.offTag,
stamp: BackgroundTaskImage.getStamp(
barcode,
imageField.offTag,
ProductQuery.getLanguage().code,
),
);

@override
Future<void> preExecute(final LocalDatabase localDatabase) async =>
TransientFile.putImage(
ImageField.fromOffTag(imageField)!,
barcode,
localDatabase,
File(croppedPath),
);

@override
Future<void> postExecute(
final LocalDatabase localDatabase,
final bool success,
) async {
try {
File(croppedPath).deleteSync();
} catch (e) {
// not likely, but let's not spoil the task for that either.
}
TransientFile.removeImage(
ImageField.fromOffTag(imageField)!,
barcode,
localDatabase,
);
localDatabase.notifyListeners();
if (success) {
await BackgroundTaskRefreshLater.addTask(
barcode,
localDatabase: localDatabase,
);
}
}

/// Uploads the product image.
@override
Future<void> upload() async {
final ImageField imageField = ImageField.fromOffTag(this.imageField)!;
final OpenFoodFactsLanguage language = getLanguage();
final User user = getUser();
final String? imageUrl = await OpenFoodAPIClient.setProductImageCrop(
barcode: barcode,
imageField: imageField,
language: language,
imgid: '$imageId',
angle: ImageAngleExtension.fromInt(rotationDegrees)!,
x1: cropX1,
y1: cropY1,
x2: cropX2,
y2: cropY2,
user: user,
);
if (imageUrl == null) {
throw Exception('Could not select picture');
}
}
}
3 changes: 3 additions & 0 deletions packages/smooth_app/lib/data_models/operation_type.dart
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import 'package:smooth_app/helpers/database_helper.dart';
/// * possibly, which barcode (not useful yet)
enum OperationType {
image('I'),
crop('C'),
unselect('U'),
refreshLater('R'),
details('D');
Expand Down Expand Up @@ -47,6 +48,8 @@ enum OperationType {
return appLocalizations.background_task_operation_image;
case OperationType.unselect:
return 'Unselect a product image';
case OperationType.crop:
return 'Crop an existing image';
case OperationType.refreshLater:
return 'Waiting 10 min before refreshing product to get all automatic edits';
}
Expand Down

This file was deleted.

16 changes: 16 additions & 0 deletions packages/smooth_app/lib/l10n/app_en.arb
Original file line number Diff line number Diff line change
Expand Up @@ -1526,6 +1526,22 @@
"@edit_photo_unselect_button_label": {
"description": "Edit 'unselect photo' button label"
},
"edit_photo_select_existing_button_label": "Select an existing image",
"@edit_photo_select_existing_button_label": {
"description": "Edit 'select existing image' button label"
},
"edit_photo_select_existing_all_label": "Existing images",
"@edit_photo_select_existing_all_label": {
"description": "Page title"
},
"edit_photo_select_existing_download_label": "Retrieving existing images...",
"@edit_photo_select_existing_download_label": {
"description": "Dialog label"
},
"edit_photo_select_existing_downloaded_none": "There are no images previously uploaded related to this product.",
"@edit_photo_select_existing_downloaded_none": {
"description": "Error message"
},
"category_picker_screen_title": "Categories",
"@category_picker_screen_title": {
"description": "Categories picker screen title"
Expand Down

0 comments on commit 7121b0e

Please sign in to comment.