From d59f09eed43ed58e85d1ca227e7d2377830e8479 Mon Sep 17 00:00:00 2001 From: Marco Date: Sun, 9 Jun 2024 19:06:10 +0200 Subject: [PATCH] Make persistence work --- lib/app_drawer.dart | 3 +- lib/enter_food_widget.dart | 26 ++---- lib/food_entry_bloc.dart | 44 +++++++-- lib/food_entry_widget.dart | 13 ++- lib/main.dart | 22 +++-- lib/perdate_widget.dart | 76 +++++++++++++++ lib/perday_widget.dart | 38 -------- lib/storage/storage.dart | 59 ++++++++++++ macos/Flutter/GeneratedPluginRegistrant.swift | 2 + pubspec.lock | 92 ++++++++++++++++++- pubspec.yaml | 1 + 11 files changed, 295 insertions(+), 81 deletions(-) create mode 100644 lib/perdate_widget.dart delete mode 100644 lib/perday_widget.dart create mode 100644 lib/storage/storage.dart diff --git a/lib/app_drawer.dart b/lib/app_drawer.dart index 1ddcc04..8e64e3d 100644 --- a/lib/app_drawer.dart +++ b/lib/app_drawer.dart @@ -1,6 +1,5 @@ import 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; -import 'package:kalodings/calendar.dart'; class AppDrawer extends StatelessWidget { const AppDrawer({ @@ -27,7 +26,7 @@ class AppDrawer extends StatelessWidget { title: const Text('MenĂ¼')), ), ListTile( - trailing: const Icon(Icons.calendar_month), + trailing: const Icon(Icons.today), title: const Text('Kalender'), onTap: () { context.goNamed('calendar'); diff --git a/lib/enter_food_widget.dart b/lib/enter_food_widget.dart index 6bec0f7..3a63737 100644 --- a/lib/enter_food_widget.dart +++ b/lib/enter_food_widget.dart @@ -6,9 +6,9 @@ import 'package:kalodings/food_entry_bloc.dart'; import 'package:kalodings/row_with_spacers_widget.dart'; class EnterFoodWidget extends StatefulWidget { - const EnterFoodWidget({ - super.key, - }); + final Function(BuildContext context, FoodEntry entry) onAdd; + + const EnterFoodWidget({super.key, required this.onAdd}); @override State createState() => _EnterFoodWidgetState(); @@ -80,22 +80,16 @@ class _EnterFoodWidgetState extends State { mass: massAsNumber, kcalPerMass: kcalPerMassAsNumber); - log('Entry id: ${entry.id}'); - - context.read().add(FoodEntryEvent(entry: entry)); + widget.onAdd(context, entry); }, child: const Icon(Icons.add)); - return Column( - children: [ - RowWidgetWithSpacers( - nameWidget, - massWidget, - kcalPerMassWidget, - null, - enterButton, - ), - ], + return RowWidgetWithSpacers( + nameWidget, + massWidget, + kcalPerMassWidget, + null, + enterButton, ); } } diff --git a/lib/food_entry_bloc.dart b/lib/food_entry_bloc.dart index 51889ae..39392d3 100644 --- a/lib/food_entry_bloc.dart +++ b/lib/food_entry_bloc.dart @@ -1,34 +1,61 @@ import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:kalodings/storage/storage.dart'; import 'package:uuid/uuid.dart'; class FoodEntryBloc extends Bloc { - FoodEntryBloc(super.initialState) { - on(addFoodEntryToState); + final FoodStorage storage; + + FoodEntryBloc(super.initialState, {required this.storage}) { + on(addFoodEntry); on(deleteFood); + on(updateEntries); } - void addFoodEntryToState(FoodEntryEvent event, Emitter emit) { + void addFoodEntry(FoodEntryEvent event, Emitter emit) async { FoodEntryState newState = FoodEntryState.from(state); newState.addEntry(event.entry); + + await storage.writeEntriesForDate(event.date, newState.foodEntries); + emit(newState); } - void deleteFood(FoodDeletionEvent event, Emitter emit) { + void deleteFood(FoodDeletionEvent event, Emitter emit) async { state.foodEntries.removeWhere((entry) => entry.id == event.entryID); + + await storage.writeEntriesForDate(event.date, state.foodEntries); + emit(FoodEntryState.from(state)); } + + void updateEntries( + PageChangedEvent event, Emitter emit) async { + var entries = await storage.getEntriesForDate(event.changedToDate); + var newState = FoodEntryState(foodEntries: entries); + emit(newState); + } } class FoodEvent {} class FoodEntryEvent extends FoodEvent { final FoodEntry entry; - FoodEntryEvent({required this.entry}); + final DateTime date; + + FoodEntryEvent({required this.entry, required this.date}); } class FoodDeletionEvent extends FoodEvent { final String entryID; - FoodDeletionEvent({required this.entryID}); + final DateTime date; + + FoodDeletionEvent({required this.entryID, required this.date}); +} + +class PageChangedEvent extends FoodEvent { + final DateTime changedToDate; + + PageChangedEvent({required this.changedToDate}); } class FoodEntryState { @@ -61,4 +88,9 @@ class FoodEntry { required this.mass, required this.kcalPerMass, }) : id = const Uuid().v1(); + + @override + String toString() { + return '$id,$name,$mass,$kcalPerMass'; + } } diff --git a/lib/food_entry_widget.dart b/lib/food_entry_widget.dart index f01810a..18aa777 100644 --- a/lib/food_entry_widget.dart +++ b/lib/food_entry_widget.dart @@ -1,12 +1,13 @@ import 'package:flutter/material.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:kalodings/food_entry_bloc.dart'; import 'package:kalodings/row_with_spacers_widget.dart'; class FoodEntryWidget extends StatelessWidget { final FoodEntry entry; + final Function(BuildContext context) onDelete; - const FoodEntryWidget({super.key, required this.entry}); + const FoodEntryWidget( + {super.key, required this.entry, required this.onDelete}); @override Widget build(BuildContext context) { @@ -16,13 +17,11 @@ class FoodEntryWidget extends StatelessWidget { Text(entry.mass.toString()), Text(entry.kcalPerMass.toString()), Text((entry.mass * entry.kcalPerMass / 100).toString()), - IconButton( + ElevatedButton( onPressed: () { - context - .read() - .add(FoodDeletionEvent(entryID: entry.id)); + onDelete(context); }, - icon: const Icon(Icons.delete_forever_rounded)), + child: const Icon(Icons.delete_forever_rounded)), ), ); } diff --git a/lib/main.dart b/lib/main.dart index d835ed4..0618332 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,24 +1,26 @@ -// ignore_for_file: prefer_const_constructors - import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:go_router/go_router.dart'; import 'package:kalodings/calendar.dart'; import 'package:kalodings/food_entry_bloc.dart'; -import 'package:kalodings/perday_widget.dart'; +import 'package:kalodings/perdate_widget.dart'; +import 'package:kalodings/storage/storage.dart'; -void main() { - runApp(const MainApp()); +void main() async { + var storage = await FoodStorage.create(); + + runApp(MainApp(storage: storage)); } class MainApp extends StatelessWidget { - const MainApp({super.key}); + final FoodStorage storage; + const MainApp({required this.storage, super.key}); @override Widget build(BuildContext context) { return BlocProvider( create: (BuildContext context) { - return FoodEntryBloc(FoodEntryState.init()); + return FoodEntryBloc(FoodEntryState.init(), storage: storage); }, child: MaterialApp.router( theme: ThemeData.dark(), @@ -33,7 +35,7 @@ final router = GoRouter(routes: [ path: '/', name: 'perDayToday', builder: (context, state) { - return PerDay(DateTime.now()); + return PerDateWidget(DateTime.now()); }), GoRoute( path: '/day', @@ -46,12 +48,12 @@ final router = GoRouter(routes: [ day = state.extra as DateTime; } - return PerDay(day); + return PerDateWidget(day); }), GoRoute( path: '/calendar', name: 'calendar', builder: (context, state) { - return CalendarWidget(); + return const CalendarWidget(); }), ]); diff --git a/lib/perdate_widget.dart b/lib/perdate_widget.dart new file mode 100644 index 0000000..3268b64 --- /dev/null +++ b/lib/perdate_widget.dart @@ -0,0 +1,76 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:go_router/go_router.dart'; +import 'package:kalodings/app_drawer.dart'; +import 'package:kalodings/enter_food_widget.dart'; +import 'package:kalodings/food_entry_bloc.dart'; +import 'package:kalodings/food_entry_widget.dart'; +import 'package:kalodings/sum_widget.dart'; + +class PerDateWidget extends StatefulWidget { + final DateTime date; + const PerDateWidget(this.date, {super.key}); + + @override + State createState() => _PerDateWidgetState(); +} + +class _PerDateWidgetState extends State { + @override + void initState() { + super.initState(); + + context + .read() + .add(PageChangedEvent(changedToDate: widget.date)); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: Text(widget.date.toString()), + ), + drawer: const AppDrawer(), + body: BlocBuilder( + builder: (context, state) { + return ListView.builder( + itemCount: state.foodEntries.length + 2, + itemBuilder: (BuildContext itemBuilderContext, int index) { + if (index == state.foodEntries.length) { + return const SumWidget(); + } + if (index == state.foodEntries.length + 1) { + return EnterFoodWidget( + onAdd: (context, entry) { + context + .read() + .add(FoodEntryEvent(entry: entry, date: widget.date)); + }, + ); + } + + return FoodEntryWidget( + entry: state.foodEntries[index], + onDelete: (callbackContext) { + callbackContext.read().add( + FoodDeletionEvent( + entryID: state.foodEntries[index].id, + date: widget.date), + ); + }, + ); + }, + ); + }, + ), + floatingActionButton: FloatingActionButton( + onPressed: () { + context.goNamed('calendar'); + }, + child: const Icon(Icons.today)), + ); + } + + void deleteCallback(BuildContext context, String idToDelete, DateTime date) {} +} diff --git a/lib/perday_widget.dart b/lib/perday_widget.dart deleted file mode 100644 index 2769601..0000000 --- a/lib/perday_widget.dart +++ /dev/null @@ -1,38 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:kalodings/app_drawer.dart'; -import 'package:kalodings/enter_food_widget.dart'; -import 'package:kalodings/food_entry_bloc.dart'; -import 'package:kalodings/food_entry_widget.dart'; -import 'package:kalodings/sum_widget.dart'; - -class PerDay extends StatelessWidget { - final DateTime day; - const PerDay(this.day, {super.key}); - - @override - Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar( - title: Text(day.toString()), - ), - drawer: const AppDrawer(), - body: BlocBuilder( - builder: (context, state) { - return ListView.builder( - itemCount: state.foodEntries.length + 2, - itemBuilder: (BuildContext itemBuilderContext, int index) { - if (index == state.foodEntries.length) { - return const SumWidget(); - } - if (index == state.foodEntries.length + 1) { - return const EnterFoodWidget(); - } - - return FoodEntryWidget(entry: state.foodEntries[index]); - }, - ); - }, - )); - } -} diff --git a/lib/storage/storage.dart b/lib/storage/storage.dart new file mode 100644 index 0000000..a601e2b --- /dev/null +++ b/lib/storage/storage.dart @@ -0,0 +1,59 @@ +import 'dart:io'; + +import 'package:kalodings/food_entry_bloc.dart'; +import 'package:path_provider/path_provider.dart'; + +class FoodStorage { + late String path; + FoodStorage._create(); + + static Future create() async { + var storage = FoodStorage._create(); + var directory = await getApplicationCacheDirectory(); + storage.path = directory.path; + + return storage; + } + + Future> getEntriesForDate(DateTime date) async { + List entries = []; + var filePath = '$path/${date.year}/${date.month}/${date.day}'; + + var file = File(filePath); + var exists = await file.exists(); + + if (!exists) return []; + + var lines = await file.readAsLines(); + + for (var line in lines) { + var fields = line.split(','); + var entry = FoodEntry( + name: fields[1], + mass: double.parse(fields[2]), + kcalPerMass: double.parse(fields[3])); + entries.add(entry); + } + + return entries; + } + + Future writeEntriesForDate( + DateTime date, List foodEntries) async { + var filePath = '$path/${date.year}/${date.month}/${date.day}'; + var file = File(filePath); + + var exists = await file.exists(); + + if (!exists) { + await file.create(recursive: true); + } + + String fullString = ''; + for (var entry in foodEntries) { + fullString += '${entry.toString()}\n'; + } + + await file.writeAsString(fullString); + } +} diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index cccf817..e777c67 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -5,6 +5,8 @@ import FlutterMacOS import Foundation +import path_provider_foundation func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { + PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) } diff --git a/pubspec.lock b/pubspec.lock index cdd190e..77477ff 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -65,6 +65,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.3.1" + ffi: + dependency: transitive + description: + name: ffi + sha256: "493f37e7df1804778ff3a53bd691d8692ddf69702cf4c1c1096a2e41b4779e21" + url: "https://pub.dev" + source: hosted + version: "2.1.2" fixnum: dependency: transitive description: @@ -192,6 +200,70 @@ packages: url: "https://pub.dev" source: hosted version: "1.9.0" + path_provider: + dependency: "direct main" + description: + name: path_provider + sha256: c9e7d3a4cd1410877472158bee69963a4579f78b68c65a2b7d40d1a7a88bb161 + url: "https://pub.dev" + source: hosted + version: "2.1.3" + path_provider_android: + dependency: transitive + description: + name: path_provider_android + sha256: "9c96da072b421e98183f9ea7464898428e764bc0ce5567f27ec8693442e72514" + url: "https://pub.dev" + source: hosted + version: "2.2.5" + path_provider_foundation: + dependency: transitive + description: + name: path_provider_foundation + sha256: f234384a3fdd67f989b4d54a5d73ca2a6c422fa55ae694381ae0f4375cd1ea16 + url: "https://pub.dev" + source: hosted + version: "2.4.0" + path_provider_linux: + dependency: transitive + description: + name: path_provider_linux + sha256: f7a1fe3a634fe7734c8d3f2766ad746ae2a2884abe22e241a8b301bf5cac3279 + url: "https://pub.dev" + source: hosted + version: "2.2.1" + path_provider_platform_interface: + dependency: transitive + description: + name: path_provider_platform_interface + sha256: "88f5779f72ba699763fa3a3b06aa4bf6de76c8e5de842cf6f29e2e06476c2334" + url: "https://pub.dev" + source: hosted + version: "2.1.2" + path_provider_windows: + dependency: transitive + description: + name: path_provider_windows + sha256: "8bc9f22eee8690981c22aa7fc602f5c85b497a6fb2ceb35ee5a5e5ed85ad8170" + url: "https://pub.dev" + source: hosted + version: "2.2.1" + platform: + dependency: transitive + description: + name: platform + sha256: "12220bb4b65720483f8fa9450b4332347737cf8213dd2840d8b2c823e47243ec" + url: "https://pub.dev" + source: hosted + version: "3.1.4" + plugin_platform_interface: + dependency: transitive + description: + name: plugin_platform_interface + sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02" + url: "https://pub.dev" + source: hosted + version: "2.1.8" provider: dependency: transitive description: @@ -301,6 +373,22 @@ packages: url: "https://pub.dev" source: hosted version: "14.2.2" + win32: + dependency: transitive + description: + name: win32 + sha256: a79dbe579cb51ecd6d30b17e0cae4e0ea15e2c0e66f69ad4198f22a6789e94f4 + url: "https://pub.dev" + source: hosted + version: "5.5.1" + xdg_directories: + dependency: transitive + description: + name: xdg_directories + sha256: faea9dee56b520b55a566385b84f2e8de55e7496104adada9962e0bd11bcff1d + url: "https://pub.dev" + source: hosted + version: "1.0.4" sdks: - dart: ">=3.3.0 <4.0.0" - flutter: ">=3.18.0-18.0.pre.54" + dart: ">=3.4.0 <4.0.0" + flutter: ">=3.22.0" diff --git a/pubspec.yaml b/pubspec.yaml index 6fe6ed9..d07c22e 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -11,6 +11,7 @@ dependencies: sdk: flutter flutter_bloc: ^8.1.5 go_router: ^14.1.4 + path_provider: ^2.1.3 quiver: ^3.2.1 uuid: ^4.4.0