diff --git a/lib/food_entry/food_entry_bloc.dart b/lib/food_entry/food_entry_bloc.dart index a95a8db..7647977 100644 --- a/lib/food_entry/food_entry_bloc.dart +++ b/lib/food_entry/food_entry_bloc.dart @@ -2,8 +2,11 @@ /* Copyright (C) 2024 Marco Groß */ import 'package:barcode_scan2/barcode_scan2.dart'; import 'package:calorimeter/food_scan/food_fact_lookup.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:calorimeter/storage/storage.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:uuid/uuid.dart'; class FoodEntryBloc extends Bloc { @@ -16,8 +19,9 @@ class FoodEntryBloc extends Bloc { on(handleFoodEntryEvent); on(handleFoodChangedEvent); on(handleDeleteFoodEvent); - on(handleBarcodeScannedEvent); + on(handleBarcodeScannedEvent); on(handleFoodEntryTapped); + on(handlePermissionException); } void handlePageBeingInitialized( PageBeingInitialized event, Emitter emit) async { @@ -92,12 +96,24 @@ class FoodEntryBloc extends Bloc { } void handleBarcodeScannedEvent( - BarcodeScanned event, Emitter emit) async { + BarcodeAboutToBeScanned event, Emitter emit) async { + ScanResult scanResult = ScanResult(); + try { + scanResult = await BarcodeScanner.scan(); + } on PlatformException catch (e) { + if (e.code == BarcodeScanner.cameraAccessDenied) { + emit(GlobalEntryState( + foodEntries: state.foodEntries, + appError: + GlobalAppError(GlobalAppErrorType.errCameraPermissionDenied))); + } + return; + } + var entriesForDate = state.foodEntries[event.forDate]; if (entriesForDate == null) return; var client = FoodFactLookupClient(); - var scanResult = await event.scanResultFuture; if (scanResult.type == ResultType.Cancelled) { return; @@ -105,7 +121,7 @@ class FoodEntryBloc extends Bloc { if (scanResult.type == ResultType.Error) { emit(GlobalEntryState( foodEntries: state.foodEntries, - errorString: "Fehler beim Scannen des Barcodes")); + appError: GlobalAppError(GlobalAppErrorType.errGeneralError))); return; } var responseFuture = client.retrieveFoodInfo(scanResult.rawContent); @@ -117,6 +133,7 @@ class FoodEntryBloc extends Bloc { waitingForNetwork: true, isSelected: false, ); + entriesForDate.add(newEntryWaiting); var newFoodEntries = state.foodEntries; newFoodEntries.addAll({event.forDate: entriesForDate}); @@ -138,7 +155,7 @@ class FoodEntryBloc extends Bloc { emit(GlobalEntryState( foodEntries: newFoodEntries, - errorString: "Barcode konnte nicht gefunden werden.")); + appError: GlobalAppError(GlobalAppErrorType.errbarcodeNotFound))); return; } if (response.status == @@ -149,14 +166,19 @@ class FoodEntryBloc extends Bloc { emit(GlobalEntryState( foodEntries: newFoodEntries, - errorString: "OpenFoodFacts-Server konnte nicht erreicht werden.")); + appError: + GlobalAppError(GlobalAppErrorType.errServerNotReachable))); return; } + int kcalToDisplay = response.food?.kcalPer100g ?? 0; + if (response.food?.kcalPer100gPrepared != 0) { + kcalToDisplay = response.food?.kcalPer100gPrepared ?? 0; + } var newEntryFinishedWaiting = FoodEntryState( name: response.food?.name ?? "", mass: response.food?.mass ?? 0, - kcalPer100: response.food?.kcalPer100g ?? 0, + kcalPer100: kcalToDisplay, waitingForNetwork: false, isSelected: false, ); @@ -194,6 +216,14 @@ class FoodEntryBloc extends Bloc { emit(GlobalEntryState(foodEntries: state.foodEntries)); } + + void handlePermissionException( + PermissionException event, Emitter emit) async { + emit(GlobalEntryState( + foodEntries: state.foodEntries, + appError: + GlobalAppError(GlobalAppErrorType.errCameraPermissionDenied))); + } } class FoodEvent { @@ -224,10 +254,8 @@ class FoodDeletionEvent extends FoodEvent { FoodDeletionEvent({required this.entryID, required super.forDate}); } -class BarcodeScanned extends FoodEvent { - final Future scanResultFuture; - - BarcodeScanned({required this.scanResultFuture, required super.forDate}); +class BarcodeAboutToBeScanned extends FoodEvent { + BarcodeAboutToBeScanned({required super.forDate}); } class FoodEntryTapped extends FoodEvent { @@ -236,12 +264,16 @@ class FoodEntryTapped extends FoodEvent { FoodEntryTapped({required this.entry, required super.forDate}); } +class PermissionException extends FoodEvent { + PermissionException({required super.forDate}); +} + /// This is the state for one date/page class GlobalEntryState { final Map> foodEntries; - final String? errorString; + final GlobalAppError? appError; - GlobalEntryState({required this.foodEntries, this.errorString}); + GlobalEntryState({required this.foodEntries, this.appError}); factory GlobalEntryState.init() { return GlobalEntryState(foodEntries: {}); @@ -305,3 +337,29 @@ class FoodEntryState { return '$id,"$name",$mass,$kcalPer100'; } } + +enum GlobalAppErrorType { + errGeneralError, + errbarcodeNotFound, + errServerNotReachable, + errCameraPermissionDenied +} + +class GlobalAppError { + final GlobalAppErrorType type; + + GlobalAppError(this.type); + + String toErrorString(BuildContext context) { + switch (type) { + case GlobalAppErrorType.errGeneralError: + return AppLocalizations.of(context)!.errGeneralBarcodeError; + case GlobalAppErrorType.errbarcodeNotFound: + return AppLocalizations.of(context)!.errBarcodeNotFound; + case GlobalAppErrorType.errServerNotReachable: + return AppLocalizations.of(context)!.errServerNotReachable; + case GlobalAppErrorType.errCameraPermissionDenied: + return AppLocalizations.of(context)!.errPermissionNotGranted; + } + } +} diff --git a/lib/food_scan/food_fact_lookup.dart b/lib/food_scan/food_fact_lookup.dart index 6542f69..28d33d7 100644 --- a/lib/food_scan/food_fact_lookup.dart +++ b/lib/food_scan/food_fact_lookup.dart @@ -47,15 +47,21 @@ class FoodFactLookupClient { class FoodFactModel { final String name; final int kcalPer100g; + final int kcalPer100gPrepared; final int mass; FoodFactModel({ required this.name, required this.mass, required this.kcalPer100g, + required this.kcalPer100gPrepared, }); factory FoodFactModel.fromJson(Map json) { + int kcalPer100g = json['product']['nutriments']['energy-kcal_100g'] ?? 0; + int kcalPer100gPrepared = + json['product']['nutriments']['energy-kcal_prepared_100g'] ?? 0; + String quantityString = json['product']['product_quantity'] ?? "0"; int quantity; @@ -66,8 +72,9 @@ class FoodFactModel { } return FoodFactModel( - name: json['product']['product_name'], - kcalPer100g: json['product']['nutriments']['energy-kcal_100g'], + name: json['product']['product_name'] ?? "", + kcalPer100g: kcalPer100g, + kcalPer100gPrepared: kcalPer100gPrepared, mass: quantity); } } diff --git a/lib/l10n/app_de.arb b/lib/l10n/app_de.arb index d9c6c2a..d6937d7 100644 --- a/lib/l10n/app_de.arb +++ b/lib/l10n/app_de.arb @@ -10,5 +10,9 @@ "yourSettings": "Deine persönlichen Einstellungen", "dayLimit": "Kalorienlimit pro Tag", "errAmountNotANumber": "Menge muss eine Zahl sein", - "errKcalNotANumber": "kcal muss eine Zahl sein" + "errKcalNotANumber": "kcal muss eine Zahl sein", + "errGeneralBarcodeError": "Fehler beim Scannen des Barcodes", + "errBarcodeNotFound": "Barcode konnte nicht gefunden werden.", + "errServerNotReachable": "OpenFoodFacts-Server konnte nicht erreicht werden.", + "errPermissionNotGranted": "Kamera-Berechtigung muss aktiviert werden." } diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index aa9c361..59fe373 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -10,5 +10,9 @@ "yourSettings": "Your personal settings", "dayLimit": "Calorie limit per day", "errAmountNotANumber": "Amount must be a number", - "errKcalNotANumber": "kcal must be a number" + "errKcalNotANumber": "kcal must be a number", + "errGeneralBarcodeError": "Error while scanning the barcode.", + "errBarcodeNotFound": "Barcode could not be found.", + "errServerNotReachable": "OpenFoodFacts server could not be reached.", + "errPermissionNotGranted": "Permission to use camera must be given." } diff --git a/lib/perdate/perdate_pageview_controller.dart b/lib/perdate/perdate_pageview_controller.dart index 376dc1b..aa02261 100644 --- a/lib/perdate/perdate_pageview_controller.dart +++ b/lib/perdate/perdate_pageview_controller.dart @@ -2,7 +2,6 @@ /* Copyright (C) 2024 Marco Groß */ import 'dart:developer'; -import 'package:barcode_scan2/barcode_scan2.dart'; import 'package:calorimeter/food_entry/food_entry_bloc.dart'; import 'package:calorimeter/perdate/perdate_pageview.dart'; import 'package:calorimeter/utils/app_drawer.dart'; @@ -77,10 +76,8 @@ class PerDatePageViewController extends StatelessWidget { floatingActionButton: OverflowBar(children: [ ScanFoodFAB( onPressed: () { - var result = BarcodeScanner.scan(); context.read().add( - BarcodeScanned( - scanResultFuture: result, + BarcodeAboutToBeScanned( forDate: context .read() .displayedDate, diff --git a/lib/perdate/perdate_widget.dart b/lib/perdate/perdate_widget.dart index 10aba35..be9ff6e 100644 --- a/lib/perdate/perdate_widget.dart +++ b/lib/perdate/perdate_widget.dart @@ -29,8 +29,8 @@ class _PerDateWidgetState extends State return BlocConsumer( listener: (context, pageState) { - if (pageState.errorString != null) { - showNewSnackbarWith(context, pageState.errorString!); + if (pageState.appError != null) { + showNewSnackbarWith(context, pageState.appError!); } }, builder: (context, pageState) { @@ -41,9 +41,10 @@ class _PerDateWidgetState extends State ); } - void showNewSnackbarWith(BuildContext context, String text) { - var snackbar = - ErrorSnackbar(colorScheme: Theme.of(context).colorScheme, text: text); + void showNewSnackbarWith(BuildContext context, GlobalAppError error) { + var snackbar = ErrorSnackbar( + colorScheme: Theme.of(context).colorScheme, + text: error.toErrorString(context)); ScaffoldMessenger.of(context) ..removeCurrentSnackBar() diff --git a/pubspec.lock b/pubspec.lock index 9c4843b..20e7c76 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -5,31 +5,31 @@ packages: dependency: transitive description: name: _fe_analyzer_shared - sha256: f256b0c0ba6c7577c15e2e4e114755640a875e885099367bf6e012b19314c834 + sha256: "16e298750b6d0af7ce8a3ba7c18c69c3785d11b15ec83f6dcd0ad2a0009b3cab" url: "https://pub.dev" source: hosted - version: "72.0.0" + version: "76.0.0" _macros: dependency: transitive description: dart source: sdk - version: "0.3.2" + version: "0.3.3" analyzer: dependency: transitive description: name: analyzer - sha256: b652861553cd3990d8ed361f7979dc6d7053a9ac8843fa73820ab68ce5410139 + sha256: "1f14db053a8c23e260789e9b0980fa27f2680dd640932cae5e1137cce0e46e1e" url: "https://pub.dev" source: hosted - version: "6.7.0" + version: "6.11.0" archive: dependency: transitive description: name: archive - sha256: cb6a278ef2dbb298455e1a713bda08524a175630ec643a242c399c932a0a1f7d + sha256: "6199c74e3db4fbfbd04f66d739e72fe11c8a8957d5f219f1f4482dbde6420b5a" url: "https://pub.dev" source: hosted - version: "3.6.1" + version: "4.0.2" args: dependency: transitive description: @@ -106,10 +106,10 @@ packages: dependency: transitive description: name: collection - sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a + sha256: a1ace0a119f20aabc852d165077c036cd864315bd99b7eaa10a60100341941bf url: "https://pub.dev" source: hosted - version: "1.18.0" + version: "1.19.0" convert: dependency: transitive description: @@ -238,26 +238,26 @@ packages: dependency: transitive description: name: http_multi_server - sha256: "97486f20f9c2f7be8f514851703d0119c3596d14ea63227af6f7a481ef2b2f8b" + sha256: aa6199f908078bb1c5efb8d8638d4ae191aac11b311132c3ef48ce352fb52ef8 url: "https://pub.dev" source: hosted - version: "3.2.1" + version: "3.2.2" http_parser: dependency: transitive description: name: http_parser - sha256: "2aa08ce0341cc9b354a498388e30986515406668dbcc4f7c950c3e715496693b" + sha256: "76d306a1c3afb33fe82e2bbacad62a61f409b5634c915fceb0d799de1a913360" url: "https://pub.dev" source: hosted - version: "4.0.2" + version: "4.1.1" image: dependency: transitive description: name: image - sha256: f31d52537dc417fdcde36088fdf11d191026fd5e4fae742491ebd40e5a8bea7d + sha256: "8346ad4b5173924b5ddddab782fc7d8a6300178c8b1dc427775405a01701c4a6" url: "https://pub.dev" source: hosted - version: "4.3.0" + version: "4.5.2" intl: dependency: "direct main" description: @@ -270,10 +270,10 @@ packages: dependency: transitive description: name: io - sha256: "2ec25704aba361659e10e3e5f5d672068d332fc8ac516421d483a11e5cbd061e" + sha256: dfd5a80599cf0165756e3181807ed3e77daf6dd4137caaad72d0b7931597650b url: "https://pub.dev" source: hosted - version: "1.0.4" + version: "1.0.5" js: dependency: transitive description: @@ -294,18 +294,18 @@ packages: dependency: transitive description: name: leak_tracker - sha256: "3f87a60e8c63aecc975dda1ceedbc8f24de75f09e4856ea27daf8958f2f0ce05" + sha256: "7bb2830ebd849694d1ec25bf1f44582d6ac531a57a365a803a6034ff751d2d06" url: "https://pub.dev" source: hosted - version: "10.0.5" + version: "10.0.7" leak_tracker_flutter_testing: dependency: transitive description: name: leak_tracker_flutter_testing - sha256: "932549fb305594d82d7183ecd9fa93463e9914e1b67cacc34bc40906594a1806" + sha256: "9491a714cca3667b60b5c420da8217e6de0d1ba7a5ec322fab01758f6998f379" url: "https://pub.dev" source: hosted - version: "3.0.5" + version: "3.0.8" leak_tracker_testing: dependency: transitive description: @@ -318,10 +318,10 @@ packages: dependency: transitive description: name: lints - sha256: "3315600f3fb3b135be672bf4a178c55f274bebe368325ae18462c89ac1e3b413" + sha256: c35bb79562d980e9a453fc715854e1ed39e24e7d0297a880ef54e17f9874a9d7 url: "https://pub.dev" source: hosted - version: "5.0.0" + version: "5.1.1" logging: dependency: transitive description: @@ -334,10 +334,10 @@ packages: dependency: transitive description: name: macros - sha256: "0acaed5d6b7eab89f63350bccd82119e6c602df0f391260d0e32b5e23db79536" + sha256: "1d9e801cd66f7ea3663c45fc708450db1fa57f988142c64289142c9b7ee80656" url: "https://pub.dev" source: hosted - version: "0.1.2-main.4" + version: "0.1.3-main.0" matcher: dependency: transitive description: @@ -390,10 +390,10 @@ packages: dependency: transitive description: name: package_config - sha256: "1c5b77ccc91e4823a5af61ee74e6b972db1ef98c2ff5a18d3161c982a55448bd" + sha256: "92d4488434b520a62570293fbd33bb556c7d49230791c1b4bbd973baf6d2dc67" url: "https://pub.dev" source: hosted - version: "2.1.0" + version: "2.1.1" path: dependency: transitive description: @@ -414,10 +414,10 @@ packages: dependency: transitive description: name: path_provider_android - sha256: "8c4967f8b7cb46dc914e178daa29813d83ae502e0529d7b0478330616a691ef7" + sha256: "4adf4fd5423ec60a29506c76581bc05854c55e3a0b72d35bb28d661c9686edf2" url: "https://pub.dev" source: hosted - version: "2.2.14" + version: "2.2.15" path_provider_foundation: dependency: transitive description: @@ -482,6 +482,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.5.1" + posix: + dependency: transitive + description: + name: posix + sha256: a0117dc2167805aa9125b82eee515cc891819bac2f538c83646d355b16f58b9a + url: "https://pub.dev" + source: hosted + version: "6.0.1" protobuf: dependency: transitive description: @@ -502,10 +510,10 @@ packages: dependency: transitive description: name: pub_semver - sha256: "40d3ab1bbd474c4c2328c91e3a7df8c6dd629b79ece4c4bd04bee496a224fb0c" + sha256: "7b3cfbf654f3edd0c6298ecd5be782ce997ddf0e00531b9464b55245185bbbbd" url: "https://pub.dev" source: hosted - version: "2.1.4" + version: "2.1.5" settings_ui: dependency: "direct main" description: @@ -518,10 +526,10 @@ packages: dependency: transitive description: name: shelf - sha256: ad29c505aee705f41a4d8963641f91ac4cee3c8fad5947e033390a7bd8180fa4 + sha256: e7dd780a7ffb623c57850b33f43309312fc863fb6aa3d276a754bb299839ef12 url: "https://pub.dev" source: hosted - version: "1.4.1" + version: "1.4.2" shelf_packages_handler: dependency: transitive description: @@ -550,7 +558,7 @@ packages: dependency: transitive description: flutter source: sdk - version: "0.0.99" + version: "0.0.0" source_map_stack_trace: dependency: transitive description: @@ -563,10 +571,10 @@ packages: dependency: transitive description: name: source_maps - sha256: "708b3f6b97248e5781f493b765c3337db11c5d2c81c3094f10904bfa8004c703" + sha256: "190222579a448b03896e0ca6eca5998fa810fda630c1d65e2f78b3f638f54812" url: "https://pub.dev" source: hosted - version: "0.10.12" + version: "0.10.13" source_span: dependency: transitive description: @@ -587,10 +595,10 @@ packages: dependency: transitive description: name: stack_trace - sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b" + sha256: "9f47fd3630d76be3ab26f0ee06d213679aa425996925ff3feffdec504931c377" url: "https://pub.dev" source: hosted - version: "1.11.1" + version: "1.12.0" stream_channel: dependency: transitive description: @@ -603,10 +611,10 @@ packages: dependency: transitive description: name: string_scanner - sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde" + sha256: "688af5ed3402a4bde5b3a6c15fd768dbf2621a614950b17f04626c431ab3c4c3" url: "https://pub.dev" source: hosted - version: "1.2.0" + version: "1.3.0" term_glyph: dependency: transitive description: @@ -619,26 +627,26 @@ packages: dependency: "direct main" description: name: test - sha256: "7ee44229615f8f642b68120165ae4c2a75fe77ae2065b1e55ae4711f6cf0899e" + sha256: "713a8789d62f3233c46b4a90b174737b2c04cb6ae4500f2aa8b1be8f03f5e67f" url: "https://pub.dev" source: hosted - version: "1.25.7" + version: "1.25.8" test_api: dependency: transitive description: name: test_api - sha256: "5b8a98dafc4d5c4c9c72d8b31ab2b23fc13422348d2997120294d3bac86b4ddb" + sha256: "664d3a9a64782fcdeb83ce9c6b39e78fd2971d4e37827b9b06c3aa1edc5e760c" url: "https://pub.dev" source: hosted - version: "0.7.2" + version: "0.7.3" test_core: dependency: transitive description: name: test_core - sha256: "55ea5a652e38a1dfb32943a7973f3681a60f872f8c3a05a14664ad54ef9c6696" + sha256: "12391302411737c176b0b5d6491f466b0dd56d4763e347b6714efbaa74d7953d" url: "https://pub.dev" source: hosted - version: "0.6.4" + version: "0.6.5" typed_data: dependency: transitive description: @@ -675,18 +683,18 @@ packages: dependency: transitive description: name: vm_service - sha256: "5c5f338a667b4c644744b661f309fb8080bb94b18a7e91ef1dbd343bed00ed6d" + sha256: f6be3ed8bd01289b34d679c2b62226f63c0e69f9fd2e50a6b3c1c729a961041b url: "https://pub.dev" source: hosted - version: "14.2.5" + version: "14.3.0" watcher: dependency: transitive description: name: watcher - sha256: "3d2ad6751b3c16cf07c7fca317a1413b3f26530319181b37e3b9039b84fc01d8" + sha256: "69da27e49efa56a15f8afe8f4438c4ec02eff0a117df1b22ea4aad194fe1c104" url: "https://pub.dev" source: hosted - version: "1.1.0" + version: "1.1.1" web: dependency: transitive description: @@ -739,10 +747,10 @@ packages: dependency: transitive description: name: yaml - sha256: "75769501ea3489fca56601ff33454fe45507ea3bfb014161abc3b43ae25989d5" + sha256: b9da305ac7c39faa3f030eccd175340f968459dae4af175130b3fc47e40d76ce url: "https://pub.dev" source: hosted - version: "3.1.2" + version: "3.1.3" sdks: - dart: ">=3.5.3 <4.0.0" + dart: ">=3.6.0 <4.0.0" flutter: ">=3.24.0"