Skip to content

Priority selector and tag system verson2#33

Merged
tqha1011 merged 5 commits intomainfrom
priority_selector_and_tag_system_Verson2
Apr 10, 2026
Merged

Priority selector and tag system verson2#33
tqha1011 merged 5 commits intomainfrom
priority_selector_and_tag_system_Verson2

Conversation

@anhkietbienhoa-crypto
Copy link
Copy Markdown
Collaborator

@anhkietbienhoa-crypto anhkietbienhoa-crypto commented Apr 9, 2026

Update tags

Summary by CodeRabbit

  • New Features

    • Introduced comprehensive tag management system with predefined categories (Work Type, Time, Status) for better task organization
    • Added ability to create and save custom tags (up to 5 per task)
    • Task detail screen now displays and allows selection of time and status tags
  • Bug Fixes

    • Resolved issue where sort button was displaying mock placeholder content

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 9, 2026

📝 Walkthrough

Walkthrough

This pull request enhances task management with tag-based organization. Changes include removing errant mock UI from the home screen, integrating tag selection and persistence in the task detail screen, introducing a new TagSelector widget for managing predefined and custom tags, and extending TaskViewModel to support multiple tag categories with SharedPreferences-backed custom tag storage.

Changes

Cohort / File(s) Summary
UI Cleanup
src/lib/features/tasks/view/screens/home_screen.dart
Removed 21 lines of incorrectly nested mock TaskCard UI that was placed inside the sort button's widget tree; restores sort control to render only the intended icon/label.
Task Detail Integration
src/lib/features/tasks/view/screens/task_detail_screen.dart
Added tag state management with selection tracking; integrated TaskViewModel to dynamically render time and status tags in new selectable Wrap sections; updated save logic to persist selected tags via updateTaskTags() before navigation.
Tag Selection Widget
src/lib/features/tasks/view/widgets/tag_selector.dart
Introduced new TagSelector widget with support for three predefined tag groups and custom tag creation; includes custom tag validation (max 12 chars, max 5 tags), AlertDialog input, and animated chip selection UI with _TagChip component.
ViewModel Enhancement
src/lib/features/tasks/viewmodel/task_viewmodel.dart
Replaced single availableTags with three predefined tag lists (workTypeTags, timeTags, statusTags); added persistent custom tag management via SharedPreferences with JSON serialization; added addCustomTag() validation and updateTaskTags() methods.

Sequence Diagram

sequenceDiagram
    participant User
    participant TaskDetailScreen
    participant TagSelector
    participant TaskViewModel
    participant SharedPreferences as SharedPreferences

    User->>TaskDetailScreen: Opens task for editing
    TaskDetailScreen->>TaskViewModel: watches TaskViewModel
    TaskViewModel->>SharedPreferences: loads customTags from storage
    SharedPreferences-->>TaskViewModel: returns custom tags JSON
    TaskViewModel-->>TaskDetailScreen: emits tag lists (predefined + custom)
    TaskDetailScreen->>TagSelector: displays tag selection UI
    User->>TagSelector: selects tags
    TaskDetailScreen->>TaskDetailScreen: updates _currentTags state
    User->>TagSelector: clicks "Tạo tag" button
    TagSelector->>TagSelector: shows tag creation dialog
    User->>TagSelector: enters tag name and confirms
    TagSelector->>TaskViewModel: calls addCustomTag(name)
    TaskViewModel->>TaskViewModel: validates tag (length, count, uniqueness)
    TaskViewModel->>SharedPreferences: persists updated custom tags
    SharedPreferences-->>TaskViewModel: confirms save
    TaskViewModel-->>TaskDetailScreen: notifies listeners
    TaskDetailScreen->>TagSelector: refreshes tag list
    User->>TaskDetailScreen: clicks save button
    TaskDetailScreen->>TaskViewModel: calls updateTaskTags(taskId, _currentTags)
    TaskViewModel->>TaskViewModel: updates task.tags in _tasks list
    TaskViewModel-->>TaskDetailScreen: notifies listeners
    TaskDetailScreen->>User: shows success SnackBar and navigates back
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

  • PR #31: Adds complementary TagModel and TagSelector usage within task feature; both modify tag selection and storage at the code level.

Suggested reviewers

  • tqha1011
  • hoanghaoz

Poem

🐰 Hop! The tags now bloom and dance,
Custom words find their chance,
Views refresh with buttons bright,
Tasks are sorted—what a sight!
No more mock cards in the way,
Order reigns another day! ✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Title check ⚠️ Warning The title 'Priority selector and tag system verson2' partially relates to the changeset but is misleading and contains a typo. The PR primarily implements a comprehensive tag system with custom tag creation, tag selection UI, and tag persistence, but the 'priority selector' component is not evident in the actual changes. Correct the typo ('verson2' → 'version 2') and clarify the title to accurately reflect the main changes, such as 'Implement tag selection and custom tag management system' or 'Add TagSelector widget with custom tag creation and persistence'.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch priority_selector_and_tag_system_Verson2

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🧹 Nitpick comments (11)
src/lib/features/tasks/model/task_model.dart (2)

33-44: Consider distinct icons for each priority level.

The icon getter returns Icons.flag for medium, high, and urgent priorities, making them visually indistinguishable by icon alone. Only low uses Icons.flag_outlined.

Consider using more distinct icons (e.g., Icons.flag_outlined, Icons.flag, Icons.priority_high, Icons.warning) to improve visual differentiation.

♻️ Suggested distinct icons
   IconData get icon {
     switch (this) {
       case Priority.low:
         return Icons.flag_outlined;
       case Priority.medium:
         return Icons.flag;
       case Priority.high:
-        return Icons.flag;
+        return Icons.priority_high;
       case Priority.urgent:
-        return Icons.flag;
+        return Icons.warning;
     }
   }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/lib/features/tasks/model/task_model.dart` around lines 33 - 44, The icon
getter for the Priority enum returns the same icon for medium/high/urgent, so
update the Priority.icon getter to return distinct Icons for each case (for
example: Priority.low -> Icons.flag_outlined, Priority.medium -> Icons.flag,
Priority.high -> Icons.priority_high, Priority.urgent -> Icons.warning) to make
priorities visually distinct; modify the switch in the icon getter accordingly
and ensure all enum cases are covered.

48-54: Consider implementing equality for TagModel.

TagModel lacks == and hashCode overrides. The codebase works around this by comparing t.id == tag.id explicitly, but implementing proper equality would make the code cleaner and prevent subtle bugs if tags are compared directly in the future.

♻️ Add equality implementation
 class TagModel {
   final String id;
   final String name;
   final Color color;

   const TagModel({required this.id, required this.name, required this.color});
+
+  `@override`
+  bool operator ==(Object other) =>
+      identical(this, other) ||
+      other is TagModel && runtimeType == other.runtimeType && id == other.id;
+
+  `@override`
+  int get hashCode => id.hashCode;
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/lib/features/tasks/model/task_model.dart` around lines 48 - 54, TagModel
lacks value equality; override operator == and hashCode (or implement Equatable)
on class TagModel so instances with the same id (and optionally name/color)
compare equal; update TagModel to implement equality by comparing id (and
include name and color if desired) and compute hashCode from the same fields to
ensure consistent behavior when tags are used in collections or compared
directly.
src/lib/features/tasks/view/screens/create_task_screen.dart (1)

