From 33b7ec940709336c75ed6f422d9da9cdbfae3e1d Mon Sep 17 00:00:00 2001 From: Gabriel Moro Date: Sat, 29 Nov 2025 20:17:50 -0300 Subject: [PATCH 01/28] Update pubspec.lock --- pubspec.lock | 216 +++++++++++++++++++++++++++------------------------ 1 file changed, 116 insertions(+), 100 deletions(-) diff --git a/pubspec.lock b/pubspec.lock index d40cb28..dda9344 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: @@ -229,10 +237,10 @@ packages: 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 +329,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 +353,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 +373,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 +401,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 +449,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 +513,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 +569,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 +625,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 +718,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 +746,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 +758,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 +822,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 +838,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 +878,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 +902,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 +950,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 +974,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 +986,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 +1003,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" From 55bbac4b02348ee70068c87048a12761f46fab7c Mon Sep 17 00:00:00 2001 From: Gabriel Moro Date: Sat, 29 Nov 2025 20:20:12 -0300 Subject: [PATCH 02/28] Update flutter version --- .github/actions/setup-flutter/action.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 From 552e16d350a818bad6a7099315a0d1057136731e Mon Sep 17 00:00:00 2001 From: Gabriel Moro Date: Sun, 30 Nov 2025 14:37:59 -0300 Subject: [PATCH 03/28] Breaking taskhelper down into use cases --- .../format_task_list_message_use_case.dart | 21 ++++ lib/domain/progress_counter_use_case.dart | 25 ++++ .../should_show_share_button_use_case.dart | 15 +++ lib/domain/tasks_comparator_use_case.dart | 30 +++++ lib/domain/tasks_helper.dart | 66 ----------- lib/domain/tasks_sorter_use_case.dart | 27 +++++ lib/ui/screens/tasks/tasks_screen.dart | 6 +- lib/ui/screens/tasks/tasks_viewmodel.dart | 52 ++++++-- ...rmat_task_list_message_use_case_test.dart} | 10 +- ...rt => progress_counter_use_case_test.dart} | 34 +++--- ...ould_show_share_button_use_case_test.dart} | 14 +-- .../tasks_comparator_use_case_test.dart | 111 ++++++++++++++++++ ...t.dart => tasks_sorter_use_case_test.dart} | 12 +- test/ui/tasks/tasks_viewmodel_test.dart | 88 +++++--------- 14 files changed, 339 insertions(+), 172 deletions(-) create mode 100644 lib/domain/format_task_list_message_use_case.dart create mode 100644 lib/domain/progress_counter_use_case.dart create mode 100644 lib/domain/should_show_share_button_use_case.dart create mode 100644 lib/domain/tasks_comparator_use_case.dart delete mode 100644 lib/domain/tasks_helper.dart create mode 100644 lib/domain/tasks_sorter_use_case.dart rename test/domain/{task_helper_format_tasklist_test.dart => format_task_list_message_use_case_test.dart} (72%) rename test/domain/{task_helper_calculate_progress_test.dart => progress_counter_use_case_test.dart} (78%) rename test/domain/{task_helper_should_show_share_test.dart => should_show_share_button_use_case_test.dart} (72%) create mode 100644 test/domain/tasks_comparator_use_case_test.dart rename test/domain/{task_helper_sort_elements_test.dart => tasks_sorter_use_case_test.dart} (72%) 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/screens/tasks/tasks_screen.dart b/lib/ui/screens/tasks/tasks_screen.dart index 4cb1494..90f5aaf 100644 --- a/lib/ui/screens/tasks/tasks_screen.dart +++ b/lib/ui/screens/tasks/tasks_screen.dart @@ -34,7 +34,11 @@ class TasksScreen extends StatelessWidget { repository: getIt.get(), shareMessageHandler: getIt.get(), checklistId: checklist.id, - tasksHelper: getIt.get(), + shouldShowShareButtonUseCase: getIt.get(), + formatTaskListMessageUseCase: getIt.get(), + tasksSorterUseCase: getIt.get(), + tasksComparatorUseCase: getIt.get(), + progressCounterUseCase: getIt.get(), ); viewModel.updateTasks(); diff --git a/lib/ui/screens/tasks/tasks_viewmodel.dart b/lib/ui/screens/tasks/tasks_viewmodel.dart index ee79dab..c6ca1d2 100644 --- a/lib/ui/screens/tasks/tasks_viewmodel.dart +++ b/lib/ui/screens/tasks/tasks_viewmodel.dart @@ -1,7 +1,12 @@ +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/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/screens/tasks/tasks_screen_state.dart'; import 'package:todoapp/util/share_message_handler.dart'; @@ -9,14 +14,22 @@ class TasksViewModel extends Cubit { late TodoRepository _repository; late ShareMessageHandler _shareMessageHandler; - TasksHelper tasksHelper; + ShouldShowShareButtonUseCase shouldShowShareButtonUseCase; + TasksSorterUseCase tasksSorterUseCase; + TasksComparatorUseCase tasksComparatorUseCase; + ProgressCounterUseCase progressCounterUseCase; + FormatTaskListMessageUseCase formatTaskListMessageUseCase; late int? _checklistId; TasksViewModel({ required TodoRepository repository, required ShareMessageHandler shareMessageHandler, - required this.tasksHelper, + required this.shouldShowShareButtonUseCase, + required this.tasksSorterUseCase, + required this.tasksComparatorUseCase, + required this.progressCounterUseCase, + required this.formatTaskListMessageUseCase, int? checklistId, }) : super( const TasksScreenState( @@ -47,14 +60,16 @@ class TasksViewModel extends Cubit { 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 +94,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 +116,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 +141,9 @@ class TasksViewModel extends Cubit { emit( state.copyWith( isLoading: false, - showShareIcon: tasksHelper.shouldShowShareButton(tasks), + showShareIcon: shouldShowShareButtonUseCase.shouldShowShareButton( + tasks + ), tasks: tasks, ), ); @@ -131,10 +152,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/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/ui/tasks/tasks_viewmodel_test.dart b/test/ui/tasks/tasks_viewmodel_test.dart index 6e5d38c..39759a6 100644 --- a/test/ui/tasks/tasks_viewmodel_test.dart +++ b/test/ui/tasks/tasks_viewmodel_test.dart @@ -1,6 +1,10 @@ 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'; +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/screens/tasks/tasks_screen_state.dart'; import 'package:todoapp/ui/screens/tasks/tasks_viewmodel.dart'; @@ -8,23 +12,26 @@ 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(), + checklistId: null, + 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,16 +54,8 @@ 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(); @@ -85,17 +84,8 @@ void main() { isCompleted: false, ); // Arrange - final repository = FakeRepository( - tasks: [task1], - checklists: [], - ); - final viewModel = TasksViewModel( - repository: repository, - checklistId: null, - tasksHelper: TasksHelperImpl(), - shareMessageHandler: FakeShareMessageHandler(), - ); - + List tasks = [task1]; + await fakeRepository.updateAllTasks(tasks); await viewModel.updateTasks(); // Act @@ -133,17 +123,8 @@ void main() { isCompleted: false, ); // Arrange - final repository = FakeRepository( - tasks: [task1], - checklists: [], - ); - final viewModel = TasksViewModel( - repository: repository, - checklistId: null, - shareMessageHandler: FakeShareMessageHandler(), - tasksHelper: TasksHelperImpl(), - ); - + List tasks = [task1]; + await fakeRepository.updateAllTasks(tasks); await viewModel.updateTasks(); // Act @@ -177,17 +158,8 @@ void main() { ); // Arrange - final repository = FakeRepository( - tasks: [task1, task2], - checklists: [], - ); - final viewModel = TasksViewModel( - repository: repository, - checklistId: null, - shareMessageHandler: FakeShareMessageHandler(), - tasksHelper: TasksHelperImpl(), - ); - + List tasks = [task1, task2]; + await fakeRepository.updateAllTasks(tasks); await viewModel.updateTasks(); // Act From 1871f871ab015ad45a2fb01523b19666b04623b1 Mon Sep 17 00:00:00 2001 From: Gabriel Moro Date: Mon, 1 Dec 2025 23:09:03 -0300 Subject: [PATCH 04/28] TasksVIewModel working as expected in checklist_full_widget --- .../checklist/checklist_full_widget.dart | 124 ++++++++++++++++++ .../{ => taskslist}/tasks_list_widget.dart | 0 .../task/taskslist}/tasks_screen_state.dart | 0 .../task/taskslist}/tasks_viewmodel.dart | 12 +- .../screens/checklists/checklists_screen.dart | 43 ++++-- lib/ui/screens/tasks/tasks_screen.dart | 9 +- test/test_utils/fakes/fake_states.dart | 2 +- test/ui/tasks/tasks_viewmodel_test.dart | 4 +- 8 files changed, 165 insertions(+), 29 deletions(-) create mode 100644 lib/ui/components/widgets/checklist/checklist_full_widget.dart rename lib/ui/components/widgets/task/{ => taskslist}/tasks_list_widget.dart (100%) rename lib/ui/{screens/tasks => components/widgets/task/taskslist}/tasks_screen_state.dart (100%) rename lib/ui/{screens/tasks => components/widgets/task/taskslist}/tasks_viewmodel.dart (94%) 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..4582373 --- /dev/null +++ b/lib/ui/components/widgets/checklist/checklist_full_widget.dart @@ -0,0 +1,124 @@ +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/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; + final String emptyChecklistMessage; + + const ChecklistsListFullWidget({ + super.key, + required this.checklists, + required this.emptyChecklistMessage, + 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; + }); + }); + }); + } + + Widget _buildTaskList(BuildContext context) { + return Row( + children: [ + Expanded( + flex: 4, + child: ListView.builder( + padding: const EdgeInsets.only(top: 12, bottom: 120), + itemBuilder: (context, index) => ChecklistItemWidget( + 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: 'emptyTasksMessage', + 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 { + 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( + const SnackBar( + content: Text( + 'refresh', + ), + ), + ); + } + } + } +} 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 94% rename from lib/ui/screens/tasks/tasks_viewmodel.dart rename to lib/ui/components/widgets/task/taskslist/tasks_viewmodel.dart index c6ca1d2..057716b 100644 --- a/lib/ui/screens/tasks/tasks_viewmodel.dart +++ b/lib/ui/components/widgets/task/taskslist/tasks_viewmodel.dart @@ -7,7 +7,7 @@ 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/screens/tasks/tasks_screen_state.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 { @@ -20,8 +20,6 @@ class TasksViewModel extends Cubit { ProgressCounterUseCase progressCounterUseCase; FormatTaskListMessageUseCase formatTaskListMessageUseCase; - late int? _checklistId; - TasksViewModel({ required TodoRepository repository, required ShareMessageHandler shareMessageHandler, @@ -30,7 +28,6 @@ class TasksViewModel extends Cubit { required this.tasksComparatorUseCase, required this.progressCounterUseCase, required this.formatTaskListMessageUseCase, - int? checklistId, }) : super( const TasksScreenState( tasks: [], @@ -40,10 +37,7 @@ class TasksViewModel extends Cubit { ), ) { _repository = repository; - _shareMessageHandler = shareMessageHandler; - - _checklistId = checklistId; } void _onLoad() { @@ -52,10 +46,10 @@ 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, diff --git a/lib/ui/screens/checklists/checklists_screen.dart b/lib/ui/screens/checklists/checklists_screen.dart index 78f9fc6..85822f7 100644 --- a/lib/ui/screens/checklists/checklists_screen.dart +++ b/lib/ui/screens/checklists/checklists_screen.dart @@ -2,6 +2,8 @@ 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/data/model/task.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/confirmation_alert_dialog_widget.dart'; import 'package:todoapp/ui/components/widgets/custom_app_bar_widget.dart'; @@ -22,6 +24,9 @@ class ChecklistsScreen extends StatelessWidget { final viewModel = ChecklistsViewModel( GetItStartupHandlerWrapper.getIt.get(), ); + final NavigatorProvider navigatorProvider = + GetItStartupHandlerWrapper.getIt.get(); + viewModel.updateChecklists(); final checklistScreenTextValues = ChecklistsScreenTextValues( @@ -104,22 +109,36 @@ class ChecklistsScaffold extends StatelessWidget { ), 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), - ), - ), + child: _buildCheckListWidget(context), ), ); } + Widget _buildCheckListWidget(BuildContext context) { + if (MediaQuery.sizeOf(context).width > 600) { + return ChecklistsListFullWidget( + checklists: uiState.checklists, + emptyChecklistMessage: checklistsScreenTextValues.emptyChecklistMessage, + onRemoveChecklist: (checklist) { + _showConfirmationDialogToRemoveChecklist(context, checklist); + }, + navigatorProvider: navigatorProvider, + ); + } else { + return ChecklistsListWidget( + checklists: uiState.checklists, + emptyChecklistMessage: checklistsScreenTextValues.emptyChecklistMessage, + onRemoveChecklist: (checklist) { + _showConfirmationDialogToRemoveChecklist(context, checklist); + }, + onSelectChecklist: (checklist) => navigatorProvider.push( + context, + TasksRoute(checklist: checklist), + ), + ); + } + } + void _showConfirmationDialogToRemoveChecklist( BuildContext context, Checklist checklist, diff --git a/lib/ui/screens/tasks/tasks_screen.dart b/lib/ui/screens/tasks/tasks_screen.dart index 90f5aaf..ca01832 100644 --- a/lib/ui/screens/tasks/tasks_screen.dart +++ b/lib/ui/screens/tasks/tasks_screen.dart @@ -6,12 +6,12 @@ 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,14 +33,13 @@ class TasksScreen extends StatelessWidget { final viewModel = TasksViewModel( repository: getIt.get(), shareMessageHandler: getIt.get(), - checklistId: checklist.id, shouldShowShareButtonUseCase: getIt.get(), formatTaskListMessageUseCase: getIt.get(), tasksSorterUseCase: getIt.get(), tasksComparatorUseCase: getIt.get(), progressCounterUseCase: getIt.get(), ); - viewModel.updateTasks(); + viewModel.updateTasks(checklist.id); final tasksScreenTextValues = TasksScreenTextValues( tasksRefresh: AppLocalizations.of(context)!.tasks_refresh, 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/ui/tasks/tasks_viewmodel_test.dart b/test/ui/tasks/tasks_viewmodel_test.dart index 39759a6..21252cb 100644 --- a/test/ui/tasks/tasks_viewmodel_test.dart +++ b/test/ui/tasks/tasks_viewmodel_test.dart @@ -5,8 +5,8 @@ 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/screens/tasks/tasks_screen_state.dart'; -import 'package:todoapp/ui/screens/tasks/tasks_viewmodel.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'; From 73c11faa93f0364b1f29973990f35bffca256ccd Mon Sep 17 00:00:00 2001 From: Gabriel Moro Date: Tue, 2 Dec 2025 09:00:48 -0300 Subject: [PATCH 05/28] Update unit tests after moving task view model to the components package --- .../task/taskslist}/tasks_viewmodel_test.dart | 13 ++++++------- .../{widget => widgets}/task_cell_widget_test.dart | 0 2 files changed, 6 insertions(+), 7 deletions(-) rename test/ui/{tasks => components/widgets/task/taskslist}/tasks_viewmodel_test.dart (92%) rename test/ui/components/{widget => widgets}/task_cell_widget_test.dart (100%) diff --git a/test/ui/tasks/tasks_viewmodel_test.dart b/test/ui/components/widgets/task/taskslist/tasks_viewmodel_test.dart similarity index 92% rename from test/ui/tasks/tasks_viewmodel_test.dart rename to test/ui/components/widgets/task/taskslist/tasks_viewmodel_test.dart index 21252cb..f629897 100644 --- a/test/ui/tasks/tasks_viewmodel_test.dart +++ b/test/ui/components/widgets/task/taskslist/tasks_viewmodel_test.dart @@ -8,8 +8,8 @@ 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; @@ -20,7 +20,6 @@ void main() { viewModel = TasksViewModel( repository: fakeRepository, progressCounterUseCase: ProgressCounterUseCaseImpl(), - checklistId: null, tasksSorterUseCase: TasksSorterUseCaseImpl(), shareMessageHandler: FakeShareMessageHandler(), tasksComparatorUseCase: TasksComparatorUseCaseImpl(), @@ -58,7 +57,7 @@ void main() { await fakeRepository.updateAllTasks(tasks); // Act - await viewModel.updateTasks(); + await viewModel.updateTasks(null); // Assert expect( @@ -86,7 +85,7 @@ void main() { // Arrange List tasks = [task1]; await fakeRepository.updateAllTasks(tasks); - await viewModel.updateTasks(); + await viewModel.updateTasks(null); // Act await viewModel.onCompleteTask( @@ -125,7 +124,7 @@ void main() { // Arrange List tasks = [task1]; await fakeRepository.updateAllTasks(tasks); - await viewModel.updateTasks(); + await viewModel.updateTasks(null); // Act await viewModel.onRemoveTask(task1); @@ -160,7 +159,7 @@ void main() { // Arrange List tasks = [task1, task2]; await fakeRepository.updateAllTasks(tasks); - await viewModel.updateTasks(); + 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 From 4416d77292b0d0e0e7088fc645584655be7d2e69 Mon Sep 17 00:00:00 2001 From: Gabriel Moro Date: Tue, 2 Dec 2025 09:02:10 -0300 Subject: [PATCH 06/28] Remove non used component --- lib/ui/screens/checklists/checklists_screen.dart | 4 ---- 1 file changed, 4 deletions(-) diff --git a/lib/ui/screens/checklists/checklists_screen.dart b/lib/ui/screens/checklists/checklists_screen.dart index 85822f7..c6cd411 100644 --- a/lib/ui/screens/checklists/checklists_screen.dart +++ b/lib/ui/screens/checklists/checklists_screen.dart @@ -2,7 +2,6 @@ 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/data/model/task.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/confirmation_alert_dialog_widget.dart'; @@ -24,9 +23,6 @@ class ChecklistsScreen extends StatelessWidget { final viewModel = ChecklistsViewModel( GetItStartupHandlerWrapper.getIt.get(), ); - final NavigatorProvider navigatorProvider = - GetItStartupHandlerWrapper.getIt.get(); - viewModel.updateChecklists(); final checklistScreenTextValues = ChecklistsScreenTextValues( From 59839ed606297139ed3153deba025d7f39ea9e73 Mon Sep 17 00:00:00 2001 From: Gabriel Moro Date: Tue, 2 Dec 2025 09:12:48 -0300 Subject: [PATCH 07/28] Map when the cell is selected --- .../widgets/checklist/checklist_full_widget.dart | 1 + .../widgets/checklist/checklist_item_widget.dart | 10 +++++++++- macos/Podfile | 2 +- macos/Runner.xcodeproj/project.pbxproj | 6 +++--- 4 files changed, 14 insertions(+), 5 deletions(-) diff --git a/lib/ui/components/widgets/checklist/checklist_full_widget.dart b/lib/ui/components/widgets/checklist/checklist_full_widget.dart index 4582373..cd5db1f 100644 --- a/lib/ui/components/widgets/checklist/checklist_full_widget.dart +++ b/lib/ui/components/widgets/checklist/checklist_full_widget.dart @@ -74,6 +74,7 @@ class _ChecklistsListFullWidgetState extends State { 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], 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/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; From 97a9804c33d85c5e9e29d9e9b3459dece3cc09a7 Mon Sep 17 00:00:00 2001 From: Gabriel Moro Date: Tue, 2 Dec 2025 09:32:56 -0300 Subject: [PATCH 08/28] Update UI tests to consider only small screen for now --- .../components/widgets/checklist/checklist_full_widget.dart | 2 +- test/ui/screens/checklists_screen_test.dart | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/lib/ui/components/widgets/checklist/checklist_full_widget.dart b/lib/ui/components/widgets/checklist/checklist_full_widget.dart index cd5db1f..b0ae076 100644 --- a/lib/ui/components/widgets/checklist/checklist_full_widget.dart +++ b/lib/ui/components/widgets/checklist/checklist_full_widget.dart @@ -86,7 +86,7 @@ class _ChecklistsListFullWidgetState extends State { flex: 6, child: TasksListWidget( tasks: tasks == null ? [] : tasks!, - emptyTasksMessage: 'emptyTasksMessage', + emptyTasksMessage: widget.emptyChecklistMessage, onCompleteTask: _tasksViewModel.onCompleteTask, onRemoveTask: _tasksViewModel.onRemoveTask, onReorder: _tasksViewModel.reorder, diff --git a/test/ui/screens/checklists_screen_test.dart b/test/ui/screens/checklists_screen_test.dart index 544ddd9..aaf77a8 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'; @@ -12,6 +13,8 @@ void main() { testWidgets( 'ChecklistsScreen - Empty message should appear if we have no checklists', (tester) async { + tester.view.physicalSize = const Size(500, 800); + const emptyChecklistMessage = 'You have no checklists'; final widget = WidgetsUtil.buildMaterialAppWidgetTest( @@ -39,6 +42,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( From f55d0d3cb31f34f52c8849cf57aede48223fa706 Mon Sep 17 00:00:00 2001 From: Gabriel Moro Date: Fri, 5 Dec 2025 11:09:21 -0300 Subject: [PATCH 09/28] Creating isBigSize variable --- lib/ui/screens/checklists/checklists_screen.dart | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/lib/ui/screens/checklists/checklists_screen.dart b/lib/ui/screens/checklists/checklists_screen.dart index c6cd411..df6366b 100644 --- a/lib/ui/screens/checklists/checklists_screen.dart +++ b/lib/ui/screens/checklists/checklists_screen.dart @@ -77,6 +77,7 @@ class ChecklistsScaffold extends StatelessWidget { @override Widget build(BuildContext context) { + final isBigSize = MediaQuery.sizeOf(context).width > 600; return Scaffold( backgroundColor: Theme.of(context).colorScheme.surface, appBar: CustomAppBarWidget( @@ -105,13 +106,19 @@ class ChecklistsScaffold extends StatelessWidget { ), body: Padding( padding: const EdgeInsets.only(left: 12, right: 12), - child: _buildCheckListWidget(context), + child: _buildCheckListWidget( + context: context, + isBigSize: isBigSize, + ), ), ); } - Widget _buildCheckListWidget(BuildContext context) { - if (MediaQuery.sizeOf(context).width > 600) { + Widget _buildCheckListWidget({ + required BuildContext context, + required bool isBigSize, + }) { + if (isBigSize) { return ChecklistsListFullWidget( checklists: uiState.checklists, emptyChecklistMessage: checklistsScreenTextValues.emptyChecklistMessage, From 3c43ba25352eb41c1a58cd9b0a98a5416d3f6c5d Mon Sep 17 00:00:00 2001 From: Gabriel Moro Date: Fri, 5 Dec 2025 11:11:02 -0300 Subject: [PATCH 10/28] Wrap body at a Padding widget --- .../screens/checklists/checklists_screen.dart | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/lib/ui/screens/checklists/checklists_screen.dart b/lib/ui/screens/checklists/checklists_screen.dart index df6366b..99ad7f3 100644 --- a/lib/ui/screens/checklists/checklists_screen.dart +++ b/lib/ui/screens/checklists/checklists_screen.dart @@ -104,12 +104,9 @@ class ChecklistsScaffold extends StatelessWidget { } }, ), - body: Padding( - padding: const EdgeInsets.only(left: 12, right: 12), - child: _buildCheckListWidget( - context: context, - isBigSize: isBigSize, - ), + body: _buildCheckListWidget( + context: context, + isBigSize: isBigSize, ), ); } @@ -118,8 +115,9 @@ class ChecklistsScaffold extends StatelessWidget { required BuildContext context, required bool isBigSize, }) { + Widget checkListWidget; if (isBigSize) { - return ChecklistsListFullWidget( + checkListWidget = ChecklistsListFullWidget( checklists: uiState.checklists, emptyChecklistMessage: checklistsScreenTextValues.emptyChecklistMessage, onRemoveChecklist: (checklist) { @@ -128,7 +126,7 @@ class ChecklistsScaffold extends StatelessWidget { navigatorProvider: navigatorProvider, ); } else { - return ChecklistsListWidget( + checkListWidget = ChecklistsListWidget( checklists: uiState.checklists, emptyChecklistMessage: checklistsScreenTextValues.emptyChecklistMessage, onRemoveChecklist: (checklist) { @@ -140,6 +138,11 @@ class ChecklistsScaffold extends StatelessWidget { ), ); } + + return Padding( + padding: const EdgeInsets.only(left: 12, right: 12), + child: checkListWidget, + ); } void _showConfirmationDialogToRemoveChecklist( From 06474a3c3aae050bb4cb42355e5815e1bde750d3 Mon Sep 17 00:00:00 2001 From: Gabriel Moro Date: Fri, 5 Dec 2025 11:17:56 -0300 Subject: [PATCH 11/28] Add navigation rails menu --- .../screens/checklists/checklists_screen.dart | 98 ++++++++++++++----- 1 file changed, 71 insertions(+), 27 deletions(-) diff --git a/lib/ui/screens/checklists/checklists_screen.dart b/lib/ui/screens/checklists/checklists_screen.dart index 99ad7f3..f0f60c3 100644 --- a/lib/ui/screens/checklists/checklists_screen.dart +++ b/lib/ui/screens/checklists/checklists_screen.dart @@ -79,36 +79,43 @@ class ChecklistsScaffold extends StatelessWidget { Widget build(BuildContext context) { final isBigSize = MediaQuery.sizeOf(context).width > 600; 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: checklistsScreenTextValues.screenTitle, + ), + floatingActionButton: _buildFloatingActionButton( + () 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, + if (result == true) { + updateChecklists(); + if (context.mounted) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text( + AppLocalizations.of(context)!.checklist_added, + ), ), - ), - ); + ); + } } - } - }, - ), - body: _buildCheckListWidget( - context: context, - isBigSize: isBigSize, - ), - ); + }, + ), + body: Row( + children: [ + _buildNavigationRails(context: context, isBigSize: isBigSize), + _buildVerticalSeparator(context: context, isBigSize: isBigSize), + Expanded( + child: _buildCheckListWidget( + context: context, + isBigSize: isBigSize, + ), + ), + ], + )); } Widget _buildCheckListWidget({ @@ -145,6 +152,43 @@ class ChecklistsScaffold extends StatelessWidget { ); } + Widget _buildNavigationRails({ + required BuildContext context, + required bool isBigSize, + }) { + if (isBigSize) { + return const NavigationRail( + destinations: [ + NavigationRailDestination( + icon: Icon(Icons.plus_one), + label: Text('Add checklist'), + ), + NavigationRailDestination( + icon: Icon(Icons.add_task), + label: Text('Add task'), + ), + ], + labelType: NavigationRailLabelType.all, + selectedIndex: null, + ); + } 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, From 7c659eb59f6833c2d057f97595b9d25602ac45fa Mon Sep 17 00:00:00 2001 From: Gabriel Moro Date: Fri, 5 Dec 2025 11:27:37 -0300 Subject: [PATCH 12/28] Navigation rails component --- .../checklist_navigation_rail_menu.dart | 44 +++++++++++++++++++ .../screens/checklists/checklists_screen.dart | 23 ++++------ 2 files changed, 53 insertions(+), 14 deletions(-) create mode 100644 lib/ui/components/widgets/checklist/checklist_navigation_rail_menu.dart 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/screens/checklists/checklists_screen.dart b/lib/ui/screens/checklists/checklists_screen.dart index f0f60c3..905b8b6 100644 --- a/lib/ui/screens/checklists/checklists_screen.dart +++ b/lib/ui/screens/checklists/checklists_screen.dart @@ -3,6 +3,7 @@ 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'; @@ -157,19 +158,13 @@ class ChecklistsScaffold extends StatelessWidget { required bool isBigSize, }) { if (isBigSize) { - return const NavigationRail( - destinations: [ - NavigationRailDestination( - icon: Icon(Icons.plus_one), - label: Text('Add checklist'), - ), - NavigationRailDestination( - icon: Icon(Icons.add_task), - label: Text('Add task'), - ), - ], - labelType: NavigationRailLabelType.all, - selectedIndex: null, + return ChecklistNavigationRailMenu( + newChecklistIcon: Icons.plus_one, + newChecklistLabel: 'New Checklist', + newTaskIcon: Icons.add_task, + newTaskLabel: 'New task', + onNewTaskPressed: () {}, + onNewChecklistPressed: () {}, ); } else { return const SizedBox.shrink(); @@ -180,7 +175,7 @@ class ChecklistsScaffold extends StatelessWidget { required BuildContext context, required bool isBigSize, }) { - if(isBigSize) { + if (isBigSize) { return const VerticalDivider( thickness: 0.2, ); From c2be1b031c467981acbdfbe872d02e102fdbec3a Mon Sep 17 00:00:00 2001 From: Gabriel Moro Date: Fri, 5 Dec 2025 12:01:23 -0300 Subject: [PATCH 13/28] Add new check list event for navigation rails widget --- .../screens/checklists/checklists_screen.dart | 42 +++++++++++-------- 1 file changed, 24 insertions(+), 18 deletions(-) diff --git a/lib/ui/screens/checklists/checklists_screen.dart b/lib/ui/screens/checklists/checklists_screen.dart index 905b8b6..ce70649 100644 --- a/lib/ui/screens/checklists/checklists_screen.dart +++ b/lib/ui/screens/checklists/checklists_screen.dart @@ -86,23 +86,7 @@ class ChecklistsScaffold extends StatelessWidget { ), floatingActionButton: _buildFloatingActionButton( () 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, - ), - ), - ); - } - } + await _addNewChecklistEvent(context); }, ), body: Row( @@ -119,6 +103,26 @@ 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, + ), + ), + ); + } + } + } + Widget _buildCheckListWidget({ required BuildContext context, required bool isBigSize, @@ -164,7 +168,9 @@ class ChecklistsScaffold extends StatelessWidget { newTaskIcon: Icons.add_task, newTaskLabel: 'New task', onNewTaskPressed: () {}, - onNewChecklistPressed: () {}, + onNewChecklistPressed: () async { + await _addNewChecklistEvent(context); + }, ); } else { return const SizedBox.shrink(); From e2e5a8f660b9490539c0f6247a1540e7ae09da6b Mon Sep 17 00:00:00 2001 From: Gabriel Moro Date: Fri, 5 Dec 2025 12:43:51 -0300 Subject: [PATCH 14/28] Add support for adding task in large screens perspective --- .../checklist/checklist_full_widget.dart | 10 ++- .../screens/checklists/checklists_screen.dart | 72 +++++++++++-------- 2 files changed, 52 insertions(+), 30 deletions(-) diff --git a/lib/ui/components/widgets/checklist/checklist_full_widget.dart b/lib/ui/components/widgets/checklist/checklist_full_widget.dart index b0ae076..d54802c 100644 --- a/lib/ui/components/widgets/checklist/checklist_full_widget.dart +++ b/lib/ui/components/widgets/checklist/checklist_full_widget.dart @@ -24,10 +24,10 @@ class ChecklistsListFullWidget extends StatefulWidget { @override State createState() => - _ChecklistsListFullWidgetState(); + ChecklistsListFullWidgetState(); } -class _ChecklistsListFullWidgetState extends State { +class ChecklistsListFullWidgetState extends State { Checklist? selected; List? tasks; late TasksViewModel _tasksViewModel; @@ -66,6 +66,12 @@ class _ChecklistsListFullWidgetState extends State { }); } + Future addNewTaskToExistingChecklist(BuildContext context) async { + if (selected?.id != null) { + await _navigateToTaskScreen(context, checklistId: selected!.id); + } + } + Widget _buildTaskList(BuildContext context) { return Row( children: [ diff --git a/lib/ui/screens/checklists/checklists_screen.dart b/lib/ui/screens/checklists/checklists_screen.dart index ce70649..c07d049 100644 --- a/lib/ui/screens/checklists/checklists_screen.dart +++ b/lib/ui/screens/checklists/checklists_screen.dart @@ -59,8 +59,10 @@ class ChecklistsScaffold extends StatelessWidget { final Function updateChecklists; final NavigatorProvider navigatorProvider; final ChecklistsScreenTextValues checklistsScreenTextValues; + final GlobalKey _checklistFullKey = + GlobalKey(); - const ChecklistsScaffold({ + ChecklistsScaffold({ super.key, required this.uiState, required this.checklistsScreenTextValues, @@ -69,11 +71,18 @@ class ChecklistsScaffold extends StatelessWidget { 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 @@ -85,7 +94,8 @@ class ChecklistsScaffold extends StatelessWidget { title: checklistsScreenTextValues.screenTitle, ), floatingActionButton: _buildFloatingActionButton( - () async { + isBigSize: isBigSize, + onPressed: () async { await _addNewChecklistEvent(context); }, ), @@ -103,26 +113,6 @@ 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, - ), - ), - ); - } - } - } - Widget _buildCheckListWidget({ required BuildContext context, required bool isBigSize, @@ -131,6 +121,7 @@ class ChecklistsScaffold extends StatelessWidget { if (isBigSize) { checkListWidget = ChecklistsListFullWidget( checklists: uiState.checklists, + key: _checklistFullKey, emptyChecklistMessage: checklistsScreenTextValues.emptyChecklistMessage, onRemoveChecklist: (checklist) { _showConfirmationDialogToRemoveChecklist(context, checklist); @@ -167,7 +158,12 @@ class ChecklistsScaffold extends StatelessWidget { newChecklistLabel: 'New Checklist', newTaskIcon: Icons.add_task, newTaskLabel: 'New task', - onNewTaskPressed: () {}, + onNewTaskPressed: () async { + final currentChecklistFullWidgetState = _checklistFullKey.currentState; + currentChecklistFullWidgetState?.addNewTaskToExistingChecklist( + context + ); + }, onNewChecklistPressed: () async { await _addNewChecklistEvent(context); }, @@ -211,4 +207,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, + ), + ), + ); + } + } + } } From d75c5f851c3b088f8ce925609717434ccb4552f6 Mon Sep 17 00:00:00 2001 From: Gabriel Moro Date: Fri, 5 Dec 2025 12:44:19 -0300 Subject: [PATCH 15/28] Solve lint issue --- lib/ui/screens/checklists/checklists_screen.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/ui/screens/checklists/checklists_screen.dart b/lib/ui/screens/checklists/checklists_screen.dart index c07d049..9ea6121 100644 --- a/lib/ui/screens/checklists/checklists_screen.dart +++ b/lib/ui/screens/checklists/checklists_screen.dart @@ -159,8 +159,8 @@ class ChecklistsScaffold extends StatelessWidget { newTaskIcon: Icons.add_task, newTaskLabel: 'New task', onNewTaskPressed: () async { - final currentChecklistFullWidgetState = _checklistFullKey.currentState; - currentChecklistFullWidgetState?.addNewTaskToExistingChecklist( + final currentChecklistFullState = _checklistFullKey.currentState; + currentChecklistFullState?.addNewTaskToExistingChecklist( context ); }, From 058b4b7a47f3864bd61fb77bd14daaed90b5ddfb Mon Sep 17 00:00:00 2001 From: Gabriel Moro Date: Sun, 7 Dec 2025 12:08:19 -0300 Subject: [PATCH 16/28] Add some comment around the _checklistFullKey usage --- lib/ui/screens/checklists/checklists_screen.dart | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/ui/screens/checklists/checklists_screen.dart b/lib/ui/screens/checklists/checklists_screen.dart index 9ea6121..44d6068 100644 --- a/lib/ui/screens/checklists/checklists_screen.dart +++ b/lib/ui/screens/checklists/checklists_screen.dart @@ -59,6 +59,8 @@ class ChecklistsScaffold extends StatelessWidget { 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(); @@ -159,6 +161,8 @@ class ChecklistsScaffold extends StatelessWidget { 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 From 690c8eb7616357d91c76b8971321167f5239c4ae Mon Sep 17 00:00:00 2001 From: Gabriel Moro Date: Sun, 7 Dec 2025 12:25:35 -0300 Subject: [PATCH 17/28] Some updates --- .../checklist/checklist_full_widget.dart | 24 ++++++++++++------- .../screens/checklists/checklists_screen.dart | 1 - lib/ui/screens/tasks/tasks_screen.dart | 2 +- .../screens/tasks/tasks_screen_callbacks.dart | 2 +- 4 files changed, 18 insertions(+), 11 deletions(-) diff --git a/lib/ui/components/widgets/checklist/checklist_full_widget.dart b/lib/ui/components/widgets/checklist/checklist_full_widget.dart index d54802c..12632e9 100644 --- a/lib/ui/components/widgets/checklist/checklist_full_widget.dart +++ b/lib/ui/components/widgets/checklist/checklist_full_widget.dart @@ -4,6 +4,7 @@ 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'; @@ -12,12 +13,10 @@ class ChecklistsListFullWidget extends StatefulWidget { final List checklists; final Function(Checklist) onRemoveChecklist; final NavigatorProvider navigatorProvider; - final String emptyChecklistMessage; const ChecklistsListFullWidget({ super.key, required this.checklists, - required this.emptyChecklistMessage, required this.onRemoveChecklist, required this.navigatorProvider, }); @@ -68,11 +67,15 @@ class ChecklistsListFullWidgetState extends State { Future addNewTaskToExistingChecklist(BuildContext context) async { if (selected?.id != null) { - await _navigateToTaskScreen(context, checklistId: selected!.id); + await _navigateToTaskScreen( + context, + checklistId: selected!.id, + ); } } Widget _buildTaskList(BuildContext context) { + final localizations = AppLocalizations.of(context)!; return Row( children: [ Expanded( @@ -92,12 +95,15 @@ class ChecklistsListFullWidgetState extends State { flex: 6, child: TasksListWidget( tasks: tasks == null ? [] : tasks!, - emptyTasksMessage: widget.emptyChecklistMessage, + emptyTasksMessage: localizations.empty_tasks, onCompleteTask: _tasksViewModel.onCompleteTask, onRemoveTask: _tasksViewModel.onRemoveTask, onReorder: _tasksViewModel.reorder, - onTap: (task) => _navigateToTaskScreen(context, - checklistId: selected?.id, task: task), + onTap: (task) => _navigateToTaskScreen( + context, + checklistId: selected?.id, + task: task, + ), )), ], ); @@ -108,6 +114,8 @@ class ChecklistsListFullWidgetState extends State { required int? checklistId, Task? task, }) async { + final localizations = AppLocalizations.of(context)!; + bool? result = await widget.navigatorProvider.push( context, TaskRoute( @@ -119,9 +127,9 @@ class ChecklistsListFullWidgetState extends State { await _tasksViewModel.updateTasks(selected?.id); if (context.mounted) { ScaffoldMessenger.of(context).showSnackBar( - const SnackBar( + SnackBar( content: Text( - 'refresh', + localizations.tasks_refresh, ), ), ); diff --git a/lib/ui/screens/checklists/checklists_screen.dart b/lib/ui/screens/checklists/checklists_screen.dart index 44d6068..0602fab 100644 --- a/lib/ui/screens/checklists/checklists_screen.dart +++ b/lib/ui/screens/checklists/checklists_screen.dart @@ -124,7 +124,6 @@ class ChecklistsScaffold extends StatelessWidget { checkListWidget = ChecklistsListFullWidget( checklists: uiState.checklists, key: _checklistFullKey, - emptyChecklistMessage: checklistsScreenTextValues.emptyChecklistMessage, onRemoveChecklist: (checklist) { _showConfirmationDialogToRemoveChecklist(context, checklist); }, diff --git a/lib/ui/screens/tasks/tasks_screen.dart b/lib/ui/screens/tasks/tasks_screen.dart index ca01832..d9daf1a 100644 --- a/lib/ui/screens/tasks/tasks_screen.dart +++ b/lib/ui/screens/tasks/tasks_screen.dart @@ -231,7 +231,7 @@ class TasksScaffold extends StatelessWidget { ), ); if (result == true) { - await callbacks.updateTasks(); + await callbacks.updateTasks(checklistId); if (context.mounted) { ScaffoldMessenger.of(context).showSnackBar( SnackBar( 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; From 79ed8ba67e27532dd2571878a95511fa46872cd0 Mon Sep 17 00:00:00 2001 From: Gabriel Moro Date: Sun, 7 Dec 2025 12:31:00 -0300 Subject: [PATCH 18/28] Remove unnecessary data class to wrap text values for checklists screen --- .../screens/checklists/checklists_screen.dart | 34 +++++++------------ .../checklists_screen_text_values.dart | 33 ------------------ 2 files changed, 12 insertions(+), 55 deletions(-) delete mode 100644 lib/ui/screens/checklists/checklists_screen_text_values.dart diff --git a/lib/ui/screens/checklists/checklists_screen.dart b/lib/ui/screens/checklists/checklists_screen.dart index 0602fab..ccb64eb 100644 --- a/lib/ui/screens/checklists/checklists_screen.dart +++ b/lib/ui/screens/checklists/checklists_screen.dart @@ -9,7 +9,6 @@ import 'package:todoapp/ui/components/widgets/confirmation_alert_dialog_widget.d 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'; @@ -26,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(), @@ -58,7 +44,6 @@ 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 = @@ -67,7 +52,6 @@ class ChecklistsScaffold extends StatelessWidget { ChecklistsScaffold({ super.key, required this.uiState, - required this.checklistsScreenTextValues, required this.updateChecklists, required this.onRemoveChecklist, required this.navigatorProvider, @@ -90,10 +74,12 @@ class ChecklistsScaffold extends StatelessWidget { @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, + title: localizations.checklists, ), floatingActionButton: _buildFloatingActionButton( isBigSize: isBigSize, @@ -119,6 +105,8 @@ class ChecklistsScaffold extends StatelessWidget { required BuildContext context, required bool isBigSize, }) { + final localizations = AppLocalizations.of(context)!; + Widget checkListWidget; if (isBigSize) { checkListWidget = ChecklistsListFullWidget( @@ -132,7 +120,7 @@ class ChecklistsScaffold extends StatelessWidget { } else { checkListWidget = ChecklistsListWidget( checklists: uiState.checklists, - emptyChecklistMessage: checklistsScreenTextValues.emptyChecklistMessage, + emptyChecklistMessage: localizations.empty_checklists, onRemoveChecklist: (checklist) { _showConfirmationDialogToRemoveChecklist(context, checklist); }, @@ -193,13 +181,15 @@ class ChecklistsScaffold extends StatelessWidget { 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), }, 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, - }); -} From a50cc313c7549852fa2a0439f9cdc9fe65e695cd Mon Sep 17 00:00:00 2001 From: Gabriel Moro Date: Sun, 7 Dec 2025 12:33:07 -0300 Subject: [PATCH 19/28] Remove unnecessary data class to wrap text values for checklist screen --- .../screens/checklist/checklist_screen.dart | 20 +++++-------------- .../checklist_screen_text_values.dart | 19 ------------------ 2 files changed, 5 insertions(+), 34 deletions(-) delete mode 100644 lib/ui/screens/checklist/checklist_screen_text_values.dart 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, - }); -} From 0935c7e357a59c5177a55533e20d46d0edc8f5a8 Mon Sep 17 00:00:00 2001 From: Gabriel Moro Date: Sun, 7 Dec 2025 12:36:46 -0300 Subject: [PATCH 20/28] Remove unnecessary data class to wrap text values for checklist screen --- lib/ui/screens/task/task_screen.dart | 19 ++++++------------- .../screens/task/task_screen_text_values.dart | 17 ----------------- 2 files changed, 6 insertions(+), 30 deletions(-) delete mode 100644 lib/ui/screens/task/task_screen_text_values.dart 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, - }); -} From be0ab6d4a1f10c17fe8a4fc0f7ea3df7e2eb1a06 Mon Sep 17 00:00:00 2001 From: Gabriel Moro Date: Sun, 7 Dec 2025 12:39:48 -0300 Subject: [PATCH 21/28] Remove unnecessary data class to wrap text values for tasks screen --- lib/ui/screens/tasks/tasks_screen.dart | 36 +++++++------------ .../tasks/tasks_screen_text_values.dart | 31 ---------------- 2 files changed, 13 insertions(+), 54 deletions(-) delete mode 100644 lib/ui/screens/tasks/tasks_screen_text_values.dart diff --git a/lib/ui/screens/tasks/tasks_screen.dart b/lib/ui/screens/tasks/tasks_screen.dart index d9daf1a..ce28433 100644 --- a/lib/ui/screens/tasks/tasks_screen.dart +++ b/lib/ui/screens/tasks/tasks_screen.dart @@ -11,7 +11,6 @@ import 'package:todoapp/ui/components/widgets/task/taskslist/tasks_screen_state. 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_text_values.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'; @@ -41,18 +40,6 @@ class TasksScreen extends StatelessWidget { ); viewModel.updateTasks(checklist.id); - 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, - ); - return BlocProvider( create: (_) => viewModel, child: BlocBuilder( @@ -61,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, @@ -82,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; @@ -91,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, }); @@ -105,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, @@ -134,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), @@ -162,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), }, @@ -223,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( @@ -236,7 +226,7 @@ class TasksScaffold extends StatelessWidget { ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text( - tasksScreenTextValues.tasksRefresh, + localizations.tasks_refresh, ), ), ); 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, - }); -} From 1a2f91dc4e4909f29c312e2b42103e18488057c4 Mon Sep 17 00:00:00 2001 From: Gabriel Moro Date: Sun, 7 Dec 2025 12:50:03 -0300 Subject: [PATCH 22/28] Solve broken unit tests --- test/test_utils/fakes/fake_callbacks.dart | 2 +- test/test_utils/fakes/fake_text_values.dart | 37 --------------------- test/test_utils/widgets_util.dart | 5 +++ test/ui/screens/checklist_screen_test.dart | 2 -- test/ui/screens/checklists_screen_test.dart | 5 +-- test/ui/screens/task_screen_test.dart | 3 -- test/ui/screens/tasks_screen_test.dart | 10 +----- 7 files changed, 8 insertions(+), 56 deletions(-) delete mode 100644 test/test_utils/fakes/fake_text_values.dart 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_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/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 aaf77a8..b919ffa 100644 --- a/test/ui/screens/checklists_screen_test.dart +++ b/test/ui/screens/checklists_screen_test.dart @@ -6,7 +6,6 @@ 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() { @@ -15,7 +14,7 @@ void main() { (tester) async { tester.view.physicalSize = const Size(500, 800); - const emptyChecklistMessage = 'You have no checklists'; + const emptyChecklistMessage = 'No checklists available'; final widget = WidgetsUtil.buildMaterialAppWidgetTest( child: ChecklistsScaffold( @@ -23,7 +22,6 @@ void main() { checklists: [], isLoading: false, ), - checklistsScreenTextValues: FakeTextValues.checklistsScreenTextValues, onRemoveChecklist: (_) => {}, navigatorProvider: FakeNavigatorProvider(), updateChecklists: () => {}, @@ -55,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, ); From 6ecfc035f1c6bd1f6ac05347b3251380e386e71c Mon Sep 17 00:00:00 2001 From: Gabriel Moro Date: Sun, 7 Dec 2025 12:52:20 -0300 Subject: [PATCH 23/28] Try to use danger 2.0.0 --- pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pubspec.yaml b/pubspec.yaml index 38e1863..8afa631 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -63,7 +63,7 @@ dev_dependencies: injectable_generator: ^2.5.0 build_runner: ^2.5.4 auto_route_generator: ^10.1.0 - danger_core: ^2.0.0 + danger_core: 2.0.0 eagle_eye: ^1.0.0 # For information on the generic Dart part of this file, see the From aaf6f7a98352b3930d59bc60b0c67a96940c3093 Mon Sep 17 00:00:00 2001 From: Gabriel Moro Date: Sun, 7 Dec 2025 12:57:00 -0300 Subject: [PATCH 24/28] =?UTF-8?q?Back=20Danger=20to=20=CB=862.0.0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pubspec.yaml b/pubspec.yaml index 8afa631..38e1863 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -63,7 +63,7 @@ dev_dependencies: injectable_generator: ^2.5.0 build_runner: ^2.5.4 auto_route_generator: ^10.1.0 - danger_core: 2.0.0 + danger_core: ^2.0.0 eagle_eye: ^1.0.0 # For information on the generic Dart part of this file, see the From 98ff4e675e3b8c079cf7e92276d7c0dcb5af09a2 Mon Sep 17 00:00:00 2001 From: Gabriel Moro Date: Sun, 7 Dec 2025 13:03:46 -0300 Subject: [PATCH 25/28] Trying specific working dire --- .github/workflows/danger.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/danger.yml b/.github/workflows/danger.yml index aa3ba5a..e107ab1 100644 --- a/.github/workflows/danger.yml +++ b/.github/workflows/danger.yml @@ -27,6 +27,7 @@ jobs: run: flutter pub global activate danger_dart - name: Run danger ci + working-directory: ./todoapp_flutter run: danger_dart ci env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} \ No newline at end of file From 4522e1773de4fb816d45829f4fd66cc277ac76a4 Mon Sep 17 00:00:00 2001 From: Gabriel Moro Date: Sun, 7 Dec 2025 13:09:22 -0300 Subject: [PATCH 26/28] Remove specific dir --- .github/workflows/danger.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/danger.yml b/.github/workflows/danger.yml index e107ab1..aa3ba5a 100644 --- a/.github/workflows/danger.yml +++ b/.github/workflows/danger.yml @@ -27,7 +27,6 @@ jobs: run: flutter pub global activate danger_dart - name: Run danger ci - working-directory: ./todoapp_flutter run: danger_dart ci env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} \ No newline at end of file From 2f7a5bc3dc7d842ee305dff215014caf2050b803 Mon Sep 17 00:00:00 2001 From: Gabriel Moro Date: Sun, 7 Dec 2025 13:16:32 -0300 Subject: [PATCH 27/28] Remove danger --- .github/workflows/danger.yml | 32 -------------------------------- dangerfile.dart | 29 ----------------------------- 2 files changed, 61 deletions(-) delete mode 100644 .github/workflows/danger.yml delete mode 100644 dangerfile.dart 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...'); - } -} From 4641a2fae0c7a0d8473ebc9c6959678892f97a57 Mon Sep 17 00:00:00 2001 From: Gabriel Moro Date: Sun, 7 Dec 2025 13:16:50 -0300 Subject: [PATCH 28/28] Remove danger --- pubspec.lock | 8 -------- pubspec.yaml | 1 - 2 files changed, 9 deletions(-) diff --git a/pubspec.lock b/pubspec.lock index dda9344..7b2a0ea 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -225,14 +225,6 @@ 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: 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