Move select logic for FoodEntry into BLoC

This commit is contained in:
Marco 2024-09-25 17:33:40 +02:00
parent ace03d98d2
commit f28626c49e
6 changed files with 67 additions and 25 deletions

View File

@ -2,7 +2,6 @@ import 'package:calorimeter/storage/storage.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:calorimeter/food_entry/food_entry_bloc.dart'; import 'package:calorimeter/food_entry/food_entry_bloc.dart';
import 'package:calorimeter/utils/row_with_spacers_widget.dart'; import 'package:calorimeter/utils/row_with_spacers_widget.dart';
import 'package:provider/provider.dart';
class EnterFoodWidget extends StatefulWidget { class EnterFoodWidget extends StatefulWidget {
final Function(BuildContext context, FoodEntryState entry) onAdd; final Function(BuildContext context, FoodEntryState entry) onAdd;
@ -128,6 +127,7 @@ class _EnterFoodWidgetState extends State<EnterFoodWidget> {
mass: massAsNumber, mass: massAsNumber,
kcalPer100: kcalPerMassAsNumber, kcalPer100: kcalPerMassAsNumber,
waitingForNetwork: false, waitingForNetwork: false,
isSelected: false,
); );
widget.onAdd(context, entry); widget.onAdd(context, entry);

View File

@ -18,6 +18,7 @@ class FoodEntryBloc extends Bloc<FoodEvent, PageState> {
on<FoodChangedEvent>(handleFoodChangedEvent); on<FoodChangedEvent>(handleFoodChangedEvent);
on<FoodDeletionEvent>(handleDeleteFoodEvent); on<FoodDeletionEvent>(handleDeleteFoodEvent);
on<BarcodeScanned>(handleBarcodeScannedEvent); on<BarcodeScanned>(handleBarcodeScannedEvent);
on<FoodEntryTapped>(handleFoodEntryTapped);
} }
void handleFoodEntryEvent( void handleFoodEntryEvent(
@ -74,12 +75,23 @@ 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(
kcalPer100: 0, name: "", mass: 0, waitingForNetwork: true); kcalPer100: 0,
name: "",
mass: 0,
waitingForNetwork: true,
isSelected: false,
);
newList.add(newEntryWaiting); newList.add(newEntryWaiting);
emit(PageState(foodEntries: newList)); emit(PageState(foodEntries: newList));
await responseFuture.then((response) { await responseFuture.then((response) {
newList.removeWhere((entryState) => entryState.id == newEntryWaiting.id); var index = newList
.indexWhere((entryState) => entryState.id == newEntryWaiting.id);
// element not found (was deleted previously)
if (index == -1) {
return;
}
if (response.status == FoodFactResponseStatus.barcodeNotFound) { if (response.status == FoodFactResponseStatus.barcodeNotFound) {
emit(PageState( emit(PageState(
@ -100,11 +112,29 @@ class FoodEntryBloc extends Bloc<FoodEvent, PageState> {
mass: response.food?.mass ?? 0, mass: response.food?.mass ?? 0,
kcalPer100: response.food?.kcalPer100g ?? 0, kcalPer100: response.food?.kcalPer100g ?? 0,
waitingForNetwork: false, waitingForNetwork: false,
isSelected: false,
); );
newList.add(newEntryFinishedWaiting);
newList.removeAt(index);
newList.insert(index, newEntryFinishedWaiting);
emit(PageState(foodEntries: newList)); emit(PageState(foodEntries: newList));
}); });
} }
void handleFoodEntryTapped(
FoodEntryTapped event, Emitter<PageState> emit) async {
for (var entry in state.foodEntries) {
entry.isSelected = false;
}
var selectedEntry = state.foodEntries.firstWhere((entry) {
return entry.id == event.entryID;
});
selectedEntry.selected = !selectedEntry.selected;
emit(PageState(foodEntries: state.foodEntries));
}
} }
class FoodEvent {} class FoodEvent {}
@ -133,6 +163,12 @@ class BarcodeScanned extends FoodEvent {
BarcodeScanned({required this.scanResultFuture}); BarcodeScanned({required this.scanResultFuture});
} }
class FoodEntryTapped extends FoodEvent {
final String entryID;
FoodEntryTapped({required this.entryID});
}
/// This is the state for one date/page /// This is the state for one date/page
class PageState { class PageState {
final List<FoodEntryState> foodEntries; final List<FoodEntryState> foodEntries;
@ -159,12 +195,14 @@ class FoodEntryState {
final int kcalPer100; final int kcalPer100;
final String id; final String id;
final bool waitingForNetwork; final bool waitingForNetwork;
bool isSelected;
factory FoodEntryState({ factory FoodEntryState({
required name, required name,
required mass, required mass,
required kcalPer100, required kcalPer100,
required waitingForNetwork, required waitingForNetwork,
required isSelected,
}) { }) {
return FoodEntryState.withID( return FoodEntryState.withID(
id: const Uuid().v1(), id: const Uuid().v1(),
@ -172,6 +210,7 @@ class FoodEntryState {
mass: mass, mass: mass,
kcalPer100: kcalPer100, kcalPer100: kcalPer100,
waitingForNetwork: waitingForNetwork, waitingForNetwork: waitingForNetwork,
isSelected: isSelected,
); );
} }
@ -181,8 +220,12 @@ class FoodEntryState {
required this.mass, required this.mass,
required this.kcalPer100, required this.kcalPer100,
required this.waitingForNetwork, required this.waitingForNetwork,
required this.isSelected,
}); });
set selected(bool selected) => isSelected = selected;
bool get selected => isSelected;
@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

View File

@ -6,12 +6,14 @@ 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; final Function(BuildContext context, FoodEntryState entry) onChange;
final Function(BuildContext context, FoodEntryState entry) onTap;
const FoodEntryWidget({ const FoodEntryWidget({
super.key, super.key,
required this.entry, required this.entry,
required this.onDelete, required this.onDelete,
required this.onChange, required this.onChange,
required this.onTap,
}); });
@override @override
@ -19,22 +21,15 @@ class FoodEntryWidget extends StatefulWidget {
} }
class _FoodEntryWidgetState extends State<FoodEntryWidget> { class _FoodEntryWidgetState extends State<FoodEntryWidget> {
late bool showCancelAndDelete;
@override @override
void initState() { void initState() {
showCancelAndDelete = false;
super.initState(); super.initState();
} }
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return GestureDetector( return GestureDetector(
onTap: () { onTap: () => widget.onTap(context, widget.entry),
setState(() {
showCancelAndDelete = !showCancelAndDelete;
});
},
child: Stack( child: Stack(
children: [ children: [
Positioned.fill( Positioned.fill(
@ -51,14 +46,14 @@ class _FoodEntryWidgetState extends State<FoodEntryWidget> {
: Text(widget.entry.mass.ceil().toString(), : Text(widget.entry.mass.ceil().toString(),
textAlign: TextAlign.end), textAlign: TextAlign.end),
Opacity( Opacity(
opacity: showCancelAndDelete ? 0.0 : 1.0, opacity: widget.entry.isSelected ? 0.0 : 1.0,
child: widget.entry.waitingForNetwork child: widget.entry.waitingForNetwork
? Container() ? Container()
: Text(widget.entry.kcalPer100.ceil().toString(), : Text(widget.entry.kcalPer100.ceil().toString(),
textAlign: TextAlign.end), textAlign: TextAlign.end),
), ),
Opacity( Opacity(
opacity: showCancelAndDelete ? 0.0 : 1.0, opacity: widget.entry.isSelected ? 0.0 : 1.0,
child: widget.entry.waitingForNetwork child: widget.entry.waitingForNetwork
? Container() ? Container()
: Text( : Text(
@ -73,13 +68,13 @@ class _FoodEntryWidgetState extends State<FoodEntryWidget> {
), ),
), ),
Opacity( Opacity(
opacity: showCancelAndDelete ? 0.66 : 0.0, opacity: widget.entry.isSelected ? 0.66 : 0.0,
child: Container( child: Container(
color: Theme.of(context).colorScheme.secondary)), color: Theme.of(context).colorScheme.secondary)),
]), ]),
), ),
Opacity( Opacity(
opacity: showCancelAndDelete ? 1.0 : 0.0, opacity: widget.entry.isSelected ? 1.0 : 0.0,
child: Row( child: Row(
mainAxisAlignment: MainAxisAlignment.end, mainAxisAlignment: MainAxisAlignment.end,
children: [ children: [
@ -87,10 +82,8 @@ class _FoodEntryWidgetState extends State<FoodEntryWidget> {
child: IconButton( child: IconButton(
padding: const EdgeInsets.all(0.0), padding: const EdgeInsets.all(0.0),
icon: const Icon(Icons.cancel), icon: const Icon(Icons.cancel),
onPressed: showCancelAndDelete onPressed: widget.entry.isSelected
? () => setState(() { ? widget.onTap(context, widget.entry)
showCancelAndDelete = false;
})
: null, : null,
), ),
), ),
@ -100,7 +93,7 @@ class _FoodEntryWidgetState extends State<FoodEntryWidget> {
iconSize: 24, iconSize: 24,
icon: const Icon(Icons.delete), icon: const Icon(Icons.delete),
color: Colors.redAccent, color: Colors.redAccent,
onPressed: showCancelAndDelete onPressed: widget.entry.isSelected
? () => widget.onDelete(context, widget.entry.id) ? () => widget.onDelete(context, widget.entry.id)
: null), : null),
), ),
@ -108,8 +101,9 @@ class _FoodEntryWidgetState extends State<FoodEntryWidget> {
child: IconButton( child: IconButton(
padding: const EdgeInsets.all(0.0), padding: const EdgeInsets.all(0.0),
icon: const Icon(Icons.edit), icon: const Icon(Icons.edit),
onPressed: showCancelAndDelete onPressed: widget.entry.isSelected
? () async { ? () async {
widget.onTap(context, widget.entry);
await showDialog( await showDialog(
context: context, context: context,
builder: (dialogContext) { builder: (dialogContext) {
@ -120,9 +114,6 @@ class _FoodEntryWidgetState extends State<FoodEntryWidget> {
}); });
}, },
); );
setState(() {
showCancelAndDelete = false;
});
} }
: null), : null),
), ),
@ -248,6 +239,7 @@ class _FoodEntryChangeDialogState extends State<FoodEntryChangeDialog> {
mass: mass, mass: mass,
kcalPer100: kcalPer100, kcalPer100: kcalPer100,
waitingForNetwork: widget.entry.waitingForNetwork, waitingForNetwork: widget.entry.waitingForNetwork,
isSelected: false,
); );
widget.onChange(context, newEntry); widget.onChange(context, newEntry);

View File

@ -17,6 +17,7 @@ class FoodFactLookupClient {
try { try {
var request = await client.getUrl(Uri.parse(getProductUrl(ean))); var request = await client.getUrl(Uri.parse(getProductUrl(ean)));
await Future.delayed(const Duration(seconds: 10));
var response = await request.close(); var response = await request.close();

View File

@ -49,6 +49,11 @@ class FoodEntryList extends StatelessWidget {
.read<FoodEntryBloc>() .read<FoodEntryBloc>()
.add(FoodChangedEvent(newEntry: changedEntry)); .add(FoodChangedEvent(newEntry: changedEntry));
}, },
onTap: (_, tappedEntry) {
context
.read<FoodEntryBloc>()
.add(FoodEntryTapped(entryID: tappedEntry.id));
},
), ),
const Divider(), const Divider(),
], ],

View File

@ -64,6 +64,7 @@ class FoodStorage {
mass: mass, mass: mass,
kcalPer100: kcalPerMass, kcalPer100: kcalPerMass,
waitingForNetwork: false, waitingForNetwork: false,
isSelected: false,
); );
entries.add(entry); entries.add(entry);
} }