Skip to content

Conversation

@ucswift
Copy link
Member

@ucswift ucswift commented Nov 1, 2025

Summary by CodeRabbit

  • New Features

    • Notification sound playback for multiple notification types.
    • Runtime push handling with foreground modal triggers and improved dark-mode styling.
    • New localized warning message for unknown notification types.
    • Added Firebase messaging/analytics support.
  • Chores

    • CI now auto-generates and submits release notes; mobile build metadata flows updated.
    • New development simulator build profile and local Google config file ignored.
  • Tests

    • Expanded tests for notification sounds, push handling, and app initialization.

@coderabbitai
Copy link

coderabbitai bot commented Nov 1, 2025

Walkthrough

Firebase and push-notification integrations added; a NotificationSoundService singleton implemented; AppInitializationService now initializes push notifications; push notification parsing and store updated to play sounds; CI workflow enhanced for iOS Google services, dynamic release-notes extraction, and Changerawr submission; build configs and deps updated.

Changes

Cohort / File(s) Change Summary
CI/CD & GitHub Workflows
\.github/workflows/react-native-cicd.yml
Adds iOS Google services secret and plist creation; implements extract_release_notes with multi-source fallback (PR body, merged PR lookup, commit history); writes RELEASE_NOTES.md; posts release notes JSON to Changerawr API; minor label update.
Build Configuration
app.config.ts, eas.json, \.gitignore
Registers @react-native-firebase/app plugin and sets googleServicesFile + useFrameworks: 'static' in app.config.ts; adds dev-sim build profile in eas.json; adds GoogleService-Info.plist to .gitignore.
Dependencies
package.json
Adds @react-native-firebase/app, @react-native-firebase/analytics, @react-native-firebase/messaging (^23.5.0); adjusts expo-notifications version.
Push Notification Service & Init
src/services/push-notification.ts, src/services/app-initialization.service.ts
Reworks PushNotificationService to runtime initialization (constructor visibility changed), adds Notifee and FCM imports, sets up Android channels, registers notification/response listeners and FCM foreground handler, and integrates push init into AppInitializationService with try/catch logging (non-fatal).
Notification Sound Management
src/services/notification-sound.service.ts
Adds a singleton NotificationSoundService to initialize audio, preload assets, play per-type sounds (call, message, chat, group-chat, unknown), and cleanup; exposes notificationSoundService.
Push Notification Store
src/stores/push-notification/store.ts
Refactors eventCode parsing from colon format (C:1234) to compact format (C1234), maps types accordingly, and triggers notificationSoundService.playNotificationSound as a side effect with error handling.
UI Components
src/components/push-notification/push-notification-modal.tsx, src/components/calls/dispatch-selection-modal.tsx
Simplifies icon props to fixed size and className-based dark-mode styling; updates text color classes; removes useMemo for filtered data (compute each render).
Tests — Initialization & Push
src/services/__tests__/app-initialization.service.test.ts, src/services/__tests__/push-notification.test.ts
Adds pushNotificationService mocks; adapts push tests to FCM mock and trigger shape (trigger: { type: 'push' }); adds listener cleanup tests; asserts init idempotency and error resilience.
Tests — Notification Sound
src/services/__tests__/notification-sound.service.test.ts
New suite mocking Expo AV/Asset and logger to test initialize, playback across types, error handling, and cleanup.
Tests — Push Notification Store
src/stores/push-notification/__tests__/store.test.ts
Updates fixtures to colon-free eventCode formats; adds notificationSoundService mock; verifies sound playback invocation and graceful handling of playback errors.
Mocks & Translations
__mocks__/react-native-ble-plx.ts, src/translations/{ar,en,es}.json
Removes empty constructor from BleManager mock; adds/adjusts translation keys (new notification type, unknown_type_warning, capitalization changes).

Sequence Diagram(s)

sequenceDiagram
    participant App as App Startup
    participant AIS as AppInitializationService
    participant PNS as PushNotificationService
    participant SNS as NotificationSoundService
    participant FIR as Firebase/Notifee

    App->>AIS: initialize()
    activate AIS
    AIS->>PNS: _initializePushNotifications()
    activate PNS
    PNS->>FIR: setupAndroidChannels()
    PNS->>FIR: addNotificationListeners()
    PNS->>FIR: setupForegroundMessageHandler()
    PNS-->>AIS: success (errors logged, not thrown)
    deactivate PNS

    AIS->>SNS: initialize()
    activate SNS
    SNS->>SNS: initializeAudio() / preload assets
    SNS-->>AIS: success
    deactivate SNS

    AIS-->>App: initialization complete
    deactivate AIS
Loading
sequenceDiagram
    participant FCM as FCM Message (foreground)
    participant PNS as PushNotificationService
    participant Store as PushNotificationStore
    participant SNS as NotificationSoundService
    participant UI as Modal UI

    FCM->>PNS: onMessage(remoteMessage)
    activate PNS
    PNS->>Store: create notificationData (extract eventCode)
    PNS->>Store: showNotificationModal(notificationData)
    deactivate PNS

    activate Store
    Store->>Store: parse eventCode (C1234 → call)
    Store->>SNS: playNotificationSound('call')
    activate SNS
    SNS-->>Store: playback result (errors logged)
    deactivate SNS
    Store->>UI: display modal
    deactivate Store
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Areas to review closely:

  • Push notification initialization, listener registration/unregistration, and FCM foreground handling (src/services/push-notification.ts)
  • EventCode parsing and downstream consumers (src/stores/push-notification/store.ts and tests)
  • NotificationSoundService async initialization, asset loading, and cleanup (src/services/notification-sound.service.ts and tests)
  • CI workflow release-notes extraction and Changerawr POST (.github/workflows/react-native-cicd.yml)

Possibly related PRs

Poem

🐰
I hopped through code and found a bell,
Firebase whispers, sounds that swell,
Notifications chirp, then play—so neat,
CI crafts notes for each new feat,
A tiny rabbit hums, "All set!" 🥕✨

Pre-merge checks and finishing touches

❌ Failed checks (1 warning, 1 inconclusive)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
Title Check ❓ Inconclusive The pull request title is "Develop," which appears to be a branch name rather than a descriptive title summarizing the changes. The changeset contains substantial modifications including Firebase integration for analytics and messaging, a comprehensive push notification system redesign with new notification sound management, GitHub Actions workflow updates, and translation updates across multiple languages. The title conveys no meaningful information about these changes and provides no guidance to teammates scanning the commit history about the primary purpose of this PR. The title should be replaced with a concise, descriptive summary that captures the main objective of the PR, such as "Integrate Firebase messaging and redesign push notification system" or similar language that reflects the primary changes in the changeset. This would provide clarity to reviewers and future developers examining the commit history.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch develop

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

❤️ Share

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

Copy link

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

🧹 Nitpick comments (4)
.gitignore (1)

22-23: Remove the duplicate /apps entry.

Line 22 and line 23 both contain /apps, creating a redundant rule.

-/apps
-/apps
+/apps
src/services/notification-sound.service.ts (3)

27-29: Clarify initialization strategy.

This public method provides explicit initialization, but the constructor also calls initializeAudio() (line 17). The isInitialized guard prevents double work, but the design is confusing. If you adopt Option 1 from the previous comment (removing constructor initialization), this method becomes the primary initialization point—document that callers must invoke it.


121-127: Consider reusing chat sound for group chat.

Currently, group chat creates a separate Audio.Sound instance from the same asset as chat. If they should sound identical, consider referencing the same sound object to save memory:

-     // Group chat uses the same sound as regular chat
-     const { sound: groupChatSound } = await Audio.Sound.createAsync({ uri: chatSoundAsset.localUri || chatSoundAsset.uri } as AVPlaybackSource, {
-       shouldPlay: false,
-       isLooping: false,
-       volume: 1.0,
-     });
-     this.groupChatSound = groupChatSound;
+     // Group chat uses the same sound as regular chat
+     this.groupChatSound = this.chatSound;

Note: Only do this if concurrent playback of chat and group-chat sounds will never occur. If they might play simultaneously, keep separate instances.


209-248: LGTM: Thorough cleanup.

All sound resources are properly unloaded, nullified, and the initialization state is reset. This allows for proper re-initialization if needed.

Optionally, you could reduce duplication by iterating over sounds:

const sounds = [
  { ref: this.callSound, name: 'callSound' },
  { ref: this.messageSound, name: 'messageSound' },
  // ...
];

for (const { ref } of sounds) {
  if (ref) await ref.unloadAsync();
}

However, the current explicit approach is clear and maintainable.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 916a3df and bfdf643.

⛔ Files ignored due to path filters (2)
  • .DS_Store is excluded by !**/.DS_Store
  • yarn.lock is excluded by !**/yarn.lock, !**/*.lock
📒 Files selected for processing (19)
  • .github/workflows/react-native-cicd.yml (4 hunks)
  • .gitignore (1 hunks)
  • __mocks__/react-native-ble-plx.ts (0 hunks)
  • app.config.ts (3 hunks)
  • eas.json (1 hunks)
  • package.json (2 hunks)
  • src/components/calls/dispatch-selection-modal.tsx (5 hunks)
  • src/components/push-notification/push-notification-modal.tsx (3 hunks)
  • src/services/__tests__/app-initialization.service.test.ts (7 hunks)
  • src/services/__tests__/notification-sound.service.test.ts (1 hunks)
  • src/services/__tests__/push-notification.test.ts (1 hunks)
  • src/services/app-initialization.service.ts (3 hunks)
  • src/services/notification-sound.service.ts (1 hunks)
  • src/services/push-notification.ts (2 hunks)
  • src/stores/push-notification/__tests__/store.test.ts (12 hunks)
  • src/stores/push-notification/store.ts (3 hunks)
  • src/translations/ar.json (1 hunks)
  • src/translations/en.json (1 hunks)
  • src/translations/es.json (1 hunks)
💤 Files with no reviewable changes (1)
  • mocks/react-native-ble-plx.ts
🧰 Additional context used
📓 Path-based instructions (9)
**/*

📄 CodeRabbit inference engine (.cursorrules)

Directory and file names should be lowercase and hyphenated (e.g., user-profile, chat-screen)

Files:

  • src/translations/es.json
  • src/translations/ar.json
  • src/stores/push-notification/__tests__/store.test.ts
  • src/stores/push-notification/store.ts
  • src/services/__tests__/push-notification.test.ts
  • src/services/push-notification.ts
  • app.config.ts
  • package.json
  • src/translations/en.json
  • src/services/__tests__/app-initialization.service.test.ts
  • src/services/__tests__/notification-sound.service.test.ts
  • src/services/app-initialization.service.ts
  • src/components/calls/dispatch-selection-modal.tsx
  • src/components/push-notification/push-notification-modal.tsx
  • src/services/notification-sound.service.ts
  • eas.json
src/translations/**

📄 CodeRabbit inference engine (.cursorrules)

Store translation dictionaries in src/translations

Files:

  • src/translations/es.json
  • src/translations/ar.json
  • src/translations/en.json
src/translations/**/*.{json,ts}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Store translation dictionaries in src/translations

Files:

  • src/translations/es.json
  • src/translations/ar.json
  • src/translations/en.json
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursorrules)

**/*.{ts,tsx}: Write concise, type-safe TypeScript code
Use camelCase for variable and function names (e.g., isFetchingData, handleUserInput)
Use TypeScript for all components, favoring interfaces for props and state
Avoid using any; strive for precise types
Use Expo SecureStore for sensitive data
Use zustand for state management
Use react-query for data fetching and caching
Use react-i18next for internationalization
Use react-native-mmkv for local storage
Use axios for API requests

**/*.{ts,tsx}: Write concise, type-safe TypeScript code
Use camelCase for variable and function names
Use TypeScript for all components, favoring interfaces for props and state
Avoid using any; strive for precise types
Use React Navigation for handling navigation and deep linking
Handle errors gracefully and provide user feedback
Use Expo's SecureStore for sensitive data
Use zustand for state management
Use react-query for data fetching
Use react-i18next for internationalization
Use react-native-mmkv for local storage
Use axios for API requests

Files:

  • src/stores/push-notification/__tests__/store.test.ts
  • src/stores/push-notification/store.ts
  • src/services/__tests__/push-notification.test.ts
  • src/services/push-notification.ts
  • app.config.ts
  • src/services/__tests__/app-initialization.service.test.ts
  • src/services/__tests__/notification-sound.service.test.ts
  • src/services/app-initialization.service.ts
  • src/components/calls/dispatch-selection-modal.tsx
  • src/components/push-notification/push-notification-modal.tsx
  • src/services/notification-sound.service.ts
