diff --git a/devtools_options.yaml b/devtools_options.yaml new file mode 100644 index 0000000..fa0b357 --- /dev/null +++ b/devtools_options.yaml @@ -0,0 +1,3 @@ +description: This file stores settings for Dart & Flutter DevTools. +documentation: https://docs.flutter.dev/tools/devtools/extensions#configure-extension-enablement-states +extensions: diff --git a/lib/app_drawer.dart b/lib/app_drawer.dart new file mode 100644 index 0000000..1ddcc04 --- /dev/null +++ b/lib/app_drawer.dart @@ -0,0 +1,45 @@ +import 'package:flutter/material.dart'; +import 'package:go_router/go_router.dart'; +import 'package:kalodings/calendar.dart'; + +class AppDrawer extends StatelessWidget { + const AppDrawer({ + super.key, + }); + + @override + Widget build(BuildContext context) { + return Drawer( + child: ListView( + padding: EdgeInsets.zero, + children: [ + DrawerHeader( + decoration: const BoxDecoration( + color: Colors.redAccent, + ), + child: ListTile( + leading: IconButton( + icon: const Icon(Icons.close), + onPressed: () { + Navigator.pop(context); + }, + ), + title: const Text('Menü')), + ), + ListTile( + trailing: const Icon(Icons.calendar_month), + title: const Text('Kalender'), + onTap: () { + context.goNamed('calendar'); + }, + ), + ListTile( + title: const Text('Einstellungen'), + trailing: const Icon(Icons.settings), + onTap: () {}, + ), + ], + ), + ); + } +} diff --git a/lib/calendar.dart b/lib/calendar.dart new file mode 100644 index 0000000..84323b0 --- /dev/null +++ b/lib/calendar.dart @@ -0,0 +1,20 @@ +import 'package:flutter/material.dart'; +import 'package:go_router/go_router.dart'; + +class CalendarWidget extends StatelessWidget { + const CalendarWidget({super.key}); + + @override + Widget build(BuildContext context) { + return Scaffold( + body: CalendarDatePicker( + onDateChanged: (value) { + context.goNamed('perDay', extra: value); + }, + initialDate: DateTime.now(), + firstDate: DateTime.now().subtract(const Duration(days: 30)), + lastDate: DateTime.now(), + ), + ); + } +} diff --git a/lib/enter_food_widget.dart b/lib/enter_food_widget.dart index 71a765d..6bec0f7 100644 --- a/lib/enter_food_widget.dart +++ b/lib/enter_food_widget.dart @@ -1,6 +1,9 @@ +import 'dart:developer'; + 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 EnterFoodWidget extends StatefulWidget { const EnterFoodWidget({ @@ -32,95 +35,67 @@ class _EnterFoodWidgetState extends State { var kcalPerMassWidget = TextField( decoration: const InputDecoration(hintText: "kcal pro 100g"), + keyboardType: TextInputType.number, controller: kcalPerMassController); + var enterButton = ElevatedButton( + onPressed: () { + double massAsNumber = 0.0; + double kcalPerMassAsNumber = 0.0; + + try { + massAsNumber = + double.parse(massController.text.replaceAll(",", ".")); + } catch (e) { + var snackbar = + const SnackBar(content: Text("Menge muss eine Zahl sein")); + ScaffoldMessenger.of(context).clearSnackBars(); + ScaffoldMessenger.of(context).showSnackBar(snackbar); + return; + } + + try { + kcalPerMassAsNumber = + double.parse(kcalPerMassController.text.replaceAll(",", ".")); + } catch (e) { + var snackbar = const SnackBar( + content: Text("'kcal pro 100g' muss eine Zahl sein")); + ScaffoldMessenger.of(context).clearSnackBars(); + ScaffoldMessenger.of(context).showSnackBar(snackbar); + return; + } + try { + massAsNumber = + double.parse(massController.text.replaceAll(",", ".")); + } catch (e) { + var snackbar = + const SnackBar(content: Text("Menge muss eine Zahl sein")); + ScaffoldMessenger.of(context).clearSnackBars(); + ScaffoldMessenger.of(context).showSnackBar(snackbar); + return; + } + + var entry = FoodEntry( + name: nameController.text, + mass: massAsNumber, + kcalPerMass: kcalPerMassAsNumber); + + log('Entry id: ${entry.id}'); + + context.read().add(FoodEntryEvent(entry: entry)); + }, + child: const Icon(Icons.add)); + return Column( children: [ RowWidgetWithSpacers( nameWidget, massWidget, kcalPerMassWidget, - ), - Padding( - padding: const EdgeInsets.all(8.0), - child: ElevatedButton( - child: const Text("Hinzufügen"), - onPressed: () { - double massAsNumber = 0.0; - double kcalPerMassAsNumber = 0.0; - - try { - massAsNumber = - double.parse(massController.text.replaceAll(",", ".")); - } catch (e) { - var snackbar = const SnackBar( - content: Text("Menge muss eine Zahl sein")); - ScaffoldMessenger.of(context).clearSnackBars(); - ScaffoldMessenger.of(context).showSnackBar(snackbar); - return; - } - - try { - kcalPerMassAsNumber = double.parse( - kcalPerMassController.text.replaceAll(",", ".")); - } catch (e) { - var snackbar = const SnackBar( - content: Text("'kcal pro 100g' muss eine Zahl sein")); - ScaffoldMessenger.of(context).clearSnackBars(); - ScaffoldMessenger.of(context).showSnackBar(snackbar); - return; - } - try { - massAsNumber = - double.parse(massController.text.replaceAll(",", ".")); - } catch (e) { - var snackbar = const SnackBar( - content: Text("Menge muss eine Zahl sein")); - ScaffoldMessenger.of(context).clearSnackBars(); - ScaffoldMessenger.of(context).showSnackBar(snackbar); - return; - } - - var entry = FoodEntry( - name: nameController.text, - mass: massAsNumber, - kcal: kcalPerMassAsNumber, - kcalPerMass: 200); - context.read().add(FoodEntryEvent(entry: entry)); - }), + null, + enterButton, ), ], ); } } - -class RowWidgetWithSpacers extends StatefulWidget { - final Widget widget1; - final Widget widget2; - final Widget widget3; - - const RowWidgetWithSpacers(this.widget1, this.widget2, this.widget3, - {super.key}); - - @override - State createState() => _RowWidgetWithSpacersState(); -} - -class _RowWidgetWithSpacersState extends State { - @override - Widget build(BuildContext context) { - return Row( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: [ - const Spacer(flex: 1), - Expanded(flex: 4, child: widget.widget1), - Expanded( - flex: 4, - child: widget.widget2, - ), - Expanded(flex: 4, child: widget.widget3), - const Spacer(flex: 1), - ], - ); - } -} diff --git a/lib/food_entry_bloc.dart b/lib/food_entry_bloc.dart index 5218865..51889ae 100644 --- a/lib/food_entry_bloc.dart +++ b/lib/food_entry_bloc.dart @@ -1,9 +1,10 @@ import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:quiver/core.dart'; +import 'package:uuid/uuid.dart'; -class FoodEntryBloc extends Bloc { +class FoodEntryBloc extends Bloc { FoodEntryBloc(super.initialState) { on(addFoodEntryToState); + on(deleteFood); } void addFoodEntryToState(FoodEntryEvent event, Emitter emit) { @@ -11,13 +12,25 @@ class FoodEntryBloc extends Bloc { newState.addEntry(event.entry); emit(newState); } + + void deleteFood(FoodDeletionEvent event, Emitter emit) { + state.foodEntries.removeWhere((entry) => entry.id == event.entryID); + emit(FoodEntryState.from(state)); + } } -class FoodEntryEvent { +class FoodEvent {} + +class FoodEntryEvent extends FoodEvent { final FoodEntry entry; FoodEntryEvent({required this.entry}); } +class FoodDeletionEvent extends FoodEvent { + final String entryID; + FoodDeletionEvent({required this.entryID}); +} + class FoodEntryState { final List foodEntries; @@ -35,42 +48,17 @@ class FoodEntryState { void addEntry(FoodEntry entry) { foodEntries.add(entry); } - - @override - int get hashCode { - return hashObjects(foodEntries); - } - - @override - operator ==(other) { - if (other.runtimeType != FoodEntryState) return false; - - other = other as FoodEntryState; - - if (foodEntries.length != other.foodEntries.length) return false; - - for (var i = 0; i < foodEntries.length; i += 1) { - if (foodEntries[i].name != other.foodEntries[i].name || - foodEntries[i].mass != other.foodEntries[i].mass || - foodEntries[i].kcalPerMass != other.foodEntries[i].kcalPerMass || - foodEntries[i].kcal != other.foodEntries[i].kcal) { - return false; - } - } - return true; - } } class FoodEntry { final String name; final double mass; final double kcalPerMass; - final double kcal; + final String id; FoodEntry({ required this.name, required this.mass, required this.kcalPerMass, - required this.kcal, - }); + }) : id = const Uuid().v1(); } diff --git a/lib/food_entry_widget.dart b/lib/food_entry_widget.dart index b342e8a..f01810a 100644 --- a/lib/food_entry_widget.dart +++ b/lib/food_entry_widget.dart @@ -1,23 +1,29 @@ import 'package:flutter/material.dart'; -import 'package:kalodings/enter_food_widget.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 String name; - final double mass; - final double kcalPerMass; + final FoodEntry entry; - const FoodEntryWidget( - {super.key, - required this.name, - required this.mass, - required this.kcalPerMass}); + const FoodEntryWidget({super.key, required this.entry}); @override Widget build(BuildContext context) { - return RowWidgetWithSpacers( - Text(name), - Text(mass.toString()), - Text(kcalPerMass.toString()), + return Card( + child: RowWidgetWithSpacers( + Text(entry.name), + Text(entry.mass.toString()), + Text(entry.kcalPerMass.toString()), + Text((entry.mass * entry.kcalPerMass / 100).toString()), + IconButton( + onPressed: () { + context + .read() + .add(FoodDeletionEvent(entryID: entry.id)); + }, + icon: const Icon(Icons.delete_forever_rounded)), + ), ); } } diff --git a/lib/main.dart b/lib/main.dart index 12c7920..d835ed4 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,5 +1,9 @@ +// 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'; @@ -16,13 +20,38 @@ class MainApp extends StatelessWidget { create: (BuildContext context) { return FoodEntryBloc(FoodEntryState.init()); }, - child: const MaterialApp( - home: Scaffold( - body: Center( - child: PerDay(), - ), - ), + child: MaterialApp.router( + theme: ThemeData.dark(), + routerConfig: router, ), ); } } + +final router = GoRouter(routes: [ + GoRoute( + path: '/', + name: 'perDayToday', + builder: (context, state) { + return PerDay(DateTime.now()); + }), + GoRoute( + path: '/day', + name: 'perDay', + builder: (context, state) { + DateTime day; + if (state.extra == null || state.extra is! DateTime) { + day = DateTime.now(); + } else { + day = state.extra as DateTime; + } + + return PerDay(day); + }), + GoRoute( + path: '/calendar', + name: 'calendar', + builder: (context, state) { + return CalendarWidget(); + }), +]); diff --git a/lib/perday_widget.dart b/lib/perday_widget.dart index 1e20c04..2769601 100644 --- a/lib/perday_widget.dart +++ b/lib/perday_widget.dart @@ -1,43 +1,38 @@ 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 StatefulWidget { - const PerDay({super.key}); - - @override - State createState() => _PerDayState(); -} - -class _PerDayState extends State { - String perFoodresult = "Kalorien der Mahlzeit"; - List entryList = []; +class PerDay extends StatelessWidget { + final DateTime day; + const PerDay(this.day, {super.key}); @override Widget build(BuildContext context) { - return BlocListener( - listener: (listenerContext, state) { - var newEntryWidgets = []; + 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(); + } - for (var entry in state.foodEntries) { - newEntryWidgets.add(FoodEntryWidget( - name: entry.name, - mass: entry.mass, - kcalPerMass: entry.kcalPerMass)); - } - setState(() { - entryList = newEntryWidgets; - }); - }, - child: ListView.builder( - itemCount: entryList.length + 1, - itemBuilder: (BuildContext itemBuilderContext, int index) { - if (index + 1 == entryList.length + 1) return EnterFoodWidget(); - return entryList[index]; - }, - ), - ); + return FoodEntryWidget(entry: state.foodEntries[index]); + }, + ); + }, + )); } } diff --git a/lib/row_with_spacers_widget.dart b/lib/row_with_spacers_widget.dart new file mode 100644 index 0000000..966457d --- /dev/null +++ b/lib/row_with_spacers_widget.dart @@ -0,0 +1,62 @@ +import 'package:flutter/material.dart'; + +class RowWidgetWithSpacers extends StatefulWidget { + final Widget? widget1; + final Widget? widget2; + final Widget? widget3; + final Widget? widget4; + final Widget? widget5; + + const RowWidgetWithSpacers( + this.widget1, this.widget2, this.widget3, this.widget4, this.widget5, + {super.key}); + + @override + State createState() => _RowWidgetWithSpacersState(); +} + +class _RowWidgetWithSpacersState extends State { + @override + Widget build(BuildContext context) { + return Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + Expanded( + flex: 5, + child: Padding( + padding: + const EdgeInsets.symmetric(vertical: 2.0, horizontal: 2.0), + child: widget.widget1 ?? Container(), + )), + Expanded( + flex: 5, + child: Padding( + padding: + const EdgeInsets.symmetric(vertical: 2.0, horizontal: 2.0), + child: widget.widget2 ?? Container(), + )), + Expanded( + flex: 5, + child: Padding( + padding: + const EdgeInsets.symmetric(vertical: 2.0, horizontal: 2.0), + child: widget.widget3 ?? Container(), + )), + Expanded( + flex: 5, + child: Padding( + padding: + const EdgeInsets.symmetric(vertical: 2.0, horizontal: 2.0), + child: widget.widget4 ?? Container(), + )), + Expanded( + flex: 5, + child: Padding( + padding: + const EdgeInsets.symmetric(vertical: 2.0, horizontal: 2.0), + child: widget.widget5 ?? Container(), + )), + ], + ); + } +} diff --git a/lib/sum_widget.dart b/lib/sum_widget.dart new file mode 100644 index 0000000..7b82456 --- /dev/null +++ b/lib/sum_widget.dart @@ -0,0 +1,28 @@ +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 SumWidget extends StatelessWidget { + const SumWidget({super.key}); + + @override + Widget build(BuildContext context) { + return BlocBuilder( + builder: (context, state) { + var sum = 0.0; + for (var entry in state.foodEntries) { + sum += entry.kcalPerMass / 100 * entry.mass; + } + + return RowWidgetWithSpacers( + null, + null, + const Text("kcal heute:"), + Text(sum.toString()), + null, + ); + }, + ); + } +} diff --git a/pubspec.lock b/pubspec.lock index f51ae8f..cdd190e 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -49,6 +49,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.18.0" + crypto: + dependency: transitive + description: + name: crypto + sha256: ff625774173754681d66daaf4a448684fb04b78f902da9cb3d308c19cc5e8bab + url: "https://pub.dev" + source: hosted + version: "3.0.3" fake_async: dependency: transitive description: @@ -57,6 +65,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.3.1" + fixnum: + dependency: transitive + description: + name: fixnum + sha256: "25517a4deb0c03aa0f32fd12db525856438902d9c16536311e76cdc57b31d7d1" + url: "https://pub.dev" + source: hosted + version: "1.1.0" flutter: dependency: "direct main" description: flutter @@ -83,6 +99,19 @@ packages: description: flutter source: sdk version: "0.0.0" + flutter_web_plugins: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" + go_router: + dependency: "direct main" + description: + name: go_router + sha256: abec47eb8c8c36ebf41d0a4c64dbbe7f956e39a012b3aafc530e951bdc12fe3f + url: "https://pub.dev" + source: hosted + version: "14.1.4" leak_tracker: dependency: transitive description: @@ -115,6 +144,14 @@ packages: url: "https://pub.dev" source: hosted version: "4.0.0" + logging: + dependency: transitive + description: + name: logging + sha256: "623a88c9594aa774443aa3eb2d41807a48486b5613e67599fb4c41c0ad47c340" + url: "https://pub.dev" + source: hosted + version: "1.2.0" matcher: dependency: transitive description: @@ -184,6 +221,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.10.0" + sprintf: + dependency: transitive + description: + name: sprintf + sha256: "1fc9ffe69d4df602376b52949af107d8f5703b77cda567c4d7d86a0693120f23" + url: "https://pub.dev" + source: hosted + version: "7.0.0" stack_trace: dependency: transitive description: @@ -224,6 +269,22 @@ packages: url: "https://pub.dev" source: hosted version: "0.7.1" + typed_data: + dependency: transitive + description: + name: typed_data + sha256: facc8d6582f16042dd49f2463ff1bd6e2c9ef9f3d5da3d9b087e244a7b564b3c + url: "https://pub.dev" + source: hosted + version: "1.3.2" + uuid: + dependency: "direct main" + description: + name: uuid + sha256: "814e9e88f21a176ae1359149021870e87f7cddaf633ab678a5d2b0bff7fd1ba8" + url: "https://pub.dev" + source: hosted + version: "4.4.0" vector_math: dependency: transitive description: @@ -241,5 +302,5 @@ packages: source: hosted version: "14.2.2" sdks: - dart: ">=3.5.0-191.0.dev <4.0.0" + dart: ">=3.3.0 <4.0.0" flutter: ">=3.18.0-18.0.pre.54" diff --git a/pubspec.yaml b/pubspec.yaml index f004bc5..6fe6ed9 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -4,13 +4,15 @@ publish_to: 'none' version: 0.1.0 environment: - sdk: '>=3.5.0-191.0.dev <4.0.0' + sdk: ^3.0.0 dependencies: flutter: sdk: flutter flutter_bloc: ^8.1.5 + go_router: ^14.1.4 quiver: ^3.2.1 + uuid: ^4.4.0 dev_dependencies: flutter_test: