Skip to content

Conversation

@DSingh0304
Copy link

@DSingh0304 DSingh0304 commented Dec 28, 2025

Proposed changes

This PR implements a "Mark as Read" quick action button for push notifications on both iOS and Android platforms. This feature allows users to mark messages/rooms as read directly from a push notification without opening the app, providing a convenient way to manage notifications.

Implementation Details:

  • Added new notification action MARK_AS_READ_ACTION alongside the existing Reply action
  • Calls the existing POST /api/v1/subscriptions.read API endpoint (available since Rocket.Chat server v0.61.0)
  • Notification is dismissed after successful API call
  • Follows the same pattern as the existing Reply notification action

Platforms:

  • ✅ iOS (13+)
  • ✅ Android (API 24+)

Issue(s)

Closes #6842

How to test or reproduce

Prerequisites:

  • Rocket.Chat server (v0.61.0+) with push notifications configured
  • Physical device or production build (debug builds may have FCM token registration limitations)

Testing Steps:

  1. Build and install the app on a device
  2. Login to your Rocket.Chat account
  3. Grant notification permissions when prompted
  4. Send a message to yourself from another device/browser (or have someone send you a message)
  5. Close/minimize the app (notification only appears when app is backgrounded)
  6. Wait for notification to appear
  7. Pull down/expand the notification to see action buttons
  8. Verify you see both:
    • "Reply" button (existing)
    • "Mark as read" button (NEW) ✨
  9. Tap "Mark as read"
  10. Expected result:
    • Notification dismisses
    • Room shows as read when you open the app (no unread badge)

Note on Testing:

Due to Firebase configuration limitations in development builds, full end-to-end push notification testing requires production Firebase credentials. The implementation follows the existing Reply action pattern and uses the established subscriptions.read API endpoint.

Screenshots

Types of changes

  • Bugfix (non-breaking change which fixes an issue)
  • Improvement (non-breaking change which improves a current function)
  • New feature (non-breaking change which adds functionality)
  • Documentation update (if none of the other choices apply)

Checklist

  • I have read the CONTRIBUTING doc
  • I have signed the CLA
  • Lint and unit tests pass locally with my changes
  • I have added tests that prove my fix is effective or that my feature works (if applicable)
  • I have added necessary documentation (if applicable)
  • Any dependent changes have been merged and published in downstream modules

Further comments

Implementation Approach:

This feature was implemented by following the existing Reply notification action pattern:

Files Modified/Created (8 total):

Common (2 files):

  • app/i18n/locales/en.json - Added "Mark_as_read" translation
  • app/lib/notifications/push.ts - Registered MARK_AS_READ_ACTION notification action

iOS (3 files):

  • ios/Shared/RocketChat/API/Requests/MarkAsRead.swift - NEW: API request definition
  • ios/Shared/RocketChat/RocketChat.swift - Added markAsRead() helper method
  • ios/ReplyNotification.swift - Added handleMarkAsReadAction() handler

Android (3 files):

  • android/app/src/main/java/.../MarkAsReadBroadcast.java - NEW: Broadcast receiver
  • android/app/src/main/AndroidManifest.xml - Registered broadcast receiver
  • android/app/src/main/java/.../CustomPushNotification.java - Added notification action button

API Endpoint:

Uses the existing POST /api/v1/subscriptions.read endpoint which:

  • Requires rid (room ID) parameter
  • Is already used internally by the app
  • Available since Rocket.Chat server v0.61.0
  • No server-side changes required

Testing Limitations:

Full push notification testing requires production Firebase credentials which are not available in development builds. This is a known limitation discussed in the community (reference: community conversation). The code follows established patterns and compiles successfully on both platforms.

Why This Approach:

  1. Consistency - Mirrors the existing Reply action implementation
  2. No breaking changes - Purely additive feature
  3. Minimal server impact - Uses existing API endpoint
  4. User convenience - Common use case in many messaging apps

Summary by CodeRabbit

  • New Features

    • Added a "Mark as read" action to notifications on iOS and Android so users can mark messages as read directly from notification alerts without opening the app.
    • Notifications now include a tappable action that performs the mark-as-read request and clears the corresponding system notification on success.
  • Localization

    • Added English label for the "Mark as read" notification action.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Dec 28, 2025

Walkthrough

Adds a "Mark as read" notification action on Android and iOS: manifest and native handlers, Android broadcast receiver and PendingIntent wiring, iOS API request and client method, MARK_AS_READ action in push config, and an English localization key. The action posts to /api/v1/subscriptions.read and dismisses the notification on success.

Changes

Cohort / File(s) Summary
Android Manifest
android/app/src/main/AndroidManifest.xml
Declares new broadcast receiver chat.rocket.reactnative.notification.MarkAsReadBroadcast (android:enabled="true", android:exported="false").
Android Notification & Receiver
android/app/src/main/java/chat/rocket/reactnative/notification/CustomPushNotification.java, android/app/src/main/java/chat/rocket/reactnative/notification/MarkAsReadBroadcast.java
Adds notificationMarkAsRead() to attach a "Mark as read" action with correct PendingIntent/API-level handling; implements MarkAsReadBroadcast to parse intent payload, POST to /api/v1/subscriptions.read, clear local messages, and cancel the system notification on success.
iOS Notification Handling
ios/ReplyNotification.swift
Wires MARK_AS_READ_ACTION handling and adds handleMarkAsReadAction(...) to extract payload, call RocketChat.markAsRead, and manage background task/completion.
iOS API Layer
ios/Shared/RocketChat/API/Requests/MarkAsRead.swift, ios/Shared/RocketChat/RocketChat.swift
New MarkAsReadRequest, MarkAsReadBody, MarkAsReadResponse; adds RocketChat.markAsRead(rid:completion:) that posts to /api/v1/subscriptions.read.
Notifications Config & Localization
app/lib/notifications/push.ts, app/i18n/locales/en.json
Adds MARK_AS_READ_ACTION to MESSAGE category and new localization key "Mark_as_read": "Mark as read".

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant OS as Notification System
    participant App
    participant Server

    User->>OS: Tap "Mark as read" action
    OS->>App: Deliver intent / UNNotificationResponse

    rect rgb(220,235,245)
    Note over App: Extract payload (rid, serverURL, auth tokens)
    App->>App: Validate payload & auth
    end

    rect rgb(245,235,220)
    Note over App: Send API request
    App->>Server: POST /api/v1/subscriptions.read { rid } (with auth headers)
    end

    alt success
        Server-->>App: { success: true }
        App->>App: Clear local unread state / cache
        App->>OS: Cancel/dismiss notification
    else failure
        Server-->>App: error / non-200
        App->>App: Log error (leave notification)
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Suggested reviewers

  • OtavioStasiak

Poem

🐰 A tiny hop, a button pressed,
Mark as read — let inbox rest,
From Android intents to Swift's reply,
A little request, the badge says bye,
Inbox lighter, I twitch my nose.

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 13.16% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title 'feat: Implement 'Mark as Read' action for push notifications' clearly and accurately summarizes the main change - adding a Mark as Read action to push notifications on both iOS and Android platforms.
Linked Issues check ✅ Passed The PR successfully implements all key requirements from #6842: Mark as Read action for iOS 13+ and Android 7.0+, subscriptions.read API integration, action buttons alongside Reply, proper dismissal of notifications, and foreground/background support.
Out of Scope Changes check ✅ Passed All changes are directly scoped to implementing the Mark as Read feature across iOS and Android platforms with supporting localization and API integration. No unrelated or extraneous changes detected.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

📜 Recent review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Jira integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between e487fe4 and 8c3bc38.

📒 Files selected for processing (1)
  • android/app/src/main/java/chat/rocket/reactnative/notification/MarkAsReadBroadcast.java
🚧 Files skipped from review as they are similar to previous changes (1)
  • android/app/src/main/java/chat/rocket/reactnative/notification/MarkAsReadBroadcast.java

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
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🧹 Nitpick comments (2)
android/app/src/main/java/chat/rocket/reactnative/notification/CustomPushNotification.java (1)

659-689: Consider externalizing the action label for localization.

The method correctly implements the Mark as Read action following the same pattern as notificationReply. However, the label is hardcoded as "Mark as read" instead of loading from Android string resources, which prevents localization.

🔎 Suggested improvement for localization

If you want to support localization similar to the iOS implementation, consider:

  1. Add to android/app/src/main/res/values/strings.xml:
<string name="mark_as_read">Mark as read</string>
  1. Update the method:
-        String label = "Mark as read";
+        String label = mContext.getString(R.string.mark_as_read);

Alternatively, if localization isn't needed on Android (the label won't be translated), the current hardcoded approach is acceptable.

ios/ReplyNotification.swift (1)

125-145: Consider adding error handling for mark-as-read failures.

The implementation correctly extracts the payload and performs the mark-as-read operation with proper background task management. However, unlike handleReplyAction (lines 113-120), this method doesn't handle or notify the user about failures.

🔎 Suggested improvement for consistency

