From 389b0ffe2b0a845a9733d29b57d50a2c114ce0d1 Mon Sep 17 00:00:00 2001 From: monsieurtanuki Date: Wed, 9 Oct 2024 13:51:28 +0200 Subject: [PATCH] feat: 5326 - users can now select proof from gallery in "add price" page Impacted files: * `image_crop_page.dart`: now we can force the image source * `price_model.dart`: now we can set a proof after init time * `price_proof_card.dart`: now we can select the proof image among "camera", "gallery" and "my proofs"; minor refactoring * `prices_proofs_page.dart`: added a "select proof?" parameter to pop the selected proof if needed * `user_preferences_account.dart`: minor refactoring --- .../smooth_app/lib/pages/image_crop_page.dart | 22 ++- .../preferences/user_preferences_account.dart | 4 +- .../lib/pages/prices/price_model.dart | 36 +++-- .../lib/pages/prices/price_proof_card.dart | 141 ++++++++++++++---- .../lib/pages/prices/prices_proofs_page.dart | 29 ++-- 5 files changed, 174 insertions(+), 58 deletions(-) diff --git a/packages/smooth_app/lib/pages/image_crop_page.dart b/packages/smooth_app/lib/pages/image_crop_page.dart index b53bcdc5f7e..ffdf8afb8f9 100644 --- a/packages/smooth_app/lib/pages/image_crop_page.dart +++ b/packages/smooth_app/lib/pages/image_crop_page.dart @@ -24,15 +24,23 @@ import 'package:smooth_app/pages/crop_parameters.dart'; import 'package:smooth_app/pages/product_crop_helper.dart'; /// Safely picks an image file from gallery or camera, regarding access denied. -Future pickImageFile(final BuildContext context) async { +Future pickImageFile( + final BuildContext context, { + final UserPictureSource? forcedSource, +}) async { /// Picks an image file from gallery or camera. Future innerPickImageFile( final BuildContext context, { bool ignorePlatformException = false, }) async { - final UserPictureSource? source = await _getUserPictureSource(context); - if (source == null) { - return null; + final UserPictureSource? source; + if (forcedSource != null) { + source = forcedSource; + } else { + source = await _getUserPictureSource(context); + if (source == null) { + return null; + } } final ImagePicker picker = ImagePicker(); if (source == UserPictureSource.GALLERY) { @@ -277,8 +285,12 @@ Future confirmAndUploadNewImage( final BuildContext context, { required final CropHelper cropHelper, required final bool isLoggedInMandatory, + final UserPictureSource? forcedSource, }) async { - final XFile? fullPhoto = await pickImageFile(context); + final XFile? fullPhoto = await pickImageFile( + context, + forcedSource: forcedSource, + ); if (fullPhoto == null) { return null; } 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 32340b08fcd..7f6d04a2dbf 100644 --- a/packages/smooth_app/lib/pages/preferences/user_preferences_account.dart +++ b/packages/smooth_app/lib/pages/preferences/user_preferences_account.dart @@ -233,7 +233,9 @@ class UserPreferencesAccount extends AbstractUserPreferences { appLocalizations.user_search_proofs_title, () async => Navigator.of(context).push( MaterialPageRoute( - builder: (BuildContext context) => const PricesProofsPage(), + builder: (BuildContext context) => const PricesProofsPage( + selectProof: false, + ), ), ), Icons.receipt, diff --git a/packages/smooth_app/lib/pages/prices/price_model.dart b/packages/smooth_app/lib/pages/prices/price_model.dart index 24cf51a9fbc..ee2d9d09764 100644 --- a/packages/smooth_app/lib/pages/prices/price_model.dart +++ b/packages/smooth_app/lib/pages/prices/price_model.dart @@ -19,7 +19,7 @@ class PriceModel with ChangeNotifier { required final List? locations, required final Currency currency, final PriceMetaProduct? initialProduct, - }) : proof = null, + }) : _proof = null, _proofType = proofType, _date = DateTime.now(), _currency = currency, @@ -29,12 +29,19 @@ class PriceModel with ChangeNotifier { ]; PriceModel.proof({ - required Proof this.proof, - }) : _proofType = proof.type!, - _date = proof.date!, - _locations = null, - _currency = proof.currency!, - priceAmountModels = []; + required Proof proof, + }) : priceAmountModels = [] { + setProof(proof); + } + + void setProof(final Proof proof) { + _proof = proof; + _cropParameters = null; + _proofType = proof.type!; + _date = proof.date!; + _locations = null; + _currency = proof.currency!; + } /// Checks if a proof cannot be reused for prices adding. /// @@ -49,6 +56,8 @@ class PriceModel with ChangeNotifier { proof.imageThumbPath == null || proof.filePath == null; + bool get hasImage => _proof != null || _cropParameters != null; + final List priceAmountModels; CropParameters? _cropParameters; @@ -57,21 +66,24 @@ class PriceModel with ChangeNotifier { set cropParameters(final CropParameters? value) { _cropParameters = value; + _proof = null; notifyListeners(); } - final Proof? proof; + Proof? _proof; + + Proof? get proof => _proof; - ProofType _proofType; + late ProofType _proofType; - ProofType get proofType => proof != null ? proof!.type! : _proofType; + ProofType get proofType => _proof != null ? _proof!.type! : _proofType; set proofType(final ProofType proofType) { _proofType = proofType; notifyListeners(); } - DateTime _date; + late DateTime _date; DateTime get date => _date; @@ -96,7 +108,7 @@ class PriceModel with ChangeNotifier { ? OsmLocation.fromPrice(proof!.location!) : _locations!.firstOrNull; - Currency _currency; + late Currency _currency; Currency get currency => _currency; 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 091d595d189..6ad223dfc7b 100644 --- a/packages/smooth_app/lib/pages/prices/price_proof_card.dart +++ b/packages/smooth_app/lib/pages/prices/price_proof_card.dart @@ -5,11 +5,14 @@ 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/data_models/preferences/user_preferences.dart'; import 'package:smooth_app/generic_lib/buttons/smooth_large_button_with_icon.dart'; import 'package:smooth_app/generic_lib/widgets/smooth_card.dart'; +import 'package:smooth_app/helpers/camera_helper.dart'; 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/prices/prices_proofs_page.dart'; import 'package:smooth_app/pages/proof_crop_helper.dart'; import 'package:smooth_app/query/product_query.dart'; @@ -29,20 +32,17 @@ class PriceProofCard extends StatelessWidget { 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(), - ), + Image( + image: NetworkImage( + model.proof! + .getFileUrl( + uriProductHelper: ProductQuery.uriPricesHelper, + isThumbnail: true, + )! + .toString(), ), - ), - if (model.cropParameters != null) + ) + else if (model.cropParameters != null) LayoutBuilder( builder: (BuildContext context, BoxConstraints constraints) => Image( @@ -53,29 +53,24 @@ class PriceProofCard extends StatelessWidget { height: constraints.maxWidth, ), ), - //Text(model.cropParameters!.smallCroppedFile.path), SmoothLargeButtonWithIcon( - text: model.proof == null && model.cropParameters == null + text: !model.hasImage ? appLocalizations.prices_proof_find : model.proofType == ProofType.receipt ? appLocalizations.prices_proof_receipt : appLocalizations.prices_proof_price_tag, - 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; - } - }, + icon: !model.hasImage ? _iconTodo : _iconDone, + onPressed: () async { + final _ProofSource? proofSource = + await _ProofSource.select(context); + if (proofSource == null) { + return; + } + if (!context.mounted) { + return; + } + return proofSource.process(context, model); + }, ), LayoutBuilder( builder: (BuildContext context, BoxConstraints constraints) => Row( @@ -112,3 +107,87 @@ class PriceProofCard extends StatelessWidget { ); } } + +enum _ProofSource { + camera, + gallery, + history; + + Future process( + final BuildContext context, + final PriceModel model, + ) async { + switch (this) { + case _ProofSource.history: + final Proof? proof = await Navigator.of(context).push( + MaterialPageRoute( + builder: (BuildContext context) => const PricesProofsPage( + selectProof: true, + ), + ), + ); + if (proof != null) { + model.setProof(proof); + model.notifyListeners(); + } + return; + case _ProofSource.camera: + case _ProofSource.gallery: + final UserPictureSource source = this == _ProofSource.gallery + ? UserPictureSource.GALLERY + : UserPictureSource.CAMERA; + final CropParameters? cropParameters = await confirmAndUploadNewImage( + context, + cropHelper: ProofCropHelper(model: model), + isLoggedInMandatory: true, + forcedSource: source, + ); + if (cropParameters != null) { + model.cropParameters = cropParameters; + } + } + } + + static Future<_ProofSource?> select(final BuildContext context) async { + final AppLocalizations appLocalizations = AppLocalizations.of(context); + return showCupertinoModalPopup<_ProofSource>( + context: context, + builder: (final BuildContext context) => CupertinoActionSheet( + title: Text(appLocalizations.prices_proof_find), + cancelButton: CupertinoActionSheetAction( + onPressed: () => Navigator.of(context).pop(), + child: Text( + appLocalizations.cancel, + ), + ), + actions: [ + if (CameraHelper.hasACamera) + CupertinoActionSheetAction( + onPressed: () => Navigator.of(context).pop( + _ProofSource.camera, + ), + child: Text( + appLocalizations.settings_app_camera, + ), + ), + CupertinoActionSheetAction( + onPressed: () => Navigator.of(context).pop( + _ProofSource.gallery, + ), + child: Text( + appLocalizations.gallery_source_label, + ), + ), + CupertinoActionSheetAction( + onPressed: () => Navigator.of(context).pop( + _ProofSource.history, + ), + child: Text( + appLocalizations.user_search_proofs_title, + ), + ), + ], + ), + ); + } +} 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 baa2cf2ddbb..65c2792ee57 100644 --- a/packages/smooth_app/lib/pages/prices/prices_proofs_page.dart +++ b/packages/smooth_app/lib/pages/prices/prices_proofs_page.dart @@ -16,7 +16,12 @@ import 'package:smooth_app/widgets/smooth_scaffold.dart'; /// Page that displays the latest proofs of the current user. class PricesProofsPage extends StatefulWidget { - const PricesProofsPage(); + const PricesProofsPage({ + required this.selectProof, + }); + + /// Do we want to select a proof (true), or just to see its details (false)? + final bool selectProof; @override State createState() => _PricesProofsPageState(); @@ -119,15 +124,21 @@ class _PricesProofsPageState extends State ); } return InkWell( - onTap: () async => Navigator.push( - context, - MaterialPageRoute( - builder: (BuildContext context) => - PriceProofPage( - proof, + onTap: () async { + if (widget.selectProof) { + Navigator.of(context).pop(proof); + return; + } + return Navigator.push( + context, + MaterialPageRoute( + builder: (BuildContext context) => + PriceProofPage( + proof, + ), ), - ), - ), // PriceProofPage + ); + }, // PriceProofPage child: _PriceProofImage(proof, squareSize: squareSize), );