From ca0b58d86743ac5ec921d340fc82146418105a31 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lui=CC=81s=20Arteiro?= Date: Wed, 2 Aug 2023 14:43:42 +0100 Subject: [PATCH 01/17] refactor: Refactoring blocs. #339 --- lib/blocs/blocs.dart | 3 +++ lib/{bloc => blocs/todo}/todo_bloc.dart | 2 +- lib/{bloc => blocs/todo}/todo_event.dart | 0 lib/{bloc => blocs/todo}/todo_state.dart | 0 lib/main.dart | 4 ++-- test/bloc/todo_bloc_test.dart | 2 +- test/bloc/todo_event_test.dart | 2 +- test/bloc/todo_state_test.dart | 2 +- 8 files changed, 9 insertions(+), 6 deletions(-) create mode 100644 lib/blocs/blocs.dart rename lib/{bloc => blocs/todo}/todo_bloc.dart (98%) rename lib/{bloc => blocs/todo}/todo_event.dart (100%) rename lib/{bloc => blocs/todo}/todo_state.dart (100%) diff --git a/lib/blocs/blocs.dart b/lib/blocs/blocs.dart new file mode 100644 index 00000000..0a4dc2c5 --- /dev/null +++ b/lib/blocs/blocs.dart @@ -0,0 +1,3 @@ +export 'package:flutter_bloc/flutter_bloc.dart'; + +export 'todo/todo_bloc.dart'; \ No newline at end of file diff --git a/lib/bloc/todo_bloc.dart b/lib/blocs/todo/todo_bloc.dart similarity index 98% rename from lib/bloc/todo_bloc.dart rename to lib/blocs/todo/todo_bloc.dart index a5df09ab..b2057dff 100644 --- a/lib/bloc/todo_bloc.dart +++ b/lib/blocs/todo/todo_bloc.dart @@ -1,7 +1,7 @@ import 'package:equatable/equatable.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; -import '../models/item.dart'; +import '../../models/item.dart'; part 'todo_event.dart'; part 'todo_state.dart'; diff --git a/lib/bloc/todo_event.dart b/lib/blocs/todo/todo_event.dart similarity index 100% rename from lib/bloc/todo_event.dart rename to lib/blocs/todo/todo_event.dart diff --git a/lib/bloc/todo_state.dart b/lib/blocs/todo/todo_state.dart similarity index 100% rename from lib/bloc/todo_state.dart rename to lib/blocs/todo/todo_state.dart diff --git a/lib/main.dart b/lib/main.dart index ce1c4ec5..b0b2c159 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,10 +1,10 @@ import 'dart:async'; import 'package:flutter/material.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:responsive_framework/responsive_framework.dart'; -import 'bloc/todo_bloc.dart'; +import 'blocs/blocs.dart'; + import 'models/item.dart'; import 'models/stopwatch.dart'; diff --git a/test/bloc/todo_bloc_test.dart b/test/bloc/todo_bloc_test.dart index a05d3a3a..c8cd048d 100644 --- a/test/bloc/todo_bloc_test.dart +++ b/test/bloc/todo_bloc_test.dart @@ -1,5 +1,5 @@ import 'package:bloc_test/bloc_test.dart'; -import 'package:dwyl_todo/bloc/todo_bloc.dart'; +import 'package:dwyl_todo/blocs/blocs.dart'; import 'package:dwyl_todo/models/item.dart'; import 'package:flutter_test/flutter_test.dart'; diff --git a/test/bloc/todo_event_test.dart b/test/bloc/todo_event_test.dart index af1a155c..9242329f 100644 --- a/test/bloc/todo_event_test.dart +++ b/test/bloc/todo_event_test.dart @@ -1,5 +1,5 @@ +import 'package:dwyl_todo/blocs/blocs.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:dwyl_todo/bloc/todo_bloc.dart'; import 'package:dwyl_todo/models/item.dart'; void main() { diff --git a/test/bloc/todo_state_test.dart b/test/bloc/todo_state_test.dart index d624f882..757827fd 100644 --- a/test/bloc/todo_state_test.dart +++ b/test/bloc/todo_state_test.dart @@ -1,5 +1,5 @@ import 'package:flutter_test/flutter_test.dart'; -import 'package:dwyl_todo/bloc/todo_bloc.dart'; +import 'package:dwyl_todo/blocs/blocs.dart'; void main() { group('TodoState', () { From a9c8737ab48075d697ca3738c4bc02c6c856d9da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lui=CC=81s=20Arteiro?= Date: Wed, 2 Aug 2023 15:21:30 +0100 Subject: [PATCH 02/17] fix: Refactoring models. --- lib/main.dart | 3 +-- lib/models/models.dart | 2 ++ 2 files changed, 3 insertions(+), 2 deletions(-) create mode 100644 lib/models/models.dart diff --git a/lib/main.dart b/lib/main.dart index b0b2c159..eafcc4e0 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -4,9 +4,8 @@ import 'package:flutter/material.dart'; import 'package:responsive_framework/responsive_framework.dart'; import 'blocs/blocs.dart'; +import 'models/models.dart'; -import 'models/item.dart'; -import 'models/stopwatch.dart'; // Keys used for testing const textfieldKey = Key("textfieldKey"); diff --git a/lib/models/models.dart b/lib/models/models.dart new file mode 100644 index 00000000..d9a6700f --- /dev/null +++ b/lib/models/models.dart @@ -0,0 +1,2 @@ +export 'item.dart'; +export 'stopwatch.dart'; From 5e53895e5a105943e82f9085beb4071ab80b3d96 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lui=CC=81s=20Arteiro?= Date: Wed, 2 Aug 2023 16:00:57 +0100 Subject: [PATCH 03/17] refactor: Items refactor. --- lib/main.dart | 266 +------------------------ lib/presentation/widgets/items.dart | 276 ++++++++++++++++++++++++++ lib/presentation/widgets/widgets.dart | 1 + test/widget/widget_test.dart | 1 + 4 files changed, 279 insertions(+), 265 deletions(-) create mode 100644 lib/presentation/widgets/items.dart create mode 100644 lib/presentation/widgets/widgets.dart diff --git a/lib/main.dart b/lib/main.dart index eafcc4e0..411816fd 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -5,14 +5,13 @@ import 'package:responsive_framework/responsive_framework.dart'; import 'blocs/blocs.dart'; import 'models/models.dart'; +import 'presentation/widgets/widgets.dart'; // Keys used for testing const textfieldKey = Key("textfieldKey"); const textfieldOnNewPageKey = Key('textfieldOnNewPageKey'); const saveButtonKey = Key('saveButtonKey'); -const itemCardWidgetKey = Key('itemCardWidgetKey'); -const itemCardTimerButtonKey = Key('itemCardTimerButtonKey'); const backButtonKey = Key('backButtonKey'); const logoKey = Key('logoKey'); @@ -353,266 +352,3 @@ class NavigationBar extends StatelessWidget implements PreferredSizeWidget { Size get preferredSize => const Size.fromHeight(50); } -/// Widget pertaining to an item card. -/// It shows its info and changes state according to its progress. -class ItemCard extends StatefulWidget { - final Item item; - - const ItemCard({required this.item, super.key}); - - @override - State createState() => _ItemCardState(); -} - -class _ItemCardState extends State { - // Timer to be displayed - late TimerStopwatch _stopwatch; - - // Used to re-render the text showing the timer - late Timer _timer; - - @override - void initState() { - super.initState(); - WidgetsFlutterBinding.ensureInitialized(); - - _stopwatch = - TimerStopwatch(initialOffset: widget.item.getCumulativeDuration()); - - // Timer to rerender the page so the text shows the seconds passing by - _timer = Timer.periodic(const Duration(milliseconds: 200), (timer) { - if (_stopwatch.isRunning) { - setState(() {}); - } - }); - } - - // Timer needs to be disposed when widget is destroyed to avoid memory leaks - // https://stackoverflow.com/questions/42448410/how-can-i-run-a-unit-test-when-the-tapped-widget-launches-a-timer - @override - void dispose() { - _timer.cancel(); - super.dispose(); - } - - // Formats milliseconds to human-readable time - // https://itnext.io/create-a-stopwatch-app-with-flutter-f0dc6a176b8a - String formatTime(int milliseconds) { - var secs = milliseconds ~/ 1000; - var hours = (secs ~/ 3600).toString().padLeft(2, '0'); - var minutes = ((secs % 3600) ~/ 60).toString().padLeft(2, '0'); - var seconds = (secs % 60).toString().padLeft(2, '0'); - return "$hours:$minutes:$seconds"; - } - - // Start and stop timer button handler - void _handleButtonClick() { - // If timer is ongoing, we stop the stopwatch and the timer in the todo item. - if (_stopwatch.isRunning) { - widget.item.stopTimer(); - _stopwatch.stop(); - - // Re-render - setState(() {}); - } - - // If we are to start timer, start the timer in todo item and stopwatch. - else { - widget.item.startTimer(); - _stopwatch.start(); - - // Re-render - setState(() {}); - } - } - - // Set proper background to timer button according to status of stopwatch - Color _renderButtonBackground() { - if (_stopwatch.elapsedMilliseconds == 0) { - return const Color.fromARGB(255, 75, 192, 169); - } else { - return _stopwatch.isRunning ? Colors.red : Colors.green; - } - } - - // Set button text according to status of stopwatch - String _renderButtonText() { - if (_stopwatch.elapsedMilliseconds == 0) { - return "Start"; - } else { - return _stopwatch.isRunning ? "Stop" : "Resume"; - } - } - - @override - Widget build(BuildContext context) { - var deviceWidth = MediaQuery.of(context).size.width; - - var checkboxSize = deviceWidth > 425.0 ? 30.0 : 20.0; - - return Container( - key: itemCardWidgetKey, - constraints: const BoxConstraints(minHeight: 70), - child: ListTile( - onTap: () { - // If the stopwatch is not running, we mark toggle it - if (!_stopwatch.isRunning) { - context.read().add(ToggleTodoEvent(widget.item)); - } - - // If the stopwatch is running, we toggle the item but also stop the timer - else { - context.read().add(ToggleTodoEvent(widget.item)); - widget.item.stopTimer(); - _stopwatch.stop(); - - // Re-render - setState(() {}); - } - }, - - // Checkbox-style icon showing if it's completed or not - leading: Column( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - widget.item.completed - ? Icon( - Icons.check_box, - color: const Color.fromARGB(255, 126, 121, 121), - size: checkboxSize, - ) - : Icon( - Icons.check_box_outline_blank, - color: Colors.black, - size: checkboxSize, - ), - ], - ), - - title: Row( - children: [ - // Todo item description - Expanded( - child: Container( - margin: const EdgeInsets.only(right: 16.0), - child: (() { - // On mobile - if (ResponsiveBreakpoints.of(context).isMobile) { - return Text( - widget.item.description, - style: TextStyle( - fontSize: 20, - decoration: widget.item.completed - ? TextDecoration.lineThrough - : TextDecoration.none, - fontStyle: widget.item.completed - ? FontStyle.italic - : FontStyle.normal, - color: widget.item.completed - ? const Color.fromARGB(255, 126, 121, 121) - : Colors.black, - ), - ); - } - - // On tablet and up - else { - return Text( - widget.item.description, - style: TextStyle( - fontSize: 25, - decoration: widget.item.completed - ? TextDecoration.lineThrough - : TextDecoration.none, - fontStyle: widget.item.completed - ? FontStyle.italic - : FontStyle.normal, - color: widget.item.completed - ? const Color.fromARGB(255, 126, 121, 121) - : Colors.black, - ), - ); - } - }()), - ), - ), - - // Stopwatch and timer button - Column( - children: [ - SizedBox( - child: (() { - // On mobile - if (ResponsiveBreakpoints.of(context).isMobile) { - return Text( - formatTime(_stopwatch.elapsedMilliseconds), - maxLines: 1, - style: const TextStyle(color: Colors.black54), - ); - } - - // On tablet and up - else { - return Text( - formatTime(_stopwatch.elapsedMilliseconds), - maxLines: 1, - style: const TextStyle( - color: Colors.black54, fontSize: 18,), - ); - } - }()), - ), - - // If the item is completed, we hide the button - if (!widget.item.completed) - SizedBox( - child: (() { - // On mobile - if (ResponsiveBreakpoints.of(context).isMobile) { - return ElevatedButton( - key: itemCardTimerButtonKey, - style: ElevatedButton.styleFrom( - backgroundColor: _renderButtonBackground(), - elevation: 0, - shape: const RoundedRectangleBorder( - borderRadius: BorderRadius.zero, - ), - ), - onPressed: _handleButtonClick, - child: Text( - _renderButtonText(), - maxLines: 1, - ), - ); - } - - // On tablet and up - else { - return ElevatedButton( - key: itemCardTimerButtonKey, - style: ElevatedButton.styleFrom( - backgroundColor: _renderButtonBackground(), - elevation: 0, - shape: const RoundedRectangleBorder( - borderRadius: BorderRadius.zero, - ), - ), - onPressed: _handleButtonClick, - child: Text( - _renderButtonText(), - maxLines: 1, - style: const TextStyle(fontSize: 20), - ), - ); - } - }()), - ), - ], - ) - ], - ), - ), - ); - } -} diff --git a/lib/presentation/widgets/items.dart b/lib/presentation/widgets/items.dart new file mode 100644 index 00000000..0c55ce11 --- /dev/null +++ b/lib/presentation/widgets/items.dart @@ -0,0 +1,276 @@ +import 'dart:async'; + +import 'package:flutter/material.dart'; +import 'package:responsive_framework/responsive_framework.dart'; + +import '../../blocs/blocs.dart'; +import '../../models/models.dart'; + + +const itemCardWidgetKey = Key('itemCardWidgetKey'); +const itemCardTimerButtonKey = Key('itemCardTimerButtonKey'); + + +/// Widget pertaining to an item card. +/// It shows its info and changes state according to its progress. +class ItemCard extends StatefulWidget { + final Item item; + + const ItemCard({required this.item, super.key}); + + @override + State createState() => _ItemCardState(); +} + +class _ItemCardState extends State { + // Timer to be displayed + late TimerStopwatch _stopwatch; + + // Used to re-render the text showing the timer + late Timer _timer; + + @override + void initState() { + super.initState(); + WidgetsFlutterBinding.ensureInitialized(); + + _stopwatch = + TimerStopwatch(initialOffset: widget.item.getCumulativeDuration()); + + // Timer to rerender the page so the text shows the seconds passing by + _timer = Timer.periodic(const Duration(milliseconds: 200), (timer) { + if (_stopwatch.isRunning) { + setState(() {}); + } + }); + } + + // Timer needs to be disposed when widget is destroyed to avoid memory leaks + // https://stackoverflow.com/questions/42448410/how-can-i-run-a-unit-test-when-the-tapped-widget-launches-a-timer + @override + void dispose() { + _timer.cancel(); + super.dispose(); + } + + // Formats milliseconds to human-readable time + // https://itnext.io/create-a-stopwatch-app-with-flutter-f0dc6a176b8a + String formatTime(int milliseconds) { + var secs = milliseconds ~/ 1000; + var hours = (secs ~/ 3600).toString().padLeft(2, '0'); + var minutes = ((secs % 3600) ~/ 60).toString().padLeft(2, '0'); + var seconds = (secs % 60).toString().padLeft(2, '0'); + return "$hours:$minutes:$seconds"; + } + + // Start and stop timer button handler + void _handleButtonClick() { + // If timer is ongoing, we stop the stopwatch and the timer in the todo item. + if (_stopwatch.isRunning) { + widget.item.stopTimer(); + _stopwatch.stop(); + + // Re-render + setState(() {}); + } + + // If we are to start timer, start the timer in todo item and stopwatch. + else { + widget.item.startTimer(); + _stopwatch.start(); + + // Re-render + setState(() {}); + } + } + + // Set proper background to timer button according to status of stopwatch + Color _renderButtonBackground() { + if (_stopwatch.elapsedMilliseconds == 0) { + return const Color.fromARGB(255, 75, 192, 169); + } else { + return _stopwatch.isRunning ? Colors.red : Colors.green; + } + } + + // Set button text according to status of stopwatch + String _renderButtonText() { + if (_stopwatch.elapsedMilliseconds == 0) { + return "Start"; + } else { + return _stopwatch.isRunning ? "Stop" : "Resume"; + } + } + + @override + Widget build(BuildContext context) { + var deviceWidth = MediaQuery.of(context).size.width; + + var checkboxSize = deviceWidth > 425.0 ? 30.0 : 20.0; + + return Container( + key: itemCardWidgetKey, + constraints: const BoxConstraints(minHeight: 70), + child: ListTile( + onTap: () { + // If the stopwatch is not running, we mark toggle it + if (!_stopwatch.isRunning) { + context.read().add(ToggleTodoEvent(widget.item)); + } + + // If the stopwatch is running, we toggle the item but also stop the timer + else { + context.read().add(ToggleTodoEvent(widget.item)); + widget.item.stopTimer(); + _stopwatch.stop(); + + // Re-render + setState(() {}); + } + }, + + // Checkbox-style icon showing if it's completed or not + leading: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + widget.item.completed + ? Icon( + Icons.check_box, + color: const Color.fromARGB(255, 126, 121, 121), + size: checkboxSize, + ) + : Icon( + Icons.check_box_outline_blank, + color: Colors.black, + size: checkboxSize, + ), + ], + ), + + title: Row( + children: [ + // Todo item description + Expanded( + child: Container( + margin: const EdgeInsets.only(right: 16.0), + child: (() { + // On mobile + if (ResponsiveBreakpoints.of(context).isMobile) { + return Text( + widget.item.description, + style: TextStyle( + fontSize: 20, + decoration: widget.item.completed + ? TextDecoration.lineThrough + : TextDecoration.none, + fontStyle: widget.item.completed + ? FontStyle.italic + : FontStyle.normal, + color: widget.item.completed + ? const Color.fromARGB(255, 126, 121, 121) + : Colors.black, + ), + ); + } + + // On tablet and up + else { + return Text( + widget.item.description, + style: TextStyle( + fontSize: 25, + decoration: widget.item.completed + ? TextDecoration.lineThrough + : TextDecoration.none, + fontStyle: widget.item.completed + ? FontStyle.italic + : FontStyle.normal, + color: widget.item.completed + ? const Color.fromARGB(255, 126, 121, 121) + : Colors.black, + ), + ); + } + }()), + ), + ), + + // Stopwatch and timer button + Column( + children: [ + SizedBox( + child: (() { + // On mobile + if (ResponsiveBreakpoints.of(context).isMobile) { + return Text( + formatTime(_stopwatch.elapsedMilliseconds), + maxLines: 1, + style: const TextStyle(color: Colors.black54), + ); + } + + // On tablet and up + else { + return Text( + formatTime(_stopwatch.elapsedMilliseconds), + maxLines: 1, + style: const TextStyle( + color: Colors.black54, fontSize: 18,), + ); + } + }()), + ), + + // If the item is completed, we hide the button + if (!widget.item.completed) + SizedBox( + child: (() { + // On mobile + if (ResponsiveBreakpoints.of(context).isMobile) { + return ElevatedButton( + key: itemCardTimerButtonKey, + style: ElevatedButton.styleFrom( + backgroundColor: _renderButtonBackground(), + elevation: 0, + shape: const RoundedRectangleBorder( + borderRadius: BorderRadius.zero, + ), + ), + onPressed: _handleButtonClick, + child: Text( + _renderButtonText(), + maxLines: 1, + ), + ); + } + + // On tablet and up + else { + return ElevatedButton( + key: itemCardTimerButtonKey, + style: ElevatedButton.styleFrom( + backgroundColor: _renderButtonBackground(), + elevation: 0, + shape: const RoundedRectangleBorder( + borderRadius: BorderRadius.zero, + ), + ), + onPressed: _handleButtonClick, + child: Text( + _renderButtonText(), + maxLines: 1, + style: const TextStyle(fontSize: 20), + ), + ); + } + }()), + ), + ], + ) + ], + ), + ), + ); + } +} diff --git a/lib/presentation/widgets/widgets.dart b/lib/presentation/widgets/widgets.dart new file mode 100644 index 00000000..f6c372f4 --- /dev/null +++ b/lib/presentation/widgets/widgets.dart @@ -0,0 +1 @@ +export 'items.dart'; diff --git a/test/widget/widget_test.dart b/test/widget/widget_test.dart index bf9ef098..b6dfb4a9 100644 --- a/test/widget/widget_test.dart +++ b/test/widget/widget_test.dart @@ -1,3 +1,4 @@ +import 'package:dwyl_todo/presentation/widgets/widgets.dart'; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:dwyl_todo/main.dart'; From 43642308632e725bfda8a448e46e625f2b079ac8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lui=CC=81s=20Arteiro?= Date: Wed, 2 Aug 2023 16:03:58 +0100 Subject: [PATCH 04/17] refactor: Navigation bar refactor. --- lib/main.dart | 64 +-------------------- lib/presentation/widgets/navigationBar.dart | 60 +++++++++++++++++++ lib/presentation/widgets/widgets.dart | 1 + 3 files changed, 64 insertions(+), 61 deletions(-) create mode 100644 lib/presentation/widgets/navigationBar.dart diff --git a/lib/main.dart b/lib/main.dart index 411816fd..771a020c 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -12,8 +12,7 @@ import 'presentation/widgets/widgets.dart'; const textfieldKey = Key("textfieldKey"); const textfieldOnNewPageKey = Key('textfieldOnNewPageKey'); const saveButtonKey = Key('saveButtonKey'); -const backButtonKey = Key('backButtonKey'); -const logoKey = Key('logoKey'); + // coverage:ignore-start void main() { @@ -60,7 +59,7 @@ class HomePage extends StatelessWidget { Widget build(BuildContext context) { return Scaffold( // Top navigation bar - appBar: NavigationBar( + appBar: NavBar( givenContext: context, ), @@ -190,7 +189,7 @@ class _NewTodoPageState extends State { Widget build(BuildContext context) { return MaterialApp( home: Scaffold( - appBar: NavigationBar( + appBar: NavBar( givenContext: context, showGoBackButton: true, ), @@ -295,60 +294,3 @@ class _NewTodoPageState extends State { ); } } - -// WIDGETS -------------------------- - -/// Navigation bar widget. -/// It needs to receive a context to dynamically elements. -class NavigationBar extends StatelessWidget implements PreferredSizeWidget { - // Boolean that tells the bar to have a button to go to the previous page - final bool showGoBackButton; - // Build context for the "go back" button works - final BuildContext givenContext; - - const NavigationBar({ - super.key, - required this.givenContext, - this.showGoBackButton = false, - }); - - @override - Widget build(BuildContext context) { - return AppBar( - title: Row( - mainAxisAlignment: MainAxisAlignment.center, - mainAxisSize: MainAxisSize.min, - children: [ - GestureDetector( - onTap: () { - Navigator.pop(givenContext); - }, - child: - // dwyl logo - Image.asset( - "assets/icon/icon.png", - key: logoKey, - fit: BoxFit.fitHeight, - height: 30, - ), - ), - ], - ), - backgroundColor: const Color.fromARGB(255, 81, 72, 72), - elevation: 0.0, - centerTitle: true, - leading: showGoBackButton - ? BackButton( - key: backButtonKey, - onPressed: () { - Navigator.pop(givenContext); - }, - ) - : null, - ); - } - - @override - Size get preferredSize => const Size.fromHeight(50); -} - diff --git a/lib/presentation/widgets/navigationBar.dart b/lib/presentation/widgets/navigationBar.dart new file mode 100644 index 00000000..590a02b3 --- /dev/null +++ b/lib/presentation/widgets/navigationBar.dart @@ -0,0 +1,60 @@ + +import 'package:flutter/material.dart'; + +const backButtonKey = Key('backButtonKey'); +const logoKey = Key('logoKey'); + +/// Navigation bar widget. +/// It needs to receive a context to dynamically elements. +class NavBar extends StatelessWidget implements PreferredSizeWidget { + // Boolean that tells the bar to have a button to go to the previous page + final bool showGoBackButton; + // Build context for the "go back" button works + final BuildContext givenContext; + + const NavBar({ + super.key, + required this.givenContext, + this.showGoBackButton = false, + }); + + @override + Widget build(BuildContext context) { + return AppBar( + title: Row( + mainAxisAlignment: MainAxisAlignment.center, + mainAxisSize: MainAxisSize.min, + children: [ + GestureDetector( + onTap: () { + Navigator.pop(givenContext); + }, + child: + // dwyl logo + Image.asset( + "assets/icon/icon.png", + key: logoKey, + fit: BoxFit.fitHeight, + height: 30, + ), + ), + ], + ), + backgroundColor: const Color.fromARGB(255, 81, 72, 72), + elevation: 0.0, + centerTitle: true, + leading: showGoBackButton + ? BackButton( + key: backButtonKey, + onPressed: () { + Navigator.pop(givenContext); + }, + ) + : null, + ); + } + + @override + Size get preferredSize => const Size.fromHeight(50); +} + diff --git a/lib/presentation/widgets/widgets.dart b/lib/presentation/widgets/widgets.dart index f6c372f4..7b09d14e 100644 --- a/lib/presentation/widgets/widgets.dart +++ b/lib/presentation/widgets/widgets.dart @@ -1 +1,2 @@ export 'items.dart'; +export 'navigationBar.dart'; From c6d8fcb5713833ffff5f33e7fc7fa4b51882e6cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lui=CC=81s=20Arteiro?= Date: Wed, 2 Aug 2023 16:10:17 +0100 Subject: [PATCH 05/17] refactor: Refactoring views. --- lib/main.dart | 253 +--------------------------- lib/presentation/views/home.dart | 114 +++++++++++++ lib/presentation/views/newTodo.dart | 147 ++++++++++++++++ lib/presentation/views/views.dart | 3 + test/widget/widget_test.dart | 1 + 5 files changed, 266 insertions(+), 252 deletions(-) create mode 100644 lib/presentation/views/home.dart create mode 100644 lib/presentation/views/newTodo.dart create mode 100644 lib/presentation/views/views.dart diff --git a/lib/main.dart b/lib/main.dart index 771a020c..0ccff779 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -4,15 +4,10 @@ import 'package:flutter/material.dart'; import 'package:responsive_framework/responsive_framework.dart'; import 'blocs/blocs.dart'; -import 'models/models.dart'; +import 'presentation/views/views.dart'; import 'presentation/widgets/widgets.dart'; -// Keys used for testing -const textfieldKey = Key("textfieldKey"); -const textfieldOnNewPageKey = Key('textfieldOnNewPageKey'); -const saveButtonKey = Key('saveButtonKey'); - // coverage:ignore-start void main() { @@ -48,249 +43,3 @@ class MainApp extends StatelessWidget { ); } } - -/// App's home page. -/// The person will be able to create a new todo item -/// and view a list of previously created ones. -class HomePage extends StatelessWidget { - const HomePage({super.key}); - - @override - Widget build(BuildContext context) { - return Scaffold( - // Top navigation bar - appBar: NavBar( - givenContext: context, - ), - - // Body of the page. - // It is responsive and change style according to the device. - body: BlocBuilder( - builder: (context, state) { - // If the list is loaded - if (state is TodoListLoadedState) { - var items = state.items; - - return SafeArea( - child: Column( - children: [ - Container( - child: (() { - // On mobile - if (ResponsiveBreakpoints.of(context).isMobile) { - return TextField( - key: textfieldKey, - controller: TextEditingController(), - keyboardType: TextInputType.none, - onTap: () { - Navigator.of(context) - .push(navigateToNewTodoItemPage()); - }, - maxLines: 2, - style: const TextStyle(fontSize: 20), - decoration: const InputDecoration( - border: OutlineInputBorder( - borderRadius: BorderRadius.zero, - ), - hintText: 'Capture more things on your mind...', - ), - textAlignVertical: TextAlignVertical.top, - ); - } - - // On tablet and up - else { - return TextField( - key: textfieldKey, - controller: TextEditingController(), - keyboardType: TextInputType.none, - onTap: () { - Navigator.of(context) - .push(navigateToNewTodoItemPage()); - }, - maxLines: 2, - style: const TextStyle(fontSize: 30), - decoration: const InputDecoration( - border: OutlineInputBorder( - borderRadius: BorderRadius.zero, - ), - hintText: 'Capture more things on your mind...', - ), - textAlignVertical: TextAlignVertical.top, - ); - } - }()), - ), - - // List of items - Expanded( - child: ListView( - padding: const EdgeInsets.only(top: 40), - scrollDirection: Axis.vertical, - shrinkWrap: true, - children: [ - if (items.isNotEmpty) const Divider(height: 0), - for (var i = 0; i < items.length; i++) ...[ - if (i > 0) const Divider(height: 0), - Padding( - padding: const EdgeInsets.symmetric(vertical: 8.0), - child: ItemCard(item: items[i]), - ) - ], - ], - ), - ), - ], - ), - ); - } - - // If the state of the TodoItemList is not loaded, we show error.ˆ - else { - return const Center(child: Text("Error loading items list.")); - } - }, - ), - ); - } -} - -// PAGES ---------------------------- - -/// Transition handler that navigates the route to the `NewTodo` item page. -Route navigateToNewTodoItemPage() { - return PageRouteBuilder( - pageBuilder: (context, animation, secondaryAnimation) => - const NewTodoPage(), - transitionDuration: Duration.zero, - reverseTransitionDuration: Duration.zero, - ); -} - -/// Page that shows a textfield expanded to create a new todo item. -class NewTodoPage extends StatefulWidget { - const NewTodoPage({super.key}); - - @override - State createState() => _NewTodoPageState(); -} - -class _NewTodoPageState extends State { - // https://stackoverflow.com/questions/61425969/is-it-okay-to-use-texteditingcontroller-in-statelesswidget-in-flutter - TextEditingController txtFieldController = TextEditingController(); - - @override - void dispose() { - txtFieldController.dispose(); - super.dispose(); - } - - @override - Widget build(BuildContext context) { - return MaterialApp( - home: Scaffold( - appBar: NavBar( - givenContext: context, - showGoBackButton: true, - ), - body: SafeArea( - child: Column( - children: [ - // Textfield that is expanded and borderless - Expanded( - child: (() { - // On mobile - if (ResponsiveBreakpoints.of(context).isMobile) { - return TextField( - key: textfieldOnNewPageKey, - controller: txtFieldController, - expands: true, - maxLines: null, - autofocus: true, - style: const TextStyle(fontSize: 20), - decoration: const InputDecoration( - border: OutlineInputBorder( - borderRadius: BorderRadius.zero, - ), - hintText: 'start typing', - ), - textAlignVertical: TextAlignVertical.top, - ); - } - - // On tablet and up - else { - return TextField( - key: textfieldOnNewPageKey, - controller: txtFieldController, - expands: true, - maxLines: null, - autofocus: true, - style: const TextStyle(fontSize: 30), - decoration: const InputDecoration( - border: OutlineInputBorder( - borderRadius: BorderRadius.zero, - ), - hintText: 'start typing', - ), - textAlignVertical: TextAlignVertical.top, - ); - } - }()), - ), - - // Save button. - // When submitted, it adds a new todo item, clears the controller and navigates back - Align( - alignment: Alignment.bottomRight, - child: ElevatedButton( - key: saveButtonKey, - style: ElevatedButton.styleFrom( - backgroundColor: const Color.fromARGB(255, 75, 192, 169), - shape: const RoundedRectangleBorder( - borderRadius: BorderRadius.zero, - ), - ), - onPressed: () { - final value = txtFieldController.text; - if (value.isNotEmpty) { - // Create new item and create AddTodo event - var newTodoItem = Item(description: value); - BlocProvider.of(context) - .add(AddTodoEvent(newTodoItem)); - - // Clear textfield - txtFieldController.clear(); - - // Go back to home page - Navigator.pop(context); - } - }, - child: SizedBox( - child: (() { - // On mobile - if (ResponsiveBreakpoints.of(context).isMobile) { - return const Text( - 'Save', - style: TextStyle(fontSize: 24), - ); - } - - // On tablet and up - else { - return const Text( - 'Save', - style: TextStyle(fontSize: 40), - ); - } - }()), - ), - ), - ), - ], - ), - ), - ), - ); - } -} diff --git a/lib/presentation/views/home.dart b/lib/presentation/views/home.dart new file mode 100644 index 00000000..72a7180a --- /dev/null +++ b/lib/presentation/views/home.dart @@ -0,0 +1,114 @@ +import 'package:flutter/material.dart'; +import 'package:responsive_framework/responsive_framework.dart'; + +import '../../blocs/blocs.dart'; +import '../widgets/widgets.dart'; +import 'newTodo.dart'; + +const textfieldKey = Key("textfieldKey"); + +/// App's home page. +/// The person will be able to create a new todo item +/// and view a list of previously created ones. +class HomePage extends StatelessWidget { + const HomePage({super.key}); + + @override + Widget build(BuildContext context) { + return Scaffold( + // Top navigation bar + appBar: NavBar( + givenContext: context, + ), + + // Body of the page. + // It is responsive and change style according to the device. + body: BlocBuilder( + builder: (context, state) { + // If the list is loaded + if (state is TodoListLoadedState) { + var items = state.items; + + return SafeArea( + child: Column( + children: [ + Container( + child: (() { + // On mobile + if (ResponsiveBreakpoints.of(context).isMobile) { + return TextField( + key: textfieldKey, + controller: TextEditingController(), + keyboardType: TextInputType.none, + onTap: () { + Navigator.of(context) + .push(navigateToNewTodoItemPage()); + }, + maxLines: 2, + style: const TextStyle(fontSize: 20), + decoration: const InputDecoration( + border: OutlineInputBorder( + borderRadius: BorderRadius.zero, + ), + hintText: 'Capture more things on your mind...', + ), + textAlignVertical: TextAlignVertical.top, + ); + } + + // On tablet and up + else { + return TextField( + key: textfieldKey, + controller: TextEditingController(), + keyboardType: TextInputType.none, + onTap: () { + Navigator.of(context) + .push(navigateToNewTodoItemPage()); + }, + maxLines: 2, + style: const TextStyle(fontSize: 30), + decoration: const InputDecoration( + border: OutlineInputBorder( + borderRadius: BorderRadius.zero, + ), + hintText: 'Capture more things on your mind...', + ), + textAlignVertical: TextAlignVertical.top, + ); + } + }()), + ), + + // List of items + Expanded( + child: ListView( + padding: const EdgeInsets.only(top: 40), + scrollDirection: Axis.vertical, + shrinkWrap: true, + children: [ + if (items.isNotEmpty) const Divider(height: 0), + for (var i = 0; i < items.length; i++) ...[ + if (i > 0) const Divider(height: 0), + Padding( + padding: const EdgeInsets.symmetric(vertical: 8.0), + child: ItemCard(item: items[i]), + ) + ], + ], + ), + ), + ], + ), + ); + } + + // If the state of the TodoItemList is not loaded, we show error.ˆ + else { + return const Center(child: Text("Error loading items list.")); + } + }, + ), + ); + } +} \ No newline at end of file diff --git a/lib/presentation/views/newTodo.dart b/lib/presentation/views/newTodo.dart new file mode 100644 index 00000000..72eab2d7 --- /dev/null +++ b/lib/presentation/views/newTodo.dart @@ -0,0 +1,147 @@ +import 'package:flutter/material.dart'; +import 'package:responsive_framework/responsive_framework.dart'; + +import '../../blocs/blocs.dart'; +import '../../models/models.dart'; +import '../widgets/navigationBar.dart'; + +const textfieldOnNewPageKey = Key('textfieldOnNewPageKey'); +const saveButtonKey = Key('saveButtonKey'); + +/// Transition handler that navigates the route to the `NewTodo` item page. +Route navigateToNewTodoItemPage() { + return PageRouteBuilder( + pageBuilder: (context, animation, secondaryAnimation) => + const NewTodoPage(), + transitionDuration: Duration.zero, + reverseTransitionDuration: Duration.zero, + ); +} + +/// Page that shows a textfield expanded to create a new todo item. +class NewTodoPage extends StatefulWidget { + const NewTodoPage({super.key}); + + @override + State createState() => _NewTodoPageState(); +} + +class _NewTodoPageState extends State { + // https://stackoverflow.com/questions/61425969/is-it-okay-to-use-texteditingcontroller-in-statelesswidget-in-flutter + TextEditingController txtFieldController = TextEditingController(); + + @override + void dispose() { + txtFieldController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return MaterialApp( + home: Scaffold( + appBar: NavBar( + givenContext: context, + showGoBackButton: true, + ), + body: SafeArea( + child: Column( + children: [ + // Textfield that is expanded and borderless + Expanded( + child: (() { + // On mobile + if (ResponsiveBreakpoints.of(context).isMobile) { + return TextField( + key: textfieldOnNewPageKey, + controller: txtFieldController, + expands: true, + maxLines: null, + autofocus: true, + style: const TextStyle(fontSize: 20), + decoration: const InputDecoration( + border: OutlineInputBorder( + borderRadius: BorderRadius.zero, + ), + hintText: 'start typing', + ), + textAlignVertical: TextAlignVertical.top, + ); + } + + // On tablet and up + else { + return TextField( + key: textfieldOnNewPageKey, + controller: txtFieldController, + expands: true, + maxLines: null, + autofocus: true, + style: const TextStyle(fontSize: 30), + decoration: const InputDecoration( + border: OutlineInputBorder( + borderRadius: BorderRadius.zero, + ), + hintText: 'start typing', + ), + textAlignVertical: TextAlignVertical.top, + ); + } + }()), + ), + + // Save button. + // When submitted, it adds a new todo item, clears the controller and navigates back + Align( + alignment: Alignment.bottomRight, + child: ElevatedButton( + key: saveButtonKey, + style: ElevatedButton.styleFrom( + backgroundColor: const Color.fromARGB(255, 75, 192, 169), + shape: const RoundedRectangleBorder( + borderRadius: BorderRadius.zero, + ), + ), + onPressed: () { + final value = txtFieldController.text; + if (value.isNotEmpty) { + // Create new item and create AddTodo event + var newTodoItem = Item(description: value); + BlocProvider.of(context) + .add(AddTodoEvent(newTodoItem)); + + // Clear textfield + txtFieldController.clear(); + + // Go back to home page + Navigator.pop(context); + } + }, + child: SizedBox( + child: (() { + // On mobile + if (ResponsiveBreakpoints.of(context).isMobile) { + return const Text( + 'Save', + style: TextStyle(fontSize: 24), + ); + } + + // On tablet and up + else { + return const Text( + 'Save', + style: TextStyle(fontSize: 40), + ); + } + }()), + ), + ), + ), + ], + ), + ), + ), + ); + } +} diff --git a/lib/presentation/views/views.dart b/lib/presentation/views/views.dart new file mode 100644 index 00000000..5260986e --- /dev/null +++ b/lib/presentation/views/views.dart @@ -0,0 +1,3 @@ +export 'home.dart'; +export 'newTodo.dart'; + diff --git a/test/widget/widget_test.dart b/test/widget/widget_test.dart index b6dfb4a9..48ad85b2 100644 --- a/test/widget/widget_test.dart +++ b/test/widget/widget_test.dart @@ -1,3 +1,4 @@ +import 'package:dwyl_todo/presentation/views/views.dart'; import 'package:dwyl_todo/presentation/widgets/widgets.dart'; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; From da996ee79a618bc6e293aed2a95cb53e338f4642 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lui=CC=81s=20Arteiro?= Date: Wed, 2 Aug 2023 16:11:41 +0100 Subject: [PATCH 06/17] fix: Fixing warnings and formatting. #339 --- lib/blocs/blocs.dart | 2 +- lib/main.dart | 4 ---- lib/presentation/views/home.dart | 4 ++-- lib/presentation/views/{newTodo.dart => new_todo.dart} | 2 +- lib/presentation/views/views.dart | 3 +-- lib/presentation/widgets/items.dart | 6 +++--- .../widgets/{navigationBar.dart => navbar.dart} | 2 -- lib/presentation/widgets/widgets.dart | 2 +- 8 files changed, 9 insertions(+), 16 deletions(-) rename lib/presentation/views/{newTodo.dart => new_todo.dart} (99%) rename lib/presentation/widgets/{navigationBar.dart => navbar.dart} (99%) diff --git a/lib/blocs/blocs.dart b/lib/blocs/blocs.dart index 0a4dc2c5..028a55da 100644 --- a/lib/blocs/blocs.dart +++ b/lib/blocs/blocs.dart @@ -1,3 +1,3 @@ export 'package:flutter_bloc/flutter_bloc.dart'; -export 'todo/todo_bloc.dart'; \ No newline at end of file +export 'todo/todo_bloc.dart'; diff --git a/lib/main.dart b/lib/main.dart index 0ccff779..8f7663d3 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,13 +1,9 @@ -import 'dart:async'; import 'package:flutter/material.dart'; import 'package:responsive_framework/responsive_framework.dart'; import 'blocs/blocs.dart'; import 'presentation/views/views.dart'; -import 'presentation/widgets/widgets.dart'; - - // coverage:ignore-start void main() { diff --git a/lib/presentation/views/home.dart b/lib/presentation/views/home.dart index 72a7180a..ae2d6784 100644 --- a/lib/presentation/views/home.dart +++ b/lib/presentation/views/home.dart @@ -3,7 +3,7 @@ import 'package:responsive_framework/responsive_framework.dart'; import '../../blocs/blocs.dart'; import '../widgets/widgets.dart'; -import 'newTodo.dart'; +import 'new_todo.dart'; const textfieldKey = Key("textfieldKey"); @@ -111,4 +111,4 @@ class HomePage extends StatelessWidget { ), ); } -} \ No newline at end of file +} diff --git a/lib/presentation/views/newTodo.dart b/lib/presentation/views/new_todo.dart similarity index 99% rename from lib/presentation/views/newTodo.dart rename to lib/presentation/views/new_todo.dart index 72eab2d7..2e7f46fc 100644 --- a/lib/presentation/views/newTodo.dart +++ b/lib/presentation/views/new_todo.dart @@ -3,7 +3,7 @@ import 'package:responsive_framework/responsive_framework.dart'; import '../../blocs/blocs.dart'; import '../../models/models.dart'; -import '../widgets/navigationBar.dart'; +import '../widgets/navbar.dart'; const textfieldOnNewPageKey = Key('textfieldOnNewPageKey'); const saveButtonKey = Key('saveButtonKey'); diff --git a/lib/presentation/views/views.dart b/lib/presentation/views/views.dart index 5260986e..b5f9c407 100644 --- a/lib/presentation/views/views.dart +++ b/lib/presentation/views/views.dart @@ -1,3 +1,2 @@ export 'home.dart'; -export 'newTodo.dart'; - +export 'new_todo.dart'; diff --git a/lib/presentation/widgets/items.dart b/lib/presentation/widgets/items.dart index 0c55ce11..a70fc19b 100644 --- a/lib/presentation/widgets/items.dart +++ b/lib/presentation/widgets/items.dart @@ -6,11 +6,9 @@ import 'package:responsive_framework/responsive_framework.dart'; import '../../blocs/blocs.dart'; import '../../models/models.dart'; - const itemCardWidgetKey = Key('itemCardWidgetKey'); const itemCardTimerButtonKey = Key('itemCardTimerButtonKey'); - /// Widget pertaining to an item card. /// It shows its info and changes state according to its progress. class ItemCard extends StatefulWidget { @@ -216,7 +214,9 @@ class _ItemCardState extends State { formatTime(_stopwatch.elapsedMilliseconds), maxLines: 1, style: const TextStyle( - color: Colors.black54, fontSize: 18,), + color: Colors.black54, + fontSize: 18, + ), ); } }()), diff --git a/lib/presentation/widgets/navigationBar.dart b/lib/presentation/widgets/navbar.dart similarity index 99% rename from lib/presentation/widgets/navigationBar.dart rename to lib/presentation/widgets/navbar.dart index 590a02b3..177fadcf 100644 --- a/lib/presentation/widgets/navigationBar.dart +++ b/lib/presentation/widgets/navbar.dart @@ -1,4 +1,3 @@ - import 'package:flutter/material.dart'; const backButtonKey = Key('backButtonKey'); @@ -57,4 +56,3 @@ class NavBar extends StatelessWidget implements PreferredSizeWidget { @override Size get preferredSize => const Size.fromHeight(50); } - diff --git a/lib/presentation/widgets/widgets.dart b/lib/presentation/widgets/widgets.dart index 7b09d14e..9b9dec16 100644 --- a/lib/presentation/widgets/widgets.dart +++ b/lib/presentation/widgets/widgets.dart @@ -1,2 +1,2 @@ export 'items.dart'; -export 'navigationBar.dart'; +export 'navbar.dart'; From d16e05fb4d62f13651dc9e27af09893114e26f64 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lui=CC=81s=20Arteiro?= Date: Thu, 3 Aug 2023 23:45:11 +0100 Subject: [PATCH 07/17] fix: Fixing warnings and enforcing proper named arguments on class constructors. --- lib/blocs/todo/todo_bloc.dart | 8 ++++---- lib/models/item.dart | 10 +++++----- lib/presentation/views/home.dart | 2 +- lib/presentation/views/new_todo.dart | 2 +- lib/presentation/widgets/items.dart | 12 ++++++------ lib/presentation/widgets/navbar.dart | 3 +-- test/bloc/todo_bloc_test.dart | 4 ++-- test/widget/widget_test.dart | 4 ++-- 8 files changed, 22 insertions(+), 23 deletions(-) diff --git a/lib/blocs/todo/todo_bloc.dart b/lib/blocs/todo/todo_bloc.dart index 4c589090..05023e5a 100644 --- a/lib/blocs/todo/todo_bloc.dart +++ b/lib/blocs/todo/todo_bloc.dart @@ -42,7 +42,7 @@ class TodoBloc extends Bloc { // Check if list is loaded if (state is TodoListLoadedState) { - var items = state.items; + final items = state.items; items.removeWhere((element) => element.id == event.todoObj.id); emit(TodoListLoadedState(items: items)); @@ -58,13 +58,13 @@ class TodoBloc extends Bloc { // You have to create a new object because the items list needs to be new so Bloc knows it needs to re-render // https://stackoverflow.com/questions/65379743/flutter-bloc-cant-update-my-list-of-boolean final items = List.from(state.items); - var indexToChange = + final indexToChange = items.indexWhere((element) => element.id == event.todoObj.id); // If the element is found, we create a copy of the element with the `completed` field toggled. if (indexToChange != -1) { - var itemToChange = items[indexToChange]; - var updatedItem = Item( + final itemToChange = items[indexToChange]; + final updatedItem = Item( description: itemToChange.description, completed: !itemToChange.completed, ); diff --git a/lib/models/item.dart b/lib/models/item.dart index 1226e70a..7fd381a8 100644 --- a/lib/models/item.dart +++ b/lib/models/item.dart @@ -19,13 +19,13 @@ class Item { // Adds a new timer that starts on current time void startTimer() { if (_timersList.isEmpty) { - _timersList.add(ItemTimer(null, start: DateTime.now())); + _timersList.add(ItemTimer(start: DateTime.now())); } else { - var lastTimer = _timersList.last; + final lastTimer = _timersList.last; // Only create a new timer if the last one is finished if (lastTimer.end != null) { - _timersList.add(ItemTimer(null, start: DateTime.now())); + _timersList.add(ItemTimer(start: DateTime.now())); } } } @@ -33,7 +33,7 @@ class Item { // Stop the timer that is at the end of the list void stopTimer() { if (_timersList.isNotEmpty) { - var lastTimer = _timersList.last; + final lastTimer = _timersList.last; // Only stop last timer if the end is null if (lastTimer.end == null) { @@ -64,5 +64,5 @@ class ItemTimer { final DateTime start; DateTime? end; - ItemTimer(this.end, {required this.start}); + ItemTimer({required this.start, this.end}); } diff --git a/lib/presentation/views/home.dart b/lib/presentation/views/home.dart index ae2d6784..1936841a 100644 --- a/lib/presentation/views/home.dart +++ b/lib/presentation/views/home.dart @@ -27,7 +27,7 @@ class HomePage extends StatelessWidget { builder: (context, state) { // If the list is loaded if (state is TodoListLoadedState) { - var items = state.items; + final items = state.items; return SafeArea( child: Column( diff --git a/lib/presentation/views/new_todo.dart b/lib/presentation/views/new_todo.dart index 2e7f46fc..7022c8b9 100644 --- a/lib/presentation/views/new_todo.dart +++ b/lib/presentation/views/new_todo.dart @@ -106,7 +106,7 @@ class _NewTodoPageState extends State { final value = txtFieldController.text; if (value.isNotEmpty) { // Create new item and create AddTodo event - var newTodoItem = Item(description: value); + final newTodoItem = Item(description: value); BlocProvider.of(context) .add(AddTodoEvent(newTodoItem)); diff --git a/lib/presentation/widgets/items.dart b/lib/presentation/widgets/items.dart index a70fc19b..eb611f00 100644 --- a/lib/presentation/widgets/items.dart +++ b/lib/presentation/widgets/items.dart @@ -54,10 +54,10 @@ class _ItemCardState extends State { // Formats milliseconds to human-readable time // https://itnext.io/create-a-stopwatch-app-with-flutter-f0dc6a176b8a String formatTime(int milliseconds) { - var secs = milliseconds ~/ 1000; - var hours = (secs ~/ 3600).toString().padLeft(2, '0'); - var minutes = ((secs % 3600) ~/ 60).toString().padLeft(2, '0'); - var seconds = (secs % 60).toString().padLeft(2, '0'); + final secs = milliseconds ~/ 1000; + final hours = (secs ~/ 3600).toString().padLeft(2, '0'); + final minutes = ((secs % 3600) ~/ 60).toString().padLeft(2, '0'); + final seconds = (secs % 60).toString().padLeft(2, '0'); return "$hours:$minutes:$seconds"; } @@ -102,9 +102,9 @@ class _ItemCardState extends State { @override Widget build(BuildContext context) { - var deviceWidth = MediaQuery.of(context).size.width; + final deviceWidth = MediaQuery.of(context).size.width; - var checkboxSize = deviceWidth > 425.0 ? 30.0 : 20.0; + final checkboxSize = deviceWidth > 425.0 ? 30.0 : 20.0; return Container( key: itemCardWidgetKey, diff --git a/lib/presentation/widgets/navbar.dart b/lib/presentation/widgets/navbar.dart index 177fadcf..31a1ff6d 100644 --- a/lib/presentation/widgets/navbar.dart +++ b/lib/presentation/widgets/navbar.dart @@ -12,8 +12,7 @@ class NavBar extends StatelessWidget implements PreferredSizeWidget { final BuildContext givenContext; const NavBar({ - super.key, - required this.givenContext, + required this.givenContext, super.key, this.showGoBackButton = false, }); diff --git a/test/bloc/todo_bloc_test.dart b/test/bloc/todo_bloc_test.dart index b0cf4e8f..5114cc0f 100644 --- a/test/bloc/todo_bloc_test.dart +++ b/test/bloc/todo_bloc_test.dart @@ -32,7 +32,7 @@ void main() { 'emits [TodoListLoadedState] when RemoveTodoEvent is created', build: () => TodoBloc()..add(TodoListStarted()), act: (bloc) { - var newItem = Item(description: "todo description"); + final newItem = Item(description: "todo description"); bloc ..add(AddTodoEvent(newItem)) ..add(RemoveTodoEvent(newItem)); // add and remove @@ -47,7 +47,7 @@ void main() { 'emits [TodoListLoadedState] when ToggleTodoEvent is created', build: () => TodoBloc()..add(TodoListStarted()), act: (bloc) { - var newItem = Item(description: "todo description"); + final newItem = Item(description: "todo description"); bloc ..add(AddTodoEvent(newItem)) ..add(ToggleTodoEvent(newItem)); diff --git a/test/widget/widget_test.dart b/test/widget/widget_test.dart index 48ad85b2..8b24066e 100644 --- a/test/widget/widget_test.dart +++ b/test/widget/widget_test.dart @@ -184,7 +184,7 @@ void main() { expect(find.byKey(itemCardWidgetKey), findsOneWidget); // Getting widget to test its value - var checkboxFinder = find.descendant( + final checkboxFinder = find.descendant( of: find.byKey(itemCardWidgetKey), matching: find.byType(Icon), ); @@ -272,7 +272,7 @@ void main() { await tester.pumpAndSettle(); // Item card should be marked as done - var checkboxFinder = find.descendant( + final checkboxFinder = find.descendant( of: find.byKey(itemCardWidgetKey), matching: find.byType(Icon), ); From ff9a01c933246a7cbac9810447bb70670aabfe4c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lui=CC=81s=20Arteiro?= Date: Mon, 14 Aug 2023 10:56:58 +0100 Subject: [PATCH 08/17] fix: Trying to get flutter analyze on GH action fixed. --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c237a661..2fd08084 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -41,7 +41,7 @@ jobs: # https://fly.io/docs/app-guides/continuous-deployment-with-github-actions/ flutter_deploy: name: Deploy Flutter app - runs-on: ubuntu-latest + runs-on: macos-latest needs: [build] # https://stackoverflow.com/questions/58139406/only-run-job-on-specific-branch-with-github-actions From ffa1a9732d1492fb1aeb42d77eb4e84f05a84211 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lui=CC=81s=20Arteiro?= Date: Mon, 14 Aug 2023 10:57:41 +0100 Subject: [PATCH 09/17] Revert "fix: Trying to get flutter analyze on GH action fixed." This reverts commit ff9a01c933246a7cbac9810447bb70670aabfe4c. --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2fd08084..c237a661 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -41,7 +41,7 @@ jobs: # https://fly.io/docs/app-guides/continuous-deployment-with-github-actions/ flutter_deploy: name: Deploy Flutter app - runs-on: macos-latest + runs-on: ubuntu-latest needs: [build] # https://stackoverflow.com/questions/58139406/only-run-job-on-specific-branch-with-github-actions From ce66d46a30fd4bfcccbb5185decf0ba45f5535e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lui=CC=81s=20Arteiro?= Date: Mon, 14 Aug 2023 13:00:23 +0100 Subject: [PATCH 10/17] fix: Fixing warning --- test/bloc/todo_bloc_test.dart | 1 - 1 file changed, 1 deletion(-) diff --git a/test/bloc/todo_bloc_test.dart b/test/bloc/todo_bloc_test.dart index 39c95e94..a353930d 100644 --- a/test/bloc/todo_bloc_test.dart +++ b/test/bloc/todo_bloc_test.dart @@ -1,6 +1,5 @@ import 'package:bloc_test/bloc_test.dart'; import 'package:dwyl_todo/blocs/blocs.dart'; -import 'package:dwyl_todo/bloc/todo_bloc.dart'; import 'package:dwyl_todo/models/item.dart'; import 'package:flutter_test/flutter_test.dart'; From e6717e9db52d3b9a14ace75f5302bc5ac6ea5ec9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lui=CC=81s=20Arteiro?= Date: Mon, 14 Aug 2023 13:26:07 +0100 Subject: [PATCH 11/17] fix: Applying fixes. --- lib/presentation/views/home.dart | 4 ++-- lib/presentation/widgets/items.dart | 6 +++--- lib/presentation/widgets/navbar.dart | 2 +- test/bloc/todo_bloc_test.dart | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/lib/presentation/views/home.dart b/lib/presentation/views/home.dart index 1936841a..bedb1035 100644 --- a/lib/presentation/views/home.dart +++ b/lib/presentation/views/home.dart @@ -5,7 +5,7 @@ import '../../blocs/blocs.dart'; import '../widgets/widgets.dart'; import 'new_todo.dart'; -const textfieldKey = Key("textfieldKey"); +const textfieldKey = Key('textfieldKey'); /// App's home page. /// The person will be able to create a new todo item @@ -105,7 +105,7 @@ class HomePage extends StatelessWidget { // If the state of the TodoItemList is not loaded, we show error.ˆ else { - return const Center(child: Text("Error loading items list.")); + return const Center(child: Text('Error loading items list.')); } }, ), diff --git a/lib/presentation/widgets/items.dart b/lib/presentation/widgets/items.dart index eb611f00..f571acf5 100644 --- a/lib/presentation/widgets/items.dart +++ b/lib/presentation/widgets/items.dart @@ -58,7 +58,7 @@ class _ItemCardState extends State { final hours = (secs ~/ 3600).toString().padLeft(2, '0'); final minutes = ((secs % 3600) ~/ 60).toString().padLeft(2, '0'); final seconds = (secs % 60).toString().padLeft(2, '0'); - return "$hours:$minutes:$seconds"; + return '$hours:$minutes:$seconds'; } // Start and stop timer button handler @@ -94,9 +94,9 @@ class _ItemCardState extends State { // Set button text according to status of stopwatch String _renderButtonText() { if (_stopwatch.elapsedMilliseconds == 0) { - return "Start"; + return 'Start'; } else { - return _stopwatch.isRunning ? "Stop" : "Resume"; + return _stopwatch.isRunning ? 'Stop' : 'Resume'; } } diff --git a/lib/presentation/widgets/navbar.dart b/lib/presentation/widgets/navbar.dart index 31a1ff6d..4b61d96d 100644 --- a/lib/presentation/widgets/navbar.dart +++ b/lib/presentation/widgets/navbar.dart @@ -30,7 +30,7 @@ class NavBar extends StatelessWidget implements PreferredSizeWidget { child: // dwyl logo Image.asset( - "assets/icon/icon.png", + 'assets/icon/icon.png', key: logoKey, fit: BoxFit.fitHeight, height: 30, diff --git a/test/bloc/todo_bloc_test.dart b/test/bloc/todo_bloc_test.dart index a353930d..990f5bdc 100644 --- a/test/bloc/todo_bloc_test.dart +++ b/test/bloc/todo_bloc_test.dart @@ -32,7 +32,7 @@ void main() { 'emits [TodoListLoadedState] when RemoveTodoEvent is created', build: () => TodoBloc()..add(TodoListStarted()), act: (bloc) { - final newItem = Item(description: "todo description"); + final newItem = Item(description: 'todo description'); bloc ..add(AddTodoEvent(newItem)) ..add(RemoveTodoEvent(newItem)); // add and remove @@ -47,7 +47,7 @@ void main() { 'emits [TodoListLoadedState] when ToggleTodoEvent is created', build: () => TodoBloc()..add(TodoListStarted()), act: (bloc) { - final newItem = Item(description: "todo description"); + final newItem = Item(description: 'todo description'); bloc ..add(AddTodoEvent(newItem)) ..add(ToggleTodoEvent(newItem)); From b7b75525acfac2be11865257fd472854d2d14f46 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lui=CC=81s=20Arteiro?= Date: Tue, 15 Aug 2023 11:48:14 +0100 Subject: [PATCH 12/17] feat: Adding logging source files. #339 --- lib/logging/log_bloc_observer.dart | 29 +++++++++++++++++++++++++++++ lib/logging/logging.dart | 4 ++++ pubspec.lock | 24 ++++++++++++++++++++++++ pubspec.yaml | 2 ++ 4 files changed, 59 insertions(+) create mode 100644 lib/logging/log_bloc_observer.dart create mode 100644 lib/logging/logging.dart diff --git a/lib/logging/log_bloc_observer.dart b/lib/logging/log_bloc_observer.dart new file mode 100644 index 00000000..f25327e2 --- /dev/null +++ b/lib/logging/log_bloc_observer.dart @@ -0,0 +1,29 @@ +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:lumberdash/lumberdash.dart'; + +class GlobalLogBlocObserver extends BlocObserver { + @override + void onEvent(Bloc bloc, Object? event) { + super.onEvent(bloc, event); + logMessage('${bloc.runtimeType} event dispatch: $event.'); + } + + @override + void onChange(BlocBase bloc, Change change) { + super.onChange(bloc, change); + } + + @override + void onTransition(Bloc bloc, Transition transition) { + super.onTransition(bloc, transition); + logMessage('${bloc.runtimeType} transition: Event ${transition.event} was dispatched. Transition occurred from ${transition.currentState} to ${transition.nextState}'); + } + + @override + void onError(BlocBase bloc, Object error, StackTrace stackTrace) { + super.onError(bloc, error, stackTrace); + + logWarning('${bloc.runtimeType} error: $error.'); + logError('Stacktrace: \n$stackTrace'); + } +} diff --git a/lib/logging/logging.dart b/lib/logging/logging.dart new file mode 100644 index 00000000..ac25598a --- /dev/null +++ b/lib/logging/logging.dart @@ -0,0 +1,4 @@ +export 'package:colorize_lumberdash/colorize_lumberdash.dart'; +export 'package:lumberdash/lumberdash.dart'; + +export 'log_bloc_observer.dart'; \ No newline at end of file diff --git a/pubspec.lock b/pubspec.lock index f2d9613e..2456fa83 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -17,6 +17,14 @@ packages: url: "https://pub.dev" source: hosted version: "5.13.0" + ansicolor: + dependency: transitive + description: + name: ansicolor + sha256: "607f8fa9786f392043f169898923e6c59b4518242b68b8862eb8a8b7d9c30b4a" + url: "https://pub.dev" + source: hosted + version: "2.0.1" args: dependency: transitive description: @@ -81,6 +89,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.17.1" + colorize_lumberdash: + dependency: "direct main" + description: + name: colorize_lumberdash + sha256: "6069ad908445caab046e93738c4f941536b6266076f2998b4ac6c012355eb1ba" + url: "https://pub.dev" + source: hosted + version: "3.0.0" convert: dependency: transitive description: @@ -227,6 +243,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.2.0" + lumberdash: + dependency: "direct main" + description: + name: lumberdash + sha256: "1bc3750c094adb7f213a61883ca9878ba052fde52d9974b9039598c651fe096c" + url: "https://pub.dev" + source: hosted + version: "3.0.0" matcher: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 621f761c..b12681c4 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -17,6 +17,8 @@ dependencies: equatable: ^2.0.5 uuid: ^3.0.7 responsive_framework: ^1.1.0 + lumberdash: ^3.0.0 + colorize_lumberdash: ^3.0.0 dev_dependencies: flutter_test: From 10a9a9bbacb62bbe1cb03f3ba82a090fb1f1c65b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lui=CC=81s=20Arteiro?= Date: Tue, 15 Aug 2023 12:18:34 +0100 Subject: [PATCH 13/17] feat: Making logging work. #339 --- analysis_options.yaml | 2 +- lib/logging/log_bloc_observer.dart | 2 +- lib/main.dart | 7 ++++++- 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/analysis_options.yaml b/analysis_options.yaml index a11b47ff..d8d0c421 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -1,4 +1,4 @@ -# This file configures the analyzer, which statically analyzes Dart code to +# This file configures the analyzer, which statically analyses Dart code to # check for errors, warnings, and lints. # # The issues identified by the analyzer are surfaced in the UI of Dart-enabled diff --git a/lib/logging/log_bloc_observer.dart b/lib/logging/log_bloc_observer.dart index f25327e2..c2c4da5f 100644 --- a/lib/logging/log_bloc_observer.dart +++ b/lib/logging/log_bloc_observer.dart @@ -16,7 +16,7 @@ class GlobalLogBlocObserver extends BlocObserver { @override void onTransition(Bloc bloc, Transition transition) { super.onTransition(bloc, transition); - logMessage('${bloc.runtimeType} transition: Event ${transition.event} was dispatched. Transition occurred from ${transition.currentState} to ${transition.nextState}'); + logMessage('${bloc.runtimeType} transition: Event ${transition.event} was dispatched. ${transition.currentState} → ${transition.nextState}'); } @override diff --git a/lib/main.dart b/lib/main.dart index 8f7663d3..e151d95a 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,4 +1,4 @@ - +import 'package:dwyl_todo/logging/logging.dart'; import 'package:flutter/material.dart'; import 'package:responsive_framework/responsive_framework.dart'; @@ -21,6 +21,11 @@ class MainApp extends StatelessWidget { @override Widget build(BuildContext context) { + + // Setting global log bloc observer and Lumberdash + Bloc.observer = GlobalLogBlocObserver(); + putLumberdashToWork(withClients: [ColorizeLumberdash()]); + return BlocProvider( create: (context) => TodoBloc()..add(TodoListStarted()), child: MaterialApp( From 3fcb9326fae0df43050c14b3374c3f651834fbf1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lui=CC=81s=20Arteiro?= Date: Tue, 15 Aug 2023 13:04:21 +0100 Subject: [PATCH 14/17] chore: Adding testing on logging. --- test/bloc/log_bloc_observer_test.dart | 63 +++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 test/bloc/log_bloc_observer_test.dart diff --git a/test/bloc/log_bloc_observer_test.dart b/test/bloc/log_bloc_observer_test.dart new file mode 100644 index 00000000..5d593a8c --- /dev/null +++ b/test/bloc/log_bloc_observer_test.dart @@ -0,0 +1,63 @@ +import 'package:bloc/bloc.dart'; +import 'package:dwyl_todo/blocs/todo/todo_bloc.dart'; +import 'package:dwyl_todo/logging/logging.dart'; +import 'package:dwyl_todo/models/item.dart'; +import 'package:test/test.dart'; + +// This is similar to Bloc's testing. +// See https://github.com/felangel/bloc/blob/master/packages/bloc/test/bloc_observer_test.dart. +void main() { + final bloc = TodoBloc(); + final error = Exception(); + const stackTrace = StackTrace.empty; + const event = AddTodoEvent; + final change = Change(currentState: [], nextState: [Item(description: 'description')]); + final transition = Transition( + currentState: [], + event: AddTodoEvent, + nextState: [Item(description: 'description')], + ); + group('BlocObserver', () { + group('onCreate', () { + test('does nothing by default (just prints logs)', () { + // ignore: invalid_use_of_protected_member + GlobalLogBlocObserver().onCreate(bloc); + }); + }); + + group('onEvent', () { + test('does nothing by default (just prints logs)', () { + // ignore: invalid_use_of_protected_member + GlobalLogBlocObserver().onEvent(bloc, event); + }); + }); + + group('onChange', () { + test('does nothing by default (just prints logs)', () { + // ignore: invalid_use_of_protected_member + GlobalLogBlocObserver().onChange(bloc, change); + }); + }); + + group('onTransition', () { + test('does nothing by default (just prints logs)', () { + // ignore: invalid_use_of_protected_member + GlobalLogBlocObserver().onTransition(bloc, transition); + }); + }); + + group('onError', () { + test('does nothing by default (just prints logs)', () { + // ignore: invalid_use_of_protected_member + GlobalLogBlocObserver().onError(bloc, error, stackTrace); + }); + }); + + group('onClose', () { + test('does nothing by default (just prints logs)', () { + // ignore: invalid_use_of_protected_member + GlobalLogBlocObserver().onClose(bloc); + }); + }); + }); +} \ No newline at end of file From 0e72b3bd8fbf8c4ae153c918376f987fee98942d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lui=CC=81s=20Arteiro?= Date: Tue, 15 Aug 2023 13:05:30 +0100 Subject: [PATCH 15/17] chore: Fixing warnings. --- test/bloc/log_bloc_observer_test.dart | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/test/bloc/log_bloc_observer_test.dart b/test/bloc/log_bloc_observer_test.dart index 5d593a8c..9201ffb4 100644 --- a/test/bloc/log_bloc_observer_test.dart +++ b/test/bloc/log_bloc_observer_test.dart @@ -1,8 +1,8 @@ -import 'package:bloc/bloc.dart'; import 'package:dwyl_todo/blocs/todo/todo_bloc.dart'; import 'package:dwyl_todo/logging/logging.dart'; import 'package:dwyl_todo/models/item.dart'; -import 'package:test/test.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_test/flutter_test.dart'; // This is similar to Bloc's testing. // See https://github.com/felangel/bloc/blob/master/packages/bloc/test/bloc_observer_test.dart. @@ -11,9 +11,9 @@ void main() { final error = Exception(); const stackTrace = StackTrace.empty; const event = AddTodoEvent; - final change = Change(currentState: [], nextState: [Item(description: 'description')]); + final change = Change(currentState: const [], nextState: [Item(description: 'description')]); final transition = Transition( - currentState: [], + currentState: const [], event: AddTodoEvent, nextState: [Item(description: 'description')], ); @@ -60,4 +60,4 @@ void main() { }); }); }); -} \ No newline at end of file +} From 9482b9453856033836d873e7b4e806b607d12573 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lui=CC=81s=20Arteiro?= Date: Wed, 16 Aug 2023 00:21:33 +0100 Subject: [PATCH 16/17] chore: Renaming the app name. #339 --- lib/main.dart | 3 +-- pubspec.yaml | 2 +- test/bloc/log_bloc_observer_test.dart | 6 ++--- test/bloc/todo_bloc_test.dart | 15 ++++------- test/bloc/todo_event_test.dart | 4 +-- test/bloc/todo_state_test.dart | 2 +- test/unit/todo_test.dart | 6 ++--- test/widget/widget_test.dart | 37 ++++++++++----------------- 8 files changed, 28 insertions(+), 47 deletions(-) diff --git a/lib/main.dart b/lib/main.dart index e151d95a..73d0a59d 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,4 +1,4 @@ -import 'package:dwyl_todo/logging/logging.dart'; +import 'package:dwyl_app/logging/logging.dart'; import 'package:flutter/material.dart'; import 'package:responsive_framework/responsive_framework.dart'; @@ -21,7 +21,6 @@ class MainApp extends StatelessWidget { @override Widget build(BuildContext context) { - // Setting global log bloc observer and Lumberdash Bloc.observer = GlobalLogBlocObserver(); putLumberdashToWork(withClients: [ColorizeLumberdash()]); diff --git a/pubspec.yaml b/pubspec.yaml index b12681c4..2f4c5845 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,4 +1,4 @@ -name: dwyl_todo +name: dwyl_app description: Clear your mind. Organise your life. Ignore distractions. Focus on what matters. publish_to: 'none' version: 0.1.0 diff --git a/test/bloc/log_bloc_observer_test.dart b/test/bloc/log_bloc_observer_test.dart index 9201ffb4..442e709e 100644 --- a/test/bloc/log_bloc_observer_test.dart +++ b/test/bloc/log_bloc_observer_test.dart @@ -1,6 +1,6 @@ -import 'package:dwyl_todo/blocs/todo/todo_bloc.dart'; -import 'package:dwyl_todo/logging/logging.dart'; -import 'package:dwyl_todo/models/item.dart'; +import 'package:dwyl_app/blocs/todo/todo_bloc.dart'; +import 'package:dwyl_app/logging/logging.dart'; +import 'package:dwyl_app/models/item.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_test/flutter_test.dart'; diff --git a/test/bloc/todo_bloc_test.dart b/test/bloc/todo_bloc_test.dart index 990f5bdc..4f615adb 100644 --- a/test/bloc/todo_bloc_test.dart +++ b/test/bloc/todo_bloc_test.dart @@ -1,6 +1,6 @@ import 'package:bloc_test/bloc_test.dart'; -import 'package:dwyl_todo/blocs/blocs.dart'; -import 'package:dwyl_todo/models/item.dart'; +import 'package:dwyl_app/blocs/blocs.dart'; +import 'package:dwyl_app/models/item.dart'; import 'package:flutter_test/flutter_test.dart'; void main() { @@ -37,10 +37,7 @@ void main() { ..add(AddTodoEvent(newItem)) ..add(RemoveTodoEvent(newItem)); // add and remove }, - expect: () => [ - const TodoListLoadedState(items: []), - const TodoListLoadedState(items: []) - ], + expect: () => [const TodoListLoadedState(items: []), const TodoListLoadedState(items: [])], ); blocTest( @@ -54,10 +51,8 @@ void main() { }, expect: () => [ isA(), - isA() - .having((obj) => obj.items.first.completed, 'completed', false), - isA() - .having((obj) => obj.items.first.completed, 'completed', true) + isA().having((obj) => obj.items.first.completed, 'completed', false), + isA().having((obj) => obj.items.first.completed, 'completed', true) ], ); }); diff --git a/test/bloc/todo_event_test.dart b/test/bloc/todo_event_test.dart index e0e8c5bc..ae701431 100644 --- a/test/bloc/todo_event_test.dart +++ b/test/bloc/todo_event_test.dart @@ -1,6 +1,6 @@ -import 'package:dwyl_todo/blocs/blocs.dart'; +import 'package:dwyl_app/blocs/blocs.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:dwyl_todo/models/item.dart'; +import 'package:dwyl_app/models/item.dart'; void main() { group('TodoEvent', () { diff --git a/test/bloc/todo_state_test.dart b/test/bloc/todo_state_test.dart index 757827fd..4850b7d0 100644 --- a/test/bloc/todo_state_test.dart +++ b/test/bloc/todo_state_test.dart @@ -1,5 +1,5 @@ import 'package:flutter_test/flutter_test.dart'; -import 'package:dwyl_todo/blocs/blocs.dart'; +import 'package:dwyl_app/blocs/blocs.dart'; void main() { group('TodoState', () { diff --git a/test/unit/todo_test.dart b/test/unit/todo_test.dart index 24050e93..67d4171a 100644 --- a/test/unit/todo_test.dart +++ b/test/unit/todo_test.dart @@ -1,12 +1,10 @@ import 'dart:io'; import 'package:flutter_test/flutter_test.dart'; -import 'package:dwyl_todo/models/item.dart'; +import 'package:dwyl_app/models/item.dart'; void main() { - test( - 'Cumulative duration after starting and stopping timer should be more than 0', - () { + test('Cumulative duration after starting and stopping timer should be more than 0', () { const description = 'description'; final item = Item(description: description); diff --git a/test/widget/widget_test.dart b/test/widget/widget_test.dart index 6783cdda..b15b2dfb 100644 --- a/test/widget/widget_test.dart +++ b/test/widget/widget_test.dart @@ -1,12 +1,11 @@ -import 'package:dwyl_todo/presentation/views/views.dart'; -import 'package:dwyl_todo/presentation/widgets/widgets.dart'; +import 'package:dwyl_app/presentation/views/views.dart'; +import 'package:dwyl_app/presentation/widgets/widgets.dart'; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:dwyl_todo/main.dart'; +import 'package:dwyl_app/main.dart'; void main() { - testWidgets('Build correctly setup and is loaded', - (WidgetTester tester) async { + testWidgets('Build correctly setup and is loaded', (WidgetTester tester) async { await tester.pumpWidget(const MainApp()); await tester.pump(); @@ -14,8 +13,7 @@ void main() { expect(find.byKey(textfieldKey), findsOneWidget); }); - testWidgets('Adding a new todo item shows a card', - (WidgetTester tester) async { + testWidgets('Adding a new todo item shows a card', (WidgetTester tester) async { await tester.pumpWidget(const MainApp()); await tester.pumpAndSettle(); @@ -57,8 +55,7 @@ void main() { expect(find.byKey(itemCardWidgetKey), findsOneWidget); }); - testWidgets('Adding a new todo item shows a card (on mobile screen)', - (WidgetTester tester) async { + testWidgets('Adding a new todo item shows a card (on mobile screen)', (WidgetTester tester) async { // Ensure binding is initialized to setup camera size TestWidgetsFlutterBinding.ensureInitialized(); tester.view.physicalSize = const Size(400, 600); @@ -108,8 +105,7 @@ void main() { addTearDown(tester.view.resetPhysicalSize); }); - testWidgets('Adding a new todo item shows a card (on tablet screen)', - (WidgetTester tester) async { + testWidgets('Adding a new todo item shows a card (on tablet screen)', (WidgetTester tester) async { // Ensure binding is initialized to setup camera size TestWidgetsFlutterBinding.ensureInitialized(); tester.view.physicalSize = const Size(400, 600); @@ -159,8 +155,7 @@ void main() { addTearDown(tester.view.resetPhysicalSize); }); - testWidgets('Adding a new todo item and checking it as done', - (WidgetTester tester) async { + testWidgets('Adding a new todo item and checking it as done', (WidgetTester tester) async { await tester.pumpWidget(const MainApp()); await tester.pumpAndSettle(); @@ -201,9 +196,7 @@ void main() { expect(checkboxWidget.icon, Icons.check_box); }); - testWidgets( - 'Adding a new todo item and clicking timer button and marking it as done while it\'s running', - (WidgetTester tester) async { + testWidgets('Adding a new todo item and clicking timer button and marking it as done while it\'s running', (WidgetTester tester) async { await tester.pumpWidget(const MainApp()); await tester.pumpAndSettle(); @@ -227,8 +220,7 @@ void main() { expect(find.byKey(itemCardWidgetKey), findsOneWidget); // Getting widget to test its value - var buttonWidget = - tester.firstWidget(find.byKey(itemCardTimerButtonKey)); + var buttonWidget = tester.firstWidget(find.byKey(itemCardTimerButtonKey)); // Button should be stopped var buttonText = buttonWidget.child as Text; @@ -240,8 +232,7 @@ void main() { await tester.pumpAndSettle(); // Updating widget and button should be ongoing - buttonWidget = - tester.firstWidget(find.byKey(itemCardTimerButtonKey)); + buttonWidget = tester.firstWidget(find.byKey(itemCardTimerButtonKey)); buttonText = buttonWidget.child as Text; expect(buttonText.data, 'Stop'); @@ -251,8 +242,7 @@ void main() { await tester.pumpAndSettle(); // Updating widget and button should be stopped - buttonWidget = - tester.firstWidget(find.byKey(itemCardTimerButtonKey)); + buttonWidget = tester.firstWidget(find.byKey(itemCardTimerButtonKey)); buttonText = buttonWidget.child as Text; expect(buttonText.data, 'Resume'); @@ -262,8 +252,7 @@ void main() { await tester.pumpAndSettle(); // Updating widget and button should be ongoing - buttonWidget = - tester.firstWidget(find.byKey(itemCardTimerButtonKey)); + buttonWidget = tester.firstWidget(find.byKey(itemCardTimerButtonKey)); buttonText = buttonWidget.child as Text; expect(buttonText.data, 'Stop'); From 6bd265f04868734018800a5f00e75e2e4ea46700 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lui=CC=81s=20Arteiro?= Date: Wed, 16 Aug 2023 00:25:19 +0100 Subject: [PATCH 17/17] fix: Upgrading checkout and codecov to v3 due to Node16 deprecation notice. --- .github/workflows/ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c237a661..ed8021b2 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -31,7 +31,7 @@ jobs: - name: Run tests run: flutter test --coverage - - uses: codecov/codecov-action@v2 + - uses: codecov/codecov-action@v3 with: files: coverage/lcov.info verbose: true # optional (default = false) @@ -49,7 +49,7 @@ jobs: env: FLY_API_TOKEN: ${{ secrets.FLY_API_TOKEN }} steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Install Flutter uses: subosito/flutter-action@v2