Skip to content

Commit

Permalink
feat: 5301 - price proofs can be cropped and will be displayed (#5305)
Browse files Browse the repository at this point in the history
* feat: 5301 - price proofs can be cropped and will be displayed

New files:
* `crop_helper.dart`: Crop Helper for images in crop page: process to run when cropping an image.
* `crop_parameters.dart`: Parameters of the crop operation.
* `product_crop_helper.dart`: Crop Helpers for product images.
* `proof_crop_helper.dart`: Crop Helper for proof images.

Impacted files
* `add_new_product_page.dart`: minor refactoring
* `background_task.dart`: new method `isDeduplicable`
* `background_task_add_price.dart`:
* `background_task_image.dart`: made some methods `static` and public to be reused
* `background_task_manager.dart`: now using new method `isDeduplicable`
* `background_task_upload.dart`: new method `isDeduplicable`
* `crop_page.dart`: refactored using `CropHelper`
* `image_crop_page.dart`: minor refactoring
* `may_exit_page_helper.dart`: minor refactoring
* `price_model.dart`: minor refactoring
* `price_proof_card.dart`: now we may crop the image and we also display the result
* `product_image_crop_button.dart`: refactored using `CropHelper`
* `uploaded_image_gallery.dart`: minor refactoring

* feat: 5301 - fixed WillPopScope2

Impacted files
* `crop_page.dart`: fixed `WillPopScope2`
* `product_price_add_page.dart`: added a `TODO`
* `price_proof_card.dart`: minor refactoring

* Unrelated - fixed `getUrl` bug (was always PROD, never TEST)

New file:
* `signalconso.png`: new asset

Impacted files
* `product_cards_helper.dart`: fixed `getUrl` bug (was always PROD, never TEST)
* `product_image_crop_button.dart`: fixed `getUrl` bug (was always PROD, never TEST)
* `product_image_gallery_other_view.dart`: fixed `getUrl` bug (was always PROD, never TEST)
* `product_image_other_page.dart`: fixed `getUrl` bug (was always PROD, never TEST)
* `uploaded_image_gallery.dart`: fixed `getUrl` bug (was always PROD, never TEST)
  • Loading branch information
monsieurtanuki authored May 30, 2024
1 parent b6ad190 commit 529fe8f
Show file tree
Hide file tree
Showing 22 changed files with 805 additions and 377 deletions.
Binary file added packages/smooth_app/assets/cache/signalconso.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions packages/smooth_app/lib/background/background_task.dart
Original file line number Diff line number Diff line change
Expand Up @@ -184,4 +184,7 @@ abstract class BackgroundTask {
// TODO(monsieurtanuki): store the uriProductHelper as well
@protected
UriProductHelper get uriProductHelper => ProductQuery.uriProductHelper;

/// Returns true if tasks with the same stamp would overwrite each-other.
bool isDeduplicable() => true;
}
154 changes: 100 additions & 54 deletions packages/smooth_app/lib/background/background_task_add_price.dart
Original file line number Diff line number Diff line change
@@ -1,121 +1,146 @@
import 'dart:async';
import 'dart:io';

import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:http_parser/http_parser.dart';
import 'package:openfoodfacts/openfoodfacts.dart';
import 'package:provider/provider.dart';
import 'package:smooth_app/background/background_task_barcode.dart';
import 'package:smooth_app/background/background_task.dart';
import 'package:smooth_app/background/background_task_image.dart';
import 'package:smooth_app/background/background_task_upload.dart';
import 'package:smooth_app/background/operation_type.dart';
import 'package:smooth_app/database/local_database.dart';
import 'package:smooth_app/pages/crop_parameters.dart';

// TODO(monsieurtanuki): use transient file, in order to have instant access to proof image?
// TODO(monsieurtanuki): add crop
// TODO(monsieurtanuki): check "is picture big enough?"
// TODO(monsieurtanuki): add source
// TODO(monsieurtanuki): make it work for several products
/// Background task about adding a product price.
class BackgroundTaskAddPrice extends BackgroundTaskBarcode {
class BackgroundTaskAddPrice extends BackgroundTask {
BackgroundTaskAddPrice._({
required super.processName,
required super.uniqueId,
required super.barcode,
required super.stamp,
// single
required this.fullPath,
required this.rotationDegrees,
required this.cropX1,
required this.cropY1,
required this.cropX2,
required this.cropY2,
required this.proofType,
required this.date,
required this.currency,
required this.locationOSMId,
required this.locationOSMType,
// multi
required this.barcode,
required this.priceIsDiscounted,
required this.price,
required this.priceWithoutDiscount,
required this.locationOSMId,
required this.locationOSMType,
});

BackgroundTaskAddPrice.fromJson(Map<String, dynamic> json)
: fullPath = json[_jsonTagImagePath] as String,
rotationDegrees = json[_jsonTagRotation] as int? ?? 0,
cropX1 = json[_jsonTagX1] as int? ?? 0,
cropY1 = json[_jsonTagY1] as int? ?? 0,
cropX2 = json[_jsonTagX2] as int? ?? 0,
cropY2 = json[_jsonTagY2] as int? ?? 0,
proofType = getProofTypeFromOffTag(json[_jsonTagProofType] as String)!,
date = JsonHelper.stringTimestampToDate(json[_jsonTagDate] as String),
currency = getCurrencyFromName(json[_jsonTagCurrency] as String)!,
priceIsDiscounted = json[_jsonTagIsDiscounted] as bool,
price = json[_jsonTagPrice] as double,
priceWithoutDiscount = json[_jsonTagPriceWithoutDiscount] as double?,
locationOSMId = json[_jsonTagOSMId] as int,
locationOSMType =
LocationOSMType.fromOffTag(json[_jsonTagOSMType] as String)!,
barcode = json[_jsonTagBarcode] as String,
priceIsDiscounted = json[_jsonTagIsDiscounted] as bool,
price = json[_jsonTagPrice] as double,
priceWithoutDiscount = json[_jsonTagPriceWithoutDiscount] as double?,
super.fromJson(json);

static const String _jsonTagImagePath = 'imagePath';
static const String _jsonTagRotation = 'rotation';
static const String _jsonTagX1 = 'x1';
static const String _jsonTagY1 = 'y1';
static const String _jsonTagX2 = 'x2';
static const String _jsonTagY2 = 'y2';
static const String _jsonTagProofType = 'proofType';
static const String _jsonTagDate = 'date';
static const String _jsonTagCurrency = 'currency';
static const String _jsonTagOSMId = 'osmId';
static const String _jsonTagOSMType = 'osmType';
static const String _jsonTagBarcode = 'barcode';
static const String _jsonTagIsDiscounted = 'isDiscounted';
static const String _jsonTagPrice = 'price';
static const String _jsonTagPriceWithoutDiscount = 'priceWithoutDiscount';
static const String _jsonTagOSMId = 'osmId';
static const String _jsonTagOSMType = 'osmType';

static const OperationType _operationType = OperationType.addPrice;

final String fullPath;
final int rotationDegrees;
final int cropX1;
final int cropY1;
final int cropX2;
final int cropY2;
final ProofType proofType;
final DateTime date;
final Currency currency;
final int locationOSMId;
final LocationOSMType locationOSMType;
final String barcode;
final bool priceIsDiscounted;
final double price;
final double? priceWithoutDiscount;
final int locationOSMId;
final LocationOSMType locationOSMType;

@override
Map<String, dynamic> toJson() {
final Map<String, dynamic> result = super.toJson();
result[_jsonTagImagePath] = fullPath;
result[_jsonTagRotation] = rotationDegrees;
result[_jsonTagX1] = cropX1;
result[_jsonTagY1] = cropY1;
result[_jsonTagX2] = cropX2;
result[_jsonTagY2] = cropY2;
result[_jsonTagProofType] = proofType.offTag;
result[_jsonTagDate] = date.toIso8601String();
result[_jsonTagCurrency] = currency.name;
result[_jsonTagOSMId] = locationOSMId;
result[_jsonTagOSMType] = locationOSMType.offTag;
result[_jsonTagBarcode] = barcode;
result[_jsonTagIsDiscounted] = priceIsDiscounted;
result[_jsonTagPrice] = price;
result[_jsonTagPriceWithoutDiscount] = priceWithoutDiscount;
result[_jsonTagOSMId] = locationOSMId;
result[_jsonTagOSMType] = locationOSMType.offTag;
return result;
}

/// Adds the background task about uploading a product image.
static Future<void> addTask(
final String barcode, {
required final File fullFile,
static Future<void> addTask({
required final CropParameters cropObject,
required final ProofType proofType,
required final DateTime date,
required final Currency currency,
required final int locationOSMId,
required final LocationOSMType locationOSMType,
required final String barcode,
required final bool priceIsDiscounted,
required final double price,
required final double? priceWithoutDiscount,
required final int locationOSMId,
required final LocationOSMType locationOSMType,
required final BuildContext context,
}) async {
final LocalDatabase localDatabase = context.read<LocalDatabase>();
final String uniqueId = await _operationType.getNewKey(
localDatabase,
barcode: barcode,
);
final BackgroundTaskBarcode task = _getNewTask(
barcode,
fullFile: fullFile,
final String uniqueId = await _operationType.getNewKey(localDatabase);
final BackgroundTask task = _getNewTask(
cropObject: cropObject,
proofType: proofType,
date: date,
currency: currency,
locationOSMId: locationOSMId,
locationOSMType: locationOSMType,
barcode: barcode,
priceIsDiscounted: priceIsDiscounted,
price: price,
priceWithoutDiscount: priceWithoutDiscount,
locationOSMId: locationOSMId,
locationOSMType: locationOSMType,
uniqueId: uniqueId,
);
if (!context.mounted) {
Expand All @@ -133,47 +158,50 @@ class BackgroundTaskAddPrice extends BackgroundTaskBarcode {
);

/// Returns a new background task about changing a product.
static BackgroundTaskAddPrice _getNewTask(
final String barcode, {
required final File fullFile,
static BackgroundTaskAddPrice _getNewTask({
required final CropParameters cropObject,
required final ProofType proofType,
required final DateTime date,
required final Currency currency,
required final int locationOSMId,
required final LocationOSMType locationOSMType,
required final String barcode,
required final bool priceIsDiscounted,
required final double price,
required final double? priceWithoutDiscount,
required final int locationOSMId,
required final LocationOSMType locationOSMType,
required final String uniqueId,
}) =>
BackgroundTaskAddPrice._(
uniqueId: uniqueId,
barcode: barcode,
processName: _operationType.processName,
fullPath: fullFile.path,
fullPath: cropObject.fullFile!.path,
rotationDegrees: cropObject.rotation,
cropX1: cropObject.x1,
cropY1: cropObject.y1,
cropX2: cropObject.x2,
cropY2: cropObject.y2,
proofType: proofType,
date: date,
currency: currency,
locationOSMId: locationOSMId,
locationOSMType: locationOSMType,
barcode: barcode,
priceIsDiscounted: priceIsDiscounted,
price: price,
priceWithoutDiscount: priceWithoutDiscount,
locationOSMId: locationOSMId,
locationOSMType: locationOSMType,
stamp: _getStamp(
barcode: barcode,
date: date,
locationOSMId: locationOSMId,
locationOSMType: locationOSMType,
),
);

static String _getStamp({
required final String barcode,
required final DateTime date,
required final int locationOSMId,
required final LocationOSMType locationOSMType,
}) =>
'$barcode;price;$date;$locationOSMId;$locationOSMType';
'no_barcode;price;$date;$locationOSMId;$locationOSMType';

@override
Future<void> postExecute(
Expand All @@ -186,27 +214,43 @@ class BackgroundTaskAddPrice extends BackgroundTaskBarcode {
} catch (e) {
// not likely, but let's not spoil the task for that either.
}
try {
(await BackgroundTaskUpload.getFile(
BackgroundTaskImage.getCroppedPath(fullPath)))
.deleteSync();
} catch (e) {
// possible, but let's not spoil the task for that either.
}
}

@override
Future<void> preExecute(final LocalDatabase localDatabase) async {}

// Here we don't need the product refresh
@override
Future<void> execute(final LocalDatabase localDatabase) async => upload();

/// Sends the product price to the server
@override
Future<void> upload() async {
Future<void> execute(final LocalDatabase localDatabase) async {
final Price newPrice = Price()
..date = date
..currency = currency
..locationOSMId = locationOSMId
..locationOSMType = locationOSMType
..priceIsDiscounted = priceIsDiscounted
..price = price
..priceWithoutDiscount = priceWithoutDiscount
..productCode = barcode
..locationOSMId = locationOSMId
..locationOSMType = locationOSMType;
..productCode = barcode;

final String? path = await BackgroundTaskImage.cropIfNeeded(
fullPath: fullPath,
croppedPath: BackgroundTaskImage.getCroppedPath(fullPath),
rotationDegrees: rotationDegrees,
cropX1: cropX1,
cropY1: cropY1,
cropX2: cropX2,
cropY2: cropY2,
);
if (path == null) {
// TODO(monsieurtanuki): maybe something more refined when we dismiss the picture, like alerting the user, though it's not supposed to happen anymore from upstream.
return;
}

// authentication
final User user = getUser();
Expand All @@ -225,8 +269,7 @@ class BackgroundTaskAddPrice extends BackgroundTaskBarcode {
final String bearerToken = token.value;

// proof upload
final File file = File(fullPath);
final Uri initialImageUri = Uri.parse(file.path);
final Uri initialImageUri = Uri.parse(path);
final MediaType initialMediaType =
HttpHelper().imagineMediaType(initialImageUri.path)!;
final MaybeError<Proof> uploadProof = await OpenPricesAPIClient.uploadProof(
Expand Down Expand Up @@ -288,4 +331,7 @@ class BackgroundTaskAddPrice extends BackgroundTaskBarcode {
}
return null;
}

@override
bool isDeduplicable() => false;
}
Loading

0 comments on commit 529fe8f

Please sign in to comment.