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(
|
||||
name: nameController.text,
|
||||
mass: massAsNumber,
|
||||
kcalPerMass: kcalPerMassAsNumber,
|
||||
kcalPer100: kcalPerMassAsNumber,
|
||||
waitingForNetwork: false,
|
||||
);
|
||||
|
||||
|
@ -15,6 +15,7 @@ class FoodEntryBloc extends Bloc<FoodEvent, PageState> {
|
||||
required this.storage})
|
||||
: super(initialState) {
|
||||
on<FoodEntryEvent>(handleFoodEntryEvent);
|
||||
on<FoodChangedEvent>(handleFoodChangedEvent);
|
||||
on<FoodDeletionEvent>(handleDeleteFoodEvent);
|
||||
on<BarcodeScanned>(handleBarcodeScannedEvent);
|
||||
}
|
||||
@ -30,6 +31,22 @@ class FoodEntryBloc extends Bloc<FoodEvent, PageState> {
|
||||
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(
|
||||
FoodDeletionEvent event, Emitter<PageState> emit) async {
|
||||
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);
|
||||
var newEntryWaiting = FoodEntryState(
|
||||
kcalPerMass: 0, name: "", mass: 0, waitingForNetwork: true);
|
||||
kcalPer100: 0, name: "", mass: 0, waitingForNetwork: true);
|
||||
newList.add(newEntryWaiting);
|
||||
emit(PageState(foodEntries: newList));
|
||||
|
||||
@ -81,7 +98,7 @@ class FoodEntryBloc extends Bloc<FoodEvent, PageState> {
|
||||
var newEntryFinishedWaiting = FoodEntryState(
|
||||
name: response.food?.name ?? "",
|
||||
mass: response.food?.mass ?? 0,
|
||||
kcalPerMass: response.food?.kcalPer100g ?? 0,
|
||||
kcalPer100: response.food?.kcalPer100g ?? 0,
|
||||
waitingForNetwork: false,
|
||||
);
|
||||
newList.add(newEntryFinishedWaiting);
|
||||
@ -98,6 +115,12 @@ class FoodEntryEvent extends FoodEvent {
|
||||
FoodEntryEvent({required this.entry});
|
||||
}
|
||||
|
||||
class FoodChangedEvent extends FoodEvent {
|
||||
final FoodEntryState newEntry;
|
||||
|
||||
FoodChangedEvent({required this.newEntry});
|
||||
}
|
||||
|
||||
class FoodDeletionEvent extends FoodEvent {
|
||||
final String entryID;
|
||||
|
||||
@ -133,21 +156,37 @@ class PageState {
|
||||
class FoodEntryState {
|
||||
final String name;
|
||||
final int mass;
|
||||
final int kcalPerMass;
|
||||
final int kcalPer100;
|
||||
final String id;
|
||||
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.mass,
|
||||
required this.kcalPerMass,
|
||||
required this.kcalPer100,
|
||||
required this.waitingForNetwork,
|
||||
}) : id = const Uuid().v1();
|
||||
});
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
//we use quotation marks around the name because the name might contain
|
||||
//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 {
|
||||
final FoodEntryState entry;
|
||||
final Function(BuildContext context, String id) onDelete;
|
||||
final Function(BuildContext context, FoodEntryState entry) onChange;
|
||||
|
||||
const FoodEntryWidget(
|
||||
{super.key, required this.entry, required this.onDelete});
|
||||
const FoodEntryWidget({
|
||||
super.key,
|
||||
required this.entry,
|
||||
required this.onDelete,
|
||||
required this.onChange,
|
||||
});
|
||||
|
||||
@override
|
||||
State<FoodEntryWidget> createState() => _FoodEntryWidgetState();
|
||||
@ -45,17 +50,24 @@ class _FoodEntryWidgetState extends State<FoodEntryWidget> {
|
||||
? 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(
|
||||
(widget.entry.mass * widget.entry.kcalPerMass / 100)
|
||||
.ceil()
|
||||
.toString(),
|
||||
textAlign: TextAlign.end),
|
||||
child: widget.entry.waitingForNetwork
|
||||
? Container()
|
||||
: Text(widget.entry.kcalPer100.ceil().toString(),
|
||||
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)
|
||||
: 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(
|
||||
key: ValueKey(entries[entryIndex].id),
|
||||
entry: entries[entryIndex],
|
||||
onDelete: (callbackContext, id) {
|
||||
callbackContext.read<FoodEntryBloc>().add(FoodDeletionEvent(
|
||||
onDelete: (_, id) {
|
||||
context.read<FoodEntryBloc>().add(FoodDeletionEvent(
|
||||
entryID: id,
|
||||
));
|
||||
},
|
||||
onChange: (_, changedEntry) {
|
||||
context
|
||||
.read<FoodEntryBloc>()
|
||||
.add(FoodChangedEvent(newEntry: changedEntry));
|
||||
},
|
||||
),
|
||||
const Divider(),
|
||||
],
|
||||
|
@ -54,43 +54,45 @@ class _PerDateWidgetState extends State<PerDateWidget> {
|
||||
)
|
||||
],
|
||||
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: 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));
|
||||
},
|
||||
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()],
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
CalendarFloatingButton(
|
||||
startFromDate: widget.date,
|
||||
onDateSelected: (dateSelected) {
|
||||
_onDateSelected(dateSelected);
|
||||
},
|
||||
),
|
||||
]),
|
||||
floatingActionButtonLocation:
|
||||
FloatingActionButtonLocation.endDocked);
|
||||
}),
|
||||
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),
|
||||
CalendarFloatingButton(
|
||||
startFromDate: widget.date,
|
||||
onDateSelected: (dateSelected) {
|
||||
_onDateSelected(dateSelected);
|
||||
},
|
||||
),
|
||||
]),
|
||||
floatingActionButtonLocation:
|
||||
FloatingActionButtonLocation.endDocked);
|
||||
},
|
||||
),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
@ -62,7 +62,7 @@ class FoodStorage {
|
||||
var entry = FoodEntryState(
|
||||
name: fields[1].replaceAll('"', ""),
|
||||
mass: mass,
|
||||
kcalPerMass: kcalPerMass,
|
||||
kcalPer100: kcalPerMass,
|
||||
waitingForNetwork: false,
|
||||
);
|
||||
entries.add(entry);
|
||||
@ -169,14 +169,14 @@ class FoodStorage {
|
||||
var entriesForDate = await getEntriesForDate(date);
|
||||
|
||||
for (var entry in entriesForDate) {
|
||||
_foodLookupDatabase[entry.name] = entry.kcalPerMass;
|
||||
log("Added entry: ${entry.name}/${entry.kcalPerMass}");
|
||||
_foodLookupDatabase[entry.name] = entry.kcalPer100;
|
||||
log("Added entry: ${entry.name}/${entry.kcalPer100}");
|
||||
}
|
||||
}
|
||||
|
||||
void addFoodEntryToLookupDatabase(FoodEntryState entry) {
|
||||
_foodLookupDatabase[entry.name] = entry.kcalPerMass;
|
||||
log("Added entry: ${entry.name}/${entry.kcalPerMass}");
|
||||
_foodLookupDatabase[entry.name] = entry.kcalPer100;
|
||||
log("Added entry: ${entry.name}/${entry.kcalPer100}");
|
||||
}
|
||||
|
||||
Map<String, int> get getFoodEntryLookupDatabase => _foodLookupDatabase;
|
||||
|
@ -13,7 +13,7 @@ class SumWidget extends StatelessWidget {
|
||||
builder: (context, state) {
|
||||
var sum = 0.0;
|
||||
for (var entry in foodEntries) {
|
||||
sum += entry.kcalPerMass / 100 * entry.mass;
|
||||
sum += entry.kcalPer100 / 100 * entry.mass;
|
||||
}
|
||||
var diff = state.kcalLimit - sum;
|
||||
var diffLimit = state.kcalLimit ~/ 4;
|
||||
|
Loading…
Reference in New Issue
Block a user