105-110: Duplicate categories array defined in two places.

The categories list is defined both in the itemBuilder (lines 105-110) and in the onPressed handler (lines 268-273). Extract this to a class-level constant to avoid duplication and ensure consistency.

♻️ Extract categories to a constant
 class _CreateTaskScreenState extends State<CreateTaskScreen> {
+  static const List<String> _categories = [
+    'Development',
+    'Research',
+    'Design',
+    'Backend',
+  ];
+
   final TextEditingController _nameController = TextEditingController(

Then use _categories instead of the inline list in both locations.

Also applies to: 268-273

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/lib/features/tasks/view/screens/create_task_screen.dart` around lines 105
- 110, The categories list is duplicated in the itemBuilder and the onPressed
handler; extract it to a single class-level constant (e.g., add a static const
List<String> _categories = [...]) in the CreateTaskScreen (or its State class)
and replace the inline lists in both the itemBuilder and the onPressed handler
with references to _categories to ensure a single source of truth and prevent
drift.
src/lib/features/tasks/view/widgets/tag_selector.dart (1)

107-107: Hardcoded magic number 5 should reference the constant.

Line 107 uses viewModel.customTags.length < 5 but TaskViewModel defines _maxCustomTags = 5. Consider exposing this constant from the ViewModel or defining a shared constant to avoid duplication.

♻️ Expose constant from ViewModel

In task_viewmodel.dart:

-  static const _maxCustomTags = 5;
+  static const maxCustomTags = 5;

Then in tag_selector.dart:

-            if (viewModel.customTags.length < 5)
+            if (viewModel.customTags.length < TaskViewModel.maxCustomTags)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/lib/features/tasks/view/widgets/tag_selector.dart` at line 107, Replace
the hardcoded 5 in tag availability checks with the canonical max-custom-tags
constant: expose TaskViewModel._maxCustomTags (rename to public like
maxCustomTags) or move the value to a shared constant (e.g., MAX_CUSTOM_TAGS)
and import it into TagSelector; then update the condition in TagSelector (the
code using viewModel.customTags.length < 5) to use viewModel.maxCustomTags or
MAX_CUSTOM_TAGS so both files reference the same constant (update any other
occurrences similarly).
src/lib/features/tasks/view/widgets/priority_selector.dart (1)

21-59: Minor: Uneven padding on last priority item.

Each item has padding: const EdgeInsets.only(right: 8), causing the last item to have unnecessary right padding while the first item touches the left edge. Consider using SizedBox as spacers between items or adjusting the padding logic.

♻️ Alternative using separated spacing
         Row(
-          children: Priority.values.map((priority) {
+          children: Priority.values.asMap().entries.map((entry) {
+            final index = entry.key;
+            final priority = entry.value;
             final isSelected = viewModel.selectedPriority == priority;
             return Expanded(
               child: Padding(
-                padding: const EdgeInsets.only(right: 8),
+                padding: EdgeInsets.only(
+                  right: index < Priority.values.length - 1 ? 8 : 0,
+                ),
                 child: GestureDetector(
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/lib/features/tasks/view/widgets/priority_selector.dart` around lines 21 -
59, The right-only padding on each priority item makes the last item have extra
space and the first item touch the left edge; update the layout in the priority
selector widget so spacing is applied between items instead of per-item right
padding: remove the per-item padding (EdgeInsets.only(right: 8)) around the
Expanded/GestureDetector/AnimatedContainer and instead insert horizontal spacing
between items (e.g., wrap children in a Row and use SizedBox(width: 8) or use
ListView.separated or Wrap with spacing) so setPriority, AnimatedContainer,
GestureDetector and the Column contents remain unchanged while visual spacing is
applied only between items.
src/lib/features/tasks/viewmodel/task_viewmodel.dart (2)

95-104: Consider awaiting _saveCustomTags() or handling save failures.

_saveCustomTags() is called fire-and-forget. If the app terminates before the async save completes, data could be lost. Consider either awaiting the save or implementing a more robust persistence strategy with retry logic.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/lib/features/tasks/viewmodel/task_viewmodel.dart` around lines 95 - 104,
The code currently adds a new TagModel to _customTags and calls
_saveCustomTags() fire-and-forget; change this to await the async
_saveCustomTags() call (or handle its Future) inside the method that contains
_customTags.add so you only call notifyListeners() and return after the save
completes, and add error handling/retry logic to handle save failures (e.g.,
catch exceptions from _saveCustomTags(), log via your logger, and surface an
error result instead of returning null). Ensure you update the method signature
to be async if needed and reference the places: _customTags.add(... TagModel
...), _customTagColors, _saveCustomTags(), notifyListeners(), and the return
path so the save is awaited and failures are handled.

144-174: Tasks are stored in-memory only and will be lost on app restart.

The _tasks list is not persisted. Unlike custom tags which use SharedPreferences, tasks added via addTask() will be lost when the app restarts. Consider whether this is intentional (e.g., tasks are synced from a backend like Supabase) or if local persistence should be added.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/lib/features/tasks/viewmodel/task_viewmodel.dart` around lines 144 - 174,
Tasks are only kept in the in-memory _tasks list and added via
addTask(TaskModel), so they are lost on app restart; either wire local
persistence or clearly delegate to remote sync. Fix by persisting task changes
when addTask/remove/edit operations occur (and loading them on ViewModel init):
update addTask(TaskModel), any mutators, and the getter
tasks/_getFilteredAndSorted to read from a persisted store (e.g.,
SharedPreferences, Hive, or your Supabase sync layer) and implement a
loadTasks() called in the TaskViewModel constructor/init; alternatively, if
tasks are meant to be remote-synced, add explicit TODOs and throw/assert when no
sync provider is configured so the behavior is clear.
src/lib/features/tasks/view/screens/task_detail_screen.dart (1)

192-297: Duplicated tag section UI code.

The "Thời gian" (Time Tags) and "Trạng thái" (Status Tags) sections have nearly identical code. Consider extracting a reusable widget similar to _buildTagGroup in TagSelector.

♻️ Extract reusable tag section widget
Widget _buildTagSection({
  required String label,
  required List<TagModel> tags,
}) {
  return Column(
    crossAxisAlignment: CrossAxisAlignment.start,
    children: [
      Text(label, style: Theme.of(context).textTheme.labelLarge),
      const SizedBox(height: 10),
      Wrap(
        spacing: 8,
        runSpacing: 8,
        children: tags.map((tag) {
          final isSelected = _isTagSelected(tag);
          return GestureDetector(
            onTap: () => _toggleTag(tag),
            child: AnimatedContainer(
              // ... common styling
            ),
          );
        }).toList(),
      ),
    ],
  );
}

Then use:

_buildTagSection(label: 'Thời gian', tags: viewModel.timeTags),
const SizedBox(height: 20),
_buildTagSection(label: 'Trạng thái', tags: viewModel.statusTags),
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/lib/features/tasks/view/screens/task_detail_screen.dart` around lines 192
- 297, The two duplicated tag UI blocks for "Thời gian" and "Trạng thái" should
be extracted into a reusable widget method (e.g., _buildTagSection) to avoid
repetition; create a helper that takes label and List<TagModel> (used with
viewModel.timeTags and viewModel.statusTags), reuse the existing logic that
references _isTagSelected(tag) and _toggleTag(tag) and the AnimatedContainer
styling, then replace both duplicated code blocks with calls to
_buildTagSection(label: ..., tags: ...), keeping the const SizedBox(height: 20)
between them.
src/lib/features/tasks/view/screens/home_screen.dart (3)

208-215: Avoid two sources of truth for priority labels.

The filter chips use p.label, while section headers use _priorityGroupLabel(). Those will drift as soon as one side changes or gets localized differently. Prefer a single label/getter on Priority for both places.

Also applies to: 316-327

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/lib/features/tasks/view/screens/home_screen.dart` around lines 208 - 215,
The UI currently uses two different label sources — p.label in the filter chips
and _priorityGroupLabel() for section headers — which can diverge; consolidate
to a single source by adding/using a unified getter on the Priority enum (e.g.,
Priority.label) and replace calls to _priorityGroupLabel() with that getter so
both the _FilterChip construction (where p.label is used) and the section header
rendering use the same Priority.label; update any localization logic to live on
the Priority getter and adjust references in viewModel.setFilterPriority usage
and the section header code to reference Priority.label instead of
_priorityGroupLabel().

16-24: Cache viewModel.tasks before grouping.

tasks is a computed getter, so this loop rebuilds the filtered/sorted list once per priority on every widget rebuild. Read it once, then group the cached result.

♻️ Proposed refactor
-    Map<Priority, List<TaskModel>> grouped = {};
-    for (var priority in Priority.values.reversed) {
-      final tasks = viewModel.tasks
+    final visibleTasks = viewModel.tasks;
+    final grouped = <Priority, List<TaskModel>>{};
+    for (final priority in Priority.values.reversed) {
+      final tasks = visibleTasks
           .where((t) => t.priority == priority)
           .toList();
       if (tasks.isNotEmpty) grouped[priority] = tasks;
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/lib/features/tasks/view/screens/home_screen.dart` around lines 16 - 24,
The loop repeatedly calls the computed getter viewModel.tasks for each priority
causing unnecessary recomputation; cache the result once (e.g., read
viewModel.tasks into a local final like allTasks) before the grouping loop and
then use that cached list when filtering by Priority.values.reversed to build
grouped (referencing viewModel, TaskViewModel, the tasks getter, grouped, and
Priority.values.reversed).

149-190: Prefer Material chips/buttons over GestureDetector here.

These are primary interactive controls, but GestureDetector skips the built-in focus, hover, semantics, splash, and tap-target behavior you get from ChoiceChip/FilterChip/InkWell. That makes the bar harder to use on web/desktop and with accessibility tools.

Also applies to: 371-390

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/lib/features/tasks/view/screens/home_screen.dart` around lines 149 - 190,
Replace the GestureDetector+AnimatedContainer block used for the "Sort" control
with a Material chip or button (e.g., ChoiceChip or FilterChip) so you get
built-in focus, hover, semantics, splash and correct tap-target behavior; use
the chip's selected property tied to viewModel.sortByPriority and call
viewModel.toggleSortByPriority (or onSelected) for taps, map the
selected/unselected colors to AppColors.primaryBlue/Colors.white and update the
Icon/Text color based on the chip's selected state, and ensure you apply the
same change to the other similar control instance that also uses GestureDetector
(the repeated block referenced in the review).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@README.md`:
- Around line 19-21: README currently instructs installing shared_preferences:
^2.3.2 but pubspec.yaml uses ^2.2.2 and the YAML code block in README is missing
the closing ```; fix by making versions consistent (either change the README
entry to shared_preferences: ^2.2.2 to match pubspec.yaml or update pubspec.yaml
to ^2.3.2) and add the missing closing triple-backtick to the README code fence
so the block is properly terminated; reference the shared_preferences dependency
name, the README.md instructions block, and pubspec.yaml entry when applying the
change.

In `@src/lib/features/tasks/view/screens/create_task_screen.dart`:
- Around line 266-289: Add input validation inside the onPressed handler before
constructing TaskModel: validate _nameController.text is non-empty and not
whitespace, ensure _startTime is before or equal to _endTime (or swap/raise UI
error), and enforce description rules if required (e.g., non-empty) — if any
check fails, show a user-facing error (e.g., SnackBar or dialog) and return
early; only call viewModel.addTask(newTask), viewModel.reset(), and
Navigator.pop(context) when all validations pass. Reference the onPressed
closure, TaskViewModel, TaskModel, _nameController, _descController, _startTime,
_endTime, _selectedCategoryIndex, and viewModel.selectedPriority/selectedTags
when implementing.

In `@src/lib/features/tasks/view/screens/home_screen.dart`:
- Around line 18-25: The grouped-by-priority block ignores the
viewModel.sortByPriority toggle because it always iterates
Priority.values.reversed; update it to honor viewModel.sortByPriority by
computing the priority iteration order first (e.g., var priorities =
viewModel.sortByPriority ? Priority.values.reversed.toList() :
Priority.values.toList()) and then build grouped by iterating over that
priorities list (leave Map<Priority, List<TaskModel>> grouped and the filtering
by t.priority in place), or alternatively remove the sort toggle UI if you
intend the grouped layout to have a fixed order; reference symbols:
TaskViewModel.tasks, viewModel.sortByPriority, Priority.values.reversed,
grouped.

In `@src/lib/features/tasks/view/screens/task_detail_screen.dart`:
- Around line 56-73: The _saveChanges method currently mutates widget.task
fields directly which can cause unexpected side effects; instead, add an
updateTask method on TaskViewModel (e.g., TaskViewModel.updateTask(String
taskId, {String? title, String? description, TimeOfDay? startTime, TimeOfDay?
endTime, String? category, List<TagModel>? tags})) that finds the task by id,
applies the non-null field updates, calls notifyListeners(), and then call that
method from _saveChanges (and keep calling updateTaskTags as needed) so the
ViewModel owns state changes rather than mutating widget.task directly.

In `@src/lib/features/tasks/viewmodel/task_viewmodel.dart`:
- Around line 56-72: _wrap the SharedPreferences access and jsonDecode in
_loadCustomTags() with a try-catch to prevent crashes from malformed JSON or
prefs failures: call SharedPreferences.getInstance() and jsonDecode inside the
try block, parse into TagModel as before, and in the catch block log the error
(e.g., debugPrint or your app logger), clear or remove the corrupt value via
prefs.remove(_customTagsKey) or set _customTags = [] and call notifyListeners()
so the app recovers gracefully; reference the existing symbols _loadCustomTags,
_customTagsKey, TagModel, _customTags, and notifyListeners when applying the
change.

---

Nitpick comments:
In `@src/lib/features/tasks/model/task_model.dart`:
- Around line 33-44: The icon getter for the Priority enum returns the same icon
for medium/high/urgent, so update the Priority.icon getter to return distinct
Icons for each case (for example: Priority.low -> Icons.flag_outlined,
Priority.medium -> Icons.flag, Priority.high -> Icons.priority_high,
Priority.urgent -> Icons.warning) to make priorities visually distinct; modify
the switch in the icon getter accordingly and ensure all enum cases are covered.
- Around line 48-54: TagModel lacks value equality; override operator == and
hashCode (or implement Equatable) on class TagModel so instances with the same
id (and optionally name/color) compare equal; update TagModel to implement
equality by comparing id (and include name and color if desired) and compute
hashCode from the same fields to ensure consistent behavior when tags are used
in collections or compared directly.

In `@src/lib/features/tasks/view/screens/create_task_screen.dart`:
- Around line 105-110: The categories list is duplicated in the itemBuilder and
the onPressed handler; extract it to a single class-level constant (e.g., add a
static const List<String> _categories = [...]) in the CreateTaskScreen (or its
State class) and replace the inline lists in both the itemBuilder and the
onPressed handler with references to _categories to ensure a single source of
truth and prevent drift.

In `@src/lib/features/tasks/view/screens/home_screen.dart`:
- Around line 208-215: The UI currently uses two different label sources —
p.label in the filter chips and _priorityGroupLabel() for section headers —
which can diverge; consolidate to a single source by adding/using a unified
getter on the Priority enum (e.g., Priority.label) and replace calls to
_priorityGroupLabel() with that getter so both the _FilterChip construction
(where p.label is used) and the section header rendering use the same
Priority.label; update any localization logic to live on the Priority getter and
adjust references in viewModel.setFilterPriority usage and the section header
code to reference Priority.label instead of _priorityGroupLabel().
- Around line 16-24: The loop repeatedly calls the computed getter
viewModel.tasks for each priority causing unnecessary recomputation; cache the
result once (e.g., read viewModel.tasks into a local final like allTasks) before
the grouping loop and then use that cached list when filtering by
Priority.values.reversed to build grouped (referencing viewModel, TaskViewModel,
the tasks getter, grouped, and Priority.values.reversed).
- Around line 149-190: Replace the GestureDetector+AnimatedContainer block used
for the "Sort" control with a Material chip or button (e.g., ChoiceChip or
FilterChip) so you get built-in focus, hover, semantics, splash and correct
tap-target behavior; use the chip's selected property tied to
viewModel.sortByPriority and call viewModel.toggleSortByPriority (or onSelected)
for taps, map the selected/unselected colors to
AppColors.primaryBlue/Colors.white and update the Icon/Text color based on the
chip's selected state, and ensure you apply the same change to the other similar
control instance that also uses GestureDetector (the repeated block referenced
in the review).

In `@src/lib/features/tasks/view/screens/task_detail_screen.dart`:
- Around line 192-297: The two duplicated tag UI blocks for "Thời gian" and
"Trạng thái" should be extracted into a reusable widget method (e.g.,
_buildTagSection) to avoid repetition; create a helper that takes label and
List<TagModel> (used with viewModel.timeTags and viewModel.statusTags), reuse
the existing logic that references _isTagSelected(tag) and _toggleTag(tag) and
the AnimatedContainer styling, then replace both duplicated code blocks with
calls to _buildTagSection(label: ..., tags: ...), keeping the const
SizedBox(height: 20) between them.

In `@src/lib/features/tasks/view/widgets/priority_selector.dart`:
- Around line 21-59: The right-only padding on each priority item makes the last
item have extra space and the first item touch the left edge; update the layout
in the priority selector widget so spacing is applied between items instead of
per-item right padding: remove the per-item padding (EdgeInsets.only(right: 8))
around the Expanded/GestureDetector/AnimatedContainer and instead insert
horizontal spacing between items (e.g., wrap children in a Row and use
SizedBox(width: 8) or use ListView.separated or Wrap with spacing) so
setPriority, AnimatedContainer, GestureDetector and the Column contents remain
unchanged while visual spacing is applied only between items.

In `@src/lib/features/tasks/view/widgets/tag_selector.dart`:
- Line 107: Replace the hardcoded 5 in tag availability checks with the
canonical max-custom-tags constant: expose TaskViewModel._maxCustomTags (rename
to public like maxCustomTags) or move the value to a shared constant (e.g.,
MAX_CUSTOM_TAGS) and import it into TagSelector; then update the condition in
TagSelector (the code using viewModel.customTags.length < 5) to use
viewModel.maxCustomTags or MAX_CUSTOM_TAGS so both files reference the same
constant (update any other occurrences similarly).

In `@src/lib/features/tasks/viewmodel/task_viewmodel.dart`:
- Around line 95-104: The code currently adds a new TagModel to _customTags and
calls _saveCustomTags() fire-and-forget; change this to await the async
_saveCustomTags() call (or handle its Future) inside the method that contains
_customTags.add so you only call notifyListeners() and return after the save
completes, and add error handling/retry logic to handle save failures (e.g.,
catch exceptions from _saveCustomTags(), log via your logger, and surface an
error result instead of returning null). Ensure you update the method signature
to be async if needed and reference the places: _customTags.add(... TagModel
...), _customTagColors, _saveCustomTags(), notifyListeners(), and the return
path so the save is awaited and failures are handled.
- Around line 144-174: Tasks are only kept in the in-memory _tasks list and
added via addTask(TaskModel), so they are lost on app restart; either wire local
persistence or clearly delegate to remote sync. Fix by persisting task changes
when addTask/remove/edit operations occur (and loading them on ViewModel init):
update addTask(TaskModel), any mutators, and the getter
tasks/_getFilteredAndSorted to read from a persisted store (e.g.,
SharedPreferences, Hive, or your Supabase sync layer) and implement a
loadTasks() called in the TaskViewModel constructor/init; alternatively, if
tasks are meant to be remote-synced, add explicit TODOs and throw/assert when no
sync provider is configured so the behavior is clear.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: b4ebaa88-7d07-4fee-992b-088df9324a7a

📥 Commits

Reviewing files that changed from the base of the PR and between 5b59d35 and 4798f20.

📒 Files selected for processing (9)
  • README.md
  • src/lib/features/tasks/model/task_model.dart
  • src/lib/features/tasks/view/screens/create_task_screen.dart
  • src/lib/features/tasks/view/screens/home_screen.dart
  • src/lib/features/tasks/view/screens/task_detail_screen.dart
  • src/lib/features/tasks/view/widgets/priority_selector.dart
  • src/lib/features/tasks/view/widgets/tag_selector.dart
  • src/lib/features/tasks/viewmodel/task_viewmodel.dart
  • src/lib/main.dart

Comment thread README.md Outdated
Comment on lines +266 to +289
onPressed: () {
final viewModel = context.read<TaskViewModel>();
final List<String> categories = [
'Development',
'Research',
'Design',
'Backend',
];
final newTask = TaskModel(
id: DateTime.now().millisecondsSinceEpoch
.toString(),
title: _nameController.text,
description: _descController.text,
category: categories[_selectedCategoryIndex],
startTime: _startTime,
endTime: _endTime,
date: _selectedDate,
priority: viewModel.selectedPriority,
tags: List.from(viewModel.selectedTags),
);
viewModel.addTask(newTask);
viewModel.reset();
Navigator.pop(context);
},
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Missing input validation before task creation.

The task is created without validating:

  • Empty or whitespace-only title
  • Start time being after end time
  • Empty description (if required)

Consider adding validation before creating the task.

🛡️ Suggested validation
                         onPressed: () {
+                          final title = _nameController.text.trim();
+                          if (title.isEmpty) {
+                            ScaffoldMessenger.of(context).showSnackBar(
+                              const SnackBar(
+                                content: Text('Task name cannot be empty'),
+                                backgroundColor: Colors.red,
+                              ),
+                            );
+                            return;
+                          }
+
                           final viewModel = context.read<TaskViewModel>();
                           final List<String> categories = [
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
onPressed: () {
final viewModel = context.read<TaskViewModel>();
final List<String> categories = [
'Development',
'Research',
'Design',
'Backend',
];
final newTask = TaskModel(
id: DateTime.now().millisecondsSinceEpoch
.toString(),
title: _nameController.text,
description: _descController.text,
category: categories[_selectedCategoryIndex],
startTime: _startTime,
endTime: _endTime,
date: _selectedDate,
priority: viewModel.selectedPriority,
tags: List.from(viewModel.selectedTags),
);
viewModel.addTask(newTask);
viewModel.reset();
Navigator.pop(context);
},
onPressed: () {
final title = _nameController.text.trim();
if (title.isEmpty) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Task name cannot be empty'),
backgroundColor: Colors.red,
),
);
return;
}
final viewModel = context.read<TaskViewModel>();
final List<String> categories = [
'Development',
'Research',
'Design',
'Backend',
];
final newTask = TaskModel(
id: DateTime.now().millisecondsSinceEpoch
.toString(),
title: _nameController.text,
description: _descController.text,
category: categories[_selectedCategoryIndex],
startTime: _startTime,
endTime: _endTime,
date: _selectedDate,
priority: viewModel.selectedPriority,
tags: List.from(viewModel.selectedTags),
);
viewModel.addTask(newTask);
viewModel.reset();
Navigator.pop(context);
},
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/lib/features/tasks/view/screens/create_task_screen.dart` around lines 266
- 289, Add input validation inside the onPressed handler before constructing
TaskModel: validate _nameController.text is non-empty and not whitespace, ensure
_startTime is before or equal to _endTime (or swap/raise UI error), and enforce
description rules if required (e.g., non-empty) — if any check fails, show a
user-facing error (e.g., SnackBar or dialog) and return early; only call
viewModel.addTask(newTask), viewModel.reset(), and Navigator.pop(context) when
all validations pass. Reference the onPressed closure, TaskViewModel, TaskModel,
_nameController, _descController, _startTime, _endTime, _selectedCategoryIndex,
and viewModel.selectedPriority/selectedTags when implementing.

Comment on lines +18 to +25
// Nhóm task theo priority
Map<Priority, List<TaskModel>> grouped = {};
for (var priority in Priority.values.reversed) {
final tasks = viewModel.tasks
.where((t) => t.priority == priority)
.toList();
if (tasks.isNotEmpty) grouped[priority] = tasks;
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

sortByPriority has no visible effect in this screen.

TaskViewModel.tasks already applies the priority sort, but this block immediately re-buckets everything into a fixed Priority.values.reversed order. That means the toggle on Line 150 changes state without changing the rendered section order. Either remove the sort toggle for this grouped layout or make the section order depend on viewModel.sortByPriority.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/lib/features/tasks/view/screens/home_screen.dart` around lines 18 - 25,
The grouped-by-priority block ignores the viewModel.sortByPriority toggle
because it always iterates Priority.values.reversed; update it to honor
viewModel.sortByPriority by computing the priority iteration order first (e.g.,
var priorities = viewModel.sortByPriority ? Priority.values.reversed.toList() :
Priority.values.toList()) and then build grouped by iterating over that
priorities list (leave Map<Priority, List<TaskModel>> grouped and the filtering
by t.priority in place), or alternatively remove the sort toggle UI if you
intend the grouped layout to have a fixed order; reference symbols:
TaskViewModel.tasks, viewModel.sortByPriority, Priority.values.reversed,
grouped.

Comment on lines 56 to 73
void _saveChanges() {
// Update the local model
// (Note: Later, this will call TaskViewModel -> TaskService -> your ASP.NET Core API to update the database)
widget.task.title = _titleController.text;
widget.task.description = _descController.text;
widget.task.startTime = _startTime;
widget.task.endTime = _endTime;
widget.task.category = _currentCategory;

// Show success message
// Lưu tags mới vào task qua ViewModel
context.read<TaskViewModel>().updateTaskTags(widget.task.id, _currentTags);

ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Task updated successfully!'), backgroundColor: Colors.green),
const SnackBar(
content: Text('Task updated successfully!'),
backgroundColor: Colors.green,
),
);

// Return to the previous screen
Navigator.pop(context);
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Direct mutation of widget.task may cause unexpected side effects.

_saveChanges() directly mutates widget.task properties (lines 57-61). This modifies the original TaskModel object that was passed to this screen, which can lead to:

  • UI inconsistencies if the parent widget doesn't expect the object to change
  • Difficulty tracking state changes
  • Potential issues with provider/state management

Consider updating the task through the ViewModel instead of direct mutation.

🐛 Suggested approach using ViewModel

Add an updateTask method to TaskViewModel that handles all task field updates:

// In TaskViewModel
void updateTask(String taskId, {
  String? title,
  String? description,
  TimeOfDay? startTime,
  TimeOfDay? endTime,
  String? category,
  List<TagModel>? tags,
}) {
  final index = _tasks.indexWhere((t) => t.id == taskId);
  if (index != -1) {
    final task = _tasks[index];
    if (title != null) task.title = title;
    if (description != null) task.description = description;
    // ... etc
    notifyListeners();
  }
}

Then call this method from _saveChanges() instead of direct mutation.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/lib/features/tasks/view/screens/task_detail_screen.dart` around lines 56
- 73, The _saveChanges method currently mutates widget.task fields directly
which can cause unexpected side effects; instead, add an updateTask method on
TaskViewModel (e.g., TaskViewModel.updateTask(String taskId, {String? title,
String? description, TimeOfDay? startTime, TimeOfDay? endTime, String? category,
List<TagModel>? tags})) that finds the task by id, applies the non-null field
updates, calls notifyListeners(), and then call that method from _saveChanges
(and keep calling updateTaskTags as needed) so the ViewModel owns state changes
rather than mutating widget.task directly.

Comment on lines +56 to +72
Future<void> _loadCustomTags() async {
final prefs = await SharedPreferences.getInstance();
final raw = prefs.getString(_customTagsKey);
if (raw != null) {
final List decoded = jsonDecode(raw);
_customTags = decoded
.map(
(e) => TagModel(
id: e['id'],
name: e['name'],
color: Color(e['color']),
),
)
.toList();
notifyListeners();
}
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Add error handling for SharedPreferences operations.

_loadCustomTags() lacks try-catch for potential jsonDecode failures (malformed JSON) or SharedPreferences errors. A corrupt stored value would crash the app.

🛡️ Proposed fix with error handling
   Future<void> _loadCustomTags() async {
+    try {
       final prefs = await SharedPreferences.getInstance();
       final raw = prefs.getString(_customTagsKey);
       if (raw != null) {
         final List decoded = jsonDecode(raw);
         _customTags = decoded
             .map(
               (e) => TagModel(
                 id: e['id'],
                 name: e['name'],
                 color: Color(e['color']),
               ),
             )
             .toList();
         notifyListeners();
       }
+    } catch (e) {
+      debugPrint('Failed to load custom tags: $e');
+      // Optionally clear corrupted data
+    }
   }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
Future<void> _loadCustomTags() async {
final prefs = await SharedPreferences.getInstance();
final raw = prefs.getString(_customTagsKey);
if (raw != null) {
final List decoded = jsonDecode(raw);
_customTags = decoded
.map(
(e) => TagModel(
id: e['id'],
name: e['name'],
color: Color(e['color']),
),
)
.toList();
notifyListeners();
}
}
Future<void> _loadCustomTags() async {
try {
final prefs = await SharedPreferences.getInstance();
final raw = prefs.getString(_customTagsKey);
if (raw != null) {
final List decoded = jsonDecode(raw);
_customTags = decoded
.map(
(e) => TagModel(
id: e['id'],
name: e['name'],
color: Color(e['color']),
),
)
.toList();
notifyListeners();
}
} catch (e) {
debugPrint('Failed to load custom tags: $e');
// Optionally clear corrupted data
}
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/lib/features/tasks/viewmodel/task_viewmodel.dart` around lines 56 - 72,
_wrap the SharedPreferences access and jsonDecode in _loadCustomTags() with a
try-catch to prevent crashes from malformed JSON or prefs failures: call
SharedPreferences.getInstance() and jsonDecode inside the try block, parse into
TagModel as before, and in the catch block log the error (e.g., debugPrint or
your app logger), clear or remove the corrupt value via
prefs.remove(_customTagsKey) or set _customTags = [] and call notifyListeners()
so the app recovers gracefully; reference the existing symbols _loadCustomTags,
_customTagsKey, TagModel, _customTags, and notifyListeners when applying the
change.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (2)
src/lib/features/tasks/view/screens/task_detail_screen.dart (1)

204-309: Consider extracting duplicated tag section widget.

The "Thời gian" (lines 204-255) and "Trạng thái" (lines 258-309) sections share identical structure. Extracting a reusable _buildTagSection(String title, List<TagModel> tags) method would reduce duplication and improve maintainability.

Also note: workTypeTags is defined in the ViewModel but not displayed here. If this is intentional, consider documenting why.

♻️ Proposed extraction
Widget _buildTagSection(String title, List<TagModel> tags) {
  return Column(
    crossAxisAlignment: CrossAxisAlignment.start,
    children: [
      Text(title, style: Theme.of(context).textTheme.labelLarge),
      const SizedBox(height: 10),
      Wrap(
        spacing: 8,
        runSpacing: 8,
        children: tags.map((tag) {
          final isSelected = _isTagSelected(tag);
          return GestureDetector(
            onTap: () => _toggleTag(tag),
            child: AnimatedContainer(
              duration: const Duration(milliseconds: 200),
              padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 8),
              decoration: BoxDecoration(
                color: isSelected ? tag.color : tag.color.withValues(alpha: 0.1),
                borderRadius: BorderRadius.circular(20),
              ),
              child: Row(
                mainAxisSize: MainAxisSize.min,
                children: [
                  if (isSelected) ...[
                    const Icon(Icons.check, color: Colors.white, size: 14),
                    const SizedBox(width: 4),
                  ],
                  Text(
                    tag.name,
                    style: TextStyle(
                      fontSize: 13,
                      fontWeight: FontWeight.w500,
                      color: isSelected ? Colors.white : tag.color,
                    ),
                  ),
                ],
              ),
            ),
          );
        }).toList(),
      ),
    ],
  );
}

Then use as:

_buildTagSection('Thời gian', viewModel.timeTags),
const SizedBox(height: 20),
_buildTagSection('Trạng thái', viewModel.statusTags),
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/lib/features/tasks/view/screens/task_detail_screen.dart` around lines 204
- 309, The two tag sections duplicate the same UI; extract a reusable widget
method (e.g., _buildTagSection) and replace the repeated blocks with calls like
_buildTagSection('Thời gian', viewModel.timeTags) and _buildTagSection('Trạng
thái', viewModel.statusTags). The extracted method should use the existing
helpers _isTagSelected(tag) and _toggleTag(tag), render the Wrap of tags with
the AnimatedContainer / Icon / Text logic, and preserve spacing (include the
SizedBox between sections); also review whether viewModel.workTypeTags should be
displayed or document why it is omitted.
src/lib/features/tasks/viewmodel/task_viewmodel.dart (1)

177-183: Task tag updates are not persisted.

updateTaskTags() modifies the in-memory task list but doesn't persist changes. Unlike custom tags (saved to SharedPreferences), task modifications will be lost on app restart. Consider whether task persistence is planned separately or if this should be added here.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/lib/features/tasks/viewmodel/task_viewmodel.dart` around lines 177 - 183,
updateTaskTags currently only updates the in-memory _tasks list and calls
notifyListeners, so tag changes are lost on restart; modify updateTaskTags to
also persist the change by invoking the existing task persistence API (e.g.,
call the repository/storage method that saves a single task or the entire tasks
list) after assigning _tasks[index].tags = newTags, or implement a new private
helper like _saveTasksToStorage() and call it there; ensure you reference
updateTaskTags, _tasks, notifyListeners and the repository/storage save method
so the change is durable (use the same persistence mechanism used for custom
tags, e.g., SharedPreferences or the app’s TaskRepository).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/lib/features/tasks/viewmodel/task_viewmodel.dart`:
- Around line 102-104: The call to _saveCustomTags() in addCustomTag currently
isn't awaited causing potential silent failures; change addCustomTag(String
name) to be async (Future<String?>) and await _saveCustomTags(), catch and
handle any exceptions (e.g., log or surface an error and return null or rethrow)
before calling notifyListeners(), ensuring the save completes or errors are
handled so data isn't lost silently.

---

Nitpick comments:
In `@src/lib/features/tasks/view/screens/task_detail_screen.dart`:
- Around line 204-309: The two tag sections duplicate the same UI; extract a
reusable widget method (e.g., _buildTagSection) and replace the repeated blocks
with calls like _buildTagSection('Thời gian', viewModel.timeTags) and
_buildTagSection('Trạng thái', viewModel.statusTags). The extracted method
should use the existing helpers _isTagSelected(tag) and _toggleTag(tag), render
the Wrap of tags with the AnimatedContainer / Icon / Text logic, and preserve
spacing (include the SizedBox between sections); also review whether
viewModel.workTypeTags should be displayed or document why it is omitted.

In `@src/lib/features/tasks/viewmodel/task_viewmodel.dart`:
- Around line 177-183: updateTaskTags currently only updates the in-memory
_tasks list and calls notifyListeners, so tag changes are lost on restart;
modify updateTaskTags to also persist the change by invoking the existing task
persistence API (e.g., call the repository/storage method that saves a single
task or the entire tasks list) after assigning _tasks[index].tags = newTags, or
implement a new private helper like _saveTasksToStorage() and call it there;
ensure you reference updateTaskTags, _tasks, notifyListeners and the
repository/storage save method so the change is durable (use the same
persistence mechanism used for custom tags, e.g., SharedPreferences or the app’s
TaskRepository).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 58f70d19-2c83-48ff-b03a-5e231926e39f

📥 Commits

Reviewing files that changed from the base of the PR and between 4798f20 and ad276cf.

📒 Files selected for processing (3)
  • src/lib/features/tasks/view/screens/home_screen.dart
  • src/lib/features/tasks/view/screens/task_detail_screen.dart
  • src/lib/features/tasks/viewmodel/task_viewmodel.dart
💤 Files with no reviewable changes (1)
  • src/lib/features/tasks/view/screens/home_screen.dart

Comment on lines +102 to +104
_saveCustomTags();
notifyListeners();
return null;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

_saveCustomTags() is not awaited, risking silent data loss.

If the save operation fails, the user sees no error and notifyListeners() proceeds normally. On app restart, the tag will be missing. Consider awaiting the save and handling errors.

🛡️ Proposed fix to await save operation
-  _saveCustomTags();
-  notifyListeners();
-  return null;
+  await _saveCustomTags();
+  notifyListeners();
+  return null;

Note: This requires changing the method signature to Future<String?> addCustomTag(String name) async.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
_saveCustomTags();
notifyListeners();
return null;
await _saveCustomTags();
notifyListeners();
return null;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/lib/features/tasks/viewmodel/task_viewmodel.dart` around lines 102 - 104,
The call to _saveCustomTags() in addCustomTag currently isn't awaited causing
potential silent failures; change addCustomTag(String name) to be async
(Future<String?>) and await _saveCustomTags(), catch and handle any exceptions
(e.g., log or surface an error and return null or rethrow) before calling
notifyListeners(), ensuring the save completes or errors are handled so data
isn't lost silently.

@tqha1011 tqha1011 merged commit ae8e44c into main Apr 10, 2026
2 checks passed
tqha1011 added a commit that referenced this pull request Apr 18, 2026
* Feature/user profile (#30)

* feat(UserProfile): build screen UserProfile

# Conflicts:
#	src/lib/features/main/view/screens/main_screen.dart

* feat: switch dark/light theme

* fix: black color theme

* fix: black theme in statistics screen

* feat: add dark theme to auth screen

* feat: apply dark theme for bottom navigation bar

* feat(priority task):Implement priority and tag selection in task creation version 1 (#31)

* feat(task): implement priority and tag selection features in task creation

* Update README.md

* Update README.md

* Update README.md

---------

Co-authored-by: Tran Quang Ha <tranquangha2006@gmail.com>

* Update README.md (#32)

* feat: Priority selector and tag system verson2 (#33)

* feat(task): implement priority and tag selection features in task creation

* feat(tags): enhance tag management with custom tag creation and selection

* Update README.md

* Update README.md

* fix: error screen in main.dart (#34)

* Enhance authentication UI and implement Focus feature set (#35)

* feat(core): add auth layout template, custom textfield and colors

* feat(auth): implement viewmodels for auth flow (MVVM)

* feat(auth): build complete auth UI screens (Login, Register, OTP, Passwords)

* chore(main): set LoginView as initial route

* refactor(auth) : delete .gitkeep

* chore: update dependencies and pubspec.lock

* refactor(auth): optimize registration logic, timezone handling, and form validation

* feat(auth): update UI for login, registration, and forgot password screens

* feat(tasks): update task management UI and statistics screen

* chore: update main entry point and fix widget tests

* chore: ignore devtools_options.yaml

* chore: ignore devtools_options.yaml

* style(login) : rewrite title for login view

* feat(auth): configure android deep link for supabase oauth

* refactor(ui): add social login callbacks to auth layout template

* feat(auth): update oauth methods with redirect url and signout

* feat(auth): implement AuthGate using StreamBuilder for session tracking

* feat(viewmodel): add oauth logic and improve provider lifecycle

* refactor(ui): migrate LoginView to Provider pattern

* chore(main): set AuthGate as initial route and setup provider

* feat: implement full Focus feature set

- Added Pomodoro timer with Start/Reset/Skip logic.
- Integrated local Quick Notes with Pin/Delete functionality.
- Supported image attachments in notes using image_picker.
- Added Focus settings: time duration, vibration, and ringtones.

* fix (auth) : dispose TextEditingControllers to prevent memory leaks

* refactor (alarm ) : create off alarm button  when time out

* fix: apply CodeRabbit auto-fixes

Fixed 3 file(s) based on 4 unresolved review comments.

Co-authored-by: CodeRabbit <noreply@coderabbit.ai>

* fix(timer): prevent division by zero in progress calculation and sanitize negative settings input

* fix(timer): prevent division by zero in progress calculation and sanitize negative settings input

* fix(auth): unblock new-user login and add settings logout

* refactor(LoginScreen) : compact all items to fit in screen to help users interface easily

---------

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Co-authored-by: CodeRabbit <noreply@coderabbit.ai>

* build(deps)(deps): bump shared_preferences from 2.5.4 to 2.5.5 in /src (#36)

Bumps [shared_preferences](https://github.com/flutter/packages/tree/main/packages/shared_preferences) from 2.5.4 to 2.5.5.
- [Commits](https://github.com/flutter/packages/commits/shared_preferences-v2.5.5/packages/shared_preferences)

---
updated-dependencies:
- dependency-name: shared_preferences
  dependency-version: 2.5.5
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* Feature/user profile (#37)

* feat(UserProfile): build screen UserProfile

# Conflicts:
#	src/lib/features/main/view/screens/main_screen.dart

* feat: switch dark/light theme

* fix: black color theme

* fix: black theme in statistics screen

* feat: add dark theme to auth screen

* feat: apply dark theme for bottom navigation bar

* feat(RPC): update RPC to get data for heatmap

* feat(RPC): update new RPC to get data for heatmap

* feat: integrate chatbot assistant

* feat(chatbot): integrate create task, answer question for chatbot

* feat: remove mock data and get data tags and categories from supabase

* feat: integrate chatbot's ability to create task

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: Nguyễn Anh Kiệt <griffinbienhoa@gmail.com>
Co-authored-by: Nguyễn Lê Hoàng Hảo <nlhhao888@gmail.com>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Co-authored-by: CodeRabbit <noreply@coderabbit.ai>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
tqha1011 added a commit that referenced this pull request Apr 18, 2026
* feat: add create task screen and fix home screen provider errors

* Feature/user profile (#30)

* feat(UserProfile): build screen UserProfile

# Conflicts:
#	src/lib/features/main/view/screens/main_screen.dart

* feat: switch dark/light theme

* fix: black color theme

* fix: black theme in statistics screen

* feat: add dark theme to auth screen

* feat: apply dark theme for bottom navigation bar

* feat(priority task):Implement priority and tag selection in task creation version 1 (#31)

* feat(task): implement priority and tag selection features in task creation

* Update README.md

* Update README.md

* Update README.md

---------

Co-authored-by: Tran Quang Ha <tranquangha2006@gmail.com>

* Remove comment about main UI in home_screen.dart

Removed commented section about main UI.

* Update README.md (#32)

* feat: Priority selector and tag system verson2 (#33)

* feat(task): implement priority and tag selection features in task creation

* feat(tags): enhance tag management with custom tag creation and selection

* Update README.md

* Update README.md

* fix: error screen in main.dart (#34)

* Enhance authentication UI and implement Focus feature set (#35)

* feat(core): add auth layout template, custom textfield and colors

* feat(auth): implement viewmodels for auth flow (MVVM)

* feat(auth): build complete auth UI screens (Login, Register, OTP, Passwords)

* chore(main): set LoginView as initial route

* refactor(auth) : delete .gitkeep

* chore: update dependencies and pubspec.lock

* refactor(auth): optimize registration logic, timezone handling, and form validation

* feat(auth): update UI for login, registration, and forgot password screens

* feat(tasks): update task management UI and statistics screen

* chore: update main entry point and fix widget tests

* chore: ignore devtools_options.yaml

* chore: ignore devtools_options.yaml

* style(login) : rewrite title for login view

* feat(auth): configure android deep link for supabase oauth

* refactor(ui): add social login callbacks to auth layout template

* feat(auth): update oauth methods with redirect url and signout

* feat(auth): implement AuthGate using StreamBuilder for session tracking

* feat(viewmodel): add oauth logic and improve provider lifecycle

* refactor(ui): migrate LoginView to Provider pattern

* chore(main): set AuthGate as initial route and setup provider

* feat: implement full Focus feature set

- Added Pomodoro timer with Start/Reset/Skip logic.
- Integrated local Quick Notes with Pin/Delete functionality.
- Supported image attachments in notes using image_picker.
- Added Focus settings: time duration, vibration, and ringtones.

* fix (auth) : dispose TextEditingControllers to prevent memory leaks

* refactor (alarm ) : create off alarm button  when time out

* fix: apply CodeRabbit auto-fixes

Fixed 3 file(s) based on 4 unresolved review comments.

Co-authored-by: CodeRabbit <noreply@coderabbit.ai>

* fix(timer): prevent division by zero in progress calculation and sanitize negative settings input

* fix(timer): prevent division by zero in progress calculation and sanitize negative settings input

* fix(auth): unblock new-user login and add settings logout

* refactor(LoginScreen) : compact all items to fit in screen to help users interface easily

---------

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Co-authored-by: CodeRabbit <noreply@coderabbit.ai>

* build(deps)(deps): bump shared_preferences from 2.5.4 to 2.5.5 in /src (#36)

Bumps [shared_preferences](https://github.com/flutter/packages/tree/main/packages/shared_preferences) from 2.5.4 to 2.5.5.
- [Commits](https://github.com/flutter/packages/commits/shared_preferences-v2.5.5/packages/shared_preferences)

---
updated-dependencies:
- dependency-name: shared_preferences
  dependency-version: 2.5.5
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* Feature/user profile (#37)

* feat(UserProfile): build screen UserProfile

# Conflicts:
#	src/lib/features/main/view/screens/main_screen.dart

* feat: switch dark/light theme

* fix: black color theme

* fix: black theme in statistics screen

* feat: add dark theme to auth screen

* feat: apply dark theme for bottom navigation bar

* feat(RPC): update RPC to get data for heatmap

* feat(RPC): update new RPC to get data for heatmap

* feat: integrate chatbot assistant

* feat(chatbot): integrate create task, answer question for chatbot

* feat: remove mock data and get data tags and categories from supabase

* fixed codes, added save delete and create tasks and notes

* Delete .vs/TaskManagement.slnx/v18/.wsuo

* Delete .vs directory

* Delete .vscode directory

* Delete src/.gitignore

* Delete src/lib/features/auth/viewmodels/task_provider.dart

* Delete src/web_entrypoint.dart

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: Tran Quang Ha <tranquangha2006@gmail.com>
Co-authored-by: Nguyễn Anh Kiệt <griffinbienhoa@gmail.com>
Co-authored-by: Nguyễn Lê Hoàng Hảo <nlhhao888@gmail.com>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Co-authored-by: CodeRabbit <noreply@coderabbit.ai>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants