Conversation
📝 WalkthroughWalkthroughCompletes GetX migration by replacing remaining setState/StatefulBuilder usage with reactive observables and Obx; adds a new TagsController for tag state and operations; exposes HomeController.refreshTaskList; introduces observable version counters and profile mode observables; and registers GTK/AppLinks plugins across desktop platforms. Changes
Sequence Diagram(s)sequenceDiagram
participant UI as TagsRoute (View)
participant TC as TagsController
participant HC as HomeController
UI->>TC: init(value?, callback)
UI->>TC: user enters comma-separated tags
TC->>TC: parseTags(input)
TC->>TC: addTags(parsedList) / removeTag(tag)
TC->>HC: read pendingTags (getter)
TC->>TC: draftTags.value = updated ListBuilder
TC->>UI: callback(draftTags.value) (via provided callback)
TC->>UI: Obx triggers UI rebuild (draftTags / pendingTags observed)
Estimated code review effort🎯 4 (Complex) | ⏱️ ~55 minutes Poem
🚥 Pre-merge checks | ✅ 3 | ❌ 2❌ Failed checks (1 warning, 1 inconclusive)
✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 6
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
lib/app/modules/profile/views/profile_view.dart (1)
306-323:⚠️ Potential issue | 🟡 MinorFix the success message for
TW3C.Line 321 still treats only
TW3as the v3 branch, but this dialog now exposesTW3C. Changing a profile to Taskchampion will currently show “Taskserver” in the snackbar.✏️ Proposed fix
final selectedMode = controller.selectedProfileMode.value; if (selectedMode != null && selectedMode != currentMode) { + final modeLabel = selectedMode == "TW3C" + ? "Taskchampion" + : selectedMode == "TW3" + ? "CCSync" + : "TaskServer"; controller.profilesWidget.changeModeTo( profile, selectedMode, ); ScaffoldMessenger.of(context).showSnackBar(SnackBar( content: Text( SentenceManager( currentLanguage: AppSettings.selectedLanguage) .sentences .profilePageSuccessfullyChangedProfileModeTo + - (selectedMode == "TW3" - ? "CCSync" - : "Taskserver"), + modeLabel,🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@lib/app/modules/profile/views/profile_view.dart` around lines 306 - 323, The success Snackbar message incorrectly maps only "TW3" to the v3 label; update the logic around controller.selectedProfileMode.value (used before calling controller.profilesWidget.changeModeTo) so that it treats "TW3C" as the v3 branch and shows "Taskchampion" (or otherwise map "TW3C" -> "Taskchampion") instead of "Taskserver"; adjust the conditional that currently checks selectedMode == "TW3" and/or the string mapping used when building the SentenceManager/profilePageSuccessfullyChangedProfileModeTo message so both "TW3" and "TW3C" produce the correct v3 label.
🧹 Nitpick comments (1)
lib/app/modules/manageTaskServer/views/manage_task_server_page_body.dart (1)
192-235: Fragile trigger pattern — consider making the underlying state reactive.Reading
taskrcVersion.value(line 193) without using it just to register an Obx subscription is a workaround. The actual state (taskrcContentController.text) isn't reactive. This pattern is fragile because any code path that modifies the text must remember to incrementtaskrcVersion.For long-term maintainability, consider making the taskrc content itself reactive (e.g.,
RxString taskrcContent) instead of relying on a manual version counter.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@lib/app/modules/manageTaskServer/views/manage_task_server_page_body.dart` around lines 192 - 235, The current Obx is relying on reading controller.taskrcVersion.value to trigger rebuilds while the actual text lives in taskrcContentController.text, which is fragile; change the controller to expose a reactive RxString (e.g., taskrcContent) and keep it in sync with the TextEditingController (add a listener to update taskrcContent when the controller.text changes and initialize controller.text from taskrcContent), then update the view to observe taskrcContent (use controller.taskrcContent.value / controller.taskrcContent.isNotEmpty) instead of reading taskrcVersion; ensure all places that modify the taskrc text update the reactive taskrcContent rather than manipulating a separate version counter.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@lib/app/modules/detailRoute/controllers/tags_controller.dart`:
- Around line 45-54: The removeTag method currently force-unwraps
draftTags.value and then sends an empty ListBuilder to callback when last tag is
removed, causing a potential NPE and inconsistent semantics with addTags; update
removeTag to first check if draftTags.value is null and return early (no-op) if
so, otherwise operate on the non-null ListBuilder (avoid using the ! operator),
remove the tag, and if the resulting list is empty set draftTags.value = null
and call callback(null) to preserve the "no tags" semantic; if not empty,
refresh draftTags and call callback(draftTags.value) — reference removeTag,
draftTags, and callback to locate changes.
In
`@lib/app/modules/manageTaskServer/controllers/manage_task_server_controller.dart`:
- Line 30: The Obx-based UI listens to the observable taskrcVersion but
setContent() currently calls update() (GetBuilder) so the view doesn't rebuild;
modify setContent() in manage_task_server_controller.dart to increment
taskrcVersion (e.g., taskrcVersion.value++) after changing the content instead
of—or in addition to—calling update(), ensuring the Obx reactive widget sees the
change and rebuilds when content is pasted.
In `@lib/app/modules/manageTaskServer/views/manage_task_server_page_body.dart`:
- Around line 418-482: The Obx only depends on serverCertExists so changes from
controller.update() in onTapPEMWidget/onLongPressPEMWidget don't rebuild; add a
reactive counter pemVersion (e.g., RxInt pemVersion = 0.obs) to the controller,
increment it at the end of onTapPEMWidget and onLongPressPEMWidget, and ensure
the Obx reads controller.pemVersion.value (for example reference
controller.pemVersion.value at top of the Obx) so the PEM list (created with
PemWidget) will rebuild when pemVersion changes; alternatively replace this Obx
with a GetBuilder for that section if you prefer controller.update() semantics.
In `@lib/app/utils/add_task_dialogue/date_picker_input.dart`:
- Line 27: The _currentIndex observable is incorrectly initialized to 0
regardless of widget.allowedIndexes, causing DropdownButton.value mismatches;
change the initialization of _currentIndex to seed from widget.allowedIndexes
(e.g., use the first element of widget.allowedIndexes if non-empty) and ensure
you handle the empty/nullable case (fallback to a safe default or disable the
dropdown). Update any code that constructs DropdownButton.value and the items
loop that references widget.allowedIndexes so they use the same source and never
pass a value not present in the items.
- Around line 119-123: Replace using picked.add(Duration(...)) with an explicit
wall-clock DateTime built from picked's Y/M/D and desired time to avoid DST
arithmetic: set _selectedDates[forIndex] = DateTime(picked.year, picked.month,
picked.day, 23, 59); then continue to call widget.onDateChanges! with
List<DateTime?>.from(_selectedDates) (preserve nullability/UTC handling if
needed by checking picked.isUtc and using DateTime.utc(...) when appropriate).
In `@linux/flutter/generated_plugins.cmake`:
- Around line 5-10: The generated FLUTTER_PLUGIN_LIST includes the plugin
identifier "gtk" but your pubspec.yaml/pubspec.lock do not, causing the
generated file to diverge on the next flutter pub get; fix by adding the gtk
dependency to your pubspec.yaml (or run flutter pub add gtk) so the plugin is
declared and flutter pub get regenerates linux/flutter/generated_plugins.cmake
consistently, then commit the updated pubspec.yaml and pubspec.lock;
alternatively, if gtk is not needed, remove the gtk entry from the generated
list and ensure the source of generation no longer declares it.
---
Outside diff comments:
In `@lib/app/modules/profile/views/profile_view.dart`:
- Around line 306-323: The success Snackbar message incorrectly maps only "TW3"
to the v3 label; update the logic around controller.selectedProfileMode.value
(used before calling controller.profilesWidget.changeModeTo) so that it treats
"TW3C" as the v3 branch and shows "Taskchampion" (or otherwise map "TW3C" ->
"Taskchampion") instead of "Taskserver"; adjust the conditional that currently
checks selectedMode == "TW3" and/or the string mapping used when building the
SentenceManager/profilePageSuccessfullyChangedProfileModeTo message so both
"TW3" and "TW3C" produce the correct v3 label.
---
Nitpick comments:
In `@lib/app/modules/manageTaskServer/views/manage_task_server_page_body.dart`:
- Around line 192-235: The current Obx is relying on reading
controller.taskrcVersion.value to trigger rebuilds while the actual text lives
in taskrcContentController.text, which is fragile; change the controller to
expose a reactive RxString (e.g., taskrcContent) and keep it in sync with the
TextEditingController (add a listener to update taskrcContent when the
controller.text changes and initialize controller.text from taskrcContent), then
update the view to observe taskrcContent (use controller.taskrcContent.value /
controller.taskrcContent.isNotEmpty) instead of reading taskrcVersion; ensure
all places that modify the taskrc text update the reactive taskrcContent rather
than manipulating a separate version counter.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 39dd2ffe-4caa-4caf-b928-adee9997a76b
📒 Files selected for processing (15)
lib/app/modules/detailRoute/controllers/tags_controller.dartlib/app/modules/detailRoute/views/tags_widget.dartlib/app/modules/home/controllers/home_controller.dartlib/app/modules/home/views/add_task_bottom_sheet_new.dartlib/app/modules/home/views/tasks_builder.dartlib/app/modules/manageTaskServer/controllers/manage_task_server_controller.dartlib/app/modules/manageTaskServer/views/manage_task_server_page_body.dartlib/app/modules/profile/controllers/profile_controller.dartlib/app/modules/profile/views/profile_view.dartlib/app/utils/add_task_dialogue/date_picker_input.dartlinux/flutter/generated_plugin_registrant.cclinux/flutter/generated_plugins.cmakemacos/Flutter/GeneratedPluginRegistrant.swiftwindows/flutter/generated_plugin_registrant.ccwindows/flutter/generated_plugins.cmake
💤 Files with no reviewable changes (1)
- lib/app/modules/home/views/add_task_bottom_sheet_new.dart
lib/app/modules/manageTaskServer/controllers/manage_task_server_controller.dart
Show resolved
Hide resolved
| Obx(() { | ||
| List<Widget> pemWidgets = []; | ||
| for (var pem in [ | ||
| 'taskd.certificate', | ||
| 'taskd.key', | ||
| 'taskd.ca', | ||
| if (controller.homeController.serverCertExists.value) | ||
| 'server.cert', | ||
| ]) { | ||
| pemWidgets.add( | ||
| PemWidget( | ||
| storage: controller.storage, | ||
| pem: pem, | ||
| optionString: pem == "taskd.certificate" | ||
| ? SentenceManager( | ||
| currentLanguage: AppSettings.selectedLanguage) | ||
| .sentences | ||
| .manageTaskServerPageConfigureYourCertificate | ||
| : pem == "taskd.key" | ||
| ? SentenceManager( | ||
| currentLanguage: AppSettings.selectedLanguage) | ||
| .sentences | ||
| .manageTaskServerPageConfigureTaskserverKey | ||
| : pem == "taskd.ca" | ||
| ? SentenceManager( | ||
| currentLanguage: | ||
| AppSettings.selectedLanguage) | ||
| .sentences | ||
| .manageTaskServerPageConfigureServerCertificate | ||
| : SentenceManager( | ||
| currentLanguage: | ||
| AppSettings.selectedLanguage) | ||
| .sentences | ||
| .manageTaskServerPageConfigureServerCertificate, | ||
| listTileTitle: pem == "taskd.certificate" | ||
| ? SentenceManager( | ||
| currentLanguage: AppSettings.selectedLanguage) | ||
| .sentences | ||
| .manageTaskServerPageSelectCertificate | ||
| : pem == "taskd.key" | ||
| ? SentenceManager( | ||
| currentLanguage: AppSettings.selectedLanguage) | ||
| .sentences | ||
| .manageTaskServerPageSelectKey | ||
| : pem == "taskd.ca" | ||
| ? SentenceManager( | ||
| currentLanguage: | ||
| AppSettings.selectedLanguage) | ||
| .sentences | ||
| .manageTaskServerPageSelectCertificate | ||
| : SentenceManager( | ||
| currentLanguage: | ||
| AppSettings.selectedLanguage) | ||
| .sentences | ||
| .manageTaskServerPageSelectCertificate, | ||
| onTapCallBack: controller.onTapPEMWidget, | ||
| onLongPressCallBack: controller.onLongPressPEMWidget, | ||
| globalKey: controller.getGlobalKey(pem), | ||
| ), | ||
| ); | ||
| }, | ||
| ) | ||
| } | ||
| return Column( | ||
| children: pemWidgets, | ||
| ); | ||
| }) |
There was a problem hiding this comment.
PEM widgets won't update after file changes — update() calls in controller don't trigger Obx.
This Obx only reacts to serverCertExists.value (line 424). When PEM files are added/removed via onTapPEMWidget or onLongPressPEMWidget, the controller calls update(), which doesn't trigger Obx rebuilds.
To fix, either:
- Add a reactive counter (similar to
taskrcVersion) and increment it inonTapPEMWidget/onLongPressPEMWidget - Use
GetBuilderfor this section (reverting this specific change)
🐛 Proposed fix: add pemVersion trigger
In controller:
final taskrcVersion = 0.obs;
+ final pemVersion = 0.obs;In onTapPEMWidget:
await setConfig(
storage: storagePem,
key: pem,
);
- update();
+ pemVersion.value++;In onLongPressPEMWidget:
storage.guiPemFiles.removePemFile(pem);
- update();
+ pemVersion.value++;In view (line 418):
Obx(() {
+ controller.pemVersion.value; // trigger rebuild
List<Widget> pemWidgets = [];🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@lib/app/modules/manageTaskServer/views/manage_task_server_page_body.dart`
around lines 418 - 482, The Obx only depends on serverCertExists so changes from
controller.update() in onTapPEMWidget/onLongPressPEMWidget don't rebuild; add a
reactive counter pemVersion (e.g., RxInt pemVersion = 0.obs) to the controller,
increment it at the end of onTapPEMWidget and onLongPressPEMWidget, and ensure
the Obx reads controller.pemVersion.value (for example reference
controller.pemVersion.value at top of the Obx) so the PEM list (created with
PemWidget) will rebuild when pemVersion changes; alternatively replace this Obx
with a GetBuilder for that section if you prefer controller.update() semantics.
| List.generate(4, (index) => TextEditingController()); | ||
| final int length = 4; | ||
| int currentIndex = 0; | ||
| final _currentIndex = 0.obs; |
There was a problem hiding this comment.
Seed _currentIndex from allowedIndexes.
Line 27 hard-codes 0, but Lines 48-53 only build items for widget.allowedIndexes. If a caller passes something like [1, 2], DropdownButton gets value: 0 with no matching item and asserts during build.
Suggested fix
class _AddTaskDatePickerInputState extends State<AddTaskDatePickerInput> {
final _selectedDates = <DateTime?>[null, null, null, null].obs;
final List<String> dateLabels = ['Due', 'Wait', 'Sched', 'Until'];
final List<TextEditingController> _controllers =
List.generate(4, (index) => TextEditingController());
final int length = 4;
- final _currentIndex = 0.obs;
+ late final RxInt _currentIndex;
+
+ `@override`
+ void initState() {
+ super.initState();
+ _currentIndex = _initialIndexFor(widget).obs;
+ }
+
+ `@override`
+ void didUpdateWidget(covariant AddTaskDatePickerInput oldWidget) {
+ super.didUpdateWidget(oldWidget);
+ if (!widget.allowedIndexes.contains(_currentIndex.value)) {
+ _currentIndex.value = _initialIndexFor(widget);
+ }
+ }
+
+ int _initialIndexFor(AddTaskDatePickerInput widget) {
+ if (widget.onlyDueDate) return 0;
+ return widget.allowedIndexes.contains(0)
+ ? 0
+ : widget.allowedIndexes.first;
+ }Also applies to: 48-53
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@lib/app/utils/add_task_dialogue/date_picker_input.dart` at line 27, The
_currentIndex observable is incorrectly initialized to 0 regardless of
widget.allowedIndexes, causing DropdownButton.value mismatches; change the
initialization of _currentIndex to seed from widget.allowedIndexes (e.g., use
the first element of widget.allowedIndexes if non-empty) and ensure you handle
the empty/nullable case (fallback to a safe default or disable the dropdown).
Update any code that constructs DropdownButton.value and the items loop that
references widget.allowedIndexes so they use the same source and never pass a
value not present in the items.
| list(APPEND FLUTTER_PLUGIN_LIST | ||
| file_selector_linux | ||
| flutter_timezone | ||
| gtk | ||
| url_launcher_linux | ||
| ) |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
fd -t f 'pubspec\.(yaml|lock)$' | while read -r f; do
echo "## $f"
rg -n -C2 '^\s*gtk:' "$f" || true
doneRepository: CCExtractor/taskwarrior-flutter
Length of output: 106
Add gtk to pubspec.yaml to sync with generated plugins.
This file is generated and currently includes gtk in FLUTTER_PLUGIN_LIST, but gtk is not declared in pubspec.yaml or pubspec.lock. On the next flutter pub get, the generated file will be regenerated without gtk, causing CI and local builds to diverge. Either add gtk to pubspec.yaml or remove it from the generated output.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@linux/flutter/generated_plugins.cmake` around lines 5 - 10, The generated
FLUTTER_PLUGIN_LIST includes the plugin identifier "gtk" but your
pubspec.yaml/pubspec.lock do not, causing the generated file to diverge on the
next flutter pub get; fix by adding the gtk dependency to your pubspec.yaml (or
run flutter pub add gtk) so the plugin is declared and flutter pub get
regenerates linux/flutter/generated_plugins.cmake consistently, then commit the
updated pubspec.yaml and pubspec.lock; alternatively, if gtk is not needed,
remove the gtk entry from the generated list and ensure the source of generation
no longer declares it.
- tags_controller: guard removeTag against null draftTags; fix callback to pass null (not empty ListBuilder) when last tag is removed, consistent with addTags semantics - manage_task_server_controller: replace update() in setContent() with taskrcVersion.value++ so Obx rebuilds correctly on paste - manage_task_server_controller: add pemVersion.obs trigger; replace update() in onTapPEMWidget/onLongPressPEMWidget with pemVersion.value++ - manage_task_server_page_body: read pemVersion.value in PEM Obx so widget list reacts to file add/remove operations - date_picker_input: initialise _currentIndex from allowedIndexes.first to prevent DropdownButton assert when 0 is not in the allowed set - date_picker_input: replace picked.add(Duration(...)) with explicit DateTime construction to avoid DST-related date/time errors
There was a problem hiding this comment.
🧹 Nitpick comments (1)
lib/app/modules/manageTaskServer/views/manage_task_server_page_body.dart (1)
432-473: Consider extracting PEM widget labels into a helper or map.The 4-level nested ternaries for
optionStringandlistTileTitleare hard to read and duplicate the same conditional structure.♻️ Suggested refactor using a map
// Define at class level or as a static method Map<String, (String optionString, String listTileTitle)> _getPemLabels(SentenceManager sm) => { 'taskd.certificate': ( sm.sentences.manageTaskServerPageConfigureYourCertificate, sm.sentences.manageTaskServerPageSelectCertificate, ), 'taskd.key': ( sm.sentences.manageTaskServerPageConfigureTaskserverKey, sm.sentences.manageTaskServerPageSelectKey, ), 'taskd.ca': ( sm.sentences.manageTaskServerPageConfigureServerCertificate, sm.sentences.manageTaskServerPageSelectCertificate, ), 'server.cert': ( sm.sentences.manageTaskServerPageConfigureServerCertificate, sm.sentences.manageTaskServerPageSelectCertificate, ), }; // Then in the loop: final sm = SentenceManager(currentLanguage: AppSettings.selectedLanguage); final labels = _getPemLabels(sm); for (var pem in [...]) { final (optionStr, titleStr) = labels[pem]!; pemWidgets.add( PemWidget( // ... optionString: optionStr, listTileTitle: titleStr, // ... ), ); }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@lib/app/modules/manageTaskServer/views/manage_task_server_page_body.dart` around lines 432 - 473, The nested ternary logic setting optionString and listTileTitle is hard to read and duplicated; replace it by creating a helper (e.g. a private method or map like _getPemLabels) that accepts a SentenceManager instance (SentenceManager(currentLanguage: AppSettings.selectedLanguage)) and returns the pair of strings for a given pem key, then use that helper inside the loop that builds PemWidget so you set optionString and listTileTitle from the returned values instead of repeated nested ternaries; ensure keys include "taskd.certificate", "taskd.key", "taskd.ca" (and the default/other key) and that callers use the single SentenceManager instance to avoid reconstructing it repeatedly.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Nitpick comments:
In `@lib/app/modules/manageTaskServer/views/manage_task_server_page_body.dart`:
- Around line 432-473: The nested ternary logic setting optionString and
listTileTitle is hard to read and duplicated; replace it by creating a helper
(e.g. a private method or map like _getPemLabels) that accepts a SentenceManager
instance (SentenceManager(currentLanguage: AppSettings.selectedLanguage)) and
returns the pair of strings for a given pem key, then use that helper inside the
loop that builds PemWidget so you set optionString and listTileTitle from the
returned values instead of repeated nested ternaries; ensure keys include
"taskd.certificate", "taskd.key", "taskd.ca" (and the default/other key) and
that callers use the single SentenceManager instance to avoid reconstructing it
repeatedly.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: c8c07d23-0bb8-4fec-88fa-f4f8262df1ef
📒 Files selected for processing (4)
lib/app/modules/detailRoute/controllers/tags_controller.dartlib/app/modules/manageTaskServer/controllers/manage_task_server_controller.dartlib/app/modules/manageTaskServer/views/manage_task_server_page_body.dartlib/app/utils/add_task_dialogue/date_picker_input.dart
🚧 Files skipped from review as they are similar to previous changes (1)
- lib/app/modules/manageTaskServer/controllers/manage_task_server_controller.dart
Refactor: Complete GetX Migration — Eliminate
setState,StatefulBuilder, andGetBuilderCloses #615
Overview
This PR completes the GetX migration across the app by converting all remaining
setState/StatefulBuilderusages to reactive.obsvariables andObxwidgets. It also removes orphanedupdate()calls and replaces the lastGetBuilderinstance withObx.Changes
New File
lib/app/modules/detailRoute/controllers/tags_controller.dartTagsController extends GetxControllerextracted fromTagsRouteStatedraftTagsasRxn<ListBuilder<String>>addTags(),removeTag(),parseTags(), andinit()for callback injection at navigation timeModified Files
tags_widget.dartTagsRouteconverted fromStatefulWidget→GetView<TagsController>; 3setStateremoved;BindingsBuilderused for scoped controller injection atGet.to()date_picker_input.dartcurrentIndex→0.obs;_selectedDates→<DateTime?>[].obs; 3setStatereplaced with direct.valueassignments;build()wrapped inObxprofile_controller.dartfinal selectedProfileMode = RxnString()profile_view.dartStatefulBuilderdialog wrapper removed; 2setStatereplaced withcontroller.selectedProfileMode.value = value; dialog content wrapped inObxmanage_task_server_controller.dartfinal taskrcVersion = 0.obsas a refresh triggermanage_task_server_page_body.dartStatefulBuildermodal wrapper removed;setState(() {})→controller.taskrcVersion.value++; TASKRC status row wrapped inObx;GetBuilder<ManageTaskServerController>→Obxhome_controller.dartvoid refreshTaskList() => _refreshTasks()to expose private method publiclyadd_task_bottom_sheet_new.darthomeController.update()NOP callstasks_builder.dartstorageWidget._refreshTasks()→storageWidget.refreshTaskList()Verification
flutter analyze— 0 new issues introduced (72 pre-existing infos/warnings unrelated to this PR)get_errorson all 10 modified files — 0 errorsgrep "setState" lib/app/**/*.dart— 0 active hits (only commented-out lines inpem_widget.dart)grep "GetBuilder" lib/app/**/*.dart— 0 hits (only code-gen file references)Notes
tags_input.dartwas intentionally left asStatefulWidget— it owns aStringTagControllerlifecycle with nosetState, making conversion unnecessary._SpinningIconinhome_page_app_bar.dartwas left as-is — it usesAnimationControlleronly, which is a legitimate lifecycle use case.Type of Change
Testing
Summary by CodeRabbit
New Features
Bug Fixes