Skip to content

Add local mood reminders + stress/doctor prompts + improve admin UX + maya for admin#12

Open
Ashish-Kumar-Dash wants to merge 9 commits intoOpenLake:mainfrom
Ashish-Kumar-Dash:akd
Open

Add local mood reminders + stress/doctor prompts + improve admin UX + maya for admin#12
Ashish-Kumar-Dash wants to merge 9 commits intoOpenLake:mainfrom
Ashish-Kumar-Dash:akd

Conversation

@Ashish-Kumar-Dash
Copy link
Contributor

@Ashish-Kumar-Dash Ashish-Kumar-Dash commented Mar 19, 2026

Added local notification scheduling to remind users to log daily mood, and to suggest stress checks / doctor sessions when mood trends low.
Implemented a shared NotificationService with deep-link routing so taps open the correct screen (Mood Tracker, Stress Check, Online Meet).
Improved admin/doctor experience (patient search + clearer selected-patient context in Maya chat).
Added user-facing error handling to avoid showing raw exception text on network issues.
Added maya to assist doctor as an assistant

Summary by CodeRabbit

  • New Features

    • Dedicated admin chat interface for Maya with patient-context selection and quick access
    • Patient search by name, ID, or email
    • Local notifications: permission prompt, daily reminders, stress-triggered alerts, and notification tap navigation to relevant app screens
  • Improvements

    • Standardized, user-friendly error messages shown throughout the app

@coderabbitai
Copy link

coderabbitai bot commented Mar 19, 2026

Walkthrough

This PR adds a centralized friendly error formatter, a NotificationService with timezone-aware daily reminders and tap routing, an admin-facing Maya chat with patient context, chat-service admin-mode prompts, mood-based notification scheduling, a patient search UI, and related navigation/plugin wiring.

Changes

Cohort / File(s) Summary
Error Message Utility & Adoptions
flutter_app/lib/core/error_messages.dart, flutter_app/lib/pages/ai_chat.dart, flutter_app/lib/pages/article_page.dart, flutter_app/lib/pages/doctor_chat_page.dart, flutter_app/lib/pages/maya_consent_page.dart, flutter_app/lib/pages/meeting_requests_page.dart, flutter_app/lib/pages/onboarding_page.dart, flutter_app/lib/pages/online_meet_page.dart, flutter_app/lib/pages/past_interactions_page.dart, flutter_app/lib/pages/past_meetings_page.dart, flutter_app/lib/pages/profile_page.dart, flutter_app/lib/pages/scheduled_meetings_page.dart
Added friendlyErrorMessage(Object, {String? fallback}) and replaced many SnackBar/raw-exception usages with calls to this helper to surface user-friendly error text.
Notification Infrastructure
flutter_app/lib/services/notification_service.dart, flutter_app/pubspec.yaml, flutter_app/macos/Flutter/GeneratedPluginRegistrant.swift, android/app/src/main/AndroidManifest.xml, flutter_app/lib/main.dart
New NotificationService singleton: initialization, timezone setup, permission request, daily-scheduling, cancel APIs, and notification-tap routing to named routes. Added dependencies (flutter_local_notifications, timezone, flutter_native_timezone), macOS plugin registration, Android POST_NOTIFICATIONS permission, and wired navigatorKey + init in main.dart.
Admin Chat UI & Integration
flutter_app/lib/pages/admin_maya_chat_page.dart, flutter_app/lib/pages/admin_dashboard.dart
Added AdminMayaChatPage providing admin-mode chat, patient dropdown (loaded from Supabase), message list, typing state, refresh/reset, and send/error flows; added FAB on admin dashboard to open the page.
ChatService Admin Mode
flutter_app/lib/services/chat_service.dart
Extended constructor with isAdminMode and targetPatientId; admin-mode builds an admin prompt and can append a generated "PATIENT SUMMARY" from profile fields when personal data usage is allowed.
Mood Tracker Notifications & Tuning
flutter_app/lib/pages/mood_tracker.dart
Hooked NotificationService into mood loading: cancels/schedules daily mood reminder, computes recent average and low-mood streak to schedule/cancel stress-related reminders (fixed IDs), and increased chat max_tokens from 300 to 500.
Patient List Search & UI
flutter_app/lib/pages/patient_list_page.dart
Added _searchController, _searchQuery, _filteredPatients getter, search TextField in UI, refresh action, lifecycle cleanup, and updated error handling to use friendlyErrorMessage.

Sequence Diagrams

sequenceDiagram
    participant Admin as Admin User
    participant Page as AdminMayaChatPage
    participant Supabase as Supabase DB
    participant ChatSvc as ChatService
    participant LLM as Maya/LLM

    Admin->>Page: Open admin chat page
    Page->>Supabase: Load patient profiles
    Supabase-->>Page: Return patient list
    Page->>Page: Render dropdown

    Admin->>Page: Select patient
    Page->>ChatSvc: Create ChatService(isAdminMode=true, targetPatientId)
    ChatSvc->>Supabase: Fetch patient profile
    Supabase-->>ChatSvc: Profile data
    ChatSvc->>ChatSvc: Build admin prompt + PATIENT SUMMARY
    ChatSvc-->>Page: Ready with context message

    Admin->>Page: Send message
    Page->>Page: Append user message, disable input
    Page->>ChatSvc: sendMessage(userMessage)
    ChatSvc->>LLM: Request with admin prompt + context
    LLM-->>ChatSvc: Assistant response
    ChatSvc-->>Page: Assistant message
    Page->>Page: Append assistant message, re-enable input
Loading
sequenceDiagram
    participant MoodPage as Mood Tracker
    participant DB as Supabase
    participant NotifSvc as NotificationService
    participant Device as OS Scheduler

    MoodPage->>DB: Fetch recent mood entries
    DB-->>MoodPage: Mood records
    MoodPage->>MoodPage: Compute avg mood and low-mood streak

    alt Mood logged today
        MoodPage->>NotifSvc: cancel(id:1) daily mood reminder
        NotifSvc-->>Device: Cancel scheduled notification
    else No mood today
        MoodPage->>NotifSvc: scheduleDailyReminder(id:1,...)
        NotifSvc-->>Device: Schedule daily notification (timezone-aware)
    end

    alt Low mood detected (avg ≤ 2.5 or streak ≥ 3)
        MoodPage->>NotifSvc: scheduleDailyReminder(id:2,...), scheduleDailyReminder(id:3,...)
    else Mood improving
        MoodPage->>NotifSvc: cancel(id:2), cancel(id:3)
    end
Loading

Estimated Code Review Effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Possibly Related PRs

  • PR #9 — Related changes to ChatService and pages; closely connected to admin-mode/chat prompt adjustments in this PR.
  • PR #8 — Introduced the mood tracker feature; this PR integrates notification scheduling into that flow.
  • PR #7 — Earlier admin dashboard additions; this PR adds an admin chat entry point from the dashboard.

Poem

🐰
Friendly words now hush the crash and clatter,
Maya chats with care where patient stories matter,
Reminders hop in time, gentle as spring—
A rabbit nods: code that helps hearts sing! ✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main changes: local mood reminders, stress/doctor prompts, improved admin UX, and Maya support for admins.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
📝 Coding Plan
  • Generate coding plan for human review comments

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.

Tip

You can disable sequence diagrams in the walkthrough.

Disable the reviews.sequence_diagrams setting to disable sequence diagrams in the walkthrough.

Copy link

@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: 8

Caution

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

⚠️ Outside diff range comments (1)
flutter_app/lib/pages/mood_tracker.dart (1)

1354-1581: ⚠️ Potential issue | 🔴 Critical

Remove the orphaned build block at file scope.

The file has already closed all widget classes before this section starts, so this trailing @override Widget build(...) block is top-level Dart and the file will not compile.

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

