Handle changing a food entry
This commit is contained in:
parent
a7a7f44050
commit
ace03d98d2
@ -126,7 +126,7 @@ class _EnterFoodWidgetState extends State<EnterFoodWidget> {
|
|||||||
var entry = FoodEntryState(
|
var entry = FoodEntryState(
|
||||||
name: nameController.text,
|
name: nameController.text,
|
||||||
mass: massAsNumber,
|
mass: massAsNumber,
|
||||||
kcalPerMass: kcalPerMassAsNumber,
|
kcalPer100: kcalPerMassAsNumber,
|
||||||
waitingForNetwork: false,
|
waitingForNetwork: false,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -15,6 +15,7 @@ class FoodEntryBloc extends Bloc<FoodEvent, PageState> {
|
|||||||
required this.storage})
|
required this.storage})
|
||||||
: super(initialState) {
|
: super(initialState) {
|
||||||
on<FoodEntryEvent>(handleFoodEntryEvent);
|
on<FoodEntryEvent>(handleFoodEntryEvent);
|
||||||
|
on<FoodChangedEvent>(handleFoodChangedEvent);
|
||||||
on<FoodDeletionEvent>(handleDeleteFoodEvent);
|
on<FoodDeletionEvent>(handleDeleteFoodEvent);
|
||||||
on<BarcodeScanned>(handleBarcodeScannedEvent);
|
on<BarcodeScanned>(handleBarcodeScannedEvent);
|
||||||
}
|
}
|
||||||
@ -30,6 +31,22 @@ class FoodEntryBloc extends Bloc<FoodEvent, PageState> {
|
|||||||
emit(newState);
|
emit(newState);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void handleFoodChangedEvent(
|
||||||
|
FoodChangedEvent event, Emitter<PageState> emit) async {
|
||||||
|
var entries = state.foodEntries;
|
||||||
|
var index = entries.indexWhere((entry) {
|
||||||
|
return entry.id == event.newEntry.id;
|
||||||
|
});
|
||||||
|
|
||||||
|
entries.removeAt(index);
|
||||||
|
entries.insert(index, event.newEntry);
|
||||||
|
|
||||||
|
await storage.writeEntriesForDate(forDate, entries);
|
||||||
|
storage.addFoodEntryToLookupDatabase(event.newEntry);
|
||||||
|
|
||||||
|
emit(PageState(foodEntries: entries));
|
||||||
|
}
|
||||||
|
|
||||||
void handleDeleteFoodEvent(
|
void handleDeleteFoodEvent(
|
||||||
FoodDeletionEvent event, Emitter<PageState> emit) async {
|
FoodDeletionEvent event, Emitter<PageState> emit) async {
|
||||||
state.foodEntries.removeWhere((entry) => entry.id == event.entryID);
|
state.foodEntries.removeWhere((entry) => entry.id == event.entryID);
|
||||||
@ -57,7 +74,7 @@ class FoodEntryBloc extends Bloc<FoodEvent, PageState> {
|
|||||||
|
|
||||||
List<FoodEntryState> newList = List.from(state.foodEntries);
|
List<FoodEntryState> newList = List.from(state.foodEntries);
|
||||||
var newEntryWaiting = FoodEntryState(
|
var newEntryWaiting = FoodEntryState(
|
||||||
kcalPerMass: 0, name: "", mass: 0, waitingForNetwork: true);
|
kcalPer100: 0, name: "", mass: 0, waitingForNetwork: true);
|
||||||
newList.add(newEntryWaiting);
|
newList.add(newEntryWaiting);
|
||||||
emit(PageState(foodEntries: newList));
|
emit(PageState(foodEntries: newList));
|
||||||
|
|
||||||
@ -81,7 +98,7 @@ class FoodEntryBloc extends Bloc<FoodEvent, PageState> {
|
|||||||
var newEntryFinishedWaiting = FoodEntryState(
|
var newEntryFinishedWaiting = FoodEntryState(
|
||||||
name: response.food?.name ?? "",
|
name: response.food?.name ?? "",
|
||||||
mass: response.food?.mass ?? 0,
|
mass: response.food?.mass ?? 0,
|
||||||
kcalPerMass: response.food?.kcalPer100g ?? 0,
|
kcalPer100: response.food?.kcalPer100g ?? 0,
|
||||||
waitingForNetwork: false,
|
waitingForNetwork: false,
|
||||||
);
|
);
|
||||||
newList.add(newEntryFinishedWaiting);
|
newList.add(newEntryFinishedWaiting);
|
||||||
@ -98,6 +115,12 @@ class FoodEntryEvent extends FoodEvent {
|
|||||||
FoodEntryEvent({required this.entry});
|
FoodEntryEvent({required this.entry});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class FoodChangedEvent extends FoodEvent {
|
||||||
|
final FoodEntryState newEntry;
|
||||||
|
|
||||||
|
FoodChangedEvent({required this.newEntry});
|
||||||
|
}
|
||||||
|
|
||||||
class FoodDeletionEvent extends FoodEvent {
|
class FoodDeletionEvent extends FoodEvent {
|
||||||
final String entryID;
|
final String entryID;
|
||||||
|
|
||||||
@ -133,21 +156,37 @@ class PageState {
|
|||||||
class FoodEntryState {
|
class FoodEntryState {
|
||||||
final String name;
|
final String name;
|
||||||
final int mass;
|
final int mass;
|
||||||
final int kcalPerMass;
|
final int kcalPer100;
|
||||||
final String id;
|
final String id;
|
||||||
final bool waitingForNetwork;
|
final bool waitingForNetwork;
|
||||||
|
|
||||||
FoodEntryState({
|
factory FoodEntryState({
|
||||||
|
required name,
|
||||||
|
required mass,
|
||||||
|
required kcalPer100,
|
||||||
|
required waitingForNetwork,
|
||||||
|
}) {
|
||||||
|
return FoodEntryState.withID(
|
||||||
|
id: const Uuid().v1(),
|
||||||
|
name: name,
|
||||||
|
mass: mass,
|
||||||
|
kcalPer100: kcalPer100,
|
||||||
|
waitingForNetwork: waitingForNetwork,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
FoodEntryState.withID({
|
||||||
|
required this.id,
|
||||||
required this.name,
|
required this.name,
|
||||||
required this.mass,
|
required this.mass,
|
||||||
required this.kcalPerMass,
|
required this.kcalPer100,
|
||||||
required this.waitingForNetwork,
|
required this.waitingForNetwork,
|
||||||
}) : id = const Uuid().v1();
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() {
|
String toString() {
|
||||||
//we use quotation marks around the name because the name might contain
|
//we use quotation marks around the name because the name might contain
|
||||||
//commas and we want to store it in a csv file
|
//commas and we want to store it in a csv file
|
||||||
return '$id,"$name",$mass,$kcalPerMass';
|
return '$id,"$name",$mass,$kcalPer100';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,9 +5,14 @@ import 'package:calorimeter/utils/row_with_spacers_widget.dart';
|
|||||||
class FoodEntryWidget extends StatefulWidget {
|
class FoodEntryWidget extends StatefulWidget {
|
||||||
final FoodEntryState entry;
|
final FoodEntryState entry;
|
||||||
final Function(BuildContext context, String id) onDelete;
|
final Function(BuildContext context, String id) onDelete;
|
||||||
|
final Function(BuildContext context, FoodEntryState entry) onChange;
|
||||||
|
|
||||||
const FoodEntryWidget(
|
const FoodEntryWidget({
|
||||||
{super.key, required this.entry, required this.onDelete});
|
super.key,
|
||||||
|
required this.entry,
|
||||||
|
required this.onDelete,
|
||||||
|
required this.onChange,
|
||||||
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<FoodEntryWidget> createState() => _FoodEntryWidgetState();
|
State<FoodEntryWidget> createState() => _FoodEntryWidgetState();
|
||||||
@ -45,17 +50,24 @@ class _FoodEntryWidgetState extends State<FoodEntryWidget> {
|
|||||||
? Container()
|
? Container()
|
||||||
: Text(widget.entry.mass.ceil().toString(),
|
: Text(widget.entry.mass.ceil().toString(),
|
||||||
textAlign: TextAlign.end),
|
textAlign: TextAlign.end),
|
||||||
widget.entry.waitingForNetwork
|
|
||||||
? Container()
|
|
||||||
: Text(widget.entry.kcalPerMass.ceil().toString(),
|
|
||||||
textAlign: TextAlign.end),
|
|
||||||
Opacity(
|
Opacity(
|
||||||
opacity: showCancelAndDelete ? 0.0 : 1.0,
|
opacity: showCancelAndDelete ? 0.0 : 1.0,
|
||||||
child: Text(
|
child: widget.entry.waitingForNetwork
|
||||||
(widget.entry.mass * widget.entry.kcalPerMass / 100)
|
? Container()
|
||||||
.ceil()
|
: Text(widget.entry.kcalPer100.ceil().toString(),
|
||||||
.toString(),
|
textAlign: TextAlign.end),
|
||||||
textAlign: TextAlign.end),
|
),
|
||||||
|
Opacity(
|
||||||
|
opacity: showCancelAndDelete ? 0.0 : 1.0,
|
||||||
|
child: widget.entry.waitingForNetwork
|
||||||
|
? Container()
|
||||||
|
: Text(
|
||||||
|
(widget.entry.mass *
|
||||||
|
widget.entry.kcalPer100 /
|
||||||
|
100)
|
||||||
|
.ceil()
|
||||||
|
.toString(),
|
||||||
|
textAlign: TextAlign.end),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -92,6 +104,28 @@ class _FoodEntryWidgetState extends State<FoodEntryWidget> {
|
|||||||
? () => widget.onDelete(context, widget.entry.id)
|
? () => widget.onDelete(context, widget.entry.id)
|
||||||
: null),
|
: null),
|
||||||
),
|
),
|
||||||
|
SizedBox(
|
||||||
|
child: IconButton(
|
||||||
|
padding: const EdgeInsets.all(0.0),
|
||||||
|
icon: const Icon(Icons.edit),
|
||||||
|
onPressed: showCancelAndDelete
|
||||||
|
? () async {
|
||||||
|
await showDialog(
|
||||||
|
context: context,
|
||||||
|
builder: (dialogContext) {
|
||||||
|
return FoodEntryChangeDialog(
|
||||||
|
entry: widget.entry,
|
||||||
|
onChange: (context, entry) {
|
||||||
|
widget.onChange(context, entry);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
);
|
||||||
|
setState(() {
|
||||||
|
showCancelAndDelete = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
: null),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -100,3 +134,124 @@ class _FoodEntryWidgetState extends State<FoodEntryWidget> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class FoodEntryChangeDialog extends StatefulWidget {
|
||||||
|
final FoodEntryState entry;
|
||||||
|
final Function(BuildContext context, FoodEntryState entry) onChange;
|
||||||
|
|
||||||
|
const FoodEntryChangeDialog(
|
||||||
|
{required this.entry, super.key, required this.onChange});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<FoodEntryChangeDialog> createState() => _FoodEntryChangeDialogState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _FoodEntryChangeDialogState extends State<FoodEntryChangeDialog> {
|
||||||
|
late TextEditingController nameController;
|
||||||
|
late TextEditingController massController;
|
||||||
|
late TextEditingController kcalPer100Controller;
|
||||||
|
|
||||||
|
static const textFieldVerticalPadding = 16.0;
|
||||||
|
static const textFieldHorizontalPadding = 16.0;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
nameController = TextEditingController();
|
||||||
|
nameController.text = widget.entry.name;
|
||||||
|
|
||||||
|
massController = TextEditingController();
|
||||||
|
massController.text = widget.entry.mass.toString();
|
||||||
|
|
||||||
|
kcalPer100Controller = TextEditingController();
|
||||||
|
kcalPer100Controller.text = widget.entry.kcalPer100.toString();
|
||||||
|
|
||||||
|
super.initState();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return AlertDialog(
|
||||||
|
content: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(
|
||||||
|
vertical: textFieldVerticalPadding,
|
||||||
|
horizontal: textFieldHorizontalPadding),
|
||||||
|
child: TextField(
|
||||||
|
onSubmitted: (val) => _onSubmitAction(),
|
||||||
|
controller: nameController,
|
||||||
|
decoration: const InputDecoration(
|
||||||
|
label: Text("Name"),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(
|
||||||
|
vertical: textFieldVerticalPadding,
|
||||||
|
horizontal: textFieldHorizontalPadding),
|
||||||
|
child: TextField(
|
||||||
|
onSubmitted: (val) => _onSubmitAction(),
|
||||||
|
controller: massController,
|
||||||
|
decoration: const InputDecoration(
|
||||||
|
label: Text("Menge"),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(
|
||||||
|
vertical: textFieldVerticalPadding,
|
||||||
|
horizontal: textFieldHorizontalPadding),
|
||||||
|
child: TextField(
|
||||||
|
onSubmitted: (val) => _onSubmitAction(),
|
||||||
|
controller: kcalPer100Controller,
|
||||||
|
decoration: const InputDecoration(
|
||||||
|
label: Text("kcal pro Menge"),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
actions: [
|
||||||
|
IconButton(
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
},
|
||||||
|
icon: const Icon(Icons.cancel)),
|
||||||
|
IconButton(
|
||||||
|
onPressed: () => _onSubmitAction(),
|
||||||
|
icon: const Icon(Icons.check),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _onSubmitAction() {
|
||||||
|
int mass;
|
||||||
|
int kcalPer100;
|
||||||
|
|
||||||
|
try {
|
||||||
|
mass = int.parse(massController.text);
|
||||||
|
} catch (e) {
|
||||||
|
mass = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
kcalPer100 = int.parse(kcalPer100Controller.text);
|
||||||
|
} catch (e) {
|
||||||
|
kcalPer100 = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
var newEntry = FoodEntryState.withID(
|
||||||
|
id: widget.entry.id,
|
||||||
|
name: nameController.text,
|
||||||
|
mass: mass,
|
||||||
|
kcalPer100: kcalPer100,
|
||||||
|
waitingForNetwork: widget.entry.waitingForNetwork,
|
||||||
|
);
|
||||||
|
|
||||||
|
widget.onChange(context, newEntry);
|
||||||
|
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -39,11 +39,16 @@ class FoodEntryList extends StatelessWidget {
|
|||||||
FoodEntryWidget(
|
FoodEntryWidget(
|
||||||
key: ValueKey(entries[entryIndex].id),
|
key: ValueKey(entries[entryIndex].id),
|
||||||
entry: entries[entryIndex],
|
entry: entries[entryIndex],
|
||||||
onDelete: (callbackContext, id) {
|
onDelete: (_, id) {
|
||||||
callbackContext.read<FoodEntryBloc>().add(FoodDeletionEvent(
|
context.read<FoodEntryBloc>().add(FoodDeletionEvent(
|
||||||
entryID: id,
|
entryID: id,
|
||||||
));
|
));
|
||||||
},
|
},
|
||||||
|
onChange: (_, changedEntry) {
|
||||||
|
context
|
||||||
|
.read<FoodEntryBloc>()
|
||||||
|
.add(FoodChangedEvent(newEntry: changedEntry));
|
||||||
|
},
|
||||||
),
|
),
|
||||||
const Divider(),
|
const Divider(),
|
||||||
],
|
],
|
||||||
|
@ -54,43 +54,45 @@ class _PerDateWidgetState extends State<PerDateWidget> {
|
|||||||
)
|
)
|
||||||
],
|
],
|
||||||
child: BlocConsumer<FoodEntryBloc, PageState>(
|
child: BlocConsumer<FoodEntryBloc, PageState>(
|
||||||
listener: (context, pageState) {
|
listener: (context, pageState) {
|
||||||
if (pageState.errorString != null) {
|
if (pageState.errorString != null) {
|
||||||
showNewSnackbarWith(context, pageState.errorString!);
|
showNewSnackbarWith(context, pageState.errorString!);
|
||||||
}
|
}
|
||||||
}, builder: (context, pageState) {
|
},
|
||||||
return Scaffold(
|
builder: (context, pageState) {
|
||||||
appBar: AppBar(
|
return Scaffold(
|
||||||
title:
|
appBar: AppBar(
|
||||||
Text(DateFormat.yMMMMd('de').format(widget.date)),
|
title: Text(
|
||||||
actions: const [ThemeSwitcherButton()],
|
DateFormat.yMMMMd('de').format(widget.date)),
|
||||||
),
|
actions: const [ThemeSwitcherButton()],
|
||||||
body: FoodEntryList(entries: pageState.foodEntries),
|
|
||||||
bottomNavigationBar: BottomAppBar(
|
|
||||||
shape: const RectangularNotchShape(),
|
|
||||||
color: Theme.of(context).colorScheme.secondary,
|
|
||||||
child:
|
|
||||||
SumWidget(foodEntries: pageState.foodEntries)),
|
|
||||||
drawer: const AppDrawer(),
|
|
||||||
floatingActionButton: OverflowBar(children: [
|
|
||||||
ScanFoodFloatingButton(
|
|
||||||
onPressed: () {
|
|
||||||
var result = BarcodeScanner.scan();
|
|
||||||
context.read<FoodEntryBloc>().add(
|
|
||||||
BarcodeScanned(scanResultFuture: result));
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
const SizedBox(width: 8),
|
body: FoodEntryList(entries: pageState.foodEntries),
|
||||||
CalendarFloatingButton(
|
bottomNavigationBar: BottomAppBar(
|
||||||
startFromDate: widget.date,
|
shape: const RectangularNotchShape(),
|
||||||
onDateSelected: (dateSelected) {
|
color: Theme.of(context).colorScheme.secondary,
|
||||||
_onDateSelected(dateSelected);
|
child: SumWidget(
|
||||||
},
|
foodEntries: pageState.foodEntries)),
|
||||||
),
|
drawer: const AppDrawer(),
|
||||||
]),
|
floatingActionButton: OverflowBar(children: [
|
||||||
floatingActionButtonLocation:
|
ScanFoodFloatingButton(
|
||||||
FloatingActionButtonLocation.endDocked);
|
onPressed: () {
|
||||||
}),
|
var result = BarcodeScanner.scan();
|
||||||
|
context.read<FoodEntryBloc>().add(
|
||||||
|
BarcodeScanned(scanResultFuture: result));
|
||||||
|
},
|
||||||
|
),
|
||||||
|
const SizedBox(width: 8),
|
||||||
|
CalendarFloatingButton(
|
||||||
|
startFromDate: widget.date,
|
||||||
|
onDateSelected: (dateSelected) {
|
||||||
|
_onDateSelected(dateSelected);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]),
|
||||||
|
floatingActionButtonLocation:
|
||||||
|
FloatingActionButtonLocation.endDocked);
|
||||||
|
},
|
||||||
|
),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -62,7 +62,7 @@ class FoodStorage {
|
|||||||
var entry = FoodEntryState(
|
var entry = FoodEntryState(
|
||||||
name: fields[1].replaceAll('"', ""),
|
name: fields[1].replaceAll('"', ""),
|
||||||
mass: mass,
|
mass: mass,
|
||||||
kcalPerMass: kcalPerMass,
|
kcalPer100: kcalPerMass,
|
||||||
waitingForNetwork: false,
|
waitingForNetwork: false,
|
||||||
);
|
);
|
||||||
entries.add(entry);
|
entries.add(entry);
|
||||||
@ -169,14 +169,14 @@ class FoodStorage {
|
|||||||
var entriesForDate = await getEntriesForDate(date);
|
var entriesForDate = await getEntriesForDate(date);
|
||||||
|
|
||||||
for (var entry in entriesForDate) {
|
for (var entry in entriesForDate) {
|
||||||
_foodLookupDatabase[entry.name] = entry.kcalPerMass;
|
_foodLookupDatabase[entry.name] = entry.kcalPer100;
|
||||||
log("Added entry: ${entry.name}/${entry.kcalPerMass}");
|
log("Added entry: ${entry.name}/${entry.kcalPer100}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void addFoodEntryToLookupDatabase(FoodEntryState entry) {
|
void addFoodEntryToLookupDatabase(FoodEntryState entry) {
|
||||||
_foodLookupDatabase[entry.name] = entry.kcalPerMass;
|
_foodLookupDatabase[entry.name] = entry.kcalPer100;
|
||||||
log("Added entry: ${entry.name}/${entry.kcalPerMass}");
|
log("Added entry: ${entry.name}/${entry.kcalPer100}");
|
||||||
}
|
}
|
||||||
|
|
||||||
Map<String, int> get getFoodEntryLookupDatabase => _foodLookupDatabase;
|
Map<String, int> get getFoodEntryLookupDatabase => _foodLookupDatabase;
|
||||||
|
@ -13,7 +13,7 @@ class SumWidget extends StatelessWidget {
|
|||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
var sum = 0.0;
|
var sum = 0.0;
|
||||||
for (var entry in foodEntries) {
|
for (var entry in foodEntries) {
|
||||||
sum += entry.kcalPerMass / 100 * entry.mass;
|
sum += entry.kcalPer100 / 100 * entry.mass;
|
||||||
}
|
}
|
||||||
var diff = state.kcalLimit - sum;
|
var diff = state.kcalLimit - sum;
|
||||||
var diffLimit = state.kcalLimit ~/ 4;
|
var diffLimit = state.kcalLimit ~/ 4;
|
||||||
|
Loading…
Reference in New Issue
Block a user