**/*.{test.ts,test.tsx,spec.ts,spec.tsx}

📄 CodeRabbit inference engine (.cursorrules)

Create and use Jest tests to validate all generated components

Files:

  • src/stores/push-notification/__tests__/store.test.ts
  • src/services/__tests__/push-notification.test.ts
  • src/services/__tests__/app-initialization.service.test.ts
  • src/services/__tests__/notification-sound.service.test.ts
**/__tests__/**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursorrules)

Generate tests for all components, services, and logic; ensure tests run without errors

Files:

  • src/stores/push-notification/__tests__/store.test.ts
  • src/services/__tests__/push-notification.test.ts
  • src/services/__tests__/app-initialization.service.test.ts
  • src/services/__tests__/notification-sound.service.test.ts
**/*.{test,spec}.{ts,tsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Create and use Jest tests for all generated components

Files:

  • src/stores/push-notification/__tests__/store.test.ts
  • src/services/__tests__/push-notification.test.ts
  • src/services/__tests__/app-initialization.service.test.ts
  • src/services/__tests__/notification-sound.service.test.ts
**/*.tsx

📄 CodeRabbit inference engine (.cursorrules)

**/*.tsx: Use functional components and hooks over class components
Use PascalCase for React component names (e.g., UserProfile, ChatScreen)
Utilize React.FC for defining functional components with props
Minimize useEffect, useState, and heavy computations inside render
Use React.memo() for components with static props to prevent unnecessary re-renders
Optimize FlatList with removeClippedSubviews, maxToRenderPerBatch, windowSize
Use getItemLayout for FlatList when items have consistent size
Avoid anonymous functions in renderItem or event handlers to prevent re-renders
Use gluestack-ui for styling; if no component exists in src/components/ui, style via StyleSheet.create or styled-components
Optimize images using react-native-fast-image
Use React Navigation for navigation and deep linking with best practices
Wrap all user-facing text in t() from react-i18next
Use react-hook-form for form handling
Use @rnmapbox/maps for maps or vehicle navigation
Use lucide-react-native for icons directly in markup; do not use the gluestack-ui icon component
Use the conditional operator (?:) for conditional rendering instead of &&

**/*.tsx: Use functional components and hooks over class components
Use PascalCase for component names
Utilize React.FC for defining functional components with props
Minimize useEffect/useState and heavy computations inside render
Use React.memo for components with static props to prevent unnecessary re-renders
Optimize FlatList with removeClippedSubviews, maxToRenderPerBatch, and windowSize
Use getItemLayout for FlatList when items have consistent size
Avoid anonymous functions in renderItem or event handlers
Optimize image handling using react-native-fast-image
Wrap all user-facing text in t() from react-i18next
Ensure support for dark mode and light mode
Ensure the app is accessible following WCAG for mobile
Use react-hook-form for form handling
Use @rnmapbox/maps for maps and navigation
Use lucide-react-native for icons directly in markup; do not us...

Files:

  • src/components/calls/dispatch-selection-modal.tsx
  • src/components/push-notification/push-notification-modal.tsx
{components/ui/**/*.{ts,tsx},**/*.tsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Use gluestack-ui consistently; if no component exists in components/ui, style via StyleSheet.create or styled-components

Files:

  • src/components/calls/dispatch-selection-modal.tsx
  • src/components/push-notification/push-notification-modal.tsx
🧠 Learnings (7)
📚 Learning: 2025-08-12T03:34:25.992Z
Learnt from: CR
Repo: Resgrid/Unit PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-08-12T03:34:25.992Z
Learning: Applies to **/*.tsx : Use rnmapbox/maps for maps and navigation

Applied to files:

  • package.json
📚 Learning: 2025-08-12T03:33:40.238Z
Learnt from: CR
Repo: Resgrid/Unit PR: 0
File: .cursorrules:0-0
Timestamp: 2025-08-12T03:33:40.238Z
Learning: Applies to **/*.{test.ts,test.tsx,spec.ts,spec.tsx} : Create and use Jest tests to validate all generated components

Applied to files:

  • src/services/__tests__/notification-sound.service.test.ts
📚 Learning: 2025-08-12T03:34:25.992Z
Learnt from: CR
Repo: Resgrid/Unit PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-08-12T03:34:25.992Z
Learning: Applies to **/*.{test,spec}.{ts,tsx} : Create and use Jest tests for all generated components

Applied to files:

  • src/services/__tests__/notification-sound.service.test.ts
📚 Learning: 2025-08-12T03:33:40.238Z
Learnt from: CR
Repo: Resgrid/Unit PR: 0
File: .cursorrules:0-0
Timestamp: 2025-08-12T03:33:40.238Z
Learning: Applies to **/__tests__/**/*.{ts,tsx} : Generate tests for all components, services, and logic; ensure tests run without errors

Applied to files:

  • src/services/__tests__/notification-sound.service.test.ts
📚 Learning: 2025-08-12T03:33:40.238Z
Learnt from: CR
Repo: Resgrid/Unit PR: 0
File: .cursorrules:0-0
Timestamp: 2025-08-12T03:33:40.238Z
Learning: Applies to **/*.tsx : Use React.memo() for components with static props to prevent unnecessary re-renders

Applied to files:

  • src/components/calls/dispatch-selection-modal.tsx
📚 Learning: 2025-08-12T03:33:40.238Z
Learnt from: CR
Repo: Resgrid/Unit PR: 0
File: .cursorrules:0-0
Timestamp: 2025-08-12T03:33:40.238Z
Learning: Applies to **/*.tsx : Use lucide-react-native for icons directly in markup; do not use the gluestack-ui icon component

Applied to files:

  • src/components/push-notification/push-notification-modal.tsx
📚 Learning: 2025-08-12T03:34:25.992Z
Learnt from: CR
Repo: Resgrid/Unit PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-08-12T03:34:25.992Z
Learning: Applies to **/*.tsx : Use lucide-react-native for icons directly in markup; do not use gluestack-ui icon component

Applied to files:

  • src/components/push-notification/push-notification-modal.tsx
🧬 Code graph analysis (7)
src/stores/push-notification/__tests__/store.test.ts (2)
src/stores/push-notification/store.ts (1)
  • usePushNotificationModalStore (32-104)
src/services/notification-sound.service.ts (1)
  • notificationSoundService (251-251)
src/stores/push-notification/store.ts (2)
src/services/notification-sound.service.ts (1)
  • notificationSoundService (251-251)
src/lib/logging/index.tsx (2)
  • error (74-76)
  • logger (80-80)
src/services/push-notification.ts (2)
src/lib/logging/index.tsx (1)
  • logger (80-80)
src/stores/push-notification/store.ts (1)
  • usePushNotificationModalStore (32-104)
src/services/__tests__/app-initialization.service.test.ts (4)
src/lib/logging/index.tsx (1)
  • logger (80-80)
src/services/callkeep.service.ios.ts (1)
  • callKeepService (341-341)
src/services/push-notification.ts (1)
  • pushNotificationService (301-301)
src/services/app-initialization.service.ts (1)
  • appInitializationService (180-180)
src/services/__tests__/notification-sound.service.test.ts (2)
__mocks__/expo-av.ts (1)
  • Audio (2-28)
src/services/notification-sound.service.ts (1)
  • notificationSoundService (251-251)
src/services/app-initialization.service.ts (2)
src/services/push-notification.ts (1)
  • pushNotificationService (301-301)
src/lib/logging/index.tsx (2)
  • logger (80-80)
  • error (74-76)
src/services/notification-sound.service.ts (3)
__mocks__/expo-av.ts (3)
  • Audio (2-28)
  • InterruptionModeIOS (30-34)
  • AVPlaybackSource (36-36)
src/lib/logging/index.tsx (2)
  • logger (80-80)
  • error (74-76)
src/stores/push-notification/store.ts (1)
  • NotificationType (13-13)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: test
🔇 Additional comments (17)
eas.json (1)

83-98: Clarify the purpose of the new dev-sim profile.

A new dev-sim build profile has been added with iOS and Android simulator support and developmentClient explicitly set to false. This profile appears to overlap with the existing simulator profile (lines 99–112), which also enables iOS simulator support.

The distinction seems to be that dev-sim explicitly disables the development client while the simulator profile leaves it unset. If this profile is intended to test production-like behavior on simulators, consider adding a comment explaining its purpose. If it's truly necessary alongside simulator, verify that both profiles serve distinct use cases in your CI/CD and development workflow.

Could you confirm:

  1. What specific use case or workflow does dev-sim address that simulator does not?
  2. Is this profile tied to the Firebase integration or CI/CD enhancements mentioned in the PR?
.gitignore (1)

27-27: Clarify the pattern scope for GoogleService-Info.plist.

The current pattern GoogleService-Info.plist will match the file in any directory recursively. Per the AI summary, the intent is to ignore the root-level file generated during iOS CI builds. If root-only matching is desired, prepend a forward slash: /GoogleService-Info.plist. If the intent is to ignore this file at any nesting level (e.g., within Xcode project directories), the current pattern is correct.

app.config.ts (3)

47-47: LGTM! Firebase iOS configuration correctly added.

The googleServicesFile configuration for iOS is properly set and aligns with the CI/CD workflow changes that create this file from the UNIT_IOS_GOOGLE_SERVICES secret.


218-218: LGTM! Required iOS framework configuration for Firebase.

The useFrameworks: 'static' setting is correctly configured and necessary for Firebase SDK compatibility on iOS.


267-267: LGTM! Firebase plugin correctly registered.

The @react-native-firebase/app plugin is properly added to the plugins array, matching the dependency addition in package.json.

.github/workflows/react-native-cicd.yml (2)

50-50: LGTM! Environment secrets properly configured.

The new environment variables for iOS Google Services and Changerawr API are correctly added.

Also applies to: 75-76


159-162: LGTM! iOS Google Services file creation properly implemented.

The step correctly creates the GoogleService-Info.plist file from the base64-encoded secret, matching the pattern used for Android's google-services.json.

src/services/__tests__/push-notification.test.ts (1)

125-129: LGTM! Mock notification structure improved.

The addition of a realistic trigger object with type: 'push' makes the test data more accurate. The double type assertion (as unknown as Notifications.Notification) is an acceptable TypeScript workaround for test data construction.

src/services/app-initialization.service.ts (3)

5-5: LGTM! Push notification service import added.

The import is correctly added for the push notification initialization flow.


77-78: LGTM! Push notification initialization integrated into app startup.

The initialization is correctly added to the startup flow, following the same pattern as CallKeep initialization.


118-136: LGTM! Push notification initialization method properly implemented.

The implementation follows the established pattern:

  • Private async method for initialization
  • Proper error handling with logging
  • Non-throwing behavior to prevent app startup failure
  • Clear log messages for success and error cases
package.json (2)

94-96: I need to verify a couple more details before finalizing the assessment. Let me check the React Native version specified in the codebase and investigate the validity of version 0.79.6:

Ensure awareness of React Native 0.79 Metro compatibility requirements with Firebase packages.

The three Firebase packages at ^23.5.0 are compatible with React Native 0.79.6:

  • All three packages are aligned at 23.5.0 (latest available)
  • Peer dependencies are satisfied (v23.5.0 is the latest version)
  • React Native 0.79 has known incompatibilities with Firebase packages due to Metro 0.82's package.json "exports" field resolution

If using React Native 0.79.6, ensure your project either uses Metro 0.81.5 hotfix or disables package exports resolution to avoid potential Firebase initialization issues.


128-128: Document the reason for the expo-notifications downgrade.

The web search found no documented incompatibility between @react-native-firebase/messaging v23.5.0 and expo-notifications 0.31.4. However, 0.31.4 (2025-07-05) contains no user-facing changes (internal fixes), while 0.28.3 (2024-05-16) added iOS support for interruptionLevel. Downgrading from 0.31.4 to 0.28.3 moves backward ~13 months without a clear technical justification. Please clarify why this downgrade is necessary—whether it addresses a specific issue or dependency conflict.

src/services/notification-sound.service.ts (4)

31-64: LGTM: Well-structured initialization.

The initialization logic is clean with proper guards, platform-specific audio configuration, and comprehensive error handling.


66-84: LGTM: Efficient parallel asset preloading.

Using Promise.all for parallel loading is efficient. Error handling allows the process to continue to loadAudioFiles even if preloading fails partially.


151-179: LGTM: Robust playback implementation.

The method includes proper guards (null check, lazy initialization) and resets playback position before playing. The on-demand initialization (lines 161-163) provides good defensive behavior.


181-207: LGTM: Complete notification type coverage.

All NotificationType cases are handled, with appropriate fallback to default sound for unknown types. Error context includes the notification type for debugging.

Comment on lines 288 to 378
- name: 📋 Prepare Release Notes file
if: ${{ matrix.platform == 'android' }}
env:
RELEASE_NOTES_INPUT: ${{ github.event.inputs.release_notes }}
PR_BODY: ${{ github.event.pull_request.body }}
run: |
set -eo pipefail
# Determine source of release notes: workflow input, PR body, or recent commits
if [ -n "$RELEASE_NOTES_INPUT" ]; then
NOTES="$RELEASE_NOTES_INPUT"
elif [ -n "$PR_BODY" ]; then
NOTES="$(printf '%s\n' "$PR_BODY" \
# Function to extract release notes from PR body
extract_release_notes() {
local body="$1"
# Try to extract content under "## Release Notes" heading
local notes="$(printf '%s\n' "$body" \
| awk 'f && /^## /{exit} /^## Release Notes/{f=1; next} f')"
else
# If no specific section found, use the entire body (up to first 500 chars for safety)
if [ -z "$notes" ]; then
notes="$(printf '%s\n' "$body" | head -c 500)"
fi
printf '%s\n' "$notes"
}
# Determine source of release notes
NOTES=""
# Check if this was triggered by a push event (likely a merge)
if [ "${{ github.event_name }}" = "push" ]; then
echo "Fetching PR body for merged commit..."
# Get the PR number associated with this merge commit
PR_NUMBER=$(gh pr list \
--state merged \
--search "${{ github.sha }}" \
--json number \
--jq '.[0].number' 2>/dev/null || echo "")
if [ -n "$PR_NUMBER" ]; then
echo "Found merged PR #$PR_NUMBER"
# Fetch the PR body
PR_BODY=$(gh pr view "$PR_NUMBER" --json body --jq '.body' 2>/dev/null || echo "")
if [ -n "$PR_BODY" ]; then
NOTES="$(extract_release_notes "$PR_BODY")"
fi
else
echo "No associated PR found, checking commit message..."
# Try to find PR number from commit message
PR_FROM_COMMIT=$(git log -1 --pretty=%B | grep -oE '#[0-9]+' | head -1 | tr -d '#' || echo "")
if [ -n "$PR_FROM_COMMIT" ]; then
echo "Found PR #$PR_FROM_COMMIT from commit message"
PR_BODY=$(gh pr view "$PR_FROM_COMMIT" --json body --jq '.body' 2>/dev/null || echo "")
if [ -n "$PR_BODY" ]; then
NOTES="$(extract_release_notes "$PR_BODY")"
fi
fi
fi
fi
# Fallback to recent commits if no PR body found
if [ -z "$NOTES" ]; then
echo "No PR body found, using recent commits..."
NOTES="$(git log -n 5 --pretty=format:'- %s')"
fi
# Fail if no notes extracted
if [ -z "$NOTES" ]; then
echo "Error: No release notes extracted" >&2
exit 1
fi
# Write header and notes to file
{
echo "## Version 7.${{ github.run_number }} - $(date +%Y-%m-%d)"
echo
printf '%s\n' "$NOTES"
} > RELEASE_NOTES.md
echo "Release notes prepared:"
cat RELEASE_NOTES.md
env:
GH_TOKEN: ${{ github.token }}

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

Fix shell script quote mismatch and improve PR detection logic.

Several issues in the release notes extraction script:

  1. Quote mismatch at line 306: The awk pattern has mismatched quotes (' at start, " at end)
  2. PR detection may fail: Line 326 uses gh pr list --search "${{ github.sha }}" which searches PR content, not commits. This won't reliably find the PR that introduced the commit.
  3. No handling for multiple results: The script assumes exactly one PR is returned.

Apply these fixes:

           extract_release_notes() {
             local body="$1"
             # Try to extract content under "## Release Notes" heading
-            local notes="$(printf '%s\n' "$body" \
-              | awk 'f && /^## /{exit} /^## Release Notes/{f=1; next} f')"
+            local notes="$(printf '%s\n' "$body" \
+              | awk 'flag && /^## /{exit} /^## Release Notes/{flag=1; next} flag')"

For better PR detection, use the GitHub API to find PRs by commit SHA:

             # Get the PR number associated with this merge commit
-            PR_NUMBER=$(gh pr list \
-              --state merged \
-              --search "${{ github.sha }}" \
-              --json number \
-              --jq '.[0].number' 2>/dev/null || echo "")
+            # Use the GitHub API to find the PR that contains this commit
+            PR_NUMBER=$(gh api "repos/${{ github.repository }}/commits/${{ github.sha }}/pulls" \
+              --jq '.[0].number' 2>/dev/null || echo "")
📝 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
- name: 📋 Prepare Release Notes file
if: ${{ matrix.platform == 'android' }}
env:
RELEASE_NOTES_INPUT: ${{ github.event.inputs.release_notes }}
PR_BODY: ${{ github.event.pull_request.body }}
run: |
set -eo pipefail
# Determine source of release notes: workflow input, PR body, or recent commits
if [ -n "$RELEASE_NOTES_INPUT" ]; then
NOTES="$RELEASE_NOTES_INPUT"
elif [ -n "$PR_BODY" ]; then
NOTES="$(printf '%s\n' "$PR_BODY" \
# Function to extract release notes from PR body
extract_release_notes() {
local body="$1"
# Try to extract content under "## Release Notes" heading
local notes="$(printf '%s\n' "$body" \
| awk 'f && /^## /{exit} /^## Release Notes/{f=1; next} f')"
else
# If no specific section found, use the entire body (up to first 500 chars for safety)
if [ -z "$notes" ]; then
notes="$(printf '%s\n' "$body" | head -c 500)"
fi
printf '%s\n' "$notes"
}
# Determine source of release notes
NOTES=""
# Check if this was triggered by a push event (likely a merge)
if [ "${{ github.event_name }}" = "push" ]; then
echo "Fetching PR body for merged commit..."
# Get the PR number associated with this merge commit
PR_NUMBER=$(gh pr list \
--state merged \
--search "${{ github.sha }}" \
--json number \
--jq '.[0].number' 2>/dev/null || echo "")
if [ -n "$PR_NUMBER" ]; then
echo "Found merged PR #$PR_NUMBER"
# Fetch the PR body
PR_BODY=$(gh pr view "$PR_NUMBER" --json body --jq '.body' 2>/dev/null || echo "")
if [ -n "$PR_BODY" ]; then
NOTES="$(extract_release_notes "$PR_BODY")"
fi
else
echo "No associated PR found, checking commit message..."
# Try to find PR number from commit message
PR_FROM_COMMIT=$(git log -1 --pretty=%B | grep -oE '#[0-9]+' | head -1 | tr -d '#' || echo "")
if [ -n "$PR_FROM_COMMIT" ]; then
echo "Found PR #$PR_FROM_COMMIT from commit message"
PR_BODY=$(gh pr view "$PR_FROM_COMMIT" --json body --jq '.body' 2>/dev/null || echo "")
if [ -n "$PR_BODY" ]; then
NOTES="$(extract_release_notes "$PR_BODY")"
fi
fi
fi
fi
# Fallback to recent commits if no PR body found
if [ -z "$NOTES" ]; then
echo "No PR body found, using recent commits..."
NOTES="$(git log -n 5 --pretty=format:'- %s')"
fi
# Fail if no notes extracted
if [ -z "$NOTES" ]; then
echo "Error: No release notes extracted" >&2
exit 1
fi
# Write header and notes to file
{
echo "## Version 7.${{ github.run_number }} - $(date +%Y-%m-%d)"
echo
printf '%s\n' "$NOTES"
} > RELEASE_NOTES.md
echo "Release notes prepared:"
cat RELEASE_NOTES.md
env:
GH_TOKEN: ${{ github.token }}
- name: 📋 Prepare Release Notes file
if: ${{ matrix.platform == 'android' }}
run: |
set -eo pipefail
# Function to extract release notes from PR body
extract_release_notes() {
local body="$1"
# Try to extract content under "## Release Notes" heading
local notes="$(printf '%s\n' "$body" \
| awk 'flag && /^## /{exit} /^## Release Notes/{flag=1; next} flag')"
# If no specific section found, use the entire body (up to first 500 chars for safety)
if [ -z "$notes" ]; then
notes="$(printf '%s\n' "$body" | head -c 500)"
fi
printf '%s\n' "$notes"
}
# Determine source of release notes
NOTES=""
# Check if this was triggered by a push event (likely a merge)
if [ "${{ github.event_name }}" = "push" ]; then
echo "Fetching PR body for merged commit..."
# Use the GitHub API to find the PR that contains this commit
PR_NUMBER=$(gh api "repos/${{ github.repository }}/commits/${{ github.sha }}/pulls" \
--jq '.[0].number' 2>/dev/null || echo "")
if [ -n "$PR_NUMBER" ]; then
echo "Found merged PR #$PR_NUMBER"
# Fetch the PR body
PR_BODY=$(gh pr view "$PR_NUMBER" --json body --jq '.body' 2>/dev/null || echo "")
if [ -n "$PR_BODY" ]; then
NOTES="$(extract_release_notes "$PR_BODY")"
fi
else
echo "No associated PR found, checking commit message..."
# Try to find PR number from commit message
PR_FROM_COMMIT=$(git log -1 --pretty=%B | grep -oE '#[0-9]+' | head -1 | tr -d '#' || echo "")
if [ -n "$PR_FROM_COMMIT" ]; then
echo "Found PR #$PR_FROM_COMMIT from commit message"
PR_BODY=$(gh pr view "$PR_FROM_COMMIT" --json body --jq '.body' 2>/dev/null || echo "")
if [ -n "$PR_BODY" ]; then
NOTES="$(extract_release_notes "$PR_BODY")"
fi
fi
fi
fi
# Fallback to recent commits if no PR body found
if [ -z "$NOTES" ]; then
echo "No PR body found, using recent commits..."
NOTES="$(git log -n 5 --pretty=format:'- %s')"
fi
# Fail if no notes extracted
if [ -z "$NOTES" ]; then
echo "Error: No release notes extracted" >&2
exit 1
fi
# Write header and notes to file
{
echo "## Version 7.${{ github.run_number }} - $(date +%Y-%m-%d)"
echo
printf '%s\n' "$NOTES"
} > RELEASE_NOTES.md
echo "Release notes prepared:"
cat RELEASE_NOTES.md
env:
GH_TOKEN: ${{ github.token }}

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

🧹 Nitpick comments (3)
.github/workflows/react-native-cicd.yml (1)

324-338: Release notes extraction and PR detection logic looks solid.

The multi-source approach for finding PR information is well-designed, with graceful fallback from commit message → GitHub API → git log. This addresses the earlier review concern about PR detection reliability.

Note: The commit message extraction (line 324) is a nice addition that wasn't in the prior review suggestion.

Minor: Consider renaming the awk variable f to flag on line 306 for clarity, though this is purely cosmetic:

-            local notes="$(printf '%s\n' "$body" \
-              | awk 'f && /^## /{exit} /^## Release Notes/{f=1; next} f')"
+            local notes="$(printf '%s\n' "$body" \
+              | awk 'flag && /^## /{exit} /^## Release Notes/{flag=1; next} flag')"
src/services/push-notification.ts (2)

1-1: Remove unused import.

The notifee import is not used anywhere in this file.

-import notifee from '@notifee/react-native';
 import messaging from '@react-native-firebase/messaging';

204-204: Simplify redundant ternary.

Both branches of the ternary return the same value.

-      const token = Platform.OS === 'ios' ? devicePushToken.data : devicePushToken.data;
+      const token = devicePushToken.data;
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between bfdf643 and d909c4c.

📒 Files selected for processing (6)
  • .github/workflows/react-native-cicd.yml (4 hunks)
  • src/components/calls/dispatch-selection-modal.tsx (6 hunks)
  • src/services/__tests__/notification-sound.service.test.ts (1 hunks)
  • src/services/__tests__/push-notification.test.ts (4 hunks)
  • src/services/notification-sound.service.ts (1 hunks)
  • src/services/push-notification.ts (4 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/services/tests/notification-sound.service.test.ts
🧰 Additional context used
📓 Path-based instructions (7)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursorrules)

**/*.{ts,tsx}: Write concise, type-safe TypeScript code
Use camelCase for variable and function names (e.g., isFetchingData, handleUserInput)
Use TypeScript for all components, favoring interfaces for props and state
Avoid using any; strive for precise types
Use Expo SecureStore for sensitive data
Use zustand for state management
Use react-query for data fetching and caching
Use react-i18next for internationalization
Use react-native-mmkv for local storage
Use axios for API requests

**/*.{ts,tsx}: Write concise, type-safe TypeScript code
Use camelCase for variable and function names
Use TypeScript for all components, favoring interfaces for props and state
Avoid using any; strive for precise types
Use React Navigation for handling navigation and deep linking
Handle errors gracefully and provide user feedback
Use Expo's SecureStore for sensitive data
Use zustand for state management
Use react-query for data fetching
Use react-i18next for internationalization
Use react-native-mmkv for local storage
Use axios for API requests

Files:

  • src/services/notification-sound.service.ts
  • src/services/__tests__/push-notification.test.ts
  • src/services/push-notification.ts
  • src/components/calls/dispatch-selection-modal.tsx
**/*

📄 CodeRabbit inference engine (.cursorrules)

Directory and file names should be lowercase and hyphenated (e.g., user-profile, chat-screen)

Files:

  • src/services/notification-sound.service.ts
  • src/services/__tests__/push-notification.test.ts
  • src/services/push-notification.ts
  • src/components/calls/dispatch-selection-modal.tsx
**/*.{test.ts,test.tsx,spec.ts,spec.tsx}

📄 CodeRabbit inference engine (.cursorrules)

Create and use Jest tests to validate all generated components

Files:

  • src/services/__tests__/push-notification.test.ts
**/__tests__/**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursorrules)

Generate tests for all components, services, and logic; ensure tests run without errors

Files:

  • src/services/__tests__/push-notification.test.ts
**/*.{test,spec}.{ts,tsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Create and use Jest tests for all generated components

Files:

  • src/services/__tests__/push-notification.test.ts
**/*.tsx

📄 CodeRabbit inference engine (.cursorrules)

**/*.tsx: Use functional components and hooks over class components
Use PascalCase for React component names (e.g., UserProfile, ChatScreen)
Utilize React.FC for defining functional components with props
Minimize useEffect, useState, and heavy computations inside render
Use React.memo() for components with static props to prevent unnecessary re-renders
Optimize FlatList with removeClippedSubviews, maxToRenderPerBatch, windowSize
Use getItemLayout for FlatList when items have consistent size
Avoid anonymous functions in renderItem or event handlers to prevent re-renders
Use gluestack-ui for styling; if no component exists in src/components/ui, style via StyleSheet.create or styled-components
Optimize images using react-native-fast-image
Use React Navigation for navigation and deep linking with best practices
Wrap all user-facing text in t() from react-i18next
Use react-hook-form for form handling
Use @rnmapbox/maps for maps or vehicle navigation
Use lucide-react-native for icons directly in markup; do not use the gluestack-ui icon component
Use the conditional operator (?:) for conditional rendering instead of &&

**/*.tsx: Use functional components and hooks over class components
Use PascalCase for component names
Utilize React.FC for defining functional components with props
Minimize useEffect/useState and heavy computations inside render
Use React.memo for components with static props to prevent unnecessary re-renders
Optimize FlatList with removeClippedSubviews, maxToRenderPerBatch, and windowSize
Use getItemLayout for FlatList when items have consistent size
Avoid anonymous functions in renderItem or event handlers
Optimize image handling using react-native-fast-image
Wrap all user-facing text in t() from react-i18next
Ensure support for dark mode and light mode
Ensure the app is accessible following WCAG for mobile
Use react-hook-form for form handling
Use @rnmapbox/maps for maps and navigation
Use lucide-react-native for icons directly in markup; do not us...

Files:

  • src/components/calls/dispatch-selection-modal.tsx
{components/ui/**/*.{ts,tsx},**/*.tsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Use gluestack-ui consistently; if no component exists in components/ui, style via StyleSheet.create or styled-components

Files:

  • src/components/calls/dispatch-selection-modal.tsx
🧠 Learnings (10)
📚 Learning: 2025-08-12T03:33:40.238Z
Learnt from: CR
Repo: Resgrid/Unit PR: 0
File: .cursorrules:0-0
Timestamp: 2025-08-12T03:33:40.238Z
Learning: Applies to **/*.tsx : Use React.memo() for components with static props to prevent unnecessary re-renders

Applied to files:

  • src/components/calls/dispatch-selection-modal.tsx
📚 Learning: 2025-08-12T03:34:25.992Z
Learnt from: CR
Repo: Resgrid/Unit PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-08-12T03:34:25.992Z
Learning: Applies to **/*.tsx : Use React.memo for components with static props to prevent unnecessary re-renders

Applied to files:

  • src/components/calls/dispatch-selection-modal.tsx
📚 Learning: 2025-08-12T03:34:25.992Z
Learnt from: CR
Repo: Resgrid/Unit PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-08-12T03:34:25.992Z
Learning: Applies to **/*.tsx : Minimize useEffect/useState and heavy computations inside render

Applied to files:

  • src/components/calls/dispatch-selection-modal.tsx
📚 Learning: 2025-08-12T03:33:40.238Z
Learnt from: CR
Repo: Resgrid/Unit PR: 0
File: .cursorrules:0-0
Timestamp: 2025-08-12T03:33:40.238Z
Learning: Applies to **/*.tsx : Minimize useEffect, useState, and heavy computations inside render

Applied to files:

  • src/components/calls/dispatch-selection-modal.tsx
📚 Learning: 2025-08-12T03:33:40.238Z
Learnt from: CR
Repo: Resgrid/Unit PR: 0
File: .cursorrules:0-0
Timestamp: 2025-08-12T03:33:40.238Z
Learning: Applies to **/*.tsx : Use functional components and hooks over class components

Applied to files:

  • src/components/calls/dispatch-selection-modal.tsx
📚 Learning: 2025-08-12T03:34:25.992Z
Learnt from: CR
Repo: Resgrid/Unit PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-08-12T03:34:25.992Z
Learning: Applies to **/*.tsx : Use lucide-react-native for icons directly in markup; do not use gluestack-ui icon component

Applied to files:

  • src/components/calls/dispatch-selection-modal.tsx
📚 Learning: 2025-08-12T03:33:40.238Z
Learnt from: CR
Repo: Resgrid/Unit PR: 0
File: .cursorrules:0-0
Timestamp: 2025-08-12T03:33:40.238Z
Learning: Applies to **/*.tsx : Use lucide-react-native for icons directly in markup; do not use the gluestack-ui icon component

Applied to files:

  • src/components/calls/dispatch-selection-modal.tsx
📚 Learning: 2025-08-12T03:33:40.238Z
Learnt from: CR
Repo: Resgrid/Unit PR: 0
File: .cursorrules:0-0
Timestamp: 2025-08-12T03:33:40.238Z
Learning: Applies to **/*.tsx : Use React Navigation for navigation and deep linking with best practices

Applied to files:

  • src/components/calls/dispatch-selection-modal.tsx
📚 Learning: 2025-08-12T03:34:25.992Z
Learnt from: CR
Repo: Resgrid/Unit PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-08-12T03:34:25.992Z
Learning: Applies to {components/ui/**/*.{ts,tsx},**/*.tsx} : Use gluestack-ui consistently; if no component exists in components/ui, style via StyleSheet.create or styled-components

Applied to files:

  • src/components/calls/dispatch-selection-modal.tsx
📚 Learning: 2025-08-12T03:34:25.992Z
Learnt from: CR
Repo: Resgrid/Unit PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-08-12T03:34:25.992Z
Learning: Applies to **/*.tsx : Optimize image handling using react-native-fast-image

Applied to files:

  • src/components/calls/dispatch-selection-modal.tsx
🧬 Code graph analysis (2)
src/services/notification-sound.service.ts (3)
__mocks__/expo-av.ts (3)
  • Audio (2-28)
  • InterruptionModeIOS (30-34)
  • AVPlaybackSource (36-36)
src/lib/logging/index.tsx (2)
  • logger (80-80)
  • error (74-76)
src/stores/push-notification/store.ts (1)
  • NotificationType (13-13)
src/services/push-notification.ts (2)
src/lib/logging/index.tsx (1)
  • logger (80-80)
src/stores/push-notification/store.ts (1)
  • usePushNotificationModalStore (32-104)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: test
🔇 Additional comments (6)
.github/workflows/react-native-cicd.yml (2)

50-50: Also applies to: 75-76


381-434: Changerawr API integration well-implemented with robust error handling.

The inline credential check (lines 387-390) provides clear messaging and allows builds to proceed when optional service credentials aren't configured. JSON payload construction properly escapes variables using jq --arg, and response parsing correctly extracts HTTP status codes using the reliable curl -w "\n%{http_code}" -s pattern paired with tail -n1 and sed '$d'. The build appropriately warns on failures without failing.

src/components/calls/dispatch-selection-modal.tsx (1)

3-3: Good fix—direct call resolves the stale-memo issue.

Removing useMemo and calling getFilteredData() directly ensures filteredData updates on every render, which properly addresses the previous issue where the memo dependencies were incomplete and caused stale data.

Also applies to: 30-30

src/services/__tests__/push-notification.test.ts (1)

29-31: Comprehensive listener lifecycle tests.

The new test section properly validates that listeners are registered during initialization and cleaned up correctly, including edge cases like cleanup without initialization and multiple cleanup calls. The mock setup enables precise verification of listener removal.

Also applies to: 87-95, 376-435

src/services/push-notification.ts (2)

25-27: Listener cleanup properly implemented.

All listener handles are now correctly stored to instance properties and cleaned up in the cleanup() method. This resolves the previous critical issue where listeners would accumulate after each initialization cycle.

Also applies to: 96-97, 100-121, 290-303


85-93: ****

The properties shouldShowBanner and shouldShowList are valid and officially supported in expo-notifications v0.28.3. These are documented properties in the official Expo notification handler API, and they replaced shouldShowAlert in the NotificationBehavior interface as part of updates to better align with iOS's UNNotificationPresentationOptions. The code at lines 85-93 correctly implements the current notification handler API.

Comment on lines +72 to +78
logger.error({
message: 'Failed to initialize notification sound service',
context: { error },
});
// Reset initialization promise on failure so it can be retried
this.initializationPromise = null;
}
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

Propagate initialization failures to callers.

performInitialization() logs the error and clears initializationPromise, but it then returns normally. Any caller awaiting initialize()/initializeAudio() will think setup worked even though nothing loaded, leaving the service in a broken-but-silent state. After logging and resetting the flag, rethrow the error (wrapping non-Error values if needed) so upstream code can decide whether to retry, surface an alert, etc.

🤖 Prompt for AI Agents
In src/services/notification-sound.service.ts around lines 72 to 78,
performInitialization() currently logs the initialization error and clears
this.initializationPromise but then returns normally, leaving callers unaware of
the failure; change the method so after logging and resetting
this.initializationPromise it rethrows the error (if the caught value is not an
instance of Error, wrap it in a new Error with the caught value stringified) so
callers awaiting initialize()/initializeAudio() receive the failure and can
handle or retry.

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: 0

♻️ Duplicate comments (1)
src/services/notification-sound.service.ts (1)

71-78: Still not propagating initialization failures.

This issue was flagged in a previous review but remains unresolved. When performInitialization() catches an error, it logs and resets the promise but returns normally. Callers awaiting initialize() will assume success even though the service is in a broken state.

After line 77, rethrow the error:

     } catch (error) {
       logger.error({
         message: 'Failed to initialize notification sound service',
         context: { error },
       });
       // Reset initialization promise on failure so it can be retried
       this.initializationPromise = null;
+      throw error instanceof Error ? error : new Error(String(error));
     }
