calorimeter/lib/food_entry/food_entry_bloc.dart

193 lines
5.2 KiB
Dart

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<FoodEvent, PageState> {
final PageState initialState;
final FoodStorage storage;
final DateTime forDate;
FoodEntryBloc(
{required this.initialState,
required this.forDate,
required this.storage})
: super(initialState) {
on<FoodEntryEvent>(handleFoodEntryEvent);
on<FoodChangedEvent>(handleFoodChangedEvent);
on<FoodDeletionEvent>(handleDeleteFoodEvent);
on<BarcodeScanned>(handleBarcodeScannedEvent);
}
void handleFoodEntryEvent(
FoodEntryEvent event, Emitter<PageState> emit) async {
PageState newState = PageState.from(state);
newState.addEntry(event.entry);
await storage.writeEntriesForDate(forDate, newState.foodEntries);
storage.addFoodEntryToLookupDatabase(event.entry);
emit(newState);
}
void handleFoodChangedEvent(
FoodChangedEvent event, Emitter<PageState> emit) async {
var entries = state.foodEntries;
var index = entries.indexWhere((entry) {
return entry.id == event.newEntry.id;
});
entries.removeAt(index);
entries.insert(index, event.newEntry);
await storage.writeEntriesForDate(forDate, entries);
storage.addFoodEntryToLookupDatabase(event.newEntry);
emit(PageState(foodEntries: entries));
}
void handleDeleteFoodEvent(
FoodDeletionEvent event, Emitter<PageState> 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<PageState> 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<FoodEntryState> newList = List.from(state.foodEntries);
var newEntryWaiting = FoodEntryState(
kcalPer100: 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,
kcalPer100: 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 FoodChangedEvent extends FoodEvent {
final FoodEntryState newEntry;
FoodChangedEvent({required this.newEntry});
}
class FoodDeletionEvent extends FoodEvent {
final String entryID;
FoodDeletionEvent({required this.entryID});
}
class BarcodeScanned extends FoodEvent {
final Future<ScanResult> scanResultFuture;
BarcodeScanned({required this.scanResultFuture});
}
/// This is the state for one date/page
class PageState {
final List<FoodEntryState> 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 kcalPer100;
final String id;
final bool waitingForNetwork;
factory FoodEntryState({
required name,
required mass,
required kcalPer100,
required waitingForNetwork,
}) {
return FoodEntryState.withID(
id: const Uuid().v1(),
name: name,
mass: mass,
kcalPer100: kcalPer100,
waitingForNetwork: waitingForNetwork,
);
}
FoodEntryState.withID({
required this.id,
required this.name,
required this.mass,
required this.kcalPer100,
required this.waitingForNetwork,
});
@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,$kcalPer100';
}
}