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.
This commit is contained in:
parent
e75a0765b4
commit
a7a7f44050
@ -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<EnterFoodWidget> {
|
||||
late TextEditingController nameController;
|
||||
late TextEditingController massController;
|
||||
late TextEditingController kcalPerMassController;
|
||||
late Map<String, double> suggestions;
|
||||
late Map<String, int> suggestions;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
@ -32,18 +31,12 @@ class _EnterFoodWidgetState extends State<EnterFoodWidget> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Consumer<EnterFoodController>(
|
||||
builder: (context, food, child) {
|
||||
nameController.text = food.name;
|
||||
kcalPerMassController.text = food.kcalPer100g;
|
||||
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8.0),
|
||||
child: RowWidget(
|
||||
Autocomplete<String>(
|
||||
optionsViewOpenDirection: OptionsViewOpenDirection.down,
|
||||
fieldViewBuilder:
|
||||
(context, controller, focusNode, onSubmitted) {
|
||||
fieldViewBuilder: (context, controller, focusNode, onSubmitted) {
|
||||
nameController = controller;
|
||||
return TextFormField(
|
||||
controller: controller,
|
||||
@ -67,17 +60,18 @@ class _EnterFoodWidgetState extends State<EnterFoodWidget> {
|
||||
);
|
||||
},
|
||||
onSelected: (selectedFood) {
|
||||
double kcalPerMassForSelectedFood =
|
||||
suggestions[selectedFood]!;
|
||||
context
|
||||
.read<EnterFoodController>()
|
||||
.set(selectedFood, kcalPerMassForSelectedFood.toString());
|
||||
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")),
|
||||
label:
|
||||
Align(alignment: Alignment.centerRight, child: Text("Menge")),
|
||||
),
|
||||
keyboardType: TextInputType.number,
|
||||
controller: massController,
|
||||
@ -87,8 +81,7 @@ class _EnterFoodWidgetState extends State<EnterFoodWidget> {
|
||||
textAlign: TextAlign.end,
|
||||
decoration: const InputDecoration(
|
||||
label: Align(
|
||||
alignment: Alignment.centerRight,
|
||||
child: Text("kcal pro"))),
|
||||
alignment: Alignment.centerRight, child: Text("kcal pro"))),
|
||||
keyboardType: TextInputType.number,
|
||||
controller: kcalPerMassController,
|
||||
onSubmitted: (value) => onSubmitAction(),
|
||||
@ -104,16 +97,14 @@ class _EnterFoodWidgetState extends State<EnterFoodWidget> {
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
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<EnterFoodWidget> {
|
||||
|
||||
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(
|
||||
var entry = FoodEntryState(
|
||||
name: nameController.text,
|
||||
mass: massAsNumber,
|
||||
kcalPerMass: kcalPerMassAsNumber);
|
||||
kcalPerMass: kcalPerMassAsNumber,
|
||||
waitingForNetwork: false,
|
||||
);
|
||||
|
||||
widget.onAdd(context, entry);
|
||||
context.read<EnterFoodController>().set("", "");
|
||||
setState(() {
|
||||
nameController.text = "";
|
||||
massController.text = "";
|
||||
kcalPerMassController.text = "";
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -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<FoodEvent, FoodEntryState> {
|
||||
final FoodEntryState initialState;
|
||||
class FoodEntryBloc extends Bloc<FoodEvent, PageState> {
|
||||
final PageState initialState;
|
||||
final FoodStorage storage;
|
||||
final DateTime forDate;
|
||||
|
||||
@ -13,13 +15,13 @@ class FoodEntryBloc extends Bloc<FoodEvent, FoodEntryState> {
|
||||
required this.storage})
|
||||
: super(initialState) {
|
||||
on<FoodEntryEvent>(handleFoodEntryEvent);
|
||||
on<FoodDeletionEvent>(deleteFood);
|
||||
on<PageChangedEvent>(updateEntries);
|
||||
on<FoodDeletionEvent>(handleDeleteFoodEvent);
|
||||
on<BarcodeScanned>(handleBarcodeScannedEvent);
|
||||
}
|
||||
|
||||
void handleFoodEntryEvent(
|
||||
FoodEntryEvent event, Emitter<FoodEntryState> emit) async {
|
||||
FoodEntryState newState = FoodEntryState.from(state);
|
||||
FoodEntryEvent event, Emitter<PageState> 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<FoodEvent, FoodEntryState> {
|
||||
emit(newState);
|
||||
}
|
||||
|
||||
void deleteFood(FoodDeletionEvent event, Emitter<FoodEntryState> emit) async {
|
||||
void handleDeleteFoodEvent(
|
||||
FoodDeletionEvent event, Emitter<PageState> 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<FoodEntryState> emit) async {
|
||||
var entries = await storage.getEntriesForDate(event.changedToDate);
|
||||
var newState = FoodEntryState(foodEntries: entries);
|
||||
emit(newState);
|
||||
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(
|
||||
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<ScanResult> scanResultFuture;
|
||||
|
||||
PageChangedEvent({required this.changedToDate});
|
||||
BarcodeScanned({required this.scanResultFuture});
|
||||
}
|
||||
|
||||
class FoodEntryState {
|
||||
final List<FoodEntry> foodEntries;
|
||||
/// This is the state for one date/page
|
||||
class PageState {
|
||||
final List<FoodEntryState> 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<FoodEntry> 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
|
||||
|
@ -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,10 +38,16 @@ class _FoodEntryWidgetState extends State<FoodEntryWidget> {
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8.0),
|
||||
child: RowWidget(
|
||||
Text(widget.entry.name),
|
||||
Text(widget.entry.mass.ceil().toString(),
|
||||
widget.entry.waitingForNetwork
|
||||
? const Center(child: CircularProgressIndicator())
|
||||
: Text(widget.entry.name),
|
||||
widget.entry.waitingForNetwork
|
||||
? Container()
|
||||
: Text(widget.entry.mass.ceil().toString(),
|
||||
textAlign: TextAlign.end),
|
||||
Text(widget.entry.kcalPerMass.ceil().toString(),
|
||||
widget.entry.waitingForNetwork
|
||||
? Container()
|
||||
: Text(widget.entry.kcalPerMass.ceil().toString(),
|
||||
textAlign: TextAlign.end),
|
||||
Opacity(
|
||||
opacity: showCancelAndDelete ? 0.0 : 1.0,
|
||||
|
@ -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<String, dynamic> 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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -5,7 +5,7 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
class FoodEntryList extends StatelessWidget {
|
||||
final List<FoodEntry> entries;
|
||||
final List<FoodEntryState> 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: [
|
||||
|
@ -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<PerDateWidget> {
|
||||
late FoodStorage storage;
|
||||
late Future<List<FoodEntry>> entriesFuture;
|
||||
List<FoodEntry> entries = [];
|
||||
late Future<List<FoodEntryState>> entriesFuture;
|
||||
List<FoodEntryState> entries = [];
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
@ -47,35 +45,41 @@ class _PerDateWidgetState extends State<PerDateWidget> {
|
||||
? const Center(child: CircularProgressIndicator())
|
||||
: MultiProvider(
|
||||
providers: [
|
||||
ChangeNotifierProvider(
|
||||
create: (context) => EnterFoodController()),
|
||||
BlocProvider(
|
||||
create: (context) => FoodEntryBloc(
|
||||
initialState: FoodEntryState(foodEntries: entries),
|
||||
initialState: PageState(foodEntries: entries),
|
||||
storage: storage,
|
||||
forDate: widget.date,
|
||||
),
|
||||
)
|
||||
],
|
||||
child: BlocBuilder<FoodEntryBloc, FoodEntryState>(
|
||||
builder: (context, state) {
|
||||
child: BlocConsumer<FoodEntryBloc, PageState>(
|
||||
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);
|
||||
}),
|
||||
floatingActionButton: OverflowBar(children: [
|
||||
ScanFoodFloatingButton(
|
||||
onPressed: () {
|
||||
var result = BarcodeScanner.scan();
|
||||
context.read<FoodEntryBloc>().add(
|
||||
BarcodeScanned(scanResultFuture: result));
|
||||
},
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
CalendarFloatingButton(
|
||||
startFromDate: widget.date,
|
||||
@ -83,15 +87,12 @@ class _PerDateWidgetState extends State<PerDateWidget> {
|
||||
_onDateSelected(dateSelected);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
]),
|
||||
floatingActionButtonLocation:
|
||||
FloatingActionButtonLocation.endDocked);
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
}),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
void showNewSnackbarWith(BuildContext context, String text) {
|
||||
@ -123,41 +124,6 @@ class _PerDateWidgetState extends State<PerDateWidget> {
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
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<EnterFoodController>().set(
|
||||
response.food!.name,
|
||||
response.food!.kcalPer100g.toString(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class ErrorSnackbar extends SnackBar {
|
||||
|
@ -8,7 +8,7 @@ import 'package:universal_platform/universal_platform.dart';
|
||||
class FoodStorage {
|
||||
static late FoodStorage _instance;
|
||||
late String path;
|
||||
final Map<String, double> _foodLookupDatabase = {};
|
||||
final Map<String, int> _foodLookupDatabase = {};
|
||||
|
||||
FoodStorage._create();
|
||||
|
||||
@ -31,8 +31,8 @@ class FoodStorage {
|
||||
|
||||
static FoodStorage getInstance() => _instance;
|
||||
|
||||
Future<List<FoodEntry>> getEntriesForDate(DateTime date) async {
|
||||
List<FoodEntry> entries = [];
|
||||
Future<List<FoodEntryState>> getEntriesForDate(DateTime date) async {
|
||||
List<FoodEntryState> 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(
|
||||
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: double.parse(fields[2]),
|
||||
kcalPerMass: double.parse(fields[3]));
|
||||
mass: mass,
|
||||
kcalPerMass: kcalPerMass,
|
||||
waitingForNetwork: false,
|
||||
);
|
||||
entries.add(entry);
|
||||
}
|
||||
|
||||
@ -55,7 +72,7 @@ class FoodStorage {
|
||||
}
|
||||
|
||||
Future<void> writeEntriesForDate(
|
||||
DateTime date, List<FoodEntry> foodEntries) async {
|
||||
DateTime date, List<FoodEntryState> 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<String, double> get getFoodEntryLookupDatabase => _foodLookupDatabase;
|
||||
Map<String, int> get getFoodEntryLookupDatabase => _foodLookupDatabase;
|
||||
}
|
||||
|
||||
extension SplitWithIgnore on String {
|
||||
|
215
lib/storage/storage.dart.orig
Normal file
215
lib/storage/storage.dart.orig
Normal file
@ -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<String, int> _foodLookupDatabase = {};
|
||||
|
||||
FoodStorage._create();
|
||||
|
||||
static Future<FoodStorage> 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<List<FoodEntryState>> getEntriesForDate(DateTime date) async {
|
||||
List<FoodEntryState> 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<void> writeEntriesForDate(
|
||||
DateTime date, List<FoodEntryState> 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<void> 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<double> 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<String> 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<void> 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<void> buildFoodLookupDatabase() async {
|
||||
// get a list of dates of the last 365 days
|
||||
var dates = List<DateTime>.generate(365, (idx) {
|
||||
var durationToPast = Duration(days: idx);
|
||||
return DateTime.now().subtract(durationToPast);
|
||||
});
|
||||
|
||||
for (var date in dates.reversed) {
|
||||
addFoodEntryToLookupDatabaseFor(date);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> 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<String, int> get getFoodEntryLookupDatabase => _foodLookupDatabase;
|
||||
}
|
||||
|
||||
extension SplitWithIgnore on String {
|
||||
List<String> splitWithIgnore(String delimiter, {String? ignoreIn}) {
|
||||
List<String> 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;
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
@ -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<FoodEntry> foodEntries;
|
||||
final List<FoodEntryState> foodEntries;
|
||||
const SumWidget({required this.foodEntries, super.key});
|
||||
|
||||
@override
|
||||
|
Loading…
Reference in New Issue
Block a user