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