wip
This commit is contained in:
parent
0e8b37dca2
commit
4f65425e66
@ -6,7 +6,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 +18,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() {
|
||||
@ -67,8 +67,7 @@ class _EnterFoodWidgetState extends State<EnterFoodWidget> {
|
||||
);
|
||||
},
|
||||
onSelected: (selectedFood) {
|
||||
double kcalPerMassForSelectedFood =
|
||||
suggestions[selectedFood]!;
|
||||
int kcalPerMassForSelectedFood = suggestions[selectedFood]!;
|
||||
context
|
||||
.read<EnterFoodController>()
|
||||
.set(selectedFood, kcalPerMassForSelectedFood.toString());
|
||||
@ -109,11 +108,11 @@ 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,19 +122,21 @@ 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(
|
||||
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<EnterFoodController>().set("", "");
|
||||
|
@ -1,10 +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;
|
||||
|
||||
@ -19,8 +20,8 @@ class FoodEntryBloc extends Bloc<FoodEvent, FoodEntryState> {
|
||||
}
|
||||
|
||||
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);
|
||||
@ -30,22 +31,49 @@ class FoodEntryBloc extends Bloc<FoodEvent, FoodEntryState> {
|
||||
}
|
||||
|
||||
void handleDeleteFoodEvent(
|
||||
FoodDeletionEvent event, Emitter<FoodEntryState> emit) async {
|
||||
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 handleBarcodeScannedEvent(
|
||||
BarcodeScanned event, Emitter<FoodEntryState> emit) async {}
|
||||
BarcodeScanned event, Emitter<PageState> emit) async {
|
||||
var client = FoodFactLookupClient();
|
||||
var scanResult = await event.scanResultFuture;
|
||||
|
||||
if (scanResult.type == ResultType.Cancelled) {
|
||||
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);
|
||||
|
||||
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});
|
||||
}
|
||||
@ -63,36 +91,36 @@ class BarcodeScanned extends FoodEvent {
|
||||
}
|
||||
|
||||
/// This is the state for one date/page
|
||||
class FoodEntryState {
|
||||
final List<FoodEntry> foodEntries;
|
||||
class PageState {
|
||||
final List<FoodEntryState> foodEntries;
|
||||
|
||||
FoodEntryState({required this.foodEntries});
|
||||
PageState({required this.foodEntries});
|
||||
|
||||
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,11 +38,17 @@ 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(),
|
||||
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(
|
||||
|
@ -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: [
|
||||
|
@ -24,8 +24,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() {
|
||||
@ -50,13 +50,13 @@ class _PerDateWidgetState extends State<PerDateWidget> {
|
||||
create: (context) => EnterFoodController()),
|
||||
BlocProvider(
|
||||
create: (context) => FoodEntryBloc(
|
||||
initialState: FoodEntryState(foodEntries: entries),
|
||||
initialState: PageState(foodEntries: entries),
|
||||
storage: storage,
|
||||
forDate: widget.date,
|
||||
),
|
||||
)
|
||||
],
|
||||
child: BlocBuilder<FoodEntryBloc, FoodEntryState>(
|
||||
child: BlocBuilder<FoodEntryBloc, PageState>(
|
||||
builder: (context, state) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
|
@ -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,12 @@ 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]));
|
||||
var entry = FoodEntryState(
|
||||
name: fields[1].replaceAll('"', ""),
|
||||
mass: int.parse(fields[2]),
|
||||
kcalPerMass: int.parse(fields[3]),
|
||||
waitingForNetwork: false,
|
||||
);
|
||||
entries.add(entry);
|
||||
}
|
||||
|
||||
@ -55,7 +57,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);
|
||||
|
||||
|
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;
|
||||
}
|
||||
}
|
@ -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