From 06137a483b3dc699e0090cb6a7b2bb0a3a1559ea Mon Sep 17 00:00:00 2001 From: Gabriel Moro Date: Sun, 14 Dec 2025 16:38:20 -0300 Subject: [PATCH 1/7] Expandable fab menu support for Checklists screen (large version) --- .../checklist_expandable_fab_menu.dart | 45 +++++++++ .../checklist/checklist_item_widget.dart | 10 +- .../checklist_navigation_rail_menu.dart | 44 --------- .../components/widgets/task_form_widget.dart | 1 + lib/ui/l10n/app_en.arb | 4 +- lib/ui/l10n/app_pt.arb | 4 +- .../screens/checklists/checklists_screen.dart | 99 +++++++------------ pubspec.lock | 8 ++ pubspec.yaml | 1 + 9 files changed, 95 insertions(+), 121 deletions(-) create mode 100644 lib/ui/components/widgets/checklist/checklist_expandable_fab_menu.dart delete mode 100644 lib/ui/components/widgets/checklist/checklist_navigation_rail_menu.dart diff --git a/lib/ui/components/widgets/checklist/checklist_expandable_fab_menu.dart b/lib/ui/components/widgets/checklist/checklist_expandable_fab_menu.dart new file mode 100644 index 0000000..054417b --- /dev/null +++ b/lib/ui/components/widgets/checklist/checklist_expandable_fab_menu.dart @@ -0,0 +1,45 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_expandable_fab/flutter_expandable_fab.dart'; +import 'package:todoapp/ui/l10n/app_localizations.dart'; + +class ChecklistExpandableFabMenu extends StatelessWidget { + final Function() onNewChecklistPressed; + final Function() onNewTaskPressed; + + const ChecklistExpandableFabMenu({ + super.key, + required this.onNewChecklistPressed, + required this.onNewTaskPressed, + }); + + @override + Widget build(BuildContext context) { + final localizations = AppLocalizations.of(context)!; + + return ExpandableFab( + type: ExpandableFabType.side, + children: [ + Column( + children: [ + FloatingActionButton.small( + onPressed: onNewTaskPressed, + child: const Icon(Icons.add_task), + ), + const SizedBox(width: 10), + Text(localizations.add_task), + ], + ), + Column( + children: [ + FloatingActionButton.small( + onPressed: onNewChecklistPressed, + child: const Icon(Icons.plus_one), + ), + const SizedBox(width: 10), + Text(localizations.add_checklist), + ], + ) + ], + ); + } +} diff --git a/lib/ui/components/widgets/checklist/checklist_item_widget.dart b/lib/ui/components/widgets/checklist/checklist_item_widget.dart index d8311d9..168cac4 100644 --- a/lib/ui/components/widgets/checklist/checklist_item_widget.dart +++ b/lib/ui/components/widgets/checklist/checklist_item_widget.dart @@ -54,15 +54,7 @@ class ChecklistItemWidget extends StatelessWidget { clipBehavior: Clip.hardEdge, child: InkWell( onTap: () => onSelectChecklist(checklist), - child: Padding( - padding: const EdgeInsets.only( - left: 8, - right: 8, - top: 16, - bottom: 16, - ), - child: _internalContent(context, checklist), - ), + child: _internalContent(context, checklist), ), ); } diff --git a/lib/ui/components/widgets/checklist/checklist_navigation_rail_menu.dart b/lib/ui/components/widgets/checklist/checklist_navigation_rail_menu.dart deleted file mode 100644 index 582c56d..0000000 --- a/lib/ui/components/widgets/checklist/checklist_navigation_rail_menu.dart +++ /dev/null @@ -1,44 +0,0 @@ -import 'package:flutter/material.dart'; - -class ChecklistNavigationRailMenu extends StatelessWidget { - final IconData newChecklistIcon; - final String newChecklistLabel; - final IconData newTaskIcon; - final String newTaskLabel; - final VoidCallback onNewChecklistPressed; - final VoidCallback onNewTaskPressed; - - const ChecklistNavigationRailMenu({ - super.key, - required this.newChecklistIcon, - required this.newChecklistLabel, - required this.newTaskIcon, - required this.newTaskLabel, - required this.onNewChecklistPressed, - required this.onNewTaskPressed, - }); - - @override - Widget build(BuildContext context) { - return NavigationRail( - destinations: [ - NavigationRailDestination( - icon: IconButton( - icon: Icon(newChecklistIcon), - onPressed: onNewChecklistPressed, - ), - label: Text(newChecklistLabel), - ), - NavigationRailDestination( - icon: IconButton( - icon: Icon(newTaskIcon), - onPressed: onNewTaskPressed, - ), - label: Text(newTaskLabel), - ), - ], - labelType: NavigationRailLabelType.all, - selectedIndex: null, - ); - } -} diff --git a/lib/ui/components/widgets/task_form_widget.dart b/lib/ui/components/widgets/task_form_widget.dart index d561bcb..2941161 100644 --- a/lib/ui/components/widgets/task_form_widget.dart +++ b/lib/ui/components/widgets/task_form_widget.dart @@ -27,6 +27,7 @@ class TaskFormWidget extends StatelessWidget { children: [ TextFormField( autofocus: true, + maxLines: 3, controller: taskEditingController, decoration: InputDecoration( labelText: taskLabel, diff --git a/lib/ui/l10n/app_en.arb b/lib/ui/l10n/app_en.arb index 884a97d..193ad57 100644 --- a/lib/ui/l10n/app_en.arb +++ b/lib/ui/l10n/app_en.arb @@ -18,5 +18,7 @@ "checklist": "Checklist", "checklist_name": "Checklist name", "remove": "Remove", - "sort_message": "It is sorted" + "sort_message": "It is sorted", + "add_task": "Add task", + "add_checklist": "Add checklist" } \ No newline at end of file diff --git a/lib/ui/l10n/app_pt.arb b/lib/ui/l10n/app_pt.arb index b962663..683250b 100644 --- a/lib/ui/l10n/app_pt.arb +++ b/lib/ui/l10n/app_pt.arb @@ -18,5 +18,7 @@ "checklist": "Checklist", "checklist_name": "Nome do Checklist", "remove": "Deletar", - "sort_message": "Está ordenado" + "sort_message": "Está ordenado", + "add_task": "Adicionar tarefa", + "add_checklist": "Adicionar checklist" } \ No newline at end of file diff --git a/lib/ui/screens/checklists/checklists_screen.dart b/lib/ui/screens/checklists/checklists_screen.dart index ccb64eb..f50b917 100644 --- a/lib/ui/screens/checklists/checklists_screen.dart +++ b/lib/ui/screens/checklists/checklists_screen.dart @@ -1,9 +1,10 @@ import 'package:auto_route/auto_route.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_expandable_fab/flutter_expandable_fab.dart'; import 'package:todoapp/data/model/checklist.dart'; +import 'package:todoapp/ui/components/widgets/checklist/checklist_expandable_fab_menu.dart'; import 'package:todoapp/ui/components/widgets/checklist/checklist_full_widget.dart'; -import 'package:todoapp/ui/components/widgets/checklist/checklist_navigation_rail_menu.dart'; import 'package:todoapp/ui/components/widgets/checklist/checklists_list_widget.dart'; import 'package:todoapp/ui/components/widgets/confirmation_alert_dialog_widget.dart'; import 'package:todoapp/ui/components/widgets/custom_app_bar_widget.dart'; @@ -44,6 +45,7 @@ class ChecklistsScaffold extends StatelessWidget { final Function(Checklist) onRemoveChecklist; final Function updateChecklists; final NavigatorProvider navigatorProvider; + /// Use key to access a specific internal behavior of TaskViewModel /// to update the task list through ChecklistFullWidget. final GlobalKey _checklistFullKey = @@ -59,13 +61,26 @@ class ChecklistsScaffold extends StatelessWidget { Widget _buildFloatingActionButton({ required bool isBigSize, - required Function() onPressed, + required BuildContext context, }) { if (isBigSize) { - return const SizedBox.shrink(); + return ChecklistExpandableFabMenu( + onNewChecklistPressed: () async { + await _addNewChecklistEvent(context); + }, + onNewTaskPressed: () async { + /// Use key to access a specific internal behavior of + /// TaskViewModel to update the task list through + /// ChecklistFullWidget. + final currentChecklistFullState = _checklistFullKey.currentState; + currentChecklistFullState?.addNewTaskToExistingChecklist(context); + }, + ); } else { return FloatingActionButton( - onPressed: onPressed, + onPressed: () async { + await _addNewChecklistEvent(context); + }, child: const Icon(Icons.plus_one), ); } @@ -77,28 +92,20 @@ class ChecklistsScaffold extends StatelessWidget { final localizations = AppLocalizations.of(context)!; return Scaffold( - backgroundColor: Theme.of(context).colorScheme.surface, - appBar: CustomAppBarWidget( - title: localizations.checklists, - ), - floatingActionButton: _buildFloatingActionButton( - isBigSize: isBigSize, - onPressed: () async { - await _addNewChecklistEvent(context); - }, - ), - body: Row( - children: [ - _buildNavigationRails(context: context, isBigSize: isBigSize), - _buildVerticalSeparator(context: context, isBigSize: isBigSize), - Expanded( - child: _buildCheckListWidget( - context: context, - isBigSize: isBigSize, - ), - ), - ], - )); + floatingActionButtonLocation: isBigSize ? ExpandableFab.location : null, + backgroundColor: Theme.of(context).colorScheme.surface, + appBar: CustomAppBarWidget( + title: localizations.checklists, + ), + floatingActionButton: _buildFloatingActionButton( + isBigSize: isBigSize, + context: context, + ), + body: _buildCheckListWidget( + context: context, + isBigSize: isBigSize, + ), + ); } Widget _buildCheckListWidget({ @@ -137,46 +144,6 @@ class ChecklistsScaffold extends StatelessWidget { ); } - Widget _buildNavigationRails({ - required BuildContext context, - required bool isBigSize, - }) { - if (isBigSize) { - return ChecklistNavigationRailMenu( - newChecklistIcon: Icons.plus_one, - newChecklistLabel: 'New Checklist', - newTaskIcon: Icons.add_task, - newTaskLabel: 'New task', - onNewTaskPressed: () async { - /// Use key to access a specific internal behavior of TaskViewModel - /// to update the task list through ChecklistFullWidget. - final currentChecklistFullState = _checklistFullKey.currentState; - currentChecklistFullState?.addNewTaskToExistingChecklist( - context - ); - }, - onNewChecklistPressed: () async { - await _addNewChecklistEvent(context); - }, - ); - } else { - return const SizedBox.shrink(); - } - } - - Widget _buildVerticalSeparator({ - required BuildContext context, - required bool isBigSize, - }) { - if (isBigSize) { - return const VerticalDivider( - thickness: 0.2, - ); - } else { - return const SizedBox.shrink(); - } - } - void _showConfirmationDialogToRemoveChecklist( BuildContext context, Checklist checklist, diff --git a/pubspec.lock b/pubspec.lock index 7b2a0ea..dc52c22 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -294,6 +294,14 @@ packages: url: "https://pub.dev" source: hosted version: "9.1.1" + flutter_expandable_fab: + dependency: "direct main" + description: + name: flutter_expandable_fab + sha256: "2a488600924fd2a041679ad889807ee5670414a7a518cf11d4854b9898b3504f" + url: "https://pub.dev" + source: hosted + version: "2.5.2" flutter_lints: dependency: "direct dev" description: diff --git a/pubspec.yaml b/pubspec.yaml index 839822d..dd51b27 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -48,6 +48,7 @@ dependencies: path: ^1.9.1 share_plus: 12.0.1 auto_route: ^10.1.0+1 + flutter_expandable_fab: ^2.5.2 dev_dependencies: flutter_test: From 3c06193fc476fec14c3bb2ca71d478854822e81f Mon Sep 17 00:00:00 2001 From: Gabriel Moro Date: Sun, 14 Dec 2025 16:49:42 -0300 Subject: [PATCH 2/7] Expandable fab menu support for Checklists screen (large version) --- .../checklist_expandable_fab_menu.dart | 31 +++++++------------ lib/ui/l10n/app_en.arb | 4 +-- lib/ui/l10n/app_pt.arb | 4 +-- .../screens/checklists/checklists_screen.dart | 2 +- 4 files changed, 15 insertions(+), 26 deletions(-) rename lib/ui/components/widgets/checklist/{ => fabmenu}/checklist_expandable_fab_menu.dart (53%) diff --git a/lib/ui/components/widgets/checklist/checklist_expandable_fab_menu.dart b/lib/ui/components/widgets/checklist/fabmenu/checklist_expandable_fab_menu.dart similarity index 53% rename from lib/ui/components/widgets/checklist/checklist_expandable_fab_menu.dart rename to lib/ui/components/widgets/checklist/fabmenu/checklist_expandable_fab_menu.dart index 054417b..09ffbdb 100644 --- a/lib/ui/components/widgets/checklist/checklist_expandable_fab_menu.dart +++ b/lib/ui/components/widgets/checklist/fabmenu/checklist_expandable_fab_menu.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_expandable_fab/flutter_expandable_fab.dart'; +import 'package:todoapp/ui/components/widgets/checklist/fabmenu/checklist_fabmenu_item.dart'; import 'package:todoapp/ui/l10n/app_localizations.dart'; class ChecklistExpandableFabMenu extends StatelessWidget { @@ -19,26 +20,18 @@ class ChecklistExpandableFabMenu extends StatelessWidget { return ExpandableFab( type: ExpandableFabType.side, children: [ - Column( - children: [ - FloatingActionButton.small( - onPressed: onNewTaskPressed, - child: const Icon(Icons.add_task), - ), - const SizedBox(width: 10), - Text(localizations.add_task), - ], + ChecklistFabMenuItem( + heroTag: 'new_task', + onPressed: onNewTaskPressed, + label: localizations.task, + icon: Icons.add_task, + ), + ChecklistFabMenuItem( + heroTag: 'new_checklist', + onPressed: onNewChecklistPressed, + label: localizations.checklist, + icon: Icons.plus_one, ), - Column( - children: [ - FloatingActionButton.small( - onPressed: onNewChecklistPressed, - child: const Icon(Icons.plus_one), - ), - const SizedBox(width: 10), - Text(localizations.add_checklist), - ], - ) ], ); } diff --git a/lib/ui/l10n/app_en.arb b/lib/ui/l10n/app_en.arb index 193ad57..884a97d 100644 --- a/lib/ui/l10n/app_en.arb +++ b/lib/ui/l10n/app_en.arb @@ -18,7 +18,5 @@ "checklist": "Checklist", "checklist_name": "Checklist name", "remove": "Remove", - "sort_message": "It is sorted", - "add_task": "Add task", - "add_checklist": "Add checklist" + "sort_message": "It is sorted" } \ No newline at end of file diff --git a/lib/ui/l10n/app_pt.arb b/lib/ui/l10n/app_pt.arb index 683250b..b962663 100644 --- a/lib/ui/l10n/app_pt.arb +++ b/lib/ui/l10n/app_pt.arb @@ -18,7 +18,5 @@ "checklist": "Checklist", "checklist_name": "Nome do Checklist", "remove": "Deletar", - "sort_message": "Está ordenado", - "add_task": "Adicionar tarefa", - "add_checklist": "Adicionar checklist" + "sort_message": "Está ordenado" } \ No newline at end of file diff --git a/lib/ui/screens/checklists/checklists_screen.dart b/lib/ui/screens/checklists/checklists_screen.dart index f50b917..cebfacb 100644 --- a/lib/ui/screens/checklists/checklists_screen.dart +++ b/lib/ui/screens/checklists/checklists_screen.dart @@ -3,9 +3,9 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_expandable_fab/flutter_expandable_fab.dart'; import 'package:todoapp/data/model/checklist.dart'; -import 'package:todoapp/ui/components/widgets/checklist/checklist_expandable_fab_menu.dart'; import 'package:todoapp/ui/components/widgets/checklist/checklist_full_widget.dart'; import 'package:todoapp/ui/components/widgets/checklist/checklists_list_widget.dart'; +import 'package:todoapp/ui/components/widgets/checklist/fabmenu/checklist_expandable_fab_menu.dart'; import 'package:todoapp/ui/components/widgets/confirmation_alert_dialog_widget.dart'; import 'package:todoapp/ui/components/widgets/custom_app_bar_widget.dart'; import 'package:todoapp/ui/l10n/app_localizations.dart'; From 2039e611dc146797fc62be7d93db590bd058b818 Mon Sep 17 00:00:00 2001 From: Gabriel Moro Date: Sun, 14 Dec 2025 16:49:47 -0300 Subject: [PATCH 3/7] Expandable fab menu support for Checklists screen (large version) --- .../fabmenu/checklist_fabmenu_item.dart | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 lib/ui/components/widgets/checklist/fabmenu/checklist_fabmenu_item.dart diff --git a/lib/ui/components/widgets/checklist/fabmenu/checklist_fabmenu_item.dart b/lib/ui/components/widgets/checklist/fabmenu/checklist_fabmenu_item.dart new file mode 100644 index 0000000..06ec999 --- /dev/null +++ b/lib/ui/components/widgets/checklist/fabmenu/checklist_fabmenu_item.dart @@ -0,0 +1,30 @@ +import 'package:flutter/material.dart'; + +class ChecklistFabMenuItem extends StatelessWidget { + final String heroTag; + final String label; + final IconData icon; + final Function() onPressed; + + const ChecklistFabMenuItem( + {super.key, + required this.label, + required this.onPressed, + required this.icon, + required this.heroTag}); + + @override + Widget build(BuildContext context) { + return Column( + children: [ + FloatingActionButton.small( + heroTag: heroTag, + onPressed: onPressed, + child: Icon(icon), + ), + const SizedBox(height: 8), + Text(label), + ], + ); + } +} From ca4c2d95ae7992a8714240d9c5a842e2d5a26606 Mon Sep 17 00:00:00 2001 From: Gabriel Moro Date: Sun, 14 Dec 2025 17:10:27 -0300 Subject: [PATCH 4/7] Clip the ripple effect of task widget cell to edge --- lib/ui/components/widgets/card_wrapper.dart | 30 +++++++++++++++++++ .../checklist/checklist_item_widget.dart | 18 ++++------- .../widgets/task/task_cell_widget.dart | 11 +++---- 3 files changed, 39 insertions(+), 20 deletions(-) create mode 100644 lib/ui/components/widgets/card_wrapper.dart diff --git a/lib/ui/components/widgets/card_wrapper.dart b/lib/ui/components/widgets/card_wrapper.dart new file mode 100644 index 0000000..1f6a07e --- /dev/null +++ b/lib/ui/components/widgets/card_wrapper.dart @@ -0,0 +1,30 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; + +class CardWrapper extends StatelessWidget { + final Color backgroundColor; + final Function()? onTap; + final Widget child; + + const CardWrapper( + {super.key, + required this.child, + required this.onTap, + required this.backgroundColor}); + + @override + Widget build(BuildContext context) { + return Card( + elevation: 2, + shape: const RoundedRectangleBorder( + borderRadius: BorderRadius.all(Radius.circular(12)), + ), + color: backgroundColor, + clipBehavior: Clip.hardEdge, + child: InkWell( + onTap: onTap, + child: child, + ), + ); + } +} diff --git a/lib/ui/components/widgets/checklist/checklist_item_widget.dart b/lib/ui/components/widgets/checklist/checklist_item_widget.dart index 168cac4..d9b13ff 100644 --- a/lib/ui/components/widgets/checklist/checklist_item_widget.dart +++ b/lib/ui/components/widgets/checklist/checklist_item_widget.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:todoapp/data/model/checklist.dart'; +import 'package:todoapp/ui/components/widgets/card_wrapper.dart'; class ChecklistItemWidget extends StatelessWidget { final Checklist checklist; @@ -43,19 +44,10 @@ class ChecklistItemWidget extends StatelessWidget { backgroundColor = Theme.of(context).colorScheme.tertiaryContainer; } - return Card( - elevation: 2, - shape: const RoundedRectangleBorder( - borderRadius: BorderRadius.all( - Radius.circular(12), - ), - ), - color: backgroundColor, - clipBehavior: Clip.hardEdge, - child: InkWell( - onTap: () => onSelectChecklist(checklist), - child: _internalContent(context, checklist), - ), + return CardWrapper( + onTap: () => onSelectChecklist(checklist), + backgroundColor: backgroundColor, + child: _internalContent(context, checklist), ); } } diff --git a/lib/ui/components/widgets/task/task_cell_widget.dart b/lib/ui/components/widgets/task/task_cell_widget.dart index e0094b5..cd5f02c 100644 --- a/lib/ui/components/widgets/task/task_cell_widget.dart +++ b/lib/ui/components/widgets/task/task_cell_widget.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:todoapp/data/model/task.dart'; +import 'package:todoapp/ui/components/widgets/card_wrapper.dart'; import 'package:todoapp/ui/components/widgets/task/task_title_widget.dart'; class TaskCellWidget extends StatelessWidget { @@ -20,13 +21,9 @@ class TaskCellWidget extends StatelessWidget { @override Widget build(BuildContext context) { - const cardShape = RoundedRectangleBorder( - borderRadius: BorderRadius.all(Radius.circular(8)), - ); - - return Card( - elevation: 4, - shape: cardShape, + return CardWrapper( + onTap: null, + backgroundColor: Theme.of(context).colorScheme.surfaceBright, child: ListTile( leading: IconButton( onPressed: () => {onRemoveTask(task)}, From 70a536dc329840c505d3a2e5f2521b8c6667e503 Mon Sep 17 00:00:00 2001 From: Gabriel Moro Date: Sun, 14 Dec 2025 17:25:59 -0300 Subject: [PATCH 5/7] Update widget components to use the suffix 'widget' --- ..._wrapper.dart => card_wrapper_widget.dart} | 5 ++-- .../checklist_form_widget.dart | 23 ++++++--------- .../checklist/checklist_item_widget.dart | 4 +-- .../widgets/form/form_field_widget.dart | 29 +++++++++++++++++++ .../widgets/task/task_cell_widget.dart | 4 +-- .../widgets/{ => task}/task_form_widget.dart | 11 ++----- .../screens/checklist/checklist_screen.dart | 2 +- lib/ui/screens/task/task_screen.dart | 2 +- 8 files changed, 49 insertions(+), 31 deletions(-) rename lib/ui/components/widgets/{card_wrapper.dart => card_wrapper_widget.dart} (84%) rename lib/ui/components/widgets/{ => checklist}/checklist_form_widget.dart (63%) create mode 100644 lib/ui/components/widgets/form/form_field_widget.dart rename lib/ui/components/widgets/{ => task}/task_form_widget.dart (78%) diff --git a/lib/ui/components/widgets/card_wrapper.dart b/lib/ui/components/widgets/card_wrapper_widget.dart similarity index 84% rename from lib/ui/components/widgets/card_wrapper.dart rename to lib/ui/components/widgets/card_wrapper_widget.dart index 1f6a07e..b1d81d1 100644 --- a/lib/ui/components/widgets/card_wrapper.dart +++ b/lib/ui/components/widgets/card_wrapper_widget.dart @@ -1,12 +1,11 @@ -import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; -class CardWrapper extends StatelessWidget { +class CardWrapperWidget extends StatelessWidget { final Color backgroundColor; final Function()? onTap; final Widget child; - const CardWrapper( + const CardWrapperWidget( {super.key, required this.child, required this.onTap, diff --git a/lib/ui/components/widgets/checklist_form_widget.dart b/lib/ui/components/widgets/checklist/checklist_form_widget.dart similarity index 63% rename from lib/ui/components/widgets/checklist_form_widget.dart rename to lib/ui/components/widgets/checklist/checklist_form_widget.dart index cb540c1..e87850c 100644 --- a/lib/ui/components/widgets/checklist_form_widget.dart +++ b/lib/ui/components/widgets/checklist/checklist_form_widget.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:todoapp/ui/components/form_validator.dart'; +import 'package:todoapp/ui/components/widgets/form/form_field_widget.dart'; class ChecklistFormWidget extends StatelessWidget { final Key formKey; @@ -24,21 +25,15 @@ class ChecklistFormWidget extends StatelessWidget { autovalidateMode: AutovalidateMode.onUserInteraction, child: Column( children: [ - TextFormField( - autofocus: true, - controller: checklistEditingController, - decoration: InputDecoration( + FormFieldWidget( labelText: checklistLabel, - labelStyle: Theme.of(context).textTheme.titleMedium, - border: const OutlineInputBorder(), - ), - validator: (value) { - if (formScreenValidator.validateValue(value) == false) { - return checklistErrorMessage; - } - return null; - }, - ), + controller: checklistEditingController, + validator: (value) { + if (formScreenValidator.validateValue(value) == false) { + return checklistErrorMessage; + } + return null; + }), ], ), ); diff --git a/lib/ui/components/widgets/checklist/checklist_item_widget.dart b/lib/ui/components/widgets/checklist/checklist_item_widget.dart index d9b13ff..04caadf 100644 --- a/lib/ui/components/widgets/checklist/checklist_item_widget.dart +++ b/lib/ui/components/widgets/checklist/checklist_item_widget.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; import 'package:todoapp/data/model/checklist.dart'; -import 'package:todoapp/ui/components/widgets/card_wrapper.dart'; +import 'package:todoapp/ui/components/widgets/card_wrapper_widget.dart'; class ChecklistItemWidget extends StatelessWidget { final Checklist checklist; @@ -44,7 +44,7 @@ class ChecklistItemWidget extends StatelessWidget { backgroundColor = Theme.of(context).colorScheme.tertiaryContainer; } - return CardWrapper( + return CardWrapperWidget( onTap: () => onSelectChecklist(checklist), backgroundColor: backgroundColor, child: _internalContent(context, checklist), diff --git a/lib/ui/components/widgets/form/form_field_widget.dart b/lib/ui/components/widgets/form/form_field_widget.dart new file mode 100644 index 0000000..2315f7a --- /dev/null +++ b/lib/ui/components/widgets/form/form_field_widget.dart @@ -0,0 +1,29 @@ +import 'package:flutter/material.dart'; + +class FormFieldWidget extends StatelessWidget { + final String labelText; + final TextEditingController controller; + final String? Function(String?)? validator; + + const FormFieldWidget({ + super.key, + required this.labelText, + required this.controller, + this.validator, + }); + + @override + Widget build(BuildContext context) { + return TextFormField( + autofocus: true, + maxLines: 3, + controller: controller, + decoration: InputDecoration( + labelText: labelText, + labelStyle: Theme.of(context).textTheme.titleMedium, + border: const OutlineInputBorder(), + ), + validator: validator, + ); + } +} diff --git a/lib/ui/components/widgets/task/task_cell_widget.dart b/lib/ui/components/widgets/task/task_cell_widget.dart index cd5f02c..9a0d4b5 100644 --- a/lib/ui/components/widgets/task/task_cell_widget.dart +++ b/lib/ui/components/widgets/task/task_cell_widget.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; import 'package:todoapp/data/model/task.dart'; -import 'package:todoapp/ui/components/widgets/card_wrapper.dart'; +import 'package:todoapp/ui/components/widgets/card_wrapper_widget.dart'; import 'package:todoapp/ui/components/widgets/task/task_title_widget.dart'; class TaskCellWidget extends StatelessWidget { @@ -21,7 +21,7 @@ class TaskCellWidget extends StatelessWidget { @override Widget build(BuildContext context) { - return CardWrapper( + return CardWrapperWidget( onTap: null, backgroundColor: Theme.of(context).colorScheme.surfaceBright, child: ListTile( diff --git a/lib/ui/components/widgets/task_form_widget.dart b/lib/ui/components/widgets/task/task_form_widget.dart similarity index 78% rename from lib/ui/components/widgets/task_form_widget.dart rename to lib/ui/components/widgets/task/task_form_widget.dart index 2941161..d99b53d 100644 --- a/lib/ui/components/widgets/task_form_widget.dart +++ b/lib/ui/components/widgets/task/task_form_widget.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:todoapp/ui/components/form_validator.dart'; +import 'package:todoapp/ui/components/widgets/form/form_field_widget.dart'; class TaskFormWidget extends StatelessWidget { final Key formKey; @@ -25,15 +26,9 @@ class TaskFormWidget extends StatelessWidget { autovalidateMode: AutovalidateMode.onUserInteraction, child: Column( children: [ - TextFormField( - autofocus: true, - maxLines: 3, + FormFieldWidget( + labelText: taskLabel, controller: taskEditingController, - decoration: InputDecoration( - labelText: taskLabel, - labelStyle: Theme.of(context).textTheme.titleMedium, - border: const OutlineInputBorder(), - ), validator: (value) { if (formScreenValidator.validateValue(value) == false) { return taskErrorMessage; diff --git a/lib/ui/screens/checklist/checklist_screen.dart b/lib/ui/screens/checklist/checklist_screen.dart index e370dcd..6f3e895 100644 --- a/lib/ui/screens/checklist/checklist_screen.dart +++ b/lib/ui/screens/checklist/checklist_screen.dart @@ -1,7 +1,7 @@ import 'package:auto_route/auto_route.dart'; import 'package:flutter/material.dart'; import 'package:todoapp/ui/components/form_validator.dart'; -import 'package:todoapp/ui/components/widgets/checklist_form_widget.dart'; +import 'package:todoapp/ui/components/widgets/checklist/checklist_form_widget.dart'; import 'package:todoapp/ui/components/widgets/custom_app_bar_widget.dart'; import 'package:todoapp/ui/l10n/app_localizations.dart'; import 'package:todoapp/ui/screens/checklist/checklist_viewmodel.dart'; diff --git a/lib/ui/screens/task/task_screen.dart b/lib/ui/screens/task/task_screen.dart index b2fe8d4..2bb9c71 100644 --- a/lib/ui/screens/task/task_screen.dart +++ b/lib/ui/screens/task/task_screen.dart @@ -3,7 +3,7 @@ import 'package:flutter/material.dart'; import 'package:todoapp/data/model/task.dart'; import 'package:todoapp/ui/components/form_validator.dart'; import 'package:todoapp/ui/components/widgets/custom_app_bar_widget.dart'; -import 'package:todoapp/ui/components/widgets/task_form_widget.dart'; +import 'package:todoapp/ui/components/widgets/task/task_form_widget.dart'; import 'package:todoapp/ui/l10n/app_localizations.dart'; import 'package:todoapp/ui/screens/task/task_viewmodel.dart'; import 'package:todoapp/util/di/dependency_startup_launcher.dart'; From 53bbb0aca85838295a6a11d29644544b3f1798ab Mon Sep 17 00:00:00 2001 From: Gabriel Moro Date: Sun, 14 Dec 2025 21:55:18 -0300 Subject: [PATCH 6/7] Add blur effect and auto-close - Expandable fab menu --- .../checklist_expandable_fab_menu.dart | 25 ++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/lib/ui/components/widgets/checklist/fabmenu/checklist_expandable_fab_menu.dart b/lib/ui/components/widgets/checklist/fabmenu/checklist_expandable_fab_menu.dart index 09ffbdb..43fba65 100644 --- a/lib/ui/components/widgets/checklist/fabmenu/checklist_expandable_fab_menu.dart +++ b/lib/ui/components/widgets/checklist/fabmenu/checklist_expandable_fab_menu.dart @@ -3,7 +3,7 @@ import 'package:flutter_expandable_fab/flutter_expandable_fab.dart'; import 'package:todoapp/ui/components/widgets/checklist/fabmenu/checklist_fabmenu_item.dart'; import 'package:todoapp/ui/l10n/app_localizations.dart'; -class ChecklistExpandableFabMenu extends StatelessWidget { +class ChecklistExpandableFabMenu extends StatefulWidget { final Function() onNewChecklistPressed; final Function() onNewTaskPressed; @@ -13,22 +13,41 @@ class ChecklistExpandableFabMenu extends StatelessWidget { required this.onNewTaskPressed, }); + @override + State createState() => + _ChecklistExpandableFabMenuState(); +} + +class _ChecklistExpandableFabMenuState + extends State { + final _key = GlobalKey(); + @override Widget build(BuildContext context) { final localizations = AppLocalizations.of(context)!; return ExpandableFab( + key: _key, type: ExpandableFabType.side, + overlayStyle: const ExpandableFabOverlayStyle( + blur: 1.0, + ), children: [ ChecklistFabMenuItem( heroTag: 'new_task', - onPressed: onNewTaskPressed, + onPressed: () => { + _key.currentState?.close(), + widget.onNewTaskPressed(), + }, label: localizations.task, icon: Icons.add_task, ), ChecklistFabMenuItem( heroTag: 'new_checklist', - onPressed: onNewChecklistPressed, + onPressed: () => { + _key.currentState?.close(), + widget.onNewChecklistPressed(), + }, label: localizations.checklist, icon: Icons.plus_one, ), From 56fb28879ba706add2f39e6b988d090583471570 Mon Sep 17 00:00:00 2001 From: Gabriel Moro Date: Sun, 14 Dec 2025 22:06:23 -0300 Subject: [PATCH 7/7] Support for share and sort - Checklist full screen --- .../checklist/checklist_full_widget.dart | 11 ++++++++++ .../checklist_expandable_fab_menu.dart | 22 +++++++++++++++++++ lib/ui/l10n/app_en.arb | 4 +++- lib/ui/l10n/app_pt.arb | 4 +++- .../screens/checklists/checklists_screen.dart | 6 +++++ 5 files changed, 45 insertions(+), 2 deletions(-) diff --git a/lib/ui/components/widgets/checklist/checklist_full_widget.dart b/lib/ui/components/widgets/checklist/checklist_full_widget.dart index 0b58924..cace041 100644 --- a/lib/ui/components/widgets/checklist/checklist_full_widget.dart +++ b/lib/ui/components/widgets/checklist/checklist_full_widget.dart @@ -75,6 +75,17 @@ class ChecklistsListFullWidgetState extends State { } } + void onSortTasks() { + _tasksViewModel.onSort(); + } + + Future onShareTasks() async { + final checklistName = selected?.title; + if (checklistName != null) { + await _tasksViewModel.shareTasks(checklistName: checklistName); + } + } + Widget _buildTaskList(BuildContext context) { final localizations = AppLocalizations.of(context)!; return Row( diff --git a/lib/ui/components/widgets/checklist/fabmenu/checklist_expandable_fab_menu.dart b/lib/ui/components/widgets/checklist/fabmenu/checklist_expandable_fab_menu.dart index 43fba65..86af53f 100644 --- a/lib/ui/components/widgets/checklist/fabmenu/checklist_expandable_fab_menu.dart +++ b/lib/ui/components/widgets/checklist/fabmenu/checklist_expandable_fab_menu.dart @@ -6,11 +6,15 @@ import 'package:todoapp/ui/l10n/app_localizations.dart'; class ChecklistExpandableFabMenu extends StatefulWidget { final Function() onNewChecklistPressed; final Function() onNewTaskPressed; + final Function() onSharePressed; + final Function() onSortPressed; const ChecklistExpandableFabMenu({ super.key, required this.onNewChecklistPressed, required this.onNewTaskPressed, + required this.onSharePressed, + required this.onSortPressed, }); @override @@ -51,6 +55,24 @@ class _ChecklistExpandableFabMenuState label: localizations.checklist, icon: Icons.plus_one, ), + ChecklistFabMenuItem( + heroTag: 'share', + onPressed: () => { + _key.currentState?.close(), + widget.onSharePressed(), + }, + label: localizations.share, + icon: Icons.share, + ), + ChecklistFabMenuItem( + heroTag: 'sort', + onPressed: () => { + _key.currentState?.close(), + widget.onSortPressed(), + }, + label: localizations.sort, + icon: Icons.sort, + ) ], ); } diff --git a/lib/ui/l10n/app_en.arb b/lib/ui/l10n/app_en.arb index 884a97d..c2149c8 100644 --- a/lib/ui/l10n/app_en.arb +++ b/lib/ui/l10n/app_en.arb @@ -18,5 +18,7 @@ "checklist": "Checklist", "checklist_name": "Checklist name", "remove": "Remove", - "sort_message": "It is sorted" + "sort_message": "It is sorted", + "share": "Share", + "sort": "Sort" } \ No newline at end of file diff --git a/lib/ui/l10n/app_pt.arb b/lib/ui/l10n/app_pt.arb index b962663..89250c7 100644 --- a/lib/ui/l10n/app_pt.arb +++ b/lib/ui/l10n/app_pt.arb @@ -18,5 +18,7 @@ "checklist": "Checklist", "checklist_name": "Nome do Checklist", "remove": "Deletar", - "sort_message": "Está ordenado" + "sort_message": "Está ordenado", + "share": "Compartilhar", + "sort": "Ordenar" } \ No newline at end of file diff --git a/lib/ui/screens/checklists/checklists_screen.dart b/lib/ui/screens/checklists/checklists_screen.dart index cebfacb..a128080 100644 --- a/lib/ui/screens/checklists/checklists_screen.dart +++ b/lib/ui/screens/checklists/checklists_screen.dart @@ -68,6 +68,12 @@ class ChecklistsScaffold extends StatelessWidget { onNewChecklistPressed: () async { await _addNewChecklistEvent(context); }, + onSharePressed: () async { + await _checklistFullKey.currentState?.onShareTasks(); + }, + onSortPressed: () => { + _checklistFullKey.currentState?.onSortTasks() + }, onNewTaskPressed: () async { /// Use key to access a specific internal behavior of /// TaskViewModel to update the task list through