Bloc is now global and responsible for all dates
This commit is contained in:
parent
cb18e1d1f0
commit
ce373404ad
@ -4,61 +4,96 @@ import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:calorimeter/storage/storage.dart';
|
||||
import 'package:uuid/uuid.dart';
|
||||
|
||||
class FoodEntryBloc extends Bloc<FoodEvent, PageState> {
|
||||
final PageState initialState;
|
||||
class FoodEntryBloc extends Bloc<FoodEvent, GlobalEntryState> {
|
||||
final GlobalEntryState initialState;
|
||||
final FoodStorage storage;
|
||||
final DateTime forDate;
|
||||
|
||||
FoodEntryBloc(
|
||||
{required this.initialState,
|
||||
required this.forDate,
|
||||
required this.storage})
|
||||
FoodEntryBloc({required this.initialState, required this.storage})
|
||||
: super(initialState) {
|
||||
on<PageBeingInitialized>(handlePageBeingInitialized);
|
||||
on<FoodEntryEvent>(handleFoodEntryEvent);
|
||||
on<FoodChangedEvent>(handleFoodChangedEvent);
|
||||
on<FoodDeletionEvent>(handleDeleteFoodEvent);
|
||||
on<BarcodeScanned>(handleBarcodeScannedEvent);
|
||||
on<FoodEntryTapped>(handleFoodEntryTapped);
|
||||
}
|
||||
void handlePageBeingInitialized(
|
||||
PageBeingInitialized event, Emitter<GlobalEntryState> emit) async {
|
||||
var newList = await storage.getEntriesForDate(event.forDate);
|
||||
state.foodEntries.addAll({event.forDate: newList});
|
||||
|
||||
emit(GlobalEntryState(foodEntries: state.foodEntries));
|
||||
}
|
||||
|
||||
void handleFoodEntryEvent(
|
||||
FoodEntryEvent event, Emitter<PageState> emit) async {
|
||||
PageState newState = PageState.from(state);
|
||||
newState.addEntry(event.entry);
|
||||
FoodEntryEvent event, Emitter<GlobalEntryState> emit) async {
|
||||
var entriesForDate = state.foodEntries[event.forDate];
|
||||
entriesForDate ??= [];
|
||||
|
||||
await storage.writeEntriesForDate(forDate, newState.foodEntries);
|
||||
entriesForDate.add(event.entry);
|
||||
|
||||
await storage.writeEntriesForDate(event.forDate, entriesForDate);
|
||||
storage.addFoodEntryToLookupDatabase(event.entry);
|
||||
|
||||
emit(newState);
|
||||
// this is just checking if writing to the database worked
|
||||
// can be optimized out by just emitting newState
|
||||
var newList = await storage.getEntriesForDate(event.forDate);
|
||||
|
||||
var newFoodEntries = state.foodEntries;
|
||||
newFoodEntries.addAll({event.forDate: newList});
|
||||
|
||||
emit(GlobalEntryState(foodEntries: newFoodEntries));
|
||||
}
|
||||
|
||||
void handleFoodChangedEvent(
|
||||
FoodChangedEvent event, Emitter<PageState> emit) async {
|
||||
var entries = state.foodEntries;
|
||||
var index = entries.indexWhere((entry) {
|
||||
FoodChangedEvent event, Emitter<GlobalEntryState> emit) async {
|
||||
var entriesForDate = state.foodEntries[event.forDate];
|
||||
if (entriesForDate == null) return;
|
||||
|
||||
var index = entriesForDate.indexWhere((entry) {
|
||||
return entry.id == event.newEntry.id;
|
||||
});
|
||||
|
||||
entries.removeAt(index);
|
||||
entries.insert(index, event.newEntry);
|
||||
entriesForDate.removeAt(index);
|
||||
entriesForDate.insert(index, event.newEntry);
|
||||
|
||||
await storage.writeEntriesForDate(forDate, entries);
|
||||
await storage.writeEntriesForDate(event.forDate, entriesForDate);
|
||||
storage.addFoodEntryToLookupDatabase(event.newEntry);
|
||||
|
||||
emit(PageState(foodEntries: entries));
|
||||
// this is just checking if writing to the database worked
|
||||
// can be optimized out by just emitting newState
|
||||
var newList = await storage.getEntriesForDate(event.forDate);
|
||||
|
||||
var newFoodEntries = state.foodEntries;
|
||||
newFoodEntries.addAll({event.forDate: newList});
|
||||
|
||||
emit(GlobalEntryState(foodEntries: newFoodEntries));
|
||||
}
|
||||
|
||||
void handleDeleteFoodEvent(
|
||||
FoodDeletionEvent event, Emitter<PageState> emit) async {
|
||||
state.foodEntries.removeWhere((entry) => entry.id == event.entryID);
|
||||
FoodDeletionEvent event, Emitter<GlobalEntryState> emit) async {
|
||||
var entriesForDate = state.foodEntries[event.forDate];
|
||||
if (entriesForDate == null) return;
|
||||
|
||||
await storage.writeEntriesForDate(forDate, state.foodEntries);
|
||||
entriesForDate.removeWhere((entry) => entry.id == event.entryID);
|
||||
|
||||
emit(PageState.from(state));
|
||||
await storage.writeEntriesForDate(event.forDate, entriesForDate);
|
||||
|
||||
// this is just checking if writing to the database worked
|
||||
// can be optimized out by just emitting newState
|
||||
var newList = await storage.getEntriesForDate(event.forDate);
|
||||
|
||||
var newFoodEntries = state.foodEntries;
|
||||
newFoodEntries.addAll({event.forDate: newList});
|
||||
|
||||
emit(GlobalEntryState(foodEntries: newFoodEntries));
|
||||
}
|
||||
|
||||
void handleBarcodeScannedEvent(
|
||||
BarcodeScanned event, Emitter<PageState> emit) async {
|
||||
BarcodeScanned event, Emitter<GlobalEntryState> emit) async {
|
||||
var entriesForDate = state.foodEntries[event.forDate];
|
||||
if (entriesForDate == null) return;
|
||||
|
||||
var client = FoodFactLookupClient();
|
||||
var scanResult = await event.scanResultFuture;
|
||||
|
||||
@ -66,14 +101,13 @@ class FoodEntryBloc extends Bloc<FoodEvent, PageState> {
|
||||
return;
|
||||
}
|
||||
if (scanResult.type == ResultType.Error) {
|
||||
emit(PageState(
|
||||
emit(GlobalEntryState(
|
||||
foodEntries: state.foodEntries,
|
||||
errorString: "Fehler beim Scannen des Barcodes"));
|
||||
return;
|
||||
}
|
||||
var responseFuture = client.retrieveFoodInfo(scanResult.rawContent);
|
||||
|
||||
List<FoodEntryState> newList = List.from(state.foodEntries);
|
||||
var newEntryWaiting = FoodEntryState(
|
||||
kcalPer100: 0,
|
||||
name: "",
|
||||
@ -81,11 +115,13 @@ class FoodEntryBloc extends Bloc<FoodEvent, PageState> {
|
||||
waitingForNetwork: true,
|
||||
isSelected: false,
|
||||
);
|
||||
newList.add(newEntryWaiting);
|
||||
emit(PageState(foodEntries: newList));
|
||||
entriesForDate.add(newEntryWaiting);
|
||||
var newFoodEntries = state.foodEntries;
|
||||
newFoodEntries.addAll({event.forDate: entriesForDate});
|
||||
emit(GlobalEntryState(foodEntries: newFoodEntries));
|
||||
|
||||
await responseFuture.then((response) async {
|
||||
var index = newList
|
||||
var index = entriesForDate
|
||||
.indexWhere((entryState) => entryState.id == newEntryWaiting.id);
|
||||
|
||||
// element not found (was deleted previously)
|
||||
@ -94,25 +130,23 @@ class FoodEntryBloc extends Bloc<FoodEvent, PageState> {
|
||||
}
|
||||
|
||||
if (response.status == FoodFactResponseStatus.barcodeNotFound) {
|
||||
List<FoodEntryState> listWithEntryRemoved =
|
||||
List.from(state.foodEntries);
|
||||
listWithEntryRemoved
|
||||
.removeWhere((entry) => entry.id == newEntryWaiting.id);
|
||||
entriesForDate.removeWhere((entry) => entry.id == newEntryWaiting.id);
|
||||
var newFoodEntries = state.foodEntries;
|
||||
newFoodEntries.addAll({event.forDate: entriesForDate});
|
||||
|
||||
emit(PageState(
|
||||
foodEntries: listWithEntryRemoved,
|
||||
emit(GlobalEntryState(
|
||||
foodEntries: newFoodEntries,
|
||||
errorString: "Barcode konnte nicht gefunden werden."));
|
||||
return;
|
||||
}
|
||||
if (response.status ==
|
||||
FoodFactResponseStatus.foodFactServerNotReachable) {
|
||||
List<FoodEntryState> listWithEntryRemoved =
|
||||
List.from(state.foodEntries);
|
||||
listWithEntryRemoved
|
||||
.removeWhere((entry) => entry.id == newEntryWaiting.id);
|
||||
entriesForDate.removeWhere((entry) => entry.id == newEntryWaiting.id);
|
||||
var newFoodEntries = state.foodEntries;
|
||||
newFoodEntries.addAll({event.forDate: entriesForDate});
|
||||
|
||||
emit(PageState(
|
||||
foodEntries: listWithEntryRemoved,
|
||||
emit(GlobalEntryState(
|
||||
foodEntries: newFoodEntries,
|
||||
errorString: "OpenFoodFacts-Server konnte nicht erreicht werden."));
|
||||
return;
|
||||
}
|
||||
@ -125,91 +159,114 @@ class FoodEntryBloc extends Bloc<FoodEvent, PageState> {
|
||||
isSelected: false,
|
||||
);
|
||||
|
||||
newList.removeAt(index);
|
||||
newList.insert(index, newEntryFinishedWaiting);
|
||||
entriesForDate.removeAt(index);
|
||||
entriesForDate.insert(index, newEntryFinishedWaiting);
|
||||
|
||||
await storage.writeEntriesForDate(forDate, newList);
|
||||
await storage.writeEntriesForDate(event.forDate, entriesForDate);
|
||||
storage.addFoodEntryToLookupDatabase(newEntryFinishedWaiting);
|
||||
|
||||
emit(PageState(foodEntries: newList));
|
||||
var entriesFromStorage = await storage.getEntriesForDate(event.forDate);
|
||||
var newFoodEntries = state.foodEntries;
|
||||
newFoodEntries.addAll({event.forDate: entriesFromStorage});
|
||||
|
||||
emit(GlobalEntryState(foodEntries: newFoodEntries));
|
||||
});
|
||||
}
|
||||
|
||||
void handleFoodEntryTapped(
|
||||
FoodEntryTapped event, Emitter<PageState> emit) async {
|
||||
FoodEntryTapped event, Emitter<GlobalEntryState> emit) async {
|
||||
var entriesForDate = state.foodEntries[event.forDate];
|
||||
if (entriesForDate == null) return;
|
||||
|
||||
var oldStateOfTappedEntry = event.entry.isSelected;
|
||||
|
||||
for (var entry in state.foodEntries) {
|
||||
for (var entry in entriesForDate) {
|
||||
entry.isSelected = false;
|
||||
}
|
||||
|
||||
var selectedEntry = state.foodEntries.firstWhere((entry) {
|
||||
var selectedEntry = entriesForDate.firstWhere((entry) {
|
||||
return entry.id == event.entry.id;
|
||||
});
|
||||
|
||||
selectedEntry.isSelected = !oldStateOfTappedEntry;
|
||||
|
||||
emit(PageState(foodEntries: state.foodEntries));
|
||||
emit(GlobalEntryState(foodEntries: state.foodEntries));
|
||||
}
|
||||
}
|
||||
|
||||
class FoodEvent {}
|
||||
class FoodEvent {
|
||||
final DateTime forDate;
|
||||
|
||||
FoodEvent({required this.forDate});
|
||||
}
|
||||
|
||||
class PageBeingInitialized extends FoodEvent {
|
||||
PageBeingInitialized({required super.forDate});
|
||||
}
|
||||
|
||||
class FoodEntryEvent extends FoodEvent {
|
||||
final FoodEntryState entry;
|
||||
|
||||
FoodEntryEvent({required this.entry});
|
||||
FoodEntryEvent({required this.entry, required super.forDate});
|
||||
}
|
||||
|
||||
class FoodChangedEvent extends FoodEvent {
|
||||
final FoodEntryState newEntry;
|
||||
|
||||
FoodChangedEvent({required this.newEntry});
|
||||
FoodChangedEvent({required this.newEntry, required super.forDate});
|
||||
}
|
||||
|
||||
class FoodDeletionEvent extends FoodEvent {
|
||||
final String entryID;
|
||||
|
||||
FoodDeletionEvent({required this.entryID});
|
||||
FoodDeletionEvent({required this.entryID, required super.forDate});
|
||||
}
|
||||
|
||||
class BarcodeScanned extends FoodEvent {
|
||||
final Future<ScanResult> scanResultFuture;
|
||||
|
||||
BarcodeScanned({required this.scanResultFuture});
|
||||
BarcodeScanned({required this.scanResultFuture, required super.forDate});
|
||||
}
|
||||
|
||||
class FoodEntryTapped extends FoodEvent {
|
||||
final FoodEntryState entry;
|
||||
|
||||
FoodEntryTapped({required this.entry});
|
||||
FoodEntryTapped({required this.entry, required super.forDate});
|
||||
}
|
||||
|
||||
/// This is the state for one date/page
|
||||
class PageState {
|
||||
final List<FoodEntryState> foodEntries;
|
||||
class GlobalEntryState {
|
||||
final Map<DateTime, List<FoodEntryState>> foodEntries;
|
||||
final String? errorString;
|
||||
|
||||
PageState({required this.foodEntries, this.errorString});
|
||||
GlobalEntryState({required this.foodEntries, this.errorString});
|
||||
|
||||
factory PageState.init() {
|
||||
return PageState(foodEntries: []);
|
||||
factory GlobalEntryState.init() {
|
||||
return GlobalEntryState(foodEntries: {});
|
||||
}
|
||||
|
||||
static from(PageState state) {
|
||||
return PageState(foodEntries: state.foodEntries);
|
||||
static from(GlobalEntryState state) {
|
||||
return GlobalEntryState(foodEntries: state.foodEntries);
|
||||
}
|
||||
|
||||
void addEntry(FoodEntryState entry) {
|
||||
foodEntries.add(entry);
|
||||
bool addEntry(FoodEntryState entry, DateTime date) {
|
||||
var list = foodEntries[date];
|
||||
|
||||
if (list == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
list.add(entry);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
class FoodEntryState {
|
||||
final String id;
|
||||
final String name;
|
||||
final int mass;
|
||||
final int kcalPer100;
|
||||
final String id;
|
||||
final bool waitingForNetwork;
|
||||
bool isSelected;
|
||||
|
||||
|
@ -1,3 +1,4 @@
|
||||
import 'package:calorimeter/food_entry/food_entry_bloc.dart';
|
||||
import 'package:calorimeter/perdate/perdate_pageview.dart';
|
||||
import 'package:calorimeter/storage/storage.dart';
|
||||
import 'package:calorimeter/utils/settings_bloc.dart';
|
||||
@ -7,6 +8,9 @@ import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_localizations/flutter_localizations.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
|
||||
List<FoodEntryState> entriesForToday = [];
|
||||
DateTime timeNow = DateTime.now();
|
||||
|
||||
void main() async {
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
@ -14,6 +18,16 @@ void main() async {
|
||||
|
||||
var storage = await FoodStorage.create();
|
||||
await storage.buildFoodLookupDatabase();
|
||||
timeNow = DateTime.now().copyWith(
|
||||
hour: 0,
|
||||
isUtc: true,
|
||||
minute: 0,
|
||||
second: 0,
|
||||
millisecond: 0,
|
||||
microsecond: 0);
|
||||
|
||||
entriesForToday = await storage.getEntriesForDate(timeNow);
|
||||
|
||||
var kcalLimit = await storage.readLimit();
|
||||
var brightness = await storage.readBrightness();
|
||||
|
||||
@ -42,6 +56,13 @@ class MainApp extends StatelessWidget {
|
||||
return SafeArea(
|
||||
child: MultiBlocProvider(
|
||||
providers: [
|
||||
BlocProvider(
|
||||
create: (context) => FoodEntryBloc(
|
||||
storage: storage,
|
||||
initialState:
|
||||
GlobalEntryState(foodEntries: {timeNow: entriesForToday}),
|
||||
),
|
||||
),
|
||||
BlocProvider(
|
||||
create: (context) => SettingsDataBloc(
|
||||
SettingsState(kcalLimit: kcalLimit),
|
||||
|
@ -6,9 +6,11 @@ import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
class FoodEntryList extends StatelessWidget {
|
||||
final List<FoodEntryState> entries;
|
||||
final DateTime date;
|
||||
|
||||
const FoodEntryList({
|
||||
required this.entries,
|
||||
required this.date,
|
||||
super.key,
|
||||
});
|
||||
|
||||
@ -25,7 +27,7 @@ class FoodEntryList extends StatelessWidget {
|
||||
onAdd: (context, entry) {
|
||||
context
|
||||
.read<FoodEntryBloc>()
|
||||
.add(FoodEntryEvent(entry: entry));
|
||||
.add(FoodEntryEvent(entry: entry, forDate: date));
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 75),
|
||||
@ -40,19 +42,19 @@ class FoodEntryList extends StatelessWidget {
|
||||
key: ValueKey(entries[entryIndex].id),
|
||||
entry: entries[entryIndex],
|
||||
onDelete: (_, id) {
|
||||
context.read<FoodEntryBloc>().add(FoodDeletionEvent(
|
||||
entryID: id,
|
||||
));
|
||||
context
|
||||
.read<FoodEntryBloc>()
|
||||
.add(FoodDeletionEvent(entryID: id, forDate: date));
|
||||
},
|
||||
onChange: (_, changedEntry) {
|
||||
context
|
||||
.read<FoodEntryBloc>()
|
||||
.add(FoodChangedEvent(newEntry: changedEntry));
|
||||
context.read<FoodEntryBloc>().add(
|
||||
FoodChangedEvent(newEntry: changedEntry, forDate: date),
|
||||
);
|
||||
},
|
||||
onTap: (_, tappedEntry) {
|
||||
context
|
||||
.read<FoodEntryBloc>()
|
||||
.add(FoodEntryTapped(entry: tappedEntry));
|
||||
context.read<FoodEntryBloc>().add(
|
||||
FoodEntryTapped(entry: tappedEntry, forDate: date),
|
||||
);
|
||||
},
|
||||
),
|
||||
const Divider(),
|
||||
|
@ -61,7 +61,7 @@ class _PerDatePageviewState extends State<PerDatePageview> {
|
||||
|
||||
return PerDateWidget(
|
||||
key: ValueKey(dateToBuildWidgetFor.toString()),
|
||||
date: dateToBuildWidgetFor,
|
||||
date: dateToBuildWidgetFor.copyWith(isUtc: true),
|
||||
onDateSelected: (dateSelected) {
|
||||
if (dateSelected == null) return;
|
||||
|
||||
|
@ -11,7 +11,6 @@ import 'package:calorimeter/utils/theme_switcher_button.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
class PerDateWidget extends StatefulWidget {
|
||||
final DateTime date;
|
||||
@ -26,16 +25,13 @@ class PerDateWidget extends StatefulWidget {
|
||||
class _PerDateWidgetState extends State<PerDateWidget>
|
||||
with AutomaticKeepAliveClientMixin<PerDateWidget> {
|
||||
late FoodStorage storage;
|
||||
late Future<List<FoodEntryState>> entriesFuture;
|
||||
List<FoodEntryState> entries = [];
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
storage = FoodStorage.getInstance();
|
||||
entriesFuture = storage.getEntriesForDate(widget.date);
|
||||
entriesFuture.then((val) {
|
||||
entries = val;
|
||||
});
|
||||
context
|
||||
.read<FoodEntryBloc>()
|
||||
.add(PageBeingInitialized(forDate: widget.date));
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@ -48,47 +44,37 @@ class _PerDateWidgetState extends State<PerDateWidget>
|
||||
Widget build(BuildContext context) {
|
||||
super.build(context);
|
||||
|
||||
return FutureBuilder(
|
||||
future: entriesFuture,
|
||||
builder: (context, snapshot) {
|
||||
return snapshot.connectionState != ConnectionState.done
|
||||
? const Center(child: CircularProgressIndicator())
|
||||
: MultiProvider(
|
||||
providers: [
|
||||
BlocProvider(
|
||||
create: (context) => FoodEntryBloc(
|
||||
initialState: PageState(foodEntries: entries),
|
||||
storage: storage,
|
||||
forDate: widget.date,
|
||||
),
|
||||
)
|
||||
],
|
||||
child: BlocConsumer<FoodEntryBloc, PageState>(
|
||||
return BlocConsumer<FoodEntryBloc, GlobalEntryState>(
|
||||
listener: (context, pageState) {
|
||||
if (pageState.errorString != null) {
|
||||
showNewSnackbarWith(context, pageState.errorString!);
|
||||
}
|
||||
},
|
||||
builder: (context, pageState) {
|
||||
builder: (context, globalState) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text(
|
||||
DateFormat.yMMMMd('de').format(widget.date)),
|
||||
title: Text(DateFormat.yMMMMd('de').format(widget.date)),
|
||||
actions: const [ThemeSwitcherButton()],
|
||||
),
|
||||
body: FoodEntryList(entries: pageState.foodEntries),
|
||||
body: FoodEntryList(
|
||||
entries: globalState.foodEntries[widget.date] ?? [],
|
||||
date: widget.date),
|
||||
bottomNavigationBar: BottomAppBar(
|
||||
shape: const RectangularNotchShape(),
|
||||
color: Theme.of(context).colorScheme.secondary,
|
||||
child: SumWidget(
|
||||
foodEntries: pageState.foodEntries)),
|
||||
foodEntries: globalState.foodEntries[widget.date] ?? [])),
|
||||
drawer: const AppDrawer(),
|
||||
floatingActionButton: OverflowBar(children: [
|
||||
ScanFoodFloatingButton(
|
||||
onPressed: () {
|
||||
var result = BarcodeScanner.scan();
|
||||
context.read<FoodEntryBloc>().add(
|
||||
BarcodeScanned(scanResultFuture: result));
|
||||
BarcodeScanned(
|
||||
scanResultFuture: result,
|
||||
forDate: widget.date,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
@ -102,9 +88,7 @@ class _PerDateWidgetState extends State<PerDateWidget>
|
||||
floatingActionButtonLocation:
|
||||
FloatingActionButtonLocation.endDocked);
|
||||
},
|
||||
),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
void showNewSnackbarWith(BuildContext context, String text) {
|
||||
|
Loading…
Reference in New Issue
Block a user