In `@flutter_app/lib/pages/mood_tracker.dart` around lines 1354 - 1581, There is
an orphaned top-level "@override Widget build(BuildContext context)" block that
should be deleted; remove the entire trailing build(...) {...} block shown in
the diff (from the "@override Widget build(BuildContext context)" line through
its matching closing brace) so the file no longer contains a standalone build
function, and then verify any surrounding widget classes (e.g., the Mood tracker
page class) still have their correct build methods and balanced braces.
🧹 Nitpick comments (4)
flutter_app/lib/pages/article_page.dart (1)

60-60: Provide page-specific fallback copy for inline error panels.

Since these errors are displayed persistently in-page, pass a fallback so users don’t see technical/non-actionable text when parsing fails.

Suggested update
-          _popularError = friendlyErrorMessage(e);
+          _popularError = friendlyErrorMessage(
+            e,
+            fallback: 'Unable to load popular articles right now. Please try again.',
+          );

...
-          _categoryError = friendlyErrorMessage(e);
+          _categoryError = friendlyErrorMessage(
+            e,
+            fallback: 'Unable to load this topic right now. Please try again.',
+          );

Also applies to: 93-93

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

In `@flutter_app/lib/pages/article_page.dart` at line 60, Replace the raw
technical fallback by passing a page-specific fallback string into
friendlyErrorMessage wherever _popularError is set (e.g., the assignments in
ArticlePage that currently read "_popularError = friendlyErrorMessage(e);");
update both occurrences (around the current lines where _popularError is
assigned) to call friendlyErrorMessage(e, "Unable to load popular articles right
now. Please try again later.") or similarly worded page-specific copy so the
inline error panel shows user-friendly, actionable text.
flutter_app/lib/pages/admin_maya_chat_page.dart (2)

304-343: Hardcoded white background breaks dark mode.

The input container uses color: Colors.white (line 309), which will clash with dark theme text colors and create poor contrast. Consider using a theme-aware color.

♻️ Suggested fix
             child: Container(
               padding:
                   const EdgeInsets.symmetric(horizontal: 12, vertical: 10),
-              color: Colors.white,
+              color: Theme.of(context).cardColor,
               child: Row(
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@flutter_app/lib/pages/admin_maya_chat_page.dart` around lines 304 - 343, The
Container around the input bar uses a hardcoded color (color: Colors.white)
which breaks dark mode; replace that hardcoded value with a theme-aware color
(e.g., use Theme.of(context).colorScheme.surface or
Theme.of(context).cardColor/scaffoldBackgroundColor) so the background adapts to
light/dark themes, and ensure the inner TextField fillColor (fillColor:
Colors.grey.shade200) is also switched to a theme-aware alternative (e.g.,
Theme.of(context).colorScheme.background or a shade derived from Theme) to
maintain proper contrast; update the Container and TextField properties in the
build where SafeArea/Container/TextField are defined.

62-67: Consider adding user feedback when patient list fails to load.

The error is silently swallowed, leaving the dropdown empty without explanation. A snackbar or inline message would help doctors understand why no patients appear.

♻️ Suggested addition
     } catch (e) {
       setState(() {
         _patients = [];
         _isFetchingPatients = false;
       });
+      if (mounted) {
+        ScaffoldMessenger.of(context).showSnackBar(
+          SnackBar(
+            content: Text('Unable to load patients: ${friendlyErrorMessage(e)}'),
+          ),
+        );
+      }
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@flutter_app/lib/pages/admin_maya_chat_page.dart` around lines 62 - 67, The
catch block that clears _patients and sets _isFetchingPatients to false silently
swallows errors; update the error handling in the fetch method (the catch in the
function that calls setState with _patients/_isFetchingPatients) to both surface
user feedback and preserve existing state updates: set a new state field like
_patientFetchError (or reuse an existing error state) inside setState, and show
a user-facing message via ScaffoldMessenger.of(context).showSnackBar(...) or
display an inline widget bound to _patientFetchError so doctors see why the
patient list failed to load; keep the existing _patients = [] and
_isFetchingPatients = false updates.
flutter_app/lib/main.dart (1)

21-25: Consider loading Supabase config from environment variables.

The .env file is already loaded (line 18), but the Supabase URL and anonKey are hardcoded. While the anon key is designed to be public, loading both values from dotenv.env would improve consistency and make environment switching easier (dev/staging/prod).

♻️ Suggested refactor
   // Initialize Supabase
   await Supabase.initialize(
-    url: 'https://prvbbbnsizxxfxreokov.supabase.co',
-    anonKey:
-        'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6InBydmJiYm5zaXp4eGZ4cmVva292Iiwicm9sZSI6ImFub24iLCJpYXQiOjE3NzA4OTMwODgsImV4cCI6MjA4NjQ2OTA4OH0.GOqP5k0AnGzqse5loXuPz9BkfGCUdJgApUL9QBVw0es',
+    url: dotenv.env['SUPABASE_URL'] ?? '',
+    anonKey: dotenv.env['SUPABASE_ANON_KEY'] ?? '',
   );
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@flutter_app/lib/main.dart` around lines 21 - 25, Replace the hardcoded
Supabase URL and anon key in the Supabase.initialize call with values read from
the loaded environment (dotenv.env); update the call in main.dart where
Supabase.initialize(...) is invoked so it uses dotenv.env['SUPABASE_URL'] and
dotenv.env['SUPABASE_ANON_KEY'] (or sensible env var names) instead of the
literal strings, and add a fallback or assert that these env values are present
before calling Supabase.initialize to avoid null values at runtime.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@flutter_app/lib/core/error_messages.dart`:
- Around line 7-21: Make the matching case-insensitive and sanitize common
exception prefixes before returning to users: normalize the captured message
(e.g., store a trimmed copy and a lowercase version) and use the lowercase form
for contains checks instead of the current case-sensitive checks on message;
then strip common leading prefixes such as "Exception:", "Error:",
"FormatException:", "SocketException:" (and any alphabetic word followed by a
colon) from the user-facing message variable so raw exception labels aren’t
shown; ensure the function still returns fallback when matches occur and
otherwise returns the sanitized message.

In `@flutter_app/lib/main.dart`:
- Around line 27-28: The app currently only registers
NotificationService.instance.onDidReceiveNotificationResponse (via
NotificationService.initialize), so taps on notifications when the app is
terminated won't navigate on cold-start; to fix, add cold-launch handling either
by (A) implementing and registering a top-level `@pragma`('vm:entry-point')
callback and pass it to NotificationService (register it with
onDidReceiveBackgroundNotificationResponse) or (B) after startup call
NotificationService.instance.getNotificationAppLaunchDetails() (or
flutter_local_notifications' getNotificationAppLaunchDetails()) in main.dart
right after initialize() and requestPermissions() and handle any returned
payload to perform the navigation/deep-link; update NotificationService to
expose a method to process that payload and reuse the same navigation logic as
onDidReceiveNotificationResponse.

In `@flutter_app/lib/pages/mood_tracker.dart`:
- Around line 183-188: The current heuristic computes avgMood over all _entries
and _lowMoodStreak() counts only streaks that reach today, which lets old data
mask recent downturns and drops streaks when today is missing; change both
avgMood and the streak logic to operate on a bounded recent window (e.g., last N
days) and make the streak anchored to the most recent logged entry rather than
today. Specifically, replace the global avgMood calculation (the fold over
_entries) with an average over a slice/filter of _entries limited to the recent
window, and update _lowMoodStreak() to compute consecutive low days ending at
the latest entry date (not necessarily today); then recompute
shouldSuggestStress from that windowed avg and the anchored streak. Ensure
references to _entries, avgMood, _lowMoodStreak(), and shouldSuggestStress are
updated consistently (also apply the same windowed approach to the other related
block around the 214-233 region).
- Around line 156-164: The code currently cancels the recurring reminder
(NotificationService.cancel(1)) before re-scheduling in
_load()/_updateReminders(), which removes the user's daily 8PM reminder if they
don't reopen the screen; instead, stop cancelling the reminder
unconditionally—change _updateReminders() to check whether the repeating
reminder is already scheduled (use a NotificationService method like isScheduled
or listScheduledNotifications) and only call
NotificationService.scheduleDailyReminder(...) when it is not present, and
remove the unconditional NotificationService.cancel(1) call; reference _load(),
_updateReminders(), NotificationService.scheduleDailyReminder and
NotificationService.cancel to locate and update the logic.
- Around line 171-177: Replace the privacy-revealing notification text used in
calls to NotificationService.instance.scheduleDailyReminder (e.g., the title
'Quick mood check-in' and body "You haven’t tracked your mood today. Tap to log
it!") with neutral, non-health-specific copy (for example a generic reminder
title like "Reminder" and a body like "Tap to open the app.") in both
occurrences (the call around scheduleDailyReminder at the 171–177 block and the
later 191–207 block); keep the payload/deep link as-is so the app can reveal the
specific mood-tracking action after unlock.

In `@flutter_app/lib/services/chat_service.dart`:
- Around line 130-166: The patient context currently sent to OpenRouter lacks an
explicit non-HIPAA disclosure; update the consent/privacy text that is
concatenated into the prompt (where _adminPrompt + _basePrompt + patientContext
are assembled in chat_service.dart) to add an explicit, prominent warning
stating that OpenRouter is not HIPAA-compliant, does not sign BAAs, that
consenting users accept this compliance gap and that sharing real patient data
will transmit protected health information to a non-HIPAA-covered entity; also
add a recommendation to avoid sending regulated patient data in production and
to consider HIPAA/BAA-capable providers as alternatives. Ensure this disclosure
appears before any patient details are appended to patientContext and is
triggered/visible whenever sensitive fields (medical_conditions,
current_medications, mental_health_concerns, therapy_history) are non-empty so
users see the warning prior to consenting.

In `@flutter_app/lib/services/notification_service.dart`:
- Around line 51-60: The requestPermissions() method only handles iOS—add
Android 13+ runtime permission handling: inside requestPermissions(), detect
Android (Platform.isAndroid) and check SDK_INT >= 33, then request the
POST_NOTIFICATIONS permission (e.g., via the permission_handler package using
Permission.notification or Permission.postNotifications) and handle the
granted/denied result before proceeding; also ensure you declare
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" /> in
AndroidManifest.xml for apps targeting API 33+. Reference the
requestPermissions() method and the _plugin field when locating where to add the
Android branch.
- Around line 102-123: The call to _plugin.zonedSchedule is missing the required
androidScheduleMode parameter for flutter_local_notifications v13+; replace the
androidAllowWhileIdle: true argument in the zonedSchedule invocation with
androidScheduleMode: AndroidScheduleMode.exactAllowWhileIdle so the call
signature matches the newer API and preserves the "allow while idle" behavior
(update the zonedSchedule call in notification_service.dart where
_plugin.zonedSchedule is invoked).

---

Outside diff comments:
In `@flutter_app/lib/pages/mood_tracker.dart`:
- Around line 1354-1581: There is an orphaned top-level "@override Widget
build(BuildContext context)" block that should be deleted; remove the entire
trailing build(...) {...} block shown in the diff (from the "@override Widget
build(BuildContext context)" line through its matching closing brace) so the
file no longer contains a standalone build function, and then verify any
surrounding widget classes (e.g., the Mood tracker page class) still have their
correct build methods and balanced braces.

---

Nitpick comments:
In `@flutter_app/lib/main.dart`:
- Around line 21-25: Replace the hardcoded Supabase URL and anon key in the
Supabase.initialize call with values read from the loaded environment
(dotenv.env); update the call in main.dart where Supabase.initialize(...) is
invoked so it uses dotenv.env['SUPABASE_URL'] and
dotenv.env['SUPABASE_ANON_KEY'] (or sensible env var names) instead of the
literal strings, and add a fallback or assert that these env values are present
before calling Supabase.initialize to avoid null values at runtime.

In `@flutter_app/lib/pages/admin_maya_chat_page.dart`:
- Around line 304-343: The Container around the input bar uses a hardcoded color
(color: Colors.white) which breaks dark mode; replace that hardcoded value with
a theme-aware color (e.g., use Theme.of(context).colorScheme.surface or
Theme.of(context).cardColor/scaffoldBackgroundColor) so the background adapts to
light/dark themes, and ensure the inner TextField fillColor (fillColor:
Colors.grey.shade200) is also switched to a theme-aware alternative (e.g.,
Theme.of(context).colorScheme.background or a shade derived from Theme) to
maintain proper contrast; update the Container and TextField properties in the
build where SafeArea/Container/TextField are defined.
- Around line 62-67: The catch block that clears _patients and sets
_isFetchingPatients to false silently swallows errors; update the error handling
in the fetch method (the catch in the function that calls setState with
_patients/_isFetchingPatients) to both surface user feedback and preserve
existing state updates: set a new state field like _patientFetchError (or reuse
an existing error state) inside setState, and show a user-facing message via
ScaffoldMessenger.of(context).showSnackBar(...) or display an inline widget
bound to _patientFetchError so doctors see why the patient list failed to load;
keep the existing _patients = [] and _isFetchingPatients = false updates.

In `@flutter_app/lib/pages/article_page.dart`:
- Line 60: Replace the raw technical fallback by passing a page-specific
fallback string into friendlyErrorMessage wherever _popularError is set (e.g.,
the assignments in ArticlePage that currently read "_popularError =
friendlyErrorMessage(e);"); update both occurrences (around the current lines
where _popularError is assigned) to call friendlyErrorMessage(e, "Unable to load
popular articles right now. Please try again later.") or similarly worded
page-specific copy so the inline error panel shows user-friendly, actionable
text.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: fb126115-e0eb-467a-8c28-24f87bf201c4

📥 Commits

Reviewing files that changed from the base of the PR and between 16e5f27 and f042b77.

⛔ Files ignored due to path filters (1)
  • flutter_app/pubspec.lock is excluded by !**/*.lock
📒 Files selected for processing (21)
  • flutter_app/lib/core/error_messages.dart
  • flutter_app/lib/main.dart
  • flutter_app/lib/pages/admin_dashboard.dart
  • flutter_app/lib/pages/admin_maya_chat_page.dart
  • flutter_app/lib/pages/ai_chat.dart
  • flutter_app/lib/pages/article_page.dart
  • flutter_app/lib/pages/doctor_chat_page.dart
  • flutter_app/lib/pages/maya_consent_page.dart
  • flutter_app/lib/pages/meeting_requests_page.dart
  • flutter_app/lib/pages/mood_tracker.dart
  • flutter_app/lib/pages/onboarding_page.dart
  • flutter_app/lib/pages/online_meet_page.dart
  • flutter_app/lib/pages/past_interactions_page.dart
  • flutter_app/lib/pages/past_meetings_page.dart
  • flutter_app/lib/pages/patient_list_page.dart
  • flutter_app/lib/pages/profile_page.dart
  • flutter_app/lib/pages/scheduled_meetings_page.dart
  • flutter_app/lib/services/chat_service.dart
  • flutter_app/lib/services/notification_service.dart
  • flutter_app/macos/Flutter/GeneratedPluginRegistrant.swift
  • flutter_app/pubspec.yaml

Comment on lines +7 to +21
final message = error.toString();

// Network / connectivity issues we want to hide from users
if (message.contains('Connection reset') ||
message.contains('Failed host lookup') ||
message.contains('Network is unreachable') ||
message.contains('Connection refused') ||
message.contains('Connection timed out') ||
message.contains('SocketException') ||
message.contains('HttpException')) {
return fallback ??
'Oops—looks like we hit a connection hiccup. Please try again in a moment — don\'t fret, it\'s on us.';
}

return message;
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Harden matching and sanitize common exception prefixes.

Line 10–16 currently depends on case-sensitive matching, and Line 21 can still surface raw strings like Exception: ... to users. That weakens the “friendly” behavior in real-world failures.

Proposed patch
 String friendlyErrorMessage(Object error, {String? fallback}) {
-  final message = error.toString();
+  final message = error.toString().trim();
+  final normalized = message.toLowerCase();

   // Network / connectivity issues we want to hide from users
-  if (message.contains('Connection reset') ||
-      message.contains('Failed host lookup') ||
-      message.contains('Network is unreachable') ||
-      message.contains('Connection refused') ||
-      message.contains('Connection timed out') ||
-      message.contains('SocketException') ||
-      message.contains('HttpException')) {
+  if (normalized.contains('connection reset') ||
+      normalized.contains('failed host lookup') ||
+      normalized.contains('network is unreachable') ||
+      normalized.contains('connection refused') ||
+      normalized.contains('connection timed out') ||
+      normalized.contains('socketexception') ||
+      normalized.contains('httpexception')) {
     return fallback ??
         'Oops—looks like we hit a connection hiccup. Please try again in a moment — don\'t fret, it\'s on us.';
   }

+  if (message.startsWith('Exception: ')) {
+    return message.replaceFirst('Exception: ', '');
+  }
+
   return message;
 }
📝 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
final message = error.toString();
// Network / connectivity issues we want to hide from users
if (message.contains('Connection reset') ||
message.contains('Failed host lookup') ||
message.contains('Network is unreachable') ||
message.contains('Connection refused') ||
message.contains('Connection timed out') ||
message.contains('SocketException') ||
message.contains('HttpException')) {
return fallback ??
'Oops—looks like we hit a connection hiccup. Please try again in a moment — don\'t fret, it\'s on us.';
}
return message;
final message = error.toString().trim();
final normalized = message.toLowerCase();
// Network / connectivity issues we want to hide from users
if (normalized.contains('connection reset') ||
normalized.contains('failed host lookup') ||
normalized.contains('network is unreachable') ||
normalized.contains('connection refused') ||
normalized.contains('connection timed out') ||
normalized.contains('socketexception') ||
normalized.contains('httpexception')) {
return fallback ??
'Oops—looks like we hit a connection hiccup. Please try again in a moment — don\'t fret, it\'s on us.';
}
if (message.startsWith('Exception: ')) {
return message.replaceFirst('Exception: ', '');
}
return message;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@flutter_app/lib/core/error_messages.dart` around lines 7 - 21, Make the
matching case-insensitive and sanitize common exception prefixes before
returning to users: normalize the captured message (e.g., store a trimmed copy
and a lowercase version) and use the lowercase form for contains checks instead
of the current case-sensitive checks on message; then strip common leading
prefixes such as "Exception:", "Error:", "FormatException:", "SocketException:"
(and any alphabetic word followed by a colon) from the user-facing message
variable so raw exception labels aren’t shown; ensure the function still returns
fallback when matches occur and otherwise returns the sanitized message.

Comment on lines +27 to +28
await NotificationService.instance.initialize(navigatorKey: navigatorKey);
await NotificationService.instance.requestPermissions();
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🌐 Web query:

flutter_local_notifications onDidReceiveBackgroundNotificationResponse setup

💡 Result:

To set up onDidReceiveBackgroundNotificationResponse for flutter_local_notifications, follow these steps for handling notification taps/responses when the app is in the background or terminated, particularly for notification actions. ## Dart Code Setup 1. Create a top-level or static function marked with @pragma('vm:entry-point'): @pragma('vm:entry-point') void notificationTapBackground(NotificationResponse notificationResponse) { debugPrint('Background notification tapped: ${notificationResponse.payload}'); // Handle the response here (e.g., process payload, schedule tasks) // Note: Cannot access Flutter UI directly; use isolates or shared storage } 2. Initialize the plugin, passing the function directly (not wrapped in a lambda): final FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin = FlutterLocalNotificationsPlugin; await flutterLocalNotificationsPlugin.initialize( initializationSettings, onDidReceiveNotificationResponse: (NotificationResponse response) { // Foreground/background (app running) handling }, onDidReceiveBackgroundNotificationResponse: notificationTapBackground, // Direct reference ); ## Platform-Specific Setup ### Android - No additional configuration needed. - Ensure proper AndroidManifest.xml for scheduled notifications if applicable. - Add receiver for actions if using notification actions: ### iOS (Critical for background handling) Required for background isolate support when app is terminated: 1. In ios/Runner/AppDelegate.swift (or .m): import Flutter import flutter_local_notifications // Add this import // Add this function (Objective-C example for .m equivalent in Swift): FlutterLocalNotificationsPlugin.setPluginRegistrantCallback { (registry) in GeneratedPluginRegistrant.register(with: registry) } 2. In application(_:didFinishLaunchingWithOptions:): [GeneratedPluginRegistrant registerWithRegistry:self]; FlutterLocalNotificationsPlugin.setPluginRegistrantCallback(registerPlugins); 3. Update Info.plist with background modes if needed: UIBackgroundModes fetch remote-notification ## Key Notes - This callback is primarily for notification actions (e.g., reply, mark as read) when app is terminated/background isolate. - For simple notification taps when terminated: Use getNotificationAppLaunchDetails after app starts. - Callback runs in background isolate: no UI access, keep operations lightweight. - Test on physical devices; simulators may not trigger background callbacks properly. - Latest package version (21.0.0 as of 2026) recommended. Common errors: - Wrapping function in lambda: use direct reference. - Missing iOS AppDelegate setup: background won't work. - Not top-level/static: compile error.

Citations:


🏁 Script executed:

fd -e dart -e swift -e java | grep -i notification | head -20

Repository: OpenLake/GetWelPlus

Length of output: 113


🏁 Script executed:

find . -type f -name "*.dart" | xargs rg "class NotificationService" -l

Repository: OpenLake/GetWelPlus

Length of output: 115


🏁 Script executed:

rg "onDidReceiveBackgroundNotificationResponse" --type dart

Repository: OpenLake/GetWelPlus

Length of output: 45


🏁 Script executed:

cat -n ./flutter_app/lib/services/notification_service.dart

Repository: OpenLake/GetWelPlus

Length of output: 4985


🏁 Script executed:

rg "getNotificationAppLaunchDetails" ./flutter_app --type dart

Repository: OpenLake/GetWelPlus

Length of output: 45


Notification taps won't navigate when the app is terminated.

The NotificationService only registers onDidReceiveNotificationResponse, which handles taps when the app is running. For taps when the app is fully terminated, flutter_local_notifications requires either:

  1. A separate @pragma('vm:entry-point') top-level callback passed to onDidReceiveBackgroundNotificationResponse, or
  2. Checking getNotificationAppLaunchDetails() after app startup for cold-launch navigation

If cold-start deep-linking from notifications is a requirement, consider adding one of these handlers. Otherwise, this is acceptable for MVP.

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

In `@flutter_app/lib/main.dart` around lines 27 - 28, The app currently only
registers NotificationService.instance.onDidReceiveNotificationResponse (via
NotificationService.initialize), so taps on notifications when the app is
terminated won't navigate on cold-start; to fix, add cold-launch handling either
by (A) implementing and registering a top-level `@pragma`('vm:entry-point')
callback and pass it to NotificationService (register it with
onDidReceiveBackgroundNotificationResponse) or (B) after startup call
NotificationService.instance.getNotificationAppLaunchDetails() (or
flutter_local_notifications' getNotificationAppLaunchDetails()) in main.dart
right after initialize() and requestPermissions() and handle any returned
payload to perform the navigation/deep-link; update NotificationService to
expose a method to process that payload and reuse the same navigation logic as
onDidReceiveNotificationResponse.

Comment on lines 156 to +164
Future<void> _load() async {
final entries = await _loadEntries();
if (mounted) setState(() { _entries = entries; _loading = false; });
if (mounted) {
setState(() {
_entries = entries;
_loading = false;
});
await _updateReminders();
}
Copy link

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 cancel the only recurring mood reminder from this page.

NotificationService.scheduleDailyReminder(...) is a repeating schedule. Once cancel(1) runs, tomorrow’s 8 PM reminder is gone too, and this code only recreates it when _load() runs again. A user who logs mood today and never reopens this screen tomorrow will stop getting reminders entirely.

Also applies to: 167-181

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

In `@flutter_app/lib/pages/mood_tracker.dart` around lines 156 - 164, The code
currently cancels the recurring reminder (NotificationService.cancel(1)) before
re-scheduling in _load()/_updateReminders(), which removes the user's daily 8PM
reminder if they don't reopen the screen; instead, stop cancelling the reminder
unconditionally—change _updateReminders() to check whether the repeating
reminder is already scheduled (use a NotificationService method like isScheduled
or listScheduledNotifications) and only call
NotificationService.scheduleDailyReminder(...) when it is not present, and
remove the unconditional NotificationService.cancel(1) call; reference _load(),
_updateReminders(), NotificationService.scheduleDailyReminder and
NotificationService.cancel to locate and update the logic.

Comment on lines +171 to +177
await NotificationService.instance.scheduleDailyReminder(
id: 1,
title: 'Quick mood check-in',
body: "You haven’t tracked your mood today. Tap to log it!",
payload: 'mood',
hour: 20,
minute: 0,
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Use neutral notification copy for health-related reminders.

These titles and bodies disclose mental-health context on the lock screen. In a wellness app, prompts like “Feeling stressed?” and “Book a quick session with a doctor...” are privacy-sensitive. Prefer generic wording and let the deep link reveal the specific action after unlock.

🔒 Suggested wording
-        title: 'Quick mood check-in',
-        body: "You haven’t tracked your mood today. Tap to log it!",
+        title: 'GetWel+ reminder',
+        body: 'Open the app for today’s check-in.',

-        title: 'Feeling stressed?',
-        body: 'Take a quick stress check and see how you’re doing.',
+        title: 'GetWel+ reminder',
+        body: 'Open the app for your next check-in.',

-        title: 'Need a check-in?',
-        body: 'Book a quick session with a doctor if you’re feeling overwhelmed.',
+        title: 'GetWel+ reminder',
+        body: 'Open the app to review support options.',

Also applies to: 191-207

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

In `@flutter_app/lib/pages/mood_tracker.dart` around lines 171 - 177, Replace the
privacy-revealing notification text used in calls to
NotificationService.instance.scheduleDailyReminder (e.g., the title 'Quick mood
check-in' and body "You haven’t tracked your mood today. Tap to log it!") with
neutral, non-health-specific copy (for example a generic reminder title like
"Reminder" and a body like "Tap to open the app.") in both occurrences (the call
around scheduleDailyReminder at the 171–177 block and the later 191–207 block);
keep the payload/deep link as-is so the app can reveal the specific
mood-tracking action after unlock.

Comment on lines +183 to +188
// If mood has been low recently, suggest a stress check.
final longLowMood = _lowMoodStreak();
final avgMood = _entries.isEmpty
? 5.0
: _entries.fold(0, (s, e) => s + e.moodLevel) / _entries.length;
final shouldSuggestStress = avgMood <= 2.5 || longLowMood >= 3;
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

The low-mood heuristic doesn’t actually track recent trends.

avgMood is computed over the entire history, while _lowMoodStreak() only counts streaks that reach today. Older positive months can suppress alerts during a current downturn, and a user with several low days ending yesterday drops to 0 as soon as today is missing. Use a bounded recent window and anchor the streak to the latest logged day instead.

Also applies to: 214-233

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

In `@flutter_app/lib/pages/mood_tracker.dart` around lines 183 - 188, The current
heuristic computes avgMood over all _entries and _lowMoodStreak() counts only
streaks that reach today, which lets old data mask recent downturns and drops
streaks when today is missing; change both avgMood and the streak logic to
operate on a bounded recent window (e.g., last N days) and make the streak
anchored to the most recent logged entry rather than today. Specifically,
replace the global avgMood calculation (the fold over _entries) with an average
over a slice/filter of _entries limited to the recent window, and update
_lowMoodStreak() to compute consecutive low days ending at the latest entry date
(not necessarily today); then recompute shouldSuggestStress from that windowed
avg and the anchored streak. Ensure references to _entries, avgMood,
_lowMoodStreak(), and shouldSuggestStress are updated consistently (also apply
the same windowed approach to the other related block around the 214-233
region).

Comment on lines +130 to +166
// build concise patient summary
final name = _patientProfile!['full_name'] ?? '';
final age = _patientProfile!['age'] ?? '';
final conditions = _patientProfile!['medical_conditions'] ?? '';
final medications = _patientProfile!['current_medications'] ?? '';
final concerns = _patientProfile!['mental_health_concerns'] ?? '';
final therapyHistory = _patientProfile!['therapy_history'] ?? '';

final contextParts = <String>[];
if (name.toString().isNotEmpty) {
contextParts.add('Name: $name');
}
if (age.toString().isNotEmpty && age != 0) {
contextParts.add('Age: $age');
}
if (conditions.toString().isNotEmpty) {
contextParts.add('Medical conditions: $conditions');
}
if (medications.toString().isNotEmpty) {
contextParts.add('Current medications: $medications');
}
if (concerns.toString().isNotEmpty) {
contextParts.add('Main concerns: $concerns');
}
if (therapyHistory.toString().isNotEmpty) {
contextParts.add('Therapy history: $therapyHistory');
}

final patientContext = '''

PATIENT SUMMARY (use for context when responding):
${contextParts.join('. ')}.

When responding, remain respectful of privacy, keep tone supportive, and avoid making definitive medical diagnoses.
''';

return _adminPrompt + _basePrompt + patientContext;
Copy link

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

🌐 Web query:

OpenRouter HIPAA BAA compliance healthcare data

💡 Result:

OpenRouter does not offer HIPAA compliance or Business Associate Agreements (BAAs). It provides privacy features like Zero Data Retention (ZDR) routing to providers that do not log or train on prompts, opt-in prompt logging, metadata-only storage by default, and enterprise options such as EU in-region routing and SOC-2 compliance mentions, but no explicit HIPAA or BAA support in official docs. OpenRouter routes requests to third-party AI providers, each with their own policies (e.g., some like OpenAI offer BAAs separately). For healthcare data (PHI), users must ensure selected providers comply with HIPAA requirements, as OpenRouter itself does not act as a HIPAA business associate. Contact OpenRouter enterprise sales for custom needs, but no public evidence of BAA availability as of 2026-03-19.

Citations:


🏁 Script executed:

# Search for context around the _buildSystemPrompt method and patient data handling
cd flutter_app && find . -name "*.dart" -type f | head -20

Repository: OpenLake/GetWelPlus

Length of output: 673


🏁 Script executed:

# Look for consent mechanisms, demo flags, or synthetic data indicators
rg -i "demo|synthetic|test.*data|consent|hipaa|privacy" --type dart flutter_app/lib

Repository: OpenLake/GetWelPlus

Length of output: 3593


🏁 Script executed:

# Check the chat_service.dart file more broadly to understand data flow
wc -l flutter_app/lib/services/chat_service.dart

Repository: OpenLake/GetWelPlus

Length of output: 109


🏁 Script executed:

# Check the consent page content and privacy disclosures
cat -n flutter_app/lib/pages/maya_consent_page.dart

Repository: OpenLake/GetWelPlus

Length of output: 8930


🏁 Script executed:

# Check how AI chat page uses the consent flag
cat -n flutter_app/lib/pages/ai_chat.dart | head -100

Repository: OpenLake/GetWelPlus

Length of output: 3486


🏁 Script executed:

# Verify if chatService respects the consent flag
rg "sharesMedicalData|maya_data_consent" flutter_app/lib/services/chat_service.dart -A 3 -B 3

Repository: OpenLake/GetWelPlus

Length of output: 45


🏁 Script executed:

# Check how ChatService constructor uses the usePersonalData parameter
cat -n flutter_app/lib/services/chat_service.dart | head -50

Repository: OpenLake/GetWelPlus

Length of output: 2294


🏁 Script executed:

# Search for usePersonalData usage in chat_service.dart
rg "usePersonalData" flutter_app/lib/services/chat_service.dart -B 2 -A 5

Repository: OpenLake/GetWelPlus

Length of output: 1215


Strengthen compliance disclosures before production use with real patient data.

OpenRouter does not offer HIPAA Business Associate Agreements or BAA compliance. If users consent to share medical data (medical conditions, medications, mental health concerns), that data goes to OpenRouter and downstream providers without HIPAA protections. While the app properly implements an opt-in consent model and users can decline data sharing entirely, the privacy notice should explicitly state:

  1. OpenRouter is not HIPAA-compliant and does not sign BAAs
  2. Consenting users accept this compliance gap
  3. For real patient data, users should understand they are sharing health information with a non-HIPAA-covered entity
  4. Consider alternative AI providers with HIPAA/BAA support before handling regulated patient data in production

The current consent mechanism mitigates risk for users who decline, but does not resolve the underlying compliance constraint for users who consent.

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

In `@flutter_app/lib/services/chat_service.dart` around lines 130 - 166, The
patient context currently sent to OpenRouter lacks an explicit non-HIPAA
disclosure; update the consent/privacy text that is concatenated into the prompt
(where _adminPrompt + _basePrompt + patientContext are assembled in
chat_service.dart) to add an explicit, prominent warning stating that OpenRouter
is not HIPAA-compliant, does not sign BAAs, that consenting users accept this
compliance gap and that sharing real patient data will transmit protected health
information to a non-HIPAA-covered entity; also add a recommendation to avoid
sending regulated patient data in production and to consider HIPAA/BAA-capable
providers as alternatives. Ensure this disclosure appears before any patient
details are appended to patientContext and is triggered/visible whenever
sensitive fields (medical_conditions, current_medications,
mental_health_concerns, therapy_history) are non-empty so users see the warning
prior to consenting.

Comment on lines +51 to +60
/// Requests platform permissions (mainly iOS/macOS) for notifications.
Future<void> requestPermissions() async {
final iosPlugin =
_plugin.resolvePlatformSpecificImplementation<IOSFlutterLocalNotificationsPlugin>();
await iosPlugin?.requestPermissions(
alert: true,
badge: true,
sound: true,
);
}
Copy link

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

🌐 Web query:

flutter_local_notifications Android 13 POST_NOTIFICATIONS permission

💡 Result:

For flutter_local_notifications on Android 13+, apps targeting API 33+ must declare the POST_NOTIFICATIONS permission in AndroidManifest.xml and request it at runtime using the plugin's API. Key steps: 1. Update android/app/build.gradle: compileSdkVersion 33 (or higher) targetSdkVersion 33 (or higher) 2. Add to android/app/src/main/AndroidManifest.xml (inside ): The plugin (v16+) already includes this minimally, but explicitly adding ensures compatibility. For scheduled notifications, add more like SCHEDULE_EXACT_ALARM. 3. Request permission in Dart code: final FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin = FlutterLocalNotificationsPlugin; final AndroidFlutterLocalNotificationsPlugin? androidImplementation = flutterLocalNotificationsPlugin.resolvePlatformSpecificImplementation; await androidImplementation?.requestNotificationsPermission; Without this, notifications won't show on Android 13+ devices as they're blocked by default. The plugin supports this since v17.0.1. Check permission status before showing notifications. Example app on pub.dev shows full AndroidManifest.xml setup. Use permission_handler for unified checks: await Permission.notification.request; Sources confirm this from official pub.dev docs and changelogs.

Citations:


🌐 Web query:

flutter_local_notifications AndroidFlutterLocalNotificationsPlugin requestNotificationsPermission API

💡 Result:

The AndroidFlutterLocalNotificationsPlugin class in the flutter_local_notifications package provides the requestNotificationsPermission method to request the POST_NOTIFICATIONS permission on Android 13 (API level 33) and higher. This is necessary for displaying notifications, as the permission is not auto-granted on these versions. Usage example (after plugin initialization): final AndroidFlutterLocalNotificationsPlugin? androidImplementation = flutterLocalNotificationsPlugin.resolvePlatformSpecificImplementation< AndroidFlutterLocalNotificationsPlugin>; final bool? granted = await androidImplementation?.requestNotificationsPermission; Add to AndroidManifest.xml for apps targeting API 33+. The method returns a Future<bool?> indicating if permission was granted. It may trigger app lifecycle changes or direct to settings if previously denied. Note: Older plugin versions used requestPermission (renamed in v16+). For checking status without requesting, use areNotificationsEnabled.

Citations:


Missing Android 13+ notification permission request.

On Android 13 (API 33+), apps must request the POST_NOTIFICATIONS permission at runtime. The current implementation only handles iOS and will silently fail on newer Android devices.

🔧 Suggested fix
   Future<void> requestPermissions() async {
+    // Android 13+ requires runtime permission
+    final androidPlugin = _plugin
+        .resolvePlatformSpecificImplementation<AndroidFlutterLocalNotificationsPlugin>();
+    await androidPlugin?.requestNotificationsPermission;
+
     final iosPlugin =
         _plugin.resolvePlatformSpecificImplementation<IOSFlutterLocalNotificationsPlugin>();
     await iosPlugin?.requestPermissions(
       alert: true,
       badge: true,
       sound: true,
     );
   }

Also ensure <uses-permission android:name="android.permission.POST_NOTIFICATIONS" /> is declared in android/app/src/main/AndroidManifest.xml for apps targeting API 33+.

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

In `@flutter_app/lib/services/notification_service.dart` around lines 51 - 60, The
requestPermissions() method only handles iOS—add Android 13+ runtime permission
handling: inside requestPermissions(), detect Android (Platform.isAndroid) and
check SDK_INT >= 33, then request the POST_NOTIFICATIONS permission (e.g., via
the permission_handler package using Permission.notification or
Permission.postNotifications) and handle the granted/denied result before
proceeding; also ensure you declare <uses-permission
android:name="android.permission.POST_NOTIFICATIONS" /> in AndroidManifest.xml
for apps targeting API 33+. Reference the requestPermissions() method and the
_plugin field when locating where to add the Android branch.

Comment on lines +102 to +123
await _plugin.zonedSchedule(
id,
title,
body,
_nextInstanceOf(hour: hour, minute: minute),
const NotificationDetails(
android: AndroidNotificationDetails(
'getwelplus_reminders',
'Reminders',
channelDescription: 'Daily wellness and mood reminders',
importance: Importance.high,
priority: Priority.high,
playSound: true,
),
iOS: DarwinNotificationDetails(),
),
payload: payload,
androidAllowWhileIdle: true,
uiLocalNotificationDateInterpretation:
UILocalNotificationDateInterpretation.absoluteTime,
matchDateTimeComponents: DateTimeComponents.time,
);
Copy link

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

🌐 Web query:

flutter_local_notifications zonedSchedule androidScheduleMode parameter

💡 Result:

The androidScheduleMode parameter in the zonedSchedule method of the flutter_local_notifications plugin specifies how Android schedules the notification, balancing timing precision with battery efficiency and permissions. It is a required parameter of type AndroidScheduleMode enum. Usage example from official docs: await flutterLocalNotificationsPlugin.zonedSchedule( id: 0, scheduledDate: scheduledDate, notificationDetails: notificationDetails, androidScheduleMode: AndroidScheduleMode.inexactAllowWhileIdle, // example value // other params... ); Available AndroidScheduleMode values (from official API docs): - inexact: Roughly at the specified time; may not execute in low-power idle mode (Doze). - inexactAllowWhileIdle: Roughly at the specified time; executes even in low-power idle mode. - exact: Exactly at the specified time; may not execute in low-power idle mode. - exactAllowWhileIdle: Exactly at the specified time; executes even in low-power idle mode. - alarmClock: Exactly at the specified time using AlarmManager's alarm clock feature; works in low-power idle mode. Requires SCHEDULE_EXACT_ALARM permission (Android 12+), which apps must declare as "normal" priority and users must grant explicitly via settings (especially Android 14+). For non-alarm apps, inexactAllowWhileIdle is recommended as it works without special permissions while allowing scheduling when idle. Exact modes may be deferred by Android's power management unless permitted. Additional AndroidManifest.xml receivers are needed for scheduling to work reliably (e.g., ScheduledNotificationReceiver, BootCompletedReceiver, ResumeCompletedReceiver, TzTransitionReceiver).

Citations:


🏁 Script executed:

find . -name "notification_service.dart" -type f

Repository: OpenLake/GetWelPlus

Length of output: 115


🏁 Script executed:

find . -name "pubspec.yaml" -type f | head -5

Repository: OpenLake/GetWelPlus

Length of output: 89


🏁 Script executed:

# Search for androidAllowWhileIdle usage in the codebase
rg "androidAllowWhileIdle" --type dart

Repository: OpenLake/GetWelPlus

Length of output: 148


🏁 Script executed:

cat -n ./flutter_app/pubspec.yaml | grep -A 5 -B 5 "flutter_local_notifications"

Repository: OpenLake/GetWelPlus

Length of output: 417


🏁 Script executed:

sed -n '90,130p' ./flutter_app/lib/services/notification_service.dart

Repository: OpenLake/GetWelPlus

Length of output: 1115


🌐 Web query:

flutter_local_notifications v13 androidScheduleMode API zonedSchedule

💡 Result:

In flutter_local_notifications version 13 and later, the zonedSchedule method requires an AndroidScheduleMode parameter to control how notifications are scheduled on Android devices. This enum determines the timing precision and behavior under battery optimizations like Doze mode. AndroidScheduleMode values: - inexact: Roughly on time, may not execute while idle (most battery efficient). - inexactAllowWhileIdle: Roughly on time, allows execution while idle. - exact: Exactly on time, but may not execute while idle. - exactAllowWhileIdle: Exactly on time, allows execution while idle (requires SCHEDULE_EXACT_ALARM permission on Android 12+). - alarmClock: Exactly on time using AlarmManager's setAlarmClock (highest priority, requires permission). Usage example: await flutterLocalNotificationsPlugin.zonedSchedule( 0, 'title', 'body', tz.TZDateTime.now(tz.local).add(Duration(seconds: 10)), const NotificationDetails( android: AndroidNotificationDetails('channel_id', 'channel_name'), ), androidScheduleMode: AndroidScheduleMode.exactAllowWhileIdle, uiLocalNotificationDateInterpretation: UILocalNotificationDateInterpretation.absoluteTime, ); For exact timing on modern Android (13+), request exact alarm permission: final androidPlugin = flutterLocalNotificationsPlugin.resolvePlatformSpecificImplementation; await androidPlugin?.requestExactAlarmsPermission; Add to AndroidManifest.xml for scheduling: Permissions for exact alarms (Android 12+): Note: v13 introduced or stabilized this API; it's required in zonedSchedule for Android scheduling precision control. Choose mode based on timing needs vs. battery impact. Inexact modes don't need special permissions.

Citations:


🏁 Script executed:

sed -n '102,130p' ./flutter_app/lib/services/notification_service.dart

Repository: OpenLake/GetWelPlus

Length of output: 888


Add the required androidScheduleMode parameter — missing in v13.0.0+.

The zonedSchedule method requires an androidScheduleMode parameter in flutter_local_notifications v13 and later. The current code omits this required parameter, which is incompatible with the declared dependency version. Replace androidAllowWhileIdle: true with androidScheduleMode: AndroidScheduleMode.exactAllowWhileIdle.

🔧 Suggested fix
     await _plugin.zonedSchedule(
       id,
       title,
       body,
       _nextInstanceOf(hour: hour, minute: minute),
       const NotificationDetails(
         android: AndroidNotificationDetails(
           'getwelplus_reminders',
           'Reminders',
           channelDescription: 'Daily wellness and mood reminders',
           importance: Importance.high,
           priority: Priority.high,
           playSound: true,
         ),
         iOS: DarwinNotificationDetails(),
       ),
       payload: payload,
-      androidAllowWhileIdle: true,
+      androidScheduleMode: AndroidScheduleMode.exactAllowWhileIdle,
       uiLocalNotificationDateInterpretation:
           UILocalNotificationDateInterpretation.absoluteTime,
       matchDateTimeComponents: DateTimeComponents.time,
     );
📝 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
await _plugin.zonedSchedule(
id,
title,
body,
_nextInstanceOf(hour: hour, minute: minute),
const NotificationDetails(
android: AndroidNotificationDetails(
'getwelplus_reminders',
'Reminders',
channelDescription: 'Daily wellness and mood reminders',
importance: Importance.high,
priority: Priority.high,
playSound: true,
),
iOS: DarwinNotificationDetails(),
),
payload: payload,
androidAllowWhileIdle: true,
uiLocalNotificationDateInterpretation:
UILocalNotificationDateInterpretation.absoluteTime,
matchDateTimeComponents: DateTimeComponents.time,
);
await _plugin.zonedSchedule(
id,
title,
body,
_nextInstanceOf(hour: hour, minute: minute),
const NotificationDetails(
android: AndroidNotificationDetails(
'getwelplus_reminders',
'Reminders',
channelDescription: 'Daily wellness and mood reminders',
importance: Importance.high,
priority: Priority.high,
playSound: true,
),
iOS: DarwinNotificationDetails(),
),
payload: payload,
androidScheduleMode: AndroidScheduleMode.exactAllowWhileIdle,
uiLocalNotificationDateInterpretation:
UILocalNotificationDateInterpretation.absoluteTime,
matchDateTimeComponents: DateTimeComponents.time,
);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@flutter_app/lib/services/notification_service.dart` around lines 102 - 123,
The call to _plugin.zonedSchedule is missing the required androidScheduleMode
parameter for flutter_local_notifications v13+; replace the
androidAllowWhileIdle: true argument in the zonedSchedule invocation with
androidScheduleMode: AndroidScheduleMode.exactAllowWhileIdle so the call
signature matches the newer API and preserves the "allow while idle" behavior
(update the zonedSchedule call in notification_service.dart where
_plugin.zonedSchedule is invoked).

Copy link

@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: 2

♻️ Duplicate comments (1)
flutter_app/lib/services/notification_service.dart (1)

106-127: ⚠️ Potential issue | 🟠 Major

Replace deprecated androidAllowWhileIdle with androidScheduleMode.

The androidAllowWhileIdle parameter was replaced by androidScheduleMode in flutter_local_notifications v13+. This code will fail to compile with the declared dependency version.

🔧 Suggested fix
     await _plugin.zonedSchedule(
       id,
       title,
       body,
       _nextInstanceOf(hour: hour, minute: minute),
       const NotificationDetails(
         android: AndroidNotificationDetails(
           'getwelplus_reminders',
           'Reminders',
           channelDescription: 'Daily wellness and mood reminders',
           importance: Importance.high,
           priority: Priority.high,
           playSound: true,
         ),
         iOS: DarwinNotificationDetails(),
       ),
       payload: payload,
-      androidAllowWhileIdle: true,
+      androidScheduleMode: AndroidScheduleMode.exactAllowWhileIdle,
       uiLocalNotificationDateInterpretation:
           UILocalNotificationDateInterpretation.absoluteTime,
       matchDateTimeComponents: DateTimeComponents.time,
     );
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@flutter_app/lib/services/notification_service.dart` around lines 106 - 127,
The zonedSchedule call in _plugin.zonedSchedule uses the removed parameter
androidAllowWhileIdle; replace it with the new androidScheduleMode parameter
(from flutter_local_notifications) in the same call site and set it to the
appropriate enum value (e.g., AndroidScheduleMode.exactAllowWhileIdle or
AndroidScheduleMode.inexactAllowWhileIdle depending on desired behavior), and
add the necessary import for AndroidScheduleMode; ensure the replacement is done
in the zonedSchedule invocation that includes NotificationDetails and
matchDateTimeComponents.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@flutter_app/lib/services/notification_service.dart`:
- Around line 27-28: tz.getLocation(timeZoneName) can throw
LocationNotFoundException for unrecognized timezones; wrap the call that sets
the local timezone (the sequence using FlutterNativeTimezone.getLocalTimezone,
tz.getLocation and tz.setLocalLocation) in a try/catch, catch
LocationNotFoundException (and a general Exception as fallback), and on error
fall back to a safe default location (e.g., tz.UTC or tz.local) and log the
error so the app doesn't crash. Ensure you reference
FlutterNativeTimezone.getLocalTimezone, tz.getLocation, tz.setLocalLocation and
the caught LocationNotFoundException when applying the fix.
- Around line 52-64: The Android notification permission call in
requestPermissions() uses the old method name; update the call on the
androidPlugin (resolved as AndroidFlutterLocalNotificationsPlugin) from
requestPermission() to requestNotificationsPermission() so it requests the
POST_NOTIFICATIONS runtime permission on Android 13+; locate the
requestPermissions() method and replace the androidPlugin?.requestPermission()
invocation with androidPlugin?.requestNotificationsPermission().

---

Duplicate comments:
In `@flutter_app/lib/services/notification_service.dart`:
- Around line 106-127: The zonedSchedule call in _plugin.zonedSchedule uses the
removed parameter androidAllowWhileIdle; replace it with the new
androidScheduleMode parameter (from flutter_local_notifications) in the same
call site and set it to the appropriate enum value (e.g.,
AndroidScheduleMode.exactAllowWhileIdle or
AndroidScheduleMode.inexactAllowWhileIdle depending on desired behavior), and
add the necessary import for AndroidScheduleMode; ensure the replacement is done
in the zonedSchedule invocation that includes NotificationDetails and
matchDateTimeComponents.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 85812709-2610-4837-92db-7399568c6ee0

📥 Commits

Reviewing files that changed from the base of the PR and between f042b77 and b87de98.

📒 Files selected for processing (2)
  • flutter_app/android/app/src/main/AndroidManifest.xml
  • flutter_app/lib/services/notification_service.dart
✅ Files skipped from review due to trivial changes (1)
  • flutter_app/android/app/src/main/AndroidManifest.xml

Comment on lines +27 to +28
final String timeZoneName = await FlutterNativeTimezone.getLocalTimezone();
tz.setLocalLocation(tz.getLocation(timeZoneName));
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Unhandled exception if device timezone is unrecognized.

tz.getLocation(timeZoneName) throws LocationNotFoundException if the timezone string returned by FlutterNativeTimezone.getLocalTimezone() isn't in the timezone database. This can crash the app on devices with unusual timezone configurations.

🛡️ Proposed fix with fallback
     tz.initializeTimeZones();
     final String timeZoneName = await FlutterNativeTimezone.getLocalTimezone();
-    tz.setLocalLocation(tz.getLocation(timeZoneName));
+    try {
+      tz.setLocalLocation(tz.getLocation(timeZoneName));
+    } catch (_) {
+      // Fallback to UTC if device timezone is unrecognized
+      tz.setLocalLocation(tz.UTC);
+    }
📝 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
final String timeZoneName = await FlutterNativeTimezone.getLocalTimezone();
tz.setLocalLocation(tz.getLocation(timeZoneName));
tz.initializeTimeZones();
final String timeZoneName = await FlutterNativeTimezone.getLocalTimezone();
try {
tz.setLocalLocation(tz.getLocation(timeZoneName));
} catch (_) {
// Fallback to UTC if device timezone is unrecognized
tz.setLocalLocation(tz.UTC);
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@flutter_app/lib/services/notification_service.dart` around lines 27 - 28,
tz.getLocation(timeZoneName) can throw LocationNotFoundException for
unrecognized timezones; wrap the call that sets the local timezone (the sequence
using FlutterNativeTimezone.getLocalTimezone, tz.getLocation and
tz.setLocalLocation) in a try/catch, catch LocationNotFoundException (and a
general Exception as fallback), and on error fall back to a safe default
location (e.g., tz.UTC or tz.local) and log the error so the app doesn't crash.
Ensure you reference FlutterNativeTimezone.getLocalTimezone, tz.getLocation,
tz.setLocalLocation and the caught LocationNotFoundException when applying the
fix.

Comment on lines +52 to +64
Future<void> requestPermissions() async {
final androidPlugin =
_plugin.resolvePlatformSpecificImplementation<AndroidFlutterLocalNotificationsPlugin>();
await androidPlugin?.requestPermission();

final iosPlugin =
_plugin.resolvePlatformSpecificImplementation<IOSFlutterLocalNotificationsPlugin>();
await iosPlugin?.requestPermissions(
alert: true,
badge: true,
sound: true,
);
}
Copy link

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

🌐 Web query:

flutter_local_notifications AndroidFlutterLocalNotificationsPlugin requestNotificationsPermission vs requestPermission API v13

💡 Result:

In the flutter_local_notifications plugin, for Android (especially API level 33/Android 13+), the AndroidFlutterLocalNotificationsPlugin class provides the requestNotificationsPermission method to request the POST_NOTIFICATIONS runtime permission required to display notifications. The older requestPermission method was deprecated/renamed to requestNotificationsPermission in version 16.0.0 (as seen in related plugin changelogs and GitHub issues where users report 'requestPermission' not defined, with comments confirming the replacement). Usage example: final AndroidFlutterLocalNotificationsPlugin? androidImplementation = flutterLocalNotificationsPlugin.resolvePlatformSpecificImplementation< AndroidFlutterLocalNotificationsPlugin>; await androidImplementation?.requestNotificationsPermission; This shows the system permission dialog on Android 13+. Check status first with areNotificationsEnabled if needed. Also add to AndroidManifest.xml and targetSdkVersion 33+. The plugin's current API docs (v21.0.0 as of 2026) confirm requestNotificationsPermission is not listed but community/official README and examples use it; the class page lists related methods like areNotificationsEnabled.

Citations:


🏁 Script executed:

find . -name "pubspec.yaml" -o -name "pubspec.lock" | head -5

Repository: OpenLake/GetWelPlus

Length of output: 116


🏁 Script executed:

cat ./flutter_app/pubspec.yaml | grep -A 5 "flutter_local_notifications"

Repository: OpenLake/GetWelPlus

Length of output: 214


🌐 Web query:

flutter_local_notifications v13.0.0 AndroidFlutterLocalNotificationsPlugin requestPermission method API

💡 Result:

In flutter_local_notifications version 13.0.0, the AndroidFlutterLocalNotificationsPlugin class provides the requestNotificationsPermission method to request notification permissions on Android, particularly for Android 13 (API 33) and higher where POST_NOTIFICATIONS runtime permission is required. Usage example: final AndroidFlutterLocalNotificationsPlugin? androidImplementation = flutterLocalNotificationsPlugin.resolvePlatformSpecificImplementation; final bool? granted = await androidImplementation?.requestNotificationsPermission; This method returns a Future<bool?> indicating if permission was granted. Prerequisites: - Target SDK 33+ in android/app/build.gradle (targetSdkVersion 33) - Add to AndroidManifest.xml Note: Earlier versions used requestPermission, but by v13.0.0 it was requestNotificationsPermission. Check permissions first with areNotificationsEnabled to avoid unnecessary requests. The method may trigger app lifecycle changes even if no dialog shows (e.g., permission previously denied).

Citations:


🏁 Script executed:

cat -n ./flutter_app/lib/services/notification_service.dart | sed -n '52,64p'

Repository: OpenLake/GetWelPlus

Length of output: 591


Change requestPermission() to requestNotificationsPermission() for Android 13+ support.

The code calls requestPermission() on line 55, but flutter_local_notifications v13.0.0 uses the method name requestNotificationsPermission(). Update the call to:

await androidPlugin?.requestNotificationsPermission();

This method is required to request the POST_NOTIFICATIONS runtime permission on Android 13+ (API 33+).

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

In `@flutter_app/lib/services/notification_service.dart` around lines 52 - 64, The
Android notification permission call in requestPermissions() uses the old method
name; update the call on the androidPlugin (resolved as
AndroidFlutterLocalNotificationsPlugin) from requestPermission() to
requestNotificationsPermission() so it requests the POST_NOTIFICATIONS runtime
permission on Android 13+; locate the requestPermissions() method and replace
the androidPlugin?.requestPermission() invocation with
androidPlugin?.requestNotificationsPermission().

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.

1 participant