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

View File

@ -18,6 +18,7 @@ class FoodEntryBloc extends Bloc<FoodEvent, PageState> {
on<FoodChangedEvent>(handleFoodChangedEvent);
on<FoodDeletionEvent>(handleDeleteFoodEvent);
on<BarcodeScanned>(handleBarcodeScannedEvent);
on<FoodEntryTapped>(handleFoodEntryTapped);
}
void handleFoodEntryEvent(
@ -74,12 +75,23 @@ class FoodEntryBloc extends Bloc<FoodEvent, PageState> {
List<FoodEntryState> newList = List.from(state.foodEntries);
var newEntryWaiting = FoodEntryState(
kcalPer100: 0, name: "", mass: 0, waitingForNetwork: true);
kcalPer100: 0,
name: "",
mass: 0,
waitingForNetwork: true,
isSelected: false,
);
newList.add(newEntryWaiting);
emit(PageState(foodEntries: newList));
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) {
emit(PageState(
@ -100,11 +112,29 @@ class FoodEntryBloc extends Bloc<FoodEvent, PageState> {
mass: response.food?.mass ?? 0,
kcalPer100: response.food?.kcalPer100g ?? 0,
waitingForNetwork: false,
isSelected: false,
);
newList.add(newEntryFinishedWaiting);
newList.removeAt(index);
newList.insert(index, newEntryFinishedWaiting);
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 {}
@ -133,6 +163,12 @@ class BarcodeScanned extends FoodEvent {
BarcodeScanned({required this.scanResultFuture});
}
class FoodEntryTapped extends FoodEvent {
final String entryID;
FoodEntryTapped({required this.entryID});
}
/// This is the state for one date/page
class PageState {
final List<FoodEntryState> foodEntries;
@ -159,12 +195,14 @@ class FoodEntryState {
final int kcalPer100;
final String id;
final bool waitingForNetwork;
bool isSelected;
factory FoodEntryState({
required name,
required mass,
required kcalPer100,
required waitingForNetwork,
required isSelected,
}) {
return FoodEntryState.withID(
id: const Uuid().v1(),
@ -172,6 +210,7 @@ class FoodEntryState {
mass: mass,
kcalPer100: kcalPer100,
waitingForNetwork: waitingForNetwork,
isSelected: isSelected,
);
}
@ -181,8 +220,12 @@ class FoodEntryState {
required this.mass,
required this.kcalPer100,
required this.waitingForNetwork,
required this.isSelected,
});
set selected(bool selected) => isSelected = selected;
bool get selected => isSelected;
@override
String toString() {
//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 Function(BuildContext context, String id) onDelete;
final Function(BuildContext context, FoodEntryState entry) onChange;
final Function(BuildContext context, FoodEntryState entry) onTap;
const FoodEntryWidget({
super.key,
required this.entry,
required this.onDelete,
required this.onChange,
required this.onTap,
});
@override
@ -19,22 +21,15 @@ class FoodEntryWidget extends StatefulWidget {
}
class _FoodEntryWidgetState extends State<FoodEntryWidget> {
late bool showCancelAndDelete;
@override
void initState() {
showCancelAndDelete = false;
super.initState();
}
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: () {
setState(() {
showCancelAndDelete = !showCancelAndDelete;
});
},
onTap: () => widget.onTap(context, widget.entry),
child: Stack(
children: [
Positioned.fill(
@ -51,14 +46,14 @@ class _FoodEntryWidgetState extends State<FoodEntryWidget> {
: Text(widget.entry.mass.ceil().toString(),
textAlign: TextAlign.end),
Opacity(
opacity: showCancelAndDelete ? 0.0 : 1.0,
opacity: widget.entry.isSelected ? 0.0 : 1.0,
child: widget.entry.waitingForNetwork
? Container()
: Text(widget.entry.kcalPer100.ceil().toString(),
textAlign: TextAlign.end),
),
Opacity(
opacity: showCancelAndDelete ? 0.0 : 1.0,
opacity: widget.entry.isSelected ? 0.0 : 1.0,
child: widget.entry.waitingForNetwork
? Container()
: Text(
@ -73,13 +68,13 @@ class _FoodEntryWidgetState extends State<FoodEntryWidget> {
),
),
Opacity(
opacity: showCancelAndDelete ? 0.66 : 0.0,
opacity: widget.entry.isSelected ? 0.66 : 0.0,
child: Container(
color: Theme.of(context).colorScheme.secondary)),
]),
),
Opacity(
opacity: showCancelAndDelete ? 1.0 : 0.0,
opacity: widget.entry.isSelected ? 1.0 : 0.0,
child: Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
@ -87,10 +82,8 @@ class _FoodEntryWidgetState extends State<FoodEntryWidget> {
child: IconButton(
padding: const EdgeInsets.all(0.0),
icon: const Icon(Icons.cancel),
onPressed: showCancelAndDelete
? () => setState(() {
showCancelAndDelete = false;
})
onPressed: widget.entry.isSelected
? widget.onTap(context, widget.entry)
: null,
),
),
@ -100,7 +93,7 @@ class _FoodEntryWidgetState extends State<FoodEntryWidget> {
iconSize: 24,
icon: const Icon(Icons.delete),
color: Colors.redAccent,
onPressed: showCancelAndDelete
onPressed: widget.entry.isSelected
? () => widget.onDelete(context, widget.entry.id)
: null),
),
@ -108,8 +101,9 @@ class _FoodEntryWidgetState extends State<FoodEntryWidget> {
child: IconButton(
padding: const EdgeInsets.all(0.0),
icon: const Icon(Icons.edit),
onPressed: showCancelAndDelete
onPressed: widget.entry.isSelected
? () async {
widget.onTap(context, widget.entry);
await showDialog(
context: context,
builder: (dialogContext) {
@ -120,9 +114,6 @@ class _FoodEntryWidgetState extends State<FoodEntryWidget> {
});
},
);
setState(() {
showCancelAndDelete = false;
});
}
: null),
),
@ -248,6 +239,7 @@ class _FoodEntryChangeDialogState extends State<FoodEntryChangeDialog> {
mass: mass,
kcalPer100: kcalPer100,
waitingForNetwork: widget.entry.waitingForNetwork,
isSelected: false,
);
widget.onChange(context, newEntry);

View File

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

View File

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

View File

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