To match the error handling pattern in handleReplyAction:

     rocketchat.markAsRead(rid: rid) { response in
       DispatchQueue.main.async {
+        defer {
+          UIApplication.shared.endBackgroundTask(backgroundTask)
+          completionHandler()
+        }
+
+        guard let response = response, response.success else {
+          // Optionally show failure notification
+          let content = UNMutableNotificationContent()
+          content.body = "Failed to mark as read."
+          let request = UNNotificationRequest(identifier: "markAsReadFailure", content: content, trigger: nil)
+          UNUserNotificationCenter.current().add(request, withCompletionHandler: nil)
+          return
+        }
-        UIApplication.shared.endBackgroundTask(backgroundTask)
-        completionHandler()
       }
     }

This also addresses the SwiftLint warning about the unused response parameter.

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Jira integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 0d13c14 and b2fd8db.

📒 Files selected for processing (8)
  • android/app/src/main/AndroidManifest.xml
  • android/app/src/main/java/chat/rocket/reactnative/notification/CustomPushNotification.java
  • android/app/src/main/java/chat/rocket/reactnative/notification/MarkAsReadBroadcast.java
  • app/i18n/locales/en.json
  • app/lib/notifications/push.ts
  • ios/ReplyNotification.swift
  • ios/Shared/RocketChat/API/Requests/MarkAsRead.swift
  • ios/Shared/RocketChat/RocketChat.swift
🧰 Additional context used
🧬 Code graph analysis (4)
ios/ReplyNotification.swift (3)
ios/NotificationService/NotificationService.swift (1)
  • didReceive (9-73)
ios/Shared/Extensions/String+Extensions.swift (1)
  • removeTrailingSlash (25-31)
ios/Shared/RocketChat/RocketChat.swift (1)
  • markAsRead (84-95)
android/app/src/main/java/chat/rocket/reactnative/notification/CustomPushNotification.java (3)
android/app/src/main/java/chat/rocket/reactnative/notification/VideoConfBroadcast.kt (1)
  • TAG (14-73)
android/app/src/main/java/chat/rocket/reactnative/notification/VideoConfNotification.kt (2)
  • context (21-210)
  • createNotificationChannel (54-77)
android/app/src/main/java/chat/rocket/reactnative/notification/MarkAsReadBroadcast.java (1)
  • MarkAsReadBroadcast (22-86)
android/app/src/main/java/chat/rocket/reactnative/notification/MarkAsReadBroadcast.java (1)
android/app/src/main/java/chat/rocket/reactnative/notification/CustomPushNotification.java (1)
  • CustomPushNotification (48-721)
ios/Shared/RocketChat/RocketChat.swift (4)
ios/Shared/RocketChat/API/API.swift (1)
  • fetch (54-91)
ios/Shared/RocketChat/API/Request.swift (1)
  • request (45-72)
ios/Shared/RocketChat/Database.swift (1)
  • readRoomEncrypted (117-125)
ios/Shared/RocketChat/Encryption.swift (2)
  • encryptContent (212-269)
  • decryptContent (175-210)
🪛 SwiftLint (0.57.0)
ios/ReplyNotification.swift

[Warning] 139-139: Unused parameter in a closure should be replaced with _

(unused_closure_parameter)

ios/Shared/RocketChat/RocketChat.swift

[Error] 25-25: @escaping must have a trailing space before the associated type

(attribute_name_spacing)


[Error] 39-39: @escaping must have a trailing space before the associated type

(attribute_name_spacing)


[Warning] 34-34: Avoid using unneeded break statements

(unneeded_break_in_switch)


[Error] 84-84: @escaping must have a trailing space before the associated type

(attribute_name_spacing)


[Warning] 71-71: Avoid using unneeded break statements

(unneeded_break_in_switch)


[Warning] 92-92: Avoid using unneeded break statements

(unneeded_break_in_switch)

🔇 Additional comments (7)
android/app/src/main/java/chat/rocket/reactnative/notification/MarkAsReadBroadcast.java (1)

22-43: LGTM!

The onReceive implementation correctly extracts notification data, validates the notification ID format, and delegates to the markAsRead method. Error handling for invalid notification IDs is appropriate.

app/i18n/locales/en.json (1)

502-502: LGTM!

The localization entry follows the existing naming convention and is properly placed in alphabetical order.

app/lib/notifications/push.ts (1)

91-110: LGTM! Properly implements iOS notification action.

The MARK_AS_READ_ACTION correctly mirrors the existing REPLY_ACTION pattern, with appropriate configuration:

  • Uses localized button title via I18n.t('Mark_as_read')
  • Sets opensAppToForeground: false to handle the action in the background
  • Properly registered in the MESSAGE category alongside existing actions
android/app/src/main/AndroidManifest.xml (1)

95-98: LGTM! Receiver declaration is properly configured.

