Skip to content

Add Message Reminders #3623

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 61 commits into
base: develop
Choose a base branch
from
Open

Add Message Reminders #3623

wants to merge 61 commits into from

Conversation

nuno-vieira
Copy link
Member

@nuno-vieira nuno-vieira commented Mar 21, 2025

🔗 Issue Links

IOS-696

🎯 Goal

Add message reminders feature. This will allow users to save messages for later and get notified about them.

📝 Summary

Docs PR: https://github.com/GetStream/docs-content/pull/260

New APIs:

  • Adds new Filter.isNil to make it easier to filter nil values
  • Adds ChatMessageController.createReminder()
  • Adds ChatMessageController.updateReminder()
  • Adds ChatMessageController.deleteReminder()
  • Adds MessageReminderListController

Demo App new features:

  • Reminders Tab to show user's reminders
  • Message Cell has Saved for Later styling if it has been saved for reminder
  • Reminder actions when long pressing a message

Bonus:

  • Renames Filter+ChatChannel.swift to Filter+predicate so that we can start using it for other data and not only channels.
  • Changes Frankfurt C2 Environment to use the base URL of our staging by default.

🎨 Showcase

Simulator.Screen.Recording.-.iPhone.16.Pro.-.2025-03-28.at.16.01.16.mp4

🧪 Manual Testing Notes

In order to test the feature, you need to change the API Key to Frankfurt C2 Staging:

  • Open Demo App
  • Logout
  • Tap on Configuration
  • Tap on 1st option (AppKey)
  • Change to "Frankfurt C2 Staging"
  • Go to "Reminders Test" channel, where reminders are enabled

Saving a message as a reminder in the message list:

  1. Long tap a message
  2. Tap on "Remind Me"
  3. Set a time
  4. Message should show "Saved for later UI"
  5. Go to the Reminders Tab
  6. The reminder should be there

Saving a message for later in the message list:

  1. Long tap a message
  2. Tap on "Save For Later"
  3. Message should show "Saved for later UI"
  4. Go to the Reminders Tab
  5. The reminder should be there

Removing a reminder in the message list:

  1. Long tap a message
  2. Tap on "Remove from Later"
  3. Message should NOT show "Saved for later UI"
  4. Go to the Reminders Tab
  5. The remainder should be removed from there as well

Updating a reminder in the message list:

  1. Long tap a message
  2. Tap on "Remind Me"
  3. Set a time
  4. Message should show "Saved for later UI"
  5. Long tap a message
  6. Tap on "Update Reminder"
  7. Change the time
  8. Go to the Reminders Tab
  9. The updated reminder should be there

Updating a saved for later message in the message list:

  1. Long tap a message
  2. Tap on "Save for later"
  3. Message should show "Saved for later UI"
  4. Long tap a message
  5. Tap on "Update Reminder"
  6. Set a time
  7. Go to the Reminders Tab
  8. The updated reminder should be there

Fetching and filtering all reminders:

  1. Tap on the Reminders Tab
  2. Tap on the "Overdue" filter button
  3. Should only show reminders which are overdue
  4. Tap on the "Upcoming" filter button
  5. Should only show reminders which are upcoming
  6. Tap on the "Scheduled" filter button
  7. Should only show reminders which are Overdue or Upcoming
  8. Tap on "Saved for later" filter button
  9. Should only show reminders that do not have any due date.
  10. Swiping a reminder and tapping on DELETE, should delete the reminder.

Deleting a message with a reminder:

  1. Set a reminder in a message
  2. Delete the message
  3. The reminder should be deleted
  4. The reminder should not appear in the Reminders Tab

Reminder when is overdue should be removed from upcoming:

  1. Add a reminder with 2min
  2. Go to the Reminders Tab
  3. Tap on the "Upcoming" filter
  4. Wait for the reminder to overdue
  5. The reminder should disappear from the Upcoming filter
  6. The reminder should now live in the Overdue filter

Reminder Due Push Notification:

  1. Save a reminder with a due date of "1 Minute"
  2. After 1 minute have passed a Push Notification should be triggered.

☑️ Contributor Checklist

  • I have signed the Stream CLA (required)
  • This change should be manually QAed
  • Changelog is updated with client-facing changes
  • Changelog is updated with new localization keys
  • New code is covered by unit tests
  • Documentation has been updated in the docs-content repo

Summary by CodeRabbit

  • New Features
    • Introduced message reminders with create, update, delete, and query capabilities.
    • Added a Reminders tab in the demo app for viewing and managing reminders with filtering options.
    • Enabled reminder and "Save for later" actions directly from message actions.
    • Added visual "Saved for later" indicators on messages.
  • Improvements
    • Added filtering support for nil values to enhance query flexibility.
    • Introduced typed push notification handling for reminder due notifications.
    • Added support for message reminders in channel configurations.
  • Tests
    • Included comprehensive tests covering reminders controllers, repositories, API endpoints, events, and middleware.
  • Documentation
    • Updated changelog to include new message reminder and filtering features.

@nuno-vieira nuno-vieira requested a review from a team as a code owner March 21, 2025 18:22
Copy link