🧹 Nitpick comments (2)
src/services/notification-sound.service.ts (2)

137-148: Inconsistent use of the loadSound helper.

Lines 137-138 manually download the chat asset before calling loadSound(), which already handles downloading internally (lines 109-110). Then lines 143-148 manually create the group-chat sound instead of using the helper, bypassing its error handling.

Simplify by using the helper consistently:

-      // Load chat sound
-      const chatSoundAsset = Asset.fromModule(require('@assets/audio/newchat.wav'));
-      await chatSoundAsset.downloadAsync();
-
       this.chatSound = await this.loadSound(require('@assets/audio/newchat.wav'), 'chat');
 
-      // Group chat uses the same sound as regular chat
-      const { sound: groupChatSound } = await Audio.Sound.createAsync({ uri: chatSoundAsset.localUri || chatSoundAsset.uri } as AVPlaybackSource, {
-        shouldPlay: false,
-        isLooping: false,
-        volume: 1.0,
-      });
-      this.groupChatSound = groupChatSound;
+      // Group chat uses the same audio file as regular chat
+      this.groupChatSound = await this.loadSound(require('@assets/audio/newchat.wav'), 'group-chat');

200-205: Optional: Remove unnecessary local variable capture.

Since initializeAudio() completes at line 197, the instance properties are stable and can be used directly in the switch statement. The local variables add verbosity without improving safety or clarity.

