From a7a7f44050109256fa6a3cd87b077c7394e382e9 Mon Sep 17 00:00:00 2001 From: Marco Date: Mon, 9 Sep 2024 22:41:48 +0200 Subject: [PATCH] Make scanned widgets appear in the list instead of putting the info into the EnterFoodWidget. 1. Make scanned foods appear in the list of foods 2. Remove Controller for entering food This commit removes the EnterFoodController that was used to put a scanned food into the EnterFoodWidget. This is now unnecessary because scanning a product will be distributed via the FoodBLoC. --- lib/food_entry/enter_food_widget.dart | 163 ++++++++++--------- lib/food_entry/food_entry_bloc.dart | 109 +++++++++---- lib/food_entry/food_entry_widget.dart | 18 ++- lib/food_scan/food_fact_lookup.dart | 19 ++- lib/perdate/entry_list.dart | 3 +- lib/perdate/perdate_widget.dart | 122 ++++++--------- lib/storage/storage.dart | 37 +++-- lib/storage/storage.dart.orig | 215 ++++++++++++++++++++++++++ lib/utils/enter_food_controller.dart | 12 -- lib/utils/sum_widget.dart | 2 +- 10 files changed, 477 insertions(+), 223 deletions(-) create mode 100644 lib/storage/storage.dart.orig delete mode 100644 lib/utils/enter_food_controller.dart diff --git a/lib/food_entry/enter_food_widget.dart b/lib/food_entry/enter_food_widget.dart index bbfe0d3..95511f6 100644 --- a/lib/food_entry/enter_food_widget.dart +++ b/lib/food_entry/enter_food_widget.dart @@ -1,4 +1,3 @@ -import 'package:calorimeter/utils/enter_food_controller.dart'; import 'package:calorimeter/storage/storage.dart'; import 'package:flutter/material.dart'; import 'package:calorimeter/food_entry/food_entry_bloc.dart'; @@ -6,7 +5,7 @@ import 'package:calorimeter/utils/row_with_spacers_widget.dart'; import 'package:provider/provider.dart'; class EnterFoodWidget extends StatefulWidget { - final Function(BuildContext context, FoodEntry entry) onAdd; + final Function(BuildContext context, FoodEntryState entry) onAdd; const EnterFoodWidget({super.key, required this.onAdd}); @@ -18,7 +17,7 @@ class _EnterFoodWidgetState extends State { late TextEditingController nameController; late TextEditingController massController; late TextEditingController kcalPerMassController; - late Map suggestions; + late Map suggestions; @override void initState() { @@ -32,88 +31,80 @@ class _EnterFoodWidgetState extends State { @override Widget build(BuildContext context) { - return Consumer( - builder: (context, food, child) { - nameController.text = food.name; - kcalPerMassController.text = food.kcalPer100g; + return Padding( + padding: const EdgeInsets.symmetric(horizontal: 8.0), + child: RowWidget( + Autocomplete( + optionsViewOpenDirection: OptionsViewOpenDirection.down, + fieldViewBuilder: (context, controller, focusNode, onSubmitted) { + nameController = controller; + return TextFormField( + controller: controller, + focusNode: focusNode, + decoration: const InputDecoration( + label: Text("Name"), + ), + ); + }, + optionsBuilder: (TextEditingValue textEditingValue) { + if (textEditingValue.text == '') { + return const Iterable.empty(); + } - return Padding( - padding: const EdgeInsets.symmetric(horizontal: 8.0), - child: RowWidget( - Autocomplete( - optionsViewOpenDirection: OptionsViewOpenDirection.down, - fieldViewBuilder: - (context, controller, focusNode, onSubmitted) { - nameController = controller; - return TextFormField( - controller: controller, - focusNode: focusNode, - decoration: const InputDecoration( - label: Text("Name"), - ), - ); + return suggestions.keys.where( + (name) { + return name + .toLowerCase() + .contains(textEditingValue.text.toLowerCase()); }, - optionsBuilder: (TextEditingValue textEditingValue) { - if (textEditingValue.text == '') { - return const Iterable.empty(); - } - - return suggestions.keys.where( - (name) { - return name - .toLowerCase() - .contains(textEditingValue.text.toLowerCase()); - }, - ); - }, - onSelected: (selectedFood) { - double kcalPerMassForSelectedFood = - suggestions[selectedFood]!; - context - .read() - .set(selectedFood, kcalPerMassForSelectedFood.toString()); - }), - TextField( - textAlign: TextAlign.end, - decoration: const InputDecoration( - label: Align( - alignment: Alignment.centerRight, child: Text("Menge")), - ), - keyboardType: TextInputType.number, - controller: massController, - onSubmitted: (value) => onSubmitAction(), - ), - TextField( - textAlign: TextAlign.end, - decoration: const InputDecoration( - label: Align( - alignment: Alignment.centerRight, - child: Text("kcal pro"))), - keyboardType: TextInputType.number, - controller: kcalPerMassController, - onSubmitted: (value) => onSubmitAction(), - ), - Padding( - padding: const EdgeInsets.only(left: 16.0), - child: ElevatedButton( - style: ElevatedButton.styleFrom( - padding: EdgeInsets.zero, - ), - onPressed: () => onSubmitAction(), - child: const Icon(Icons.add)), - ), + ); + }, + onSelected: (selectedFood) { + int kcalPerMassForSelectedFood = suggestions[selectedFood]!; + setState(() { + nameController.text = selectedFood; + kcalPerMassController.text = + kcalPerMassForSelectedFood.toString(); + }); + }), + TextField( + textAlign: TextAlign.end, + decoration: const InputDecoration( + label: + Align(alignment: Alignment.centerRight, child: Text("Menge")), ), - ); - }, + keyboardType: TextInputType.number, + controller: massController, + onSubmitted: (value) => onSubmitAction(), + ), + TextField( + textAlign: TextAlign.end, + decoration: const InputDecoration( + label: Align( + alignment: Alignment.centerRight, child: Text("kcal pro"))), + keyboardType: TextInputType.number, + controller: kcalPerMassController, + onSubmitted: (value) => onSubmitAction(), + ), + Padding( + padding: const EdgeInsets.only(left: 16.0), + child: ElevatedButton( + style: ElevatedButton.styleFrom( + padding: EdgeInsets.zero, + ), + onPressed: () => onSubmitAction(), + child: const Icon(Icons.add)), + ), + ), ); } void onSubmitAction() { - double massAsNumber = 0.0; - double kcalPerMassAsNumber = 0.0; + int massAsNumber = 0; + int kcalPerMassAsNumber = 0; try { - massAsNumber = double.parse(massController.text.replaceAll(",", ".")); + massAsNumber = int.parse(massController.text.replaceAll(",", ".")); } catch (e) { var snackbar = const SnackBar(content: Text("Menge muss eine Zahl sein")); ScaffoldMessenger.of(context).clearSnackBars(); @@ -123,21 +114,27 @@ class _EnterFoodWidgetState extends State { try { kcalPerMassAsNumber = - double.parse(kcalPerMassController.text.replaceAll(",", ".")); + int.parse(kcalPerMassController.text.replaceAll(",", ".")); } catch (e) { var snackbar = const SnackBar(content: Text("'kcal pro 100g' muss eine Zahl sein")); - ScaffoldMessenger.of(context).clearSnackBars(); + ScaffoldMessenger.of(context).removeCurrentSnackBar(); ScaffoldMessenger.of(context).showSnackBar(snackbar); return; } - var entry = FoodEntry( - name: nameController.text, - mass: massAsNumber, - kcalPerMass: kcalPerMassAsNumber); + var entry = FoodEntryState( + name: nameController.text, + mass: massAsNumber, + kcalPerMass: kcalPerMassAsNumber, + waitingForNetwork: false, + ); widget.onAdd(context, entry); - context.read().set("", ""); + setState(() { + nameController.text = ""; + massController.text = ""; + kcalPerMassController.text = ""; + }); } } diff --git a/lib/food_entry/food_entry_bloc.dart b/lib/food_entry/food_entry_bloc.dart index 4a95671..35baaf8 100644 --- a/lib/food_entry/food_entry_bloc.dart +++ b/lib/food_entry/food_entry_bloc.dart @@ -1,9 +1,11 @@ +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 FoodEntryState initialState; +class FoodEntryBloc extends Bloc { + final PageState initialState; final FoodStorage storage; final DateTime forDate; @@ -13,13 +15,13 @@ class FoodEntryBloc extends Bloc { required this.storage}) : super(initialState) { on(handleFoodEntryEvent); - on(deleteFood); - on(updateEntries); + on(handleDeleteFoodEvent); + on(handleBarcodeScannedEvent); } void handleFoodEntryEvent( - FoodEntryEvent event, Emitter emit) async { - FoodEntryState newState = FoodEntryState.from(state); + FoodEntryEvent event, Emitter emit) async { + PageState newState = PageState.from(state); newState.addEntry(event.entry); await storage.writeEntriesForDate(forDate, newState.foodEntries); @@ -28,26 +30,70 @@ class FoodEntryBloc extends Bloc { emit(newState); } - void deleteFood(FoodDeletionEvent event, Emitter emit) async { + void handleDeleteFoodEvent( + FoodDeletionEvent event, Emitter emit) async { state.foodEntries.removeWhere((entry) => entry.id == event.entryID); await storage.writeEntriesForDate(forDate, state.foodEntries); - emit(FoodEntryState.from(state)); + emit(PageState.from(state)); } - void updateEntries( - PageChangedEvent event, Emitter emit) async { - var entries = await storage.getEntriesForDate(event.changedToDate); - var newState = FoodEntryState(foodEntries: entries); - emit(newState); + 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 FoodEntry entry; + final FoodEntryState entry; FoodEntryEvent({required this.entry}); } @@ -58,41 +104,44 @@ class FoodDeletionEvent extends FoodEvent { FoodDeletionEvent({required this.entryID}); } -class PageChangedEvent extends FoodEvent { - final DateTime changedToDate; +class BarcodeScanned extends FoodEvent { + final Future scanResultFuture; - PageChangedEvent({required this.changedToDate}); + BarcodeScanned({required this.scanResultFuture}); } -class FoodEntryState { - final List foodEntries; +/// This is the state for one date/page +class PageState { + final List foodEntries; + final String? errorString; - FoodEntryState({required this.foodEntries}); + PageState({required this.foodEntries, this.errorString}); - factory FoodEntryState.init() { - return FoodEntryState(foodEntries: []); + factory PageState.init() { + return PageState(foodEntries: []); } - static from(FoodEntryState state) { - List newList = List.from(state.foodEntries); - return FoodEntryState(foodEntries: newList); + static from(PageState state) { + return PageState(foodEntries: state.foodEntries); } - void addEntry(FoodEntry entry) { + void addEntry(FoodEntryState entry) { foodEntries.add(entry); } } -class FoodEntry { +class FoodEntryState { final String name; - final double mass; - final double kcalPerMass; + final int mass; + final int kcalPerMass; final String id; + final bool waitingForNetwork; - FoodEntry({ + FoodEntryState({ required this.name, required this.mass, required this.kcalPerMass, + required this.waitingForNetwork, }) : id = const Uuid().v1(); @override diff --git a/lib/food_entry/food_entry_widget.dart b/lib/food_entry/food_entry_widget.dart index 49760ee..6f52be6 100644 --- a/lib/food_entry/food_entry_widget.dart +++ b/lib/food_entry/food_entry_widget.dart @@ -3,7 +3,7 @@ import 'package:calorimeter/food_entry/food_entry_bloc.dart'; import 'package:calorimeter/utils/row_with_spacers_widget.dart'; class FoodEntryWidget extends StatefulWidget { - final FoodEntry entry; + final FoodEntryState entry; final Function(BuildContext context, String id) onDelete; const FoodEntryWidget( @@ -38,11 +38,17 @@ class _FoodEntryWidgetState extends State { child: Padding( padding: const EdgeInsets.symmetric(horizontal: 8.0), child: RowWidget( - Text(widget.entry.name), - Text(widget.entry.mass.ceil().toString(), - textAlign: TextAlign.end), - Text(widget.entry.kcalPerMass.ceil().toString(), - textAlign: TextAlign.end), + widget.entry.waitingForNetwork + ? const Center(child: CircularProgressIndicator()) + : Text(widget.entry.name), + widget.entry.waitingForNetwork + ? Container() + : Text(widget.entry.mass.ceil().toString(), + textAlign: TextAlign.end), + widget.entry.waitingForNetwork + ? Container() + : Text(widget.entry.kcalPerMass.ceil().toString(), + textAlign: TextAlign.end), Opacity( opacity: showCancelAndDelete ? 0.0 : 1.0, child: Text( diff --git a/lib/food_scan/food_fact_lookup.dart b/lib/food_scan/food_fact_lookup.dart index 2d1667f..4ca736a 100644 --- a/lib/food_scan/food_fact_lookup.dart +++ b/lib/food_scan/food_fact_lookup.dart @@ -45,13 +45,28 @@ class FoodFactLookupClient { class FoodFactModel { final String name; final int kcalPer100g; + final int mass; - FoodFactModel({required this.name, required this.kcalPer100g}); + FoodFactModel({ + required this.name, + required this.mass, + required this.kcalPer100g, + }); factory FoodFactModel.fromJson(Map json) { + String quantityString = json['product']['product_quantity'] ?? "0"; + int quantity; + + try { + quantity = int.parse(quantityString); + } catch (e) { + quantity = 0; + } + return FoodFactModel( name: json['product']['product_name'], - kcalPer100g: json['product']['nutriments']['energy-kcal_100g']); + kcalPer100g: json['product']['nutriments']['energy-kcal_100g'], + mass: quantity); } } diff --git a/lib/perdate/entry_list.dart b/lib/perdate/entry_list.dart index 756f9fd..1017749 100644 --- a/lib/perdate/entry_list.dart +++ b/lib/perdate/entry_list.dart @@ -5,7 +5,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; class FoodEntryList extends StatelessWidget { - final List entries; + final List entries; const FoodEntryList({ required this.entries, @@ -17,6 +17,7 @@ class FoodEntryList extends StatelessWidget { return ListView.builder( itemCount: entries.length + 1, itemBuilder: (BuildContext itemBuilderContext, int listIndex) { + //last item in list is the widget to enter food if (listIndex == entries.length) { return Column( children: [ diff --git a/lib/perdate/perdate_widget.dart b/lib/perdate/perdate_widget.dart index f649070..3c725ce 100644 --- a/lib/perdate/perdate_widget.dart +++ b/lib/perdate/perdate_widget.dart @@ -1,6 +1,4 @@ import 'package:barcode_scan2/barcode_scan2.dart'; -import 'package:calorimeter/food_scan/food_fact_lookup.dart'; -import 'package:calorimeter/utils/enter_food_controller.dart'; import 'package:calorimeter/utils/scan_food_floating_button.dart'; import 'package:calorimeter/utils/app_drawer.dart'; import 'package:calorimeter/food_entry/food_entry_bloc.dart'; @@ -25,8 +23,8 @@ class PerDateWidget extends StatefulWidget { class _PerDateWidgetState extends State { late FoodStorage storage; - late Future> entriesFuture; - List entries = []; + late Future> entriesFuture; + List entries = []; @override void initState() { @@ -41,57 +39,60 @@ class _PerDateWidgetState extends State { @override Widget build(BuildContext context) { return FutureBuilder( - future: entriesFuture, - builder: (context, snapshot) { - return snapshot.connectionState != ConnectionState.done - ? const Center(child: CircularProgressIndicator()) - : MultiProvider( - providers: [ - ChangeNotifierProvider( - create: (context) => EnterFoodController()), - BlocProvider( - create: (context) => FoodEntryBloc( - initialState: FoodEntryState(foodEntries: entries), - storage: storage, - forDate: widget.date, - ), - ) - ], - child: BlocBuilder( - builder: (context, state) { + future: entriesFuture, + builder: (context, snapshot) { + return snapshot.connectionState != ConnectionState.done + ? const Center(child: CircularProgressIndicator()) + : MultiProvider( + providers: [ + BlocProvider( + create: (context) => FoodEntryBloc( + initialState: PageState(foodEntries: entries), + storage: storage, + forDate: widget.date, + ), + ) + ], + child: BlocConsumer( + listener: (context, pageState) { + if (pageState.errorString != null) { + showNewSnackbarWith(context, pageState.errorString!); + } + }, builder: (context, pageState) { return Scaffold( appBar: AppBar( title: Text(DateFormat.yMMMMd('de').format(widget.date)), actions: const [ThemeSwitcherButton()], ), - body: FoodEntryList(entries: state.foodEntries), + body: FoodEntryList(entries: pageState.foodEntries), bottomNavigationBar: BottomAppBar( shape: const RectangularNotchShape(), color: Theme.of(context).colorScheme.secondary, - child: SumWidget(foodEntries: state.foodEntries)), + child: + SumWidget(foodEntries: pageState.foodEntries)), drawer: const AppDrawer(), - floatingActionButton: OverflowBar( - children: [ - ScanFoodFloatingButton(onPressed: () { - _onScanButtonPressed(context); - }), - const SizedBox(width: 8), - CalendarFloatingButton( - startFromDate: widget.date, - onDateSelected: (dateSelected) { - _onDateSelected(dateSelected); - }, - ), - ], - ), + floatingActionButton: OverflowBar(children: [ + ScanFoodFloatingButton( + onPressed: () { + var result = BarcodeScanner.scan(); + context.read().add( + BarcodeScanned(scanResultFuture: result)); + }, + ), + const SizedBox(width: 8), + CalendarFloatingButton( + startFromDate: widget.date, + onDateSelected: (dateSelected) { + _onDateSelected(dateSelected); + }, + ), + ]), floatingActionButtonLocation: FloatingActionButtonLocation.endDocked); - }, - ), - ); - }, - ); + }), + ); + }); } void showNewSnackbarWith(BuildContext context, String text) { @@ -123,41 +124,6 @@ class _PerDateWidgetState extends State { ); }); } - - void _onScanButtonPressed(BuildContext context) async { - var client = FoodFactLookupClient(); - - var scanResult = await BarcodeScanner.scan(); - - if (scanResult.type == ResultType.Cancelled) { - return; - } - - if (!context.mounted) return; - - if (scanResult.type == ResultType.Error) { - showNewSnackbarWith(context, "Fehler beim Scannen des Barcodes."); - } - var response = await client.retrieveFoodInfo(scanResult.rawContent); - - if (!context.mounted) return; - - if (response.status == FoodFactResponseStatus.barcodeNotFound) { - showNewSnackbarWith(context, "Barcode konnte nicht gefunden werden."); - return; - } - - if (response.status == FoodFactResponseStatus.foodFactServerNotReachable) { - showNewSnackbarWith( - context, "OpenFoodFacts-Server konnte nicht erreicht werden."); - return; - } - - context.read().set( - response.food!.name, - response.food!.kcalPer100g.toString(), - ); - } } class ErrorSnackbar extends SnackBar { diff --git a/lib/storage/storage.dart b/lib/storage/storage.dart index ac4598b..06aa58c 100644 --- a/lib/storage/storage.dart +++ b/lib/storage/storage.dart @@ -8,7 +8,7 @@ import 'package:universal_platform/universal_platform.dart'; class FoodStorage { static late FoodStorage _instance; late String path; - final Map _foodLookupDatabase = {}; + final Map _foodLookupDatabase = {}; FoodStorage._create(); @@ -31,8 +31,8 @@ class FoodStorage { static FoodStorage getInstance() => _instance; - Future> getEntriesForDate(DateTime date) async { - List entries = []; + Future> getEntriesForDate(DateTime date) async { + List entries = []; var filePath = '$path/${date.year}/${date.month}/${date.day}'; var file = File(filePath); @@ -44,10 +44,27 @@ class FoodStorage { for (var line in lines) { var fields = line.splitWithIgnore(',', ignoreIn: '"'); - var entry = FoodEntry( - name: fields[1].replaceAll('"', ""), - mass: double.parse(fields[2]), - kcalPerMass: double.parse(fields[3])); + int mass = 0; + int kcalPerMass = 0; + + try { + mass = int.parse(fields[2]); + } catch (e) { + mass = double.parse(fields[2]).toInt(); + } + + try { + kcalPerMass = int.parse(fields[3]); + } catch (e) { + kcalPerMass = double.parse(fields[3]).toInt(); + } + + var entry = FoodEntryState( + name: fields[1].replaceAll('"', ""), + mass: mass, + kcalPerMass: kcalPerMass, + waitingForNetwork: false, + ); entries.add(entry); } @@ -55,7 +72,7 @@ class FoodStorage { } Future writeEntriesForDate( - DateTime date, List foodEntries) async { + DateTime date, List foodEntries) async { var filePath = '$path/${date.year}/${date.month}/${date.day}'; var file = File(filePath); @@ -157,12 +174,12 @@ class FoodStorage { } } - void addFoodEntryToLookupDatabase(FoodEntry entry) { + void addFoodEntryToLookupDatabase(FoodEntryState entry) { _foodLookupDatabase[entry.name] = entry.kcalPerMass; log("Added entry: ${entry.name}/${entry.kcalPerMass}"); } - Map get getFoodEntryLookupDatabase => _foodLookupDatabase; + Map get getFoodEntryLookupDatabase => _foodLookupDatabase; } extension SplitWithIgnore on String { diff --git a/lib/storage/storage.dart.orig b/lib/storage/storage.dart.orig new file mode 100644 index 0000000..55ea163 --- /dev/null +++ b/lib/storage/storage.dart.orig @@ -0,0 +1,215 @@ +import 'dart:developer'; +import 'dart:io'; + +import 'package:calorimeter/food_entry/food_entry_bloc.dart'; +import 'package:path_provider/path_provider.dart'; +import 'package:universal_platform/universal_platform.dart'; + +class FoodStorage { + static late FoodStorage _instance; + late String path; + final Map _foodLookupDatabase = {}; + + FoodStorage._create(); + + static Future create() async { + var storage = FoodStorage._create(); + + Directory dir = Directory(''); + + if (UniversalPlatform.isDesktop) { + dir = await getApplicationCacheDirectory(); + } else if (UniversalPlatform.isAndroid) { + dir = await getApplicationDocumentsDirectory(); + } + + storage.path = dir.path; + _instance = storage; + + return _instance; + } + + static FoodStorage getInstance() => _instance; + + Future> getEntriesForDate(DateTime date) async { + List entries = []; + var filePath = '$path/${date.year}/${date.month}/${date.day}'; + + var file = File(filePath); + var exists = await file.exists(); + + if (!exists) return []; + + var lines = await file.readAsLines(); + + for (var line in lines) { +<<<<<<< HEAD + var fields = line.splitWithIgnore(',', ignoreIn: '"'); + var entry = FoodEntry( + name: fields[1].replaceAll('"', ""), + mass: double.parse(fields[2]), + kcalPerMass: double.parse(fields[3])); +======= + var fields = line.split(','); + var entry = FoodEntryState( + name: fields[1], + mass: int.parse(fields[2]), + kcalPerMass: int.parse(fields[3]), + waitingForNetwork: false, + ); +>>>>>>> 7921f09 (wip) + entries.add(entry); + } + + return entries; + } + + Future writeEntriesForDate( + DateTime date, List foodEntries) async { + var filePath = '$path/${date.year}/${date.month}/${date.day}'; + var file = File(filePath); + + var exists = await file.exists(); + + if (!exists) { + await file.create(recursive: true); + } + + String fullString = ''; + for (var entry in foodEntries) { + fullString += '${entry.toString()}\n'; + } + + await file.writeAsString(fullString); + } + + Future updateLimit(double limit) async { + var filePath = '$path/limit'; + var file = File(filePath); + + var exists = await file.exists(); + if (!exists) { + await file.create(); + } + + await file.writeAsString(limit.toString()); + } + + Future readLimit() async { + var filePath = '$path/limit'; + var file = File(filePath); + var exists = await file.exists(); + + if (!exists) { + return 2000; + } + + var line = await file.readAsLines(); + + double limit; + try { + limit = double.parse(line[0]); + } catch (e) { + limit = 2000; + } + + return limit; + } + + Future readBrightness() async { + var filePath = '$path/brightness'; + var file = File(filePath); + var exists = await file.exists(); + + if (!exists) { + return 'dark'; + } + + var line = await file.readAsLines(); + + if (line.isEmpty || (line[0] != 'dark' && line[0] != 'light')) { + return 'dark'; + } + + return line[0]; + } + + Future writeBrightness(String brightness) async { + var filePath = '$path/brightness'; + var file = File(filePath); + var exists = await file.exists(); + + if (!exists) { + file.create(); + } + + await file.writeAsString(brightness); + } + + Future buildFoodLookupDatabase() async { + // get a list of dates of the last 365 days + var dates = List.generate(365, (idx) { + var durationToPast = Duration(days: idx); + return DateTime.now().subtract(durationToPast); + }); + + for (var date in dates.reversed) { + addFoodEntryToLookupDatabaseFor(date); + } + } + + Future addFoodEntryToLookupDatabaseFor(DateTime date) async { + var entriesForDate = await getEntriesForDate(date); + + for (var entry in entriesForDate) { + _foodLookupDatabase[entry.name] = entry.kcalPerMass; + log("Added entry: ${entry.name}/${entry.kcalPerMass}"); + } + } + + void addFoodEntryToLookupDatabase(FoodEntryState entry) { + _foodLookupDatabase[entry.name] = entry.kcalPerMass; + log("Added entry: ${entry.name}/${entry.kcalPerMass}"); + } + + Map get getFoodEntryLookupDatabase => _foodLookupDatabase; +} + +extension SplitWithIgnore on String { + List splitWithIgnore(String delimiter, {String? ignoreIn}) { + List parts = []; + + if (ignoreIn == null) { + return split(delimiter); + } + + int index = -1; + int indexCharAfterDelimiter = 0; + bool inIgnore = false; + for (var rune in runes) { + var char = String.fromCharCode(rune); + + index += 1; + + if (char == ignoreIn) { + inIgnore = !inIgnore; + continue; + } + + if (inIgnore) { + continue; + } + + if (char == delimiter) { + parts.add(substring(indexCharAfterDelimiter, index)); + indexCharAfterDelimiter = index + 1; + } + + if (index + 1 == length) { + parts.add(substring(indexCharAfterDelimiter, length)); + } + } + + return parts; + } +} diff --git a/lib/utils/enter_food_controller.dart b/lib/utils/enter_food_controller.dart deleted file mode 100644 index 44a998f..0000000 --- a/lib/utils/enter_food_controller.dart +++ /dev/null @@ -1,12 +0,0 @@ -import 'package:flutter/material.dart'; - -class EnterFoodController extends ChangeNotifier { - String name = ""; - String kcalPer100g = ""; - - void set(String newName, String newKcal) { - name = newName; - kcalPer100g = newKcal; - notifyListeners(); - } -} diff --git a/lib/utils/sum_widget.dart b/lib/utils/sum_widget.dart index 8184f21..5334030 100644 --- a/lib/utils/sum_widget.dart +++ b/lib/utils/sum_widget.dart @@ -4,7 +4,7 @@ import 'package:calorimeter/food_entry/food_entry_bloc.dart'; import 'package:calorimeter/utils/settings_bloc.dart'; class SumWidget extends StatelessWidget { - final List foodEntries; + final List foodEntries; const SumWidget({required this.foodEntries, super.key}); @override