diff --git a/.github/actions/setup-flutter/action.yml b/.github/actions/setup-flutter/action.yml index 1b859fa..304d292 100644 --- a/.github/actions/setup-flutter/action.yml +++ b/.github/actions/setup-flutter/action.yml @@ -7,7 +7,7 @@ runs: - name: Set up Flutter uses: subosito/flutter-action@v2 with: - flutter-version: '3.32.6' # Set the desired Flutter version + flutter-version: '3.38.3' # Set the desired Flutter version - name: Install dependencies run: flutter pub get diff --git a/.github/workflows/danger.yml b/.github/workflows/danger.yml deleted file mode 100644 index aa3ba5a..0000000 --- a/.github/workflows/danger.yml +++ /dev/null @@ -1,32 +0,0 @@ -name: Danger - -on: - pull_request: - branches: - - main - -jobs: - danger: - runs-on: ubuntu-latest - - steps: - - name: Checkout repository - uses: actions/checkout@v5 - - - name: Set up Flutter - uses: ./.github/actions/setup-flutter - - - name: Install dependencies - run: flutter pub get - shell: bash - - - name: Install Danger-js - run: npm install -g danger - - - name: Activate command - run: flutter pub global activate danger_dart - - - name: Run danger ci - run: danger_dart ci - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} \ No newline at end of file diff --git a/dangerfile.dart b/dangerfile.dart deleted file mode 100644 index 6865379..0000000 --- a/dangerfile.dart +++ /dev/null @@ -1,29 +0,0 @@ -import 'package:danger_core/danger_core.dart'; - -const bigPRThreshold = 300; - -void main() { - // WIP check - if (danger.github.pr.title.contains('WIP')) { - warn('PR is considered WIP'); - } - - // Unit tests check - final allSourceFiles = [ - ...danger.git.modifiedFiles, - ...danger.git.createdFiles, - ]; - - if (allSourceFiles.any((file) => file.endsWith('_test.dart'))) { - message('Thanks for updating the unit tests'); - } - - // Big PR check - final prAdditions = danger.github.pr.additions ?? 0; - final prDeletions = danger.github.pr.additions ?? 0; - final isBigPR = (prAdditions + prDeletions) > bigPRThreshold; - - if (isBigPR) { - warn('Big PR, try to keep changes smaller if you can...'); - } -} diff --git a/lib/domain/format_task_list_message_use_case.dart b/lib/domain/format_task_list_message_use_case.dart new file mode 100644 index 0000000..2d331b3 --- /dev/null +++ b/lib/domain/format_task_list_message_use_case.dart @@ -0,0 +1,21 @@ +import 'package:injectable/injectable.dart'; +import 'package:todoapp/data/model/task.dart'; + +abstract class FormatTaskListMessageUseCase { + String formatTaskList({required List tasks}); +} + +@Injectable(as: FormatTaskListMessageUseCase) +class FormatTaskListMessageUseCaseImpl extends FormatTaskListMessageUseCase { + @override + String formatTaskList({required List tasks}) { + var checklist = ''; + + for (var task in tasks) { + if (task.isCompleted == false) { + checklist += '- ${task.title}\n'; + } + } + return checklist; + } +} diff --git a/lib/domain/progress_counter_use_case.dart b/lib/domain/progress_counter_use_case.dart new file mode 100644 index 0000000..992786f --- /dev/null +++ b/lib/domain/progress_counter_use_case.dart @@ -0,0 +1,25 @@ +import 'package:injectable/injectable.dart'; +import 'package:todoapp/data/model/task.dart'; + +abstract class ProgressCounterUseCase { + double calculateProgress({required List tasks}); +} + +@Injectable(as: ProgressCounterUseCase) +class ProgressCounterUseCaseImpl extends ProgressCounterUseCase { + @override + double calculateProgress({required List tasks}) { + int completedTasks = 0; + for (var task in tasks) { + if (task.isCompleted) { + completedTasks++; + } + } + + if (tasks.isNotEmpty) { + return completedTasks / tasks.length.toDouble(); + } else { + return 0.0; + } + } +} diff --git a/lib/domain/should_show_share_button_use_case.dart b/lib/domain/should_show_share_button_use_case.dart new file mode 100644 index 0000000..2a37862 --- /dev/null +++ b/lib/domain/should_show_share_button_use_case.dart @@ -0,0 +1,15 @@ +import 'package:injectable/injectable.dart'; +import 'package:todoapp/data/model/task.dart'; + +abstract class ShouldShowShareButtonUseCase { + + bool shouldShowShareButton(List tasks); +} + +@Injectable(as: ShouldShowShareButtonUseCase) +class ShouldShowShareButtonUseCaseImpl extends ShouldShowShareButtonUseCase { + @override + bool shouldShowShareButton(List tasks) { + return tasks.any((task) => task.isCompleted == false); + } +} diff --git a/lib/domain/tasks_comparator_use_case.dart b/lib/domain/tasks_comparator_use_case.dart new file mode 100644 index 0000000..075a3a3 --- /dev/null +++ b/lib/domain/tasks_comparator_use_case.dart @@ -0,0 +1,30 @@ +import 'package:injectable/injectable.dart'; +import 'package:todoapp/data/model/task.dart'; + +abstract class TasksComparatorUseCase { + bool areThemEqual({required List oldList, required List newList}); +} + +@Injectable(as: TasksComparatorUseCase) +class TasksComparatorUseCaseImpl extends TasksComparatorUseCase { + @override + bool areThemEqual( + {required List oldList, required List newList}) { + if (oldList.length == newList.length) { + if (oldList.isEmpty && newList.isEmpty) { + return true; + } else { + for (var i = 0; i < oldList.length; i++) { + if (oldList[i] == newList[i]) { + continue; + } else { + return false; + } + } + } + } else { + return false; + } + return true; + } +} diff --git a/lib/domain/tasks_helper.dart b/lib/domain/tasks_helper.dart deleted file mode 100644 index 7b3b02b..0000000 --- a/lib/domain/tasks_helper.dart +++ /dev/null @@ -1,66 +0,0 @@ -import 'package:injectable/injectable.dart'; -import 'package:todoapp/data/model/task.dart'; - -abstract class TasksHelper { - double calculateProgress({required List tasks}); - - String formatTaskList({required List tasks}); - - bool shouldShowShareButton(List tasks); - - List sortByCompletedStatus(List tasks); -} - -@Injectable(as: TasksHelper) -class TasksHelperImpl implements TasksHelper { - - @override - double calculateProgress({required List tasks}) { - int completedTasks = 0; - for (var task in tasks) { - if (task.isCompleted) { - completedTasks++; - } - } - - if (tasks.isNotEmpty) { - return completedTasks / tasks.length.toDouble(); - } else { - return 0.0; - } - } - - @override - String formatTaskList({required List tasks}) { - var checklist = ''; - - for (var task in tasks) { - if (task.isCompleted == false) { - checklist += '- ${task.title}\n'; - } - } - return checklist; - } - - @override - bool shouldShowShareButton(List tasks) { - return tasks.any((task) => task.isCompleted == false); - } - - @override - List sortByCompletedStatus(List tasks) { - List tasksToBeSorted = List.from(tasks); - tasksToBeSorted.sort((a, b) => _sort(a, b)); - return tasksToBeSorted; - } - - int _sort(Task a, Task b) { - if (a.isCompleted == false && b.isCompleted) { - return -1; - } else if (a.isCompleted && b.isCompleted == false) { - return 1; - } else { - return 0; - } - } -} diff --git a/lib/domain/tasks_sorter_use_case.dart b/lib/domain/tasks_sorter_use_case.dart new file mode 100644 index 0000000..522f4e3 --- /dev/null +++ b/lib/domain/tasks_sorter_use_case.dart @@ -0,0 +1,27 @@ +import 'package:injectable/injectable.dart'; +import 'package:todoapp/data/model/task.dart'; + +abstract class TasksSorterUseCase { + List sortByCompletedStatus(List tasks); +} + +@Injectable(as: TasksSorterUseCase) +class TasksSorterUseCaseImpl implements TasksSorterUseCase { + @override + List sortByCompletedStatus(List tasks) { + List tasksToBeSorted = List.from(tasks); + tasksToBeSorted.sort((a, b) => _sort(a, b)); + return tasksToBeSorted; + } + + int _sort(Task a, Task b) { + if (a.isCompleted == false && b.isCompleted) { + return -1; + } else if (a.isCompleted && b.isCompleted == false) { + return 1; + } else { + return 0; + } + } + +} \ No newline at end of file diff --git a/lib/ui/components/widgets/checklist/checklist_full_widget.dart b/lib/ui/components/widgets/checklist/checklist_full_widget.dart new file mode 100644 index 0000000..12632e9 --- /dev/null +++ b/lib/ui/components/widgets/checklist/checklist_full_widget.dart @@ -0,0 +1,139 @@ +import 'package:flutter/material.dart'; +import 'package:todoapp/data/model/checklist.dart'; +import 'package:todoapp/data/model/task.dart'; +import 'package:todoapp/ui/components/widgets/checklist/checklist_item_widget.dart'; +import 'package:todoapp/ui/components/widgets/task/taskslist/tasks_list_widget.dart'; +import 'package:todoapp/ui/components/widgets/task/taskslist/tasks_viewmodel.dart'; +import 'package:todoapp/ui/l10n/app_localizations.dart'; +import 'package:todoapp/ui/todo_app_router_config.gr.dart'; +import 'package:todoapp/util/di/dependency_startup_launcher.dart'; +import 'package:todoapp/util/navigation_provider.dart'; + +class ChecklistsListFullWidget extends StatefulWidget { + final List checklists; + final Function(Checklist) onRemoveChecklist; + final NavigatorProvider navigatorProvider; + + const ChecklistsListFullWidget({ + super.key, + required this.checklists, + required this.onRemoveChecklist, + required this.navigatorProvider, + }); + + @override + State createState() => + ChecklistsListFullWidgetState(); +} + +class ChecklistsListFullWidgetState extends State { + Checklist? selected; + List? tasks; + late TasksViewModel _tasksViewModel; + + @override + void initState() { + super.initState(); + final getIt = GetItStartupHandlerWrapper.getIt; + _tasksViewModel = TasksViewModel( + repository: getIt.get(), + shareMessageHandler: getIt.get(), + shouldShowShareButtonUseCase: getIt.get(), + formatTaskListMessageUseCase: getIt.get(), + tasksSorterUseCase: getIt.get(), + tasksComparatorUseCase: getIt.get(), + progressCounterUseCase: getIt.get(), + ); + } + + @override + Widget build(BuildContext context) { + return _buildTaskList(context); + } + + void onSelectCheckList(Checklist checklist) { + setState(() { + selected = checklist; + + _tasksViewModel.updateTasks(checklist.id); + + _tasksViewModel.stream.listen((state) { + setState(() { + tasks = state.tasks; + }); + }); + }); + } + + Future addNewTaskToExistingChecklist(BuildContext context) async { + if (selected?.id != null) { + await _navigateToTaskScreen( + context, + checklistId: selected!.id, + ); + } + } + + Widget _buildTaskList(BuildContext context) { + final localizations = AppLocalizations.of(context)!; + return Row( + children: [ + Expanded( + flex: 4, + child: ListView.builder( + padding: const EdgeInsets.only(top: 12, bottom: 120), + itemBuilder: (context, index) => ChecklistItemWidget( + isSelected: widget.checklists[index].id == selected?.id, + onRemoveChecklist: widget.onRemoveChecklist, + onSelectChecklist: (checklist) => onSelectCheckList(checklist), + checklist: widget.checklists[index], + ), + itemCount: widget.checklists.length, + ), + ), + Expanded( + flex: 6, + child: TasksListWidget( + tasks: tasks == null ? [] : tasks!, + emptyTasksMessage: localizations.empty_tasks, + onCompleteTask: _tasksViewModel.onCompleteTask, + onRemoveTask: _tasksViewModel.onRemoveTask, + onReorder: _tasksViewModel.reorder, + onTap: (task) => _navigateToTaskScreen( + context, + checklistId: selected?.id, + task: task, + ), + )), + ], + ); + } + + Future _navigateToTaskScreen( + BuildContext context, { + required int? checklistId, + Task? task, + }) async { + final localizations = AppLocalizations.of(context)!; + + bool? result = await widget.navigatorProvider.push( + context, + TaskRoute( + checklistId: checklistId, + task: task, + ), + ); + if (result == true) { + await _tasksViewModel.updateTasks(selected?.id); + if (context.mounted) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text( + localizations.tasks_refresh, + ), + ), + ); + } + } + } +} diff --git a/lib/ui/components/widgets/checklist/checklist_item_widget.dart b/lib/ui/components/widgets/checklist/checklist_item_widget.dart index 4e7411e..d8311d9 100644 --- a/lib/ui/components/widgets/checklist/checklist_item_widget.dart +++ b/lib/ui/components/widgets/checklist/checklist_item_widget.dart @@ -6,9 +6,11 @@ class ChecklistItemWidget extends StatelessWidget { final Checklist checklist; final Function(Checklist) onRemoveChecklist; final Function(Checklist) onSelectChecklist; + final bool? isSelected; const ChecklistItemWidget({ super.key, + this.isSelected, required this.checklist, required this.onRemoveChecklist, required this.onSelectChecklist, @@ -35,6 +37,12 @@ class ChecklistItemWidget extends StatelessWidget { @override Widget build(BuildContext context) { + Color backgroundColor = Theme.of(context).colorScheme.surfaceBright; + + if (isSelected == true) { + backgroundColor = Theme.of(context).colorScheme.tertiaryContainer; + } + return Card( elevation: 2, shape: const RoundedRectangleBorder( @@ -42,7 +50,7 @@ class ChecklistItemWidget extends StatelessWidget { Radius.circular(12), ), ), - color: Theme.of(context).colorScheme.surfaceBright, + color: backgroundColor, clipBehavior: Clip.hardEdge, child: InkWell( onTap: () => onSelectChecklist(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 new file mode 100644 index 0000000..582c56d --- /dev/null +++ b/lib/ui/components/widgets/checklist/checklist_navigation_rail_menu.dart @@ -0,0 +1,44 @@ +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/tasks_list_widget.dart b/lib/ui/components/widgets/task/taskslist/tasks_list_widget.dart similarity index 100% rename from lib/ui/components/widgets/task/tasks_list_widget.dart rename to lib/ui/components/widgets/task/taskslist/tasks_list_widget.dart diff --git a/lib/ui/screens/tasks/tasks_screen_state.dart b/lib/ui/components/widgets/task/taskslist/tasks_screen_state.dart similarity index 100% rename from lib/ui/screens/tasks/tasks_screen_state.dart rename to lib/ui/components/widgets/task/taskslist/tasks_screen_state.dart diff --git a/lib/ui/screens/tasks/tasks_viewmodel.dart b/lib/ui/components/widgets/task/taskslist/tasks_viewmodel.dart similarity index 56% rename from lib/ui/screens/tasks/tasks_viewmodel.dart rename to lib/ui/components/widgets/task/taskslist/tasks_viewmodel.dart index ee79dab..057716b 100644 --- a/lib/ui/screens/tasks/tasks_viewmodel.dart +++ b/lib/ui/components/widgets/task/taskslist/tasks_viewmodel.dart @@ -1,23 +1,33 @@ +import 'package:flutter/widgets.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:todoapp/data/model/task.dart'; import 'package:todoapp/data/todo_repository.dart'; -import 'package:todoapp/domain/tasks_helper.dart'; -import 'package:todoapp/ui/screens/tasks/tasks_screen_state.dart'; +import 'package:todoapp/domain/format_task_list_message_use_case.dart'; +import 'package:todoapp/domain/progress_counter_use_case.dart'; +import 'package:todoapp/domain/should_show_share_button_use_case.dart'; +import 'package:todoapp/domain/tasks_comparator_use_case.dart'; +import 'package:todoapp/domain/tasks_sorter_use_case.dart'; +import 'package:todoapp/ui/components/widgets/task/taskslist/tasks_screen_state.dart'; import 'package:todoapp/util/share_message_handler.dart'; class TasksViewModel extends Cubit { late TodoRepository _repository; late ShareMessageHandler _shareMessageHandler; - TasksHelper tasksHelper; - - late int? _checklistId; + ShouldShowShareButtonUseCase shouldShowShareButtonUseCase; + TasksSorterUseCase tasksSorterUseCase; + TasksComparatorUseCase tasksComparatorUseCase; + ProgressCounterUseCase progressCounterUseCase; + FormatTaskListMessageUseCase formatTaskListMessageUseCase; TasksViewModel({ required TodoRepository repository, required ShareMessageHandler shareMessageHandler, - required this.tasksHelper, - int? checklistId, + required this.shouldShowShareButtonUseCase, + required this.tasksSorterUseCase, + required this.tasksComparatorUseCase, + required this.progressCounterUseCase, + required this.formatTaskListMessageUseCase, }) : super( const TasksScreenState( tasks: [], @@ -27,10 +37,7 @@ class TasksViewModel extends Cubit { ), ) { _repository = repository; - _shareMessageHandler = shareMessageHandler; - - _checklistId = checklistId; } void _onLoad() { @@ -39,22 +46,24 @@ class TasksViewModel extends Cubit { ); } - Future updateTasks() async { + Future updateTasks(int? checklistId) async { _onLoad(); - var tasks = await _repository.getTasks(_checklistId); + var tasks = await _repository.getTasks(checklistId); emit( state.copyWith( isLoading: false, tasks: tasks, - showShareIcon: tasksHelper.shouldShowShareButton(tasks), - progress: tasksHelper.calculateProgress(tasks: tasks), + showShareIcon: shouldShowShareButtonUseCase.shouldShowShareButton( + tasks + ), + progress: progressCounterUseCase.calculateProgress(tasks: tasks), ), ); } Future shareTasks({required String checklistName}) async { - final checklist = tasksHelper.formatTaskList( + final checklist = formatTaskListMessageUseCase.formatTaskList( tasks: state.tasks, ); @@ -79,8 +88,10 @@ class TasksViewModel extends Cubit { tasks[index] = tasks[index].copyWith(isCompleted: value); emit( state.copyWith( - progress: tasksHelper.calculateProgress(tasks: tasks), - showShareIcon: tasksHelper.shouldShowShareButton(tasks), + progress: progressCounterUseCase.calculateProgress(tasks: tasks), + showShareIcon: shouldShowShareButtonUseCase.shouldShowShareButton( + tasks + ), isLoading: false, tasks: tasks, ), @@ -99,8 +110,10 @@ class TasksViewModel extends Cubit { tasks.remove(task); emit( state.copyWith( - progress: tasksHelper.calculateProgress(tasks: tasks), - showShareIcon: tasksHelper.shouldShowShareButton(tasks), + progress: progressCounterUseCase.calculateProgress(tasks: tasks), + showShareIcon: shouldShowShareButtonUseCase.shouldShowShareButton( + tasks + ), isLoading: false, tasks: tasks), ); @@ -122,7 +135,9 @@ class TasksViewModel extends Cubit { emit( state.copyWith( isLoading: false, - showShareIcon: tasksHelper.shouldShowShareButton(tasks), + showShareIcon: shouldShowShareButtonUseCase.shouldShowShareButton( + tasks + ), tasks: tasks, ), ); @@ -131,10 +146,17 @@ class TasksViewModel extends Cubit { } void onSort() { - List tasksToBeSorted = tasksHelper.sortByCompletedStatus( + List tasksToBeSorted = tasksSorterUseCase.sortByCompletedStatus( state.tasks, ); + bool wasSortedPerformed = tasksComparatorUseCase.areThemEqual( + oldList: state.tasks, + newList: tasksToBeSorted, + ); + + debugPrint('Was Sorted performed $wasSortedPerformed'); + emit( state.copyWith( tasks: tasksToBeSorted, diff --git a/lib/ui/screens/checklist/checklist_screen.dart b/lib/ui/screens/checklist/checklist_screen.dart index 82c483c..e370dcd 100644 --- a/lib/ui/screens/checklist/checklist_screen.dart +++ b/lib/ui/screens/checklist/checklist_screen.dart @@ -4,7 +4,6 @@ import 'package:todoapp/ui/components/form_validator.dart'; import 'package:todoapp/ui/components/widgets/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_screen_text_values.dart'; import 'package:todoapp/ui/screens/checklist/checklist_viewmodel.dart'; import 'package:todoapp/util/di/dependency_startup_launcher.dart'; import 'package:todoapp/util/navigation_provider.dart'; @@ -21,15 +20,7 @@ class ChecklistScreen extends StatelessWidget { GetItStartupHandlerWrapper.getIt.get(), ); - final checklistScreenTextValues = ChecklistScreenTextValues( - screenTitle: AppLocalizations.of(context)!.checklist, - checklistErrorMessage: - AppLocalizations.of(context)!.checklist_name_required, - checklistLabel: AppLocalizations.of(context)!.checklist, - ); - return ChecklistScreenScaffold( - checklistScreenTextValues: checklistScreenTextValues, formScreenValidator: GetItStartupHandlerWrapper.getIt.get(), onAddNewChecklist: (title) => viewModel.addChecklist( title: title, @@ -45,12 +36,10 @@ class ChecklistScreenScaffold extends StatelessWidget { final Function(String) onAddNewChecklist; final _formKey = GlobalKey(); final FormScreenValidator formScreenValidator; - final ChecklistScreenTextValues checklistScreenTextValues; final NavigatorProvider navigatorProvider; ChecklistScreenScaffold({ super.key, - required this.checklistScreenTextValues, required this.onAddNewChecklist, required this.formScreenValidator, required this.navigatorProvider, @@ -58,9 +47,11 @@ class ChecklistScreenScaffold extends StatelessWidget { @override Widget build(BuildContext context) { + final localizations = AppLocalizations.of(context)!; + return Scaffold( appBar: CustomAppBarWidget( - title: checklistScreenTextValues.screenTitle, + title: localizations.checklist, ), floatingActionButton: FloatingActionButton( onPressed: () async { @@ -81,9 +72,8 @@ class ChecklistScreenScaffold extends StatelessWidget { padding: const EdgeInsets.all(12), child: ChecklistFormWidget( formKey: _formKey, - checklistLabel: checklistScreenTextValues.checklistLabel, - checklistErrorMessage: - checklistScreenTextValues.checklistErrorMessage, + checklistLabel: localizations.checklist_name, + checklistErrorMessage: localizations.checklist_name_required, formScreenValidator: formScreenValidator, checklistEditingController: _checklistEditingController, ), diff --git a/lib/ui/screens/checklist/checklist_screen_text_values.dart b/lib/ui/screens/checklist/checklist_screen_text_values.dart deleted file mode 100644 index 3648f12..0000000 --- a/lib/ui/screens/checklist/checklist_screen_text_values.dart +++ /dev/null @@ -1,19 +0,0 @@ -import 'package:freezed_annotation/freezed_annotation.dart'; - -part 'checklist_screen_text_values.freezed.dart'; - -@freezed -class ChecklistScreenTextValues with _$ChecklistScreenTextValues { - @override - final String screenTitle; - @override - final String checklistLabel; - @override - final String checklistErrorMessage; - - const ChecklistScreenTextValues({ - required this.screenTitle, - required this.checklistLabel, - required this.checklistErrorMessage, - }); -} diff --git a/lib/ui/screens/checklists/checklists_screen.dart b/lib/ui/screens/checklists/checklists_screen.dart index 78f9fc6..ccb64eb 100644 --- a/lib/ui/screens/checklists/checklists_screen.dart +++ b/lib/ui/screens/checklists/checklists_screen.dart @@ -2,12 +2,13 @@ import 'package:auto_route/auto_route.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:todoapp/data/model/checklist.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'; import 'package:todoapp/ui/l10n/app_localizations.dart'; import 'package:todoapp/ui/screens/checklists/checklists_screen_state.dart'; -import 'package:todoapp/ui/screens/checklists/checklists_screen_text_values.dart'; import 'package:todoapp/ui/screens/checklists/checklists_viewmodel.dart'; import 'package:todoapp/ui/todo_app_router_config.gr.dart'; import 'package:todoapp/util/di/dependency_startup_launcher.dart'; @@ -24,24 +25,11 @@ class ChecklistsScreen extends StatelessWidget { ); viewModel.updateChecklists(); - final checklistScreenTextValues = ChecklistsScreenTextValues( - screenTitle: AppLocalizations.of(context)!.checklists, - checklistAdded: AppLocalizations.of(context)!.checklist_added, - removeChecklistDialogTitle: - AppLocalizations.of(context)!.remove_checklist_dialog_title, - removeChecklistDialogDesc: - AppLocalizations.of(context)!.remove_checklist_dialog_desc, - yes: AppLocalizations.of(context)!.yes, - no: AppLocalizations.of(context)!.no, - emptyChecklistMessage: AppLocalizations.of(context)!.empty_checklists, - ); - return BlocProvider( create: (_) => viewModel, child: BlocBuilder( builder: (context, uiState) => ChecklistsScaffold( uiState: uiState, - checklistsScreenTextValues: checklistScreenTextValues, updateChecklists: viewModel.updateChecklists, onRemoveChecklist: viewModel.onRemoveChecklist, navigatorProvider: GetItStartupHandlerWrapper.getIt.get(), @@ -56,81 +44,152 @@ class ChecklistsScaffold extends StatelessWidget { final Function(Checklist) onRemoveChecklist; final Function updateChecklists; final NavigatorProvider navigatorProvider; - final ChecklistsScreenTextValues checklistsScreenTextValues; + /// Use key to access a specific internal behavior of TaskViewModel + /// to update the task list through ChecklistFullWidget. + final GlobalKey _checklistFullKey = + GlobalKey(); - const ChecklistsScaffold({ + ChecklistsScaffold({ super.key, required this.uiState, - required this.checklistsScreenTextValues, required this.updateChecklists, required this.onRemoveChecklist, required this.navigatorProvider, }); - Widget _buildFloatingActionButton(Function() onPressed) { - return FloatingActionButton( - onPressed: onPressed, - child: const Icon(Icons.plus_one), - ); + Widget _buildFloatingActionButton({ + required bool isBigSize, + required Function() onPressed, + }) { + if (isBigSize) { + return const SizedBox.shrink(); + } else { + return FloatingActionButton( + onPressed: onPressed, + child: const Icon(Icons.plus_one), + ); + } } @override Widget build(BuildContext context) { + final isBigSize = MediaQuery.sizeOf(context).width > 600; + final localizations = AppLocalizations.of(context)!; + return Scaffold( - backgroundColor: Theme.of(context).colorScheme.surface, - appBar: CustomAppBarWidget( - title: checklistsScreenTextValues.screenTitle, - ), - floatingActionButton: _buildFloatingActionButton( - () async { - bool? result = await navigatorProvider.push( - context, - const ChecklistRoute(), - ); + 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, + ), + ), + ], + )); + } - if (result == true) { - updateChecklists(); - if (context.mounted) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text( - AppLocalizations.of(context)!.checklist_added, - ), - ), - ); - } - } + Widget _buildCheckListWidget({ + required BuildContext context, + required bool isBigSize, + }) { + final localizations = AppLocalizations.of(context)!; + + Widget checkListWidget; + if (isBigSize) { + checkListWidget = ChecklistsListFullWidget( + checklists: uiState.checklists, + key: _checklistFullKey, + onRemoveChecklist: (checklist) { + _showConfirmationDialogToRemoveChecklist(context, checklist); }, - ), - body: Padding( - padding: const EdgeInsets.only(left: 12, right: 12), - child: ChecklistsListWidget( - checklists: uiState.checklists, - emptyChecklistMessage: - checklistsScreenTextValues.emptyChecklistMessage, - onRemoveChecklist: (checklist) { - _showConfirmationDialogToRemoveChecklist(context, checklist); - }, - onSelectChecklist: (checklist) => navigatorProvider.push( - context, - TasksRoute(checklist: checklist), - ), + navigatorProvider: navigatorProvider, + ); + } else { + checkListWidget = ChecklistsListWidget( + checklists: uiState.checklists, + emptyChecklistMessage: localizations.empty_checklists, + onRemoveChecklist: (checklist) { + _showConfirmationDialogToRemoveChecklist(context, checklist); + }, + onSelectChecklist: (checklist) => navigatorProvider.push( + context, + TasksRoute(checklist: checklist), ), - ), + ); + } + + return Padding( + padding: const EdgeInsets.only(left: 12, right: 12), + child: checkListWidget, ); } + 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, ) { + final localizations = AppLocalizations.of(context)!; + showDialog( context: context, builder: (BuildContext context) => ConfirmationAlertDialogWidget( - title: checklistsScreenTextValues.removeChecklistDialogTitle, - description: checklistsScreenTextValues.removeChecklistDialogDesc, - secondaryButtonText: checklistsScreenTextValues.no, - primaryButtonText: checklistsScreenTextValues.yes, + title: localizations.remove_checklist_dialog_title, + description: localizations.remove_checklist_dialog_desc, + secondaryButtonText: localizations.no, + primaryButtonText: localizations.yes, onSecondaryButtonPressed: () => { navigatorProvider.onPop(context, null), }, @@ -141,4 +200,24 @@ class ChecklistsScaffold extends StatelessWidget { ), ); } + + Future _addNewChecklistEvent(BuildContext context) async { + bool? result = await navigatorProvider.push( + context, + const ChecklistRoute(), + ); + + if (result == true) { + updateChecklists(); + if (context.mounted) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text( + AppLocalizations.of(context)!.checklist_added, + ), + ), + ); + } + } + } } diff --git a/lib/ui/screens/checklists/checklists_screen_text_values.dart b/lib/ui/screens/checklists/checklists_screen_text_values.dart deleted file mode 100644 index ce11f77..0000000 --- a/lib/ui/screens/checklists/checklists_screen_text_values.dart +++ /dev/null @@ -1,33 +0,0 @@ - - -import 'package:freezed_annotation/freezed_annotation.dart'; - -part 'checklists_screen_text_values.freezed.dart'; - -@freezed -class ChecklistsScreenTextValues with _$ChecklistsScreenTextValues { - @override - final String screenTitle; - @override - final String checklistAdded; - @override - final String removeChecklistDialogTitle; - @override - final String removeChecklistDialogDesc; - @override - final String yes; - @override - final String no; - @override - final String emptyChecklistMessage; - - const ChecklistsScreenTextValues({ - required this.screenTitle, - required this.checklistAdded, - required this.removeChecklistDialogTitle, - required this.removeChecklistDialogDesc, - required this.yes, - required this.no, - required this.emptyChecklistMessage, - }); -} diff --git a/lib/ui/screens/task/task_screen.dart b/lib/ui/screens/task/task_screen.dart index 721039a..b2fe8d4 100644 --- a/lib/ui/screens/task/task_screen.dart +++ b/lib/ui/screens/task/task_screen.dart @@ -5,7 +5,6 @@ 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/l10n/app_localizations.dart'; -import 'package:todoapp/ui/screens/task/task_screen_text_values.dart'; import 'package:todoapp/ui/screens/task/task_viewmodel.dart'; import 'package:todoapp/util/di/dependency_startup_launcher.dart'; import 'package:todoapp/util/navigation_provider.dart'; @@ -31,14 +30,8 @@ class TaskScreen extends StatelessWidget { final NavigatorProvider navigatorProvider = GetItStartupHandlerWrapper.getIt.get(); - final taskScreenTextValues = TaskScreenTextValues( - taskErrorMessage: AppLocalizations.of(context)!.task_name_required, - taskLabel: AppLocalizations.of(context)!.task, - ); - return TaskScreenScaffold( taskTitle: task?.title, - taskScreenTextValues: taskScreenTextValues, addTaskOrUpdate: (title) => viewModel.addTaskOrUpdate( title: title, ), @@ -55,14 +48,12 @@ class TaskScreenScaffold extends StatelessWidget { final FormScreenValidator formScreenValidator; final _formKey = GlobalKey(); final NavigatorProvider navigatorProvider; - final TaskScreenTextValues taskScreenTextValues; final IconData floatingActionIcon; TaskScreenScaffold({ super.key, String? taskTitle, required this.floatingActionIcon, - required this.taskScreenTextValues, required this.addTaskOrUpdate, required this.formScreenValidator, required this.navigatorProvider, @@ -72,9 +63,11 @@ class TaskScreenScaffold extends StatelessWidget { @override Widget build(BuildContext context) { + final localizations = AppLocalizations.of(context)!; + return Scaffold( - appBar: const CustomAppBarWidget( - title: 'Task', + appBar: CustomAppBarWidget( + title: localizations.task, ), floatingActionButton: FloatingActionButton( onPressed: () async { @@ -95,8 +88,8 @@ class TaskScreenScaffold extends StatelessWidget { padding: const EdgeInsets.all(12), child: TaskFormWidget( formKey: _formKey, - taskLabel: taskScreenTextValues.taskLabel, - taskErrorMessage: taskScreenTextValues.taskErrorMessage, + taskLabel: localizations.task, + taskErrorMessage: localizations.task_name_required, taskEditingController: _taskEditingController, formScreenValidator: formScreenValidator, ), diff --git a/lib/ui/screens/task/task_screen_text_values.dart b/lib/ui/screens/task/task_screen_text_values.dart deleted file mode 100644 index 380a325..0000000 --- a/lib/ui/screens/task/task_screen_text_values.dart +++ /dev/null @@ -1,17 +0,0 @@ - -import 'package:freezed_annotation/freezed_annotation.dart'; - -part 'task_screen_text_values.freezed.dart'; - -@freezed -class TaskScreenTextValues with _$TaskScreenTextValues { - @override - final String taskLabel; - @override - final String taskErrorMessage; - - const TaskScreenTextValues({ - required this.taskErrorMessage, - required this.taskLabel, - }); -} diff --git a/lib/ui/screens/tasks/tasks_screen.dart b/lib/ui/screens/tasks/tasks_screen.dart index 4cb1494..ce28433 100644 --- a/lib/ui/screens/tasks/tasks_screen.dart +++ b/lib/ui/screens/tasks/tasks_screen.dart @@ -6,12 +6,11 @@ import 'package:todoapp/data/model/task.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/components/widgets/progress_widget.dart'; -import 'package:todoapp/ui/components/widgets/task/tasks_list_widget.dart'; +import 'package:todoapp/ui/components/widgets/task/taskslist/tasks_list_widget.dart'; +import 'package:todoapp/ui/components/widgets/task/taskslist/tasks_screen_state.dart'; +import 'package:todoapp/ui/components/widgets/task/taskslist/tasks_viewmodel.dart'; import 'package:todoapp/ui/l10n/app_localizations.dart'; import 'package:todoapp/ui/screens/tasks/tasks_screen_callbacks.dart'; -import 'package:todoapp/ui/screens/tasks/tasks_screen_state.dart'; -import 'package:todoapp/ui/screens/tasks/tasks_screen_text_values.dart'; -import 'package:todoapp/ui/screens/tasks/tasks_viewmodel.dart'; import 'package:todoapp/ui/todo_app_router_config.gr.dart'; import 'package:todoapp/util/di/dependency_startup_launcher.dart'; import 'package:todoapp/util/navigation_provider.dart'; @@ -33,22 +32,13 @@ class TasksScreen extends StatelessWidget { final viewModel = TasksViewModel( repository: getIt.get(), shareMessageHandler: getIt.get(), - checklistId: checklist.id, - tasksHelper: getIt.get(), - ); - viewModel.updateTasks(); - - final tasksScreenTextValues = TasksScreenTextValues( - tasksRefresh: AppLocalizations.of(context)!.tasks_refresh, - removeTaskDialogTitle: - AppLocalizations.of(context)!.remove_task_dialog_title, - removeTaskDialogDesc: - AppLocalizations.of(context)!.remove_task_dialog_desc, - yes: AppLocalizations.of(context)!.yes, - no: AppLocalizations.of(context)!.no, - emptyTasksMessage: AppLocalizations.of(context)!.empty_tasks, - sortMessage: AppLocalizations.of(context)!.sort_message, + shouldShowShareButtonUseCase: getIt.get(), + formatTaskListMessageUseCase: getIt.get(), + tasksSorterUseCase: getIt.get(), + tasksComparatorUseCase: getIt.get(), + progressCounterUseCase: getIt.get(), ); + viewModel.updateTasks(checklist.id); return BlocProvider( create: (_) => viewModel, @@ -58,7 +48,6 @@ class TasksScreen extends StatelessWidget { uiState: uiState, checklistId: checklist.id, checklistName: checklist.title, - tasksScreenTextValues: tasksScreenTextValues, callbacks: TasksScreenCallbacks( onCompleteTask: viewModel.onCompleteTask, onRemoveTask: viewModel.onRemoveTask, @@ -79,7 +68,6 @@ class TasksScaffold extends StatelessWidget { final TasksScreenState uiState; final int? checklistId; final String checklistName; - final TasksScreenTextValues tasksScreenTextValues; final TasksScreenCallbacks callbacks; final NavigatorProvider navigatorProvider; @@ -88,7 +76,6 @@ class TasksScaffold extends StatelessWidget { required this.uiState, required this.checklistId, required this.checklistName, - required this.tasksScreenTextValues, required this.navigatorProvider, required this.callbacks, }); @@ -102,13 +89,15 @@ class TasksScaffold extends StatelessWidget { @override Widget build(BuildContext context) { + final localizations = AppLocalizations.of(context)!; + return Scaffold( backgroundColor: Theme.of(context).colorScheme.surface, appBar: CustomAppBarWidget( title: checklistName, actions: _buildTopBarActions( context: context, - sortedMessage: tasksScreenTextValues.sortMessage, + sortedMessage: localizations.sort_message, onShare: callbacks.onShare, showShareButton: uiState.showShareIcon, onSort: callbacks.onSort, @@ -131,7 +120,7 @@ class TasksScaffold extends StatelessWidget { ), child: TasksListWidget( tasks: uiState.tasks, - emptyTasksMessage: tasksScreenTextValues.emptyTasksMessage, + emptyTasksMessage: localizations.empty_tasks, onReorder: callbacks.onReorder, onRemoveTask: (task) => _showConfirmationDialogToRemoveTask(context, task), @@ -159,13 +148,15 @@ class TasksScaffold extends StatelessWidget { } void _showConfirmationDialogToRemoveTask(BuildContext context, Task task) { + final localizations = AppLocalizations.of(context)!; + showDialog( context: context, builder: (BuildContext context) => ConfirmationAlertDialogWidget( - title: tasksScreenTextValues.removeTaskDialogTitle, - description: tasksScreenTextValues.removeTaskDialogDesc, - secondaryButtonText: tasksScreenTextValues.no, - primaryButtonText: tasksScreenTextValues.yes, + title: localizations.remove_task_dialog_title, + description: localizations.remove_task_dialog_desc, + secondaryButtonText: localizations.no, + primaryButtonText: localizations.yes, onSecondaryButtonPressed: () => { navigatorProvider.onPop(context, null), }, @@ -220,6 +211,8 @@ class TasksScaffold extends StatelessWidget { required int? checklistId, Task? task, }) async { + final localizations = AppLocalizations.of(context)!; + bool? result = await navigatorProvider.push( context, TaskRoute( @@ -228,12 +221,12 @@ class TasksScaffold extends StatelessWidget { ), ); if (result == true) { - await callbacks.updateTasks(); + await callbacks.updateTasks(checklistId); if (context.mounted) { ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text( - tasksScreenTextValues.tasksRefresh, + localizations.tasks_refresh, ), ), ); diff --git a/lib/ui/screens/tasks/tasks_screen_callbacks.dart b/lib/ui/screens/tasks/tasks_screen_callbacks.dart index cedd4e6..ce23894 100644 --- a/lib/ui/screens/tasks/tasks_screen_callbacks.dart +++ b/lib/ui/screens/tasks/tasks_screen_callbacks.dart @@ -1,7 +1,7 @@ import 'package:todoapp/data/model/task.dart'; class TasksScreenCallbacks { - final Function updateTasks; + final Function(int?) updateTasks; final Function(Task, bool) onCompleteTask; final Function(Task) onRemoveTask; final Function(int oldIndex, int newIndex) onReorder; diff --git a/lib/ui/screens/tasks/tasks_screen_text_values.dart b/lib/ui/screens/tasks/tasks_screen_text_values.dart deleted file mode 100644 index 3344bc5..0000000 --- a/lib/ui/screens/tasks/tasks_screen_text_values.dart +++ /dev/null @@ -1,31 +0,0 @@ -import 'package:freezed_annotation/freezed_annotation.dart'; - -part 'tasks_screen_text_values.freezed.dart'; - -@freezed -class TasksScreenTextValues with _$TasksScreenTextValues { - @override - final String removeTaskDialogTitle; - @override - final String removeTaskDialogDesc; - @override - final String yes; - @override - final String no; - @override - final String emptyTasksMessage; - @override - final String tasksRefresh; - @override - final String sortMessage; - - const TasksScreenTextValues({ - required this.tasksRefresh, - required this.removeTaskDialogTitle, - required this.removeTaskDialogDesc, - required this.yes, - required this.no, - required this.emptyTasksMessage, - required this.sortMessage, - }); -} diff --git a/macos/Podfile b/macos/Podfile index 29c8eb3..ff5ddb3 100644 --- a/macos/Podfile +++ b/macos/Podfile @@ -1,4 +1,4 @@ -platform :osx, '10.14' +platform :osx, '10.15' # CocoaPods analytics sends network stats synchronously affecting flutter build latency. ENV['COCOAPODS_DISABLE_STATS'] = 'true' diff --git a/macos/Runner.xcodeproj/project.pbxproj b/macos/Runner.xcodeproj/project.pbxproj index ab7417a..f884f02 100644 --- a/macos/Runner.xcodeproj/project.pbxproj +++ b/macos/Runner.xcodeproj/project.pbxproj @@ -557,7 +557,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - MACOSX_DEPLOYMENT_TARGET = 10.14; + MACOSX_DEPLOYMENT_TARGET = 10.15; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = macosx; SWIFT_COMPILATION_MODE = wholemodule; @@ -639,7 +639,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - MACOSX_DEPLOYMENT_TARGET = 10.14; + MACOSX_DEPLOYMENT_TARGET = 10.15; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = macosx; @@ -689,7 +689,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - MACOSX_DEPLOYMENT_TARGET = 10.14; + MACOSX_DEPLOYMENT_TARGET = 10.15; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = macosx; SWIFT_COMPILATION_MODE = wholemodule; diff --git a/pubspec.lock b/pubspec.lock index d40cb28..7b2a0ea 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -17,6 +17,14 @@ packages: url: "https://pub.dev" source: hosted version: "7.7.1" + ansicolor: + dependency: transitive + description: + name: ansicolor + sha256: "50e982d500bc863e1d703448afdbf9e5a72eb48840a4f766fa361ffd6877055f" + url: "https://pub.dev" + source: hosted + version: "2.0.3" args: dependency: transitive description: @@ -37,26 +45,26 @@ packages: dependency: "direct main" description: name: auto_route - sha256: b8c036fa613a98a759cf0fdcba26e62f4985dcbff01a5e760ab411e8554bbaf0 + sha256: "6d3ccc11b520b6eff0ab5a2c3d1c43c46d1486249cc746c4bb14486d876e8b43" url: "https://pub.dev" source: hosted - version: "10.1.0+1" + version: "10.3.0" auto_route_generator: dependency: "direct dev" description: name: auto_route_generator - sha256: "9e3846fcbeacba5c362557328dd8c8fbc953b6a0cbc3395365e8d8f92eea29c4" + sha256: "4d78093dc102eef57c9d53e43454d735f1552039dc9c595ef8cfc7445d9017f2" url: "https://pub.dev" source: hosted - version: "10.1.0" + version: "10.2.6" bloc: dependency: transitive description: name: bloc - sha256: "52c10575f4445c61dd9e0cafcc6356fdd827c4c64dd7945ef3c4105f6b6ac189" + sha256: a2cebb899f91d36eeeaa55c7b20b5915db5a9df1b8fd4a3c9c825e22e474537d url: "https://pub.dev" source: hosted - version: "9.0.0" + version: "9.1.0" boolean_selector: dependency: transitive description: @@ -69,50 +77,50 @@ packages: dependency: transitive description: name: build - sha256: "51dc711996cbf609b90cbe5b335bbce83143875a9d58e4b5c6d3c4f684d3dda7" + sha256: ce76b1d48875e3233fde17717c23d1f60a91cc631597e49a400c89b475395b1d url: "https://pub.dev" source: hosted - version: "2.5.4" + version: "3.1.0" build_config: dependency: transitive description: name: build_config - sha256: "4ae2de3e1e67ea270081eaee972e1bd8f027d459f249e0f1186730784c2e7e33" + sha256: "4f64382b97504dc2fcdf487d5aae33418e08b4703fc21249e4db6d804a4d0187" url: "https://pub.dev" source: hosted - version: "1.1.2" + version: "1.2.0" build_daemon: dependency: transitive description: name: build_daemon - sha256: "8e928697a82be082206edb0b9c99c5a4ad6bc31c9e9b8b2f291ae65cd4a25daa" + sha256: bf05f6e12cfea92d3c09308d7bcdab1906cd8a179b023269eed00c071004b957 url: "https://pub.dev" source: hosted - version: "4.0.4" + version: "4.1.1" build_resolvers: dependency: transitive description: name: build_resolvers - sha256: ee4257b3f20c0c90e72ed2b57ad637f694ccba48839a821e87db762548c22a62 + sha256: d1d57f7807debd7349b4726a19fd32ec8bc177c71ad0febf91a20f84cd2d4b46 url: "https://pub.dev" source: hosted - version: "2.5.4" + version: "3.0.3" build_runner: dependency: "direct dev" description: name: build_runner - sha256: "382a4d649addbfb7ba71a3631df0ec6a45d5ab9b098638144faf27f02778eb53" + sha256: b24597fceb695969d47025c958f3837f9f0122e237c6a22cb082a5ac66c3ca30 url: "https://pub.dev" source: hosted - version: "2.5.4" + version: "2.7.1" build_runner_core: dependency: transitive description: name: build_runner_core - sha256: "85fbbb1036d576d966332a3f5ce83f2ce66a40bea1a94ad2d5fc29a19a0d3792" + sha256: "066dda7f73d8eb48ba630a55acb50c4a84a2e6b453b1cb4567f581729e794f7b" url: "https://pub.dev" source: hosted - version: "9.1.2" + version: "9.3.1" built_collection: dependency: transitive description: @@ -125,10 +133,10 @@ packages: dependency: transitive description: name: built_value - sha256: "082001b5c3dc495d4a42f1d5789990505df20d8547d42507c29050af6933ee27" + sha256: "426cf75afdb23aa74bd4e471704de3f9393f3c7b04c1e2d9c6f1073ae0b8b139" url: "https://pub.dev" source: hosted - version: "8.10.1" + version: "8.12.1" characters: dependency: transitive description: @@ -165,10 +173,10 @@ packages: dependency: transitive description: name: code_builder - sha256: "0ec10bf4a89e4c613960bf1e8b42c64127021740fb21640c29c909826a5eea3e" + sha256: "11654819532ba94c34de52ff5feb52bd81cba1de00ef2ed622fd50295f9d4243" url: "https://pub.dev" source: hosted - version: "4.10.1" + version: "4.11.0" collection: dependency: transitive description: @@ -189,26 +197,26 @@ packages: dependency: transitive description: name: coverage - sha256: "7436ae7fbbf8f09c70079ce132a0a8ca67e197d5b8da3e441bc89af708c5a13b" + sha256: "5da775aa218eaf2151c721b16c01c7676fbfdd99cebba2bf64e8b807a28ff94d" url: "https://pub.dev" source: hosted - version: "1.13.0" + version: "1.15.0" cross_file: dependency: transitive description: name: cross_file - sha256: "7caf6a750a0c04effbb52a676dce9a4a592e10ad35c34d6d2d0e4811160d5670" + sha256: "701dcfc06da0882883a2657c445103380e53e647060ad8d9dfb710c100996608" url: "https://pub.dev" source: hosted - version: "0.3.4+2" + version: "0.3.5+1" crypto: dependency: transitive description: name: crypto - sha256: "1e445881f28f22d6140f181e07737b22f1e099a5e1ff94b0af2f9e4a463f4855" + sha256: c8ea0233063ba03258fbcf2ca4d6dadfefe14f02fab57702265467a19f27fadf url: "https://pub.dev" source: hosted - version: "3.0.6" + version: "3.0.7" cupertino_icons: dependency: "direct main" description: @@ -217,22 +225,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.8" - danger_core: - dependency: "direct dev" - description: - name: danger_core - sha256: f949e126fcd0dcbd41ce973d733f2cb18426ee3870c0ec16ad331a2d9ce698c1 - url: "https://pub.dev" - source: hosted - version: "2.0.0" dart_style: dependency: transitive description: name: dart_style - sha256: "5b236382b47ee411741447c1f1e111459c941ea1b3f2b540dde54c210a3662af" + sha256: "8a0e5fba27e8ee025d2ffb4ee820b4e6e2cf5e4246a6b1a477eb66866947e0bb" url: "https://pub.dev" source: hosted - version: "3.1.0" + version: "3.1.1" eagle_eye: dependency: "direct dev" description: @@ -321,18 +321,18 @@ packages: dependency: "direct main" description: name: freezed - sha256: "6022db4c7bfa626841b2a10f34dd1e1b68e8f8f9650db6112dcdeeca45ca793c" + sha256: "13065f10e135263a4f5a4391b79a8efc5fb8106f8dd555a9e49b750b45393d77" url: "https://pub.dev" source: hosted - version: "3.0.6" + version: "3.2.3" freezed_annotation: dependency: "direct main" description: name: freezed_annotation - sha256: c87ff004c8aa6af2d531668b46a4ea379f7191dc6dfa066acd53d506da6e044b + sha256: "7294967ff0a6d98638e7acb774aac3af2550777accd8149c90af5b014e6d44d8" url: "https://pub.dev" source: hosted - version: "3.0.0" + version: "3.1.0" frontend_server_client: dependency: transitive description: @@ -345,10 +345,10 @@ packages: dependency: "direct main" description: name: get_it - sha256: f126a3e286b7f5b578bf436d5592968706c4c1de28a228b870ce375d9f743103 + sha256: ae78de7c3f2304b8d81f2bb6e320833e5e81de942188542328f074978cc0efa9 url: "https://pub.dev" source: hosted - version: "8.0.3" + version: "8.3.0" glob: dependency: transitive description: @@ -365,14 +365,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.3.2" - http: + hotreloader: dependency: transitive description: - name: http - sha256: "2c11f3f94c687ee9bad77c171151672986360b2b001d109814ee7140b2cf261b" + name: hotreloader + sha256: bc167a1163807b03bada490bfe2df25b0d744df359227880220a5cbd04e5734b url: "https://pub.dev" source: hosted - version: "1.4.0" + version: "4.3.0" http_multi_server: dependency: transitive description: @@ -393,18 +393,18 @@ packages: dependency: "direct main" description: name: injectable - sha256: "5e1556ea1d374fe44cbe846414d9bab346285d3d8a1da5877c01ad0774006068" + sha256: "32e9bac6fe9c84339c5add60478d27a01e363ce1ad5c22ca7e525c6b28a7559c" url: "https://pub.dev" source: hosted - version: "2.5.0" + version: "2.7.0" injectable_generator: dependency: "direct dev" description: name: injectable_generator - sha256: b04673a4c88b3a848c0c77bf58b8309f9b9e064d9fe1df5450c8ee1675eaea1a + sha256: "09c55dba52b53d17411b90134a6751270b8930abd2529e2637d700fc99b0d5b5" url: "https://pub.dev" source: hosted - version: "2.7.0" + version: "2.8.1" intl: dependency: "direct main" description: @@ -441,26 +441,34 @@ packages: dependency: transitive description: name: leak_tracker - sha256: "6bb818ecbdffe216e81182c2f0714a2e62b593f4a4f13098713ff1685dfb6ab0" + sha256: "33e2e26bdd85a0112ec15400c8cbffea70d0f9c3407491f672a2fad47915e2de" url: "https://pub.dev" source: hosted - version: "10.0.9" + version: "11.0.2" leak_tracker_flutter_testing: dependency: transitive description: name: leak_tracker_flutter_testing - sha256: f8b613e7e6a13ec79cfdc0e97638fddb3ab848452eff057653abd3edba760573 + sha256: "1dbc140bb5a23c75ea9c4811222756104fbcd1a27173f0c34ca01e16bea473c1" url: "https://pub.dev" source: hosted - version: "3.0.9" + version: "3.0.10" leak_tracker_testing: dependency: transitive description: name: leak_tracker_testing - sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3" + sha256: "8d5a2d49f4a66b49744b23b018848400d23e54caf9463f4eb20df3eb8acb2eb1" + url: "https://pub.dev" + source: hosted + version: "3.0.2" + lean_builder: + dependency: transitive + description: + name: lean_builder + sha256: ef5cd5f907157eb7aa87d1704504b5a6386d2cbff88a3c2b3344477bab323ee9 url: "https://pub.dev" source: hosted - version: "3.0.1" + version: "0.1.2" lints: dependency: transitive description: @@ -497,10 +505,10 @@ packages: dependency: transitive description: name: meta - sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c + sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394" url: "https://pub.dev" source: hosted - version: "1.16.0" + version: "1.17.0" mime: dependency: transitive description: @@ -553,18 +561,18 @@ packages: dependency: transitive description: name: path_provider_android - sha256: d0d310befe2c8ab9e7f393288ccbb11b60c019c6b5afc21973eeee4dda2b35e9 + sha256: f2c65e21139ce2c3dad46922be8272bb5963516045659e71bb16e151c93b580e url: "https://pub.dev" source: hosted - version: "2.2.17" + version: "2.2.22" path_provider_foundation: dependency: transitive description: name: path_provider_foundation - sha256: "4843174df4d288f5e29185bd6e72a6fbdf5a4a4602717eed565497429f179942" + sha256: "6d13aece7b3f5c5a9731eaf553ff9dcbc2eff41087fd2df587fd0fed9a3eb0c4" url: "https://pub.dev" source: hosted - version: "2.4.1" + version: "2.5.1" path_provider_linux: dependency: transitive description: @@ -609,18 +617,18 @@ packages: dependency: transitive description: name: pool - sha256: "20fe868b6314b322ea036ba325e6fc0711a22948856475e2c2b6306e8ab39c2a" + sha256: "978783255c543aa3586a1b3c21f6e9d720eb315376a915872c61ef8b5c20177d" url: "https://pub.dev" source: hosted - version: "1.5.1" + version: "1.5.2" provider: dependency: transitive description: name: provider - sha256: "4abbd070a04e9ddc287673bf5a030c7ca8b685ff70218720abab8b092f53dd84" + sha256: "4e82183fa20e5ca25703ead7e05de9e4cceed1fbd1eadc1ac3cb6f565a09f272" url: "https://pub.dev" source: hosted - version: "6.1.5" + version: "6.1.5+1" pub_semver: dependency: transitive description: @@ -702,10 +710,10 @@ packages: dependency: transitive description: name: source_gen - sha256: "35c8150ece9e8c8d263337a265153c3329667640850b9304861faea59fc98f6b" + sha256: "7b19d6ba131c6eb98bfcbf8d56c1a7002eba438af2e7ae6f8398b2b0f4f381e3" url: "https://pub.dev" source: hosted - version: "2.0.0" + version: "3.1.0" source_map_stack_trace: dependency: transitive description: @@ -730,14 +738,6 @@ packages: url: "https://pub.dev" source: hosted version: "1.10.1" - sprintf: - dependency: transitive - description: - name: sprintf - sha256: "1fc9ffe69d4df602376b52949af107d8f5703b77cda567c4d7d86a0693120f23" - url: "https://pub.dev" - source: hosted - version: "7.0.0" sqflite: dependency: "direct main" description: @@ -750,18 +750,18 @@ packages: dependency: transitive description: name: sqflite_android - sha256: "2b3070c5fa881839f8b402ee4a39c1b4d561704d4ebbbcfb808a119bc2a1701b" + sha256: ecd684501ebc2ae9a83536e8b15731642b9570dc8623e0073d227d0ee2bfea88 url: "https://pub.dev" source: hosted - version: "2.4.1" + version: "2.4.2+2" sqflite_common: dependency: transitive description: name: sqflite_common - sha256: "84731e8bfd8303a3389903e01fb2141b6e59b5973cacbb0929021df08dddbe8b" + sha256: "6ef422a4525ecc601db6c0a2233ff448c731307906e92cabc9ba292afaae16a6" url: "https://pub.dev" source: hosted - version: "2.5.5" + version: "2.5.6" sqflite_darwin: dependency: transitive description: @@ -814,10 +814,10 @@ packages: dependency: transitive description: name: synchronized - sha256: "0669c70faae6270521ee4f05bffd2919892d42d1276e6c495be80174b6bc0ef6" + sha256: c254ade258ec8282947a0acbbc90b9575b4f19673533ee46f2f6e9b3aeefd7c0 url: "https://pub.dev" source: hosted - version: "3.3.1" + version: "3.4.0" term_glyph: dependency: transitive description: @@ -830,26 +830,26 @@ packages: dependency: "direct dev" description: name: test - sha256: "301b213cd241ca982e9ba50266bd3f5bd1ea33f1455554c5abb85d1be0e2d87e" + sha256: "75906bf273541b676716d1ca7627a17e4c4070a3a16272b7a3dc7da3b9f3f6b7" url: "https://pub.dev" source: hosted - version: "1.25.15" + version: "1.26.3" test_api: dependency: transitive description: name: test_api - sha256: fb31f383e2ee25fbbfe06b40fe21e1e458d14080e3c67e7ba0acfde4df4e0bbd + sha256: ab2726c1a94d3176a45960b6234466ec367179b87dd74f1611adb1f3b5fb9d55 url: "https://pub.dev" source: hosted - version: "0.7.4" + version: "0.7.7" test_core: dependency: transitive description: name: test_core - sha256: "84d17c3486c8dfdbe5e12a50c8ae176d15e2a771b96909a9442b40173649ccaa" + sha256: "0cc24b5ff94b38d2ae73e1eb43cc302b77964fbf67abad1e296025b78deb53d0" url: "https://pub.dev" source: hosted - version: "0.6.8" + version: "0.6.12" timing: dependency: transitive description: @@ -870,10 +870,10 @@ packages: dependency: transitive description: name: url_launcher_linux - sha256: "4e9ba368772369e3e08f231d2301b4ef72b9ff87c31192ef471b380ef29a4935" + sha256: d5e14138b3bc193a0f63c10a53c94b91d399df0512b1f29b94a043db7482384a url: "https://pub.dev" source: hosted - version: "3.2.1" + version: "3.2.2" url_launcher_platform_interface: dependency: transitive description: @@ -894,42 +894,42 @@ packages: dependency: transitive description: name: url_launcher_windows - sha256: "3284b6d2ac454cf34f114e1d3319866fdd1e19cdc329999057e44ffe936cfa77" + sha256: "712c70ab1b99744ff066053cbe3e80c73332b38d46e5e945c98689b2e66fc15f" url: "https://pub.dev" source: hosted - version: "3.1.4" + version: "3.1.5" uuid: dependency: transitive description: name: uuid - sha256: a5be9ef6618a7ac1e964353ef476418026db906c4facdedaa299b7a2e71690ff + sha256: a11b666489b1954e01d992f3d601b1804a33937b5a8fe677bd26b8a9f96f96e8 url: "https://pub.dev" source: hosted - version: "4.5.1" + version: "4.5.2" vector_math: dependency: transitive description: name: vector_math - sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803" + sha256: d530bd74fea330e6e364cda7a85019c434070188383e1cd8d9777ee586914c5b url: "https://pub.dev" source: hosted - version: "2.1.4" + version: "2.2.0" vm_service: dependency: transitive description: name: vm_service - sha256: ddfa8d30d89985b96407efce8acbdd124701f96741f2d981ca860662f1c0dc02 + sha256: "45caa6c5917fa127b5dbcfbd1fa60b14e583afdc08bfc96dda38886ca252eb60" url: "https://pub.dev" source: hosted - version: "15.0.0" + version: "15.0.2" watcher: dependency: transitive description: name: watcher - sha256: "69da27e49efa56a15f8afe8f4438c4ec02eff0a117df1b22ea4aad194fe1c104" + sha256: "592ab6e2892f67760543fb712ff0177f4ec76c031f02f5b4ff8d3fc5eb9fb61a" url: "https://pub.dev" source: hosted - version: "1.1.1" + version: "1.1.4" web: dependency: transitive description: @@ -942,10 +942,10 @@ packages: dependency: transitive description: name: web_socket - sha256: bfe6f435f6ec49cb6c01da1e275ae4228719e59a6b067048c51e72d9d63bcc4b + sha256: "34d64019aa8e36bf9842ac014bb5d2f5586ca73df5e4d9bf5c936975cae6982c" url: "https://pub.dev" source: hosted - version: "1.0.0" + version: "1.0.1" web_socket_channel: dependency: transitive description: @@ -966,10 +966,10 @@ packages: dependency: transitive description: name: win32 - sha256: "66814138c3562338d05613a6e368ed8cfb237ad6d64a9e9334be3f309acfca03" + sha256: d7cb55e04cd34096cd3a79b3330245f54cb96a370a1c27adb3c84b917de8b08e url: "https://pub.dev" source: hosted - version: "5.14.0" + version: "5.15.0" xdg_directories: dependency: transitive description: @@ -978,6 +978,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.1.0" + xxh3: + dependency: transitive + description: + name: xxh3 + sha256: "399a0438f5d426785723c99da6b16e136f4953fb1e9db0bf270bd41dd4619916" + url: "https://pub.dev" + source: hosted + version: "1.2.0" yaml: dependency: transitive description: @@ -987,5 +995,5 @@ packages: source: hosted version: "3.1.3" sdks: - dart: ">=3.8.0 <4.0.0" - flutter: ">=3.27.0" + dart: ">=3.9.0 <4.0.0" + flutter: ">=3.35.0" diff --git a/pubspec.yaml b/pubspec.yaml index 38e1863..ebe9e04 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -63,7 +63,6 @@ dev_dependencies: injectable_generator: ^2.5.0 build_runner: ^2.5.4 auto_route_generator: ^10.1.0 - danger_core: ^2.0.0 eagle_eye: ^1.0.0 # For information on the generic Dart part of this file, see the diff --git a/test/domain/task_helper_format_tasklist_test.dart b/test/domain/format_task_list_message_use_case_test.dart similarity index 72% rename from test/domain/task_helper_format_tasklist_test.dart rename to test/domain/format_task_list_message_use_case_test.dart index ad80efa..6f0066e 100644 --- a/test/domain/task_helper_format_tasklist_test.dart +++ b/test/domain/format_task_list_message_use_case_test.dart @@ -1,16 +1,16 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:todoapp/data/model/task.dart'; -import 'package:todoapp/domain/tasks_helper.dart'; +import 'package:todoapp/domain/format_task_list_message_use_case.dart'; void main() { test( 'formatTaskList -> test empty task list', () { // Arrange - final taskHelper = TasksHelperImpl(); + final formatTaskListMessageUseCase = FormatTaskListMessageUseCaseImpl(); // Act - final result = taskHelper.formatTaskList( + final result = formatTaskListMessageUseCase.formatTaskList( tasks: [], ); @@ -23,10 +23,10 @@ void main() { 'formatTaskList -> test only not completed', () { // Arrange - final taskHelper = TasksHelperImpl(); + final formatTaskListMessageUseCase = FormatTaskListMessageUseCaseImpl(); // Act - final result = taskHelper.formatTaskList( + final result = formatTaskListMessageUseCase.formatTaskList( tasks: [ const Task( id: null, diff --git a/test/domain/task_helper_calculate_progress_test.dart b/test/domain/progress_counter_use_case_test.dart similarity index 78% rename from test/domain/task_helper_calculate_progress_test.dart rename to test/domain/progress_counter_use_case_test.dart index 6d6c5e5..64e7f3a 100644 --- a/test/domain/task_helper_calculate_progress_test.dart +++ b/test/domain/progress_counter_use_case_test.dart @@ -1,16 +1,16 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:todoapp/data/model/task.dart'; -import 'package:todoapp/domain/tasks_helper.dart'; +import 'package:todoapp/domain/progress_counter_use_case.dart'; void main() { test( 'calculateProgress -> test empty task list', - () { + () { // Arrange - final taskHelper = TasksHelperImpl(); + final progressCounterUseCase = ProgressCounterUseCaseImpl(); // Act - final result = taskHelper.calculateProgress( + final result = progressCounterUseCase.calculateProgress( tasks: [], ); @@ -21,12 +21,12 @@ void main() { test( 'calculateProgress -> test a positive progress', - () { + () { // Arrange - final taskHelper = TasksHelperImpl(); + final progressCounterUseCase = ProgressCounterUseCaseImpl(); // Act - final result = taskHelper.calculateProgress( + final result = progressCounterUseCase.calculateProgress( tasks: [ const Task( id: null, @@ -56,12 +56,12 @@ void main() { test( 'calculateProgress -> test a bad progress', - () { + () { // Arrange - final taskHelper = TasksHelperImpl(); + final progressCounterUseCase = ProgressCounterUseCaseImpl(); // Act - final result = taskHelper.calculateProgress( + final result = progressCounterUseCase.calculateProgress( tasks: [ const Task( id: null, @@ -91,12 +91,12 @@ void main() { test( 'calculateProgress -> test a great progress', - () { + () { // Arrange - final useCase = TasksHelperImpl(); + final progressCounterUseCase = ProgressCounterUseCaseImpl(); // Act - final result = useCase.calculateProgress( + final result = progressCounterUseCase.calculateProgress( tasks: [ const Task( id: null, @@ -126,12 +126,12 @@ void main() { test( 'calculateProgress -> test a horrible progress', - () { + () { // Arrange - final taskHelper = TasksHelperImpl(); + final progressCounterUseCase = ProgressCounterUseCaseImpl(); // Act - final result = taskHelper.calculateProgress( + final result = progressCounterUseCase.calculateProgress( tasks: [ const Task( id: null, @@ -155,4 +155,4 @@ void main() { expect(result, 0.0); }, ); -} \ No newline at end of file +} diff --git a/test/domain/task_helper_should_show_share_test.dart b/test/domain/should_show_share_button_use_case_test.dart similarity index 72% rename from test/domain/task_helper_should_show_share_test.dart rename to test/domain/should_show_share_button_use_case_test.dart index c9bf3e1..f002967 100644 --- a/test/domain/task_helper_should_show_share_test.dart +++ b/test/domain/should_show_share_button_use_case_test.dart @@ -1,16 +1,16 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:todoapp/data/model/task.dart'; -import 'package:todoapp/domain/tasks_helper.dart'; +import 'package:todoapp/domain/should_show_share_button_use_case.dart'; void main() { test( 'shouldShowShareButton -> test empty task list', () { // Arrange - final taskHelper = TasksHelperImpl(); + final shouldShowShareButtonUseCase = ShouldShowShareButtonUseCaseImpl(); // Act - final result = taskHelper.shouldShowShareButton([]); + final result = shouldShowShareButtonUseCase.shouldShowShareButton([]); // Assert expect(result, false); @@ -21,10 +21,10 @@ void main() { 'shouldShowShareButton -> there is a task not completed', () { // Arrange - final taskHelper = TasksHelperImpl(); + final shouldShowShareButtonUseCase = ShouldShowShareButtonUseCaseImpl(); // Act - final result = taskHelper.shouldShowShareButton( + final result = shouldShowShareButtonUseCase.shouldShowShareButton( [ const Task( id: null, @@ -56,10 +56,10 @@ void main() { 'shouldShowShareButton -> there is none not completed task', () { // Arrange - final taskHelper = TasksHelperImpl(); + final shouldShowShareButtonUseCase = ShouldShowShareButtonUseCaseImpl(); // Act - final result = taskHelper.shouldShowShareButton( + final result = shouldShowShareButtonUseCase.shouldShowShareButton( [ const Task( id: null, diff --git a/test/domain/tasks_comparator_use_case_test.dart b/test/domain/tasks_comparator_use_case_test.dart new file mode 100644 index 0000000..05b8625 --- /dev/null +++ b/test/domain/tasks_comparator_use_case_test.dart @@ -0,0 +1,111 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:todoapp/data/model/task.dart'; +import 'package:todoapp/domain/tasks_comparator_use_case.dart'; + +void main() { + test( + 'areThemEqual -> test empty task list', + () { + // Arrange + final tasksComparatorUseCase = TasksComparatorUseCaseImpl(); + + // Act + final result = tasksComparatorUseCase.areThemEqual( + oldList: [], + newList: [], + ); + + // Assert + expect(result, true); + }, + ); + + test( + 'areThemEqual -> test different sizes', + () { + // Arrange + final tasksComparatorUseCase = TasksComparatorUseCaseImpl(); + + // Act + final result = tasksComparatorUseCase.areThemEqual( + oldList: [const Task(isCompleted: false, title: 'test', id: 1)], + newList: [ + const Task(isCompleted: false, title: 'test', id: 1), + const Task(isCompleted: true, title: 'test2', id: 2) + ], + ); + + // Assert + expect(result, false); + }, + ); + + test( + 'areThemEqual -> test same size with different elements', + () { + // Arrange + final tasksComparatorUseCase = TasksComparatorUseCaseImpl(); + + // Act + final result = tasksComparatorUseCase.areThemEqual( + oldList: [ + const Task(isCompleted: true, title: 'test2', id: 2), + const Task(isCompleted: false, title: 'test1', id: 1) + ], + newList: [ + const Task(isCompleted: false, title: 'test1', id: 1), + const Task(isCompleted: true, title: 'test2', id: 2), + ], + ); + + // Assert + expect(result, false); + }, + ); + + test( + 'areThemEqual -> test same size with equal elements but different values', + () { + // Arrange + final tasksComparatorUseCase = TasksComparatorUseCaseImpl(); + + // Act + final result = tasksComparatorUseCase.areThemEqual( + oldList: [ + const Task(isCompleted: true, title: 'test1', id: 1), + const Task(isCompleted: false, title: 'test2', id: 2) + ], + newList: [ + const Task(isCompleted: true, title: 'test1', id: 1), + const Task(isCompleted: true, title: 'test2', id: 2), + ], + ); + + // Assert + expect(result, false); + }, + ); + + test( + 'areThemEqual -> test same size with equal elements + equal values', + () { + // Arrange + final tasksComparatorUseCase = TasksComparatorUseCaseImpl(); + + // Act + final result = tasksComparatorUseCase.areThemEqual( + oldList: [ + const Task(isCompleted: true, title: 'test1', id: 1), + const Task(isCompleted: false, title: 'test2', id: 2) + ], + newList: [ + const Task(isCompleted: true, title: 'test1', id: 1), + const Task(isCompleted: false, title: 'test2', id: 2), + ], + ); + + // Assert + expect(result, true); + }, + ); +} diff --git a/test/domain/task_helper_sort_elements_test.dart b/test/domain/tasks_sorter_use_case_test.dart similarity index 72% rename from test/domain/task_helper_sort_elements_test.dart rename to test/domain/tasks_sorter_use_case_test.dart index 90c9f87..bc73607 100644 --- a/test/domain/task_helper_sort_elements_test.dart +++ b/test/domain/tasks_sorter_use_case_test.dart @@ -1,16 +1,16 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:todoapp/data/model/task.dart'; -import 'package:todoapp/domain/tasks_helper.dart'; +import 'package:todoapp/domain/tasks_sorter_use_case.dart'; void main() { test( 'sortByCompletedStatus -> test empty task list', () { // Arrange - final taskHelper = TasksHelperImpl(); + final tasksSorterUseCase = TasksSorterUseCaseImpl(); // Act - final result = taskHelper.sortByCompletedStatus([]); + final result = tasksSorterUseCase.sortByCompletedStatus([]); // Assert expect(result, []); @@ -21,8 +21,8 @@ void main() { 'sortByCompletedStatus -> there is a task not completed', () { // Arrange - final taskHelper = TasksHelperImpl(); - const taskB = Task( + final tasksSorterUseCase = TasksSorterUseCaseImpl(); + const taskB = Task( id: null, title: 'Task B - Completed', isCompleted: true, @@ -39,7 +39,7 @@ void main() { ); // Act - final result = taskHelper.sortByCompletedStatus( + final result = tasksSorterUseCase.sortByCompletedStatus( [ taskB, taskA, diff --git a/test/test_utils/fakes/fake_callbacks.dart b/test/test_utils/fakes/fake_callbacks.dart index 5d54e91..0ce151a 100644 --- a/test/test_utils/fakes/fake_callbacks.dart +++ b/test/test_utils/fakes/fake_callbacks.dart @@ -2,7 +2,7 @@ import 'package:todoapp/ui/screens/tasks/tasks_screen_callbacks.dart'; class FakeCallbacks { static final emptyTasksScreenCallbacks = TasksScreenCallbacks( - updateTasks: () => const {}, + updateTasks: (_) => const {}, onShare: () => const {}, onCompleteTask: (_, __) => const {}, onRemoveTask: (_) => const {}, diff --git a/test/test_utils/fakes/fake_states.dart b/test/test_utils/fakes/fake_states.dart index 2b08504..1808cd1 100644 --- a/test/test_utils/fakes/fake_states.dart +++ b/test/test_utils/fakes/fake_states.dart @@ -1,5 +1,5 @@ import 'package:todoapp/data/model/task.dart'; -import 'package:todoapp/ui/screens/tasks/tasks_screen_state.dart'; +import 'package:todoapp/ui/components/widgets/task/taskslist/tasks_screen_state.dart'; class FakeStates { static const fakeTasksEmptyState = TasksScreenState( diff --git a/test/test_utils/fakes/fake_text_values.dart b/test/test_utils/fakes/fake_text_values.dart deleted file mode 100644 index 74e4da9..0000000 --- a/test/test_utils/fakes/fake_text_values.dart +++ /dev/null @@ -1,37 +0,0 @@ -import 'package:todoapp/ui/screens/checklist/checklist_screen_text_values.dart'; -import 'package:todoapp/ui/screens/checklists/checklists_screen_text_values.dart'; -import 'package:todoapp/ui/screens/task/task_screen_text_values.dart'; -import 'package:todoapp/ui/screens/tasks/tasks_screen_text_values.dart'; - -class FakeTextValues { - static const tasksScreenTextValues = TasksScreenTextValues( - tasksRefresh: 'Task list refreshed', - removeTaskDialogTitle: 'Remove Task', - removeTaskDialogDesc: 'Are you sure?', - yes: 'yes', - no: 'no', - sortMessage: 'It is sorted', - emptyTasksMessage: 'No tasks available', - ); - - static const taskScreenTextValues = TaskScreenTextValues( - taskErrorMessage: 'Task name is required', - taskLabel: 'Task', - ); - - static const checklistsScreenTextValues = ChecklistsScreenTextValues( - screenTitle: 'checklists', - checklistAdded: 'A new checklist has been added', - removeChecklistDialogTitle: 'Are you sure you want to remove it?', - removeChecklistDialogDesc: 'Remove this checklist', - yes: 'yes', - no: 'no', - emptyChecklistMessage: 'You have no checklists', - ); - - static const checklistScreenTextValues = ChecklistScreenTextValues( - screenTitle: 'Checklist', - checklistLabel: 'Checklist', - checklistErrorMessage: 'Checklist name is required', - ); -} diff --git a/test/test_utils/widgets_util.dart b/test/test_utils/widgets_util.dart index 217b238..268132c 100644 --- a/test/test_utils/widgets_util.dart +++ b/test/test_utils/widgets_util.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; +import 'package:todoapp/ui/l10n/app_localizations.dart'; class WidgetsUtil { static Widget buildMaterialAppWidgetTest({ @@ -13,6 +14,10 @@ class WidgetsUtil { return MaterialApp( home: child, themeMode: ThemeMode.dark, + // Forcing en-US because our unit tests are using english words + locale: const Locale('en'), + // Required to load the localizations to avoid null pointer + localizationsDelegates: AppLocalizations.localizationsDelegates, darkTheme: ThemeData( colorScheme: ColorScheme.fromSeed( seedColor: baseColor, diff --git a/test/ui/tasks/tasks_viewmodel_test.dart b/test/ui/components/widgets/task/taskslist/tasks_viewmodel_test.dart similarity index 54% rename from test/ui/tasks/tasks_viewmodel_test.dart rename to test/ui/components/widgets/task/taskslist/tasks_viewmodel_test.dart index 6e5d38c..f629897 100644 --- a/test/ui/tasks/tasks_viewmodel_test.dart +++ b/test/ui/components/widgets/task/taskslist/tasks_viewmodel_test.dart @@ -1,30 +1,36 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:todoapp/data/model/task.dart'; -import 'package:todoapp/domain/tasks_helper.dart'; -import 'package:todoapp/ui/screens/tasks/tasks_screen_state.dart'; -import 'package:todoapp/ui/screens/tasks/tasks_viewmodel.dart'; +import 'package:todoapp/domain/format_task_list_message_use_case.dart'; +import 'package:todoapp/domain/progress_counter_use_case.dart'; +import 'package:todoapp/domain/should_show_share_button_use_case.dart'; +import 'package:todoapp/domain/tasks_comparator_use_case.dart'; +import 'package:todoapp/domain/tasks_sorter_use_case.dart'; +import 'package:todoapp/ui/components/widgets/task/taskslist/tasks_screen_state.dart'; +import 'package:todoapp/ui/components/widgets/task/taskslist/tasks_viewmodel.dart'; -import '../../test_utils/fakes/fake_repository.dart'; -import '../../test_utils/fakes/fake_share_message_handler.dart'; +import '../../../../../test_utils/fakes/fake_repository.dart'; +import '../../../../../test_utils/fakes/fake_share_message_handler.dart'; void main() { + late TasksViewModel viewModel; + late FakeRepository fakeRepository; + + setUp(() { + fakeRepository = FakeRepository(tasks: [], checklists: []); + viewModel = TasksViewModel( + repository: fakeRepository, + progressCounterUseCase: ProgressCounterUseCaseImpl(), + tasksSorterUseCase: TasksSorterUseCaseImpl(), + shareMessageHandler: FakeShareMessageHandler(), + tasksComparatorUseCase: TasksComparatorUseCaseImpl(), + formatTaskListMessageUseCase: FormatTaskListMessageUseCaseImpl(), + shouldShowShareButtonUseCase: ShouldShowShareButtonUseCaseImpl(), + ); + }); + test( 'TasksViewModel -> test initial state', () { - // Arrange - final repository = FakeRepository( - tasks: [], - checklists: [], - ); - - // Act - final viewModel = TasksViewModel( - repository: repository, - checklistId: null, - shareMessageHandler: FakeShareMessageHandler(), - tasksHelper: TasksHelperImpl(), - ); - // Assert expect( viewModel.state, @@ -47,19 +53,11 @@ void main() { title: 'Task 1', isCompleted: false, ); - final repository = FakeRepository( - tasks: [task1], - checklists: [], - ); - final viewModel = TasksViewModel( - repository: repository, - checklistId: null, - shareMessageHandler: FakeShareMessageHandler(), - tasksHelper: TasksHelperImpl(), - ); + List tasks = [task1]; + await fakeRepository.updateAllTasks(tasks); // Act - await viewModel.updateTasks(); + await viewModel.updateTasks(null); // Assert expect( @@ -85,18 +83,9 @@ void main() { isCompleted: false, ); // Arrange - final repository = FakeRepository( - tasks: [task1], - checklists: [], - ); - final viewModel = TasksViewModel( - repository: repository, - checklistId: null, - tasksHelper: TasksHelperImpl(), - shareMessageHandler: FakeShareMessageHandler(), - ); - - await viewModel.updateTasks(); + List tasks = [task1]; + await fakeRepository.updateAllTasks(tasks); + await viewModel.updateTasks(null); // Act await viewModel.onCompleteTask( @@ -133,18 +122,9 @@ void main() { isCompleted: false, ); // Arrange - final repository = FakeRepository( - tasks: [task1], - checklists: [], - ); - final viewModel = TasksViewModel( - repository: repository, - checklistId: null, - shareMessageHandler: FakeShareMessageHandler(), - tasksHelper: TasksHelperImpl(), - ); - - await viewModel.updateTasks(); + List tasks = [task1]; + await fakeRepository.updateAllTasks(tasks); + await viewModel.updateTasks(null); // Act await viewModel.onRemoveTask(task1); @@ -177,18 +157,9 @@ void main() { ); // Arrange - final repository = FakeRepository( - tasks: [task1, task2], - checklists: [], - ); - final viewModel = TasksViewModel( - repository: repository, - checklistId: null, - shareMessageHandler: FakeShareMessageHandler(), - tasksHelper: TasksHelperImpl(), - ); - - await viewModel.updateTasks(); + List tasks = [task1, task2]; + await fakeRepository.updateAllTasks(tasks); + await viewModel.updateTasks(null); // Act viewModel.onSort(); diff --git a/test/ui/components/widget/task_cell_widget_test.dart b/test/ui/components/widgets/task_cell_widget_test.dart similarity index 100% rename from test/ui/components/widget/task_cell_widget_test.dart rename to test/ui/components/widgets/task_cell_widget_test.dart diff --git a/test/ui/screens/checklist_screen_test.dart b/test/ui/screens/checklist_screen_test.dart index ace734f..9ec327f 100644 --- a/test/ui/screens/checklist_screen_test.dart +++ b/test/ui/screens/checklist_screen_test.dart @@ -4,7 +4,6 @@ import 'package:todoapp/ui/components/form_validator.dart'; import 'package:todoapp/ui/screens/checklist/checklist_screen.dart'; import '../../test_utils/fakes/fake_navigator_provider.dart'; -import '../../test_utils/fakes/fake_text_values.dart'; import '../../test_utils/widgets_util.dart'; void main() { @@ -13,7 +12,6 @@ void main() { (tester) async { final widget = WidgetsUtil.buildMaterialAppWidgetTest( child: ChecklistScreenScaffold( - checklistScreenTextValues: FakeTextValues.checklistScreenTextValues, onAddNewChecklist: (_) => {}, formScreenValidator: FormScreenValidator(), navigatorProvider: FakeNavigatorProvider(), diff --git a/test/ui/screens/checklists_screen_test.dart b/test/ui/screens/checklists_screen_test.dart index 544ddd9..b919ffa 100644 --- a/test/ui/screens/checklists_screen_test.dart +++ b/test/ui/screens/checklists_screen_test.dart @@ -1,3 +1,4 @@ +import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:todoapp/data/model/checklist.dart'; import 'package:todoapp/ui/components/widgets/checklist/checklists_list_widget.dart'; @@ -5,14 +6,15 @@ import 'package:todoapp/ui/screens/checklists/checklists_screen.dart'; import 'package:todoapp/ui/screens/checklists/checklists_screen_state.dart'; import '../../test_utils/fakes/fake_navigator_provider.dart'; -import '../../test_utils/fakes/fake_text_values.dart'; import '../../test_utils/widgets_util.dart'; void main() { testWidgets( 'ChecklistsScreen - Empty message should appear if we have no checklists', (tester) async { - const emptyChecklistMessage = 'You have no checklists'; + tester.view.physicalSize = const Size(500, 800); + + const emptyChecklistMessage = 'No checklists available'; final widget = WidgetsUtil.buildMaterialAppWidgetTest( child: ChecklistsScaffold( @@ -20,7 +22,6 @@ void main() { checklists: [], isLoading: false, ), - checklistsScreenTextValues: FakeTextValues.checklistsScreenTextValues, onRemoveChecklist: (_) => {}, navigatorProvider: FakeNavigatorProvider(), updateChecklists: () => {}, @@ -39,6 +40,8 @@ void main() { testWidgets( 'ChecklistsScreen - Checklist widget should appear if we have checklists', (tester) async { + tester.view.physicalSize = const Size(500, 800); + final widget = WidgetsUtil.buildMaterialAppWidgetTest( child: ChecklistsScaffold( uiState: const ChecklistsScreenState( @@ -50,7 +53,6 @@ void main() { ], isLoading: false, ), - checklistsScreenTextValues: FakeTextValues.checklistsScreenTextValues, onRemoveChecklist: (_) => {}, navigatorProvider: FakeNavigatorProvider(), updateChecklists: () => {}, diff --git a/test/ui/screens/task_screen_test.dart b/test/ui/screens/task_screen_test.dart index 6af74fe..6319c79 100644 --- a/test/ui/screens/task_screen_test.dart +++ b/test/ui/screens/task_screen_test.dart @@ -4,7 +4,6 @@ import 'package:todoapp/ui/components/form_validator.dart'; import 'package:todoapp/ui/screens/task/task_screen.dart'; import '../../test_utils/fakes/fake_navigator_provider.dart'; -import '../../test_utils/fakes/fake_text_values.dart'; import '../../test_utils/widgets_util.dart'; void main() { @@ -18,7 +17,6 @@ void main() { return Future.value(false); }, floatingActionIcon: Icons.plus_one, - taskScreenTextValues: FakeTextValues.taskScreenTextValues, formScreenValidator: FormScreenValidator(), ), tester: tester, @@ -46,7 +44,6 @@ void main() { return Future.value(false); }, floatingActionIcon: Icons.save, - taskScreenTextValues: FakeTextValues.taskScreenTextValues, formScreenValidator: FormScreenValidator(), ), tester: tester, diff --git a/test/ui/screens/tasks_screen_test.dart b/test/ui/screens/tasks_screen_test.dart index 713c210..645a148 100644 --- a/test/ui/screens/tasks_screen_test.dart +++ b/test/ui/screens/tasks_screen_test.dart @@ -6,14 +6,13 @@ import 'package:todoapp/ui/screens/tasks/tasks_screen.dart'; import '../../test_utils/fakes/fake_callbacks.dart'; import '../../test_utils/fakes/fake_navigator_provider.dart'; import '../../test_utils/fakes/fake_states.dart'; -import '../../test_utils/fakes/fake_text_values.dart'; import '../../test_utils/widgets_util.dart'; void main() { testWidgets( 'TasksScreen - Empty state message should appear if there is no task', (tester) async { - const emptyMessage = 'No Tasks available'; + const emptyMessage = 'No tasks available'; final widget = WidgetsUtil.buildMaterialAppWidgetTest( child: TasksScaffold( @@ -21,9 +20,6 @@ void main() { uiState: FakeStates.fakeTasksEmptyState, checklistId: 1, checklistName: 'Pets', - tasksScreenTextValues: FakeTextValues.tasksScreenTextValues.copyWith( - emptyTasksMessage: emptyMessage, - ), callbacks: FakeCallbacks.emptyTasksScreenCallbacks, ), tester: tester, @@ -45,7 +41,6 @@ void main() { callbacks: FakeCallbacks.emptyTasksScreenCallbacks, checklistId: 1, checklistName: 'Pets', - tasksScreenTextValues: FakeTextValues.tasksScreenTextValues, ), tester: tester, ); @@ -72,7 +67,6 @@ void main() { callbacks: FakeCallbacks.emptyTasksScreenCallbacks, checklistId: 1, checklistName: 'pets', - tasksScreenTextValues: FakeTextValues.tasksScreenTextValues, ), tester: tester, ); @@ -99,7 +93,6 @@ void main() { callbacks: FakeCallbacks.emptyTasksScreenCallbacks, checklistId: 1, checklistName: 'pets', - tasksScreenTextValues: FakeTextValues.tasksScreenTextValues, ), tester: tester, ); @@ -120,7 +113,6 @@ void main() { callbacks: FakeCallbacks.emptyTasksScreenCallbacks, checklistId: 1, checklistName: 'pets', - tasksScreenTextValues: FakeTextValues.tasksScreenTextValues, ), tester: tester, );