From 1d61debf10397fb6f56d93e934a58a0c2aae72b4 Mon Sep 17 00:00:00 2001 From: Jan Marsh Date: Mon, 12 Sep 2022 12:46:12 +0200 Subject: [PATCH 01/18] Refactoring to bloc --- lib/src/app.dart | 20 +++- .../presentation/bloc/counter.bloc.dart | 26 ++++++ .../presentation/bloc/counter.event.dart | 12 +++ .../presentation/bloc/counter.state.dart | 9 ++ .../presentation/counter.controller.dart | 23 ----- .../counter/presentation/counter.page.dart | 93 ------------------- .../presentation/view/counter.page.dart | 60 ++++++++++++ .../view/widgets/counter_text.widget.dart | 17 ++++ .../custom_circular_button.widget.dart | 23 +++++ pubspec.lock | 29 ++++++ pubspec.yaml | 1 + 11 files changed, 194 insertions(+), 119 deletions(-) create mode 100644 lib/src/features/counter/presentation/bloc/counter.bloc.dart create mode 100644 lib/src/features/counter/presentation/bloc/counter.event.dart create mode 100644 lib/src/features/counter/presentation/bloc/counter.state.dart delete mode 100644 lib/src/features/counter/presentation/counter.controller.dart delete mode 100644 lib/src/features/counter/presentation/counter.page.dart create mode 100644 lib/src/features/counter/presentation/view/counter.page.dart create mode 100644 lib/src/features/counter/presentation/view/widgets/counter_text.widget.dart create mode 100644 lib/src/features/counter/presentation/view/widgets/custom_circular_button.widget.dart diff --git a/lib/src/app.dart b/lib/src/app.dart index e0116fa..86deb50 100644 --- a/lib/src/app.dart +++ b/lib/src/app.dart @@ -1,7 +1,8 @@ import 'package:counter_workshop/src/core/theme/app.theme.dart'; import 'package:counter_workshop/src/features/counter/data/repositories/counter.repository.dart'; -import 'package:counter_workshop/src/features/counter/presentation/counter.page.dart'; +import 'package:counter_workshop/src/features/counter/presentation/view/counter.page.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; class App extends StatelessWidget { const App({required this.counterRepository, super.key}); @@ -9,14 +10,27 @@ class App extends StatelessWidget { @override Widget build(BuildContext context) { - final appTheme = AppTheme(); + return RepositoryProvider.value( + value: counterRepository, + child: const AppView(), + ); + } +} +class AppView extends StatelessWidget { + const AppView({ + Key? key, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + final appTheme = AppTheme(); return MaterialApp( title: 'Counter Demo', theme: appTheme.light, darkTheme: appTheme.dark, themeMode: ThemeMode.system, - home: CounterPage(counterRepository: counterRepository), + home: const CounterPage(), ); } } diff --git a/lib/src/features/counter/presentation/bloc/counter.bloc.dart b/lib/src/features/counter/presentation/bloc/counter.bloc.dart new file mode 100644 index 0000000..280a56f --- /dev/null +++ b/lib/src/features/counter/presentation/bloc/counter.bloc.dart @@ -0,0 +1,26 @@ +import 'package:counter_workshop/src/features/counter/data/repositories/counter.repository.dart'; +import 'package:counter_workshop/src/features/counter/presentation/bloc/counter.event.dart'; +import 'package:counter_workshop/src/features/counter/presentation/bloc/counter.state.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +class CounterBloc extends Bloc { + final CounterRepository counterRepository; + + CounterBloc({required this.counterRepository}) : super(const CounterState(value: 0)) { + on(_onIncrement); + on(_onDecrement); + } + + void _onIncrement(CounterIncrementPressed event, Emitter emit) { + debugPrint('INCREMENT: ${state.value.toString()}'); + emit(CounterState(value: state.value + 1)); + } + + void _onDecrement(CounterDecrementPressed event, Emitter emit) { + debugPrint('DECREMENT: ${state.value.toString()}'); + if (state.value > 0) { + emit(CounterState(value: state.value - 1)); + } + } +} diff --git a/lib/src/features/counter/presentation/bloc/counter.event.dart b/lib/src/features/counter/presentation/bloc/counter.event.dart new file mode 100644 index 0000000..635321c --- /dev/null +++ b/lib/src/features/counter/presentation/bloc/counter.event.dart @@ -0,0 +1,12 @@ +import 'package:equatable/equatable.dart'; + +abstract class CounterEvent extends Equatable { + @override + List get props => []; +} + +/// Notifies bloc to increment state. +class CounterIncrementPressed extends CounterEvent {} + +/// Notifies bloc to decrement state. +class CounterDecrementPressed extends CounterEvent {} diff --git a/lib/src/features/counter/presentation/bloc/counter.state.dart b/lib/src/features/counter/presentation/bloc/counter.state.dart new file mode 100644 index 0000000..4567282 --- /dev/null +++ b/lib/src/features/counter/presentation/bloc/counter.state.dart @@ -0,0 +1,9 @@ +import 'package:equatable/equatable.dart'; + +class CounterState extends Equatable { + const CounterState({required this.value}); + final int value; + + @override + List get props => [value]; +} diff --git a/lib/src/features/counter/presentation/counter.controller.dart b/lib/src/features/counter/presentation/counter.controller.dart deleted file mode 100644 index 32d8cd4..0000000 --- a/lib/src/features/counter/presentation/counter.controller.dart +++ /dev/null @@ -1,23 +0,0 @@ -import 'package:counter_workshop/src/features/counter/data/repositories/counter.repository.dart'; -import 'package:counter_workshop/src/features/counter/domain/counter.model.dart'; - -class CounterController { - CounterController({required this.counterRepository}) { - counterModel = counterRepository.getCounter(); - } - - final CounterRepository counterRepository; - CounterModel counterModel = CounterModel(); - - Future increment() async { - counterModel.value += 1; - counterRepository.updateCounter(counterModel: counterModel); - } - - Future decrement() async { - if (counterModel.value > 0) { - counterModel.value -= 1; - counterRepository.updateCounter(counterModel: counterModel); - } - } -} diff --git a/lib/src/features/counter/presentation/counter.page.dart b/lib/src/features/counter/presentation/counter.page.dart deleted file mode 100644 index 105f19c..0000000 --- a/lib/src/features/counter/presentation/counter.page.dart +++ /dev/null @@ -1,93 +0,0 @@ -import 'package:counter_workshop/src/features/counter/data/repositories/counter.repository.dart'; -import 'package:counter_workshop/src/features/counter/presentation/counter.controller.dart'; -import 'package:flutter/material.dart'; - -class CounterPage extends StatefulWidget { - const CounterPage({required this.counterRepository, super.key}); - final CounterRepository counterRepository; - - @override - State createState() => _CounterPageState(); -} - -class _CounterPageState extends State { - late final CounterController counterController; - @override - void initState() { - counterController = CounterController(counterRepository: widget.counterRepository); - super.initState(); - } - - void _incrementCounter() { - setState(() { - counterController.increment(); - }); - } - - void _decrementCounter() { - setState(() { - counterController.decrement(); - }); - } - - @override - Widget build(BuildContext context) { - final theme = Theme.of(context); - return Scaffold( - extendBodyBehindAppBar: true, - appBar: AppBar( - title: const Text('Counter Page'), - ), - body: Center( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Text( - '${counterController.counterModel.value}', - style: theme.textTheme.headlineLarge, - ), - ], - ), - ), - floatingActionButtonLocation: FloatingActionButtonLocation.centerFloat, - floatingActionButton: Container( - padding: const EdgeInsets.symmetric(vertical: 5, horizontal: 40.0), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - _CustomCircularButton( - icon: Icons.remove, - onPressed: _decrementCounter, - ), - _CustomCircularButton( - icon: Icons.add, - onPressed: _incrementCounter, - ), - ], - ), - ), - ); - } -} - -class _CustomCircularButton extends StatelessWidget { - const _CustomCircularButton({required this.icon, this.onPressed}); - - final IconData icon; - final Function()? onPressed; - - @override - Widget build(BuildContext context) { - return OutlinedButton( - style: OutlinedButton.styleFrom( - shape: const CircleBorder(), - padding: const EdgeInsets.all(15), - ), - onPressed: onPressed, - child: Icon( - icon, - size: 50, - ), - ); - } -} diff --git a/lib/src/features/counter/presentation/view/counter.page.dart b/lib/src/features/counter/presentation/view/counter.page.dart new file mode 100644 index 0000000..fc9a809 --- /dev/null +++ b/lib/src/features/counter/presentation/view/counter.page.dart @@ -0,0 +1,60 @@ +import 'package:counter_workshop/src/features/counter/data/repositories/counter.repository.dart'; +import 'package:counter_workshop/src/features/counter/presentation/bloc/counter.bloc.dart'; +import 'package:counter_workshop/src/features/counter/presentation/bloc/counter.event.dart'; +import 'package:counter_workshop/src/features/counter/presentation/view/widgets/counter_text.widget.dart'; +import 'package:counter_workshop/src/features/counter/presentation/view/widgets/custom_circular_button.widget.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +/// bloc +class CounterPage extends StatelessWidget { + const CounterPage({super.key}); + + @override + Widget build(BuildContext context) { + final counterRepository = context.read(); + return BlocProvider(create: (_) => CounterBloc(counterRepository: counterRepository), child: const _CounterView()); + } +} + +/// actual counter page +class _CounterView extends StatelessWidget { + const _CounterView(); + + @override + Widget build(BuildContext context) { + final counterBloc = context.read(); + + return Scaffold( + extendBodyBehindAppBar: true, + appBar: AppBar( + title: const Text('Counter Page'), + ), + body: Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: const [ + CounterText(), + ], + ), + ), + floatingActionButtonLocation: FloatingActionButtonLocation.centerFloat, + floatingActionButton: Container( + padding: const EdgeInsets.symmetric(vertical: 5, horizontal: 40.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + CustomCircularButton( + icon: Icons.remove, + onPressed: () => counterBloc.add(CounterDecrementPressed()), + ), + CustomCircularButton( + icon: Icons.add, + onPressed: () => counterBloc.add(CounterIncrementPressed()), + ), + ], + ), + ), + ); + } +} diff --git a/lib/src/features/counter/presentation/view/widgets/counter_text.widget.dart b/lib/src/features/counter/presentation/view/widgets/counter_text.widget.dart new file mode 100644 index 0000000..aea3e1e --- /dev/null +++ b/lib/src/features/counter/presentation/view/widgets/counter_text.widget.dart @@ -0,0 +1,17 @@ +import 'package:counter_workshop/src/features/counter/presentation/bloc/counter.bloc.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +class CounterText extends StatelessWidget { + const CounterText({super.key}); + + @override + Widget build(BuildContext context) { + final theme = Theme.of(context); + final counterValue = context.select((CounterBloc bloc) => bloc.state.value); + return Text( + '$counterValue', + style: theme.textTheme.headlineLarge, + ); + } +} diff --git a/lib/src/features/counter/presentation/view/widgets/custom_circular_button.widget.dart b/lib/src/features/counter/presentation/view/widgets/custom_circular_button.widget.dart new file mode 100644 index 0000000..76c0783 --- /dev/null +++ b/lib/src/features/counter/presentation/view/widgets/custom_circular_button.widget.dart @@ -0,0 +1,23 @@ +import 'package:flutter/material.dart'; + +class CustomCircularButton extends StatelessWidget { + const CustomCircularButton({super.key, required this.icon, this.onPressed}); + + final IconData icon; + final Function()? onPressed; + + @override + Widget build(BuildContext context) { + return OutlinedButton( + style: OutlinedButton.styleFrom( + shape: const CircleBorder(), + padding: const EdgeInsets.all(15), + ), + onPressed: onPressed, + child: Icon( + icon, + size: 50, + ), + ); + } +} diff --git a/pubspec.lock b/pubspec.lock index 9f08ebd..f418633 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -29,6 +29,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "2.9.0" + bloc: + dependency: transitive + description: + name: bloc + url: "https://pub.dartlang.org" + source: hosted + version: "8.1.0" boolean_selector: dependency: transitive description: @@ -188,6 +195,13 @@ packages: description: flutter source: sdk version: "0.0.0" + flutter_bloc: + dependency: "direct main" + description: + name: flutter_bloc + url: "https://pub.dartlang.org" + source: hosted + version: "8.1.1" flutter_lints: dependency: "direct dev" description: @@ -305,6 +319,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.0.2" + nested: + dependency: transitive + description: + name: nested + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.0" package_config: dependency: transitive description: @@ -326,6 +347,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.5.1" + provider: + dependency: transitive + description: + name: provider + url: "https://pub.dartlang.org" + source: hosted + version: "6.0.3" pub_semver: dependency: transitive description: @@ -452,3 +480,4 @@ packages: version: "3.1.1" sdks: dart: ">=2.18.0 <3.0.0" + flutter: ">=1.16.0" diff --git a/pubspec.yaml b/pubspec.yaml index 4c5fb24..8f03ea3 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -15,6 +15,7 @@ dependencies: cupertino_icons: ^1.0.2 equatable: ^2.0.5 http: ^0.13.5 + flutter_bloc: ^8.1.1 dev_dependencies: flutter_test: From b7f7f32fd706cd0b1ab8320e95d61e0da30b8e90 Mon Sep 17 00:00:00 2001 From: julia Date: Mon, 12 Sep 2022 14:02:51 +0200 Subject: [PATCH 02/18] Added go router --- lib/src/app.dart | 9 +++++--- lib/src/core/routing/.gitkeep | 0 lib/src/core/routing/router.dart | 18 ++++++++++++++++ .../presentation/view/counter.page.dart | 7 +++++++ .../settings/presentation/settings.page.dart | 21 +++++++++++++++++++ pubspec.lock | 14 ++++++++++++- pubspec.yaml | 1 + 7 files changed, 66 insertions(+), 4 deletions(-) delete mode 100644 lib/src/core/routing/.gitkeep create mode 100644 lib/src/core/routing/router.dart create mode 100644 lib/src/features/settings/presentation/settings.page.dart diff --git a/lib/src/app.dart b/lib/src/app.dart index 86deb50..c2a3138 100644 --- a/lib/src/app.dart +++ b/lib/src/app.dart @@ -1,11 +1,12 @@ +import 'package:counter_workshop/src/core/routing/router.dart'; import 'package:counter_workshop/src/core/theme/app.theme.dart'; import 'package:counter_workshop/src/features/counter/data/repositories/counter.repository.dart'; -import 'package:counter_workshop/src/features/counter/presentation/view/counter.page.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; class App extends StatelessWidget { const App({required this.counterRepository, super.key}); + final CounterRepository counterRepository; @override @@ -25,12 +26,14 @@ class AppView extends StatelessWidget { @override Widget build(BuildContext context) { final appTheme = AppTheme(); - return MaterialApp( + return MaterialApp.router( title: 'Counter Demo', theme: appTheme.light, darkTheme: appTheme.dark, themeMode: ThemeMode.system, - home: const CounterPage(), + routeInformationProvider: router.routeInformationProvider, + routeInformationParser: router.routeInformationParser, + routerDelegate: router.routerDelegate, ); } } diff --git a/lib/src/core/routing/.gitkeep b/lib/src/core/routing/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/lib/src/core/routing/router.dart b/lib/src/core/routing/router.dart new file mode 100644 index 0000000..c3402e8 --- /dev/null +++ b/lib/src/core/routing/router.dart @@ -0,0 +1,18 @@ +import 'package:counter_workshop/src/features/counter/presentation/view/counter.page.dart'; +import 'package:counter_workshop/src/features/settings/presentation/settings.page.dart'; +import 'package:go_router/go_router.dart'; + +final router = GoRouter( + urlPathStrategy: UrlPathStrategy.path, + initialLocation: '/counter', + routes: [ + GoRoute( + path: '/counter', + builder: (context, state) => const CounterPage(), + ), + GoRoute( + path: '/settings', + builder: (context, state) => const SettingsPage(), + ), + ], +); diff --git a/lib/src/features/counter/presentation/view/counter.page.dart b/lib/src/features/counter/presentation/view/counter.page.dart index fc9a809..931b7a1 100644 --- a/lib/src/features/counter/presentation/view/counter.page.dart +++ b/lib/src/features/counter/presentation/view/counter.page.dart @@ -5,6 +5,7 @@ import 'package:counter_workshop/src/features/counter/presentation/view/widgets/ import 'package:counter_workshop/src/features/counter/presentation/view/widgets/custom_circular_button.widget.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:go_router/go_router.dart'; /// bloc class CounterPage extends StatelessWidget { @@ -29,6 +30,12 @@ class _CounterView extends StatelessWidget { extendBodyBehindAppBar: true, appBar: AppBar( title: const Text('Counter Page'), + actions: [ + IconButton( + onPressed: () => context.push('/settings'), + icon: const Icon(Icons.settings), + ), + ], ), body: Center( child: Column( diff --git a/lib/src/features/settings/presentation/settings.page.dart b/lib/src/features/settings/presentation/settings.page.dart new file mode 100644 index 0000000..5f81766 --- /dev/null +++ b/lib/src/features/settings/presentation/settings.page.dart @@ -0,0 +1,21 @@ +import 'package:flutter/material.dart'; + +class SettingsPage extends StatelessWidget { + const SettingsPage({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return Scaffold( + extendBodyBehindAppBar: true, + appBar: AppBar( + title: const Text('Settings Page'), + ), + body: const Center( + child: Text( + "Make settings\n...", + textAlign: TextAlign.center, + ), + ), + ); + } +} diff --git a/pubspec.lock b/pubspec.lock index f418633..a72db10 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -214,6 +214,11 @@ packages: description: flutter source: sdk version: "0.0.0" + flutter_web_plugins: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" frontend_server_client: dependency: transitive description: @@ -228,6 +233,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "2.1.0" + go_router: + dependency: "direct main" + description: + name: go_router + url: "https://pub.dartlang.org" + source: hosted + version: "4.4.1" graphs: dependency: transitive description: @@ -480,4 +492,4 @@ packages: version: "3.1.1" sdks: dart: ">=2.18.0 <3.0.0" - flutter: ">=1.16.0" + flutter: ">=3.0.0" diff --git a/pubspec.yaml b/pubspec.yaml index 8f03ea3..43377bb 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -16,6 +16,7 @@ dependencies: equatable: ^2.0.5 http: ^0.13.5 flutter_bloc: ^8.1.1 + go_router: ^4.4.1 dev_dependencies: flutter_test: From 79ca60faae3aaa0dd2507ba3fb0b923a10a5d2c7 Mon Sep 17 00:00:00 2001 From: julia Date: Mon, 12 Sep 2022 14:11:33 +0200 Subject: [PATCH 03/18] Converted to single quoted string --- lib/src/features/settings/presentation/settings.page.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/src/features/settings/presentation/settings.page.dart b/lib/src/features/settings/presentation/settings.page.dart index 5f81766..11d7efe 100644 --- a/lib/src/features/settings/presentation/settings.page.dart +++ b/lib/src/features/settings/presentation/settings.page.dart @@ -12,7 +12,7 @@ class SettingsPage extends StatelessWidget { ), body: const Center( child: Text( - "Make settings\n...", + 'Make settings\n...', textAlign: TextAlign.center, ), ), From 0aea3a31e1f3f1827f325424945fb6580a745efe Mon Sep 17 00:00:00 2001 From: Jan Marsh Date: Mon, 12 Sep 2022 14:58:47 +0200 Subject: [PATCH 04/18] Adding connection to repository with async data --- lib/main.dart | 2 +- .../datasources/local/counter.database.dart | 22 ++++++ .../data/datasources/local/counter.db.dart | 14 ---- .../data/repositories/counter.repository.dart | 14 ++-- .../presentation/bloc/counter.bloc.dart | 46 +++++++++-- .../presentation/bloc/counter.event.dart | 26 ++++++- .../presentation/bloc/counter.state.dart | 30 ++++++- .../presentation/view/counter.page.dart | 78 ++++++++++++++----- .../view/widgets/counter_text.widget.dart | 6 +- .../custom_circular_button.widget.dart | 1 + test/widget_test.dart | 6 +- 11 files changed, 180 insertions(+), 65 deletions(-) create mode 100644 lib/src/features/counter/data/datasources/local/counter.database.dart delete mode 100644 lib/src/features/counter/data/datasources/local/counter.db.dart diff --git a/lib/main.dart b/lib/main.dart index 199d822..f57dfe3 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,5 +1,5 @@ import 'package:counter_workshop/src/app.dart'; -import 'package:counter_workshop/src/features/counter/data/datasources/local/counter.db.dart'; +import 'package:counter_workshop/src/features/counter/data/datasources/local/counter.database.dart'; import 'package:counter_workshop/src/features/counter/data/datasources/remote/src/mock/counter_fake.api.dart'; import 'package:counter_workshop/src/features/counter/data/repositories/counter.repository.dart'; import 'package:flutter/material.dart'; diff --git a/lib/src/features/counter/data/datasources/local/counter.database.dart b/lib/src/features/counter/data/datasources/local/counter.database.dart new file mode 100644 index 0000000..aa8aabd --- /dev/null +++ b/lib/src/features/counter/data/datasources/local/counter.database.dart @@ -0,0 +1,22 @@ +import 'package:counter_workshop/src/features/counter/domain/counter.model.dart'; + +/// Locale app database like SqlLite that providers a [CounterModel] +class CounterDatabase { + CounterModel _counter = CounterModel(value: 0); + final int databaseDelay = 200; + + Future getCounter() { + // Pretend it's a db call + return Future.delayed(Duration(milliseconds: databaseDelay), () => _counter); + } + + Future storeCounter(CounterModel counter) { + _counter = counter; + if (_counter.value == 10) { + throw Exception('Database read lock while updating Counter to ${_counter.value}.'); + } else { + // Pretend it's a db call + return Future.delayed(Duration(milliseconds: databaseDelay)); + } + } +} diff --git a/lib/src/features/counter/data/datasources/local/counter.db.dart b/lib/src/features/counter/data/datasources/local/counter.db.dart deleted file mode 100644 index 92e8e5c..0000000 --- a/lib/src/features/counter/data/datasources/local/counter.db.dart +++ /dev/null @@ -1,14 +0,0 @@ -import 'package:counter_workshop/src/features/counter/domain/counter.model.dart'; - -/// Locale app database like SqlLite that providers a [CounterModel] -class CounterDatabase { - CounterModel _counter = CounterModel(value: 0); - - CounterModel getCounter() { - return _counter; - } - - storeCounter(CounterModel counter) { - _counter = counter; - } -} diff --git a/lib/src/features/counter/data/repositories/counter.repository.dart b/lib/src/features/counter/data/repositories/counter.repository.dart index 6c7a9a0..be67d0c 100644 --- a/lib/src/features/counter/data/repositories/counter.repository.dart +++ b/lib/src/features/counter/data/repositories/counter.repository.dart @@ -1,6 +1,6 @@ import 'dart:async'; -import 'package:counter_workshop/src/features/counter/data/datasources/local/counter.db.dart'; +import 'package:counter_workshop/src/features/counter/data/datasources/local/counter.database.dart'; import 'package:counter_workshop/src/features/counter/data/datasources/remote/counter.api.dart'; import 'package:counter_workshop/src/features/counter/data/datasources/remote/converters/counter_response.converter.dart'; import 'package:counter_workshop/src/features/counter/data/datasources/remote/dtos/counter_response.dto.dart'; @@ -25,16 +25,20 @@ class CounterRepository { CounterModel counterModel = CounterResponseConverter().toModel(counterResponseDto); // store model in database - counterDatabase.storeCounter(counterModel); + await counterDatabase.storeCounter(counterModel); } - CounterModel getCounter() { - return counterDatabase.getCounter(); + Future getCounter() async { + return await counterDatabase.getCounter(); } Future updateCounter({required CounterModel counterModel}) async { log('updating counter: ${counterModel.id} with value: $counterModel'); + + // store model in database + await counterDatabase.storeCounter(counterModel); + + // store model in api await counterApi.updateCounter(counterModel.id, counterModel.value); - return; } } diff --git a/lib/src/features/counter/presentation/bloc/counter.bloc.dart b/lib/src/features/counter/presentation/bloc/counter.bloc.dart index 280a56f..1380ed9 100644 --- a/lib/src/features/counter/presentation/bloc/counter.bloc.dart +++ b/lib/src/features/counter/presentation/bloc/counter.bloc.dart @@ -1,3 +1,5 @@ +import 'dart:async'; + import 'package:counter_workshop/src/features/counter/data/repositories/counter.repository.dart'; import 'package:counter_workshop/src/features/counter/presentation/bloc/counter.event.dart'; import 'package:counter_workshop/src/features/counter/presentation/bloc/counter.state.dart'; @@ -7,20 +9,48 @@ import 'package:flutter_bloc/flutter_bloc.dart'; class CounterBloc extends Bloc { final CounterRepository counterRepository; - CounterBloc({required this.counterRepository}) : super(const CounterState(value: 0)) { + CounterBloc({required this.counterRepository}) : super(CounterLoadingState()) { + on(_onFetchData); on(_onIncrement); on(_onDecrement); } - void _onIncrement(CounterIncrementPressed event, Emitter emit) { - debugPrint('INCREMENT: ${state.value.toString()}'); - emit(CounterState(value: state.value + 1)); + Future> _onFetchData(CounterFetchData event, Emitter emit) async { + emit(CounterLoadingState()); + try { + final counter = await counterRepository.getCounter(); + emit(CounterDataState(counter)); + } catch (e) { + emit(CounterErrorState(e.toString())); + } + } + + Future _onIncrement(CounterIncrementPressed event, Emitter emit) async { + debugPrint('INCREMENT: ${event.counterModel.toString()}'); + emit(CounterLoadingState()); + try { + event.counterModel.value += 1; + await counterRepository.updateCounter(counterModel: event.counterModel); + emit(CounterDataState(event.counterModel)); + } catch (e) { + emit(CounterErrorState(e.toString())); + } } - void _onDecrement(CounterDecrementPressed event, Emitter emit) { - debugPrint('DECREMENT: ${state.value.toString()}'); - if (state.value > 0) { - emit(CounterState(value: state.value - 1)); + Future _onDecrement(CounterDecrementPressed event, Emitter emit) async { + debugPrint('DECREMENT: ${event.counterModel.toString()}'); + + if (event.counterModel.value == 0) { + return; + } + + emit(CounterLoadingState()); + try { + event.counterModel.value -= 1; + await counterRepository.updateCounter(counterModel: event.counterModel); + emit(CounterDataState(event.counterModel)); + } catch (e) { + emit(CounterErrorState(e.toString())); } } } diff --git a/lib/src/features/counter/presentation/bloc/counter.event.dart b/lib/src/features/counter/presentation/bloc/counter.event.dart index 635321c..484e786 100644 --- a/lib/src/features/counter/presentation/bloc/counter.event.dart +++ b/lib/src/features/counter/presentation/bloc/counter.event.dart @@ -1,12 +1,30 @@ +import 'package:counter_workshop/src/features/counter/domain/counter.model.dart'; import 'package:equatable/equatable.dart'; abstract class CounterEvent extends Equatable { + const CounterEvent(); + @override List get props => []; } -/// Notifies bloc to increment state. -class CounterIncrementPressed extends CounterEvent {} +/// Load data from repository +class CounterFetchData extends CounterEvent {} + +/// Notifies bloc to increment state +class CounterIncrementPressed extends CounterEvent { + const CounterIncrementPressed(this.counterModel); + final CounterModel counterModel; + + @override + List get props => [counterModel]; +} + +/// Notifies bloc to decrement state +class CounterDecrementPressed extends CounterEvent { + const CounterDecrementPressed(this.counterModel); + final CounterModel counterModel; -/// Notifies bloc to decrement state. -class CounterDecrementPressed extends CounterEvent {} + @override + List get props => [counterModel]; +} diff --git a/lib/src/features/counter/presentation/bloc/counter.state.dart b/lib/src/features/counter/presentation/bloc/counter.state.dart index 4567282..09ac43b 100644 --- a/lib/src/features/counter/presentation/bloc/counter.state.dart +++ b/lib/src/features/counter/presentation/bloc/counter.state.dart @@ -1,9 +1,31 @@ +import 'package:counter_workshop/src/features/counter/domain/counter.model.dart'; import 'package:equatable/equatable.dart'; +import 'package:flutter/material.dart'; -class CounterState extends Equatable { - const CounterState({required this.value}); - final int value; +@immutable +abstract class CounterState extends Equatable { + @override + List get props => []; +} + +/// Loading counter State +class CounterLoadingState extends CounterState {} + +/// Data counter State +class CounterDataState extends CounterState { + final CounterModel counterModel; + CounterDataState(this.counterModel); + + @override + List get props => [counterModel]; +} + +/// Error counter State +class CounterErrorState extends CounterState { + final String error; + + CounterErrorState(this.error); @override - List get props => [value]; + List get props => [error]; } diff --git a/lib/src/features/counter/presentation/view/counter.page.dart b/lib/src/features/counter/presentation/view/counter.page.dart index fc9a809..5c10af0 100644 --- a/lib/src/features/counter/presentation/view/counter.page.dart +++ b/lib/src/features/counter/presentation/view/counter.page.dart @@ -1,6 +1,7 @@ import 'package:counter_workshop/src/features/counter/data/repositories/counter.repository.dart'; import 'package:counter_workshop/src/features/counter/presentation/bloc/counter.bloc.dart'; import 'package:counter_workshop/src/features/counter/presentation/bloc/counter.event.dart'; +import 'package:counter_workshop/src/features/counter/presentation/bloc/counter.state.dart'; import 'package:counter_workshop/src/features/counter/presentation/view/widgets/counter_text.widget.dart'; import 'package:counter_workshop/src/features/counter/presentation/view/widgets/custom_circular_button.widget.dart'; import 'package:flutter/material.dart'; @@ -13,7 +14,10 @@ class CounterPage extends StatelessWidget { @override Widget build(BuildContext context) { final counterRepository = context.read(); - return BlocProvider(create: (_) => CounterBloc(counterRepository: counterRepository), child: const _CounterView()); + return BlocProvider( + create: (_) => CounterBloc(counterRepository: counterRepository)..add(CounterFetchData()), + child: const _CounterView(), + ); } } @@ -24,35 +28,69 @@ class _CounterView extends StatelessWidget { @override Widget build(BuildContext context) { final counterBloc = context.read(); + // final showButton = false; return Scaffold( extendBodyBehindAppBar: true, appBar: AppBar( title: const Text('Counter Page'), ), - body: Center( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: const [ - CounterText(), - ], - ), + body: BlocBuilder( + builder: (context, state) { + if (state is CounterLoadingState) { + // loading + return const Center( + child: CircularProgressIndicator(strokeWidth: 3), + ); + } else if (state is CounterDataState) { + // data + return Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + CounterText(counterValue: state.counterModel.value), + ], + ), + ); + } else if (state is CounterErrorState) { + // error + return Center( + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Text( + 'Es ist ein Fehler aufgetreten: ${state.error}', + style: const TextStyle(color: Colors.red), + ), + ), + ); + } + // state unknown, fallback to empty or return a common error + return const SizedBox(); + }, ), floatingActionButtonLocation: FloatingActionButtonLocation.centerFloat, floatingActionButton: Container( padding: const EdgeInsets.symmetric(vertical: 5, horizontal: 40.0), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - CustomCircularButton( - icon: Icons.remove, - onPressed: () => counterBloc.add(CounterDecrementPressed()), - ), - CustomCircularButton( - icon: Icons.add, - onPressed: () => counterBloc.add(CounterIncrementPressed()), - ), - ], + child: BlocBuilder( + builder: (context, state) { + return Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + CustomCircularButton( + icon: Icons.remove, + onPressed: state is CounterDataState + ? () => counterBloc.add(CounterDecrementPressed(state.counterModel)) + : null, + ), + CustomCircularButton( + icon: Icons.add, + onPressed: state is CounterDataState + ? () => counterBloc.add(CounterIncrementPressed(state.counterModel)) + : null, + ), + ], + ); + }, ), ), ); diff --git a/lib/src/features/counter/presentation/view/widgets/counter_text.widget.dart b/lib/src/features/counter/presentation/view/widgets/counter_text.widget.dart index aea3e1e..fba6ef6 100644 --- a/lib/src/features/counter/presentation/view/widgets/counter_text.widget.dart +++ b/lib/src/features/counter/presentation/view/widgets/counter_text.widget.dart @@ -1,14 +1,12 @@ -import 'package:counter_workshop/src/features/counter/presentation/bloc/counter.bloc.dart'; import 'package:flutter/material.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; class CounterText extends StatelessWidget { - const CounterText({super.key}); + const CounterText({super.key, required this.counterValue}); + final int counterValue; @override Widget build(BuildContext context) { final theme = Theme.of(context); - final counterValue = context.select((CounterBloc bloc) => bloc.state.value); return Text( '$counterValue', style: theme.textTheme.headlineLarge, diff --git a/lib/src/features/counter/presentation/view/widgets/custom_circular_button.widget.dart b/lib/src/features/counter/presentation/view/widgets/custom_circular_button.widget.dart index 76c0783..f0dfa62 100644 --- a/lib/src/features/counter/presentation/view/widgets/custom_circular_button.widget.dart +++ b/lib/src/features/counter/presentation/view/widgets/custom_circular_button.widget.dart @@ -12,6 +12,7 @@ class CustomCircularButton extends StatelessWidget { style: OutlinedButton.styleFrom( shape: const CircleBorder(), padding: const EdgeInsets.all(15), + disabledForegroundColor: Colors.black12, ), onPressed: onPressed, child: Icon( diff --git a/test/widget_test.dart b/test/widget_test.dart index 977ae37..7f83444 100644 --- a/test/widget_test.dart +++ b/test/widget_test.dart @@ -6,7 +6,7 @@ // tree, read text, and verify that the values of widget properties are correct. import 'package:counter_workshop/src/app.dart'; -import 'package:counter_workshop/src/features/counter/data/datasources/local/counter.db.dart'; +import 'package:counter_workshop/src/features/counter/data/datasources/local/counter.database.dart'; import 'package:counter_workshop/src/features/counter/data/datasources/remote/src/mock/counter_fake.api.dart'; import 'package:counter_workshop/src/features/counter/data/repositories/counter.repository.dart'; import 'package:flutter/material.dart'; @@ -20,10 +20,6 @@ void main() { const Duration(milliseconds: 300), // Because of FakeApi delay ); - // Verify that our counter starts at 0. - expect(find.text('0'), findsOneWidget); - expect(find.text('1'), findsNothing); - // Tap the '-' icon and trigger a frame. await tester.tap(find.byIcon(Icons.remove)); await tester.pumpAndSettle(); From dc07c52cb7356e4f820cee1c39658cf578e890bb Mon Sep 17 00:00:00 2001 From: Jan Marsh Date: Mon, 12 Sep 2022 16:14:08 +0200 Subject: [PATCH 05/18] Fix format --- lib/main.dart | 4 +- .../datasources/local/counter.database.dart | 4 +- .../counter_response.converter.dart | 2 +- .../data/datasources/remote/counter.api.dart | 18 ++++- .../remote/dtos/counter_request.dto.dart | 23 ++++++ .../remote/dtos/counter_response.dto.dart | 10 +++ .../remote/src/mock/counter_fake.api.dart | 80 ++++++++++++++----- .../remote/src/rest/counter_rest.api.dart | 25 +++++- .../data/repositories/counter.repository.dart | 48 ++++++----- .../counter/domain/counter.model.dart | 17 ---- .../counter/domain/model/counter.model.dart | 26 ++++++ .../counter.repository_interface.dart | 13 +++ .../presentation/bloc/counter.event.dart | 2 +- .../presentation/bloc/counter.state.dart | 2 +- test/widget_test.dart | 3 +- 15 files changed, 202 insertions(+), 75 deletions(-) create mode 100644 lib/src/features/counter/data/datasources/remote/dtos/counter_request.dto.dart delete mode 100644 lib/src/features/counter/domain/counter.model.dart create mode 100644 lib/src/features/counter/domain/model/counter.model.dart create mode 100644 lib/src/features/counter/domain/repository/counter.repository_interface.dart diff --git a/lib/main.dart b/lib/main.dart index f57dfe3..847b865 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,12 +1,10 @@ import 'package:counter_workshop/src/app.dart'; -import 'package:counter_workshop/src/features/counter/data/datasources/local/counter.database.dart'; import 'package:counter_workshop/src/features/counter/data/datasources/remote/src/mock/counter_fake.api.dart'; import 'package:counter_workshop/src/features/counter/data/repositories/counter.repository.dart'; import 'package:flutter/material.dart'; void main() { - final CounterRepository counterRepository = - CounterRepository(counterApi: CounterFakeApi(), counterDatabase: CounterDatabase()); + final CounterRepository counterRepository = CounterRepository(counterApi: CounterFakeApi()); runApp( App( counterRepository: counterRepository, diff --git a/lib/src/features/counter/data/datasources/local/counter.database.dart b/lib/src/features/counter/data/datasources/local/counter.database.dart index aa8aabd..39c012d 100644 --- a/lib/src/features/counter/data/datasources/local/counter.database.dart +++ b/lib/src/features/counter/data/datasources/local/counter.database.dart @@ -1,8 +1,8 @@ -import 'package:counter_workshop/src/features/counter/domain/counter.model.dart'; +import 'package:counter_workshop/src/features/counter/domain/model/counter.model.dart'; /// Locale app database like SqlLite that providers a [CounterModel] class CounterDatabase { - CounterModel _counter = CounterModel(value: 0); + CounterModel _counter = const CounterModel(id: '1', value: 0, name: 'A'); final int databaseDelay = 200; Future getCounter() { diff --git a/lib/src/features/counter/data/datasources/remote/converters/counter_response.converter.dart b/lib/src/features/counter/data/datasources/remote/converters/counter_response.converter.dart index cedca38..ffb94de 100644 --- a/lib/src/features/counter/data/datasources/remote/converters/counter_response.converter.dart +++ b/lib/src/features/counter/data/datasources/remote/converters/counter_response.converter.dart @@ -1,5 +1,5 @@ import 'package:counter_workshop/src/features/counter/data/datasources/remote/dtos/counter_response.dto.dart'; -import 'package:counter_workshop/src/features/counter/domain/counter.model.dart'; +import 'package:counter_workshop/src/features/counter/domain/model/counter.model.dart'; class CounterResponseConverter { CounterModel toModel(CounterResponseDto counterResponseDto) { diff --git a/lib/src/features/counter/data/datasources/remote/counter.api.dart b/lib/src/features/counter/data/datasources/remote/counter.api.dart index c78d714..38904e1 100644 --- a/lib/src/features/counter/data/datasources/remote/counter.api.dart +++ b/lib/src/features/counter/data/datasources/remote/counter.api.dart @@ -1,17 +1,29 @@ +import 'package:counter_workshop/src/features/counter/data/datasources/remote/dtos/counter_request.dto.dart'; import 'package:counter_workshop/src/features/counter/data/datasources/remote/dtos/counter_response.dto.dart'; -import 'package:counter_workshop/src/features/counter/domain/counter.model.dart'; +import 'package:counter_workshop/src/features/counter/domain/model/counter.model.dart'; /// The interface for a DataSource that provides access to a single [CounterModel] abstract class CounterApi { + /// Fetches all counters + Future> fetchAll(); + /// Fetches a counter with the give [id] /// /// If no counter with the given id exits, a [CounterNotFoundException] error is thrown. Future fetchCounter(String id); - /// Update the value [value] of a given counter [id] + /// Update the counter [CounterResponseDto] of a given counter [id] + /// + /// If no counter with the given id exits, a [CounterNotFoundException] error is thrown. + Future updateCounter(String id, CounterResponseDto counterResponseDto); + + /// Create a new counter + Future createCounter(CounterRequestDto counterRequestDto); + + /// Deletes a counter by a given counter [id] /// /// If no counter with the given id exits, a [CounterNotFoundException] error is thrown. - Future updateCounter(String id, int value); + Future deleteCounter(String id); } /// Error thrown when a [CounterModel] is not found. diff --git a/lib/src/features/counter/data/datasources/remote/dtos/counter_request.dto.dart b/lib/src/features/counter/data/datasources/remote/dtos/counter_request.dto.dart new file mode 100644 index 0000000..f14ce9d --- /dev/null +++ b/lib/src/features/counter/data/datasources/remote/dtos/counter_request.dto.dart @@ -0,0 +1,23 @@ +class CounterRequestDto { + CounterRequestDto({ + this.sysId, + required this.name, + required this.counterValue, + this.stepSize = 1, + this.startValue = 0, + this.color = '#ff3300', + this.goalValue, + this.createdAt, + this.updatedAt, + }); + + final String? sysId; + final String name; + final int counterValue; + final int startValue; + final int stepSize; + final String color; + final int? goalValue; + final DateTime? createdAt; + final DateTime? updatedAt; +} diff --git a/lib/src/features/counter/data/datasources/remote/dtos/counter_response.dto.dart b/lib/src/features/counter/data/datasources/remote/dtos/counter_response.dto.dart index dd580ee..16ec619 100644 --- a/lib/src/features/counter/data/datasources/remote/dtos/counter_response.dto.dart +++ b/lib/src/features/counter/data/datasources/remote/dtos/counter_response.dto.dart @@ -1,13 +1,23 @@ class CounterResponseDto { CounterResponseDto({ required this.sysId, + required this.name, required this.counterValue, + this.stepSize = 1, + this.startValue = 0, + this.color = '#ff3300', + this.goalValue, this.createdAt, this.updatedAt, }); final String sysId; + final String name; final int counterValue; + final int startValue; + final int stepSize; + final String color; + final int? goalValue; final DateTime? createdAt; final DateTime? updatedAt; } diff --git a/lib/src/features/counter/data/datasources/remote/src/mock/counter_fake.api.dart b/lib/src/features/counter/data/datasources/remote/src/mock/counter_fake.api.dart index bd3572e..9d2c572 100644 --- a/lib/src/features/counter/data/datasources/remote/src/mock/counter_fake.api.dart +++ b/lib/src/features/counter/data/datasources/remote/src/mock/counter_fake.api.dart @@ -1,36 +1,72 @@ import 'package:counter_workshop/src/features/counter/data/datasources/remote/counter.api.dart'; +import 'package:counter_workshop/src/features/counter/data/datasources/remote/dtos/counter_request.dto.dart'; import 'package:counter_workshop/src/features/counter/data/datasources/remote/dtos/counter_response.dto.dart'; -import 'package:counter_workshop/src/features/counter/domain/counter.model.dart'; +import 'package:counter_workshop/src/features/counter/domain/model/counter.model.dart'; /// FakeApi that simulates a remote restful API which providers a [CounterModel] class CounterFakeApi implements CounterApi { + final int fakeApiDelay = 300; + final List _counterList = [ + CounterResponseDto( + counterValue: 1, + sysId: '1', + name: 'Kaffee', + createdAt: DateTime.now(), + ), + CounterResponseDto( + counterValue: 4, + sysId: '2', + name: 'Wassergläser', + createdAt: DateTime.now(), + ), + CounterResponseDto( + counterValue: 21, + sysId: '3', + name: 'Überstunden', + createdAt: DateTime.now(), + ), + CounterResponseDto( + counterValue: 2, + sysId: '4', + name: 'Kekse', + createdAt: DateTime.now(), + ), + ]; + + @override + Future> fetchAll() { + return Future.delayed(Duration(milliseconds: fakeApiDelay), () => _counterList); + } + @override Future fetchCounter(String id) { // simulate a network delay - return Future.delayed(const Duration(milliseconds: 300), () { - if (id == '1') { - // return a dummy counter - return CounterResponseDto( - counterValue: 0, - sysId: '1', - createdAt: DateTime.now(), - ); - } else { - // return a exception - throw CounterNotFoundException(); - } - }); + return Future.delayed( + Duration(milliseconds: fakeApiDelay), + () => _counterList.firstWhere((c) => c.sysId == id, orElse: () => throw CounterNotFoundException()), + ); } @override - Future updateCounter(String id, int value) { - return Future.delayed(const Duration(milliseconds: 300), () { - if (id == '1') { - return; - } else { - // return a exception - throw CounterNotFoundException(); - } + Future updateCounter(String id, CounterResponseDto counterResponseDto) { + final dbIndex = _counterList.indexWhere((c) => c.sysId == id); + if (dbIndex != -1) { + return Future.delayed(Duration(milliseconds: fakeApiDelay), () { + _counterList[dbIndex] = counterResponseDto; + }); + } + return Future.value(); + } + + @override + Future createCounter(CounterRequestDto counterRequestDto) { + return Future.delayed(Duration(milliseconds: fakeApiDelay), () { + _counterList.add(counterRequestDto as CounterResponseDto); }); } + + @override + Future deleteCounter(String id) async { + _counterList.removeWhere((c) => c.sysId == id); + } } diff --git a/lib/src/features/counter/data/datasources/remote/src/rest/counter_rest.api.dart b/lib/src/features/counter/data/datasources/remote/src/rest/counter_rest.api.dart index f712c0c..6dca328 100644 --- a/lib/src/features/counter/data/datasources/remote/src/rest/counter_rest.api.dart +++ b/lib/src/features/counter/data/datasources/remote/src/rest/counter_rest.api.dart @@ -1,6 +1,7 @@ import 'package:counter_workshop/src/features/counter/data/datasources/remote/counter.api.dart'; +import 'package:counter_workshop/src/features/counter/data/datasources/remote/dtos/counter_request.dto.dart'; import 'package:counter_workshop/src/features/counter/data/datasources/remote/dtos/counter_response.dto.dart'; -import 'package:counter_workshop/src/features/counter/domain/counter.model.dart'; +import 'package:counter_workshop/src/features/counter/domain/model/counter.model.dart'; import 'package:http/http.dart' as http; /// Remote restful API that providers a [CounterModel] @@ -8,6 +9,24 @@ class CounterRestApi implements CounterApi { CounterRestApi({required this.client}); final http.Client client; + @override + Future createCounter(CounterRequestDto counterRequestDto) { + // TODO: implement createCounter + throw UnimplementedError(); + } + + @override + Future deleteCounter(String id) { + // TODO: implement deleteCounter + throw UnimplementedError(); + } + + @override + Future> fetchAll() { + // TODO: implement fetchAll + throw UnimplementedError(); + } + @override Future fetchCounter(String id) { // TODO: implement fetchCounter @@ -15,8 +34,8 @@ class CounterRestApi implements CounterApi { } @override - Future updateCounter(String id, int value) { - // TODO: implement incrementCounter + Future updateCounter(String id, CounterResponseDto counterResponseDto) { + // TODO: implement updateCounter throw UnimplementedError(); } } diff --git a/lib/src/features/counter/data/repositories/counter.repository.dart b/lib/src/features/counter/data/repositories/counter.repository.dart index be67d0c..77e7d16 100644 --- a/lib/src/features/counter/data/repositories/counter.repository.dart +++ b/lib/src/features/counter/data/repositories/counter.repository.dart @@ -1,44 +1,52 @@ import 'dart:async'; -import 'package:counter_workshop/src/features/counter/data/datasources/local/counter.database.dart'; import 'package:counter_workshop/src/features/counter/data/datasources/remote/counter.api.dart'; import 'package:counter_workshop/src/features/counter/data/datasources/remote/converters/counter_response.converter.dart'; import 'package:counter_workshop/src/features/counter/data/datasources/remote/dtos/counter_response.dto.dart'; -import 'package:counter_workshop/src/features/counter/domain/counter.model.dart'; +import 'package:counter_workshop/src/features/counter/domain/model/counter.model.dart'; import 'dart:developer'; -class CounterRepository { - CounterRepository({required this.counterApi, required this.counterDatabase}) { +import 'package:counter_workshop/src/features/counter/domain/repository/counter.repository_interface.dart'; + +class CounterRepository implements CounterRepositoryInterface { + CounterRepository({required this.counterApi}) { // prefill repository Counter from API - _fetchCounterData(); + getCounterList(); } final CounterApi counterApi; - final CounterDatabase counterDatabase; - final String defaultCounterId = '1'; // TODO: allow multiple counters - - Future _fetchCounterData() async { - log('retriving default counter'); - CounterResponseDto counterResponseDto = await counterApi.fetchCounter(defaultCounterId); - // map result to Model - CounterModel counterModel = CounterResponseConverter().toModel(counterResponseDto); + @override + Future> getCounterList() async { + log('retriving counter list'); + final List response = await counterApi.fetchAll(); - // store model in database - await counterDatabase.storeCounter(counterModel); + // map result to model + return response.map((c) => CounterResponseConverter().toModel(c)).toList(); } - Future getCounter() async { - return await counterDatabase.getCounter(); + @override + Future getCounter({required String id}) async { + final CounterResponseDto response = await counterApi.fetchCounter(id); + + // map result to model + return CounterResponseConverter().toModel(response); } + @override Future updateCounter({required CounterModel counterModel}) async { log('updating counter: ${counterModel.id} with value: $counterModel'); + // map model to dto + final dto = CounterResponseConverter().toDto(counterModel); + // store model in database - await counterDatabase.storeCounter(counterModel); + await counterApi.updateCounter(counterModel.id, dto); + } - // store model in api - await counterApi.updateCounter(counterModel.id, counterModel.value); + @override + Future deleteCounter({required String id}) async { + log('deleting counter: $id'); + await counterApi.deleteCounter(id); } } diff --git a/lib/src/features/counter/domain/counter.model.dart b/lib/src/features/counter/domain/counter.model.dart deleted file mode 100644 index 880955d..0000000 --- a/lib/src/features/counter/domain/counter.model.dart +++ /dev/null @@ -1,17 +0,0 @@ -import 'package:equatable/equatable.dart'; - -// ignore: must_be_immutable -class CounterModel extends Equatable { - CounterModel({ - this.value = 0, - this.id = '1', - }); - - /// technical counter id - final String id; - - int value; - - @override - List get props => [id, value]; -} diff --git a/lib/src/features/counter/domain/model/counter.model.dart b/lib/src/features/counter/domain/model/counter.model.dart new file mode 100644 index 0000000..358328c --- /dev/null +++ b/lib/src/features/counter/domain/model/counter.model.dart @@ -0,0 +1,26 @@ +import 'package:equatable/equatable.dart'; +import 'package:flutter/material.dart'; + +class CounterModel extends Equatable { + const CounterModel({ + required this.id, + required this.name, + this.value = 0, + this.stepSize = 1, + this.startValue = 0, + this.color = Colors.pink, + this.goalValue, + }); + + /// technical counter id + final String id; + final String name; + final int value; + final int startValue; + final int stepSize; + final Color color; + final int? goalValue; + + @override + List get props => [id, name, value]; +} diff --git a/lib/src/features/counter/domain/repository/counter.repository_interface.dart b/lib/src/features/counter/domain/repository/counter.repository_interface.dart new file mode 100644 index 0000000..294d491 --- /dev/null +++ b/lib/src/features/counter/domain/repository/counter.repository_interface.dart @@ -0,0 +1,13 @@ +import 'dart:async'; + +import 'package:counter_workshop/src/features/counter/domain/model/counter.model.dart'; + +abstract class CounterRepositoryInterface { + Future> getCounterList(); + + Future getCounter({required String id}); + + Future updateCounter({required CounterModel counterModel}); + + Future deleteCounter({required String id}); +} diff --git a/lib/src/features/counter/presentation/bloc/counter.event.dart b/lib/src/features/counter/presentation/bloc/counter.event.dart index 484e786..42c28c2 100644 --- a/lib/src/features/counter/presentation/bloc/counter.event.dart +++ b/lib/src/features/counter/presentation/bloc/counter.event.dart @@ -1,4 +1,4 @@ -import 'package:counter_workshop/src/features/counter/domain/counter.model.dart'; +import 'package:counter_workshop/src/features/counter/domain/model/counter.model.dart'; import 'package:equatable/equatable.dart'; abstract class CounterEvent extends Equatable { diff --git a/lib/src/features/counter/presentation/bloc/counter.state.dart b/lib/src/features/counter/presentation/bloc/counter.state.dart index 09ac43b..49a2bd0 100644 --- a/lib/src/features/counter/presentation/bloc/counter.state.dart +++ b/lib/src/features/counter/presentation/bloc/counter.state.dart @@ -1,4 +1,4 @@ -import 'package:counter_workshop/src/features/counter/domain/counter.model.dart'; +import 'package:counter_workshop/src/features/counter/domain/model/counter.model.dart'; import 'package:equatable/equatable.dart'; import 'package:flutter/material.dart'; diff --git a/test/widget_test.dart b/test/widget_test.dart index 7f83444..f1151e4 100644 --- a/test/widget_test.dart +++ b/test/widget_test.dart @@ -6,7 +6,6 @@ // tree, read text, and verify that the values of widget properties are correct. import 'package:counter_workshop/src/app.dart'; -import 'package:counter_workshop/src/features/counter/data/datasources/local/counter.database.dart'; import 'package:counter_workshop/src/features/counter/data/datasources/remote/src/mock/counter_fake.api.dart'; import 'package:counter_workshop/src/features/counter/data/repositories/counter.repository.dart'; import 'package:flutter/material.dart'; @@ -16,7 +15,7 @@ void main() { testWidgets('Counter Smoke Test', (WidgetTester tester) async { // Build our app and trigger a frame. await tester.pumpWidget( - App(counterRepository: CounterRepository(counterApi: CounterFakeApi(), counterDatabase: CounterDatabase())), + App(counterRepository: CounterRepository(counterApi: CounterFakeApi())), const Duration(milliseconds: 300), // Because of FakeApi delay ); From 9dfe598da862ff6f3954b2676bc4c741b6a82ee4 Mon Sep 17 00:00:00 2001 From: julia Date: Mon, 12 Sep 2022 16:59:34 +0200 Subject: [PATCH 06/18] Added converter data --- lib/src/core/extensions/.gitkeep | 0 lib/src/core/extensions/color.extension.dart | 11 +++++ .../counter_response.converter.dart | 15 ++++++- test/counter_response.converter_test.dart | 40 +++++++++++++++++++ 4 files changed, 64 insertions(+), 2 deletions(-) delete mode 100644 lib/src/core/extensions/.gitkeep create mode 100644 lib/src/core/extensions/color.extension.dart create mode 100644 test/counter_response.converter_test.dart diff --git a/lib/src/core/extensions/.gitkeep b/lib/src/core/extensions/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/lib/src/core/extensions/color.extension.dart b/lib/src/core/extensions/color.extension.dart new file mode 100644 index 0000000..f72f8b0 --- /dev/null +++ b/lib/src/core/extensions/color.extension.dart @@ -0,0 +1,11 @@ +import 'package:flutter/material.dart'; + +extension ColorExtension on String { + toColor() { + var hexString = this; + final buffer = StringBuffer(); + if (hexString.length == 6 || hexString.length == 7) buffer.write('ff'); + buffer.write(hexString.replaceFirst('#', '')); + return Color(int.parse(buffer.toString(), radix: 16)); + } +} diff --git a/lib/src/features/counter/data/datasources/remote/converters/counter_response.converter.dart b/lib/src/features/counter/data/datasources/remote/converters/counter_response.converter.dart index ffb94de..fa41971 100644 --- a/lib/src/features/counter/data/datasources/remote/converters/counter_response.converter.dart +++ b/lib/src/features/counter/data/datasources/remote/converters/counter_response.converter.dart @@ -1,18 +1,29 @@ import 'package:counter_workshop/src/features/counter/data/datasources/remote/dtos/counter_response.dto.dart'; import 'package:counter_workshop/src/features/counter/domain/model/counter.model.dart'; +import 'package:counter_workshop/src/core/extensions/color.extension.dart'; class CounterResponseConverter { CounterModel toModel(CounterResponseDto counterResponseDto) { return CounterModel( - value: counterResponseDto.counterValue, id: counterResponseDto.sysId, + name: counterResponseDto.name, + value: counterResponseDto.counterValue, + stepSize: counterResponseDto.stepSize, + startValue: counterResponseDto.startValue, + color: counterResponseDto.color.toColor(), + goalValue: counterResponseDto.goalValue, ); } CounterResponseDto toDto(CounterModel counter) { return CounterResponseDto( - counterValue: counter.value, sysId: counter.id, + name: counter.name, + counterValue: counter.value, + stepSize: counter.stepSize, + startValue: counter.value, + color: '#${(counter.color.value & 0xFFFFFF).toRadixString(16).padLeft(6, '0')}', + goalValue: counter.goalValue, ); } } diff --git a/test/counter_response.converter_test.dart b/test/counter_response.converter_test.dart new file mode 100644 index 0000000..e7af8ff --- /dev/null +++ b/test/counter_response.converter_test.dart @@ -0,0 +1,40 @@ +import 'package:counter_workshop/src/features/counter/data/datasources/remote/converters/counter_response.converter.dart'; +import 'package:counter_workshop/src/features/counter/data/datasources/remote/dtos/counter_response.dto.dart'; +import 'package:counter_workshop/src/features/counter/domain/model/counter.model.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + const model = CounterModel( + value: 1, + id: '1', + name: 'Kaffee', + color: Color(0xffff3300), + ); + final dto = CounterResponseDto( + counterValue: 1, + sysId: '1', + name: 'Kaffee', + ); + group('Counter Response Converter', () { + test('should be convert to model', () { + expect(CounterResponseConverter().toModel(dto), model); + }); + + test('should be convert to dto', () { + expect(CounterResponseConverter().toDto(model), dto); + }); + }); + + group('Counter Response Converter', () { + test('convert hex to color', () { + final model = CounterResponseConverter().toModel(dto); + expect(model.color, const Color(0xffff3300)); + }); + + test('convert color to hex', () { + final dto = CounterResponseConverter().toDto(model); + expect(dto.color, '#ff3300'); + }); + }); +} From 089e48e41c0cbfcc73507443d29379ed1d843efd Mon Sep 17 00:00:00 2001 From: Jan Marsh Date: Mon, 12 Sep 2022 18:46:19 +0200 Subject: [PATCH 07/18] making counter a CRUD application --- lib/src/app.dart | 4 +- .../counter_response.converter.dart | 2 +- .../remote/dtos/counter_response.dto.dart | 10 ++- .../presentation/bloc/counter.bloc.dart | 56 ----------------- .../dashboard/bloc/dashboard.bloc.dart | 24 +++++++ .../dashboard/bloc/dashboard.event.dart | 11 ++++ .../dashboard/bloc/dashboard.state.dart | 31 ++++++++++ .../dashboard/view/dashboard.page.dart | 62 +++++++++++++++++++ .../dashboard/view/widgets/counter_grid.dart | 42 +++++++++++++ .../detail/bloc/counter.bloc.dart | 44 +++++++++++++ .../{ => detail}/bloc/counter.event.dart | 2 +- .../{ => detail}/bloc/counter.state.dart | 6 +- .../{ => detail}/view/counter.page.dart | 18 +++--- .../view/widgets/counter_text.widget.dart | 0 .../custom_circular_button.widget.dart | 0 test/counter_response.converter_test.dart | 6 +- test/widget_test.dart | 54 +++++++--------- 17 files changed, 265 insertions(+), 107 deletions(-) delete mode 100644 lib/src/features/counter/presentation/bloc/counter.bloc.dart create mode 100644 lib/src/features/counter/presentation/dashboard/bloc/dashboard.bloc.dart create mode 100644 lib/src/features/counter/presentation/dashboard/bloc/dashboard.event.dart create mode 100644 lib/src/features/counter/presentation/dashboard/bloc/dashboard.state.dart create mode 100644 lib/src/features/counter/presentation/dashboard/view/dashboard.page.dart create mode 100644 lib/src/features/counter/presentation/dashboard/view/widgets/counter_grid.dart create mode 100644 lib/src/features/counter/presentation/detail/bloc/counter.bloc.dart rename lib/src/features/counter/presentation/{ => detail}/bloc/counter.event.dart (94%) rename lib/src/features/counter/presentation/{ => detail}/bloc/counter.state.dart (83%) rename lib/src/features/counter/presentation/{ => detail}/view/counter.page.dart (90%) rename lib/src/features/counter/presentation/{ => detail}/view/widgets/counter_text.widget.dart (100%) rename lib/src/features/counter/presentation/{ => detail}/view/widgets/custom_circular_button.widget.dart (100%) diff --git a/lib/src/app.dart b/lib/src/app.dart index 86deb50..66c99ec 100644 --- a/lib/src/app.dart +++ b/lib/src/app.dart @@ -1,6 +1,6 @@ import 'package:counter_workshop/src/core/theme/app.theme.dart'; import 'package:counter_workshop/src/features/counter/data/repositories/counter.repository.dart'; -import 'package:counter_workshop/src/features/counter/presentation/view/counter.page.dart'; +import 'package:counter_workshop/src/features/counter/presentation/dashboard/view/dashboard.page.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; @@ -30,7 +30,7 @@ class AppView extends StatelessWidget { theme: appTheme.light, darkTheme: appTheme.dark, themeMode: ThemeMode.system, - home: const CounterPage(), + home: const DashboardPage(), ); } } diff --git a/lib/src/features/counter/data/datasources/remote/converters/counter_response.converter.dart b/lib/src/features/counter/data/datasources/remote/converters/counter_response.converter.dart index fa41971..0bcfa0d 100644 --- a/lib/src/features/counter/data/datasources/remote/converters/counter_response.converter.dart +++ b/lib/src/features/counter/data/datasources/remote/converters/counter_response.converter.dart @@ -22,7 +22,7 @@ class CounterResponseConverter { counterValue: counter.value, stepSize: counter.stepSize, startValue: counter.value, - color: '#${(counter.color.value & 0xFFFFFF).toRadixString(16).padLeft(6, '0')}', + color: '#${(counter.color.value & 0xFFFFFF).toRadixString(16).padLeft(6, '0')}', goalValue: counter.goalValue, ); } diff --git a/lib/src/features/counter/data/datasources/remote/dtos/counter_response.dto.dart b/lib/src/features/counter/data/datasources/remote/dtos/counter_response.dto.dart index 16ec619..5bb776c 100644 --- a/lib/src/features/counter/data/datasources/remote/dtos/counter_response.dto.dart +++ b/lib/src/features/counter/data/datasources/remote/dtos/counter_response.dto.dart @@ -1,5 +1,7 @@ -class CounterResponseDto { - CounterResponseDto({ +import 'package:equatable/equatable.dart'; + +class CounterResponseDto extends Equatable { + const CounterResponseDto({ required this.sysId, required this.name, required this.counterValue, @@ -20,4 +22,8 @@ class CounterResponseDto { final int? goalValue; final DateTime? createdAt; final DateTime? updatedAt; + + @override + // TODO: implement props + List get props => [sysId, counterValue]; } diff --git a/lib/src/features/counter/presentation/bloc/counter.bloc.dart b/lib/src/features/counter/presentation/bloc/counter.bloc.dart deleted file mode 100644 index 1380ed9..0000000 --- a/lib/src/features/counter/presentation/bloc/counter.bloc.dart +++ /dev/null @@ -1,56 +0,0 @@ -import 'dart:async'; - -import 'package:counter_workshop/src/features/counter/data/repositories/counter.repository.dart'; -import 'package:counter_workshop/src/features/counter/presentation/bloc/counter.event.dart'; -import 'package:counter_workshop/src/features/counter/presentation/bloc/counter.state.dart'; -import 'package:flutter/foundation.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; - -class CounterBloc extends Bloc { - final CounterRepository counterRepository; - - CounterBloc({required this.counterRepository}) : super(CounterLoadingState()) { - on(_onFetchData); - on(_onIncrement); - on(_onDecrement); - } - - Future> _onFetchData(CounterFetchData event, Emitter emit) async { - emit(CounterLoadingState()); - try { - final counter = await counterRepository.getCounter(); - emit(CounterDataState(counter)); - } catch (e) { - emit(CounterErrorState(e.toString())); - } - } - - Future _onIncrement(CounterIncrementPressed event, Emitter emit) async { - debugPrint('INCREMENT: ${event.counterModel.toString()}'); - emit(CounterLoadingState()); - try { - event.counterModel.value += 1; - await counterRepository.updateCounter(counterModel: event.counterModel); - emit(CounterDataState(event.counterModel)); - } catch (e) { - emit(CounterErrorState(e.toString())); - } - } - - Future _onDecrement(CounterDecrementPressed event, Emitter emit) async { - debugPrint('DECREMENT: ${event.counterModel.toString()}'); - - if (event.counterModel.value == 0) { - return; - } - - emit(CounterLoadingState()); - try { - event.counterModel.value -= 1; - await counterRepository.updateCounter(counterModel: event.counterModel); - emit(CounterDataState(event.counterModel)); - } catch (e) { - emit(CounterErrorState(e.toString())); - } - } -} diff --git a/lib/src/features/counter/presentation/dashboard/bloc/dashboard.bloc.dart b/lib/src/features/counter/presentation/dashboard/bloc/dashboard.bloc.dart new file mode 100644 index 0000000..c0c0834 --- /dev/null +++ b/lib/src/features/counter/presentation/dashboard/bloc/dashboard.bloc.dart @@ -0,0 +1,24 @@ +import 'dart:async'; + +import 'package:counter_workshop/src/features/counter/data/repositories/counter.repository.dart'; +import 'package:counter_workshop/src/features/counter/presentation/dashboard/bloc/dashboard.event.dart'; +import 'package:counter_workshop/src/features/counter/presentation/dashboard/bloc/dashboard.state.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +class DashboardBloc extends Bloc { + final CounterRepository counterRepository; + + DashboardBloc({required this.counterRepository}) : super(DashboardLoadingState()) { + on(_onFetchCounterList); + } + + Future> _onFetchCounterList(FetchCounterList event, Emitter emit) async { + emit(DashboardLoadingState()); + try { + final counter = await counterRepository.getCounterList(); + emit(DashboardDataState(counter)); + } catch (e) { + emit(DashboardErrorState(e.toString())); + } + } +} diff --git a/lib/src/features/counter/presentation/dashboard/bloc/dashboard.event.dart b/lib/src/features/counter/presentation/dashboard/bloc/dashboard.event.dart new file mode 100644 index 0000000..8f553fb --- /dev/null +++ b/lib/src/features/counter/presentation/dashboard/bloc/dashboard.event.dart @@ -0,0 +1,11 @@ +import 'package:equatable/equatable.dart'; + +abstract class DashboardEvent extends Equatable { + const DashboardEvent(); + + @override + List get props => []; +} + +/// Load data from repository +class FetchCounterList extends DashboardEvent {} diff --git a/lib/src/features/counter/presentation/dashboard/bloc/dashboard.state.dart b/lib/src/features/counter/presentation/dashboard/bloc/dashboard.state.dart new file mode 100644 index 0000000..37140cf --- /dev/null +++ b/lib/src/features/counter/presentation/dashboard/bloc/dashboard.state.dart @@ -0,0 +1,31 @@ +import 'package:counter_workshop/src/features/counter/domain/model/counter.model.dart'; +import 'package:equatable/equatable.dart'; +import 'package:flutter/material.dart'; + +@immutable +abstract class DashboardState extends Equatable { + @override + List get props => []; +} + +/// Loading counter State +class DashboardLoadingState extends DashboardState {} + +/// Data counter State +class DashboardDataState extends DashboardState { + final List counterList; + DashboardDataState(this.counterList); + + @override + List get props => [counterList]; +} + +/// Error counter State +class DashboardErrorState extends DashboardState { + final String error; + + DashboardErrorState(this.error); + + @override + List get props => [error]; +} diff --git a/lib/src/features/counter/presentation/dashboard/view/dashboard.page.dart b/lib/src/features/counter/presentation/dashboard/view/dashboard.page.dart new file mode 100644 index 0000000..2e79b9f --- /dev/null +++ b/lib/src/features/counter/presentation/dashboard/view/dashboard.page.dart @@ -0,0 +1,62 @@ +import 'package:counter_workshop/src/features/counter/data/repositories/counter.repository.dart'; +import 'package:counter_workshop/src/features/counter/presentation/dashboard/bloc/dashboard.bloc.dart'; +import 'package:counter_workshop/src/features/counter/presentation/dashboard/bloc/dashboard.event.dart'; +import 'package:counter_workshop/src/features/counter/presentation/dashboard/bloc/dashboard.state.dart'; +import 'package:counter_workshop/src/features/counter/presentation/dashboard/view/widgets/counter_grid.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +/// bloc +class DashboardPage extends StatelessWidget { + const DashboardPage({super.key}); + + @override + Widget build(BuildContext context) { + final counterRepository = context.read(); + return BlocProvider( + create: (_) => DashboardBloc(counterRepository: counterRepository)..add(FetchCounterList()), + child: const _DashboardView(), + ); + } +} + +/// actual counter page +class _DashboardView extends StatelessWidget { + const _DashboardView(); + + @override + Widget build(BuildContext context) { + return Scaffold( + extendBodyBehindAppBar: true, + appBar: AppBar( + title: const Text('Counter Page'), + ), + body: BlocBuilder( + builder: (context, state) { + if (state is DashboardLoadingState) { + // loading + return const Center( + child: CircularProgressIndicator(strokeWidth: 3), + ); + } else if (state is DashboardDataState) { + // data + return CounterGrid(counterList: state.counterList, columnCount: 2); + } else if (state is DashboardErrorState) { + // error + return Center( + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Text( + 'Es ist ein Fehler aufgetreten: ${state.error}', + style: const TextStyle(color: Colors.red), + ), + ), + ); + } + // state unknown, fallback to empty or return a common error + return const SizedBox(); + }, + ), + ); + } +} diff --git a/lib/src/features/counter/presentation/dashboard/view/widgets/counter_grid.dart b/lib/src/features/counter/presentation/dashboard/view/widgets/counter_grid.dart new file mode 100644 index 0000000..77ebfff --- /dev/null +++ b/lib/src/features/counter/presentation/dashboard/view/widgets/counter_grid.dart @@ -0,0 +1,42 @@ +import 'package:counter_workshop/src/features/counter/domain/model/counter.model.dart'; +import 'package:counter_workshop/src/features/counter/presentation/detail/view/counter.page.dart'; +import 'package:flutter/material.dart'; + +class CounterGrid extends StatelessWidget { + const CounterGrid({ + Key? key, + required this.counterList, + required this.columnCount, + }) : super(key: key); + + final List counterList; + final int columnCount; + + @override + Widget build(BuildContext context) { + final theme = Theme.of(context); + return GridView.builder( + gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: columnCount, + crossAxisSpacing: 10, + mainAxisSpacing: 10, + ), + itemCount: counterList.length, + itemBuilder: (BuildContext ctx, index) { + final counter = counterList[index]; + return Card( + child: InkWell( + onTap: () => Navigator.push(context, MaterialPageRoute(builder: (context) => const CounterPage())), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text('${counter.value}', style: theme.textTheme.headlineLarge?.copyWith(fontSize: 60)), + Text(counter.name, style: theme.textTheme.caption), + ], + ), + ), + ); + }, + ); + } +} diff --git a/lib/src/features/counter/presentation/detail/bloc/counter.bloc.dart b/lib/src/features/counter/presentation/detail/bloc/counter.bloc.dart new file mode 100644 index 0000000..692e3b6 --- /dev/null +++ b/lib/src/features/counter/presentation/detail/bloc/counter.bloc.dart @@ -0,0 +1,44 @@ +import 'dart:async'; + +import 'package:counter_workshop/src/features/counter/data/repositories/counter.repository.dart'; +import 'package:counter_workshop/src/features/counter/presentation/detail/bloc/counter.event.dart'; +import 'package:counter_workshop/src/features/counter/presentation/detail/bloc/counter.state.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +class CounterBloc extends Bloc { + final CounterRepository counterRepository; + + CounterBloc({required this.counterRepository}) : super(CounterLoadingState()) { + on(_onIncrement); + on(_onDecrement); + } + + Future _onIncrement(CounterIncrementPressed event, Emitter emit) async { + // debugPrint('INCREMENT: ${event.counterModel.toString()}'); + // emit(CounterLoadingState()); + // try { + // event.counterModel.value += 1; + // await counterRepository.updateCounter(counterModel: event.counterModel); + // emit(CounterDataState(event.counterModel)); + // } catch (e) { + // emit(CounterErrorState(e.toString())); + // } + } + + Future _onDecrement(CounterDecrementPressed event, Emitter emit) async { + // debugPrint('DECREMENT: ${event.counterModel.toString()}'); + + // if (event.counterModel.value == 0) { + // return; + // } + + // emit(CounterLoadingState()); + // try { + // event.counterModel.value -= 1; + // await counterRepository.updateCounter(counterModel: event.counterModel); + // emit(CounterDataState(event.counterModel)); + // } catch (e) { + // emit(CounterErrorState(e.toString())); + // } + } +} diff --git a/lib/src/features/counter/presentation/bloc/counter.event.dart b/lib/src/features/counter/presentation/detail/bloc/counter.event.dart similarity index 94% rename from lib/src/features/counter/presentation/bloc/counter.event.dart rename to lib/src/features/counter/presentation/detail/bloc/counter.event.dart index 42c28c2..9caee4d 100644 --- a/lib/src/features/counter/presentation/bloc/counter.event.dart +++ b/lib/src/features/counter/presentation/detail/bloc/counter.event.dart @@ -9,7 +9,7 @@ abstract class CounterEvent extends Equatable { } /// Load data from repository -class CounterFetchData extends CounterEvent {} +class FetchCounterList extends CounterEvent {} /// Notifies bloc to increment state class CounterIncrementPressed extends CounterEvent { diff --git a/lib/src/features/counter/presentation/bloc/counter.state.dart b/lib/src/features/counter/presentation/detail/bloc/counter.state.dart similarity index 83% rename from lib/src/features/counter/presentation/bloc/counter.state.dart rename to lib/src/features/counter/presentation/detail/bloc/counter.state.dart index 49a2bd0..54b26e7 100644 --- a/lib/src/features/counter/presentation/bloc/counter.state.dart +++ b/lib/src/features/counter/presentation/detail/bloc/counter.state.dart @@ -13,11 +13,11 @@ class CounterLoadingState extends CounterState {} /// Data counter State class CounterDataState extends CounterState { - final CounterModel counterModel; - CounterDataState(this.counterModel); + final List counterList; + CounterDataState(this.counterList); @override - List get props => [counterModel]; + List get props => [counterList]; } /// Error counter State diff --git a/lib/src/features/counter/presentation/view/counter.page.dart b/lib/src/features/counter/presentation/detail/view/counter.page.dart similarity index 90% rename from lib/src/features/counter/presentation/view/counter.page.dart rename to lib/src/features/counter/presentation/detail/view/counter.page.dart index 5c10af0..e17cc13 100644 --- a/lib/src/features/counter/presentation/view/counter.page.dart +++ b/lib/src/features/counter/presentation/detail/view/counter.page.dart @@ -1,9 +1,9 @@ import 'package:counter_workshop/src/features/counter/data/repositories/counter.repository.dart'; -import 'package:counter_workshop/src/features/counter/presentation/bloc/counter.bloc.dart'; -import 'package:counter_workshop/src/features/counter/presentation/bloc/counter.event.dart'; -import 'package:counter_workshop/src/features/counter/presentation/bloc/counter.state.dart'; -import 'package:counter_workshop/src/features/counter/presentation/view/widgets/counter_text.widget.dart'; -import 'package:counter_workshop/src/features/counter/presentation/view/widgets/custom_circular_button.widget.dart'; +import 'package:counter_workshop/src/features/counter/presentation/detail/bloc/counter.bloc.dart'; +import 'package:counter_workshop/src/features/counter/presentation/detail/bloc/counter.event.dart'; +import 'package:counter_workshop/src/features/counter/presentation/detail/bloc/counter.state.dart'; +import 'package:counter_workshop/src/features/counter/presentation/detail/view/widgets/counter_text.widget.dart'; +import 'package:counter_workshop/src/features/counter/presentation/detail/view/widgets/custom_circular_button.widget.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; @@ -15,7 +15,7 @@ class CounterPage extends StatelessWidget { Widget build(BuildContext context) { final counterRepository = context.read(); return BlocProvider( - create: (_) => CounterBloc(counterRepository: counterRepository)..add(CounterFetchData()), + create: (_) => CounterBloc(counterRepository: counterRepository), child: const _CounterView(), ); } @@ -48,7 +48,7 @@ class _CounterView extends StatelessWidget { child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ - CounterText(counterValue: state.counterModel.value), + CounterText(counterValue: state.counterList[0].value), ], ), ); @@ -79,13 +79,13 @@ class _CounterView extends StatelessWidget { CustomCircularButton( icon: Icons.remove, onPressed: state is CounterDataState - ? () => counterBloc.add(CounterDecrementPressed(state.counterModel)) + ? () => counterBloc.add(CounterDecrementPressed(state.counterList[0])) : null, ), CustomCircularButton( icon: Icons.add, onPressed: state is CounterDataState - ? () => counterBloc.add(CounterIncrementPressed(state.counterModel)) + ? () => counterBloc.add(CounterIncrementPressed(state.counterList[0])) : null, ), ], diff --git a/lib/src/features/counter/presentation/view/widgets/counter_text.widget.dart b/lib/src/features/counter/presentation/detail/view/widgets/counter_text.widget.dart similarity index 100% rename from lib/src/features/counter/presentation/view/widgets/counter_text.widget.dart rename to lib/src/features/counter/presentation/detail/view/widgets/counter_text.widget.dart diff --git a/lib/src/features/counter/presentation/view/widgets/custom_circular_button.widget.dart b/lib/src/features/counter/presentation/detail/view/widgets/custom_circular_button.widget.dart similarity index 100% rename from lib/src/features/counter/presentation/view/widgets/custom_circular_button.widget.dart rename to lib/src/features/counter/presentation/detail/view/widgets/custom_circular_button.widget.dart diff --git a/test/counter_response.converter_test.dart b/test/counter_response.converter_test.dart index e7af8ff..e0c0c32 100644 --- a/test/counter_response.converter_test.dart +++ b/test/counter_response.converter_test.dart @@ -11,17 +11,17 @@ void main() { name: 'Kaffee', color: Color(0xffff3300), ); - final dto = CounterResponseDto( + const dto = CounterResponseDto( counterValue: 1, sysId: '1', name: 'Kaffee', ); group('Counter Response Converter', () { - test('should be convert to model', () { + test('should convert to model', () { expect(CounterResponseConverter().toModel(dto), model); }); - test('should be convert to dto', () { + test('should convert to dto', () { expect(CounterResponseConverter().toDto(model), dto); }); }); diff --git a/test/widget_test.dart b/test/widget_test.dart index f1151e4..55951da 100644 --- a/test/widget_test.dart +++ b/test/widget_test.dart @@ -5,39 +5,33 @@ // gestures. You can also use WidgetTester to find child widgets in the widget // tree, read text, and verify that the values of widget properties are correct. -import 'package:counter_workshop/src/app.dart'; -import 'package:counter_workshop/src/features/counter/data/datasources/remote/src/mock/counter_fake.api.dart'; -import 'package:counter_workshop/src/features/counter/data/repositories/counter.repository.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter_test/flutter_test.dart'; - void main() { - testWidgets('Counter Smoke Test', (WidgetTester tester) async { - // Build our app and trigger a frame. - await tester.pumpWidget( - App(counterRepository: CounterRepository(counterApi: CounterFakeApi())), - const Duration(milliseconds: 300), // Because of FakeApi delay - ); + // testWidgets('Counter Smoke Test', (WidgetTester tester) async { + // // Build our app and trigger a frame. + // await tester.pumpWidget( + // App(counterRepository: CounterRepository(counterApi: CounterFakeApi())), + // const Duration(milliseconds: 300), // Because of FakeApi delay + // ); - // Tap the '-' icon and trigger a frame. - await tester.tap(find.byIcon(Icons.remove)); - await tester.pumpAndSettle(); - await tester.pumpAndSettle(const Duration(milliseconds: 300)); // Because of FakeApi delay + // // Tap the '-' icon and trigger a frame. + // await tester.tap(find.byIcon(Icons.remove)); + // await tester.pumpAndSettle(); + // await tester.pumpAndSettle(const Duration(milliseconds: 300)); // Because of FakeApi delay - // Verify that our counter does not decremented. - expect(find.text('-1'), findsNothing); - expect(find.text('0'), findsOneWidget); + // // Verify that our counter does not decremented. + // expect(find.text('-1'), findsNothing); + // expect(find.text('0'), findsOneWidget); - await tester.tap(find.byIcon(Icons.add)); - await tester.pumpAndSettle(const Duration(milliseconds: 300)); // Because of FakeApi delay - // Verify that our counter has incremented. - expect(find.text('0'), findsNothing); - expect(find.text('1'), findsOneWidget); + // await tester.tap(find.byIcon(Icons.add)); + // await tester.pumpAndSettle(const Duration(milliseconds: 300)); // Because of FakeApi delay + // // Verify that our counter has incremented. + // expect(find.text('0'), findsNothing); + // expect(find.text('1'), findsOneWidget); - await tester.tap(find.byIcon(Icons.remove)); - await tester.pumpAndSettle(const Duration(milliseconds: 300)); // Because of FakeApi delay - // Verify that our counter has decremented. - expect(find.text('1'), findsNothing); - expect(find.text('0'), findsOneWidget); - }); + // await tester.tap(find.byIcon(Icons.remove)); + // await tester.pumpAndSettle(const Duration(milliseconds: 300)); // Because of FakeApi delay + // // Verify that our counter has decremented. + // expect(find.text('1'), findsNothing); + // expect(find.text('0'), findsOneWidget); + // }); } From 5e7a57cf40d26122391604e91da42056e45160aa Mon Sep 17 00:00:00 2001 From: Jan Marsh Date: Mon, 12 Sep 2022 21:25:39 +0200 Subject: [PATCH 08/18] fixing crud --- .../custom_loading_indicator.widget.dart | 14 +++ .../converters/counter_request.converter.dart | 28 ++++++ .../counter_response.converter.dart | 2 +- .../data/datasources/remote/counter.api.dart | 2 +- .../remote/dtos/counter_request.dto.dart | 2 +- .../remote/src/mock/counter_fake.api.dart | 7 +- .../remote/src/rest/counter_rest.api.dart | 2 +- .../data/repositories/counter.repository.dart | 21 +++- .../counter/domain/model/counter.model.dart | 24 ++++- .../counter.repository_interface.dart | 4 +- .../dashboard/view/widgets/counter_grid.dart | 10 +- .../detail/bloc/counter.bloc.dart | 44 --------- .../detail/bloc/counter.state.dart | 31 ------ .../detail/view/counter.page.dart | 98 ------------------- .../edit/bloc/edit_counter.bloc.dart | 36 +++++++ .../bloc/edit_counter.event.dart} | 11 +-- .../edit/bloc/edit_counter.state.dart | 17 ++++ .../edit/view/edit_counter.page.dart | 76 ++++++++++++++ .../view/widgets/counter_text.widget.dart | 0 .../custom_circular_button.widget.dart | 0 20 files changed, 232 insertions(+), 197 deletions(-) create mode 100644 lib/src/core/widgets/custom_loading_indicator.widget.dart create mode 100644 lib/src/features/counter/data/datasources/remote/converters/counter_request.converter.dart delete mode 100644 lib/src/features/counter/presentation/detail/bloc/counter.bloc.dart delete mode 100644 lib/src/features/counter/presentation/detail/bloc/counter.state.dart delete mode 100644 lib/src/features/counter/presentation/detail/view/counter.page.dart create mode 100644 lib/src/features/counter/presentation/edit/bloc/edit_counter.bloc.dart rename lib/src/features/counter/presentation/{detail/bloc/counter.event.dart => edit/bloc/edit_counter.event.dart} (68%) create mode 100644 lib/src/features/counter/presentation/edit/bloc/edit_counter.state.dart create mode 100644 lib/src/features/counter/presentation/edit/view/edit_counter.page.dart rename lib/src/features/counter/presentation/{detail => edit}/view/widgets/counter_text.widget.dart (100%) rename lib/src/features/counter/presentation/{detail => edit}/view/widgets/custom_circular_button.widget.dart (100%) diff --git a/lib/src/core/widgets/custom_loading_indicator.widget.dart b/lib/src/core/widgets/custom_loading_indicator.widget.dart new file mode 100644 index 0000000..681c6d4 --- /dev/null +++ b/lib/src/core/widgets/custom_loading_indicator.widget.dart @@ -0,0 +1,14 @@ +import 'package:flutter/material.dart'; + +class CustomLoadingIndicator extends StatelessWidget { + const CustomLoadingIndicator({ + Key? key, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return const Center( + child: CircularProgressIndicator(strokeWidth: 3), + ); + } +} diff --git a/lib/src/features/counter/data/datasources/remote/converters/counter_request.converter.dart b/lib/src/features/counter/data/datasources/remote/converters/counter_request.converter.dart new file mode 100644 index 0000000..86e1924 --- /dev/null +++ b/lib/src/features/counter/data/datasources/remote/converters/counter_request.converter.dart @@ -0,0 +1,28 @@ +import 'package:counter_workshop/src/core/extensions/color.extension.dart'; +import 'package:counter_workshop/src/features/counter/data/datasources/remote/dtos/counter_request.dto.dart'; +import 'package:counter_workshop/src/features/counter/domain/model/counter.model.dart'; + +class CounterRequestConverter { + CounterModel toModel(CounterRequestDto counterRequestDto) { + return CounterModel( + id: counterRequestDto.sysId, + name: counterRequestDto.name, + value: counterRequestDto.counterValue, + stepSize: counterRequestDto.stepSize, + startValue: counterRequestDto.startValue, + color: counterRequestDto.color.toColor(), + goalValue: counterRequestDto.goalValue, + ); + } + + CounterRequestDto toDto(CounterModel counter) { + return CounterRequestDto( + name: counter.name, + counterValue: counter.value, + stepSize: counter.stepSize, + startValue: counter.value, + color: '#${(counter.color.value & 0xFFFFFF).toRadixString(16).padLeft(6, '0')}', + goalValue: counter.goalValue, + ); + } +} diff --git a/lib/src/features/counter/data/datasources/remote/converters/counter_response.converter.dart b/lib/src/features/counter/data/datasources/remote/converters/counter_response.converter.dart index 0bcfa0d..d2952b6 100644 --- a/lib/src/features/counter/data/datasources/remote/converters/counter_response.converter.dart +++ b/lib/src/features/counter/data/datasources/remote/converters/counter_response.converter.dart @@ -17,7 +17,7 @@ class CounterResponseConverter { CounterResponseDto toDto(CounterModel counter) { return CounterResponseDto( - sysId: counter.id, + sysId: counter.id!, name: counter.name, counterValue: counter.value, stepSize: counter.stepSize, diff --git a/lib/src/features/counter/data/datasources/remote/counter.api.dart b/lib/src/features/counter/data/datasources/remote/counter.api.dart index 38904e1..6d4fcbf 100644 --- a/lib/src/features/counter/data/datasources/remote/counter.api.dart +++ b/lib/src/features/counter/data/datasources/remote/counter.api.dart @@ -18,7 +18,7 @@ abstract class CounterApi { Future updateCounter(String id, CounterResponseDto counterResponseDto); /// Create a new counter - Future createCounter(CounterRequestDto counterRequestDto); + Future createCounter(CounterRequestDto counterRequestDto); /// Deletes a counter by a given counter [id] /// diff --git a/lib/src/features/counter/data/datasources/remote/dtos/counter_request.dto.dart b/lib/src/features/counter/data/datasources/remote/dtos/counter_request.dto.dart index f14ce9d..a977968 100644 --- a/lib/src/features/counter/data/datasources/remote/dtos/counter_request.dto.dart +++ b/lib/src/features/counter/data/datasources/remote/dtos/counter_request.dto.dart @@ -11,7 +11,7 @@ class CounterRequestDto { this.updatedAt, }); - final String? sysId; + late final String? sysId; final String name; final int counterValue; final int startValue; diff --git a/lib/src/features/counter/data/datasources/remote/src/mock/counter_fake.api.dart b/lib/src/features/counter/data/datasources/remote/src/mock/counter_fake.api.dart index 9d2c572..612b4e8 100644 --- a/lib/src/features/counter/data/datasources/remote/src/mock/counter_fake.api.dart +++ b/lib/src/features/counter/data/datasources/remote/src/mock/counter_fake.api.dart @@ -59,9 +59,12 @@ class CounterFakeApi implements CounterApi { } @override - Future createCounter(CounterRequestDto counterRequestDto) { + Future createCounter(CounterRequestDto counterRequestDto) { + counterRequestDto.sysId = '5'; + final dto = counterRequestDto as CounterResponseDto; + _counterList.add(dto); return Future.delayed(Duration(milliseconds: fakeApiDelay), () { - _counterList.add(counterRequestDto as CounterResponseDto); + return dto; }); } diff --git a/lib/src/features/counter/data/datasources/remote/src/rest/counter_rest.api.dart b/lib/src/features/counter/data/datasources/remote/src/rest/counter_rest.api.dart index 6dca328..4e80231 100644 --- a/lib/src/features/counter/data/datasources/remote/src/rest/counter_rest.api.dart +++ b/lib/src/features/counter/data/datasources/remote/src/rest/counter_rest.api.dart @@ -10,7 +10,7 @@ class CounterRestApi implements CounterApi { final http.Client client; @override - Future createCounter(CounterRequestDto counterRequestDto) { + Future createCounter(CounterRequestDto counterRequestDto) { // TODO: implement createCounter throw UnimplementedError(); } diff --git a/lib/src/features/counter/data/repositories/counter.repository.dart b/lib/src/features/counter/data/repositories/counter.repository.dart index 77e7d16..d38aa77 100644 --- a/lib/src/features/counter/data/repositories/counter.repository.dart +++ b/lib/src/features/counter/data/repositories/counter.repository.dart @@ -1,5 +1,6 @@ import 'dart:async'; +import 'package:counter_workshop/src/features/counter/data/datasources/remote/converters/counter_request.converter.dart'; import 'package:counter_workshop/src/features/counter/data/datasources/remote/counter.api.dart'; import 'package:counter_workshop/src/features/counter/data/datasources/remote/converters/counter_response.converter.dart'; import 'package:counter_workshop/src/features/counter/data/datasources/remote/dtos/counter_response.dto.dart'; @@ -34,14 +35,28 @@ class CounterRepository implements CounterRepositoryInterface { } @override - Future updateCounter({required CounterModel counterModel}) async { - log('updating counter: ${counterModel.id} with value: $counterModel'); + Future createCounter({required CounterModel counterModel}) async { + log('creating new counter with name ${counterModel.name}'); + + // map model to dto + final dto = CounterRequestConverter().toDto(counterModel); + + // store model in database + final response = await counterApi.createCounter(dto); + + // map dto to model + return CounterResponseConverter().toModel(response); + } + + @override + Future updateCounter({required String id, required CounterModel counterModel}) async { + log('updating counter: $id with value: $counterModel'); // map model to dto final dto = CounterResponseConverter().toDto(counterModel); // store model in database - await counterApi.updateCounter(counterModel.id, dto); + await counterApi.updateCounter(id, dto); } @override diff --git a/lib/src/features/counter/domain/model/counter.model.dart b/lib/src/features/counter/domain/model/counter.model.dart index 358328c..a94962d 100644 --- a/lib/src/features/counter/domain/model/counter.model.dart +++ b/lib/src/features/counter/domain/model/counter.model.dart @@ -3,7 +3,7 @@ import 'package:flutter/material.dart'; class CounterModel extends Equatable { const CounterModel({ - required this.id, + this.id, required this.name, this.value = 0, this.stepSize = 1, @@ -13,7 +13,7 @@ class CounterModel extends Equatable { }); /// technical counter id - final String id; + final String? id; final String name; final int value; final int startValue; @@ -21,6 +21,26 @@ class CounterModel extends Equatable { final Color color; final int? goalValue; + CounterModel copyWith({ + String? id, + String? name, + int? value, + int? startValue, + int? stepSize, + Color? color, + int? goalValue, + }) { + return CounterModel( + id: id ?? this.id, + name: name ?? this.name, + value: value ?? this.value, + startValue: startValue ?? this.startValue, + stepSize: stepSize ?? this.stepSize, + color: color ?? this.color, + goalValue: goalValue ?? this.goalValue, + ); + } + @override List get props => [id, name, value]; } diff --git a/lib/src/features/counter/domain/repository/counter.repository_interface.dart b/lib/src/features/counter/domain/repository/counter.repository_interface.dart index 294d491..29c5337 100644 --- a/lib/src/features/counter/domain/repository/counter.repository_interface.dart +++ b/lib/src/features/counter/domain/repository/counter.repository_interface.dart @@ -7,7 +7,9 @@ abstract class CounterRepositoryInterface { Future getCounter({required String id}); - Future updateCounter({required CounterModel counterModel}); + Future createCounter({required CounterModel counterModel}); + + Future updateCounter({required String id, required CounterModel counterModel}); Future deleteCounter({required String id}); } diff --git a/lib/src/features/counter/presentation/dashboard/view/widgets/counter_grid.dart b/lib/src/features/counter/presentation/dashboard/view/widgets/counter_grid.dart index 77ebfff..4cb5eb4 100644 --- a/lib/src/features/counter/presentation/dashboard/view/widgets/counter_grid.dart +++ b/lib/src/features/counter/presentation/dashboard/view/widgets/counter_grid.dart @@ -1,5 +1,5 @@ import 'package:counter_workshop/src/features/counter/domain/model/counter.model.dart'; -import 'package:counter_workshop/src/features/counter/presentation/detail/view/counter.page.dart'; +import 'package:counter_workshop/src/features/counter/presentation/edit/view/edit_counter.page.dart'; import 'package:flutter/material.dart'; class CounterGrid extends StatelessWidget { @@ -23,15 +23,15 @@ class CounterGrid extends StatelessWidget { ), itemCount: counterList.length, itemBuilder: (BuildContext ctx, index) { - final counter = counterList[index]; + final counterModel = counterList[index]; return Card( child: InkWell( - onTap: () => Navigator.push(context, MaterialPageRoute(builder: (context) => const CounterPage())), + onTap: () => Navigator.push(context, EditCounterPage.route(counterModel: counterModel)), child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ - Text('${counter.value}', style: theme.textTheme.headlineLarge?.copyWith(fontSize: 60)), - Text(counter.name, style: theme.textTheme.caption), + Text('${counterModel.value}', style: theme.textTheme.headlineLarge?.copyWith(fontSize: 60)), + Text(counterModel.name, style: theme.textTheme.caption), ], ), ), diff --git a/lib/src/features/counter/presentation/detail/bloc/counter.bloc.dart b/lib/src/features/counter/presentation/detail/bloc/counter.bloc.dart deleted file mode 100644 index 692e3b6..0000000 --- a/lib/src/features/counter/presentation/detail/bloc/counter.bloc.dart +++ /dev/null @@ -1,44 +0,0 @@ -import 'dart:async'; - -import 'package:counter_workshop/src/features/counter/data/repositories/counter.repository.dart'; -import 'package:counter_workshop/src/features/counter/presentation/detail/bloc/counter.event.dart'; -import 'package:counter_workshop/src/features/counter/presentation/detail/bloc/counter.state.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; - -class CounterBloc extends Bloc { - final CounterRepository counterRepository; - - CounterBloc({required this.counterRepository}) : super(CounterLoadingState()) { - on(_onIncrement); - on(_onDecrement); - } - - Future _onIncrement(CounterIncrementPressed event, Emitter emit) async { - // debugPrint('INCREMENT: ${event.counterModel.toString()}'); - // emit(CounterLoadingState()); - // try { - // event.counterModel.value += 1; - // await counterRepository.updateCounter(counterModel: event.counterModel); - // emit(CounterDataState(event.counterModel)); - // } catch (e) { - // emit(CounterErrorState(e.toString())); - // } - } - - Future _onDecrement(CounterDecrementPressed event, Emitter emit) async { - // debugPrint('DECREMENT: ${event.counterModel.toString()}'); - - // if (event.counterModel.value == 0) { - // return; - // } - - // emit(CounterLoadingState()); - // try { - // event.counterModel.value -= 1; - // await counterRepository.updateCounter(counterModel: event.counterModel); - // emit(CounterDataState(event.counterModel)); - // } catch (e) { - // emit(CounterErrorState(e.toString())); - // } - } -} diff --git a/lib/src/features/counter/presentation/detail/bloc/counter.state.dart b/lib/src/features/counter/presentation/detail/bloc/counter.state.dart deleted file mode 100644 index 54b26e7..0000000 --- a/lib/src/features/counter/presentation/detail/bloc/counter.state.dart +++ /dev/null @@ -1,31 +0,0 @@ -import 'package:counter_workshop/src/features/counter/domain/model/counter.model.dart'; -import 'package:equatable/equatable.dart'; -import 'package:flutter/material.dart'; - -@immutable -abstract class CounterState extends Equatable { - @override - List get props => []; -} - -/// Loading counter State -class CounterLoadingState extends CounterState {} - -/// Data counter State -class CounterDataState extends CounterState { - final List counterList; - CounterDataState(this.counterList); - - @override - List get props => [counterList]; -} - -/// Error counter State -class CounterErrorState extends CounterState { - final String error; - - CounterErrorState(this.error); - - @override - List get props => [error]; -} diff --git a/lib/src/features/counter/presentation/detail/view/counter.page.dart b/lib/src/features/counter/presentation/detail/view/counter.page.dart deleted file mode 100644 index e17cc13..0000000 --- a/lib/src/features/counter/presentation/detail/view/counter.page.dart +++ /dev/null @@ -1,98 +0,0 @@ -import 'package:counter_workshop/src/features/counter/data/repositories/counter.repository.dart'; -import 'package:counter_workshop/src/features/counter/presentation/detail/bloc/counter.bloc.dart'; -import 'package:counter_workshop/src/features/counter/presentation/detail/bloc/counter.event.dart'; -import 'package:counter_workshop/src/features/counter/presentation/detail/bloc/counter.state.dart'; -import 'package:counter_workshop/src/features/counter/presentation/detail/view/widgets/counter_text.widget.dart'; -import 'package:counter_workshop/src/features/counter/presentation/detail/view/widgets/custom_circular_button.widget.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; - -/// bloc -class CounterPage extends StatelessWidget { - const CounterPage({super.key}); - - @override - Widget build(BuildContext context) { - final counterRepository = context.read(); - return BlocProvider( - create: (_) => CounterBloc(counterRepository: counterRepository), - child: const _CounterView(), - ); - } -} - -/// actual counter page -class _CounterView extends StatelessWidget { - const _CounterView(); - - @override - Widget build(BuildContext context) { - final counterBloc = context.read(); - // final showButton = false; - - return Scaffold( - extendBodyBehindAppBar: true, - appBar: AppBar( - title: const Text('Counter Page'), - ), - body: BlocBuilder( - builder: (context, state) { - if (state is CounterLoadingState) { - // loading - return const Center( - child: CircularProgressIndicator(strokeWidth: 3), - ); - } else if (state is CounterDataState) { - // data - return Center( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - CounterText(counterValue: state.counterList[0].value), - ], - ), - ); - } else if (state is CounterErrorState) { - // error - return Center( - child: Padding( - padding: const EdgeInsets.all(8.0), - child: Text( - 'Es ist ein Fehler aufgetreten: ${state.error}', - style: const TextStyle(color: Colors.red), - ), - ), - ); - } - // state unknown, fallback to empty or return a common error - return const SizedBox(); - }, - ), - floatingActionButtonLocation: FloatingActionButtonLocation.centerFloat, - floatingActionButton: Container( - padding: const EdgeInsets.symmetric(vertical: 5, horizontal: 40.0), - child: BlocBuilder( - builder: (context, state) { - return Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - CustomCircularButton( - icon: Icons.remove, - onPressed: state is CounterDataState - ? () => counterBloc.add(CounterDecrementPressed(state.counterList[0])) - : null, - ), - CustomCircularButton( - icon: Icons.add, - onPressed: state is CounterDataState - ? () => counterBloc.add(CounterIncrementPressed(state.counterList[0])) - : null, - ), - ], - ); - }, - ), - ), - ); - } -} diff --git a/lib/src/features/counter/presentation/edit/bloc/edit_counter.bloc.dart b/lib/src/features/counter/presentation/edit/bloc/edit_counter.bloc.dart new file mode 100644 index 0000000..d5c69a2 --- /dev/null +++ b/lib/src/features/counter/presentation/edit/bloc/edit_counter.bloc.dart @@ -0,0 +1,36 @@ +import 'dart:async'; + +import 'package:counter_workshop/src/features/counter/data/repositories/counter.repository.dart'; +import 'package:counter_workshop/src/features/counter/domain/model/counter.model.dart'; +import 'package:counter_workshop/src/features/counter/presentation/edit/bloc/edit_counter.event.dart'; +import 'package:counter_workshop/src/features/counter/presentation/edit/bloc/edit_counter.state.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +class EditCounterBloc extends Bloc { + final CounterRepository counterRepository; + + EditCounterBloc({required this.counterRepository, required CounterModel counterModel}) + : super( + EditCounterState(counterModel: counterModel), + ) { + on(_onIncrement); + on(_onDecrement); + } + + Future _onIncrement(CounterIncrementPressed event, Emitter emit) async { + debugPrint('INCREMENT: ${state.counterModel.toString()}'); + final newCounterModel = state.counterModel.copyWith(value: state.counterModel.value + 1); + emit(EditCounterState(counterModel: newCounterModel)); + } + + Future _onDecrement(CounterDecrementPressed event, Emitter emit) async { + debugPrint('DECREMENT: ${state.counterModel.toString()}'); + + if (event.counterModel.value == 0) { + return; + } + final newCounterModel = state.counterModel.copyWith(value: state.counterModel.value - 1); + emit(EditCounterState(counterModel: newCounterModel)); + } +} diff --git a/lib/src/features/counter/presentation/detail/bloc/counter.event.dart b/lib/src/features/counter/presentation/edit/bloc/edit_counter.event.dart similarity index 68% rename from lib/src/features/counter/presentation/detail/bloc/counter.event.dart rename to lib/src/features/counter/presentation/edit/bloc/edit_counter.event.dart index 9caee4d..a6f7e51 100644 --- a/lib/src/features/counter/presentation/detail/bloc/counter.event.dart +++ b/lib/src/features/counter/presentation/edit/bloc/edit_counter.event.dart @@ -1,18 +1,15 @@ import 'package:counter_workshop/src/features/counter/domain/model/counter.model.dart'; import 'package:equatable/equatable.dart'; -abstract class CounterEvent extends Equatable { - const CounterEvent(); +abstract class EditCounterEvent extends Equatable { + const EditCounterEvent(); @override List get props => []; } -/// Load data from repository -class FetchCounterList extends CounterEvent {} - /// Notifies bloc to increment state -class CounterIncrementPressed extends CounterEvent { +class CounterIncrementPressed extends EditCounterEvent { const CounterIncrementPressed(this.counterModel); final CounterModel counterModel; @@ -21,7 +18,7 @@ class CounterIncrementPressed extends CounterEvent { } /// Notifies bloc to decrement state -class CounterDecrementPressed extends CounterEvent { +class CounterDecrementPressed extends EditCounterEvent { const CounterDecrementPressed(this.counterModel); final CounterModel counterModel; diff --git a/lib/src/features/counter/presentation/edit/bloc/edit_counter.state.dart b/lib/src/features/counter/presentation/edit/bloc/edit_counter.state.dart new file mode 100644 index 0000000..d238874 --- /dev/null +++ b/lib/src/features/counter/presentation/edit/bloc/edit_counter.state.dart @@ -0,0 +1,17 @@ +import 'package:counter_workshop/src/features/counter/domain/model/counter.model.dart'; +import 'package:equatable/equatable.dart'; + +class EditCounterState extends Equatable { + const EditCounterState({required this.counterModel}); + + final CounterModel counterModel; + + EditCounterState copyWith({ + CounterModel? counterModel, + }) { + return EditCounterState(counterModel: counterModel ?? this.counterModel); + } + + @override + List get props => [counterModel]; +} diff --git a/lib/src/features/counter/presentation/edit/view/edit_counter.page.dart b/lib/src/features/counter/presentation/edit/view/edit_counter.page.dart new file mode 100644 index 0000000..6db86c5 --- /dev/null +++ b/lib/src/features/counter/presentation/edit/view/edit_counter.page.dart @@ -0,0 +1,76 @@ +import 'package:counter_workshop/src/features/counter/data/repositories/counter.repository.dart'; +import 'package:counter_workshop/src/features/counter/domain/model/counter.model.dart'; +import 'package:counter_workshop/src/features/counter/presentation/edit/bloc/edit_counter.bloc.dart'; +import 'package:counter_workshop/src/features/counter/presentation/edit/bloc/edit_counter.event.dart'; +import 'package:counter_workshop/src/features/counter/presentation/edit/view/widgets/counter_text.widget.dart'; +import 'package:counter_workshop/src/features/counter/presentation/edit/view/widgets/custom_circular_button.widget.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +/// bloc +class EditCounterPage extends StatelessWidget { + const EditCounterPage({super.key}); + + static Route route({required CounterModel counterModel, bool fullscreen = false}) { + return MaterialPageRoute( + fullscreenDialog: fullscreen, + builder: (context) => BlocProvider( + create: (context) => EditCounterBloc( + counterRepository: context.read(), + counterModel: counterModel, + ), + child: const EditCounterPage(), + ), + ); + } + + @override + Widget build(BuildContext context) { + return const CounterView(); + } +} + +/// actual counter page +class CounterView extends StatelessWidget { + const CounterView({super.key}); + + @override + Widget build(BuildContext context) { + final theme = Theme.of(context); + final editCounterBloc = context.watch(); + final counterModel = editCounterBloc.state.counterModel; + + return Scaffold( + extendBodyBehindAppBar: true, + appBar: AppBar( + title: const Text('Counter Page'), + ), + body: Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + CounterText(counterValue: counterModel.value), + Text(counterModel.name, style: theme.textTheme.caption), + ], + ), + ), + floatingActionButtonLocation: FloatingActionButtonLocation.centerFloat, + floatingActionButton: Container( + padding: const EdgeInsets.symmetric(vertical: 5, horizontal: 40.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + CustomCircularButton( + icon: Icons.remove, + onPressed: () => editCounterBloc.add(CounterDecrementPressed(counterModel)), + ), + CustomCircularButton( + icon: Icons.add, + onPressed: () => editCounterBloc.add(CounterIncrementPressed(counterModel)), + ), + ], + ), + ), + ); + } +} diff --git a/lib/src/features/counter/presentation/detail/view/widgets/counter_text.widget.dart b/lib/src/features/counter/presentation/edit/view/widgets/counter_text.widget.dart similarity index 100% rename from lib/src/features/counter/presentation/detail/view/widgets/counter_text.widget.dart rename to lib/src/features/counter/presentation/edit/view/widgets/counter_text.widget.dart diff --git a/lib/src/features/counter/presentation/detail/view/widgets/custom_circular_button.widget.dart b/lib/src/features/counter/presentation/edit/view/widgets/custom_circular_button.widget.dart similarity index 100% rename from lib/src/features/counter/presentation/detail/view/widgets/custom_circular_button.widget.dart rename to lib/src/features/counter/presentation/edit/view/widgets/custom_circular_button.widget.dart From af781fd066e5359903c696233f2137e294a51f69 Mon Sep 17 00:00:00 2001 From: Jan Marsh Date: Mon, 12 Sep 2022 22:00:15 +0200 Subject: [PATCH 09/18] fix it --- .../converters/counter_request.converter.dart | 1 - .../converters/counter_response.converter.dart | 2 +- .../remote/dtos/counter_request.dto.dart | 2 -- .../remote/dtos/counter_response.dto.dart | 16 ++++++++++++++++ .../remote/src/mock/counter_fake.api.dart | 4 ++-- .../counter/domain/model/counter.model.dart | 4 ++-- .../edit/bloc/edit_counter.bloc.dart | 1 + 7 files changed, 22 insertions(+), 8 deletions(-) diff --git a/lib/src/features/counter/data/datasources/remote/converters/counter_request.converter.dart b/lib/src/features/counter/data/datasources/remote/converters/counter_request.converter.dart index 86e1924..e973020 100644 --- a/lib/src/features/counter/data/datasources/remote/converters/counter_request.converter.dart +++ b/lib/src/features/counter/data/datasources/remote/converters/counter_request.converter.dart @@ -5,7 +5,6 @@ import 'package:counter_workshop/src/features/counter/domain/model/counter.model class CounterRequestConverter { CounterModel toModel(CounterRequestDto counterRequestDto) { return CounterModel( - id: counterRequestDto.sysId, name: counterRequestDto.name, value: counterRequestDto.counterValue, stepSize: counterRequestDto.stepSize, diff --git a/lib/src/features/counter/data/datasources/remote/converters/counter_response.converter.dart b/lib/src/features/counter/data/datasources/remote/converters/counter_response.converter.dart index d2952b6..0bcfa0d 100644 --- a/lib/src/features/counter/data/datasources/remote/converters/counter_response.converter.dart +++ b/lib/src/features/counter/data/datasources/remote/converters/counter_response.converter.dart @@ -17,7 +17,7 @@ class CounterResponseConverter { CounterResponseDto toDto(CounterModel counter) { return CounterResponseDto( - sysId: counter.id!, + sysId: counter.id, name: counter.name, counterValue: counter.value, stepSize: counter.stepSize, diff --git a/lib/src/features/counter/data/datasources/remote/dtos/counter_request.dto.dart b/lib/src/features/counter/data/datasources/remote/dtos/counter_request.dto.dart index a977968..47022fe 100644 --- a/lib/src/features/counter/data/datasources/remote/dtos/counter_request.dto.dart +++ b/lib/src/features/counter/data/datasources/remote/dtos/counter_request.dto.dart @@ -1,6 +1,5 @@ class CounterRequestDto { CounterRequestDto({ - this.sysId, required this.name, required this.counterValue, this.stepSize = 1, @@ -11,7 +10,6 @@ class CounterRequestDto { this.updatedAt, }); - late final String? sysId; final String name; final int counterValue; final int startValue; diff --git a/lib/src/features/counter/data/datasources/remote/dtos/counter_response.dto.dart b/lib/src/features/counter/data/datasources/remote/dtos/counter_response.dto.dart index 5bb776c..8f95100 100644 --- a/lib/src/features/counter/data/datasources/remote/dtos/counter_response.dto.dart +++ b/lib/src/features/counter/data/datasources/remote/dtos/counter_response.dto.dart @@ -23,6 +23,22 @@ class CounterResponseDto extends Equatable { final DateTime? createdAt; final DateTime? updatedAt; + CounterResponseDto copyWith({ + String? sysId, + }) { + return CounterResponseDto( + sysId: sysId ?? this.sysId, + name: name, + counterValue: counterValue, + startValue: startValue, + stepSize: stepSize, + color: color, + goalValue: goalValue, + createdAt: createdAt, + updatedAt: updatedAt, + ); + } + @override // TODO: implement props List get props => [sysId, counterValue]; diff --git a/lib/src/features/counter/data/datasources/remote/src/mock/counter_fake.api.dart b/lib/src/features/counter/data/datasources/remote/src/mock/counter_fake.api.dart index 612b4e8..f659b77 100644 --- a/lib/src/features/counter/data/datasources/remote/src/mock/counter_fake.api.dart +++ b/lib/src/features/counter/data/datasources/remote/src/mock/counter_fake.api.dart @@ -60,8 +60,8 @@ class CounterFakeApi implements CounterApi { @override Future createCounter(CounterRequestDto counterRequestDto) { - counterRequestDto.sysId = '5'; - final dto = counterRequestDto as CounterResponseDto; + var dto = counterRequestDto as CounterResponseDto; + dto.copyWith(sysId: '5'); _counterList.add(dto); return Future.delayed(Duration(milliseconds: fakeApiDelay), () { return dto; diff --git a/lib/src/features/counter/domain/model/counter.model.dart b/lib/src/features/counter/domain/model/counter.model.dart index a94962d..655f7e7 100644 --- a/lib/src/features/counter/domain/model/counter.model.dart +++ b/lib/src/features/counter/domain/model/counter.model.dart @@ -3,7 +3,7 @@ import 'package:flutter/material.dart'; class CounterModel extends Equatable { const CounterModel({ - this.id, + this.id = '-1', required this.name, this.value = 0, this.stepSize = 1, @@ -13,7 +13,7 @@ class CounterModel extends Equatable { }); /// technical counter id - final String? id; + final String id; final String name; final int value; final int startValue; diff --git a/lib/src/features/counter/presentation/edit/bloc/edit_counter.bloc.dart b/lib/src/features/counter/presentation/edit/bloc/edit_counter.bloc.dart index d5c69a2..def3b59 100644 --- a/lib/src/features/counter/presentation/edit/bloc/edit_counter.bloc.dart +++ b/lib/src/features/counter/presentation/edit/bloc/edit_counter.bloc.dart @@ -22,6 +22,7 @@ class EditCounterBloc extends Bloc { debugPrint('INCREMENT: ${state.counterModel.toString()}'); final newCounterModel = state.counterModel.copyWith(value: state.counterModel.value + 1); emit(EditCounterState(counterModel: newCounterModel)); + counterRepository.updateCounter(id: state.counterModel.id, counterModel: newCounterModel); } Future _onDecrement(CounterDecrementPressed event, Emitter emit) async { From 788adda509099e82fd90e079522358d3ea08d67a Mon Sep 17 00:00:00 2001 From: Jan Marsh Date: Mon, 12 Sep 2022 22:01:27 +0200 Subject: [PATCH 10/18] fixing crud --- .../counter/presentation/edit/bloc/edit_counter.bloc.dart | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/src/features/counter/presentation/edit/bloc/edit_counter.bloc.dart b/lib/src/features/counter/presentation/edit/bloc/edit_counter.bloc.dart index def3b59..d5c69a2 100644 --- a/lib/src/features/counter/presentation/edit/bloc/edit_counter.bloc.dart +++ b/lib/src/features/counter/presentation/edit/bloc/edit_counter.bloc.dart @@ -22,7 +22,6 @@ class EditCounterBloc extends Bloc { debugPrint('INCREMENT: ${state.counterModel.toString()}'); final newCounterModel = state.counterModel.copyWith(value: state.counterModel.value + 1); emit(EditCounterState(counterModel: newCounterModel)); - counterRepository.updateCounter(id: state.counterModel.id, counterModel: newCounterModel); } Future _onDecrement(CounterDecrementPressed event, Emitter emit) async { From 4e711304d6443676affa1a66d1f41056ee67ad0e Mon Sep 17 00:00:00 2001 From: Jan Marsh Date: Mon, 12 Sep 2022 22:46:27 +0200 Subject: [PATCH 11/18] fix format --- lib/src/core/routing/router.dart | 6 +++--- .../presentation/dashboard/view/widgets/counter_grid.dart | 1 - .../counter/presentation/edit/view/edit_counter.page.dart | 1 - 3 files changed, 3 insertions(+), 5 deletions(-) diff --git a/lib/src/core/routing/router.dart b/lib/src/core/routing/router.dart index 616a080..202b0b3 100644 --- a/lib/src/core/routing/router.dart +++ b/lib/src/core/routing/router.dart @@ -15,9 +15,9 @@ final router = GoRouter( GoRoute( path: '/counters/new', pageBuilder: (context, state) => const MaterialPage( - fullscreenDialog: true, - child: EditCounterPage(), - ), + fullscreenDialog: true, + child: EditCounterPage(), + ), ), GoRoute( path: '/counters/:id', diff --git a/lib/src/features/counter/presentation/dashboard/view/widgets/counter_grid.dart b/lib/src/features/counter/presentation/dashboard/view/widgets/counter_grid.dart index b9ff9f1..931699e 100644 --- a/lib/src/features/counter/presentation/dashboard/view/widgets/counter_grid.dart +++ b/lib/src/features/counter/presentation/dashboard/view/widgets/counter_grid.dart @@ -1,5 +1,4 @@ import 'package:counter_workshop/src/features/counter/domain/model/counter.model.dart'; -import 'package:counter_workshop/src/features/counter/presentation/edit/view/edit_counter.page.dart'; import 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; diff --git a/lib/src/features/counter/presentation/edit/view/edit_counter.page.dart b/lib/src/features/counter/presentation/edit/view/edit_counter.page.dart index 35c9177..b1d7606 100644 --- a/lib/src/features/counter/presentation/edit/view/edit_counter.page.dart +++ b/lib/src/features/counter/presentation/edit/view/edit_counter.page.dart @@ -17,7 +17,6 @@ class EditCounterPage extends StatelessWidget { return BlocProvider( create: (context) => EditCounterBloc( counterRepository: context.read(), - counterModel: counterModel, counterId: counterId, ), child: const CounterView(), From 661ccaf2876d732dbe7a834fac9bef67b3898ed1 Mon Sep 17 00:00:00 2001 From: Jan Marsh Date: Tue, 13 Sep 2022 09:09:03 +0200 Subject: [PATCH 12/18] extracting error-widget --- .../core/widgets/error_message.widget.dart | 23 +++++++++++++++++++ .../dashboard/view/dashboard.page.dart | 11 ++------- 2 files changed, 25 insertions(+), 9 deletions(-) create mode 100644 lib/src/core/widgets/error_message.widget.dart diff --git a/lib/src/core/widgets/error_message.widget.dart b/lib/src/core/widgets/error_message.widget.dart new file mode 100644 index 0000000..8f2c14d --- /dev/null +++ b/lib/src/core/widgets/error_message.widget.dart @@ -0,0 +1,23 @@ +import 'package:flutter/material.dart'; + +class ErrorMessage extends StatelessWidget { + final Object error; + + const ErrorMessage({ + required this.error, + Key? key, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return Center( + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Text( + 'Es ist ein Fehler aufgetreten: ${error.toString()}', + style: const TextStyle(color: Colors.red), + ), + ), + ); + } +} diff --git a/lib/src/features/counter/presentation/dashboard/view/dashboard.page.dart b/lib/src/features/counter/presentation/dashboard/view/dashboard.page.dart index 2e79b9f..b63a0f1 100644 --- a/lib/src/features/counter/presentation/dashboard/view/dashboard.page.dart +++ b/lib/src/features/counter/presentation/dashboard/view/dashboard.page.dart @@ -1,3 +1,4 @@ +import 'package:counter_workshop/src/core/widgets/error_message.widget.dart'; import 'package:counter_workshop/src/features/counter/data/repositories/counter.repository.dart'; import 'package:counter_workshop/src/features/counter/presentation/dashboard/bloc/dashboard.bloc.dart'; import 'package:counter_workshop/src/features/counter/presentation/dashboard/bloc/dashboard.event.dart'; @@ -43,15 +44,7 @@ class _DashboardView extends StatelessWidget { return CounterGrid(counterList: state.counterList, columnCount: 2); } else if (state is DashboardErrorState) { // error - return Center( - child: Padding( - padding: const EdgeInsets.all(8.0), - child: Text( - 'Es ist ein Fehler aufgetreten: ${state.error}', - style: const TextStyle(color: Colors.red), - ), - ), - ); + return ErrorMessage(error: state.error); } // state unknown, fallback to empty or return a common error return const SizedBox(); From d20b3a478e106f680aefd8d372e8c276d7f0bd48 Mon Sep 17 00:00:00 2001 From: Jan Marsh Date: Tue, 13 Sep 2022 12:20:16 +0200 Subject: [PATCH 13/18] Counter with goRouter with deepLinking --- Makefile | 7 +- android/app/src/main/AndroidManifest.xml | 41 ++++----- ios/Runner/Info.plist | 17 ++++ lib/src/core/routing/router.dart | 34 ++++---- lib/src/core/theme/app.theme.dart | 4 +- .../core/widgets/error_message.widget.dart | 4 +- .../dashboard/view/dashboard.page.dart | 7 ++ .../edit/bloc/edit_counter.bloc.dart | 29 ++++--- .../edit/bloc/edit_counter.event.dart | 21 ++++- .../edit/bloc/edit_counter.state.dart | 35 ++++++-- .../edit/view/edit_counter.page.dart | 87 ++++++++++++------- 11 files changed, 187 insertions(+), 99 deletions(-) diff --git a/Makefile b/Makefile index 0596fb6..092fe22 100644 --- a/Makefile +++ b/Makefile @@ -96,9 +96,6 @@ l10n: appicon: flutter pub run flutter_launcher_icons:main -f flutter_launcher_icons.yaml deeplink: - @printf "Android:\nadb shell am start -a android.intent.action.VIEW -c andrmoid.intent.category.BROWSABLE -d de.coodoo.counter://settings" + @printf "Android:\nadb shell am start -a android.intent.action.VIEW -c android.intent.category.BROWSABLE -d 'https://counter.de/counters/2'" @printf "\n\n" - @printf "iOS:\nxcrun simctl openurl booted de.coodoo.counter://settings" - - - + @printf "iOS:\nxcrun simctl openurl booted counter:///counters/2" diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 7484486..f419e05 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -1,35 +1,26 @@ - + - - + + - - - - + + + + + + + + + + + - + - + \ No newline at end of file diff --git a/ios/Runner/Info.plist b/ios/Runner/Info.plist index abad461..6c54137 100644 --- a/ios/Runner/Info.plist +++ b/ios/Runner/Info.plist @@ -47,5 +47,22 @@ UIApplicationSupportsIndirectInputEvents + + FlutterDeepLinkingEnabled + + CFBundleURLTypes + + + CFBundleTypeRole + Editor + CFBundleURLName + counter.de + CFBundleURLSchemes + + de.coodoo.counter + counter + + + diff --git a/lib/src/core/routing/router.dart b/lib/src/core/routing/router.dart index 202b0b3..a46adfb 100644 --- a/lib/src/core/routing/router.dart +++ b/lib/src/core/routing/router.dart @@ -6,25 +6,27 @@ import 'package:go_router/go_router.dart'; final router = GoRouter( urlPathStrategy: UrlPathStrategy.path, debugLogDiagnostics: true, - initialLocation: '/dashboard', + initialLocation: '/counters', routes: [ GoRoute( - path: '/dashboard', + path: '/counters', builder: (context, state) => const DashboardPage(), - ), - GoRoute( - path: '/counters/new', - pageBuilder: (context, state) => const MaterialPage( - fullscreenDialog: true, - child: EditCounterPage(), - ), - ), - GoRoute( - path: '/counters/:id', - builder: (context, state) { - final counterId = state.params['id']; - return EditCounterPage(counterId: counterId); - }, + routes: [ + GoRoute( + path: 'new', + pageBuilder: (context, state) => const MaterialPage( + fullscreenDialog: true, + child: EditCounterPage(), + ), + ), + GoRoute( + path: ':id', + builder: (context, state) { + final counterId = state.params['id']; + return EditCounterPage(counterId: counterId!); + }, + ), + ], ), ], ); diff --git a/lib/src/core/theme/app.theme.dart b/lib/src/core/theme/app.theme.dart index c7ddf20..8476cec 100644 --- a/lib/src/core/theme/app.theme.dart +++ b/lib/src/core/theme/app.theme.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; class AppTheme { // Light Mode @@ -25,11 +26,11 @@ class AppTheme { final currentHeadlineColor = isLightMode ? headlineColor : headlineColorDark; return base.copyWith( - brightness: isLightMode ? Brightness.light : Brightness.dark, useMaterial3: true, primaryColor: currentPrimaryColor, scaffoldBackgroundColor: isLightMode ? scaffoldColor : scaffoldColorDark, appBarTheme: base.appBarTheme.copyWith( + systemOverlayStyle: isLightMode ? SystemUiOverlayStyle.dark : SystemUiOverlayStyle.light, backgroundColor: Colors.transparent, foregroundColor: currentPrimaryColor, titleTextStyle: TextStyle( @@ -38,6 +39,7 @@ class AppTheme { color: currentPrimaryColor, ), ), + floatingActionButtonTheme: base.floatingActionButtonTheme.copyWith(backgroundColor: primaryColor), outlinedButtonTheme: OutlinedButtonThemeData( style: OutlinedButton.styleFrom( side: BorderSide(width: 2.0, color: currentHeadlineColor), diff --git a/lib/src/core/widgets/error_message.widget.dart b/lib/src/core/widgets/error_message.widget.dart index 8f2c14d..c8a2f77 100644 --- a/lib/src/core/widgets/error_message.widget.dart +++ b/lib/src/core/widgets/error_message.widget.dart @@ -14,8 +14,8 @@ class ErrorMessage extends StatelessWidget { child: Padding( padding: const EdgeInsets.all(8.0), child: Text( - 'Es ist ein Fehler aufgetreten: ${error.toString()}', - style: const TextStyle(color: Colors.red), + 'Es ist ein Fehler aufgetreten:\n${error.toString()}', + style: const TextStyle(color: Colors.red, height: 1.5), ), ), ); diff --git a/lib/src/features/counter/presentation/dashboard/view/dashboard.page.dart b/lib/src/features/counter/presentation/dashboard/view/dashboard.page.dart index b63a0f1..2eca28b 100644 --- a/lib/src/features/counter/presentation/dashboard/view/dashboard.page.dart +++ b/lib/src/features/counter/presentation/dashboard/view/dashboard.page.dart @@ -6,6 +6,7 @@ import 'package:counter_workshop/src/features/counter/presentation/dashboard/blo import 'package:counter_workshop/src/features/counter/presentation/dashboard/view/widgets/counter_grid.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:go_router/go_router.dart'; /// bloc class DashboardPage extends StatelessWidget { @@ -50,6 +51,12 @@ class _DashboardView extends StatelessWidget { return const SizedBox(); }, ), + floatingActionButton: FloatingActionButton( + child: const Icon(Icons.add), + onPressed: () { + context.go('/counters/new'); + }, + ), ); } } diff --git a/lib/src/features/counter/presentation/edit/bloc/edit_counter.bloc.dart b/lib/src/features/counter/presentation/edit/bloc/edit_counter.bloc.dart index d5c69a2..1bdadb2 100644 --- a/lib/src/features/counter/presentation/edit/bloc/edit_counter.bloc.dart +++ b/lib/src/features/counter/presentation/edit/bloc/edit_counter.bloc.dart @@ -1,7 +1,6 @@ import 'dart:async'; import 'package:counter_workshop/src/features/counter/data/repositories/counter.repository.dart'; -import 'package:counter_workshop/src/features/counter/domain/model/counter.model.dart'; import 'package:counter_workshop/src/features/counter/presentation/edit/bloc/edit_counter.event.dart'; import 'package:counter_workshop/src/features/counter/presentation/edit/bloc/edit_counter.state.dart'; import 'package:flutter/foundation.dart'; @@ -10,27 +9,35 @@ import 'package:flutter_bloc/flutter_bloc.dart'; class EditCounterBloc extends Bloc { final CounterRepository counterRepository; - EditCounterBloc({required this.counterRepository, required CounterModel counterModel}) - : super( - EditCounterState(counterModel: counterModel), - ) { + EditCounterBloc({required this.counterRepository, required String? counterId}) : super(const EditCounterInitial()) { + on(_onFetchCounter); on(_onIncrement); on(_onDecrement); } + Future> _onFetchCounter(FetchCounter event, Emitter emit) async { + try { + emit(const EditCounterLoading()); + final counterModel = await counterRepository.getCounter(id: event.counterId); + emit(EditCounterData(counterModel)); + } catch (e) { + emit(EditCounterError(e.toString())); + } + } + Future _onIncrement(CounterIncrementPressed event, Emitter emit) async { - debugPrint('INCREMENT: ${state.counterModel.toString()}'); - final newCounterModel = state.counterModel.copyWith(value: state.counterModel.value + 1); - emit(EditCounterState(counterModel: newCounterModel)); + debugPrint('INCREMENT: ${event.counterModel.toString()}'); + final newCounterModel = event.counterModel.copyWith(value: event.counterModel.value + 1); + emit(EditCounterData(newCounterModel)); } Future _onDecrement(CounterDecrementPressed event, Emitter emit) async { - debugPrint('DECREMENT: ${state.counterModel.toString()}'); + debugPrint('DECREMENT: ${event.counterModel.toString()}'); if (event.counterModel.value == 0) { return; } - final newCounterModel = state.counterModel.copyWith(value: state.counterModel.value - 1); - emit(EditCounterState(counterModel: newCounterModel)); + final newCounterModel = event.counterModel.copyWith(value: event.counterModel.value - 1); + emit(EditCounterData(newCounterModel)); } } diff --git a/lib/src/features/counter/presentation/edit/bloc/edit_counter.event.dart b/lib/src/features/counter/presentation/edit/bloc/edit_counter.event.dart index a6f7e51..722916d 100644 --- a/lib/src/features/counter/presentation/edit/bloc/edit_counter.event.dart +++ b/lib/src/features/counter/presentation/edit/bloc/edit_counter.event.dart @@ -8,11 +8,29 @@ abstract class EditCounterEvent extends Equatable { List get props => []; } +class CounterLoading extends EditCounterEvent {} + +class CounterData extends EditCounterEvent { + const CounterData(this.counterModel); + final CounterModel counterModel; + @override + List get props => [counterModel]; +} + +class CounterError extends EditCounterEvent {} + +/// Notifies bloc to increment state +class FetchCounter extends EditCounterEvent { + const FetchCounter(this.counterId); + final String counterId; + @override + List get props => [counterId]; +} + /// Notifies bloc to increment state class CounterIncrementPressed extends EditCounterEvent { const CounterIncrementPressed(this.counterModel); final CounterModel counterModel; - @override List get props => [counterModel]; } @@ -21,7 +39,6 @@ class CounterIncrementPressed extends EditCounterEvent { class CounterDecrementPressed extends EditCounterEvent { const CounterDecrementPressed(this.counterModel); final CounterModel counterModel; - @override List get props => [counterModel]; } diff --git a/lib/src/features/counter/presentation/edit/bloc/edit_counter.state.dart b/lib/src/features/counter/presentation/edit/bloc/edit_counter.state.dart index d238874..100eb04 100644 --- a/lib/src/features/counter/presentation/edit/bloc/edit_counter.state.dart +++ b/lib/src/features/counter/presentation/edit/bloc/edit_counter.state.dart @@ -1,17 +1,36 @@ import 'package:counter_workshop/src/features/counter/domain/model/counter.model.dart'; import 'package:equatable/equatable.dart'; +import 'package:flutter/foundation.dart'; -class EditCounterState extends Equatable { - const EditCounterState({required this.counterModel}); +@immutable +abstract class EditCounterState extends Equatable { + const EditCounterState(); + @override + List get props => []; +} - final CounterModel counterModel; +/// The initial Counter State +class EditCounterInitial extends EditCounterState { + const EditCounterInitial(); +} - EditCounterState copyWith({ - CounterModel? counterModel, - }) { - return EditCounterState(counterModel: counterModel ?? this.counterModel); - } +/// State indicating that data is being loaded +class EditCounterLoading extends EditCounterState { + const EditCounterLoading(); +} +/// State indicating that data was loaded +class EditCounterData extends EditCounterState { + final CounterModel counterModel; + const EditCounterData(this.counterModel); @override List get props => [counterModel]; } + +/// Error counter State +class EditCounterError extends EditCounterState { + final String error; + const EditCounterError(this.error); + @override + List get props => [error]; +} diff --git a/lib/src/features/counter/presentation/edit/view/edit_counter.page.dart b/lib/src/features/counter/presentation/edit/view/edit_counter.page.dart index b1d7606..fa14db5 100644 --- a/lib/src/features/counter/presentation/edit/view/edit_counter.page.dart +++ b/lib/src/features/counter/presentation/edit/view/edit_counter.page.dart @@ -1,7 +1,9 @@ +import 'package:counter_workshop/src/core/widgets/custom_loading_indicator.widget.dart'; +import 'package:counter_workshop/src/core/widgets/error_message.widget.dart'; import 'package:counter_workshop/src/features/counter/data/repositories/counter.repository.dart'; -import 'package:counter_workshop/src/features/counter/domain/model/counter.model.dart'; import 'package:counter_workshop/src/features/counter/presentation/edit/bloc/edit_counter.bloc.dart'; import 'package:counter_workshop/src/features/counter/presentation/edit/bloc/edit_counter.event.dart'; +import 'package:counter_workshop/src/features/counter/presentation/edit/bloc/edit_counter.state.dart'; import 'package:counter_workshop/src/features/counter/presentation/edit/view/widgets/counter_text.widget.dart'; import 'package:counter_workshop/src/features/counter/presentation/edit/view/widgets/custom_circular_button.widget.dart'; import 'package:flutter/material.dart'; @@ -9,16 +11,21 @@ import 'package:flutter_bloc/flutter_bloc.dart'; /// bloc class EditCounterPage extends StatelessWidget { - const EditCounterPage({this.counterId, this.counterModel, super.key}); + const EditCounterPage({this.counterId, super.key}); final String? counterId; - final CounterModel? counterModel; @override Widget build(BuildContext context) { + final bloc = EditCounterBloc( + counterRepository: context.read(), + counterId: counterId, + ); + // Fetch data if counterId is provider + if (counterId != null) { + bloc.add(FetchCounter(counterId!)); + } + return BlocProvider( - create: (context) => EditCounterBloc( - counterRepository: context.read(), - counterId: counterId, - ), + create: (context) => bloc, child: const CounterView(), ); } @@ -32,37 +39,59 @@ class CounterView extends StatelessWidget { Widget build(BuildContext context) { final theme = Theme.of(context); final editCounterBloc = context.watch(); - final counterModel = editCounterBloc.state.counterModel; return Scaffold( extendBodyBehindAppBar: true, appBar: AppBar( - title: const Text('Counter Page'), - ), - body: Center( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - CounterText(counterValue: counterModel.value), - Text(counterModel.name, style: theme.textTheme.caption), - ], + title: BlocBuilder( + builder: (context, state) { + return state is EditCounterData ? Text(state.counterModel.name) : const Text(''); + }, ), ), + body: BlocBuilder( + builder: (context, state) { + if (state is EditCounterLoading) { + return const CustomLoadingIndicator(); + } else if (state is EditCounterData) { + return Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + CounterText(counterValue: state.counterModel.value), + Text(state.counterModel.name, style: theme.textTheme.caption), + ], + ), + ); + } else if (state is EditCounterError) { + return ErrorMessage(error: state.error); + } + return const SizedBox(); + }, + ), floatingActionButtonLocation: FloatingActionButtonLocation.centerFloat, floatingActionButton: Container( padding: const EdgeInsets.symmetric(vertical: 5, horizontal: 40.0), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - CustomCircularButton( - icon: Icons.remove, - onPressed: () => editCounterBloc.add(CounterDecrementPressed(counterModel)), - ), - CustomCircularButton( - icon: Icons.add, - onPressed: () => editCounterBloc.add(CounterIncrementPressed(counterModel)), - ), - ], + child: BlocBuilder( + builder: (context, state) { + return Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + CustomCircularButton( + icon: Icons.remove, + onPressed: state is EditCounterData + ? () => editCounterBloc.add(CounterDecrementPressed(state.counterModel)) + : null, + ), + CustomCircularButton( + icon: Icons.add, + onPressed: state is EditCounterData + ? () => editCounterBloc.add(CounterIncrementPressed(state.counterModel)) + : null, + ), + ], + ); + }, ), ), ); From baab315b649785a55a6825fc17806286f63d1f19 Mon Sep 17 00:00:00 2001 From: Jan Marsh Date: Wed, 14 Sep 2022 10:59:54 +0200 Subject: [PATCH 14/18] removing database-layer --- .../counter/data/repositories/counter.repository.dart | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/lib/src/features/counter/data/repositories/counter.repository.dart b/lib/src/features/counter/data/repositories/counter.repository.dart index d38aa77..0927222 100644 --- a/lib/src/features/counter/data/repositories/counter.repository.dart +++ b/lib/src/features/counter/data/repositories/counter.repository.dart @@ -10,10 +10,7 @@ import 'dart:developer'; import 'package:counter_workshop/src/features/counter/domain/repository/counter.repository_interface.dart'; class CounterRepository implements CounterRepositoryInterface { - CounterRepository({required this.counterApi}) { - // prefill repository Counter from API - getCounterList(); - } + const CounterRepository({required this.counterApi}); final CounterApi counterApi; From 1641b855a31714395ee13aa133b5dfda84db4a90 Mon Sep 17 00:00:00 2001 From: Jan Marsh Date: Wed, 14 Sep 2022 17:13:43 +0200 Subject: [PATCH 15/18] Adding State refresh --- lib/src/app.dart | 25 ++++++++++++++++--- .../dashboard/view/dashboard.page.dart | 16 ------------ .../edit/bloc/edit_counter.bloc.dart | 2 ++ .../edit/view/edit_counter.page.dart | 23 ++++++++++++++++- 4 files changed, 46 insertions(+), 20 deletions(-) diff --git a/lib/src/app.dart b/lib/src/app.dart index c2a3138..a494fd0 100644 --- a/lib/src/app.dart +++ b/lib/src/app.dart @@ -1,19 +1,38 @@ import 'package:counter_workshop/src/core/routing/router.dart'; import 'package:counter_workshop/src/core/theme/app.theme.dart'; import 'package:counter_workshop/src/features/counter/data/repositories/counter.repository.dart'; +import 'package:counter_workshop/src/features/counter/presentation/dashboard/bloc/dashboard.bloc.dart'; +import 'package:counter_workshop/src/features/counter/presentation/dashboard/bloc/dashboard.event.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; -class App extends StatelessWidget { +class App extends StatefulWidget { const App({required this.counterRepository, super.key}); final CounterRepository counterRepository; + @override + State createState() => _AppState(); +} + +class _AppState extends State { + late final DashboardBloc dashboardBloc; + + @override + void initState() { + dashboardBloc = DashboardBloc(counterRepository: widget.counterRepository); + dashboardBloc.add(FetchCounterList()); + super.initState(); + } + @override Widget build(BuildContext context) { return RepositoryProvider.value( - value: counterRepository, - child: const AppView(), + value: widget.counterRepository, + child: BlocProvider.value( + value: dashboardBloc, + child: const AppView(), + ), ); } } diff --git a/lib/src/features/counter/presentation/dashboard/view/dashboard.page.dart b/lib/src/features/counter/presentation/dashboard/view/dashboard.page.dart index 2eca28b..0e3d57a 100644 --- a/lib/src/features/counter/presentation/dashboard/view/dashboard.page.dart +++ b/lib/src/features/counter/presentation/dashboard/view/dashboard.page.dart @@ -1,7 +1,5 @@ import 'package:counter_workshop/src/core/widgets/error_message.widget.dart'; -import 'package:counter_workshop/src/features/counter/data/repositories/counter.repository.dart'; import 'package:counter_workshop/src/features/counter/presentation/dashboard/bloc/dashboard.bloc.dart'; -import 'package:counter_workshop/src/features/counter/presentation/dashboard/bloc/dashboard.event.dart'; import 'package:counter_workshop/src/features/counter/presentation/dashboard/bloc/dashboard.state.dart'; import 'package:counter_workshop/src/features/counter/presentation/dashboard/view/widgets/counter_grid.dart'; import 'package:flutter/material.dart'; @@ -12,20 +10,6 @@ import 'package:go_router/go_router.dart'; class DashboardPage extends StatelessWidget { const DashboardPage({super.key}); - @override - Widget build(BuildContext context) { - final counterRepository = context.read(); - return BlocProvider( - create: (_) => DashboardBloc(counterRepository: counterRepository)..add(FetchCounterList()), - child: const _DashboardView(), - ); - } -} - -/// actual counter page -class _DashboardView extends StatelessWidget { - const _DashboardView(); - @override Widget build(BuildContext context) { return Scaffold( diff --git a/lib/src/features/counter/presentation/edit/bloc/edit_counter.bloc.dart b/lib/src/features/counter/presentation/edit/bloc/edit_counter.bloc.dart index 1bdadb2..9c196fe 100644 --- a/lib/src/features/counter/presentation/edit/bloc/edit_counter.bloc.dart +++ b/lib/src/features/counter/presentation/edit/bloc/edit_counter.bloc.dart @@ -29,6 +29,7 @@ class EditCounterBloc extends Bloc { debugPrint('INCREMENT: ${event.counterModel.toString()}'); final newCounterModel = event.counterModel.copyWith(value: event.counterModel.value + 1); emit(EditCounterData(newCounterModel)); + await counterRepository.updateCounter(id: event.counterModel.id, counterModel: newCounterModel); } Future _onDecrement(CounterDecrementPressed event, Emitter emit) async { @@ -39,5 +40,6 @@ class EditCounterBloc extends Bloc { } final newCounterModel = event.counterModel.copyWith(value: event.counterModel.value - 1); emit(EditCounterData(newCounterModel)); + await counterRepository.updateCounter(id: event.counterModel.id, counterModel: newCounterModel); } } diff --git a/lib/src/features/counter/presentation/edit/view/edit_counter.page.dart b/lib/src/features/counter/presentation/edit/view/edit_counter.page.dart index fa14db5..3b8fbda 100644 --- a/lib/src/features/counter/presentation/edit/view/edit_counter.page.dart +++ b/lib/src/features/counter/presentation/edit/view/edit_counter.page.dart @@ -1,6 +1,10 @@ +import 'dart:developer'; + import 'package:counter_workshop/src/core/widgets/custom_loading_indicator.widget.dart'; import 'package:counter_workshop/src/core/widgets/error_message.widget.dart'; import 'package:counter_workshop/src/features/counter/data/repositories/counter.repository.dart'; +import 'package:counter_workshop/src/features/counter/presentation/dashboard/bloc/dashboard.bloc.dart'; +import 'package:counter_workshop/src/features/counter/presentation/dashboard/bloc/dashboard.event.dart'; import 'package:counter_workshop/src/features/counter/presentation/edit/bloc/edit_counter.bloc.dart'; import 'package:counter_workshop/src/features/counter/presentation/edit/bloc/edit_counter.event.dart'; import 'package:counter_workshop/src/features/counter/presentation/edit/bloc/edit_counter.state.dart'; @@ -8,6 +12,7 @@ import 'package:counter_workshop/src/features/counter/presentation/edit/view/wid import 'package:counter_workshop/src/features/counter/presentation/edit/view/widgets/custom_circular_button.widget.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:go_router/go_router.dart'; /// bloc class EditCounterPage extends StatelessWidget { @@ -49,7 +54,23 @@ class CounterView extends StatelessWidget { }, ), ), - body: BlocBuilder( + body: BlocConsumer( + listenWhen: (previous, current) { + if (previous is EditCounterData && current is EditCounterData) { + if (previous.counterModel.value != current.counterModel.value) { + return true; + } + } + return false; + }, + listener: (context, state) { + if (state is EditCounterData) { + // Calling DashboardBloc (MasterPage) from EditCounterBloc (DetailPage) + log('EditBlocListener: ${state.counterModel.value}'); + final dashboardBloc = context.read(); + dashboardBloc.add(FetchCounterList()); + } + }, builder: (context, state) { if (state is EditCounterLoading) { return const CustomLoadingIndicator(); From 470e8fc34590c6bf8a62ba1ccf8942df54286e4a Mon Sep 17 00:00:00 2001 From: Jan Marsh Date: Wed, 14 Sep 2022 17:20:05 +0200 Subject: [PATCH 16/18] Navigator 1 with counterId instead of model --- lib/src/app.dart | 8 ++--- lib/src/core/routing/router.dart | 32 ------------------- .../dashboard/view/dashboard.page.dart | 4 +-- .../dashboard/view/widgets/counter_grid.dart | 4 +-- .../edit/view/edit_counter.page.dart | 16 ++++++++-- pubspec.lock | 14 +------- pubspec.yaml | 1 - 7 files changed, 22 insertions(+), 57 deletions(-) delete mode 100644 lib/src/core/routing/router.dart diff --git a/lib/src/app.dart b/lib/src/app.dart index a494fd0..f985d20 100644 --- a/lib/src/app.dart +++ b/lib/src/app.dart @@ -1,8 +1,8 @@ -import 'package:counter_workshop/src/core/routing/router.dart'; import 'package:counter_workshop/src/core/theme/app.theme.dart'; import 'package:counter_workshop/src/features/counter/data/repositories/counter.repository.dart'; import 'package:counter_workshop/src/features/counter/presentation/dashboard/bloc/dashboard.bloc.dart'; import 'package:counter_workshop/src/features/counter/presentation/dashboard/bloc/dashboard.event.dart'; +import 'package:counter_workshop/src/features/counter/presentation/dashboard/view/dashboard.page.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; @@ -45,14 +45,12 @@ class AppView extends StatelessWidget { @override Widget build(BuildContext context) { final appTheme = AppTheme(); - return MaterialApp.router( + return MaterialApp( title: 'Counter Demo', theme: appTheme.light, darkTheme: appTheme.dark, themeMode: ThemeMode.system, - routeInformationProvider: router.routeInformationProvider, - routeInformationParser: router.routeInformationParser, - routerDelegate: router.routerDelegate, + home: const DashboardPage(), ); } } diff --git a/lib/src/core/routing/router.dart b/lib/src/core/routing/router.dart deleted file mode 100644 index a46adfb..0000000 --- a/lib/src/core/routing/router.dart +++ /dev/null @@ -1,32 +0,0 @@ -import 'package:counter_workshop/src/features/counter/presentation/dashboard/view/dashboard.page.dart'; -import 'package:counter_workshop/src/features/counter/presentation/edit/view/edit_counter.page.dart'; -import 'package:flutter/material.dart'; -import 'package:go_router/go_router.dart'; - -final router = GoRouter( - urlPathStrategy: UrlPathStrategy.path, - debugLogDiagnostics: true, - initialLocation: '/counters', - routes: [ - GoRoute( - path: '/counters', - builder: (context, state) => const DashboardPage(), - routes: [ - GoRoute( - path: 'new', - pageBuilder: (context, state) => const MaterialPage( - fullscreenDialog: true, - child: EditCounterPage(), - ), - ), - GoRoute( - path: ':id', - builder: (context, state) { - final counterId = state.params['id']; - return EditCounterPage(counterId: counterId!); - }, - ), - ], - ), - ], -); diff --git a/lib/src/features/counter/presentation/dashboard/view/dashboard.page.dart b/lib/src/features/counter/presentation/dashboard/view/dashboard.page.dart index 0e3d57a..bda3324 100644 --- a/lib/src/features/counter/presentation/dashboard/view/dashboard.page.dart +++ b/lib/src/features/counter/presentation/dashboard/view/dashboard.page.dart @@ -2,9 +2,9 @@ import 'package:counter_workshop/src/core/widgets/error_message.widget.dart'; import 'package:counter_workshop/src/features/counter/presentation/dashboard/bloc/dashboard.bloc.dart'; import 'package:counter_workshop/src/features/counter/presentation/dashboard/bloc/dashboard.state.dart'; import 'package:counter_workshop/src/features/counter/presentation/dashboard/view/widgets/counter_grid.dart'; +import 'package:counter_workshop/src/features/counter/presentation/edit/view/edit_counter.page.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:go_router/go_router.dart'; /// bloc class DashboardPage extends StatelessWidget { @@ -38,7 +38,7 @@ class DashboardPage extends StatelessWidget { floatingActionButton: FloatingActionButton( child: const Icon(Icons.add), onPressed: () { - context.go('/counters/new'); + Navigator.push(context, EditCounterPage.route(fullscreen: true)); }, ), ); diff --git a/lib/src/features/counter/presentation/dashboard/view/widgets/counter_grid.dart b/lib/src/features/counter/presentation/dashboard/view/widgets/counter_grid.dart index 931699e..c2c0516 100644 --- a/lib/src/features/counter/presentation/dashboard/view/widgets/counter_grid.dart +++ b/lib/src/features/counter/presentation/dashboard/view/widgets/counter_grid.dart @@ -1,6 +1,6 @@ import 'package:counter_workshop/src/features/counter/domain/model/counter.model.dart'; +import 'package:counter_workshop/src/features/counter/presentation/edit/view/edit_counter.page.dart'; import 'package:flutter/material.dart'; -import 'package:go_router/go_router.dart'; class CounterGrid extends StatelessWidget { const CounterGrid({ @@ -26,7 +26,7 @@ class CounterGrid extends StatelessWidget { final counterModel = counterList[index]; return Card( child: InkWell( - onTap: () => context.push('/counters/${counterModel.id}'), + onTap: () => Navigator.push(context, EditCounterPage.route(counterId: counterModel.id)), child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ diff --git a/lib/src/features/counter/presentation/edit/view/edit_counter.page.dart b/lib/src/features/counter/presentation/edit/view/edit_counter.page.dart index 3b8fbda..386a5fe 100644 --- a/lib/src/features/counter/presentation/edit/view/edit_counter.page.dart +++ b/lib/src/features/counter/presentation/edit/view/edit_counter.page.dart @@ -12,12 +12,24 @@ import 'package:counter_workshop/src/features/counter/presentation/edit/view/wid import 'package:counter_workshop/src/features/counter/presentation/edit/view/widgets/custom_circular_button.widget.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:go_router/go_router.dart'; -/// bloc class EditCounterPage extends StatelessWidget { const EditCounterPage({this.counterId, super.key}); final String? counterId; + + static Route route({String? counterId, bool fullscreen = false}) { + return MaterialPageRoute( + fullscreenDialog: fullscreen, + builder: (context) => BlocProvider( + create: (context) => EditCounterBloc( + counterRepository: context.read(), + counterId: counterId, + ), + child: const EditCounterPage(), + ), + ); + } + @override Widget build(BuildContext context) { final bloc = EditCounterBloc( diff --git a/pubspec.lock b/pubspec.lock index a72db10..f418633 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -214,11 +214,6 @@ packages: description: flutter source: sdk version: "0.0.0" - flutter_web_plugins: - dependency: transitive - description: flutter - source: sdk - version: "0.0.0" frontend_server_client: dependency: transitive description: @@ -233,13 +228,6 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "2.1.0" - go_router: - dependency: "direct main" - description: - name: go_router - url: "https://pub.dartlang.org" - source: hosted - version: "4.4.1" graphs: dependency: transitive description: @@ -492,4 +480,4 @@ packages: version: "3.1.1" sdks: dart: ">=2.18.0 <3.0.0" - flutter: ">=3.0.0" + flutter: ">=1.16.0" diff --git a/pubspec.yaml b/pubspec.yaml index 43377bb..8f03ea3 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -16,7 +16,6 @@ dependencies: equatable: ^2.0.5 http: ^0.13.5 flutter_bloc: ^8.1.1 - go_router: ^4.4.1 dev_dependencies: flutter_test: From b62f1216620a81b8811c8c412389b04121fc9f55 Mon Sep 17 00:00:00 2001 From: Jan Marsh Date: Wed, 14 Sep 2022 17:23:32 +0200 Subject: [PATCH 17/18] fix counter --- .../counter/presentation/edit/view/edit_counter.page.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/src/features/counter/presentation/edit/view/edit_counter.page.dart b/lib/src/features/counter/presentation/edit/view/edit_counter.page.dart index 386a5fe..2e6a793 100644 --- a/lib/src/features/counter/presentation/edit/view/edit_counter.page.dart +++ b/lib/src/features/counter/presentation/edit/view/edit_counter.page.dart @@ -25,7 +25,7 @@ class EditCounterPage extends StatelessWidget { counterRepository: context.read(), counterId: counterId, ), - child: const EditCounterPage(), + child: EditCounterPage(counterId: counterId), ), ); } From 3211be95d85fe3446d4f5d6fdc251b1e0e097ac1 Mon Sep 17 00:00:00 2001 From: Jan Marsh Date: Wed, 14 Sep 2022 17:33:18 +0200 Subject: [PATCH 18/18] removing routing --- Makefile | 6 +----- android/app/src/main/AndroidManifest.xml | 11 ++--------- ios/Runner/Info.plist | 17 ----------------- 3 files changed, 3 insertions(+), 31 deletions(-) diff --git a/Makefile b/Makefile index 092fe22..42e5466 100644 --- a/Makefile +++ b/Makefile @@ -94,8 +94,4 @@ packages-upgrade: l10n: flutter gen-l10n appicon: - flutter pub run flutter_launcher_icons:main -f flutter_launcher_icons.yaml -deeplink: - @printf "Android:\nadb shell am start -a android.intent.action.VIEW -c android.intent.category.BROWSABLE -d 'https://counter.de/counters/2'" - @printf "\n\n" - @printf "iOS:\nxcrun simctl openurl booted counter:///counters/2" + flutter pub run flutter_launcher_icons:main -f flutter_launcher_icons.yaml \ No newline at end of file diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index f419e05..283a60f 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -7,20 +7,13 @@ while the Flutter UI initializes. After that, this theme continues to determine the Window background behind the Flutter UI. --> - - - + - - - - - - \ No newline at end of file + diff --git a/ios/Runner/Info.plist b/ios/Runner/Info.plist index 6c54137..abad461 100644 --- a/ios/Runner/Info.plist +++ b/ios/Runner/Info.plist @@ -47,22 +47,5 @@ UIApplicationSupportsIndirectInputEvents - - FlutterDeepLinkingEnabled - - CFBundleURLTypes - - - CFBundleTypeRole - Editor - CFBundleURLName - counter.de - CFBundleURLSchemes - - de.coodoo.counter - counter - - -