The receiver configuration is correct:

  • android:exported="false" properly restricts access to internal app components only (security best practice)
  • No intent-filter needed since it's triggered by direct PendingIntent from the notification action
  • Logical placement between similar notification receivers
android/app/src/main/java/chat/rocket/reactnative/notification/CustomPushNotification.java (1)

446-446: LGTM! Action properly integrated into notification flow.

The notificationMarkAsRead call is correctly placed alongside notificationReply, ensuring the Mark as Read action appears only on full notifications (not on fallback message-id-only placeholders).

ios/ReplyNotification.swift (1)

44-48: LGTM! Action handler properly registered.

The MARK_AS_READ_ACTION is correctly intercepted and handled natively before forwarding to the original delegate, following the same pattern as REPLY_ACTION.

ios/Shared/RocketChat/API/Requests/MarkAsRead.swift (1)

11-34: LGTM! API request implementation follows established patterns.

The implementation correctly follows the Request/Response pattern used throughout the iOS codebase:

  • MarkAsReadBody properly encodes the required rid parameter
  • MarkAsReadResponse conforms to the Response protocol
  • MarkAsReadRequest uses the correct HTTP POST method and endpoint path
  • Body encoding uses JSONEncoder consistently with other requests

…edentials, and a memory monitor, while refactoring Android notification handling and updating Firebase configuration.
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (2)
android/app/src/main/java/chat/rocket/reactnative/notification/MarkAsReadBroadcast.java (2)

25-25: Configure timeouts for OkHttpClient.

While the static instance addresses the reuse concern from previous reviews, the client lacks timeout configuration. Network operations without timeouts can hang indefinitely, particularly on poor connections or when the server is unresponsive.

🔎 Proposed timeout configuration
-    private static final OkHttpClient client = new OkHttpClient();
+    private static final OkHttpClient client = new OkHttpClient.Builder()
+            .connectTimeout(10, TimeUnit.SECONDS)
+            .readTimeout(10, TimeUnit.SECONDS)
+            .writeTimeout(10, TimeUnit.SECONDS)
+            .build();

You'll also need to add the import:

import java.util.concurrent.TimeUnit;

56-56: Prefer Gson for JSON construction.

Manual string formatting is fragile—if rid contains quotes or backslashes, the JSON will be malformed. Using Gson ensures proper escaping and is more maintainable.

🔎 Proposed refactor
+        // Create a simple wrapper class or use a Map
+        java.util.Map<String, String> payload = new java.util.HashMap<>();
+        payload.put("rid", rid);
+        String json = new Gson().toJson(payload);
-        String json = String.format("{\"rid\":\"%s\"}", rid);

         RequestBody body = RequestBody.create(JSON, json);
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Jira integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between b2fd8db and e487fe4.

📒 Files selected for processing (2)
  • android/app/src/main/java/chat/rocket/reactnative/notification/MarkAsReadBroadcast.java
  • ios/Shared/RocketChat/RocketChat.swift
🚧 Files skipped from review as they are similar to previous changes (1)
  • ios/Shared/RocketChat/RocketChat.swift
🔇 Additional comments (2)
android/app/src/main/java/chat/rocket/reactnative/notification/MarkAsReadBroadcast.java (2)

60-61: No action needed. The ejson.token() and ejson.userId() methods already validate their inputs and return non-null values (returning empty strings as fallback when credentials are missing), with appropriate error logging. The code at lines 60-61 is safe from null reference exceptions.

Likely an incorrect or invalid review comment.


77-78: No action needed. CustomPushNotification.clearMessages(notId) is thread-safe because it performs an atomic remove() operation on a ConcurrentHashMap, which is designed for concurrent access.

Likely an incorrect or invalid review comment.

…n, and improve Android notification async handling.
@DSingh0304 DSingh0304 requested a deployment to approve_e2e_testing January 5, 2026 13:08 — with GitHub Actions Waiting
@diegolmello
Copy link
Member

@DSingh0304 Thanks for your contribution!
It may take a while to review, since we're just starting the year, but we'll do it eventually 🙏🏼

@DSingh0304 DSingh0304 requested a deployment to experimental_android_build January 5, 2026 13:12 — with GitHub Actions Waiting
@DSingh0304 DSingh0304 requested a deployment to experimental_ios_build January 5, 2026 13:12 — with GitHub Actions Waiting
@DSingh0304 DSingh0304 requested a deployment to official_android_build January 5, 2026 13:12 — with GitHub Actions Waiting
@DSingh0304
Copy link
Author

@DSingh0304 Thanks for your contribution! It may take a while to review, since we're just starting the year, but we'll do it eventually 🙏🏼

Yes Sure !!

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.

feature: Add "Mark as Read" action button to push notifications

2 participants