github-actions bot commented Mar 21, 2025

1 Warning
⚠️ Big PR

Generated by 🚫 Danger

@Stream-SDK-Bot
Copy link
Collaborator

SDK Size

title develop branch diff status
StreamChat 7.41 MB 7.77 MB +363 KB 🟡
StreamChatUI 4.78 MB 4.84 MB +65 KB 🟢

@Stream-SDK-Bot
Copy link
Collaborator

SDK Performance

target metric benchmark branch performance status
MessageList Hitches total duration 10 ms 10.01 ms -0.1% 🔽 🟡
Duration 2.6 s 2.54 s 2.31% 🔼 🟢
Hitch time ratio 4 ms per s 3.95 ms per s 1.25% 🔼 🟢
Frame rate 75 fps 77.91 fps 3.88% 🔼 🟢
Number of hitches 1 1.0 0.0% 🟰 🟢

@Stream-SDK-Bot
Copy link
Collaborator

Stream-SDK-Bot commented Mar 21, 2025

SDK Size

title develop branch diff status
StreamChat 7.2 MB 7.55 MB +353 KB 🟡
StreamChatUI 4.72 MB 4.78 MB +65 KB 🟢

Copy link
Contributor

@martinmitrevski martinmitrevski left a comment

Choose a reason for hiding this comment

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

Looks great so far 👍 I think the most important thing is to check how / when push notifications will be supported - the due feature doesn't make much sense without it.
Additionally, apart from docs, we need a notion doc for other SDKs.

@nuno-vieira nuno-vieira requested a review from Copilot March 27, 2025 14:58
Copy link

@Copilot Copilot AI left a comment

Choose a reason for hiding this comment

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

Copilot reviewed 35 out of 55 changed files in this pull request and generated no comments.

Files not reviewed (20)
  • DemoApp/Screens/AppConfigViewController/AppConfigViewController.swift: Language not supported
  • DemoApp/Screens/DemoAppTabBarController.swift: Language not supported
  • DemoApp/Shared/DemoUsers.swift: Language not supported
  • DemoApp/Shared/StreamChatWrapper.swift: Language not supported
  • DemoApp/StreamChat/Components/DemoChatMessageActionsVC.swift: Language not supported
  • DemoApp/StreamChat/Components/DemoChatMessageContentView.swift: Language not supported
  • DemoApp/StreamChat/Components/DemoChatMessageLayoutOptionsResolver.swift: Language not supported
  • DemoApp/StreamChat/DemoAppCoordinator+DemoApp.swift: Language not supported
  • Sources/StreamChat/APIClient/Endpoints/EndpointPath+OfflineRequest.swift: Language not supported
  • Sources/StreamChat/APIClient/Endpoints/EndpointPath.swift: Language not supported
  • Sources/StreamChat/APIClient/Endpoints/MessageEndpoints.swift: Language not supported
  • Sources/StreamChat/APIClient/Endpoints/Payloads/MessagePayloads.swift: Language not supported
  • Sources/StreamChat/ChatClient+Environment.swift: Language not supported
  • Sources/StreamChat/ChatClient.swift: Language not supported
  • Sources/StreamChat/ChatClientFactory.swift: Language not supported
  • Sources/StreamChat/Controllers/CurrentUserController/CurrentUserController.swift: Language not supported
  • Sources/StreamChat/Controllers/MessageController/MessageController.swift: Language not supported
  • Sources/StreamChat/Database/DTOs/MessageDTO.swift: Language not supported
  • Sources/StreamChat/Database/DTOs/MessageReminderDTO.swift: Language not supported
  • Sources/StreamChat/Database/DatabaseSession.swift: Language not supported

@nuno-vieira nuno-vieira force-pushed the add/snooze-messages branch from 60495fe to 1eca9b2 Compare March 27, 2025 15:36
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: 4

♻️ Duplicate comments (1)
Sources/StreamChat/Repositories/RemindersRepository.swift (1)

80-108: ⚠️ Potential issue

Local database write errors are still swallowed – caller may hang indefinitely

A previous review already pointed this out.
If the optimistic local write throws, the completion passed into createReminder is never invoked. The UI will spin forever.

