From 66223d60e7d5e0dc1ef467b0c29139902f043491 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 6 Oct 2024 17:32:34 +0200 Subject: [PATCH 1/5] chore(deps): bump fastlane in /packages/smooth_app/ios (#5667) Bumps [fastlane](https://github.com/fastlane/fastlane) from 2.223.1 to 2.224.0. - [Release notes](https://github.com/fastlane/fastlane/releases) - [Changelog](https://github.com/fastlane/fastlane/blob/master/CHANGELOG.latest.md) - [Commits](https://github.com/fastlane/fastlane/compare/fastlane/2.223.1...fastlane/2.224.0) --- updated-dependencies: - dependency-name: fastlane dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- packages/smooth_app/ios/Gemfile.lock | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/packages/smooth_app/ios/Gemfile.lock b/packages/smooth_app/ios/Gemfile.lock index a0622316070..f11bf6f002a 100644 --- a/packages/smooth_app/ios/Gemfile.lock +++ b/packages/smooth_app/ios/Gemfile.lock @@ -16,7 +16,7 @@ GEM artifactory (3.0.17) atomos (0.1.3) aws-eventstream (1.3.0) - aws-partitions (1.980.0) + aws-partitions (1.984.0) aws-sdk-core (3.209.1) aws-eventstream (~> 1, >= 1.3.0) aws-partitions (~> 1, >= 1.651.0) @@ -25,7 +25,7 @@ GEM aws-sdk-kms (1.94.0) aws-sdk-core (~> 3, >= 3.207.0) aws-sigv4 (~> 1.5) - aws-sdk-s3 (1.166.0) + aws-sdk-s3 (1.167.0) aws-sdk-core (~> 3, >= 3.207.0) aws-sdk-kms (~> 1) aws-sigv4 (~> 1.5) @@ -45,7 +45,7 @@ GEM domain_name (0.6.20240107) dotenv (2.8.1) emoji_regex (3.2.3) - excon (0.111.0) + excon (0.112.0) faraday (1.10.4) faraday-em_http (~> 1.0) faraday-em_synchrony (~> 1.0) @@ -75,7 +75,7 @@ GEM faraday_middleware (1.2.1) faraday (~> 1.0) fastimage (2.3.1) - fastlane (2.223.1) + fastlane (2.224.0) CFPropertyList (>= 2.3, < 4.0.0) addressable (>= 2.8, < 3.0.0) artifactory (~> 3.0) @@ -161,7 +161,7 @@ GEM httpclient (2.8.3) jmespath (1.6.2) json (2.7.2) - jwt (2.9.1) + jwt (2.9.3) base64 mini_magick (4.13.2) mini_mime (1.1.5) @@ -180,7 +180,7 @@ GEM trailblazer-option (>= 0.1.1, < 0.2.0) uber (< 0.2.0) retriable (3.1.2) - rexml (3.3.7) + rexml (3.3.8) rouge (2.0.7) ruby2_keywords (0.0.5) rubyzip (2.3.2) @@ -209,13 +209,13 @@ GEM xcode-install (2.8.1) claide (>= 0.9.1) fastlane (>= 2.1.0, < 3.0.0) - xcodeproj (1.25.0) + xcodeproj (1.25.1) CFPropertyList (>= 2.3.3, < 4.0) atomos (~> 0.1.3) claide (>= 1.0.2, < 2.0) colored2 (~> 3.1) nanaimo (~> 0.3.0) - rexml (>= 3.3.2, < 4.0) + rexml (>= 3.3.6, < 4.0) xcpretty (0.3.0) rouge (~> 2.0.7) xcpretty-travis-formatter (1.0.1) From 15f0338cd7ea3ee16b22a3df20ab79505a417064 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 6 Oct 2024 17:32:54 +0200 Subject: [PATCH 2/5] chore(deps): bump fastlane in /packages/smooth_app/android (#5666) Bumps [fastlane](https://github.com/fastlane/fastlane) from 2.223.1 to 2.224.0. - [Release notes](https://github.com/fastlane/fastlane/releases) - [Changelog](https://github.com/fastlane/fastlane/blob/master/CHANGELOG.latest.md) - [Commits](https://github.com/fastlane/fastlane/compare/fastlane/2.223.1...fastlane/2.224.0) --- updated-dependencies: - dependency-name: fastlane dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- packages/smooth_app/android/Gemfile.lock | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/packages/smooth_app/android/Gemfile.lock b/packages/smooth_app/android/Gemfile.lock index e4c808f5cc6..b087ba1e0f8 100644 --- a/packages/smooth_app/android/Gemfile.lock +++ b/packages/smooth_app/android/Gemfile.lock @@ -16,7 +16,7 @@ GEM artifactory (3.0.17) atomos (0.1.3) aws-eventstream (1.3.0) - aws-partitions (1.980.0) + aws-partitions (1.984.0) aws-sdk-core (3.209.1) aws-eventstream (~> 1, >= 1.3.0) aws-partitions (~> 1, >= 1.651.0) @@ -25,7 +25,7 @@ GEM aws-sdk-kms (1.94.0) aws-sdk-core (~> 3, >= 3.207.0) aws-sigv4 (~> 1.5) - aws-sdk-s3 (1.166.0) + aws-sdk-s3 (1.167.0) aws-sdk-core (~> 3, >= 3.207.0) aws-sdk-kms (~> 1) aws-sigv4 (~> 1.5) @@ -44,7 +44,7 @@ GEM domain_name (0.6.20240107) dotenv (2.8.1) emoji_regex (3.2.3) - excon (0.111.0) + excon (0.112.0) faraday (1.10.4) faraday-em_http (~> 1.0) faraday-em_synchrony (~> 1.0) @@ -74,7 +74,7 @@ GEM faraday_middleware (1.2.1) faraday (~> 1.0) fastimage (2.3.1) - fastlane (2.223.1) + fastlane (2.224.0) CFPropertyList (>= 2.3, < 4.0.0) addressable (>= 2.8, < 3.0.0) artifactory (~> 3.0) @@ -160,7 +160,7 @@ GEM httpclient (2.8.3) jmespath (1.6.2) json (2.7.2) - jwt (2.9.1) + jwt (2.9.3) base64 mini_magick (4.13.2) mini_mime (1.1.5) @@ -179,7 +179,7 @@ GEM trailblazer-option (>= 0.1.1, < 0.2.0) uber (< 0.2.0) retriable (3.1.2) - rexml (3.3.7) + rexml (3.3.8) rouge (2.0.7) ruby2_keywords (0.0.5) rubyzip (2.3.2) @@ -203,13 +203,13 @@ GEM uber (0.1.0) unicode-display_width (2.6.0) word_wrap (1.0.0) - xcodeproj (1.25.0) + xcodeproj (1.25.1) CFPropertyList (>= 2.3.3, < 4.0) atomos (~> 0.1.3) claide (>= 1.0.2, < 2.0) colored2 (~> 3.1) nanaimo (~> 0.3.0) - rexml (>= 3.3.2, < 4.0) + rexml (>= 3.3.6, < 4.0) xcpretty (0.3.0) rouge (~> 2.0.7) xcpretty-travis-formatter (1.0.1) From ed191b730c603ff9744b50baf7081572dde2c21c Mon Sep 17 00:00:00 2001 From: Edouard Marquez Date: Sun, 6 Oct 2024 17:33:10 +0200 Subject: [PATCH 3/5] Remove `flutter_icons` dependency (#5657) --- .../app/release_icon_transparent_1152x1152.png | Bin 31895 -> 0 bytes ...release_icon_transparent_70pct_1152x1152.png | Bin 25698 -> 0 bytes packages/smooth_app/pubspec.yaml | 13 ------------- 3 files changed, 13 deletions(-) delete mode 100644 packages/smooth_app/assets/app/release_icon_transparent_1152x1152.png delete mode 100644 packages/smooth_app/assets/app/release_icon_transparent_70pct_1152x1152.png diff --git a/packages/smooth_app/assets/app/release_icon_transparent_1152x1152.png b/packages/smooth_app/assets/app/release_icon_transparent_1152x1152.png deleted file mode 100644 index aae1b7666cf0afef3699acf5499b0c19bce086c9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 31895 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 diff --git a/packages/smooth_app/assets/app/release_icon_transparent_70pct_1152x1152.png b/packages/smooth_app/assets/app/release_icon_transparent_70pct_1152x1152.png deleted file mode 100644 index 630cf46a6c3ec001dfc8f5248fea9be7ddae6a55..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 25698 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 I-@2XsH-WJ@Q2+n{ diff --git a/packages/smooth_app/pubspec.yaml b/packages/smooth_app/pubspec.yaml index a71a5ab2399..4035f79e43e 100644 --- a/packages/smooth_app/pubspec.yaml +++ b/packages/smooth_app/pubspec.yaml @@ -108,7 +108,6 @@ dev_dependencies: sdk: flutter flutter_driver: sdk: flutter - flutter_launcher_icons: 0.13.1 flutter_test: sdk: flutter mockito: 5.4.4 @@ -118,18 +117,6 @@ dev_dependencies: openfoodfacts_flutter_lints: git: https://github.com/openfoodfacts/openfoodfacts_flutter_lints.git -# 'flutter pub run flutter_launcher_icons:main' to update -flutter_icons: - android: "launcher_icon" - ios: true - remove_alpha_ios: true - image_path: "assets/app/release_icon_transparent_1152x1152.png" - adaptive_icon_background: "#FFFFFF" - # Only the inner 72x72dp of the 108x108dp adaptive icon is shown - # (extra padding of 18dp on all sides is used for visual effects) - # https://developer.android.com/guide/practices/ui_guidelines/icon_design_adaptive - adaptive_icon_foreground: "assets/app/release_icon_transparent_70pct_1152x1152.png" - # 'flutter pub run flutter_native_splash:create' to update flutter_native_splash: color: "#FFFFFF" From 19ed4f0e74e7487662822f483d9c06f38eaad4eb Mon Sep 17 00:00:00 2001 From: monsieurtanuki Date: Mon, 7 Oct 2024 19:30:54 +0200 Subject: [PATCH 4/5] feat: 5326 - add prices to proof from gallery (and thumbnails) (#5670) * feat: 5326 - add prices to proof from gallery (and thumbnails) New files: * `background_task_add_other_price.dart`: Background task about adding prices to an existing proof. * `background_task_price.dart`: Abstract background task about adding prices. Impacted files: * `background_task_add_price.dart`: refactored using new class `BackgroundTaskPrice` * `get_prices_model.dart`: new web app root address * `operation_type.dart`: added the new background task * `osm_location.dart`: minor refactoring * `price_currency_selector.dart`: additional case - proof instead of input; refactored using model * `price_date_card.dart`: additional case - proof instead of input * `price_location_card.dart`: additional case - proof instead of input * `price_model.dart`: added specific constructor and background task for the "existing proof" case * `price_product_widget.dart`: minor refactoring * `price_proof_card.dart`: additional case - proof instead of input * `price_proof_page.dart`: added a FAB to add price from an existing proof; now displaying the thumbnail and then the full proof * `price_user_button.dart`: new web app root address * `prices_proofs_page.dart`: proof thumnails; new web app root address * `prices_users_page.dart`: new web app root address * `product_price_add_page.dart`: now accepting a model as parameter, in order to deal with both old proofs and new prices * `pubspec.lock`: wtf * `pubspec.yaml`: upgraded to `openfoodfacts` `3.16.0`, for proof thumbnails * `user_preferences_account.dart`: new web app root address * More flexibility with potentially null data Impacted files: * `osm_location.dart`: more flexibility with potentially null data * `price_model.dart`: more flexibility with potentially null data; better display of thumbnail image * `price_proof_page.dart`: more flexibility with potentially null data --- .../background_task_add_other_price.dart | 123 ++++++++++ .../background/background_task_add_price.dart | 206 ++-------------- .../lib/background/background_task_price.dart | 226 ++++++++++++++++++ .../lib/background/operation_type.dart | 4 + .../lib/pages/locations/osm_location.dart | 14 ++ .../preferences/user_preferences_account.dart | 6 +- .../lib/pages/prices/get_prices_model.dart | 2 +- .../pages/prices/price_currency_selector.dart | 38 +-- .../lib/pages/prices/price_date_card.dart | 51 ++-- .../lib/pages/prices/price_location_card.dart | 69 +++--- .../lib/pages/prices/price_model.dart | 94 ++++++-- .../pages/prices/price_product_widget.dart | 2 +- .../lib/pages/prices/price_proof_card.dart | 57 +++-- .../lib/pages/prices/price_proof_page.dart | 60 ++++- .../lib/pages/prices/price_user_button.dart | 2 +- .../lib/pages/prices/prices_proofs_page.dart | 3 +- .../lib/pages/prices/prices_users_page.dart | 9 +- .../pages/prices/product_price_add_page.dart | 38 +-- packages/smooth_app/pubspec.lock | 4 +- packages/smooth_app/pubspec.yaml | 2 +- 20 files changed, 673 insertions(+), 337 deletions(-) create mode 100644 packages/smooth_app/lib/background/background_task_add_other_price.dart create mode 100644 packages/smooth_app/lib/background/background_task_price.dart diff --git a/packages/smooth_app/lib/background/background_task_add_other_price.dart b/packages/smooth_app/lib/background/background_task_add_other_price.dart new file mode 100644 index 00000000000..ee0c6bbdaf7 --- /dev/null +++ b/packages/smooth_app/lib/background/background_task_add_other_price.dart @@ -0,0 +1,123 @@ +import 'dart:async'; + +import 'package:flutter/material.dart'; +import 'package:openfoodfacts/openfoodfacts.dart'; +import 'package:provider/provider.dart'; +import 'package:smooth_app/background/background_task.dart'; +import 'package:smooth_app/background/background_task_price.dart'; +import 'package:smooth_app/background/operation_type.dart'; +import 'package:smooth_app/database/local_database.dart'; + +/// Background task about adding prices to an existing proof. +class BackgroundTaskAddOtherPrice extends BackgroundTaskPrice { + BackgroundTaskAddOtherPrice._({ + required super.processName, + required super.uniqueId, + required super.stamp, + // single + required this.proofId, + required super.date, + required super.currency, + required super.locationOSMId, + required super.locationOSMType, + // multi + required super.barcodes, + required super.pricesAreDiscounted, + required super.prices, + required super.pricesWithoutDiscount, + }); + + BackgroundTaskAddOtherPrice.fromJson(super.json) + : proofId = json[_jsonTagProofId] as int, + super.fromJson(); + + static const String _jsonTagProofId = 'proofId'; + + static const OperationType _operationType = OperationType.addOtherPrice; + + final int proofId; + + @override + Map toJson() { + final Map result = super.toJson(); + result[_jsonTagProofId] = proofId; + return result; + } + + /// Adds the background task about uploading a product image. + static Future addTask({ + required final BuildContext context, + required final int proofId, + required final DateTime date, + required final Currency currency, + required final int locationOSMId, + required final LocationOSMType locationOSMType, + required final List barcodes, + required final List pricesAreDiscounted, + required final List prices, + required final List pricesWithoutDiscount, + }) async { + final LocalDatabase localDatabase = context.read(); + final String uniqueId = await _operationType.getNewKey(localDatabase); + final BackgroundTask task = _getNewTask( + uniqueId: uniqueId, + proofId: proofId, + date: date, + currency: currency, + locationOSMId: locationOSMId, + locationOSMType: locationOSMType, + barcodes: barcodes, + pricesAreDiscounted: pricesAreDiscounted, + prices: prices, + pricesWithoutDiscount: pricesWithoutDiscount, + ); + if (!context.mounted) { + return; + } + await task.addToManager(localDatabase, context: context); + } + + /// Returns a new background task about changing a product. + static BackgroundTaskAddOtherPrice _getNewTask({ + required final String uniqueId, + required final int proofId, + required final DateTime date, + required final Currency currency, + required final int locationOSMId, + required final LocationOSMType locationOSMType, + required final List barcodes, + required final List pricesAreDiscounted, + required final List prices, + required final List pricesWithoutDiscount, + }) => + BackgroundTaskAddOtherPrice._( + uniqueId: uniqueId, + processName: _operationType.processName, + proofId: proofId, + date: date, + currency: currency, + locationOSMId: locationOSMId, + locationOSMType: locationOSMType, + barcodes: barcodes, + pricesAreDiscounted: pricesAreDiscounted, + prices: prices, + pricesWithoutDiscount: pricesWithoutDiscount, + stamp: BackgroundTaskPrice.getStamp( + date: date, + locationOSMId: locationOSMId, + locationOSMType: locationOSMType, + ), + ); + + @override + Future execute(final LocalDatabase localDatabase) async { + final String bearerToken = await getBearerToken(); + + await addPrices( + bearerToken: bearerToken, + proofId: proofId, + ); + + await closeSession(bearerToken: bearerToken); + } +} diff --git a/packages/smooth_app/lib/background/background_task_add_price.dart b/packages/smooth_app/lib/background/background_task_add_price.dart index f7bcad2d5e1..8e21f72f5c2 100644 --- a/packages/smooth_app/lib/background/background_task_add_price.dart +++ b/packages/smooth_app/lib/background/background_task_add_price.dart @@ -2,12 +2,12 @@ import 'dart:async'; import 'package:crop_image/crop_image.dart'; 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.dart'; import 'package:smooth_app/background/background_task_image.dart'; +import 'package:smooth_app/background/background_task_price.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'; @@ -18,12 +18,12 @@ import 'package:smooth_app/query/product_query.dart'; // TODO(monsieurtanuki): use transient file, in order to have instant access to proof image? /// Background task about adding a product price. -class BackgroundTaskAddPrice extends BackgroundTask { +class BackgroundTaskAddPrice extends BackgroundTaskPrice { BackgroundTaskAddPrice._({ required super.processName, required super.uniqueId, required super.stamp, - // single + // proof display required this.fullPath, required this.rotationDegrees, required this.cropX1, @@ -31,17 +31,17 @@ class BackgroundTaskAddPrice extends BackgroundTask { required this.cropX2, required this.cropY2, required this.proofType, - required this.date, - required this.currency, - required this.locationOSMId, - required this.locationOSMType, - // lines required this.eraserCoordinates, + // single + required super.date, + required super.currency, + required super.locationOSMId, + required super.locationOSMType, // multi - required this.barcodes, - required this.pricesAreDiscounted, - required this.prices, - required this.pricesWithoutDiscount, + required super.barcodes, + required super.pricesAreDiscounted, + required super.prices, + required super.pricesWithoutDiscount, }); BackgroundTaskAddPrice.fromJson(super.json) @@ -52,73 +52,10 @@ class BackgroundTaskAddPrice extends BackgroundTask { cropX2 = json[_jsonTagX2] as int? ?? 0, cropY2 = json[_jsonTagY2] as int? ?? 0, proofType = ProofType.fromOffTag(json[_jsonTagProofType] as String)!, - date = JsonHelper.stringTimestampToDate(json[_jsonTagDate] as String), - currency = Currency.fromName(json[_jsonTagCurrency] as String)!, - locationOSMId = json[_jsonTagOSMId] as int, - locationOSMType = - LocationOSMType.fromOffTag(json[_jsonTagOSMType] as String)!, - eraserCoordinates = - _fromJsonListDouble(json[_jsonTagEraserCoordinates]), - barcodes = json.containsKey(_jsonTagBarcode) - ? [json[_jsonTagBarcode] as String] - : _fromJsonListString(json[_jsonTagBarcodes])!, - pricesAreDiscounted = json.containsKey(_jsonTagIsDiscounted) - ? [json[_jsonTagIsDiscounted] as bool] - : _fromJsonListBool(json[_jsonTagAreDiscounted])!, - prices = json.containsKey(_jsonTagPrice) - ? [json[_jsonTagPrice] as double] - : _fromJsonListDouble(json[_jsonTagPrices])!, - pricesWithoutDiscount = json.containsKey(_jsonTagPriceWithoutDiscount) - ? [json[_jsonTagPriceWithoutDiscount] as double?] - : _fromJsonListNullableDouble(json[_jsonTagPricesWithoutDiscount])!, + eraserCoordinates = BackgroundTaskPrice.fromJsonListDouble( + json[_jsonTagEraserCoordinates]), super.fromJson(); - static List? _fromJsonListDouble(final List? input) { - if (input == null) { - return null; - } - final List result = []; - for (final dynamic item in input) { - result.add(item as double); - } - return result; - } - - static List? _fromJsonListNullableDouble( - final List? input, - ) { - if (input == null) { - return null; - } - final List result = []; - for (final dynamic item in input) { - result.add(item as double?); - } - return result; - } - - static List? _fromJsonListString(final List? input) { - if (input == null) { - return null; - } - final List result = []; - for (final dynamic item in input) { - result.add(item as String); - } - return result; - } - - static List? _fromJsonListBool(final List? input) { - if (input == null) { - return null; - } - final List result = []; - for (final dynamic item in input) { - result.add(item as bool); - } - return result; - } - static const String _jsonTagImagePath = 'imagePath'; static const String _jsonTagRotation = 'rotation'; static const String _jsonTagX1 = 'x1'; @@ -126,23 +63,7 @@ class BackgroundTaskAddPrice extends BackgroundTask { static const String _jsonTagX2 = 'x2'; static const String _jsonTagY2 = 'y2'; static const String _jsonTagProofType = 'proofType'; - static const String _jsonTagDate = 'date'; static const String _jsonTagEraserCoordinates = 'eraserCoordinates'; - static const String _jsonTagCurrency = 'currency'; - static const String _jsonTagOSMId = 'osmId'; - static const String _jsonTagOSMType = 'osmType'; - static const String _jsonTagBarcodes = 'barcodes'; - static const String _jsonTagAreDiscounted = 'areDiscounted'; - static const String _jsonTagPrices = 'prices'; - static const String _jsonTagPricesWithoutDiscount = 'pricesWithoutDiscount'; - @Deprecated('Use [_jsonTagBarcodes] instead') - static const String _jsonTagBarcode = 'barcode'; - @Deprecated('Use [_jsonTagAreDiscounted] instead') - static const String _jsonTagIsDiscounted = 'isDiscounted'; - @Deprecated('Use [_jsonTagPrices] instead') - static const String _jsonTagPrice = 'price'; - @Deprecated('Use [_jsonTagPricesWithoutDiscount] instead') - static const String _jsonTagPriceWithoutDiscount = 'priceWithoutDiscount'; static const OperationType _operationType = OperationType.addPrice; @@ -153,18 +74,8 @@ class BackgroundTaskAddPrice extends BackgroundTask { final int cropX2; final int cropY2; final ProofType proofType; - final DateTime date; - final Currency currency; - final int locationOSMId; - final LocationOSMType locationOSMType; final List? eraserCoordinates; - // per line - final List barcodes; - final List pricesAreDiscounted; - final List prices; - final List pricesWithoutDiscount; - @override Map toJson() { final Map result = super.toJson(); @@ -175,15 +86,7 @@ class BackgroundTaskAddPrice extends BackgroundTask { 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[_jsonTagEraserCoordinates] = eraserCoordinates; - result[_jsonTagBarcodes] = barcodes; - result[_jsonTagAreDiscounted] = pricesAreDiscounted; - result[_jsonTagPrices] = prices; - result[_jsonTagPricesWithoutDiscount] = pricesWithoutDiscount; return result; } @@ -222,14 +125,6 @@ class BackgroundTaskAddPrice extends BackgroundTask { await task.addToManager(localDatabase, context: context); } - @override - (String, AlignmentGeometry)? getFloatingMessage( - final AppLocalizations appLocalizations) => - ( - appLocalizations.add_price_queued, - AlignmentDirectional.center, - ); - /// Returns a new background task about changing a product. static BackgroundTaskAddPrice _getNewTask({ required final String uniqueId, @@ -263,20 +158,13 @@ class BackgroundTaskAddPrice extends BackgroundTask { pricesAreDiscounted: pricesAreDiscounted, prices: prices, pricesWithoutDiscount: pricesWithoutDiscount, - stamp: _getStamp( + stamp: BackgroundTaskPrice.getStamp( date: date, locationOSMId: locationOSMId, locationOSMType: locationOSMType, ), ); - static String _getStamp({ - required final DateTime date, - required final int locationOSMId, - required final LocationOSMType locationOSMType, - }) => - 'no_barcode;price;$date;$locationOSMId;$locationOSMType'; - @override Future postExecute( final LocalDatabase localDatabase, @@ -297,9 +185,6 @@ class BackgroundTaskAddPrice extends BackgroundTask { } } - @override - Future preExecute(final LocalDatabase localDatabase) async {} - @override Future execute(final LocalDatabase localDatabase) async { final List offsets = []; @@ -340,21 +225,7 @@ class BackgroundTaskAddPrice extends BackgroundTask { return; } - // authentication - final User user = getUser(); - final MaybeError token = - await OpenPricesAPIClient.getAuthenticationToken( - username: user.userId, - password: user.password, - uriHelper: ProductQuery.uriPricesHelper, - ); - if (token.isError) { - throw Exception('Could not get token: ${token.error}'); - } - if (token.value.isEmpty) { - throw Exception('Unexpected empty token'); - } - final String bearerToken = token.value; + final String bearerToken = await getBearerToken(); // proof upload final Uri initialImageUri = Uri.parse(path); @@ -375,48 +246,11 @@ class BackgroundTaskAddPrice extends BackgroundTask { throw Exception('Could not upload proof: ${uploadProof.error}'); } - for (int i = 0; i < barcodes.length; i++) { - final Price newPrice = Price() - ..date = date - ..currency = currency - ..locationOSMId = locationOSMId - ..locationOSMType = locationOSMType - ..proofId = uploadProof.value.id - ..priceIsDiscounted = pricesAreDiscounted[i] - ..price = prices[i] - ..priceWithoutDiscount = pricesWithoutDiscount[i] - ..productCode = barcodes[i]; - - // create price - final MaybeError addedPrice = - await OpenPricesAPIClient.createPrice( - price: newPrice, - bearerToken: bearerToken, - uriHelper: ProductQuery.uriPricesHelper, - ); - if (addedPrice.isError) { - throw Exception('Could not add price: ${addedPrice.error}'); - } - } - - // close session - final MaybeError closedSession = - await OpenPricesAPIClient.deleteUserSession( - uriHelper: ProductQuery.uriPricesHelper, + await addPrices( bearerToken: bearerToken, + proofId: uploadProof.value.id, ); - if (closedSession.isError) { - // TODO(monsieurtanuki): do we really care? - // throw Exception('Could not close session: ${closedSession.error}'); - return; - } - if (!closedSession.value) { - // TODO(monsieurtanuki): do we really care? - // throw Exception('Could not really close session'); - return; - } - } - @override - bool isDeduplicable() => false; + await closeSession(bearerToken: bearerToken); + } } diff --git a/packages/smooth_app/lib/background/background_task_price.dart b/packages/smooth_app/lib/background/background_task_price.dart new file mode 100644 index 00000000000..8d5eb080d57 --- /dev/null +++ b/packages/smooth_app/lib/background/background_task_price.dart @@ -0,0 +1,226 @@ +import 'dart:async'; + +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.dart'; +import 'package:smooth_app/database/local_database.dart'; +import 'package:smooth_app/query/product_query.dart'; + +/// Abstract background task about adding prices. +abstract class BackgroundTaskPrice extends BackgroundTask { + BackgroundTaskPrice({ + required super.processName, + required super.uniqueId, + required super.stamp, + // single + required this.date, + required this.currency, + required this.locationOSMId, + required this.locationOSMType, + // multi + required this.barcodes, + required this.pricesAreDiscounted, + required this.prices, + required this.pricesWithoutDiscount, + }); + + BackgroundTaskPrice.fromJson(super.json) + : date = JsonHelper.stringTimestampToDate(json[_jsonTagDate] as String), + currency = Currency.fromName(json[_jsonTagCurrency] as String)!, + locationOSMId = json[_jsonTagOSMId] as int, + locationOSMType = + LocationOSMType.fromOffTag(json[_jsonTagOSMType] as String)!, + barcodes = json.containsKey(_jsonTagBarcode) + ? [json[_jsonTagBarcode] as String] + : _fromJsonListString(json[_jsonTagBarcodes])!, + pricesAreDiscounted = json.containsKey(_jsonTagIsDiscounted) + ? [json[_jsonTagIsDiscounted] as bool] + : _fromJsonListBool(json[_jsonTagAreDiscounted])!, + prices = json.containsKey(_jsonTagPrice) + ? [json[_jsonTagPrice] as double] + : fromJsonListDouble(json[_jsonTagPrices])!, + pricesWithoutDiscount = json.containsKey(_jsonTagPriceWithoutDiscount) + ? [json[_jsonTagPriceWithoutDiscount] as double?] + : _fromJsonListNullableDouble(json[_jsonTagPricesWithoutDiscount])!, + super.fromJson(); + + static const String _jsonTagDate = 'date'; + static const String _jsonTagCurrency = 'currency'; + static const String _jsonTagOSMId = 'osmId'; + static const String _jsonTagOSMType = 'osmType'; + static const String _jsonTagBarcodes = 'barcodes'; + static const String _jsonTagAreDiscounted = 'areDiscounted'; + static const String _jsonTagPrices = 'prices'; + static const String _jsonTagPricesWithoutDiscount = 'pricesWithoutDiscount'; + @Deprecated('Use [_jsonTagBarcodes] instead') + static const String _jsonTagBarcode = 'barcode'; + @Deprecated('Use [_jsonTagAreDiscounted] instead') + static const String _jsonTagIsDiscounted = 'isDiscounted'; + @Deprecated('Use [_jsonTagPrices] instead') + static const String _jsonTagPrice = 'price'; + @Deprecated('Use [_jsonTagPricesWithoutDiscount] instead') + static const String _jsonTagPriceWithoutDiscount = 'priceWithoutDiscount'; + + static List? fromJsonListDouble(final List? input) { + if (input == null) { + return null; + } + final List result = []; + for (final dynamic item in input) { + result.add(item as double); + } + return result; + } + + static List? _fromJsonListNullableDouble( + final List? input, + ) { + if (input == null) { + return null; + } + final List result = []; + for (final dynamic item in input) { + result.add(item as double?); + } + return result; + } + + static List? _fromJsonListString(final List? input) { + if (input == null) { + return null; + } + final List result = []; + for (final dynamic item in input) { + result.add(item as String); + } + return result; + } + + static List? _fromJsonListBool(final List? input) { + if (input == null) { + return null; + } + final List result = []; + for (final dynamic item in input) { + result.add(item as bool); + } + return result; + } + + final DateTime date; + final Currency currency; + final int locationOSMId; + final LocationOSMType locationOSMType; + + // per line + final List barcodes; + final List pricesAreDiscounted; + final List prices; + final List pricesWithoutDiscount; + + @override + Map toJson() { + final Map result = super.toJson(); + result[_jsonTagDate] = date.toIso8601String(); + result[_jsonTagCurrency] = currency.name; + result[_jsonTagOSMId] = locationOSMId; + result[_jsonTagOSMType] = locationOSMType.offTag; + result[_jsonTagBarcodes] = barcodes; + result[_jsonTagAreDiscounted] = pricesAreDiscounted; + result[_jsonTagPrices] = prices; + result[_jsonTagPricesWithoutDiscount] = pricesWithoutDiscount; + return result; + } + + @override + (String, AlignmentGeometry)? getFloatingMessage( + final AppLocalizations appLocalizations) => + ( + appLocalizations.add_price_queued, + AlignmentDirectional.center, + ); + + @protected + static String getStamp({ + required final DateTime date, + required final int locationOSMId, + required final LocationOSMType locationOSMType, + }) => + 'no_barcode;price;$date;$locationOSMId;$locationOSMType'; + + @override + Future preExecute(final LocalDatabase localDatabase) async {} + + @protected + Future getBearerToken() async { + final User user = getUser(); + final MaybeError token = + await OpenPricesAPIClient.getAuthenticationToken( + username: user.userId, + password: user.password, + uriHelper: ProductQuery.uriPricesHelper, + ); + if (token.isError) { + throw Exception('Could not get token: ${token.error}'); + } + if (token.value.isEmpty) { + throw Exception('Unexpected empty token'); + } + return token.value; + } + + @protected + Future addPrices({ + required final String bearerToken, + required final int proofId, + }) async { + for (int i = 0; i < barcodes.length; i++) { + final Price newPrice = Price() + ..date = date + ..currency = currency + ..locationOSMId = locationOSMId + ..locationOSMType = locationOSMType + ..proofId = proofId + ..priceIsDiscounted = pricesAreDiscounted[i] + ..price = prices[i] + ..priceWithoutDiscount = pricesWithoutDiscount[i] + ..productCode = barcodes[i]; + + // create price + final MaybeError addedPrice = + await OpenPricesAPIClient.createPrice( + price: newPrice, + bearerToken: bearerToken, + uriHelper: ProductQuery.uriPricesHelper, + ); + if (addedPrice.isError) { + throw Exception('Could not add price: ${addedPrice.error}'); + } + } + } + + @protected + Future closeSession({ + required final String bearerToken, + }) async { + final MaybeError closedSession = + await OpenPricesAPIClient.deleteUserSession( + uriHelper: ProductQuery.uriPricesHelper, + bearerToken: bearerToken, + ); + if (closedSession.isError) { + // TODO(monsieurtanuki): do we really care? + // throw Exception('Could not close session: ${closedSession.error}'); + return; + } + if (!closedSession.value) { + // TODO(monsieurtanuki): do we really care? + // throw Exception('Could not really close session'); + return; + } + } + + @override + bool isDeduplicable() => false; +} diff --git a/packages/smooth_app/lib/background/operation_type.dart b/packages/smooth_app/lib/background/operation_type.dart index 92cee088a82..f1d83e874ce 100644 --- a/packages/smooth_app/lib/background/operation_type.dart +++ b/packages/smooth_app/lib/background/operation_type.dart @@ -1,5 +1,6 @@ import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:smooth_app/background/background_task.dart'; +import 'package:smooth_app/background/background_task_add_other_price.dart'; import 'package:smooth_app/background/background_task_add_price.dart'; import 'package:smooth_app/background/background_task_crop.dart'; import 'package:smooth_app/background/background_task_details.dart'; @@ -37,6 +38,7 @@ enum OperationType { fullRefresh('F', 'FULL_REFRESH'), languageRefresh('L', 'LANGUAGE_REFRESH'), addPrice('A', 'ADD_PRICE'), + addOtherPrice('E', 'ADD_OTHER_PRICE'), details('D', 'PRODUCT_EDIT'); const OperationType(this.header, this.processName); @@ -70,6 +72,7 @@ enum OperationType { BackgroundTask fromJson(Map map) => switch (this) { crop => BackgroundTaskCrop.fromJson(map), addPrice => BackgroundTaskAddPrice.fromJson(map), + addOtherPrice => BackgroundTaskAddOtherPrice.fromJson(map), details => BackgroundTaskDetails.fromJson(map), hungerGames => BackgroundTaskHungerGames.fromJson(map), image => BackgroundTaskImage.fromJson(map), @@ -89,6 +92,7 @@ enum OperationType { OperationType.details => appLocalizations.background_task_operation_details, OperationType.addPrice => 'Add price', + OperationType.addOtherPrice => 'Add price to existing proof', OperationType.image => appLocalizations.background_task_operation_image, OperationType.unselect => 'Unselect a product image', OperationType.hungerGames => 'Answering to a Hunger Games question', diff --git a/packages/smooth_app/lib/pages/locations/osm_location.dart b/packages/smooth_app/lib/pages/locations/osm_location.dart index dd4742ed3ae..d66733e76c9 100644 --- a/packages/smooth_app/lib/pages/locations/osm_location.dart +++ b/packages/smooth_app/lib/pages/locations/osm_location.dart @@ -18,6 +18,20 @@ class OsmLocation { this.osmValue, }); + OsmLocation.fromPrice(final Location location) + : osmId = location.osmId, + osmType = location.type, + longitude = location.longitude ?? 0, + latitude = location.latitude ?? 0, + name = location.name, + street = null, + city = location.city, + postcode = location.postcode, + country = location.country, + countryCode = location.countryCode, + osmKey = location.osmKey, + osmValue = location.osmValue; + final int osmId; final LocationOSMType osmType; final double longitude; diff --git a/packages/smooth_app/lib/pages/preferences/user_preferences_account.dart b/packages/smooth_app/lib/pages/preferences/user_preferences_account.dart index 2418130f07d..32340b08fcd 100644 --- a/packages/smooth_app/lib/pages/preferences/user_preferences_account.dart +++ b/packages/smooth_app/lib/pages/preferences/user_preferences_account.dart @@ -272,7 +272,7 @@ class UserPreferencesAccount extends AbstractUserPreferences { displayOwner: true, displayProduct: true, uri: OpenPricesAPIClient.getUri( - path: 'app/prices', + path: 'prices', uriHelper: ProductQuery.uriPricesHelper, ), title: appLocalizations.all_search_prices_latest_title, @@ -294,11 +294,11 @@ class UserPreferencesAccount extends AbstractUserPreferences { ), _getPriceListTile( appLocalizations.all_search_prices_top_location_title, - 'app/locations', + 'locations', ), _getPriceListTile( appLocalizations.all_search_prices_top_product_title, - 'app/products', + 'products', ), _buildProductQueryTile( productQuery: PagedToBeCompletedProductQuery( diff --git a/packages/smooth_app/lib/pages/prices/get_prices_model.dart b/packages/smooth_app/lib/pages/prices/get_prices_model.dart index 84b5333f4f7..765a3b8bba2 100644 --- a/packages/smooth_app/lib/pages/prices/get_prices_model.dart +++ b/packages/smooth_app/lib/pages/prices/get_prices_model.dart @@ -56,7 +56,7 @@ class GetPricesModel { final String barcode, ) => OpenPricesAPIClient.getUri( - path: 'app/products/$barcode', + path: 'products/$barcode', uriHelper: ProductQuery.uriPricesHelper, ); diff --git a/packages/smooth_app/lib/pages/prices/price_currency_selector.dart b/packages/smooth_app/lib/pages/prices/price_currency_selector.dart index f0941f22db1..77ca9ffafab 100644 --- a/packages/smooth_app/lib/pages/prices/price_currency_selector.dart +++ b/packages/smooth_app/lib/pages/prices/price_currency_selector.dart @@ -1,36 +1,38 @@ import 'package:flutter/material.dart'; import 'package:openfoodfacts/openfoodfacts.dart'; import 'package:provider/provider.dart'; -import 'package:smooth_app/data_models/preferences/user_preferences.dart'; import 'package:smooth_app/generic_lib/buttons/smooth_large_button_with_icon.dart'; import 'package:smooth_app/pages/onboarding/currency_selector_helper.dart'; import 'package:smooth_app/pages/prices/currency_extension.dart'; +import 'package:smooth_app/pages/prices/price_model.dart'; /// Button that displays the currency for price adding. class PriceCurrencySelector extends StatelessWidget { PriceCurrencySelector(); - final CurrencySelectorHelper helper = CurrencySelectorHelper(); + final CurrencySelectorHelper _helper = CurrencySelectorHelper(); @override Widget build(BuildContext context) { - // TODO(monsieurtanuki): use PriceModel for currency? - final UserPreferences userPreferences = context.watch(); - final Currency selected = helper.getSelected( - userPreferences.userCurrencyCode, - ); + final PriceModel model = context.watch(); return SmoothLargeButtonWithIcon( - onPressed: () async { - final Currency? currency = await helper.openCurrencySelector( - context: context, - selected: selected, - ); - if (currency != null) { - await userPreferences.setUserCurrencyCode(currency.name); - } - }, - text: selected.getFullName(), - icon: helper.currencyIconData, + onPressed: model.proof != null + ? null + : () async { + final Currency? currency = await _helper.openCurrencySelector( + context: context, + selected: model.currency, + ); + if (currency == null) { + return; + } + if (!context.mounted) { + return; + } + model.currency = currency; + }, + text: model.currency.getFullName(), + icon: _helper.currencyIconData, ); } } diff --git a/packages/smooth_app/lib/pages/prices/price_date_card.dart b/packages/smooth_app/lib/pages/prices/price_date_card.dart index 7a48e3864c3..4acce28ac87 100644 --- a/packages/smooth_app/lib/pages/prices/price_date_card.dart +++ b/packages/smooth_app/lib/pages/prices/price_date_card.dart @@ -24,30 +24,33 @@ class PriceDateCard extends StatelessWidget { SmoothLargeButtonWithIcon( text: dateFormat.format(model.date), icon: Icons.calendar_month, - onPressed: () async { - final DateTime? newDate = await showDatePicker( - context: context, - locale: Locale(ProductQuery.getLanguage().offTag), - firstDate: model.firstDate, - lastDate: model.today, - builder: (final BuildContext context, final Widget? child) { - // for some reason we don't have a fine display without that theme. - // cf. https://stackoverflow.com/questions/50321182/how-to-customize-a-date-picker - final ThemeData themeData = - Theme.of(context).brightness == Brightness.light - ? ThemeData.light() - : ThemeData.dark(); - return Theme( - data: themeData.copyWith(), - child: child!, - ); - }, - ); - if (newDate == null) { - return; - } - model.date = newDate; - }, + onPressed: model.proof != null + ? null + : () async { + final DateTime? newDate = await showDatePicker( + context: context, + locale: Locale(ProductQuery.getLanguage().offTag), + firstDate: model.firstDate, + lastDate: model.today, + builder: + (final BuildContext context, final Widget? child) { + // for some reason we don't have a fine display without that theme. + // cf. https://stackoverflow.com/questions/50321182/how-to-customize-a-date-picker + final ThemeData themeData = + Theme.of(context).brightness == Brightness.light + ? ThemeData.light() + : ThemeData.dark(); + return Theme( + data: themeData.copyWith(), + child: child!, + ); + }, + ); + if (newDate == null) { + return; + } + model.date = newDate; + }, ), ], ), diff --git a/packages/smooth_app/lib/pages/prices/price_location_card.dart b/packages/smooth_app/lib/pages/prices/price_location_card.dart index ad34b1457d6..32b920fbba8 100644 --- a/packages/smooth_app/lib/pages/prices/price_location_card.dart +++ b/packages/smooth_app/lib/pages/prices/price_location_card.dart @@ -35,39 +35,42 @@ class PriceLocationCard extends StatelessWidget { location.getSubtitle() ?? location.getLatLng().toString(), icon: location == null ? _iconTodo : _iconDone, - onPressed: () async { - final LocalDatabase localDatabase = context.read(); - final List preloadedList = - []; - for (final OsmLocation osmLocation in model.locations) { - preloadedList.add( - SearchLocationPreloadedItem( - osmLocation, - popFirst: false, - ), - ); - } - final OsmLocation? osmLocation = - await Navigator.push( - context, - MaterialPageRoute( - builder: (BuildContext context) => SearchPage( - SearchLocationHelper(), - preloadedList: preloadedList, - autofocus: false, - ), - ), - ); - if (osmLocation == null) { - return; - } - final DaoOsmLocation daoOsmLocation = - DaoOsmLocation(localDatabase); - await daoOsmLocation.put(osmLocation); - final List newOsmLocations = - await daoOsmLocation.getAll(); - model.locations = newOsmLocations; - }, + onPressed: model.proof != null + ? null + : () async { + final LocalDatabase localDatabase = + context.read(); + final List preloadedList = + []; + for (final OsmLocation osmLocation in model.locations!) { + preloadedList.add( + SearchLocationPreloadedItem( + osmLocation, + popFirst: false, + ), + ); + } + final OsmLocation? osmLocation = + await Navigator.push( + context, + MaterialPageRoute( + builder: (BuildContext context) => SearchPage( + SearchLocationHelper(), + preloadedList: preloadedList, + autofocus: false, + ), + ), + ); + if (osmLocation == null) { + return; + } + final DaoOsmLocation daoOsmLocation = + DaoOsmLocation(localDatabase); + await daoOsmLocation.put(osmLocation); + final List newOsmLocations = + await daoOsmLocation.getAll(); + model.locations = newOsmLocations; + }, ), ], ), diff --git a/packages/smooth_app/lib/pages/prices/price_model.dart b/packages/smooth_app/lib/pages/prices/price_model.dart index 4deeb56f153..24cf51a9fbc 100644 --- a/packages/smooth_app/lib/pages/prices/price_model.dart +++ b/packages/smooth_app/lib/pages/prices/price_model.dart @@ -1,12 +1,14 @@ +import 'dart:async'; + 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/background_task_add_other_price.dart'; import 'package:smooth_app/background/background_task_add_price.dart'; import 'package:smooth_app/data_models/preferences/user_preferences.dart'; import 'package:smooth_app/pages/crop_parameters.dart'; import 'package:smooth_app/pages/locations/osm_location.dart'; -import 'package:smooth_app/pages/onboarding/currency_selector_helper.dart'; import 'package:smooth_app/pages/prices/price_amount_model.dart'; import 'package:smooth_app/pages/prices/price_meta_product.dart'; @@ -14,15 +16,39 @@ import 'package:smooth_app/pages/prices/price_meta_product.dart'; class PriceModel with ChangeNotifier { PriceModel({ required final ProofType proofType, - required final List locations, + required final List? locations, + required final Currency currency, final PriceMetaProduct? initialProduct, - }) : _proofType = proofType, + }) : proof = null, + _proofType = proofType, _date = DateTime.now(), + _currency = currency, _locations = locations, priceAmountModels = [ if (initialProduct != null) PriceAmountModel(product: initialProduct), ]; + PriceModel.proof({ + required Proof this.proof, + }) : _proofType = proof.type!, + _date = proof.date!, + _locations = null, + _currency = proof.currency!, + priceAmountModels = []; + + /// Checks if a proof cannot be reused for prices adding. + /// + /// Sometimes we get partial data from the Prices server. + static bool isProofNotGoodEnough(final Proof proof) => + proof.currency == null || + proof.date == null || + proof.type == null || + proof.location == null || + proof.locationOSMId == null || + proof.locationOSMType == null || + proof.imageThumbPath == null || + proof.filePath == null; + final List priceAmountModels; CropParameters? _cropParameters; @@ -34,9 +60,11 @@ class PriceModel with ChangeNotifier { notifyListeners(); } + final Proof? proof; + ProofType _proofType; - ProofType get proofType => _proofType; + ProofType get proofType => proof != null ? proof!.type! : _proofType; set proofType(final ProofType proofType) { _proofType = proofType; @@ -55,18 +83,27 @@ class PriceModel with ChangeNotifier { final DateTime today = DateTime.now(); final DateTime firstDate = DateTime.utc(2020, 1, 1); - late List _locations; + List? _locations; - List get locations => _locations; + List? get locations => _locations; - set locations(final List locations) { + set locations(final List? locations) { _locations = locations; notifyListeners(); } - OsmLocation? get location => _locations.firstOrNull; + OsmLocation? get location => proof != null + ? OsmLocation.fromPrice(proof!.location!) + : _locations!.firstOrNull; + + Currency _currency; - late Currency _checkedCurrency; + Currency get currency => _currency; + + set currency(final Currency currency) { + _currency = currency; + notifyListeners(); + } // overriding in order to make it public @override @@ -75,14 +112,15 @@ class PriceModel with ChangeNotifier { /// Returns the error message of the parameter check, or null if OK. String? checkParameters(final BuildContext context) { final AppLocalizations appLocalizations = AppLocalizations.of(context); - if (cropParameters == null) { - return appLocalizations.prices_proof_mandatory; + if (proof == null) { + if (cropParameters == null) { + return appLocalizations.prices_proof_mandatory; + } + if (location == null) { + return appLocalizations.prices_location_mandatory; + } } - final UserPreferences userPreferences = context.read(); - _checkedCurrency = - CurrencySelectorHelper().getSelected(userPreferences.userCurrencyCode); - for (final PriceAmountModel priceAmountModel in priceAmountModels) { final String? checkParameters = priceAmountModel.checkParameters(context); if (checkParameters != null) { @@ -90,9 +128,8 @@ class PriceModel with ChangeNotifier { } } - if (location == null) { - return appLocalizations.prices_location_mandatory; - } + final UserPreferences userPreferences = context.read(); + unawaited(userPreferences.setUserCurrencyCode(currency.name)); return null; } @@ -109,15 +146,32 @@ class PriceModel with ChangeNotifier { prices.add(priceAmountModel.checkedPaidPrice); pricesWithoutDiscount.add(priceAmountModel.checkedPriceWithoutDiscount); } + if (proof != null) { + return BackgroundTaskAddOtherPrice.addTask( + context: context, + // per receipt + locationOSMId: proof!.locationOSMId!, + locationOSMType: proof!.locationOSMType!, + date: proof!.date!, + currency: proof!.currency!, + proofId: proof!.id, + // per item + barcodes: barcodes, + pricesAreDiscounted: pricesAreDiscounted, + prices: prices, + pricesWithoutDiscount: pricesWithoutDiscount, + ); + } return BackgroundTaskAddPrice.addTask( context: context, - // per receipt + // proof display cropObject: cropParameters!, + // per receipt locationOSMId: location!.osmId, locationOSMType: location!.osmType, date: date, proofType: proofType, - currency: _checkedCurrency, + currency: currency, // per item barcodes: barcodes, pricesAreDiscounted: pricesAreDiscounted, diff --git a/packages/smooth_app/lib/pages/prices/price_product_widget.dart b/packages/smooth_app/lib/pages/prices/price_product_widget.dart index 776b9445cc0..ef898f5ccd4 100644 --- a/packages/smooth_app/lib/pages/prices/price_product_widget.dart +++ b/packages/smooth_app/lib/pages/prices/price_product_widget.dart @@ -26,7 +26,7 @@ class PriceProductWidget extends StatelessWidget { final String name = priceProduct.name ?? priceProduct.code; final bool unknown = priceProduct.name == null; final String? imageURL = priceProduct.imageURL; - final int priceCount = priceProduct.priceCount; + final int priceCount = priceProduct.priceCount ?? 0; final List? brands = priceProduct.brands == '' ? null : priceProduct.brands?.split(','); final String? quantity = priceProduct.quantity == null diff --git a/packages/smooth_app/lib/pages/prices/price_proof_card.dart b/packages/smooth_app/lib/pages/prices/price_proof_card.dart index d972ae2932a..091d595d189 100644 --- a/packages/smooth_app/lib/pages/prices/price_proof_card.dart +++ b/packages/smooth_app/lib/pages/prices/price_proof_card.dart @@ -11,6 +11,7 @@ import 'package:smooth_app/pages/crop_parameters.dart'; import 'package:smooth_app/pages/image_crop_page.dart'; import 'package:smooth_app/pages/prices/price_model.dart'; import 'package:smooth_app/pages/proof_crop_helper.dart'; +import 'package:smooth_app/query/product_query.dart'; /// Card that displays the proof for price adding. class PriceProofCard extends StatelessWidget { @@ -27,6 +28,20 @@ class PriceProofCard extends StatelessWidget { child: Column( children: [ Text(appLocalizations.prices_proof_subtitle), + if (model.proof != null) + LayoutBuilder( + builder: (BuildContext context, BoxConstraints constraints) => + Image( + image: NetworkImage( + model.proof! + .getFileUrl( + uriProductHelper: ProductQuery.uriPricesHelper, + isThumbnail: true, + )! + .toString(), + ), + ), + ), if (model.cropParameters != null) LayoutBuilder( builder: (BuildContext context, BoxConstraints constraints) => @@ -40,23 +55,27 @@ class PriceProofCard extends StatelessWidget { ), //Text(model.cropParameters!.smallCroppedFile.path), SmoothLargeButtonWithIcon( - text: model.cropParameters == null + text: model.proof == null && model.cropParameters == null ? appLocalizations.prices_proof_find : model.proofType == ProofType.receipt ? appLocalizations.prices_proof_receipt : appLocalizations.prices_proof_price_tag, - icon: model.cropParameters == null ? _iconTodo : _iconDone, - onPressed: () async { - final CropParameters? cropParameters = - await confirmAndUploadNewImage( - context, - cropHelper: ProofCropHelper(model: model), - isLoggedInMandatory: true, - ); - if (cropParameters != null) { - model.cropParameters = cropParameters; - } - }, + icon: model.proof == null && model.cropParameters == null + ? _iconTodo + : _iconDone, + onPressed: model.proof != null + ? null + : () async { + final CropParameters? cropParameters = + await confirmAndUploadNewImage( + context, + cropHelper: ProofCropHelper(model: model), + isLoggedInMandatory: true, + ); + if (cropParameters != null) { + model.cropParameters = cropParameters; + } + }, ), LayoutBuilder( builder: (BuildContext context, BoxConstraints constraints) => Row( @@ -67,8 +86,10 @@ class PriceProofCard extends StatelessWidget { title: Text(appLocalizations.prices_proof_receipt), value: ProofType.receipt, groupValue: model.proofType, - onChanged: (final ProofType? proofType) => - model.proofType = proofType!, + onChanged: model.proof != null + ? null + : (final ProofType? proofType) => + model.proofType = proofType!, ), ), SizedBox( @@ -77,8 +98,10 @@ class PriceProofCard extends StatelessWidget { title: Text(appLocalizations.prices_proof_price_tag), value: ProofType.priceTag, groupValue: model.proofType, - onChanged: (final ProofType? proofType) => - model.proofType = proofType!, + onChanged: model.proof != null + ? null + : (final ProofType? proofType) => + model.proofType = proofType!, ), ), ], diff --git a/packages/smooth_app/lib/pages/prices/price_proof_page.dart b/packages/smooth_app/lib/pages/prices/price_proof_page.dart index 84a8eea48e9..7077340dc7c 100644 --- a/packages/smooth_app/lib/pages/prices/price_proof_page.dart +++ b/packages/smooth_app/lib/pages/prices/price_proof_page.dart @@ -3,6 +3,9 @@ import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:intl/intl.dart'; import 'package:openfoodfacts/openfoodfacts.dart'; import 'package:smooth_app/helpers/launch_url_helper.dart'; +import 'package:smooth_app/pages/prices/price_model.dart'; +import 'package:smooth_app/pages/prices/product_price_add_page.dart'; +import 'package:smooth_app/pages/product/common/product_refresher.dart'; import 'package:smooth_app/query/product_query.dart'; import 'package:smooth_app/widgets/smooth_app_bar.dart'; import 'package:smooth_app/widgets/smooth_scaffold.dart'; @@ -21,6 +24,30 @@ class PriceProofPage extends StatelessWidget { final DateFormat dateFormat = DateFormat.yMd(ProductQuery.getLocaleString()).add_Hms(); return SmoothScaffold( + floatingActionButton: PriceModel.isProofNotGoodEnough(proof) + ? null + : FloatingActionButton.extended( + label: Text(appLocalizations.prices_add_a_price), + icon: const Icon(Icons.add), + onPressed: () async { + if (!await ProductRefresher().checkIfLoggedIn( + context, + isLoggedInMandatory: true, + )) { + return; + } + if (!context.mounted) { + return; + } + await Navigator.of(context).push( + MaterialPageRoute( + builder: (BuildContext context) => ProductPriceAddPage( + PriceModel.proof(proof: proof), + ), + ), + ); + }, + ), appBar: SmoothAppBar( title: Text(appLocalizations.user_search_proof_title), subTitle: Text(dateFormat.format(proof.created)), @@ -28,18 +55,39 @@ class PriceProofPage extends StatelessWidget { IconButton( tooltip: appLocalizations.prices_app_button, icon: const Icon(Icons.open_in_new), - onPressed: () async => LaunchUrlHelper.launchURL(_getUrl()), + onPressed: () async => LaunchUrlHelper.launchURL(_getUrl(true)), ), ], ), - body: Image( - image: NetworkImage(_getUrl()), - fit: BoxFit.cover, + body: Center( + child: Image.network( + _getUrl(false), + fit: BoxFit.cover, + loadingBuilder: (BuildContext context, Widget child, + ImageChunkEvent? loadingProgress) { + if (loadingProgress == null) { + return child; + } + return Center( + child: SizedBox( + width: double.maxFinite, + height: double.maxFinite, + child: Image.network( + _getUrl(true), + fit: BoxFit.contain, + ), + ), + ); + }, + ), ), ); } - String _getUrl() => proof - .getFileUrl(uriProductHelper: ProductQuery.uriPricesHelper) + String _getUrl(final bool isThumbnail) => proof + .getFileUrl( + uriProductHelper: ProductQuery.uriPricesHelper, + isThumbnail: isThumbnail, + ) .toString(); } diff --git a/packages/smooth_app/lib/pages/prices/price_user_button.dart b/packages/smooth_app/lib/pages/prices/price_user_button.dart index c9521ab4fc2..070af93e861 100644 --- a/packages/smooth_app/lib/pages/prices/price_user_button.dart +++ b/packages/smooth_app/lib/pages/prices/price_user_button.dart @@ -41,7 +41,7 @@ class PriceUserButton extends StatelessWidget { displayOwner: false, displayProduct: true, uri: OpenPricesAPIClient.getUri( - path: 'app/users/$user', + path: 'users/$user', uriHelper: ProductQuery.uriPricesHelper, ), title: showUserTitle(user: user, context: context), diff --git a/packages/smooth_app/lib/pages/prices/prices_proofs_page.dart b/packages/smooth_app/lib/pages/prices/prices_proofs_page.dart index d1a8d842f7d..baa2cf2ddbb 100644 --- a/packages/smooth_app/lib/pages/prices/prices_proofs_page.dart +++ b/packages/smooth_app/lib/pages/prices/prices_proofs_page.dart @@ -46,7 +46,7 @@ class _PricesProofsPageState extends State icon: const Icon(Icons.open_in_new), onPressed: () async => LaunchUrlHelper.launchURL( OpenPricesAPIClient.getUri( - path: 'app/dashboard/proofs', + path: 'dashboard/proofs', uriHelper: ProductQuery.uriPricesHelper, ).toString(), ), @@ -204,6 +204,7 @@ class _PriceProofImage extends StatelessWidget { proof .getFileUrl( uriProductHelper: ProductQuery.uriPricesHelper, + isThumbnail: true, ) .toString(), ), diff --git a/packages/smooth_app/lib/pages/prices/prices_users_page.dart b/packages/smooth_app/lib/pages/prices/prices_users_page.dart index 6e0b801c37d..f430bae6d40 100644 --- a/packages/smooth_app/lib/pages/prices/prices_users_page.dart +++ b/packages/smooth_app/lib/pages/prices/prices_users_page.dart @@ -45,7 +45,7 @@ class _PricesUsersPageState extends State icon: const Icon(Icons.open_in_new), onPressed: () async => LaunchUrlHelper.launchURL( OpenPricesAPIClient.getUri( - path: 'app/users', + path: 'users', uriHelper: ProductQuery.uriPricesHelper, ).toString(), ), @@ -79,6 +79,7 @@ class _PricesUsersPageState extends State final List children = []; for (final PriceUser item in result.items!) { + final int priceCount = item.priceCount ?? 0; children.add( SmoothCard( child: Wrap( @@ -91,13 +92,13 @@ class _PricesUsersPageState extends State context: context, ), iconData: Icons.label, - title: '${item.priceCount}', + title: '$priceCount', buttonStyle: ElevatedButton.styleFrom( foregroundColor: PriceCountWidget.getForegroundColor( - item.priceCount, + priceCount, ), backgroundColor: PriceCountWidget.getBackgroundColor( - item.priceCount, + priceCount, ), ), ), diff --git a/packages/smooth_app/lib/pages/prices/product_price_add_page.dart b/packages/smooth_app/lib/pages/prices/product_price_add_page.dart index 9276299c707..13d71fac9fc 100644 --- a/packages/smooth_app/lib/pages/prices/product_price_add_page.dart +++ b/packages/smooth_app/lib/pages/prices/product_price_add_page.dart @@ -10,6 +10,7 @@ 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/widgets/smooth_back_button.dart'; import 'package:smooth_app/pages/locations/osm_location.dart'; +import 'package:smooth_app/pages/onboarding/currency_selector_helper.dart'; import 'package:smooth_app/pages/prices/price_add_product_card.dart'; import 'package:smooth_app/pages/prices/price_amount_card.dart'; import 'package:smooth_app/pages/prices/price_currency_card.dart'; @@ -24,15 +25,11 @@ import 'package:smooth_app/widgets/smooth_scaffold.dart'; /// Single page that displays all the elements of price adding. class ProductPriceAddPage extends StatefulWidget { - const ProductPriceAddPage({ - required this.product, - required this.latestOsmLocations, - required this.proofType, - }); + const ProductPriceAddPage( + this.model, + ); - final PriceMetaProduct? product; - final List latestOsmLocations; - final ProofType proofType; + final PriceModel model; static Future showProductPage({ required final BuildContext context, @@ -55,12 +52,20 @@ class ProductPriceAddPage extends StatefulWidget { return; } + final UserPreferences userPreferences = context.read(); + final Currency currency = CurrencySelectorHelper().getSelected( + userPreferences.userCurrencyCode, + ); + await Navigator.of(context).push( MaterialPageRoute( builder: (BuildContext context) => ProductPriceAddPage( - product: product, - latestOsmLocations: osmLocations, - proofType: proofType, + PriceModel( + proofType: proofType, + locations: osmLocations, + initialProduct: product, + currency: currency, + ), ), ), ); @@ -72,19 +77,13 @@ class ProductPriceAddPage extends StatefulWidget { class _ProductPriceAddPageState extends State with TraceableClientMixin { - late final PriceModel _model = PriceModel( - proofType: widget.proofType, - locations: widget.latestOsmLocations, - initialProduct: widget.product, - ); - final GlobalKey _formKey = GlobalKey(); @override Widget build(BuildContext context) { // TODO(monsieurtanuki): add WillPopScope2 return ChangeNotifierProvider.value( - value: _model, + value: widget.model, builder: ( final BuildContext context, final Widget? child, @@ -231,5 +230,6 @@ class _ProductPriceAddPageState extends State } @override - String get actionName => 'Opened price_page with ${widget.proofType.offTag}'; + String get actionName => + 'Opened price_page with ${widget.model.proofType.offTag}'; } diff --git a/packages/smooth_app/pubspec.lock b/packages/smooth_app/pubspec.lock index ac741214a97..914ee0e48cd 100644 --- a/packages/smooth_app/pubspec.lock +++ b/packages/smooth_app/pubspec.lock @@ -1100,10 +1100,10 @@ packages: dependency: "direct main" description: name: openfoodfacts - sha256: "9ae8bfaed74c0e59bfe2cd217e2ad805d2d52710ec563861ea728176b8cd419f" + sha256: d35a213d6354246e3b27e0b18fe3def658e00c337346cf11c4f8ba082f92dfaa url: "https://pub.dev" source: hosted - version: "3.15.0" + version: "3.16.0" openfoodfacts_flutter_lints: dependency: "direct dev" description: diff --git a/packages/smooth_app/pubspec.yaml b/packages/smooth_app/pubspec.yaml index 4035f79e43e..66755982d44 100644 --- a/packages/smooth_app/pubspec.yaml +++ b/packages/smooth_app/pubspec.yaml @@ -99,7 +99,7 @@ dependencies: path: ../scanner/zxing - openfoodfacts: 3.15.0 + openfoodfacts: 3.16.0 # openfoodfacts: # path: ../../../openfoodfacts-dart From 56c2cf2a77ea1f2756b2766f52c53a4f678d6874 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 7 Oct 2024 19:32:55 +0200 Subject: [PATCH 5/5] chore(deps): bump go_router in /packages/smooth_app (#5672) Bumps [go_router](https://github.com/flutter/packages/tree/main/packages) from 14.2.8 to 14.3.0. - [Release notes](https://github.com/flutter/packages/releases) - [Commits](https://github.com/flutter/packages/commits/go_router-v14.3.0/packages) --- updated-dependencies: - dependency-name: go_router dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- packages/smooth_app/pubspec.lock | 28 ++-------------------------- packages/smooth_app/pubspec.yaml | 2 +- 2 files changed, 3 insertions(+), 27 deletions(-) diff --git a/packages/smooth_app/pubspec.lock b/packages/smooth_app/pubspec.lock index 914ee0e48cd..a9a0e8daeca 100644 --- a/packages/smooth_app/pubspec.lock +++ b/packages/smooth_app/pubspec.lock @@ -253,22 +253,6 @@ packages: url: "https://pub.dev" source: hosted version: "1.3.0" - checked_yaml: - dependency: transitive - description: - name: checked_yaml - sha256: feb6bed21949061731a7a75fc5d2aa727cf160b91af9a3e464c5e3a32e28b5ff - url: "https://pub.dev" - source: hosted - version: "2.0.3" - cli_util: - dependency: transitive - description: - name: cli_util - sha256: c05b7406fdabc7a49a3929d4af76bcaccbbffcbcdcf185b082e1ae07da323d19 - url: "https://pub.dev" - source: hosted - version: "0.4.1" clock: dependency: transitive description: @@ -623,14 +607,6 @@ packages: url: "https://pub.dev" source: hosted version: "0.1.4+1" - flutter_launcher_icons: - dependency: "direct dev" - description: - name: flutter_launcher_icons - sha256: "526faf84284b86a4cb36d20a5e45147747b7563d921373d4ee0559c54fcdbcea" - url: "https://pub.dev" - source: hosted - version: "0.13.1" flutter_lints: dependency: "direct dev" description: @@ -775,10 +751,10 @@ packages: dependency: "direct main" description: name: go_router - sha256: "5cf5fdcf853b0629deb35891c7af643be900c3dcaed7489009f9e7dbcfe55ab6" + sha256: "6f1b756f6e863259a99135ff3c95026c3cdca17d10ebef2bba2261a25ddc8bbc" url: "https://pub.dev" source: hosted - version: "14.2.8" + version: "14.3.0" graphs: dependency: transitive description: diff --git a/packages/smooth_app/pubspec.yaml b/packages/smooth_app/pubspec.yaml index 66755982d44..ad161077785 100644 --- a/packages/smooth_app/pubspec.yaml +++ b/packages/smooth_app/pubspec.yaml @@ -13,7 +13,7 @@ dependencies: sdk: flutter async: 2.11.0 - go_router: 14.2.8 + go_router: 14.3.0 barcode_widget: 2.0.4 carousel_slider: 5.0.0 cupertino_icons: 1.0.8