Simplify by accessing properties directly:

       // Ensure audio is initialized before accessing sound properties
       await this.initializeAudio();
 
-      // Capture sound properties into local variables after initialization
-      const callSound = this.callSound;
-      const messageSound = this.messageSound;
-      const chatSound = this.chatSound;
-      const groupChatSound = this.groupChatSound;
-      const defaultSound = this.defaultSound;
-
       switch (type) {
         case 'call':
-          await this.playSound(callSound, 'call');
+          await this.playSound(this.callSound, 'call');
           break;
         case 'message':
-          await this.playSound(messageSound, 'message');
+          await this.playSound(this.messageSound, 'message');
           break;
         case 'chat':
-          await this.playSound(chatSound, 'chat');
+          await this.playSound(this.chatSound, 'chat');
           break;
         case 'group-chat':
-          await this.playSound(groupChatSound, 'group-chat');
+          await this.playSound(this.groupChatSound, 'group-chat');
           break;
         case 'unknown':
         default:
-          await this.playSound(defaultSound, 'default');
+          await this.playSound(this.defaultSound, 'default');
           break;
       }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d909c4c and 0a7d9e3.

📒 Files selected for processing (1)
  • src/services/notification-sound.service.ts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursorrules)

**/*.{ts,tsx}: Write concise, type-safe TypeScript code
Use camelCase for variable and function names (e.g., isFetchingData, handleUserInput)
Use TypeScript for all components, favoring interfaces for props and state
Avoid using any; strive for precise types
Use Expo SecureStore for sensitive data
Use zustand for state management
Use react-query for data fetching and caching
Use react-i18next for internationalization
Use react-native-mmkv for local storage
Use axios for API requests

**/*.{ts,tsx}: Write concise, type-safe TypeScript code
Use camelCase for variable and function names
Use TypeScript for all components, favoring interfaces for props and state
Avoid using any; strive for precise types
Use React Navigation for handling navigation and deep linking
Handle errors gracefully and provide user feedback
Use Expo's SecureStore for sensitive data
Use zustand for state management
Use react-query for data fetching
Use react-i18next for internationalization
Use react-native-mmkv for local storage
Use axios for API requests

Files:

  • src/services/notification-sound.service.ts
**/*

📄 CodeRabbit inference engine (.cursorrules)

Directory and file names should be lowercase and hyphenated (e.g., user-profile, chat-screen)

Files:

  • src/services/notification-sound.service.ts
🧬 Code graph analysis (1)
src/services/notification-sound.service.ts (3)
__mocks__/expo-av.ts (3)
  • Audio (2-28)
  • InterruptionModeIOS (30-34)
  • AVPlaybackSource (36-36)
src/lib/logging/index.tsx (2)
  • logger (80-80)
  • error (74-76)
src/stores/push-notification/store.ts (1)
  • NotificationType (13-13)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: test

@ucswift
Copy link
Member Author

ucswift commented Nov 1, 2025

Approve

Copy link

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

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

This PR is approved.

@ucswift ucswift merged commit 5b79d7e into master Nov 1, 2025
13 of 14 checks passed
@coderabbitai coderabbitai bot mentioned this pull request Jan 16, 2026
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