import 'package:barcode_scan2/barcode_scan2.dart'; import 'package:calorimeter/food_scan/food_fact_lookup.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:calorimeter/storage/storage.dart'; import 'package:uuid/uuid.dart'; class FoodEntryBloc extends Bloc { final PageState initialState; final FoodStorage storage; final DateTime forDate; FoodEntryBloc( {required this.initialState, required this.forDate, required this.storage}) : super(initialState) { on(handleFoodEntryEvent); on(handleDeleteFoodEvent); on(handleBarcodeScannedEvent); } void handleFoodEntryEvent( FoodEntryEvent event, Emitter emit) async { PageState newState = PageState.from(state); newState.addEntry(event.entry); await storage.writeEntriesForDate(forDate, newState.foodEntries); storage.addFoodEntryToLookupDatabase(event.entry); emit(newState); } void handleDeleteFoodEvent( FoodDeletionEvent event, Emitter emit) async { state.foodEntries.removeWhere((entry) => entry.id == event.entryID); await storage.writeEntriesForDate(forDate, state.foodEntries); emit(PageState.from(state)); } void handleBarcodeScannedEvent( BarcodeScanned event, Emitter emit) async { var client = FoodFactLookupClient(); var scanResult = await event.scanResultFuture; if (scanResult.type == ResultType.Cancelled) { return; } if (scanResult.type == ResultType.Error) { emit(PageState( foodEntries: state.foodEntries, errorString: "Fehler beim Scannen des Barcodes")); return; } var responseFuture = client.retrieveFoodInfo(scanResult.rawContent); List newList = List.from(state.foodEntries); var newEntryWaiting = FoodEntryState( kcalPerMass: 0, name: "", mass: 0, waitingForNetwork: true); newList.add(newEntryWaiting); emit(PageState(foodEntries: newList)); await responseFuture.then((response) { newList.removeWhere((entryState) => entryState.id == newEntryWaiting.id); if (response.status == FoodFactResponseStatus.barcodeNotFound) { emit(PageState( foodEntries: state.foodEntries, errorString: "Barcode konnte nicht gefunden werden.")); return; } if (response.status == FoodFactResponseStatus.foodFactServerNotReachable) { emit(PageState( foodEntries: state.foodEntries, errorString: "OpenFoodFacts-Server konnte nicht erreicht werden.")); return; } var newEntryFinishedWaiting = FoodEntryState( name: response.food?.name ?? "", mass: response.food?.mass ?? 0, kcalPerMass: response.food?.kcalPer100g ?? 0, waitingForNetwork: false, ); newList.add(newEntryFinishedWaiting); emit(PageState(foodEntries: newList)); }); } } class FoodEvent {} class FoodEntryEvent extends FoodEvent { final FoodEntryState entry; FoodEntryEvent({required this.entry}); } class FoodDeletionEvent extends FoodEvent { final String entryID; FoodDeletionEvent({required this.entryID}); } class BarcodeScanned extends FoodEvent { final Future scanResultFuture; BarcodeScanned({required this.scanResultFuture}); } /// This is the state for one date/page class PageState { final List foodEntries; final String? errorString; PageState({required this.foodEntries, this.errorString}); factory PageState.init() { return PageState(foodEntries: []); } static from(PageState state) { return PageState(foodEntries: state.foodEntries); } void addEntry(FoodEntryState entry) { foodEntries.add(entry); } } class FoodEntryState { final String name; final int mass; final int kcalPerMass; final String id; final bool waitingForNetwork; FoodEntryState({ required this.name, required this.mass, required this.kcalPerMass, required this.waitingForNetwork, }) : id = const Uuid().v1(); @override String toString() { //we use quotation marks around the name because the name might contain //commas and we want to store it in a csv file return '$id,"$name",$mass,$kcalPerMass'; } }