-        } completion: { _ in
+        } completion: { dbError in
+            if let dbError = dbError {
+                completion(.failure(dbError))
+                return
+            }

Apply the same pattern to updateReminder and deleteReminder.

🧹 Nitpick comments (1)
Sources/StreamChat/Repositories/RemindersRepository.swift (1)

41-55: Consider surfacing partial-save failures to the caller

session.saveReminder can throw.
Currently every failure is silently eaten and the reminder is omitted from the resulting array, while the completion is still called with .success.
If a reminder is missing in the UI the user has no clue something went wrong.

If silent dropping is intentional please confirm. Otherwise, wrap the compactMap in a Result and propagate an aggregated error (or at least log at error level).

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 360590d and 97e0650.

📒 Files selected for processing (4)
  • Sources/StreamChat/APIClient/ChatRemoteNotificationHandler.swift (2 hunks)
  • Sources/StreamChat/Database/DTOs/MessageReminderDTO.swift (1 hunks)
  • Sources/StreamChat/Repositories/RemindersRepository.swift (1 hunks)
  • StreamChat.xcodeproj/project.pbxproj (64 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • Sources/StreamChat/Database/DTOs/MessageReminderDTO.swift
  • StreamChat.xcodeproj/project.pbxproj

@nuno-vieira nuno-vieira force-pushed the add/snooze-messages branch from 97e0650 to 9741b8a Compare May 28, 2025 11:20
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: 1

♻️ Duplicate comments (2)
Sources/StreamChat/Database/DTOs/MessageReminderDTO.swift (1)

88-90: ⚠️ Potential issue

Critical compilation error: Invalid Core Data API usage

The method NSEntityDescription.insertNewObject(into:for:) does not exist in Apple's Core Data framework and will cause a compilation error.

Apply this fix to use the correct Core Data API:

-        let request = fetchRequest(messageId: messageId)
-        let new = NSEntityDescription.insertNewObject(into: context, for: request)
+        let new = NSEntityDescription.insertNewObject(
+            forEntityName: MessageReminderDTO.entityName,
+            into: context
+        ) as! MessageReminderDTO
StreamChat.xcodeproj/project.pbxproj (1)

1677-1690: Duplicate PBXBuildFile entries still present

The duplicate entries for Message Reminder feature files identified in the previous review are still present in this code segment, which will cause build issues.

🧹 Nitpick comments (1)
Sources/StreamChat/Database/DTOs/MessageReminderDTO.swift (1)

147-153: Proper cleanup but potential optimization opportunity

The delete method correctly removes the relationship and deletes the DTO. However, it could be optimized by directly loading the reminder DTO instead of going through the message relationship.

Consider this more direct approach:

 func deleteReminder(messageId: MessageId) {
-    let message = message(id: messageId)
-    guard let reminderDTO = message?.reminder else {
+    guard let reminderDTO = MessageReminderDTO.load(messageId: messageId, context: self) else {
         return
     }
     delete(reminderDTO)
-    message?.reminder = nil
+    reminderDTO.message.reminder = nil
 }
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 97e0650 and 9741b8a.

📒 Files selected for processing (5)
  • DemoAppPush/NotificationService.swift (1 hunks)
  • Sources/StreamChat/APIClient/ChatRemoteNotificationHandler.swift (2 hunks)
  • Sources/StreamChat/Database/DTOs/MessageReminderDTO.swift (1 hunks)
  • Sources/StreamChat/Repositories/RemindersRepository.swift (1 hunks)
  • StreamChat.xcodeproj/project.pbxproj (64 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • Sources/StreamChat/APIClient/ChatRemoteNotificationHandler.swift
  • Sources/StreamChat/Repositories/RemindersRepository.swift
🔇 Additional comments (11)
Sources/StreamChat/Database/DTOs/MessageReminderDTO.swift (3)

22-29: Efficient sorting helper implementation

The willSave() override efficiently maintains the sortingRemindAt property for consistent sorting behavior when remindAt is nil. The conditional check prevents unnecessary property updates.


39-69: Comprehensive query handling with proper fallback

The remindersFetchRequest method properly handles dynamic sorting from the query with a sensible default fallback. The switch statement covers all expected sorting keys with graceful handling of unknown keys.


135-135:

❌ Incorrect review comment

Potential data inconsistency: ID assignment mismatch

The reminder ID is being set to payload.messageId instead of payload.id, which could cause data inconsistency if the reminder ID and message ID are meant to be different entities.

Please verify if this is intentional or if it should be:

-        reminderDTO.id = payload.messageId
+        reminderDTO.id = payload.id

🏁 Script executed:

#!/bin/bash
# Check if ReminderPayload has both id and messageId properties
ast-grep --pattern 'struct ReminderPayload {
  $$$
}'

Length of output: 55


🏁 Script executed:

#!/bin/bash
# Locate the definition of ReminderPayload in the codebase
rg -n "struct ReminderPayload" .

# Fallback: check for class-based or typealias occurrences
rg -n "class ReminderPayload" .

# Inspect payload property usages to see which fields exist
rg -n "payload.id" .
rg -n "payload.messageId" .

Length of output: 8239


🏁 Script executed:

#!/bin/bash
# Show ReminderPayload class definition
rg -n "class ReminderPayload" -A 20 Sources/StreamChat/APIClient/Endpoints/Payloads/ReminderPayloads.swift

# Show tests for ReminderPayload
rg -n "class ReminderPayload_Tests" -A 50 Tests/StreamChatTests/APIClient/Endpoints/Payloads/ReminderPayloads_Tests.swift

# Show MessageReminderDTO class definition and key properties
rg -n "class MessageReminderDTO" -A 20 Sources/StreamChat/Database/DTOs/MessageReminderDTO.swift

Length of output: 4386


Incorrect suggestion: ReminderPayload has no separate id field
The ReminderPayload model only includes messageId (no id property), and the API’s JSON uses "message_id"—there is no "id" key for a reminder. Assigning reminderDTO.id = payload.messageId is therefore intentional and correct. No change needed.

Likely an incorrect or invalid review comment.

DemoAppPush/NotificationService.swift (2)

99-101: Proper early return for reminder notifications

The special handling for .reminderDue message types with early return preserves the original notification content, which is appropriate for reminder notifications that likely have pre-formatted content.


112-118: Robust fallback handling for reminder notifications

The fallback logic properly checks the stream payload for reminder due event types, ensuring reminder notifications are handled consistently even when they don't match the expected message notification format.

StreamChat.xcodeproj/project.pbxproj (6)

4429-4466: Comprehensive Message Reminder feature file organization

The file organization for the Message Reminder feature looks well-structured with proper separation of concerns:

  • Controllers and their Combine extensions
  • Queries and DTOs for data management
  • Endpoints and payloads for API communication
  • Events and middleware for real-time updates
  • Repository for data persistence
  • Comprehensive test coverage and mocks
  • Demo UI integration

This follows good architectural patterns for the StreamChat SDK.


5606-5606: Well-organized group structure for new feature

The Message Reminder files are properly organized into logical groups:

  • Events grouped with other event types
  • DTOs with other data transfer objects
  • Queries with other query types
  • Middleware with other event middlewares
  • Payloads with other payload types
  • Endpoints with other API endpoints
  • Models with other domain models
  • Controllers with other controller types

This maintains consistency with the existing project structure.

Also applies to: 5650-5650, 5732-5740, 5815-5815, 5837-5837, 5899-5899, 6008-6008, 6056-6056


8862-8879: Proper controller grouping structure

The MessageReminderListController files are properly grouped with separate directories for the main implementation and tests, following the established pattern in the project.


8906-8916: Comprehensive JSON test resources for reminder events

Good coverage of reminder event types with dedicated JSON files:

  • ReminderCreated.json
  • ReminderUpdated.json
  • ReminderDeleted.json
  • ReminderDue.json

This ensures proper testing of all reminder event scenarios.


2496-2497:

✅ Verification successful

Verify the Filter file renaming impact

The renaming from Filter+ChatChannel.swift to Filter+predicate.swift suggests the filter functionality is being extended beyond channels. Ensure this change is properly reflected throughout the codebase and doesn't break existing channel-specific filter usage.


🏁 Script executed:

#!/bin/bash
# Description: Search for any remaining references to the old filename or channel-specific filter usage that might be affected

# Search for old filename references
rg -i "filter.*channel\.swift|filterchannel" --type swift

# Search for imports or references that might need updating
rg "import.*Filter.*Channel|Filter.*Channel" --type swift

# Check for any channel-specific filter methods that might be affected
ast-grep --pattern 'extension Filter {
  $$$
}'

Length of output: 11727


🏁 Script executed:

#!/bin/bash
# Verify that the old file reference was removed and the new one added in the Xcode project
rg 'Filter\+ChatChannel.swift' -n StreamChat.xcodeproj/project.pbxproj
rg 'Filter\+predicate.swift' -n StreamChat.xcodeproj/project.pbxproj

Length of output: 871


Filter File Rename Verified

I confirmed there are no lingering references to Filter+ChatChannel.swift in the Xcode project or Swift sources—only the new Filter+predicate.swift is listed in project.pbxproj. No further updates are needed.


15568-15570:

✅ Verification successful

Verify atlantis package version compatibility

The atlantis package dependency is being updated from an exact version (1.18.0) to a minimum version requirement (1.28.0) with up-to-next-major-version. This is a significant version jump that should be verified for compatibility.


🌐 Web query:

What are the breaking changes between atlantis 1.18.0 and 1.28.0? Are there any compatibility issues or new requirements?

💡 Result:

Based on the search results, I can provide information about the changes between Atlantis 1.18.0 and 1.28.0, focusing on the ProxymanApp/Atlantis repository which appears to be the most relevant to your query.

Breaking Changes and Updates in Atlantis 1.18.0 to 1.28.0

The most significant changes between versions 1.18.0 and 1.28.0 of the ProxymanApp/Atlantis tool revolve around connectivity improvements, bug fixes, and feature additions. Here's a detailed breakdown:

Major Changes

  • Version 1.28.0 (Released April 19, 2025): Fixed connection issues on macOS 15.4 by implementing modern networking APIs[1][6]

    • Replaced deprecated NetService with NWBrowser and NWConnection
    • Improved connectivity for iOS Simulators with direct connection to Proxyman App
    • Maintained compatibility with iOS devices using Bonjour Service
  • Version 1.27.0: Updated with a new SwiftUI Sample App and revised documentation[6]

  • Version 1.26.0: Addressed duplicated requests issue when using custom URLProtocols[6]

  • Version 1.25.1 & 1.25.2: Fixed crashes when using Atlantis with AVAggregateAssetDownloadTask and updated Podspec[6]

  • Version 1.24.0: Added Privacy Manifest support[6]

  • Version 1.23.0: Added option to opt-out of Method Swizzling on WebSocket (WS/WSS) connections[6]

  • Version 1.22.0: Fixed crashes when intercepting URLSessionUploadTask with nil Data[6]

Compatibility Considerations

  • Atlantis 1.28.0 is backward-compatible with Proxyman 5.18.0 or older versions[6]
  • However, it's recommended to use Atlantis 1.28.0 with Proxyman 5.19.0 or later for optimal performance with the modern APIs[6]

WebSocket Support

The tool provides built-in support for capturing WebSocket traffic from URLSessionWebsocketTask but doesn't natively work with third-party WebSocket libraries such as Starscream that don't use URLSessionWebsocketTask under the hood[1]

Manual Request/Response Handling

For applications using C++ Network libraries or other non-URLSession networking stacks, Atlantis provides functionality to manually add requests and responses for inspection:

Atlantis.add(request: request, response: response, responseBody: responseData)

This allows capturing network traffic from non-standard networking implementations[1]

The changes from 1.18.0 to 1.28.0 represent a series of incremental improvements rather than radical breaking changes, with the most significant update being the connectivity fixes for macOS 15.4 in the latest release.

Citations:


Atlantis dependency version bump is backward-compatible

We’ve verified that upgrading the Atlantis dependency from 1.18.0 to “^1.28.0” introduces only incremental improvements (macOS 15.4 connectivity fixes, bug fixes, new features) with no breaking API changes.

• Location: StreamChat.xcodeproj/project.pbxproj (lines 15568–15570)
• No removed or replaced APIs between 1.18.0 and 1.28.0
• Atlantis 1.28.0 remains compatible with Proxyman 5.18.0+, though Proxyman 5.19.0+ is recommended for optimal performance

No further action required.

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

♻️ Duplicate comments (3)
DemoApp/Screens/DemoReminderListVC.swift (3)

186-208: Add deinit to stop the timer and avoid a retain-cycle leak

refreshTimer holds a strong reference to its target (self).
If the VC is popped while the timer is still scheduled, both will leak.
A simple deinit { stopRefreshTimer() } will guarantee cleanup.


381-384: Handle the Error returned by synchronize

The closure parameter is an Error?, but it is ignored.
Surface the error to the user (alert / banner) or at least log it so failures are discoverable during QA.


491-505: activeController is recreated but never synchronised

After swapping in a fresh controller you immediately read activeController.reminders.
Without a synchronize() call the list will stay empty until the user manually refreshes.
Call activeController.synchronize() right after setting the delegate.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9741b8a and bdc665d.

📒 Files selected for processing (3)
  • DemoApp/Screens/DemoReminderListVC.swift (1 hunks)
  • DemoApp/StreamChat/Components/DemoChatMessageActionsVC.swift (3 hunks)
  • DemoAppPush/NotificationService.swift (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • DemoAppPush/NotificationService.swift
  • DemoApp/StreamChat/Components/DemoChatMessageActionsVC.swift
🔇 Additional comments (1)
DemoApp/Screens/DemoReminderListVC.swift (1)

460-465: Race condition when switching filters during pagination

reminders = Array(activeController.reminders) assumes that activeController did not change during an on-going pagination request (see isPaginatingReminders).
If the user changes the filter while a page is loading, loadMoreReminders’s completion will mutate isPaginatingReminders of the old controller, potentially leaving the new one blocked.

Consider scoping isPaginatingReminders per-controller or cancelling outstanding loads when switching filters.

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

🧹 Nitpick comments (1)
CHANGELOG.md (1)

12-15: ⚠️ Fix list indentation
Nested sub-bullets should be indented with 2 spaces to comply with markdown style guidelines.

Proposed diff:

-   - Add `ChatMessageController.createReminder()`
-   - Add `ChatMessageController.updateReminder()` 
-   - Add `ChatMessageController.deleteReminder()`
-   - Add `MessageReminderListController` and `MessageReminderListQuery`
+  - Add `ChatMessageController.createReminder()`
+  - Add `ChatMessageController.updateReminder()` 
+  - Add `ChatMessageController.deleteReminder()`
+  - Add `MessageReminderListController` and `MessageReminderListQuery`
🧰 Tools
🪛 markdownlint-cli2 (0.17.2)

12-12: Unordered list indentation
Expected: 2; Actual: 3

(MD007, ul-indent)


13-13: Unordered list indentation
Expected: 2; Actual: 3

(MD007, ul-indent)


14-14: Unordered list indentation
Expected: 2; Actual: 3

(MD007, ul-indent)


15-15: Unordered list indentation
Expected: 2; Actual: 3

(MD007, ul-indent)

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e171129 and 2b2ad9a.

📒 Files selected for processing (6)
  • CHANGELOG.md (1 hunks)
  • Sources/StreamChat/APIClient/Endpoints/Payloads/MessagePayloads.swift (5 hunks)
  • Sources/StreamChat/Controllers/MessageController/MessageController.swift (3 hunks)
  • Sources/StreamChat/Database/DTOs/MessageDTO.swift (3 hunks)
  • Sources/StreamChat/Database/DatabaseSession.swift (2 hunks)
  • StreamChat.xcodeproj/project.pbxproj (64 hunks)
🚧 Files skipped from review as they are similar to previous changes (5)
  • Sources/StreamChat/APIClient/Endpoints/Payloads/MessagePayloads.swift
  • Sources/StreamChat/Database/DatabaseSession.swift
  • Sources/StreamChat/Controllers/MessageController/MessageController.swift
  • StreamChat.xcodeproj/project.pbxproj
  • Sources/StreamChat/Database/DTOs/MessageDTO.swift
🧰 Additional context used
🪛 markdownlint-cli2 (0.17.2)
CHANGELOG.md

12-12: Unordered list indentation
Expected: 2; Actual: 3

(MD007, ul-indent)


13-13: Unordered list indentation
Expected: 2; Actual: 3

(MD007, ul-indent)


14-14: Unordered list indentation
Expected: 2; Actual: 3

(MD007, ul-indent)


15-15: Unordered list indentation
Expected: 2; Actual: 3

(MD007, ul-indent)

⏰ Context from checks skipped due to timeout of 90000ms (3)
  • GitHub Check: Test LLC (Debug)
  • GitHub Check: Automated Code Review
  • GitHub Check: Metrics
🔇 Additional comments (3)
CHANGELOG.md (3)

10-10: ✅ Correct: Document Filter.isNil addition
The new Filter.isNil API is accurately reflected in the changelog under the added section.


11-11: ✅ Document Message Reminders
The top-level Add Message Reminders entry correctly surfaces the new feature for users.


1-15: ❓ Document file rename
The PR also renames Filter+ChatChannel.swift to Filter+predicate. Consider adding a “Renamed” or “Changed” entry in the changelog so consumers are aware of the updated filename and import paths.

🧰 Tools
🪛 markdownlint-cli2 (0.17.2)

12-12: Unordered list indentation
Expected: 2; Actual: 3

(MD007, ul-indent)


13-13: Unordered list indentation
Expected: 2; Actual: 3

(MD007, ul-indent)


14-14: Unordered list indentation
Expected: 2; Actual: 3

(MD007, ul-indent)


15-15: Unordered list indentation
Expected: 2; Actual: 3

(MD007, ul-indent)

@nuno-vieira nuno-vieira force-pushed the add/snooze-messages branch from 8bb1b97 to 3f59a94 Compare May 28, 2025 16:10
@nuno-vieira nuno-vieira added the 🤞 Ready For QA A PR that is Ready for QA label May 28, 2025
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

🧹 Nitpick comments (1)
TestTools/StreamChatTestTools/TestData/DummyData/XCTestCase+Dummy.swift (1)

282-305: Consider adding the messageRemindersEnabled flag for consistency.

The dummyPayloadWithNoExtraData function creates a ChannelConfig inline but doesn't include the messageRemindersEnabled flag, which could lead to inconsistent test behavior depending on which dummy data function is used.

For consistency, consider adding the flag here as well:

                    config: .init(
                        reactionsEnabled: true,
                        typingEventsEnabled: true,
                        readEventsEnabled: true,
                        connectEventsEnabled: true,
                        uploadsEnabled: true,
                        repliesEnabled: true,
                        quotesEnabled: true,
                        searchEnabled: true,
                        mutesEnabled: true,
                        urlEnrichmentEnabled: true,
+                        messageRemindersEnabled: true,
                        messageRetention: "1000",
                        maxMessageLength: 100,
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8bb1b97 and 3f59a94.

📒 Files selected for processing (3)
  • TestTools/StreamChatTestTools/TestData/DummyData/XCTestCase+Dummy.swift (1 hunks)
  • Tests/StreamChatTests/Database/DTOs/ChannelDTO_Tests.swift (1 hunks)
  • Tests/StreamChatTests/WebSocketClient/Events/ReminderEvents_Tests.swift (1 hunks)
✅ Files skipped from review due to trivial changes (1)
  • Tests/StreamChatTests/Database/DTOs/ChannelDTO_Tests.swift
🚧 Files skipped from review as they are similar to previous changes (1)
  • Tests/StreamChatTests/WebSocketClient/Events/ReminderEvents_Tests.swift
⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: Metrics
🔇 Additional comments (1)
TestTools/StreamChatTestTools/TestData/DummyData/XCTestCase+Dummy.swift (1)

142-142: LGTM! Test data correctly enables message reminders feature.

The addition of messageRemindersEnabled: true to the default channel configuration ensures that test scenarios properly reflect the new message reminders capability.

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

🧹 Nitpick comments (2)
CHANGELOG.md (2)

6-9: Document the Filter+predicate file rename.

The PR renames Filter+ChatChannel.swift to Filter+predicate.swift to generalize filter extensions beyond channels, but the changelog only lists the addition of Filter.isNil. Please add a "🔄 Changed" entry under the Upcoming ## StreamChat section to reflect this rename. For example:

### 🔄 Changed
- Renamed `Filter+ChatChannel.swift``Filter+predicate.swift` to extend filter helpers beyond channels

10-13: Fix markdown list indentation for nested items.

The nested bullets under - Add Message Reminders [#3623] are indented by 3 spaces, but markdownlint expects 2 spaces for nested list items.
Apply this diff:

-   - Add `ChatMessageController.createReminder()`
-   - Add `ChatMessageController.updateReminder()`
-   - Add `ChatMessageController.deleteReminder()`
-   - Add `MessageReminderListController` and `MessageReminderListQuery`
+  - Add `ChatMessageController.createReminder()`
+  - Add `ChatMessageController.updateReminder()`
+  - Add `ChatMessageController.deleteReminder()`
+  - Add `MessageReminderListController` and `MessageReminderListQuery`
🧰 Tools
🪛 markdownlint-cli2 (0.17.2)

10-10: Unordered list indentation
Expected: 2; Actual: 3

(MD007, ul-indent)


11-11: Unordered list indentation
Expected: 2; Actual: 3

(MD007, ul-indent)


12-12: Unordered list indentation
Expected: 2; Actual: 3

(MD007, ul-indent)


13-13: Unordered list indentation
Expected: 2; Actual: 3

(MD007, ul-indent)

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3f59a94 and 8f90596.

📒 Files selected for processing (2)
  • CHANGELOG.md (1 hunks)
  • DemoAppPush/NotificationService.swift (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • DemoAppPush/NotificationService.swift
🧰 Additional context used
🪛 markdownlint-cli2 (0.17.2)
CHANGELOG.md

10-10: Unordered list indentation
Expected: 2; Actual: 3

(MD007, ul-indent)


11-11: Unordered list indentation
Expected: 2; Actual: 3

(MD007, ul-indent)


12-12: Unordered list indentation
Expected: 2; Actual: 3

(MD007, ul-indent)


13-13: Unordered list indentation
Expected: 2; Actual: 3

(MD007, ul-indent)

@testableapple testableapple added 🧪 QAing and removed 🤞 Ready For QA A PR that is Ready for QA labels May 29, 2025
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

🧹 Nitpick comments (2)
CHANGELOG.md (2)

8-9: Document file rename as part of PR. The PR also renames Filter+ChatChannel.swift to Filter+predicate to extend its reuse beyond channels. Please add a bullet under this section to capture that breaking/user-facing change.


10-13: Fix sub-list indentation. The nested bullets under "Add Message Reminders" use 3 spaces but should follow the project's 2-space indentation per list level (MD007). For example:

-   - Add `ChatMessageController.createReminder()`
+  - Add `ChatMessageController.createReminder()`
🧰 Tools
🪛 markdownlint-cli2 (0.17.2)

10-10: Unordered list indentation
Expected: 2; Actual: 3

(MD007, ul-indent)


11-11: Unordered list indentation
Expected: 2; Actual: 3

(MD007, ul-indent)


12-12: Unordered list indentation
Expected: 2; Actual: 3

(MD007, ul-indent)


13-13: Unordered list indentation
Expected: 2; Actual: 3

(MD007, ul-indent)

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8f90596 and 774961f.

📒 Files selected for processing (1)
  • CHANGELOG.md (1 hunks)
🧰 Additional context used
🪛 markdownlint-cli2 (0.17.2)
CHANGELOG.md

10-10: Unordered list indentation
Expected: 2; Actual: 3

(MD007, ul-indent)


11-11: Unordered list indentation
Expected: 2; Actual: 3

(MD007, ul-indent)


12-12: Unordered list indentation
Expected: 2; Actual: 3

(MD007, ul-indent)


13-13: Unordered list indentation
Expected: 2; Actual: 3

(MD007, ul-indent)

⏰ Context from checks skipped due to timeout of 90000ms (5)
  • GitHub Check: Automated Code Review
  • GitHub Check: Test LLC (Debug)
  • GitHub Check: Build LLC + UI (Xcode 15)
  • GitHub Check: Build Test App and Frameworks
  • GitHub Check: Metrics
🔇 Additional comments (1)
CHANGELOG.md (1)

7-7: Changelog section header formatting is consistent. Using ### ✅ Added aligns with the style of other release entries and clearly denotes the added features.

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 (4)
DemoApp/Screens/DemoReminderListVC.swift (4)

186-208: Timer leaks if the VC is deallocated while hidden

This issue was previously identified. Add cleanup in deinit for safety:

deinit {
    stopRefreshTimer()
}

381-384: Surface synchronisation errors to the user

This issue was previously identified. controller.synchronize { _ in } ignores the Error?. Handle the callback and show an alert or retry banner if the network call fails.


386-395: Propagate pagination errors to the UI

This issue was previously identified. loadMoreReminders swallows the Error? returned by loadMoreReminders { … }. Handle the error and inform the user of pagination failures.


491-505: 🛠️ Refactor suggestion

Controllers recreated but not synchronised and incomplete filter handling

This partially duplicates a previous issue - controllers are recreated but synchronize() is not called. Additionally, the logic only handles upcoming, overdue, and scheduled filters but ignores all and later filters when updating with new date.

 private func updateReminderListsWithNewNowDate() {
+    allRemindersController = FilterOption.all.makeController(client: currentUserController.client)
     upcomingRemindersController = FilterOption.upcoming.makeController(client: currentUserController.client)
     overdueRemindersController = FilterOption.overdue.makeController(client: currentUserController.client)
     scheduledRemindersController = FilterOption.scheduled.makeController(client: currentUserController.client)
-    if selectedFilter == .upcoming {
+    laterRemindersController = FilterOption.later.makeController(client: currentUserController.client)
+    
+    switch selectedFilter {
+    case .all:
+        activeController = allRemindersController
+    case .upcoming:
         activeController = upcomingRemindersController
-    } else if selectedFilter == .overdue {
+    case .overdue:
         activeController = overdueRemindersController
-    } else if selectedFilter == .scheduled {
+    case .scheduled:
         activeController = scheduledRemindersController
-    } else {
-        return
+    case .later:
+        activeController = laterRemindersController
     }
+    
     activeController.delegate = self
+    activeController.synchronize()
     updateRemindersData()
 }
🧹 Nitpick comments (2)
DemoApp/Screens/DemoReminderListVC.swift (2)

409-411: Fix suspicious calculation for 1-minute reminder.

The calculation 1.05 * 60 for a 1-minute reminder seems incorrect. This results in 63 seconds instead of 60 seconds. If the intention is to add a small buffer, this should be documented or use a more explicit approach.

-        alert.addAction(UIAlertAction(title: "Remind in 1 Minutes", style: .default) { [weak self] _ in
-            let date = Date().addingTimeInterval(1.05 * 60)
+        alert.addAction(UIAlertAction(title: "Remind in 1 Minute", style: .default) { [weak self] _ in
+            let date = Date().addingTimeInterval(60) // 1 minute
             self?.updateReminderDate(for: reminder, newDate: date)
         })

703-762: Consider simplifying time formatting logic.

The time formatting logic works correctly but has duplicate rounding calculations. Consider extracting the common rounding logic to reduce code duplication.

+    private func roundTimeInterval(_ timeInterval: TimeInterval) -> TimeInterval {
+        let roundedMinutes = ceil(timeInterval / 60 - 0.5)
+        return max(roundedMinutes * 60, 60) // Minimum 1 minute
+    }
+    
     private func formatOverdueTime(timeInterval: TimeInterval) -> String {
-        // Round to the nearest minute (30 seconds or more rounds up)
-        let roundedMinutes = ceil(timeInterval / 60 - 0.5)
-        let roundedInterval = roundedMinutes * 60
-        
-        // If less than a minute, show "1 min" instead of "0 min"
-        if roundedInterval == 0 {
-            return "Overdue by 1 min"
-        }
+        let roundedInterval = roundTimeInterval(timeInterval)
         
         let formatter = DateComponentsFormatter()
         // ... rest of the formatting logic
     }
     
     private func formatDueTime(timeInterval: TimeInterval) -> String {
-        // Round to the nearest minute (30 seconds or more rounds up)
-        let roundedMinutes = ceil(timeInterval / 60 - 0.5)
-        let roundedInterval = roundedMinutes * 60
-        
-        // If less than a minute, show "1 min" instead of "0 min"
-        if roundedInterval == 0 {
-            return "1m"
-        }
+        let roundedInterval = roundTimeInterval(timeInterval)
+        
+        if roundedInterval == 60 {
+            return "1m"
+        }
         
         let formatter = DateComponentsFormatter()
         // ... rest of the formatting logic
     }
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 774961f and bcbf854.

📒 Files selected for processing (1)
  • DemoApp/Screens/DemoReminderListVC.swift (1 hunks)
⏰ Context from checks skipped due to timeout of 90000ms (5)
  • GitHub Check: Build LLC + UI (Xcode 15)
  • GitHub Check: Test LLC (Debug)
  • GitHub Check: Build Test App and Frameworks
  • GitHub Check: Automated Code Review
  • GitHub Check: Metrics
🔇 Additional comments (3)
DemoApp/Screens/DemoReminderListVC.swift (3)

398-459: Well-implemented user interaction handlers.

The error handling, user avatar interaction, and edit reminder options are well-designed and provide good user experience. The alert presentation with proper popover configuration for iPad is a nice touch.


510-581: Solid table view implementation with good UX.

The table view implementation includes proper error handling for deletion, well-configured swipe actions, and appropriate navigation to channels with pagination parameters. The scroll-based pagination trigger is also well-implemented.


9-765: Well-architected reminder list implementation.

This is a comprehensive and well-structured implementation of the reminder list feature. The architecture demonstrates good separation of concerns, proper use of delegation patterns, and follows iOS best practices. The filter system is particularly well-designed with dedicated controllers for performance optimization.

Copy link

sonarqubecloud bot commented Jun 4, 2025

Quality Gate Failed Quality Gate failed

Failed conditions
4.1% Duplication on New Code (required ≤ 3%)

See analysis details on SonarQube Cloud

@testableapple testableapple added 🟢 QAed A PR that was QAed and removed 🧪 QAing labels Jun 4, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
✅ Feature An issue or PR related to a feature 🟢 QAed A PR that was QAed 🌐 SDK: StreamChat (LLC) Tasks related to the StreamChat LLC SDK 🎨 SDK: StreamChatUI Tasks related to the StreamChatUI SDK
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants