Skip to content

Feature/user profile#30

Merged
hoanghaoz merged 6 commits intomainfrom
feature/user-profile
Apr 9, 2026
Merged

Feature/user profile#30
hoanghaoz merged 6 commits intomainfrom
feature/user-profile

Conversation

@tqha1011
Copy link
Copy Markdown
Owner

@tqha1011 tqha1011 commented Apr 9, 2026

Summary by CodeRabbit

  • New Features

    • System-wide light/dark themes with persistent preference and quick appearance toggle
    • User Profile screen with avatar, stats (tasks & streak), appearance setting, notification toggle, and logout
  • Improvements

    • App-wide visual consistency: all screens and controls follow the active theme in real time
    • Bottom navigation updated to include the new profile destination and theme-aware styling
  • Chores

    • Backend RPC added to supply profile stats for the UI

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 9, 2026

📝 Walkthrough

Walkthrough

This PR centralizes theming (AppTheme + ThemeProvider), replaces hardcoded AppColors with theme-driven ColorScheme throughout the UI, adds a persisted theme provider, and introduces a full User Profile feature (model, service, viewmodel, views/widgets) plus a Supabase RPC for profile stats. Main app wiring updated to use Provider and AppTheme.

Changes

Cohort / File(s) Summary
Theme System Core
src/lib/core/theme/app_theme.dart, src/lib/core/theme/theme_provider.dart
Added AppTheme with lightTheme/darkTheme ThemeData and ThemeProvider ChangeNotifier persisting theme_mode via SharedPreferences and exposing updateTheme()/themeMode.
Auth UI & Layout
src/lib/core/theme/auth_layout_template.dart, src/lib/features/auth/otp_verification_view.dart, src/lib/features/auth/presentation/view/login_view.dart, src/lib/features/auth/presentation/view/forgot_password_view.dart, src/lib/features/auth/presentation/view/register_view.dart, src/lib/features/auth/presentation/view/new_password_view.dart
Removed AppColors; migrated to Theme.of(context).colorScheme; added dark-mode detection/conditional styling; threaded BuildContext into helpers; updated buttons, snackbars, headers, and social buttons to use themed colors.
Core Input Widgets
src/lib/core/theme/custom_text_field.dart, src/lib/core/widgets/custom_input_field.dart
Replaced fixed AppColors with theme-derived ColorScheme for text, hints, icons, fill, borders; adjusted radii and border widths; focused border now uses runtime theme primary color.
Main / App Wiring
src/lib/main.dart, src/lib/features/main/view/screens/main_screen.dart
Wired MultiProvider with ThemeProvider; MaterialApp uses AppTheme themes and themeProvider.themeMode; added UserProfileView to bottom nav; nav and bottom bar made theme-aware.
User Profile Feature (New)
src/lib/features/user/model/user_profile_model.dart, src/lib/features/user/service/user_service.dart, src/lib/features/user/viewmodel/user_profile_viewmodel.dart, src/lib/features/user/view/user_profile_view.dart, src/lib/features/user/view/widgets/profile_header.dart, src/lib/features/user/view/widgets/logout_button.dart, src/lib/features/user/view/widgets/settings_list_tile.dart, src/lib/features/user/view/widgets/settings_section.dart, src/lib/features/user/view/widgets/stat_card.dart
New model with JSON (fromJson/toJson/copyWith), UserService calling Supabase RPC, UserProfileViewModel managing loading, mock fallback, appearance sync with ThemeProvider, logout; UI and widgets (header, stat cards, settings tiles, logout) implemented and themed.
Note / Focus Feature
src/lib/features/note/view/focus_screen.dart, src/lib/features/note/view/focus_widget.dart
Replaced AppColors with ColorScheme values; added dark-mode gradients, conditional borders/backgrounds; refactored helpers to accept BuildContext (changed _buildTabBtn signature).
Tasks Feature UI
src/lib/features/tasks/view/screens/home_screen.dart, src/lib/features/tasks/view/screens/create_task_screen.dart, src/lib/features/tasks/view/screens/task_detail_screen.dart, src/lib/features/tasks/view/widgets/task_widgets.dart
Migrated task screens/widgets to use theme ColorScheme for backgrounds, chips, icons, badges; added dark-mode conditional borders and surface variants.
Statistics Feature UI
src/lib/features/statistics/view/screens/statistics_screen.dart, src/lib/features/statistics/view/widgets/statistics_widgets.dart
Removed AppColors usage, added theme-driven scaffold/container backgrounds, dark-mode gradient, and theme-aware card/text/icon/chart colors; removed unused helper.
Various Auth & Misc UI Tweaks
src/lib/core/theme/custom_text_field.dart, src/lib/features/auth/..., src/lib/features/tasks/..., src/lib/features/note/...
Multiple files converted from const to runtime widgets to allow theme-derived colors; snackbars updated to use theme colors; numerous style adjustments to follow ColorScheme.
Database Migration
supabase/migrations/20260409084009_create_user_profile_rpc.sql
Added get_user_profile_stats() PostgreSQL RPC returning JSON with name, avatarUrl, tasksDone, and streaks, computed from profile and task tables.

Sequence Diagram(s)

sequenceDiagram
    participant App as App Startup
    participant MP as MultiProvider
    participant TP as ThemeProvider
    participant SP as SharedPreferences
    participant MA as MaterialApp

    App->>MP: Wrap app with providers
    MP->>TP: Initialize ThemeProvider()
    TP->>SP: _loadThemeFromPrefs()
    SP-->>TP: Return saved "theme_mode"
    TP->>TP: Map string -> ThemeMode
    TP->>MP: notifyListeners()
    MP->>MA: Provide themeMode to MaterialApp
    MA->>MA: Apply AppTheme.lightTheme/darkTheme
    MA-->>App: Render UI with theme
Loading
sequenceDiagram
    participant UI as UserProfileView
    participant VM as UserProfileViewModel
    participant US as UserService
    participant SB as SupabaseClient
    participant DB as Database

    UI->>VM: loadProfile()
    VM->>VM: set isLoading=true
    VM->>US: fetchUserProfile()
    US->>SB: RPC get_user_profile_stats()
    SB->>DB: Query profile, tasks, streaks
    DB-->>SB: Return aggregated JSON
    SB-->>US: RPC response
    US->>US: Augment response with current user id
    US-->>VM: Return UserProfileModel
    VM->>VM: set _user, isLoading=false
    VM->>UI: notifyListeners -> UI rebuilds with profile
Loading
sequenceDiagram
    participant User as User
    participant UI as Settings Tile
    participant VM as UserProfileViewModel
    participant TP as ThemeProvider
    participant SP as SharedPreferences

    User->>UI: Tap appearance toggle
    UI->>VM: updateAppearance(context, newAppearance)
    VM->>VM: update local user.appearance
    VM->>TP: ThemeProvider.updateTheme(newAppearance)
    TP->>SP: _saveThemeToPrefs(newAppearance)
    SP-->>TP: persist OK
    TP->>TP: notifyListeners()
    TP-->>VM: app theme updated
    VM->>UI: notifyListeners -> UI reflects new theme
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Suggested reviewers

  • hoanghaoz

"🐰
I hopped through themes both dark and light,
Replaced hardcoded hues with palette bright.
Profiles now bloom, persisted and neat,
Saved appearances — a thematic treat!
Thump-thump — code spruced up, looking sweet."

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Feature/user profile' is directly related to the primary changes in the PR, which introduce a comprehensive user profile feature with new view components, viewmodel, service, and theme integration.
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 feature/user-profile

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.

# Conflicts:
#	src/lib/features/main/view/screens/main_screen.dart
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: 7

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/lib/features/statistics/view/widgets/statistics_widgets.dart (1)

262-263: ⚠️ Potential issue | 🟡 Minor

Potential TimeOfDay overflow when hour is 23.

If task.updatedAt.hour is 23, adding 1 results in hour 24, which is invalid for TimeOfDay (valid range: 0-23). This could cause unexpected behavior or crashes.

🐛 Proposed fix to handle hour wraparound
              startTime: TimeOfDay(hour: task.updatedAt.hour, minute: task.updatedAt.minute),
-              endTime: TimeOfDay(hour: task.updatedAt.hour + 1, minute: task.updatedAt.minute), // Add 1 hour just for display
+              endTime: TimeOfDay(hour: (task.updatedAt.hour + 1) % 24, minute: task.updatedAt.minute), // Add 1 hour with wraparound
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/lib/features/statistics/view/widgets/statistics_widgets.dart` around
lines 262 - 263, The endTime TimeOfDay construction can overflow when
task.updatedAt.hour == 23 (producing hour 24); update the endTime creation in
the widget that builds startTime/endTime (where startTime: TimeOfDay(...),
endTime: TimeOfDay(...)) to compute the end hour safely by wrapping (e.g.,
(task.updatedAt.hour + 1) % 24) or by deriving it from
task.updatedAt.add(Duration(hours:1)) before creating TimeOfDay so the hour
stays within 0-23 and no invalid TimeOfDay is produced.
🧹 Nitpick comments (21)
src/lib/core/theme/app_theme.dart (1)

71-71: Use a success-aligned tertiary in dark theme for semantic consistency.

Line 71 currently maps tertiary to mustard, but multiple screens use colorScheme.tertiary for success messages. In dark mode, this makes success look warning-like.

🎨 Suggested adjustment
-      tertiary: Color(0xFFD19900),         // Mustard Yellow (Similar to Edit Pencil Icon)
+      tertiary: Color(0xFF22C55E),         // Success green for consistency with success SnackBars
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/lib/core/theme/app_theme.dart` at line 71, The dark ColorScheme's
tertiary is currently set to mustard in app_theme.dart; change the dark theme
ColorScheme tertiary value to a success-aligned green (use the same
success/positive hue your app uses elsewhere) so that widgets referencing
colorScheme.tertiary render as success in dark mode; update the tertiary mapping
in the dark ColorScheme definition and adjust the inline comment to reflect
"Success (Green)" for clarity.
src/lib/features/statistics/view/screens/statistics_screen.dart (1)

179-183: Remove redundant color: null in TextStyle.

The explicit color: null in the TextStyle is unnecessary and can be removed for cleaner code.

♻️ Proposed cleanup
                          icon: Text(task.avatar ?? '📝', style: const TextStyle(
-                              fontSize: 24,
-                              color: null
-                            )
+                              fontSize: 24,
+                            ),
                          ),
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/lib/features/statistics/view/screens/statistics_screen.dart` around lines
179 - 183, Remove the redundant color: null from the TextStyle used in the icon
Text widget (Text(task.avatar ?? '📝', style: const TextStyle(...))) in
statistics_screen.dart; update the TextStyle passed to the icon so it only
specifies fontSize: 24 (and any other non-null style properties) and omit color
entirely to clean up the code.
src/lib/features/user/service/user_service.dart (1)

7-9: Misleading comment - this is not a simulation.

The comment "Simulates fetching user profile data with a fake network delay" appears to be leftover from development. The method actually performs a real RPC call.

📝 Proposed fix
-  /// Simulates fetching user profile data with a fake network delay
+  /// Fetches user profile data from Supabase RPC
   Future<UserProfileModel> fetchUserProfile() async {
-    // Mimic API call delay for smooth state switching
     try{
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/lib/features/user/service/user_service.dart` around lines 7 - 9, The doc
comment above fetchUserProfile in user_service.dart is misleading—replace or
remove "Simulates fetching user profile data with a fake network delay" since
fetchUserProfile performs a real RPC call; update the comment to accurately
describe that it performs a network/RPC request to retrieve the UserProfileModel
(including any retry/timeout behavior if applicable) or remove the simulation
wording entirely so the method comment reflects the real network operation.
src/lib/features/user/view/widgets/stat_card.dart (1)

16-17: Prefer null check over empty callback for onTap.

Using onTap ?? () {} creates an unnecessary empty callback that still triggers ripple effects even when no action is intended. Consider using a null check instead.

♻️ Proposed fix
       child: InkWell(
-        onTap: onTap ?? () {}, // Interactive ripple effect
+        onTap: onTap, // Ripple effect only when onTap is provided
         borderRadius: BorderRadius.circular(24),
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/lib/features/user/view/widgets/stat_card.dart` around lines 16 - 17, The
InkWell currently forces a no-op callback via "onTap ?? () {}", causing ripples
even when no action is intended; update the StatCard/Widget to pass the actual
onTap handler (or null) to InkWell instead of a default empty closure—i.e.,
remove the fallback empty callback and set InkWell's onTap to the onTap
parameter so the ripple is suppressed when onTap is null.
src/lib/features/auth/presentation/view/new_password_view.dart (1)

45-50: Consider using a semantic success color instead of colorScheme.tertiary.

Using colorScheme.tertiary for success feedback may not convey the intended semantic meaning across different themes. Consider defining a dedicated success color in the theme or using a consistent green color.

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

In `@src/lib/features/auth/presentation/view/new_password_view.dart` around lines
45 - 50, The SnackBar in ScaffoldMessenger.of(context).showSnackBar currently
uses Theme.of(context).colorScheme.tertiary for success feedback; replace this
with a semantic success color by adding a dedicated success color to your theme
(e.g., extend ColorScheme with a `success` property or add an AppColors
ThemeExtension with `success`) and then use
Theme.of(context).colorScheme.success (or
Theme.of(context).extension<AppColors>()!.success) as the SnackBar
backgroundColor so success feedback is theme-aware and consistent.
src/lib/core/theme/theme_provider.dart (1)

28-41: Theme persistence write is not awaited/handled.

updateTheme updates UI immediately but does not handle save failures from SharedPreferences. Consider making updateTheme async and handling persistence errors explicitly for better reliability.

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

In `@src/lib/core/theme/theme_provider.dart` around lines 28 - 41, Make
updateTheme await persistence: change updateTheme to async and await the
_saveThemeToPrefs(...) call so SharedPreferences failures are handled; wrap the
await in a try/catch around SharedPreferences.getInstance()/prefs.setString
(inside _saveThemeToPrefs or around its call) to log or surface errors (using
your logger or rethrow) and consider reverting _themeMode or notifying listeners
of failure if persistence fails; reference the updateTheme method,
_saveThemeToPrefs, _themeMode, _themeKey, notifyListeners and
SharedPreferences.getInstance when making these changes.
src/lib/features/auth/presentation/view/login_view.dart (1)

30-35: Consolidate repeated SnackBar creation into a helper.

The same SnackBar construction is duplicated across auth actions. A small helper (e.g. _showMessage(String text, Color bg)) will keep behavior consistent and reduce drift.

♻️ Example refactor
+ void _showMessage(BuildContext context, String message, Color bgColor) {
+   ScaffoldMessenger.of(context).showSnackBar(
+     SnackBar(content: Text(message), backgroundColor: bgColor),
+   );
+ }

- ScaffoldMessenger.of(context).showSnackBar(
-   SnackBar(
-     content: Text(error),
-     backgroundColor: Theme.of(context).colorScheme.error,
-   ),
- );
+ _showMessage(context, error, Theme.of(context).colorScheme.error);

Also applies to: 41-46, 55-60, 62-67

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

In `@src/lib/features/auth/presentation/view/login_view.dart` around lines 30 -
35, Repeated ScaffoldMessenger.of(context).showSnackBar(...) blocks in
login_view.dart should be extracted into a single helper (e.g., a private method
like _showMessage(String text, Color bg) or _showSnackBar) inside the LoginView
(or its State) so all auth actions call that helper; replace each duplicated
construction (the occurrences currently using
ScaffoldMessenger.of(context).showSnackBar with Text(error) and
Theme.of(context).colorScheme.error, plus the other similar blocks at the noted
locations) with calls to the helper to centralize message text and background
color handling and keep behavior consistent.
src/lib/features/main/view/screens/main_screen.dart (1)

53-55: Use theme tokens for nav container/selection backgrounds.

These hardcoded colors can drift from the active theme palette. Consider replacing them with colorScheme values to keep dark/light behavior centralized and consistent.

Also applies to: 85-89

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

In `@src/lib/features/main/view/screens/main_screen.dart` around lines 53 - 55,
Replace hardcoded background colors for the nav/selection containers with theme
color tokens so they follow the active colorScheme; locate the color assignment
in main_screen.dart (the ternary using isDark and const Color(0xFF1A2945)) and
swap the dark branch to a colorScheme value (for example
colorScheme.surfaceVariant, colorScheme.primaryContainer, or
colorScheme.background depending on intended contrast) and the light branch
already using Theme.of(context).colorScheme.surface should remain or be adjusted
to the matching token; make the same change for the other occurrences referenced
(lines around 85-89) so all nav/selection backgrounds use
Theme.of(context).colorScheme.* instead of hardcoded colors.
src/lib/features/tasks/view/screens/create_task_screen.dart (1)

90-93: ChoiceChip still has hardcoded light-mode color tokens.

Color(0xFFF1F7FD) bypasses the centralized theme system and can become inconsistent as themes evolve. Prefer colorScheme tokens for both background and border in all modes.

Also applies to: 102-106

🤖 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 90
- 93, The ChoiceChip backgroundColor is using a hardcoded Color(0xFFF1F7FD)
which bypasses the theme; update the widget(s) (where backgroundColor and
selectedColor are set) to use Theme.of(context).colorScheme tokens instead
(e.g., use colorScheme.surfaceVariant or colorScheme.background for unselected
background and colorScheme.primary/primaryContainer for selectedColor) and
replace any hardcoded border color with a colorScheme outline/outlineVariant
token; apply the same replacement to the other occurrences noted (lines 102-106)
so both light and dark branches consistently use Theme.of(context).colorScheme
values.
src/lib/features/user/model/user_profile_model.dart (2)

7-8: Consider making all fields immutable.

isNotificationEnabled and appearance are mutable (bool and String without final), while other fields are final. This breaks the immutability pattern and allows direct mutation (e.g., _user!.appearance = newAppearance in the viewmodel).

For consistency and to avoid unintended side effects, consider making these fields final and using copyWith for updates:

♻️ Proposed immutable approach
-  bool isNotificationEnabled;
-  String appearance;
+  final bool isNotificationEnabled;
+  final String appearance;

Then in the viewmodel, update via copyWith:

void toggleNotification(bool value) {
  if (_user != null) {
    _user = _user!.copyWith(isNotificationEnabled: value);
    notifyListeners();
  }
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/lib/features/user/model/user_profile_model.dart` around lines 7 - 8, The
fields isNotificationEnabled and appearance on the UserProfileModel are mutable
while other fields are final; make them immutable by declaring them final on the
UserProfileModel class, update the constructor to accept these final fields, and
ensure updates use the model's copyWith method (e.g., implement/extend copyWith
to accept isNotificationEnabled and appearance and return a new
UserProfileModel) so callers (like the viewmodel) call
copyWith(isNotificationEnabled: ..., appearance: ...) instead of mutating
properties directly.

46-56: toJson() asymmetry with fromJson() is not currently problematic but could be if used for API updates.

fromJson() accepts both camelCase and snake_case keys (e.g., tasksDone or tasks_done), but toJson() only outputs camelCase. However, toJson() is not currently called anywhere in the codebase—only fromJson() is used to deserialize server responses. If API update functionality is added later, ensure toJson() output matches the server's expected format or align the method with fromJson() for consistency.

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

In `@src/lib/features/user/model/user_profile_model.dart` around lines 46 - 56,
toJson() currently emits camelCase keys while fromJson() accepts both camelCase
and snake_case (e.g., tasksDone/tasks_done, avatarUrl/avatar_url,
isNotificationEnabled/is_notification_enabled), creating an asymmetry that could
break API updates; update the toJson() method (user_profile_model.toJson) to
produce the same key format the server expects (preferably snake_case if the API
uses that) or add an optional parameter (e.g., toJson({bool useSnakeCase =
true})) to emit snake_case equivalents (tasks_done, avatar_url,
is_notification_enabled) to align with fromJson() and ensure consistent
serialization for API updates.
src/lib/core/theme/auth_layout_template.dart (1)

233-234: Minor: Add const to EdgeInsets.

EdgeInsets.symmetric(horizontal: 16) can be const for a micro-optimization.

-              Padding(
-                padding: EdgeInsets.symmetric(horizontal: 16),
+              Padding(
+                padding: const EdgeInsets.symmetric(horizontal: 16),
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/lib/core/theme/auth_layout_template.dart` around lines 233 - 234, The
Padding widget in auth_layout_template.dart uses a non-const EdgeInsets
(Padding(padding: EdgeInsets.symmetric(horizontal: 16))), so change it to use a
compile-time constant by replacing EdgeInsets.symmetric with const
EdgeInsets.symmetric; update the Padding invocation (padding: const
EdgeInsets.symmetric(horizontal: 16)) to enable the const micro-optimization.
src/lib/features/user/viewmodel/user_profile_viewmodel.dart (4)

60-65: Direct mutation of model fields.

_user!.isNotificationEnabled = value directly mutates the model. If the model fields are made immutable (as suggested in the model review), this should use copyWith instead:

♻️ Proposed change using copyWith
 void toggleNotification(bool value) {
   if (_user != null) {
-    _user!.isNotificationEnabled = value;
+    _user = _user!.copyWith(isNotificationEnabled: value);
     notifyListeners();
   }
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/lib/features/user/viewmodel/user_profile_viewmodel.dart` around lines 60
- 65, toggleNotification currently mutates the model field directly
(_user!.isNotificationEnabled = value); change it to create and assign an
updated immutable user instance using the model's copyWith (e.g. _user =
_user!.copyWith(isNotificationEnabled: value)) while preserving null-safety,
then call notifyListeners() so the view updates; update the toggleNotification
method to use copyWith on the _user object instead of direct field assignment.

68-78: Same mutation pattern for appearance.

Consistent with the notification toggle, consider using copyWith for appearance updates if the model is made immutable.

♻️ Proposed change
 void updateAppearance(BuildContext context, String newAppearance) {
   if (_user != null) {
-    _user!.appearance = newAppearance;
+    _user = _user!.copyWith(appearance: newAppearance);
     _lastAppliedAppearance = newAppearance;
     notifyListeners();
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/lib/features/user/viewmodel/user_profile_viewmodel.dart` around lines 68
- 78, The updateAppearance method mutates the _user model directly; change it to
assign a new immutable instance using _user = _user!.copyWith(appearance:
newAppearance) (preserving null-check), update _lastAppliedAppearance, call
notifyListeners(), and then (if context.mounted) invoke
context.read<ThemeProvider>().updateTheme(newAppearance); ensure you reference
updateAppearance, _user, _lastAppliedAppearance, and ThemeProvider.updateTheme
when making the change.

37-39: Silent fallback to mock data on error may mask issues.

When loadProfile() fails, it silently falls back to mock data with only a debugPrint. In production, this could hide real authentication or network issues from users. Consider showing a user-facing error state or retry option when not using mock data.

💡 Suggested approach
   } catch (e) {
     debugPrint("Error loading profile: $e");
-    _user = _buildMockUser();
+    if (useMockData) {
+      _user = _buildMockUser();
+    } else {
+      _user = null; // Let UI show error state
+    }
   } finally {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/lib/features/user/viewmodel/user_profile_viewmodel.dart` around lines 37
- 39, The catch block in loadProfile currently swallows errors by debugPrint and
unconditionally assigning _user = _buildMockUser(), which hides real failures;
change the catch in loadProfile to (1) set a viewmodel error state (e.g.,
_loadError or _isError) and notify listeners instead of silently replacing real
data, (2) only use _buildMockUser() behind a dev/mock flag or test guard, and
(3) surface a user-facing error/retry path (or rethrow) so callers/UI can show
an error state and trigger retry logic rather than assuming a successful mock
profile.

92-92: Consider using English for debug messages.

Debug messages like "Đã clear session trên Supabase." and "Lỗi đăng xuất: $e" are in Vietnamese. For broader team accessibility and consistency, consider using English for code artifacts (comments, logs, debug messages), while keeping user-facing strings localized as needed.

Also applies to: 102-102, 105-105

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

In `@src/lib/features/user/viewmodel/user_profile_viewmodel.dart` at line 92, The
debug logs in user_profile_viewmodel.dart use Vietnamese strings (e.g., "Đã
clear session trên Supabase." and "Lỗi đăng xuất: $e"); update these debugPrint
/ logging calls inside the UserProfileViewModel (or the signOut/logout method)
to use clear English messages (for example "Cleared Supabase session." and
"Sign-out error: $e") and apply the same replacement to the other occurrences in
this file so all debug/log messages are consistently in English while leaving
any user-facing localized strings untouched.
src/lib/features/user/view/user_profile_view.dart (1)

141-144: Appearance toggle only supports 'Dark' and 'Light'.

The toggle logic hardcodes a binary switch between 'Dark' and 'Light'. If the system later supports a 'System' appearance mode (following platform preference), this logic would need adjustment. Consider whether this is intentional or if a more flexible approach is needed.

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

In `@src/lib/features/user/view/user_profile_view.dart` around lines 141 - 144,
The onTap currently toggles appearance by hardcoding user.appearance == 'Dark' ?
'Light' : 'Dark', which breaks if a third mode like 'System' is introduced;
update the toggle to derive the next mode from a canonical list or enum (e.g.,
List<String> appearanceModes or an Appearance enum) and select the next item
after user.appearance (wrapping to start) before calling
vm.updateAppearance(context, nextMode), or explicitly handle 'System' cases in
the vm.updateAppearance call so the UI cycles predictably; reference the onTap
closure, vm.updateAppearance, and user.appearance to implement this change.
src/lib/features/user/view/widgets/profile_header.dart (1)

71-73: Address the TODO for the edit profile action.

The edit button currently has no implementation. Consider tracking this as a follow-up task.

Would you like me to open a new issue to track the "Edit Profile" feature implementation?

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

In `@src/lib/features/user/view/widgets/profile_header.dart` around lines 71 - 73,
The onTap handler inside the ProfileHeader widget currently contains a TODO and
does nothing; implement the Edit Profile action by wiring the onTap to either
navigate to an EditProfileScreen or invoke a provided callback prop (e.g., pass
an onEditProfile callback into ProfileHeader) so the UI triggers the actual edit
flow; update the constructor/signature of ProfileHeader to accept an
onEditProfile callback if not present, replace the TODO in the onTap with a call
to Navigator.push to the editable screen or call widget.onEditProfile(), and add
a unit/widget test to assert the tap triggers the navigation/callback; if you
prefer backlog tracking, create a follow-up issue titled "Implement Edit Profile
action for ProfileHeader" to track remaining UX/data integration work.
src/lib/features/note/view/focus_screen.dart (2)

78-81: Inconsistent color usage for "Nghỉ ngắn" value.

The Pomodoro time value uses Theme.of(context).colorScheme.primary (line 55), but the short break ("Nghỉ ngắn") value at line 81 uses a hardcoded Colors.orange. Consider using a consistent theming approach, perhaps defining a secondary accent color in the theme for break-related UI elements.

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

In `@src/lib/features/note/view/focus_screen.dart` around lines 78 - 81, The "Nghỉ
ngắn" value Text widget currently uses a hardcoded Colors.orange; update that
Text's style to use the app theme (e.g.,
Theme.of(context).colorScheme.secondary) so it matches the Pomodoro value
theming and remove the const TextStyle to allow dynamic theme colors; locate the
Text('${currentBreak.toInt()} phút', ...) in focus_screen.dart and replace the
hardcoded color with Theme.of(context).colorScheme.secondary (or another
designated break-accent color).

255-261: Unusual widget tree closing structure.

The closing structure here is syntactically correct but the formatting is unconventional. The child of Container ends at line 258, but the closing parentheses cascade in a confusing way. Consider reformatting for clarity.

📝 Suggested formatting
-          ),
-        ),
-      ),
-    )
-    );
+            ),
+          ),
+        ),
+      ),
+    );
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/lib/features/note/view/focus_screen.dart` around lines 255 - 261, The
widget tree in the build method ending around the Container with its child is
syntactically correct but uses an unconventional cascading of closing
parentheses that makes the structure hard to read; reformat the trailing lines
so each widget's closing parenthesis/bracket aligns with its opening widget (for
example align the closing parens for Container, child, and surrounding widgets
like Column/Scaffold/Widget returned by build), remove the extra blank line, and
ensure consistent indentation for the Container and its child to make the
hierarchy clear (locate the Container instance in focus_screen.dart around the
end of the build method and adjust its closing lines).
src/lib/features/note/view/focus_widget.dart (1)

13-22: Consider centralizing dark mode color constants.

Multiple hard-coded hex colors for dark mode are scattered throughout this file (e.g., Color(0xFF132544), Color(0xFF2A3D5D), Color(0xFF1A2945), Color(0xFF12223D)). These same values appear in other files like focus_screen.dart and auth_layout_template.dart.

Centralizing these in the theme definition or a dedicated dark-mode palette would improve maintainability and ensure visual consistency across the app.

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

In `@src/lib/features/note/view/focus_widget.dart` around lines 13 - 22, Multiple
hard-coded dark-mode hex colors (e.g., Color(0xFF132544), Color(0xFF2A3D5D),
Color(0xFF1A2945), Color(0xFF12223D)) are repeated across focus_widget.dart,
focus_screen.dart and auth_layout_template.dart; create a centralized palette
(e.g., DarkPalette or extend ThemeData/colorScheme) and replace those literal
Color(...) usages in the widgets (the Container decoration in focus_widget.dart
and analogous uses in focus_screen.dart/auth_layout_template.dart) with
references to the new constants or theme properties
(Theme.of(context).colorScheme.<name> or AppColors.dark.<name>) so all
components pull colors from one source for consistency and maintainability.
🤖 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/core/theme/theme_provider.dart`:
- Around line 8-14: The provider currently defaults _themeMode to
ThemeMode.light and calls async _loadThemeFromPrefs() in the constructor, which
can cause a wrong theme on first frame; change ThemeProvider to set the theme
synchronously by either (A) adding a static async factory ThemeProvider.load()
that awaits _loadThemeFromPrefs() (or reads prefs synchronously if available)
and returns an instance with _themeMode set before use, or (B) add a constructor
parameter initialThemeMode (e.g., ThemeProvider({ThemeMode? initial})) so
callers can pass a preloaded value before runApp; adjust _loadThemeFromPrefs to
return the resolved ThemeMode and ensure themeMode and any isInitialized/ready
signal are set before the provider is provided to the widget tree (references:
ThemeProvider, _themeMode, themeMode, _loadThemeFromPrefs).

In `@src/lib/features/auth/presentation/view/otp_verification_view.dart`:
- Around line 66-67: The label Text('Nhập mã 8 số') is inconsistent with the OTP
input which renders 6 boxes; fix by making the displayed length and the input
widget agree: either change the text to 'Nhập mã 6 số' or update the OTP input's
length/box count to 8. Locate and update the Text widget text and the OTP input
widget configuration (e.g. the function/build method that renders the OTP boxes
such as the OTP input widget or any length constant used to build the boxes) so
both use the same digit count and ensure any validation/verification logic (OTP
length checks) uses the same value.

In `@src/lib/features/main/view/screens/main_screen.dart`:
- Around line 35-36: The UserProfile tab is hard-coded to use mock data; change
the UserProfileViewModel instantiation so useMockData is false (or wire it to an
environment/config flag) instead of true in the provider creation where
UserProfileViewModel(useMockData: true)..loadProfile() is called; ensure the
provider still calls loadProfile() and that UserProfileView continues to consume
the real-viewmodel instance so backend profile retrieval occurs in normal
builds.

In `@src/lib/features/user/service/user_service.dart`:
- Around line 8-17: The code in fetchUserProfile uses a non-null assertion on
_supabase.auth.currentUser!.id which can crash if the session is missing; update
fetchUserProfile to guard access to the current user (e.g., final user =
_supabase.auth.currentUser) and handle user == null explicitly (throw a clear
authentication Exception or return an error result) before setting
response['id'], keeping the existing _supabase.rpc('get_user_profile_stats')
call and returning UserProfileModel.fromJson(response) when user is present.

In `@src/lib/features/user/view/user_profile_view.dart`:
- Around line 57-61: The Builder's build currently calls
vm.syncThemeWithProfile(innerContext) causing a side-effect during build; remove
that direct call and instead defer the sync to a lifecycle or post-frame
callback (either convert the widget to a StatefulWidget and call
vm.syncThemeWithProfile from initState / didChangeDependencies, or inside the
Builder use WidgetsBinding.instance.addPostFrameCallback and check
innerContext.mounted before calling vm.syncThemeWithProfile), keeping the return
of _buildProfileContent(innerContext, vm) unchanged.

In `@src/lib/main.dart`:
- Line 58: The app currently sets MaterialApp's home to MainScreen which
bypasses authentication; change the entry point to use AuthGate instead (e.g.,
set home to an AuthGate widget that performs session/auth checks and navigates
to MainScreen when authenticated) and ensure any necessary import for AuthGate
is added and that AuthGate handles routing to MainScreen (or pushes MainScreen)
after successful auth; update references in main.dart where home: const
MainScreen() appears to home: const AuthGate() so the auth/session gating is
restored.

In `@supabase/migrations/20260409084009_create_user_profile_rpc.sql`:
- Around line 31-33: The streak date extraction currently forces UTC with
DATE(updated_at AT TIME ZONE 'UTC') which misaligns local day boundaries; change
the query that builds task_date (the SELECT DISTINCT ... FROM public.task WHERE
profile_id = v_user_id AND status = 1) to apply the user's timezone instead of
'UTC' (e.g., use DATE(updated_at AT TIME ZONE COALESCE(profile.timezone,
v_user_timezone, 'UTC')) or pass a v_user_timezone parameter and use
DATE(updated_at AT TIME ZONE v_user_timezone) so task boundaries match the
user's local day), or if no timezone is available add a clear comment/docstring
in the procedure indicating the UTC limitation and ensure profile.timezone is
collected on profile creation and referenced by the RPC.

---

Outside diff comments:
In `@src/lib/features/statistics/view/widgets/statistics_widgets.dart`:
- Around line 262-263: The endTime TimeOfDay construction can overflow when
task.updatedAt.hour == 23 (producing hour 24); update the endTime creation in
the widget that builds startTime/endTime (where startTime: TimeOfDay(...),
endTime: TimeOfDay(...)) to compute the end hour safely by wrapping (e.g.,
(task.updatedAt.hour + 1) % 24) or by deriving it from
task.updatedAt.add(Duration(hours:1)) before creating TimeOfDay so the hour
stays within 0-23 and no invalid TimeOfDay is produced.

---

Nitpick comments:
In `@src/lib/core/theme/app_theme.dart`:
- Line 71: The dark ColorScheme's tertiary is currently set to mustard in
app_theme.dart; change the dark theme ColorScheme tertiary value to a
success-aligned green (use the same success/positive hue your app uses
elsewhere) so that widgets referencing colorScheme.tertiary render as success in
dark mode; update the tertiary mapping in the dark ColorScheme definition and
adjust the inline comment to reflect "Success (Green)" for clarity.

In `@src/lib/core/theme/auth_layout_template.dart`:
- Around line 233-234: The Padding widget in auth_layout_template.dart uses a
non-const EdgeInsets (Padding(padding: EdgeInsets.symmetric(horizontal: 16))),
so change it to use a compile-time constant by replacing EdgeInsets.symmetric
with const EdgeInsets.symmetric; update the Padding invocation (padding: const
EdgeInsets.symmetric(horizontal: 16)) to enable the const micro-optimization.

In `@src/lib/core/theme/theme_provider.dart`:
- Around line 28-41: Make updateTheme await persistence: change updateTheme to
async and await the _saveThemeToPrefs(...) call so SharedPreferences failures
are handled; wrap the await in a try/catch around
SharedPreferences.getInstance()/prefs.setString (inside _saveThemeToPrefs or
around its call) to log or surface errors (using your logger or rethrow) and
consider reverting _themeMode or notifying listeners of failure if persistence
fails; reference the updateTheme method, _saveThemeToPrefs, _themeMode,
_themeKey, notifyListeners and SharedPreferences.getInstance when making these
changes.

In `@src/lib/features/auth/presentation/view/login_view.dart`:
- Around line 30-35: Repeated ScaffoldMessenger.of(context).showSnackBar(...)
blocks in login_view.dart should be extracted into a single helper (e.g., a
private method like _showMessage(String text, Color bg) or _showSnackBar) inside
the LoginView (or its State) so all auth actions call that helper; replace each
duplicated construction (the occurrences currently using
ScaffoldMessenger.of(context).showSnackBar with Text(error) and
Theme.of(context).colorScheme.error, plus the other similar blocks at the noted
locations) with calls to the helper to centralize message text and background
color handling and keep behavior consistent.

In `@src/lib/features/auth/presentation/view/new_password_view.dart`:
- Around line 45-50: The SnackBar in ScaffoldMessenger.of(context).showSnackBar
currently uses Theme.of(context).colorScheme.tertiary for success feedback;
replace this with a semantic success color by adding a dedicated success color
to your theme (e.g., extend ColorScheme with a `success` property or add an
AppColors ThemeExtension with `success`) and then use
Theme.of(context).colorScheme.success (or
Theme.of(context).extension<AppColors>()!.success) as the SnackBar
backgroundColor so success feedback is theme-aware and consistent.

In `@src/lib/features/main/view/screens/main_screen.dart`:
- Around line 53-55: Replace hardcoded background colors for the nav/selection
containers with theme color tokens so they follow the active colorScheme; locate
the color assignment in main_screen.dart (the ternary using isDark and const
Color(0xFF1A2945)) and swap the dark branch to a colorScheme value (for example
colorScheme.surfaceVariant, colorScheme.primaryContainer, or
colorScheme.background depending on intended contrast) and the light branch
already using Theme.of(context).colorScheme.surface should remain or be adjusted
to the matching token; make the same change for the other occurrences referenced
(lines around 85-89) so all nav/selection backgrounds use
Theme.of(context).colorScheme.* instead of hardcoded colors.

In `@src/lib/features/note/view/focus_screen.dart`:
- Around line 78-81: The "Nghỉ ngắn" value Text widget currently uses a
hardcoded Colors.orange; update that Text's style to use the app theme (e.g.,
Theme.of(context).colorScheme.secondary) so it matches the Pomodoro value
theming and remove the const TextStyle to allow dynamic theme colors; locate the
Text('${currentBreak.toInt()} phút', ...) in focus_screen.dart and replace the
hardcoded color with Theme.of(context).colorScheme.secondary (or another
designated break-accent color).
- Around line 255-261: The widget tree in the build method ending around the
Container with its child is syntactically correct but uses an unconventional
cascading of closing parentheses that makes the structure hard to read; reformat
the trailing lines so each widget's closing parenthesis/bracket aligns with its
opening widget (for example align the closing parens for Container, child, and
surrounding widgets like Column/Scaffold/Widget returned by build), remove the
extra blank line, and ensure consistent indentation for the Container and its
child to make the hierarchy clear (locate the Container instance in
focus_screen.dart around the end of the build method and adjust its closing
lines).

In `@src/lib/features/note/view/focus_widget.dart`:
- Around line 13-22: Multiple hard-coded dark-mode hex colors (e.g.,
Color(0xFF132544), Color(0xFF2A3D5D), Color(0xFF1A2945), Color(0xFF12223D)) are
repeated across focus_widget.dart, focus_screen.dart and
auth_layout_template.dart; create a centralized palette (e.g., DarkPalette or
extend ThemeData/colorScheme) and replace those literal Color(...) usages in the
widgets (the Container decoration in focus_widget.dart and analogous uses in
focus_screen.dart/auth_layout_template.dart) with references to the new
constants or theme properties (Theme.of(context).colorScheme.<name> or
AppColors.dark.<name>) so all components pull colors from one source for
consistency and maintainability.

In `@src/lib/features/statistics/view/screens/statistics_screen.dart`:
- Around line 179-183: Remove the redundant color: null from the TextStyle used
in the icon Text widget (Text(task.avatar ?? '📝', style: const TextStyle(...)))
in statistics_screen.dart; update the TextStyle passed to the icon so it only
specifies fontSize: 24 (and any other non-null style properties) and omit color
entirely to clean up the code.

In `@src/lib/features/tasks/view/screens/create_task_screen.dart`:
- Around line 90-93: The ChoiceChip backgroundColor is using a hardcoded
Color(0xFFF1F7FD) which bypasses the theme; update the widget(s) (where
backgroundColor and selectedColor are set) to use Theme.of(context).colorScheme
tokens instead (e.g., use colorScheme.surfaceVariant or colorScheme.background
for unselected background and colorScheme.primary/primaryContainer for
selectedColor) and replace any hardcoded border color with a colorScheme
outline/outlineVariant token; apply the same replacement to the other
occurrences noted (lines 102-106) so both light and dark branches consistently
use Theme.of(context).colorScheme values.

In `@src/lib/features/user/model/user_profile_model.dart`:
- Around line 7-8: The fields isNotificationEnabled and appearance on the
UserProfileModel are mutable while other fields are final; make them immutable
by declaring them final on the UserProfileModel class, update the constructor to
accept these final fields, and ensure updates use the model's copyWith method
(e.g., implement/extend copyWith to accept isNotificationEnabled and appearance
and return a new UserProfileModel) so callers (like the viewmodel) call
copyWith(isNotificationEnabled: ..., appearance: ...) instead of mutating
properties directly.
- Around line 46-56: toJson() currently emits camelCase keys while fromJson()
accepts both camelCase and snake_case (e.g., tasksDone/tasks_done,
avatarUrl/avatar_url, isNotificationEnabled/is_notification_enabled), creating
an asymmetry that could break API updates; update the toJson() method
(user_profile_model.toJson) to produce the same key format the server expects
(preferably snake_case if the API uses that) or add an optional parameter (e.g.,
toJson({bool useSnakeCase = true})) to emit snake_case equivalents (tasks_done,
avatar_url, is_notification_enabled) to align with fromJson() and ensure
consistent serialization for API updates.

In `@src/lib/features/user/service/user_service.dart`:
- Around line 7-9: The doc comment above fetchUserProfile in user_service.dart
is misleading—replace or remove "Simulates fetching user profile data with a
fake network delay" since fetchUserProfile performs a real RPC call; update the
comment to accurately describe that it performs a network/RPC request to
retrieve the UserProfileModel (including any retry/timeout behavior if
applicable) or remove the simulation wording entirely so the method comment
reflects the real network operation.

In `@src/lib/features/user/view/user_profile_view.dart`:
- Around line 141-144: The onTap currently toggles appearance by hardcoding
user.appearance == 'Dark' ? 'Light' : 'Dark', which breaks if a third mode like
'System' is introduced; update the toggle to derive the next mode from a
canonical list or enum (e.g., List<String> appearanceModes or an Appearance
enum) and select the next item after user.appearance (wrapping to start) before
calling vm.updateAppearance(context, nextMode), or explicitly handle 'System'
cases in the vm.updateAppearance call so the UI cycles predictably; reference
the onTap closure, vm.updateAppearance, and user.appearance to implement this
change.

In `@src/lib/features/user/view/widgets/profile_header.dart`:
- Around line 71-73: The onTap handler inside the ProfileHeader widget currently
contains a TODO and does nothing; implement the Edit Profile action by wiring
the onTap to either navigate to an EditProfileScreen or invoke a provided
callback prop (e.g., pass an onEditProfile callback into ProfileHeader) so the
UI triggers the actual edit flow; update the constructor/signature of
ProfileHeader to accept an onEditProfile callback if not present, replace the
TODO in the onTap with a call to Navigator.push to the editable screen or call
widget.onEditProfile(), and add a unit/widget test to assert the tap triggers
the navigation/callback; if you prefer backlog tracking, create a follow-up
issue titled "Implement Edit Profile action for ProfileHeader" to track
remaining UX/data integration work.

In `@src/lib/features/user/view/widgets/stat_card.dart`:
- Around line 16-17: The InkWell currently forces a no-op callback via "onTap ??
() {}", causing ripples even when no action is intended; update the
StatCard/Widget to pass the actual onTap handler (or null) to InkWell instead of
a default empty closure—i.e., remove the fallback empty callback and set
InkWell's onTap to the onTap parameter so the ripple is suppressed when onTap is
null.

In `@src/lib/features/user/viewmodel/user_profile_viewmodel.dart`:
- Around line 60-65: toggleNotification currently mutates the model field
directly (_user!.isNotificationEnabled = value); change it to create and assign
an updated immutable user instance using the model's copyWith (e.g. _user =
_user!.copyWith(isNotificationEnabled: value)) while preserving null-safety,
then call notifyListeners() so the view updates; update the toggleNotification
method to use copyWith on the _user object instead of direct field assignment.
- Around line 68-78: The updateAppearance method mutates the _user model
directly; change it to assign a new immutable instance using _user =
_user!.copyWith(appearance: newAppearance) (preserving null-check), update
_lastAppliedAppearance, call notifyListeners(), and then (if context.mounted)
invoke context.read<ThemeProvider>().updateTheme(newAppearance); ensure you
reference updateAppearance, _user, _lastAppliedAppearance, and
ThemeProvider.updateTheme when making the change.
- Around line 37-39: The catch block in loadProfile currently swallows errors by
debugPrint and unconditionally assigning _user = _buildMockUser(), which hides
real failures; change the catch in loadProfile to (1) set a viewmodel error
state (e.g., _loadError or _isError) and notify listeners instead of silently
replacing real data, (2) only use _buildMockUser() behind a dev/mock flag or
test guard, and (3) surface a user-facing error/retry path (or rethrow) so
callers/UI can show an error state and trigger retry logic rather than assuming
a successful mock profile.
- Line 92: The debug logs in user_profile_viewmodel.dart use Vietnamese strings
(e.g., "Đã clear session trên Supabase." and "Lỗi đăng xuất: $e"); update these
debugPrint / logging calls inside the UserProfileViewModel (or the
signOut/logout method) to use clear English messages (for example "Cleared
Supabase session." and "Sign-out error: $e") and apply the same replacement to
the other occurrences in this file so all debug/log messages are consistently in
English while leaving any user-facing localized strings untouched.
🪄 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: c84988a3-c35b-4efe-ab77-245825961110

📥 Commits

Reviewing files that changed from the base of the PR and between bb72c59 and 1f414b9.

⛔ Files ignored due to path filters (1)
  • src/pubspec.lock is excluded by !**/*.lock
📒 Files selected for processing (31)
  • src/lib/core/theme/app_theme.dart
  • src/lib/core/theme/auth_layout_template.dart
  • src/lib/core/theme/custom_text_field.dart
  • src/lib/core/theme/theme_provider.dart
  • src/lib/core/widgets/custom_input_field.dart
  • src/lib/features/auth/otp_verification_view.dart
  • src/lib/features/auth/presentation/view/forgot_password_view.dart
  • src/lib/features/auth/presentation/view/login_view.dart
  • src/lib/features/auth/presentation/view/new_password_view.dart
  • src/lib/features/auth/presentation/view/otp_verification_view.dart
  • src/lib/features/auth/presentation/view/register_view.dart
  • src/lib/features/main/view/screens/main_screen.dart
  • src/lib/features/note/view/focus_screen.dart
  • src/lib/features/note/view/focus_widget.dart
  • src/lib/features/statistics/view/screens/statistics_screen.dart
  • src/lib/features/statistics/view/widgets/statistics_widgets.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/task_widgets.dart
  • src/lib/features/user/model/user_profile_model.dart
  • src/lib/features/user/service/user_service.dart
  • src/lib/features/user/view/user_profile_view.dart
  • src/lib/features/user/view/widgets/logout_button.dart
  • src/lib/features/user/view/widgets/profile_header.dart
  • src/lib/features/user/view/widgets/settings_list_tile.dart
  • src/lib/features/user/view/widgets/settings_section.dart
  • src/lib/features/user/view/widgets/stat_card.dart
  • src/lib/features/user/viewmodel/user_profile_viewmodel.dart
  • src/lib/main.dart
  • supabase/migrations/20260409084009_create_user_profile_rpc.sql

Comment on lines +8 to +14
ThemeMode _themeMode = ThemeMode.light;

ThemeMode get themeMode => _themeMode;

ThemeProvider() {
_loadThemeFromPrefs();
}
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

Initial theme can be wrong on first frame due to async constructor load.

Defaulting to ThemeMode.light and loading prefs later can briefly show the wrong theme at app startup. Preload theme before runApp (or initialize with a non-forcing default and gate first paint) to avoid flicker.

Also applies to: 44-56

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

In `@src/lib/core/theme/theme_provider.dart` around lines 8 - 14, The provider
currently defaults _themeMode to ThemeMode.light and calls async
_loadThemeFromPrefs() in the constructor, which can cause a wrong theme on first
frame; change ThemeProvider to set the theme synchronously by either (A) adding
a static async factory ThemeProvider.load() that awaits _loadThemeFromPrefs()
(or reads prefs synchronously if available) and returns an instance with
_themeMode set before use, or (B) add a constructor parameter initialThemeMode
(e.g., ThemeProvider({ThemeMode? initial})) so callers can pass a preloaded
value before runApp; adjust _loadThemeFromPrefs to return the resolved ThemeMode
and ensure themeMode and any isInitialized/ready signal are set before the
provider is provided to the widget tree (references: ThemeProvider, _themeMode,
themeMode, _loadThemeFromPrefs).

Comment thread src/lib/features/auth/presentation/view/otp_verification_view.dart
Comment on lines +35 to +36
create: (_) => UserProfileViewModel(useMockData: true)..loadProfile(),
child: const UserProfileView(),
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

Profile tab is permanently pinned to mock data.

UserProfileViewModel(useMockData: true) prevents real backend profile retrieval in normal app flow. This should be false (or environment-gated) before release.

✅ Suggested fix
- create: (_) => UserProfileViewModel(useMockData: true)..loadProfile(),
+ create: (_) => UserProfileViewModel(useMockData: false)..loadProfile(),
📝 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
create: (_) => UserProfileViewModel(useMockData: true)..loadProfile(),
child: const UserProfileView(),
create: (_) => UserProfileViewModel(useMockData: false)..loadProfile(),
child: const UserProfileView(),
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/lib/features/main/view/screens/main_screen.dart` around lines 35 - 36,
The UserProfile tab is hard-coded to use mock data; change the
UserProfileViewModel instantiation so useMockData is false (or wire it to an
environment/config flag) instead of true in the provider creation where
UserProfileViewModel(useMockData: true)..loadProfile() is called; ensure the
provider still calls loadProfile() and that UserProfileView continues to consume
the real-viewmodel instance so backend profile retrieval occurs in normal
builds.

Comment on lines +8 to +17
Future<UserProfileModel> fetchUserProfile() async {
// Mimic API call delay for smooth state switching
try{
final response = await _supabase.rpc('get_user_profile_stats');
response['id'] = _supabase.auth.currentUser!.id;
return UserProfileModel.fromJson(response);
}
catch(e){
throw Exception("Failed to fetch user profile: $e");
}
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

Potential crash with non-null assertion on currentUser.

The _supabase.auth.currentUser!.id assertion will throw if the user session has expired or is not authenticated. While the RPC checks auth at the database level, this client-side crash would occur before the RPC call completes.

🐛 Proposed fix with null check
   Future<UserProfileModel> fetchUserProfile() async {
-    // Mimic API call delay for smooth state switching
     try{
+      final currentUser = _supabase.auth.currentUser;
+      if (currentUser == null) {
+        throw Exception("User not authenticated");
+      }
       final response = await _supabase.rpc('get_user_profile_stats');
-      response['id'] = _supabase.auth.currentUser!.id;
+      response['id'] = currentUser.id;
       return UserProfileModel.fromJson(response);
     }
     catch(e){
       throw Exception("Failed to fetch user profile: $e");
     }
   }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/lib/features/user/service/user_service.dart` around lines 8 - 17, The
code in fetchUserProfile uses a non-null assertion on
_supabase.auth.currentUser!.id which can crash if the session is missing; update
fetchUserProfile to guard access to the current user (e.g., final user =
_supabase.auth.currentUser) and handle user == null explicitly (throw a clear
authentication Exception or return an error result) before setting
response['id'], keeping the existing _supabase.rpc('get_user_profile_stats')
call and returning UserProfileModel.fromJson(response) when user is present.

Comment on lines +57 to +61
: Builder(
builder: (innerContext) {
vm.syncThemeWithProfile(innerContext);
return _buildProfileContent(innerContext, vm);
},
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

Avoid side effects during the build phase.

Calling vm.syncThemeWithProfile(innerContext) inside the Builder's build method triggers theme updates during widget construction, which can cause unnecessary rebuilds or unpredictable behavior. Side effects should be handled in lifecycle methods or callbacks, not during build.

Consider moving this to initState (by converting to StatefulWidget) or using WidgetsBinding.instance.addPostFrameCallback to defer the sync.

🛠️ Suggested approach using post-frame callback
: Builder(
    builder: (innerContext) {
      WidgetsBinding.instance.addPostFrameCallback((_) {
        if (innerContext.mounted) {
          vm.syncThemeWithProfile(innerContext);
        }
      });
      return _buildProfileContent(innerContext, vm);
    },
  ),
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/lib/features/user/view/user_profile_view.dart` around lines 57 - 61, The
Builder's build currently calls vm.syncThemeWithProfile(innerContext) causing a
side-effect during build; remove that direct call and instead defer the sync to
a lifecycle or post-frame callback (either convert the widget to a
StatefulWidget and call vm.syncThemeWithProfile from initState /
didChangeDependencies, or inside the Builder use
WidgetsBinding.instance.addPostFrameCallback and check innerContext.mounted
before calling vm.syncThemeWithProfile), keeping the return of
_buildProfileContent(innerContext, vm) unchanged.

Comment thread src/lib/main.dart
Comment on lines +31 to +33
SELECT DISTINCT DATE(updated_at AT TIME ZONE 'UTC') AS task_date
FROM public.task
WHERE profile_id = v_user_id AND status = 1
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

🧩 Analysis chain

🏁 Script executed:

# First, locate and examine the migration file
fd -type f "20260409084009_create_user_profile_rpc.sql" -x cat {}

Repository: tqha1011/TaskManagement

Length of output: 237


🏁 Script executed:

# Check the full migration file content to see all references to timezone
fd -type f "*create_user_profile_rpc*" -x wc -l {}

Repository: tqha1011/TaskManagement

Length of output: 237


🏁 Script executed:

# Search for timezone-related columns in profile/user tables across migrations
fd -type f "*.sql" -path "*/migrations/*" -x rg -l "timezone|time zone" {}

Repository: tqha1011/TaskManagement

Length of output: 237


🏁 Script executed:

# Look for user/profile table definitions to check for timezone storage
fd -type f "*.sql" -path "*/migrations/*" -x rg "CREATE TABLE.*profile|CREATE TABLE.*user" -A 20 {}

Repository: tqha1011/TaskManagement

Length of output: 237


🏁 Script executed:

# Find the migration file
find . -name "*20260409084009*.sql" -o -name "*create_user_profile_rpc*"

Repository: tqha1011/TaskManagement

Length of output: 131


🏁 Script executed:

# List migration files to verify structure
ls -la supabase/migrations/ 2>/dev/null | head -20

Repository: tqha1011/TaskManagement

Length of output: 529


🏁 Script executed:

# Search for the specific migration file content
find . -name "*.sql" | grep -i "user_profile_rpc"

Repository: tqha1011/TaskManagement

Length of output: 131


🏁 Script executed:

# Read the migration file content with line numbers
cat -n ./supabase/migrations/20260409084009_create_user_profile_rpc.sql

Repository: tqha1011/TaskManagement

Length of output: 2131


🏁 Script executed:

# Also check if there's a profile table definition with timezone column
find . -name "*.sql" -path "*/migrations/*" -exec cat -n {} + | grep -i "profile\|timezone" | head -30

Repository: tqha1011/TaskManagement

Length of output: 994


Streak calculation is hardcoded to UTC, misaligning day boundaries for non-UTC users.

Lines 31 and 49 use UTC to derive task dates and check streak continuity:

  • DATE(updated_at AT TIME ZONE 'UTC') converts all task completions to UTC before extracting the date, shifting local midnight boundaries.
  • A user completing a task at 23:30 local time (UTC-5) will have it counted on the next UTC day, breaking streak continuity if they log no task the following local day.

Apply a user-specific timezone (stored on the profile or via an explicit product-wide timezone rule) to derive task boundaries. Without user timezone data available, document the UTC limitation or store timezone on profile creation.

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

In `@supabase/migrations/20260409084009_create_user_profile_rpc.sql` around lines
31 - 33, The streak date extraction currently forces UTC with DATE(updated_at AT
TIME ZONE 'UTC') which misaligns local day boundaries; change the query that
builds task_date (the SELECT DISTINCT ... FROM public.task WHERE profile_id =
v_user_id AND status = 1) to apply the user's timezone instead of 'UTC' (e.g.,
use DATE(updated_at AT TIME ZONE COALESCE(profile.timezone, v_user_timezone,
'UTC')) or pass a v_user_timezone parameter and use DATE(updated_at AT TIME ZONE
v_user_timezone) so task boundaries match the user's local day), or if no
timezone is available add a clear comment/docstring in the procedure indicating
the UTC limitation and ensure profile.timezone is collected on profile creation
and referenced by the RPC.

@tqha1011 tqha1011 force-pushed the feature/user-profile branch from 1f414b9 to 7db7ab3 Compare April 9, 2026 12:03
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: 7

♻️ Duplicate comments (2)
src/lib/main.dart (1)

58-58: ⚠️ Potential issue | 🔴 Critical

Restore AuthGate as the app entry point.

Line 58 still points home at MainScreen, which bypasses the session check in src/lib/features/auth/presentation/view/auth_gate.dart:6-31. src/lib/features/main/view/screens/main_screen.dart:14-44 assumes the user is already authenticated.

🔧 Proposed fix
-      home: const MainScreen(),
+      home: const AuthGate(),
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/lib/main.dart` at line 58, The app currently sets MainScreen as the
MaterialApp home, bypassing the session/auth check; restore AuthGate as the
entry point by replacing the home: const MainScreen() with home: const
AuthGate() so the session flow in AuthGate
(src/lib/features/auth/presentation/view/auth_gate.dart) runs before navigating
to MainScreen (src/lib/features/main/view/screens/main_screen.dart); locate the
MaterialApp instantiation in main.dart and swap the home widget to AuthGate,
import the AuthGate symbol if needed, and remove any direct assumptions about an
authenticated user in MainScreen initialization.
src/lib/features/user/view/user_profile_view.dart (1)

57-60: ⚠️ Potential issue | 🟠 Major

Don’t call syncThemeWithProfile() during build.

Line 59 performs a side effect from the build path. Because syncThemeWithProfile() updates ThemeProvider, this can notify listeners while the widget tree is still building and cause extra rebuilds or build-phase assertions. Defer it to a lifecycle hook or a post-frame callback instead.

🛠️ Safer approach
-                : Builder(
-                    builder: (innerContext) {
-                      vm.syncThemeWithProfile(innerContext);
-                      return _buildProfileContent(innerContext, vm);
-                    },
-                  ),
+                : Builder(
+                    builder: (innerContext) {
+                      WidgetsBinding.instance.addPostFrameCallback((_) {
+                        if (innerContext.mounted) {
+                          vm.syncThemeWithProfile(innerContext);
+                        }
+                      });
+                      return _buildProfileContent(innerContext, vm);
+                    },
+                  ),

Verify the notify-during-build path with:

#!/bin/bash
sed -n '57,62p' src/lib/features/user/view/user_profile_view.dart
sed -n '80,86p' src/lib/features/user/viewmodel/user_profile_viewmodel.dart
sed -n '17,36p' src/lib/core/theme/theme_provider.dart
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/lib/features/user/view/user_profile_view.dart` around lines 57 - 60, The
build is performing a side effect by calling
vm.syncThemeWithProfile(innerContext); remove this call from the Builder in
user_profile_view (so _buildProfileContent stays pure) and invoke
syncThemeWithProfile from a lifecycle/post-frame callback instead: add the call
inside the Widget's State (e.g., initState or didChangeDependencies) or use
WidgetsBinding.instance.addPostFrameCallback to call
vm.syncThemeWithProfile(context) once after build, and guard against calling it
after dispose; reference syncThemeWithProfile, vm, _buildProfileContent, Builder
and innerContext to locate and move the call.
🧹 Nitpick comments (6)
src/lib/features/tasks/view/screens/create_task_screen.dart (2)

90-92: Remove remaining hardcoded light chip color token.

const Color(0xFFF1F7FD) is still embedded in chip styling. Prefer a theme token to keep color changes centralized and avoid palette drift.

♻️ Suggested refactor
-                              backgroundColor: isDark
-                                  ? Theme.of(context).colorScheme.surfaceContainerHighest
-                                  : const Color(0xFFF1F7FD),
+                              backgroundColor:
+                                  Theme.of(context).colorScheme.surfaceContainerHighest,
@@
-                                    color: isDark
-                                        ? Theme.of(context).colorScheme.outline
-                                        : const Color(0xFFF1F7FD),
+                                    color: isDark
+                                        ? Theme.of(context).colorScheme.outline
+                                        : Theme.of(context).colorScheme.surfaceContainerHighest,

Also applies to: 103-105

🤖 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 90
- 92, Replace the hardcoded light chip color literal (const Color(0xFFF1F7FD))
used in the backgroundColor property (the ternary using isDark) with a theme
token so colors are centralized; update the backgroundColor expression in the
CreateTaskScreen widget (where backgroundColor: isDark ?
Theme.of(context).colorScheme.surfaceContainerHighest : const Color(0xFFF1F7FD))
to use an appropriate Theme token (e.g.,
Theme.of(context).colorScheme.<desiredToken> or a custom theme extension like
AppColors.lightChipBackground) and apply the same change to the other occurrence
referenced (the similar backgroundColor usage around the 103-105 block) so both
branches use theme values instead of a hardcoded Color.

90-107: Consider extracting shared ChoiceChip styling into a reusable helper.

This dark/light chip style block is very similar to src/lib/features/tasks/view/screens/task_detail_screen.dart (Lines 132-157). A shared builder/theme helper would reduce duplication and prevent style drift.

🤖 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 90
- 107, Extract the duplicated ChoiceChip styling into a shared builder function
(e.g., buildTaskChoiceChip or taskChoiceChipBuilder) and replace the inline
style block in CreateTaskScreen and TaskDetailScreen with calls to that helper;
the helper should accept context, isDark, isSelected plus label/onSelected
parameters and return a configured ChoiceChip (or a small Widget) that sets
backgroundColor, selectedColor, labelStyle, shape (with borderRadius and side
using Theme.of(context).colorScheme.outline or the light color), and fontSize so
both files reuse the same styling logic and avoid drift.
src/lib/features/statistics/view/widgets/statistics_widgets.dart (2)

182-188: Consider generating weekday bars via a loop to reduce duplication.

Current repeated calls are correct but harder to maintain.

🧹 Optional cleanup
+ const labels = ['T2', 'T3', 'T4', 'T5', 'T6', 'T7', 'CN'];
  Row(
    mainAxisAlignment: MainAxisAlignment.spaceBetween,
    crossAxisAlignment: CrossAxisAlignment.end,
    children: [
-     _buildBar(context, 'T2', weeklyHeights.length > 0 ? weeklyHeights[0] : 0.1, 0),
-     _buildBar(context, 'T3', weeklyHeights.length > 1 ? weeklyHeights[1] : 0.1, 1),
-     _buildBar(context, 'T4', weeklyHeights.length > 2 ? weeklyHeights[2] : 0.1, 2),
-     _buildBar(context, 'T5', weeklyHeights.length > 3 ? weeklyHeights[3] : 0.1, 3),
-     _buildBar(context, 'T6', weeklyHeights.length > 4 ? weeklyHeights[4] : 0.1, 4),
-     _buildBar(context, 'T7', weeklyHeights.length > 5 ? weeklyHeights[5] : 0.1, 5),
-     _buildBar(context, 'CN', weeklyHeights.length > 6 ? weeklyHeights[6] : 0.1, 6),
+     ...List.generate(labels.length, (index) {
+       final ratio = index < weeklyHeights.length ? weeklyHeights[index] : 0.1;
+       return _buildBar(context, labels[index], ratio, index);
+     }),
    ],
  ),
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/lib/features/statistics/view/widgets/statistics_widgets.dart` around
lines 182 - 188, Replace the repeated _buildBar calls with a loop or map over a
weekday labels array to reduce duplication: create a list of labels like
['T2','T3','T4','T5','T6','T7','CN'] and iterate with index i to call
_buildBar(context, labels[i], weeklyHeights.length > i ? weeklyHeights[i] : 0.1,
i), producing the same widgets in order; update the parent widget to accept the
generated list (e.g., using labels.asMap().entries.map(...) or a for loop) so
all seven bars are built programmatically using the existing _buildBar function
and weeklyHeights.

20-24: Replace remaining hardcoded dark colors with theme tokens.

These literals bypass the centralized theme and can drift from AppTheme over time.

♻️ Suggested refactor
- color: isDark ? const Color(0xFF1A2945) : Theme.of(context).colorScheme.surface,
+ color: Theme.of(context).colorScheme.surface,

- border: isDark
-     ? Border.all(color: const Color(0xFF2A3E62), width: 1)
-     : null,
+ border: isDark
+     ? Border.all(color: Theme.of(context).colorScheme.outline, width: 1)
+     : null,

- color: isDark
-     ? const Color(0xFF23395D)
-     : const Color(0xFFF1F7FD),
+ color: Theme.of(context).colorScheme.surfaceContainerHighest,

Also applies to: 134-138, 279-283, 289-294

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

In `@src/lib/features/statistics/view/widgets/statistics_widgets.dart` around
lines 20 - 24, Summary: Replace hardcoded dark color literals with centralized
theme tokens. Fix: where the widget branches on isDark (the color property using
0xFF1A2945 and the Border.all color 0xFF2A3E62), swap those literal values for
the appropriate AppTheme/Theme color tokens (e.g., Theme.of(context).colorScheme
or your AppTheme color palette) and use the same token for the border color so
dark-mode styling is driven by theme, not literals; apply the same replacement
pattern to the other occurrences referenced (lines 134-138, 279-283, 289-294) so
all hardcoded dark colors in this widget are sourced from the theme.
src/lib/features/statistics/view/screens/statistics_screen.dart (1)

99-103: Replace hardcoded dark notification background with a theme token.

Keeps this badge in sync with future theme updates and removes one-off palette values.

🎨 Suggested change
- color: isDark
-     ? const Color(0xFF172744)
-     : Theme.of(context).colorScheme.surface,
+ color: isDark
+     ? Theme.of(context).colorScheme.surfaceContainerHighest
+     : Theme.of(context).colorScheme.surface,
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/lib/features/statistics/view/screens/statistics_screen.dart` around lines
99 - 103, The badge currently uses a hardcoded dark color (const
Color(0xFF172744)) in the BoxDecoration based on isDark; replace that literal
with a theme token so the badge follows app theming. Update the decoration color
expression in statistics_screen.dart (the BoxDecoration used for the badge where
isDark is checked) to use Theme.of(context).colorScheme.surfaceVariant (or
another appropriate colorScheme token your design system provides) instead of
the hardcoded color, so both dark and light modes come from the theme.
src/lib/features/note/view/focus_widget.dart (1)

18-18: Prefer ColorScheme tokens over hardcoded dark hex values.

These inline dark colors bypass the centralized theme and will drift from AppTheme over time. Moving them into ColorScheme keeps light/dark behavior consistent and easier to maintain.

Also applies to: 57-59, 97-98, 118-118, 175-176, 199-200, 262-266, 298-301, 427-431

🤖 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/note/view/focus_widget.dart`:
- Around line 21-22: The code uses Color.withValues() (e.g., in BoxShadow(color:
Colors.black.withValues(alpha: 0.10), ... ) in focus_widget.dart and other
locations) which requires Flutter 3.22.0+; either update your pubspec.yaml SDK
environment to sdk: ^3.22.0 to allow withValues(), or replace all
withValues(alpha: X) calls with the older equivalent Colors.black.withOpacity(X)
(or Color(...).withOpacity(X)) across the referenced occurrences (the BoxShadow
entries and the other mentions flagged at lines ~122–123, 149, 225, 231, 315) so
the project builds on the current ^3.10.4 constraint. Ensure you update all
instances consistently and run pub get/build to verify.
- Around line 50-52: The _buildTabBtn currently returns a GestureDetector which
lacks keyboard/focus and semantic roles; replace the GestureDetector wrapper
with a Semantics widget (providing button role and label) and use a Material +
InkWell for the clickable area so it supports focus, keyboard activation, and
ripple feedback; update the return in _buildTabBtn to wrap the AnimatedContainer
(or its child) with Semantics(role: button, label: ...), then put a
Material(child: InkWell(onTap: onTap, onKey: ..., child: AnimatedContainer)) and
ensure focus/hover/semantic properties mirror the existing enabled/selected
state.

In `@src/lib/features/statistics/view/screens/statistics_screen.dart`:
- Around line 80-84: In StatisticsScreen replace the hardcoded NetworkImage URL
inside the CircleAvatar with the actual user's avatar source (e.g., use the
profile/user model field such as user.avatarUrl or profile.avatarUrl) and handle
null/empty values by falling back to a default asset or rendering initials;
update the CircleAvatar's backgroundImage to use NetworkImage(user.avatarUrl)
guarded by a null check (or use a placeholder asset/provider when empty) so the
avatar reflects real profile data instead of the fixed placeholder.

In `@src/lib/features/user/view/user_profile_view.dart`:
- Around line 31-36: The IconButton with Icons.settings in the UserProfileView
build is a dead/tappable control because its onPressed is empty; either remove
the IconButton entirely or wire it to a real action (e.g., in UserProfileView's
AppBar add an onPressed that navigates: Navigator.of(context).push(...) to your
SettingsPage/SettingsRoute or opens a modal). Locate the IconButton (search for
IconButton, Icons.settings, onPressed) and replace the empty callback with the
navigation call or delete the widget so the settings control is not shown until
implemented.

In `@src/lib/features/user/viewmodel/user_profile_viewmodel.dart`:
- Around line 60-76: toggleNotification and updateAppearance only mutate the
in-memory _user and therefore get reverted when loadProfile rebuilds _user (and
syncThemeWithProfile can overwrite appearance); persist these preference changes
to the underlying storage or API whenever they are changed: in
toggleNotification call the repository/service method that saves the user's
notification preference (e.g., UserRepository.updateNotification or similar) and
await/handle errors, then update _user and notifyListeners; in updateAppearance
persist the newAppearance to the same persistent layer before/after setting
_lastAppliedAppearance and calling ThemeProvider.updateTheme, and ensure
syncThemeWithProfile honors the persisted value; reference the methods
toggleNotification, updateAppearance, loadProfile, syncThemeWithProfile and the
fields _user and _lastAppliedAppearance when making the changes.
- Around line 37-39: The catch block in the profile load is replacing a failed
fetch with fake data by assigning _user = _buildMockUser(); instead of surfacing
the error; remove that assignment and instead set an explicit error state (e.g.,
set a _loadError or _hasError flag, populate an error message property, and call
notifyListeners()) or rethrow the exception so UserProfileView can show the
proper error UI; update the catch in the profile-loading method where _user and
_buildMockUser() are referenced to propagate the failure via the viewmodel's
error state rather than injecting mock data.
- Around line 101-105: The catch block in signOut (catch (e) { ... }) logs the
raw exception via debugPrint and displays it directly in the SnackBar, leaking
implementation details; keep the debug/log of the exception but replace the
SnackBar content with a generic user-facing message. Update the catch handler
around ScaffoldMessenger.of(context).showSnackBar(...) to call debugPrint or a
logger with the full exception (e) and then show a generic SnackBar message like
"Đăng xuất không thành công, vui lòng thử lại" (without interpolating e); keep
the context.mounted check and existing ScaffoldMessenger/ SnackBar usage but
remove the exception string from the visible text. Ensure the change targets the
catch block that currently prints "Lỗi đăng xuất: $e".

---

Duplicate comments:
In `@src/lib/features/user/view/user_profile_view.dart`:
- Around line 57-60: The build is performing a side effect by calling
vm.syncThemeWithProfile(innerContext); remove this call from the Builder in
user_profile_view (so _buildProfileContent stays pure) and invoke
syncThemeWithProfile from a lifecycle/post-frame callback instead: add the call
inside the Widget's State (e.g., initState or didChangeDependencies) or use
WidgetsBinding.instance.addPostFrameCallback to call
vm.syncThemeWithProfile(context) once after build, and guard against calling it
after dispose; reference syncThemeWithProfile, vm, _buildProfileContent, Builder
and innerContext to locate and move the call.

In `@src/lib/main.dart`:
- Line 58: The app currently sets MainScreen as the MaterialApp home, bypassing
the session/auth check; restore AuthGate as the entry point by replacing the
home: const MainScreen() with home: const AuthGate() so the session flow in
AuthGate (src/lib/features/auth/presentation/view/auth_gate.dart) runs before
navigating to MainScreen (src/lib/features/main/view/screens/main_screen.dart);
locate the MaterialApp instantiation in main.dart and swap the home widget to
AuthGate, import the AuthGate symbol if needed, and remove any direct
assumptions about an authenticated user in MainScreen initialization.

---

Nitpick comments:
In `@src/lib/features/statistics/view/screens/statistics_screen.dart`:
- Around line 99-103: The badge currently uses a hardcoded dark color (const
Color(0xFF172744)) in the BoxDecoration based on isDark; replace that literal
with a theme token so the badge follows app theming. Update the decoration color
expression in statistics_screen.dart (the BoxDecoration used for the badge where
isDark is checked) to use Theme.of(context).colorScheme.surfaceVariant (or
another appropriate colorScheme token your design system provides) instead of
the hardcoded color, so both dark and light modes come from the theme.

In `@src/lib/features/statistics/view/widgets/statistics_widgets.dart`:
- Around line 182-188: Replace the repeated _buildBar calls with a loop or map
over a weekday labels array to reduce duplication: create a list of labels like
['T2','T3','T4','T5','T6','T7','CN'] and iterate with index i to call
_buildBar(context, labels[i], weeklyHeights.length > i ? weeklyHeights[i] : 0.1,
i), producing the same widgets in order; update the parent widget to accept the
generated list (e.g., using labels.asMap().entries.map(...) or a for loop) so
all seven bars are built programmatically using the existing _buildBar function
and weeklyHeights.
- Around line 20-24: Summary: Replace hardcoded dark color literals with
centralized theme tokens. Fix: where the widget branches on isDark (the color
property using 0xFF1A2945 and the Border.all color 0xFF2A3E62), swap those
literal values for the appropriate AppTheme/Theme color tokens (e.g.,
Theme.of(context).colorScheme or your AppTheme color palette) and use the same
token for the border color so dark-mode styling is driven by theme, not
literals; apply the same replacement pattern to the other occurrences referenced
(lines 134-138, 279-283, 289-294) so all hardcoded dark colors in this widget
are sourced from the theme.

In `@src/lib/features/tasks/view/screens/create_task_screen.dart`:
- Around line 90-92: Replace the hardcoded light chip color literal (const
Color(0xFFF1F7FD)) used in the backgroundColor property (the ternary using
isDark) with a theme token so colors are centralized; update the backgroundColor
expression in the CreateTaskScreen widget (where backgroundColor: isDark ?
Theme.of(context).colorScheme.surfaceContainerHighest : const Color(0xFFF1F7FD))
to use an appropriate Theme token (e.g.,
Theme.of(context).colorScheme.<desiredToken> or a custom theme extension like
AppColors.lightChipBackground) and apply the same change to the other occurrence
referenced (the similar backgroundColor usage around the 103-105 block) so both
branches use theme values instead of a hardcoded Color.
- Around line 90-107: Extract the duplicated ChoiceChip styling into a shared
builder function (e.g., buildTaskChoiceChip or taskChoiceChipBuilder) and
replace the inline style block in CreateTaskScreen and TaskDetailScreen with
calls to that helper; the helper should accept context, isDark, isSelected plus
label/onSelected parameters and return a configured ChoiceChip (or a small
Widget) that sets backgroundColor, selectedColor, labelStyle, shape (with
borderRadius and side using Theme.of(context).colorScheme.outline or the light
color), and fontSize so both files reuse the same styling logic and avoid drift.
🪄 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: e14d9f6f-8d8f-4bb9-9985-15d749b595f9

📥 Commits

Reviewing files that changed from the base of the PR and between 1f414b9 and 7db7ab3.

📒 Files selected for processing (31)
  • src/lib/core/theme/app_theme.dart
  • src/lib/core/theme/auth_layout_template.dart
  • src/lib/core/theme/custom_text_field.dart
  • src/lib/core/theme/theme_provider.dart
  • src/lib/core/widgets/custom_input_field.dart
  • src/lib/features/auth/otp_verification_view.dart
  • src/lib/features/auth/presentation/view/forgot_password_view.dart
  • src/lib/features/auth/presentation/view/login_view.dart
  • src/lib/features/auth/presentation/view/new_password_view.dart
  • src/lib/features/auth/presentation/view/otp_verification_view.dart
  • src/lib/features/auth/presentation/view/register_view.dart
  • src/lib/features/main/view/screens/main_screen.dart
  • src/lib/features/note/view/focus_screen.dart
  • src/lib/features/note/view/focus_widget.dart
  • src/lib/features/statistics/view/screens/statistics_screen.dart
  • src/lib/features/statistics/view/widgets/statistics_widgets.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/task_widgets.dart
  • src/lib/features/user/model/user_profile_model.dart
  • src/lib/features/user/service/user_service.dart
  • src/lib/features/user/view/user_profile_view.dart
  • src/lib/features/user/view/widgets/logout_button.dart
  • src/lib/features/user/view/widgets/profile_header.dart
  • src/lib/features/user/view/widgets/settings_list_tile.dart
  • src/lib/features/user/view/widgets/settings_section.dart
  • src/lib/features/user/view/widgets/stat_card.dart
  • src/lib/features/user/viewmodel/user_profile_viewmodel.dart
  • src/lib/main.dart
  • supabase/migrations/20260409084009_create_user_profile_rpc.sql
✅ Files skipped from review due to trivial changes (6)
  • src/lib/features/auth/otp_verification_view.dart
  • src/lib/features/auth/presentation/view/forgot_password_view.dart
  • supabase/migrations/20260409084009_create_user_profile_rpc.sql
  • src/lib/features/auth/presentation/view/otp_verification_view.dart
  • src/lib/features/tasks/view/widgets/task_widgets.dart
  • src/lib/core/theme/custom_text_field.dart
🚧 Files skipped from review as they are similar to previous changes (13)
  • src/lib/core/widgets/custom_input_field.dart
  • src/lib/features/tasks/view/screens/task_detail_screen.dart
  • src/lib/features/auth/presentation/view/register_view.dart
  • src/lib/features/user/view/widgets/logout_button.dart
  • src/lib/features/user/service/user_service.dart
  • src/lib/core/theme/app_theme.dart
  • src/lib/features/user/view/widgets/settings_list_tile.dart
  • src/lib/features/user/view/widgets/stat_card.dart
  • src/lib/core/theme/theme_provider.dart
  • src/lib/features/user/view/widgets/profile_header.dart
  • src/lib/features/user/model/user_profile_model.dart
  • src/lib/features/main/view/screens/main_screen.dart
  • src/lib/features/auth/presentation/view/login_view.dart

Comment on lines +21 to +22
BoxShadow(color: Colors.black.withValues(alpha: 0.10), blurRadius: 10)
],
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 | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== pubspec constraints =="
fd -i pubspec.yaml --exec sh -c 'echo ">>> $1"; sed -n "1,180p" "$1"' sh {}

echo
echo "== withValues usage =="
rg -n --type=dart '\.withValues\s*\(' src/lib/features/note/view/focus_widget.dart src/lib/features/note/view/focus_screen.dart

Repository: tqha1011/TaskManagement

Length of output: 4784


Update Flutter SDK constraint to 3.22.0+ to support Color.withValues().

Your pubspec.yaml specifies sdk: ^3.10.4, but Color.withValues() was introduced in Flutter 3.22.0. Update the environment constraint to sdk: ^3.22.0 or change all withValues() calls to the older withOpacity() method.

Also applies to: 122–123, 149, 225, 231, 315

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

In `@src/lib/features/note/view/focus_widget.dart` around lines 21 - 22, The code
uses Color.withValues() (e.g., in BoxShadow(color:
Colors.black.withValues(alpha: 0.10), ... ) in focus_widget.dart and other
locations) which requires Flutter 3.22.0+; either update your pubspec.yaml SDK
environment to sdk: ^3.22.0 to allow withValues(), or replace all
withValues(alpha: X) calls with the older equivalent Colors.black.withOpacity(X)
(or Color(...).withOpacity(X)) across the referenced occurrences (the BoxShadow
entries and the other mentions flagged at lines ~122–123, 149, 225, 231, 315) so
the project builds on the current ^3.10.4 constraint. Ensure you update all
instances consistently and run pub get/build to verify.

Comment on lines 50 to 52
return GestureDetector(
onTap: onTap,
child: AnimatedContainer(
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

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== GestureDetector/onTap usage in focus views =="
rg -n --type=dart 'GestureDetector\(|onTap:' src/lib/features/note/view/focus_widget.dart src/lib/features/note/view/focus_screen.dart -C2

echo
echo "== Existing semantic/button widgets nearby =="
rg -n --type=dart 'Semantics\(|IconButton\(|TextButton\(|InkWell\(' src/lib/features/note/view/focus_widget.dart src/lib/features/note/view/focus_screen.dart -C1

Repository: tqha1011/TaskManagement

Length of output: 5313


🏁 Script executed:

#!/bin/bash
# Get full context around lines 50-52 and 63-71
sed -n '40,80p' src/lib/features/note/view/focus_widget.dart

Repository: tqha1011/TaskManagement

Length of output: 1173


🏁 Script executed:

#!/bin/bash
# Find usages of _buildTabBtn
rg -n '_buildTabBtn' src/lib/features/note/view/focus_widget.dart -B2 -A2

echo "==="

# Check for TabBar usage in the file
rg -n 'TabBar\|DefaultTabController' src/lib/features/note/view/focus_widget.dart

Repository: tqha1011/TaskManagement

Length of output: 441


🏁 Script executed:

#!/bin/bash
# Check for Semantics usage patterns in the codebase
echo "=== Semantics usage patterns ==="
rg -n 'Semantics\(' src/lib/features --type=dart | head -20

echo
echo "=== InkWell with Material patterns ==="
rg -n 'Material\(' src/lib/features/note/view --type=dart -A3 | head -30

Repository: tqha1011/TaskManagement

Length of output: 469


🏁 Script executed:

#!/bin/bash
# View the button widget that uses Material + InkWell
sed -n '225,250p' src/lib/features/note/view/focus_widget.dart

Repository: tqha1011/TaskManagement

Length of output: 761


🏁 Script executed:

#!/bin/bash
# Get the function signature for this widget
sed -n '200,240p' src/lib/features/note/view/focus_widget.dart

Repository: tqha1011/TaskManagement

Length of output: 1136


Tab buttons need semantic/focus support for keyboard and assistive technology users.

The _buildTabBtn widget uses GestureDetector which provides no keyboard navigation, ripple feedback, or semantic button role. Wrap it with Semantics and replace GestureDetector with Material + InkWell to provide proper accessibility and visual feedback.

Suggested fix
-    return GestureDetector(
-      onTap: onTap,
-      child: AnimatedContainer(
+    return Semantics(
+      button: true,
+      label: label,
+      child: Material(
+        color: Colors.transparent,
+        child: InkWell(
+          borderRadius: BorderRadius.circular(15),
+          onTap: onTap,
+          child: AnimatedContainer(
             duration: const Duration(milliseconds: 200),
             padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 10),
             decoration: BoxDecoration(
               color: isSelected
                   ? (Theme.of(context).brightness == Brightness.dark
                       ? const Color(0xFF2A3D5D)
                       : Theme.of(context).colorScheme.primary)
                   : Colors.transparent,
               borderRadius: BorderRadius.circular(15),
             ),
             child: Text(
               label,
               style: TextStyle(
                 color: isSelected
                     ? Colors.white
                     : Theme.of(context).colorScheme.onSurfaceVariant,
                 fontWeight: FontWeight.bold,
                 fontSize: 14,
               ),
             ),
-      ),
+          ),
+        ),
+      ),
     );
📝 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
return GestureDetector(
onTap: onTap,
child: AnimatedContainer(
return Semantics(
button: true,
label: label,
child: Material(
color: Colors.transparent,
child: InkWell(
borderRadius: BorderRadius.circular(15),
onTap: onTap,
child: AnimatedContainer(
duration: const Duration(milliseconds: 200),
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 10),
decoration: BoxDecoration(
color: isSelected
? (Theme.of(context).brightness == Brightness.dark
? const Color(0xFF2A3D5D)
: Theme.of(context).colorScheme.primary)
: Colors.transparent,
borderRadius: BorderRadius.circular(15),
),
child: Text(
label,
style: TextStyle(
color: isSelected
? Colors.white
: Theme.of(context).colorScheme.onSurfaceVariant,
fontWeight: FontWeight.bold,
fontSize: 14,
),
),
),
),
),
);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/lib/features/note/view/focus_widget.dart` around lines 50 - 52, The
_buildTabBtn currently returns a GestureDetector which lacks keyboard/focus and
semantic roles; replace the GestureDetector wrapper with a Semantics widget
(providing button role and label) and use a Material + InkWell for the clickable
area so it supports focus, keyboard activation, and ripple feedback; update the
return in _buildTabBtn to wrap the AnimatedContainer (or its child) with
Semantics(role: button, label: ...), then put a Material(child: InkWell(onTap:
onTap, onKey: ..., child: AnimatedContainer)) and ensure focus/hover/semantic
properties mirror the existing enabled/selected state.

Comment on lines +80 to +84
const CircleAvatar(
radius: 22,
backgroundImage: NetworkImage('https://i.pravatar.cc/150?u=a042581f4e29026704d')
),
SizedBox(width: 15),
Text('Thống kê', style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold, color: Color(0xFF2C3E50))),
],
),
backgroundImage: NetworkImage(
'https://i.pravatar.cc/150?u=a042581f4e29026704d',
),
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

Use real profile avatar instead of a fixed placeholder URL.

The hardcoded avatar image prevents this screen from reflecting actual user profile data.

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

In `@src/lib/features/statistics/view/screens/statistics_screen.dart` around lines
80 - 84, In StatisticsScreen replace the hardcoded NetworkImage URL inside the
CircleAvatar with the actual user's avatar source (e.g., use the profile/user
model field such as user.avatarUrl or profile.avatarUrl) and handle null/empty
values by falling back to a default asset or rendering initials; update the
CircleAvatar's backgroundImage to use NetworkImage(user.avatarUrl) guarded by a
null check (or use a placeholder asset/provider when empty) so the avatar
reflects real profile data instead of the fixed placeholder.

Comment on lines +31 to +36
IconButton(
icon: Icon(
Icons.settings,
color: Theme.of(context).colorScheme.primary,
),
onPressed: () {},
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

Remove or wire the settings action.

This button is visible and tappable, but onPressed is empty, so it creates a dead control in the app bar. Either navigate somewhere meaningful or omit the action until it exists.

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

In `@src/lib/features/user/view/user_profile_view.dart` around lines 31 - 36, The
IconButton with Icons.settings in the UserProfileView build is a dead/tappable
control because its onPressed is empty; either remove the IconButton entirely or
wire it to a real action (e.g., in UserProfileView's AppBar add an onPressed
that navigates: Navigator.of(context).push(...) to your
SettingsPage/SettingsRoute or opens a modal). Locate the IconButton (search for
IconButton, Icons.settings, onPressed) and replace the empty callback with the
navigation call or delete the widget so the settings control is not shown until
implemented.

Comment on lines +37 to +39
} catch (e) {
debugPrint("Error loading profile: $e");
_user = _buildMockUser();
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

Don’t replace a failed fetch with a fake profile.

When the real load fails, this path silently swaps in mock data instead of surfacing the error state that UserProfileView already handles. That can show the wrong identity/stats and hide auth or backend failures.

🔧 Minimal fix
-    } catch (e) {
-      debugPrint("Error loading profile: $e");
-      _user = _buildMockUser();
+    } catch (e) {
+      debugPrint("Error loading profile: $e");
+      _user = useMockData ? _buildMockUser() : null;
📝 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
} catch (e) {
debugPrint("Error loading profile: $e");
_user = _buildMockUser();
} catch (e) {
debugPrint("Error loading profile: $e");
_user = useMockData ? _buildMockUser() : null;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/lib/features/user/viewmodel/user_profile_viewmodel.dart` around lines 37
- 39, The catch block in the profile load is replacing a failed fetch with fake
data by assigning _user = _buildMockUser(); instead of surfacing the error;
remove that assignment and instead set an explicit error state (e.g., set a
_loadError or _hasError flag, populate an error message property, and call
notifyListeners()) or rethrow the exception so UserProfileView can show the
proper error UI; update the catch in the profile-loading method where _user and
_buildMockUser() are referenced to propagate the failure via the viewmodel's
error state rather than injecting mock data.

Comment on lines +60 to +76
void toggleNotification(bool value) {
if (_user != null) {
_user!.isNotificationEnabled = value;
notifyListeners();
}
}


void updateAppearance(BuildContext context, String newAppearance) {
if (_user != null) {
_user!.appearance = newAppearance;
_lastAppliedAppearance = newAppearance;
notifyListeners();

if (context.mounted) {
context.read<ThemeProvider>().updateTheme(newAppearance);
}
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

Persist preference changes instead of only mutating local state.

toggleNotification() and updateAppearance() only update the in-memory model. A later loadProfile() rebuilds _user, so both settings revert; for appearance, syncThemeWithProfile() can then overwrite the theme again with the stale profile value.

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

In `@src/lib/features/user/viewmodel/user_profile_viewmodel.dart` around lines 60
- 76, toggleNotification and updateAppearance only mutate the in-memory _user
and therefore get reverted when loadProfile rebuilds _user (and
syncThemeWithProfile can overwrite appearance); persist these preference changes
to the underlying storage or API whenever they are changed: in
toggleNotification call the repository/service method that saves the user's
notification preference (e.g., UserRepository.updateNotification or similar) and
await/handle errors, then update _user and notifyListeners; in updateAppearance
persist the newAppearance to the same persistent layer before/after setting
_lastAppliedAppearance and calling ThemeProvider.updateTheme, and ensure
syncThemeWithProfile honors the persisted value; reference the methods
toggleNotification, updateAppearance, loadProfile, syncThemeWithProfile and the
fields _user and _lastAppliedAppearance when making the changes.

Comment on lines +101 to +105
} catch (e) {
debugPrint("Lỗi đăng xuất: $e");
if (context.mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Lỗi đăng xuất: $e')),
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

Avoid exposing raw logout exceptions in the snackbar.

Line 105 shows the full exception text to the user. That tends to leak implementation details and produces poor UX. Log the error, but show a generic failure message in the UI.

🔧 Proposed fix
-        ScaffoldMessenger.of(context).showSnackBar(
-          SnackBar(content: Text('Lỗi đăng xuất: $e')),
-        );
+        ScaffoldMessenger.of(context).showSnackBar(
+          const SnackBar(content: Text('Đăng xuất thất bại. Vui lòng thử lại.')),
+        );
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/lib/features/user/viewmodel/user_profile_viewmodel.dart` around lines 101
- 105, The catch block in signOut (catch (e) { ... }) logs the raw exception via
debugPrint and displays it directly in the SnackBar, leaking implementation
details; keep the debug/log of the exception but replace the SnackBar content
with a generic user-facing message. Update the catch handler around
ScaffoldMessenger.of(context).showSnackBar(...) to call debugPrint or a logger
with the full exception (e) and then show a generic SnackBar message like "Đăng
xuất không thành công, vui lòng thử lại" (without interpolating e); keep the
context.mounted check and existing ScaffoldMessenger/ SnackBar usage but remove
the exception string from the visible text. Ensure the change targets the catch
block that currently prints "Lỗi đăng xuất: $e".

@hoanghaoz hoanghaoz merged commit c8c7e6d into main Apr 9, 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