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 60 commits into
base: develop
Choose a base branch
from
Open

Add Message Reminders #3623

wants to merge 60 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

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

🔭 Outside diff range comments (1)
Sources/StreamChat/Models/ChatMessage.swift (1)

272-325: ⚠️ Potential issue

Missing reminder parameter in the changing method.

The changing method doesn't include a reminder parameter, which is inconsistent with the replacing method and could lead to unexpected behavior when creating modified message instances.

     public func changing(
         text: String? = nil,
         type: MessageType? = nil,
         state: LocalMessageState? = nil,
         command: String? = nil,
         arguments: String? = nil,
         attachments: [AnyChatMessageAttachment]? = nil,
         translations: [TranslationLanguage: String]? = nil,
         originalLanguage: TranslationLanguage? = nil,
         moderationDetails: MessageModerationDetails? = nil,
-        extraData: [String: RawJSON]? = nil
+        extraData: [String: RawJSON]? = nil,
+        reminder: MessageReminderInfo? = nil
     ) -> ChatMessage {
         .init(
             // ... existing parameters ...
             textUpdatedAt: textUpdatedAt,
-            draftReply: draftReply
+            draftReply: draftReply,
+            reminder: reminder ?? self.reminder
         )
     }
♻️ Duplicate comments (2)
Tests/StreamChatTests/Repositories/RemindersRepository_Tests.swift (2)

282-301: Same response–enqueue race as above
See the earlier comment – reorder the call to test_mockResponseResult before invoking updateReminder.


428-438: Same response–enqueue race as above
Re-order the mocked response setup for deleteReminder.

🧹 Nitpick comments (20)
TestTools/StreamChatTestTools/Mocks/StreamChat/Database/DatabaseSession_Mock.swift (1)

69-71: Consider adding error simulation capability.

The deleteReminder method delegates correctly, but unlike some other mock methods, it doesn't call throwErrorIfNeeded(). Consider whether error simulation should be supported for testing error scenarios in reminder deletion.

 func deleteReminder(messageId: MessageId) {
+    try? throwErrorIfNeeded()
     underlyingSession.deleteReminder(messageId: messageId)
 }

Note: This would require changing the method signature to throws if error simulation is desired.

DemoApp/Shared/DemoUsers.swift (1)

272-279:

❓ Verification inconclusive

Verify URL correctness and consider configuration alternatives.

The new customBaseURL property implementation looks structurally sound, but there are a couple of considerations:

  1. URL Inconsistency: The URL contains "frankfurt-ce1" but this is for the Frankfurt C2 environment. Please verify this is the correct staging URL.

  2. Hardcoded URLs: Consider whether staging URLs should be externally configurable rather than hardcoded, especially for security and maintainability reasons.

Please verify the staging URL is correct:


🌐 Web query:

Is https://chat-edge-frankfurt-ce1.stream-io-api.com/ the correct staging URL for Stream's Frankfurt C2 environment?

💡 Result:

Based on the available information, there is no direct confirmation in the provided sources regarding whether https://chat-edge-frankfurt-ce1.stream-io-api.com/ is the correct staging URL for Stream's Frankfurt C2 environment. None of the search results specifically reference Stream staging or production environments, their corresponding URLs, or naming conventions.

Given the URL structure, it appears to follow a naming convention that could be used for a Stream Chat edge node in Frankfurt ("frankfurt"), potentially in a "ce1" cluster or environment. The "C2" in your query may refer to a specific cluster or environment designation at Stream, but without documentation or a reliable reference in the search results, this cannot be confirmed.

Recommendation:
To verify the correct staging URL for Stream's Frankfurt C2 environment, consult:

  • Stream's official developer documentation.
  • The Stream team or your internal environment configuration documentation.
  • Any onboarding or environment setup materials provided by Stream.

If you have access to your organization's environment configuration files, they may also reference the correct URL directly. If you want a definitive answer, you should reach out to Stream support or consult their environment documentation.

Citations:


Confirm Frankfurt C2 Staging URL and Externalize Configuration

The customBaseURL for .frankfurtC2 currently points to
https://chat-edge-frankfurt-ce1.stream-io-api.com/
Please verify that this “ce1” cluster is indeed the correct staging endpoint for your Frankfurt C2 environment.

File: DemoApp/Shared/DemoUsers.swift — Lines 272–279

• Ensure the URL matches the official Stream staging endpoint for Frankfurt C2 (consult Stream’s docs or internal environment-config sources).
• Consider moving these staging URLs into a configuration file or environment variables to improve maintainability and security.

CHANGELOG.md (1)

12-15: Fix list indentation for nested items
The sub-bullets under “Add Message Reminders” are indented by 3 spaces, but the rest of the changelog uses 2 spaces. Adjust to maintain consistency and satisfy markdownlint’s MD007 rule.

-   - Add `ChatMessageController.createReminder()`
+  - Add `ChatMessageController.createReminder()`
-   - Add `ChatMessageController.updateReminder()` 
+  - Add `ChatMessageController.updateReminder()` 
-   - Add `ChatMessageController.deleteReminder()`
+  - Add `ChatMessageController.deleteReminder()`
-   - Add `MessageReminderListController` and `MessageReminderListQuery`
+  - 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)

Sources/StreamChat/APIClient/Endpoints/ReminderEndpoints.swift (1)

8-9: Clarify the comment for createReminder method.

The comment states "Creates or updates a reminder" but there's a separate updateReminder method. This could be confusing for API consumers.

-    // Creates or updates a reminder for a message
+    // Creates a new reminder for a message
Sources/StreamChat/APIClient/Endpoints/Payloads/ReminderPayloads.swift (1)

8-44: Consider using struct instead of class for ReminderPayload.

Since ReminderPayload represents immutable data and doesn't require reference semantics, a struct would be more appropriate than a class. This applies to the other payload types as well.

-class ReminderPayload: Decodable {
+struct ReminderPayload: Decodable {

Structs provide value semantics, better performance for small data models, and are more idiomatic in Swift for data transfer objects.

TestTools/StreamChatTestTools/Mocks/StreamChat/Repositories/RemindersRepository_Mock.swift (1)

121-121: Ensure consistent error handling pattern.

The deleteReminder method immediately calls the completion with the preset error, while other methods check for preset results first. Consider making this pattern consistent across all methods.

Apply this diff for consistency:

-        completion(deleteReminder_error)
+        if let error = deleteReminder_error {
+            completion(error)
+        } else {
+            completion(nil)
+        }
DemoApp/Screens/DemoAppTabBarController.swift (3)

17-18: Unused property should be removed or utilized.

The eventsController property is declared but never used in the implementation. Either remove it or implement the intended functionality.

-    // Events controller for listening to chat events
-    private var eventsController: EventsController!

109-112: Consider caching reminder count for performance.

The badge update method accesses allRemindersListController.reminders which might trigger database queries. Consider caching the count if this becomes a performance concern.

 private func updateRemindersBadge() {
-    let reminders = allRemindersListController.reminders
-    reminderListVC.tabBarItem.badgeValue = reminders.isEmpty ? nil : "\(reminders.count)"
+    let reminderCount = allRemindersListController.reminders.count
+    reminderListVC.tabBarItem.badgeValue = reminderCount > 0 ? "\(reminderCount)" : nil
 }

17-18: Consider removing or utilizing the events controller property.

The eventsController property is declared but doesn't appear to be used in the constructor or elsewhere in the visible code. This might be leftover code or incomplete implementation.

If this property is not used, consider removing it:

-    // Events controller for listening to chat events
-    private var eventsController: EventsController!

If it's needed for future implementation, add a TODO comment explaining its purpose.

DemoApp/StreamChat/Components/DemoChatMessageActionsVC.swift (1)

117-151: Localise & DRY alert-action titles

Hard-coded English strings and duplicated blocks make localisation/error-prone maintenance hard.
Consider extracting a helper that builds the preset list or using String.localizedStringWithFormat.

Sources/StreamChat/WebSocketClient/Events/ReminderEvents.swift (1)

28-33: payload property is dead code

ReminderCreated/Updated/Deleted/Due …DTO store the full EventPayload in an unused payload var.
If it’s only needed inside init, drop the property to save memory and silence warnings.

DemoApp/Screens/DemoReminderListVC.swift (2)

387-395: Same for pagination – expose failures

loadMoreReminders swallows the pagination error; consider updating UI or reverting isPaginatingReminders only on success.


703-738: Rounding logic can under-report 59-second intervals

ceil(timeInterval / 60 - 0.5) is clever but produces 0 when timeInterval < 30 s.
Use max(1, Int((timeInterval / 60).rounded())) to guarantee a minimum of “1 min”.

Tests/StreamChatTests/Repositories/RemindersRepository_Tests.swift (1)

267-268: Remove unused local messageDTO

messageDTO is never referenced. Xcode will warn and some linters will fail CI.

-        let messageDTO = try XCTUnwrap(database.viewContext.message(id: messageId))

Simply delete the line.

Sources/StreamChat/Repositories/RemindersRepository.swift (1)

114-118: cid parameter is unused in updateReminder

cid is accepted but never referenced, which may confuse API users and invites drift between signature and implementation.
Either remove it or use it (e.g. for extra validation/logging).

-func updateReminder(
-    messageId: MessageId,
-    cid: ChannelId,
+func updateReminder(
+    messageId: MessageId,
     remindAt: Date?,
     completion: @escaping ((Result<MessageReminder, Error>) -> Void)
 )

(and update call-sites accordingly).

Sources/StreamChat/Database/DTOs/MessageReminderDTO.swift (2)

22-29: willSave() mutates every time even for unchanged objects

Because willSave is invoked on every save cycle for all registered objects, touching sortingRemindAt when the underlying value hasn’t changed still marks the DTO as dirty and forces Core Data to issue an UPDATE.
You already compute newSortingRemindAt; simply bail out early if nothing changes to avoid unnecessary SQL traffic:

-        let newSortingRemindAt = remindAt ?? .distantFuture.bridgeDate
-        if sortingRemindAt != newSortingRemindAt {
-            sortingRemindAt = newSortingRemindAt
-        }
+        let newSortingRemindAt = remindAt ?? .distantFuture.bridgeDate
+        guard sortingRemindAt != newSortingRemindAt else { return }
+        sortingRemindAt = newSortingRemindAt

146-153: No error propagation when the reminder is missing

deleteReminder(messageId:) just returns when no reminder is found, swallowing a potential inconsistency (e.g. local DB drift).
Consider surfacing an error or at least logging a warning so the caller can react.

-        guard let reminderDTO = message?.reminder else {
-            return
-        }
+        guard let reminderDTO = message?.reminder else {
+            log.warning("Attempted to delete a reminder that does not exist for messageId: \(messageId)")
+            return
+        }
Sources/StreamChat/Query/MessageReminderListQuery.swift (1)

97-104: Guard against invalid page sizes

The public initializer accepts any pageSize. Negative or zero values will hit the backend and probably fail. A small clamp prevents misuse:

-        pageSize: Int = 25,
+        pageSize: Int = 25,
let safeSize = max(1, pageSize)
pagination = Pagination(pageSize: safeSize, cursor: next)
Sources/StreamChat/Controllers/MessageReminderListController/MessageReminderListController.swift (1)

121-137: synchronize does not clear pagination state on subsequent calls

If a caller re-invokes synchronize() after loading more pages, nextCursor and hasLoadedAllReminders keep their old values, which can short-circuit further pagination.

Reset them before the network call:

+        nextCursor = nil
+        hasLoadedAllReminders = false
         remindersRepository.loadReminders(query: query) { [weak self] result in
DemoApp/StreamChat/Components/DemoChatMessageContentView.swift (1)

30-37: Consider localizing the hardcoded text string.

The label text "Saved for later" is hardcoded, which could cause localization issues in international deployments.

Apply this diff to use localized strings:

-        label.text = "Saved for later"
+        label.text = L10n.Message.savedForLater

Alternatively, if localization constants aren't available yet, consider adding a comment indicating this needs localization:

         label.text = "Saved for later"
+        // TODO: Localize this string
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between f2c94a3 and 4f1f54b.

📒 Files selected for processing (61)
  • CHANGELOG.md (1 hunks)
  • DemoApp/Screens/AppConfigViewController/AppConfigViewController.swift (6 hunks)
  • DemoApp/Screens/DemoAppTabBarController.swift (3 hunks)
  • DemoApp/Screens/DemoReminderListVC.swift (1 hunks)
  • DemoApp/Shared/DemoUsers.swift (1 hunks)
  • DemoApp/Shared/StreamChatWrapper.swift (1 hunks)
  • DemoApp/StreamChat/Components/DemoChatMessageActionsVC.swift (3 hunks)
  • DemoApp/StreamChat/Components/DemoChatMessageContentView.swift (2 hunks)
  • DemoApp/StreamChat/Components/DemoChatMessageLayoutOptionsResolver.swift (2 hunks)
  • DemoApp/StreamChat/DemoAppCoordinator+DemoApp.swift (1 hunks)
  • Sources/StreamChat/APIClient/Endpoints/EndpointPath+OfflineRequest.swift (1 hunks)
  • Sources/StreamChat/APIClient/Endpoints/EndpointPath.swift (3 hunks)
  • Sources/StreamChat/APIClient/Endpoints/Payloads/MessagePayloads.swift (5 hunks)
  • Sources/StreamChat/APIClient/Endpoints/Payloads/ReminderPayloads.swift (1 hunks)
  • Sources/StreamChat/APIClient/Endpoints/ReminderEndpoints.swift (1 hunks)
  • Sources/StreamChat/ChatClient+Environment.swift (1 hunks)
  • Sources/StreamChat/ChatClient.swift (1 hunks)
  • Sources/StreamChat/ChatClientFactory.swift (1 hunks)
  • Sources/StreamChat/Controllers/CurrentUserController/CurrentUserController.swift (3 hunks)
  • Sources/StreamChat/Controllers/MessageController/MessageController.swift (3 hunks)
  • Sources/StreamChat/Controllers/MessageReminderListController/MessageReminderListController+Combine.swift (1 hunks)
  • Sources/StreamChat/Controllers/MessageReminderListController/MessageReminderListController.swift (1 hunks)
  • Sources/StreamChat/Database/DTOs/MessageDTO.swift (3 hunks)
  • Sources/StreamChat/Database/DTOs/MessageReminderDTO.swift (1 hunks)
  • Sources/StreamChat/Database/DatabaseSession.swift (2 hunks)
  • Sources/StreamChat/Database/StreamChatModel.xcdatamodeld/StreamChatModel.xcdatamodel/contents (3 hunks)
  • Sources/StreamChat/Models/ChatMessage.swift (5 hunks)
  • Sources/StreamChat/Models/DraftMessage.swift (1 hunks)
  • Sources/StreamChat/Models/MessageReminder.swift (1 hunks)
  • Sources/StreamChat/Query/Filter+predicate.swift (2 hunks)
  • Sources/StreamChat/Query/Filter.swift (1 hunks)
  • Sources/StreamChat/Query/MessageReminderListQuery.swift (1 hunks)
  • Sources/StreamChat/Repositories/RemindersRepository.swift (1 hunks)
  • Sources/StreamChat/WebSocketClient/EventMiddlewares/ReminderUpdaterMiddleware.swift (1 hunks)
  • Sources/StreamChat/WebSocketClient/Events/EventPayload.swift (5 hunks)
  • Sources/StreamChat/WebSocketClient/Events/EventType.swift (2 hunks)
  • Sources/StreamChat/WebSocketClient/Events/MessageEvents.swift (1 hunks)
  • Sources/StreamChat/WebSocketClient/Events/ReminderEvents.swift (1 hunks)
  • StreamChat.xcodeproj/project.pbxproj (63 hunks)
  • TestTools/StreamChatTestTools/Extensions/Unique/ChatMessage+Unique.swift (1 hunks)
  • TestTools/StreamChatTestTools/Fixtures/JSONs/Events/Reminder/ReminderCreated.json (1 hunks)
  • TestTools/StreamChatTestTools/Fixtures/JSONs/Events/Reminder/ReminderDeleted.json (1 hunks)
  • TestTools/StreamChatTestTools/Fixtures/JSONs/Events/Reminder/ReminderDue.json (1 hunks)
  • TestTools/StreamChatTestTools/Fixtures/JSONs/Events/Reminder/ReminderUpdated.json (1 hunks)
  • TestTools/StreamChatTestTools/Fixtures/JSONs/ReminderPayload.json (1 hunks)
  • TestTools/StreamChatTestTools/Mocks/Models + Extensions/ChatMessage_Mock.swift (2 hunks)
  • TestTools/StreamChatTestTools/Mocks/Models + Extensions/MessageReminder_Mock.swift (1 hunks)
  • TestTools/StreamChatTestTools/Mocks/StreamChat/ChatClient_Mock.swift (3 hunks)
  • TestTools/StreamChatTestTools/Mocks/StreamChat/Database/DatabaseSession_Mock.swift (1 hunks)
  • TestTools/StreamChatTestTools/Mocks/StreamChat/Repositories/RemindersRepository_Mock.swift (1 hunks)
  • Tests/StreamChatTests/APIClient/Endpoints/EndpointPath_Tests.swift (2 hunks)
  • Tests/StreamChatTests/APIClient/Endpoints/Payloads/ReminderPayloads_Tests.swift (1 hunks)
  • Tests/StreamChatTests/APIClient/Endpoints/ReminderEndpoints_Tests.swift (1 hunks)
  • Tests/StreamChatTests/Controllers/MessageController/MessageController+Reminders_Tests.swift (1 hunks)
  • Tests/StreamChatTests/Controllers/MessageReminderListController/MessageReminderListController+Combine_Tests.swift (1 hunks)
  • Tests/StreamChatTests/Controllers/MessageReminderListController/MessageReminderListController_Tests.swift (1 hunks)
  • Tests/StreamChatTests/Database/DatabaseContainer_Tests.swift (1 hunks)
  • Tests/StreamChatTests/Query/MessageReminderListQuery_Tests.swift (1 hunks)
  • Tests/StreamChatTests/Repositories/RemindersRepository_Tests.swift (1 hunks)
  • Tests/StreamChatTests/WebSocketClient/EventMiddlewares/ReminderUpdaterMiddleware_Tests.swift (1 hunks)
  • Tests/StreamChatTests/WebSocketClient/Events/ReminderEvents_Tests.swift (1 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (8)
Tests/StreamChatTests/Database/DatabaseContainer_Tests.swift (2)
TestTools/StreamChatTestTools/Mocks/StreamChat/Database/DatabaseSession_Mock.swift (2)
  • saveReminder (65-67)
  • message (281-283)
TestTools/StreamChatTestTools/Extensions/Unique/URL+Unique.swift (1)
  • unique (9-11)
Sources/StreamChat/APIClient/Endpoints/EndpointPath.swift (3)
Sources/StreamChat/Controllers/ChannelController/ChannelController.swift (1)
  • messageId (1912-1914)
TestTools/StreamChatTestTools/Mocks/StreamChat/Database/DatabaseSession_Mock.swift (1)
  • pollVotes (597-599)
Sources/StreamChat/Database/DTOs/PollVoteDTO.swift (1)
  • pollVotes (214-219)
TestTools/StreamChatTestTools/Mocks/Models + Extensions/MessageReminder_Mock.swift (2)
Tests/StreamChatTests/Controllers/MessageReminderListController/MessageReminderListController_Tests.swift (1)
  • mock (261-277)
TestTools/StreamChatTestTools/Extensions/Unique/URL+Unique.swift (1)
  • unique (9-11)
Tests/StreamChatTests/Controllers/MessageController/MessageController+Reminders_Tests.swift (5)
TestTools/StreamChatTestTools/Mocks/StreamChat/ChatClient_Mock.swift (4)
  • mock (128-161)
  • mock (293-307)
  • mock (311-325)
  • cleanUp (101-116)
TestTools/StreamChatTestTools/Mocks/StreamChat/Repositories/RemindersRepository_Mock.swift (4)
  • cleanUp (44-65)
  • createReminder (80-94)
  • updateReminder (96-110)
  • deleteReminder (112-122)
TestTools/StreamChatTestTools/Mocks/Models + Extensions/ChatChannel_Mock.swift (1)
  • mockDMChannel (130-184)
Sources/StreamChat/Repositories/RemindersRepository.swift (3)
  • createReminder (63-105)
  • updateReminder (113-157)
  • deleteReminder (164-220)
Sources/StreamChat/Controllers/MessageController/MessageController.swift (3)
  • createReminder (945-958)
  • updateReminder (965-978)
  • deleteReminder (983-994)
Sources/StreamChat/Controllers/MessageReminderListController/MessageReminderListController+Combine.swift (3)
DemoApp/Screens/DemoReminderListVC.swift (1)
  • controller (471-478)
DemoApp/Screens/DemoAppTabBarController.swift (1)
  • controller (102-107)
Tests/StreamChatTests/Controllers/MessageReminderListController/MessageReminderListController_Tests.swift (1)
  • controller (174-181)
TestTools/StreamChatTestTools/Mocks/StreamChat/Repositories/RemindersRepository_Mock.swift (2)
Sources/StreamChat/Repositories/RemindersRepository.swift (4)
  • loadReminders (34-55)
  • createReminder (63-105)
  • updateReminder (113-157)
  • deleteReminder (164-220)
Sources/StreamChat/Controllers/MessageController/MessageController.swift (3)
  • createReminder (945-958)
  • updateReminder (965-978)
  • deleteReminder (983-994)
Tests/StreamChatTests/Repositories/RemindersRepository_Tests.swift (4)
Sources/StreamChat/Repositories/RemindersRepository.swift (4)
  • loadReminders (34-55)
  • createReminder (63-105)
  • updateReminder (113-157)
  • deleteReminder (164-220)
TestTools/StreamChatTestTools/SpyPattern/Spy/APIClient_Spy.swift (2)
  • test_simulateResponse (108-111)
  • test_mockResponseResult (118-120)
TestTools/StreamChatTestTools/Extensions/Unique/URL+Unique.swift (1)
  • unique (9-11)
TestTools/StreamChatTestTools/SpyPattern/Spy/DatabaseContainer_Spy.swift (1)
  • writeSynchronously (175-182)
DemoApp/StreamChat/Components/DemoChatMessageActionsVC.swift (1)
Sources/StreamChatUI/ChatMessageList/ChatMessageListVC.swift (1)
  • chatMessageActionsVCDidFinish (894-897)
🪛 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 (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 (125)
Sources/StreamChat/ChatClientFactory.swift (1)

129-129: LGTM! Proper integration of reminder middleware.

The addition of ReminderUpdaterMiddleware() correctly integrates the reminder event handling into the event processing pipeline. The positioning between DraftUpdaterMiddleware() and UserTypingStateUpdaterMiddleware() follows the established pattern for updater middlewares.

Sources/StreamChat/WebSocketClient/Events/MessageEvents.swift (1)

297-298: LGTM! Consistent model property initialization.

The addition of reminder: nil alongside the existing draftReply: nil correctly maintains consistency with the updated ChatMessage model structure. Setting reminder to nil is appropriate for this workaround method that handles deleted messages, as deleted messages wouldn't have active reminders.

TestTools/StreamChatTestTools/Extensions/Unique/ChatMessage+Unique.swift (1)

56-57: LGTM! Proper test utility alignment with model changes.

The addition of reminder: nil alongside draftReply: nil correctly updates the test utility to match the updated ChatMessage model structure. This ensures that test messages can properly represent the new reminder state while providing a clean baseline for testing scenarios.

Sources/StreamChat/APIClient/Endpoints/EndpointPath+OfflineRequest.swift (1)

18-18: LGTM! Appropriate offline queuing behavior for reminder endpoints.

The addition of .reminders, .reminder to the list of endpoints that should not be queued offline is correct. Reminder operations require real-time server interaction for proper scheduling and notification handling, making offline queuing inappropriate. This follows the established pattern for other endpoints requiring immediate server communication.

Tests/StreamChatTests/Database/DatabaseContainer_Tests.swift (1)

438-447: LGTM! Test data setup correctly extended for reminders feature.

The addition of reminder creation in the test data setup follows the established pattern and properly supports testing the new message reminders functionality. The payload structure with unique timestamps for remindAt, createdAt, and updatedAt creates realistic test scenarios.

DemoApp/Shared/StreamChatWrapper.swift (1)

41-44: LGTM! Clean implementation of custom base URL support.

The code properly uses optional chaining to safely extract and apply custom base URLs from DemoApiKeys. The placement in the initialization sequence is logical, and the implementation supports the Frankfurt C2 environment configuration mentioned in the PR objectives.

TestTools/StreamChatTestTools/Fixtures/JSONs/Events/Reminder/ReminderDue.json (1)

1-14: LGTM! Well-structured JSON fixture for reminder due events.

This fixture provides comprehensive test data for reminder due notifications with:

  • Proper ISO timestamp formatting
  • Realistic UUIDs and channel identifiers
  • Complete reminder object structure with all required fields
  • Consistent user IDs across the payload

The fixture correctly supports testing the new reminder due event handling functionality.

Sources/StreamChat/Query/Filter.swift (1)

430-439: LGTM! Excellent addition of nil filtering convenience method.

The isNil method is correctly implemented using the .exists operator with false value, which is the standard approach for checking non-existence. The implementation:

  • Follows the established pattern of other filter methods
  • Properly utilizes the key's valueMapper and keyPathString properties
  • Includes clear documentation
  • Provides a useful convenience for filtering nil values in the reminders feature
Sources/StreamChat/Models/DraftMessage.swift (1)

165-165: LGTM! Consistent with established pattern.

The explicit reset of the reminder property to nil when creating a ChatMessage from a DraftMessage is correct and follows the same pattern as other properties (poll, textUpdatedAt, draftReply). This ensures that draft messages don't carry reminder state when converted to regular messages.

TestTools/StreamChatTestTools/Mocks/Models + Extensions/ChatMessage_Mock.swift (2)

53-54: LGTM! Proper mock parameter addition.

The addition of the reminder parameter to the mock factory method follows the established pattern of other optional parameters with appropriate default values. This enables comprehensive testing of reminder functionality.


95-96: LGTM! Correct parameter forwarding.

The parameters are properly forwarded to the ChatMessage initializer, maintaining consistency with the mock method signature.

Sources/StreamChat/APIClient/Endpoints/EndpointPath.swift (3)

58-60: LGTM! Proper endpoint case addition.

The new reminder endpoint cases follow the established naming convention and pattern. The addition of both general reminders and message-specific reminder(MessageId) cases provides the necessary API endpoints for the reminder feature.


142-143: LGTM! Consistent URL path mapping.

The URL path mappings are consistent with existing patterns:

  • reminders"reminders/query" follows the query pattern
  • reminder(MessageId)"messages/\(messageId)/reminders" follows the message-specific resource pattern

161-161: LGTM! Minor reorganization.

The pollVotes case appears to have been reordered in the switch statement, which doesn't affect functionality and may improve code organization.

Sources/StreamChat/ChatClient+Environment.swift (1)

149-154: LGTM! Follows established dependency injection pattern.

The remindersRepositoryBuilder addition perfectly follows the established pattern of other repository builders in the environment:

  • Consistent parameter types (DatabaseContainer, APIClient)
  • Proper closure structure and initialization
  • Enables clean dependency injection for the reminder feature
TestTools/StreamChatTestTools/Fixtures/JSONs/Events/Reminder/ReminderUpdated.json (1)

1-15: Well-structured test fixture for reminder updated events.

The JSON structure is consistent with webhook event patterns and includes all necessary fields with appropriate data types. The test data is realistic and properly formatted.

Sources/StreamChat/Controllers/CurrentUserController/CurrentUserController.swift (4)

88-89: Good code organization with MARK comments.

Adding section markers improves code navigation and readability.


115-116: Proper section organization for initialization code.

The MARK comment clearly delineates the initialization section.


127-127: Essential super.init() call for proper inheritance.

The super.init() call is necessary to ensure proper superclass initialization in the inheritance hierarchy.


130-130: Minor formatting improvement in comment.

Using straight apostrophe improves comment consistency.

TestTools/StreamChatTestTools/Fixtures/JSONs/Events/Reminder/ReminderDeleted.json (1)

1-15: Consistent test fixture for reminder deleted events.

The structure matches the reminder updated fixture appropriately, with only the event type differing. This provides good test coverage for reminder deletion scenarios.

DemoApp/StreamChat/Components/DemoChatMessageLayoutOptionsResolver.swift (2)

11-11: New layout option properly defined.

The saveForLaterInfo option follows the same pattern as the existing pinInfo option.


32-35: Correct implementation of conditional layout option logic.

The logic appropriately checks both the feature flag (isRemindersEnabled) and message state (reminder != nil) before adding the layout option. This follows the same pattern as the existing pinInfo implementation and ensures proper feature gating.

Sources/StreamChat/ChatClient.swift (2)

81-84: LGTM: Consistent repository initialization pattern.

The change from non-lazy constant to lazy variable for draftMessagesRepository aligns with the environment-based dependency injection pattern used throughout the codebase. This maintains architectural consistency with other repository initializations.


86-89: LGTM: Proper integration of reminders repository.

The remindersRepository follows the established lazy initialization pattern using the environment builder. This integration is consistent with the existing repository architecture and supports the new message reminders feature effectively.

TestTools/StreamChatTestTools/Mocks/Models + Extensions/MessageReminder_Mock.swift (1)

8-26: LGTM: Well-structured mock implementation.

The mock factory method follows established patterns with appropriate default values. The use of .unique, .mock(), and date initialization is consistent with other mock implementations in the codebase.

TestTools/StreamChatTestTools/Mocks/StreamChat/Database/DatabaseSession_Mock.swift (1)

65-67: LGTM: Proper mock delegation for saveReminder.

The saveReminder method correctly delegates to the underlying session with all required parameters and proper return type handling.

Tests/StreamChatTests/APIClient/Endpoints/EndpointPath_Tests.swift (2)

69-72: LGTM: Comprehensive test coverage for reminder endpoints.

The test properly verifies that both reminder endpoint cases correctly return false for shouldBeQueuedOffline, which aligns with the expectation that reminder operations should not be queued for offline execution.


154-156: LGTM: Proper encoding/decoding test coverage.

The additions to the Codable test ensure that the new reminder endpoints are properly tested for encoding and decoding functionality, maintaining consistency with other endpoint tests.

DemoApp/Shared/DemoUsers.swift (2)

260-260: LGTM! Consistent with existing pattern.

The new API key case follows the same pattern as existing entries and includes a helpful comment identifying it as Frankfurt C2 Staging.


266-266: LGTM! Consistent app name mapping.

The app name mapping for the new Frankfurt C2 case is consistent with the existing pattern and provides a clear, descriptive name.

CHANGELOG.md (2)

10-10: Documenting new filter
The new Filter.isNil entry clearly conveys its purpose. Please ensure that the corresponding API reference and usage examples are updated to include this filter.


11-11: Introduce Message Reminders feature
The “Add Message Reminders” entry succinctly summarizes the new capability. Good call on linking to the PR for context.

DemoApp/StreamChat/DemoAppCoordinator+DemoApp.swift (1)

61-77: LGTM! Consistent integration pattern.

The reminder list VC integration follows the exact same pattern as the existing thread and draft controllers, maintaining consistency in the codebase. The logout/disconnect closure setup and navigation controller wrapping are properly implemented.

Sources/StreamChat/APIClient/Endpoints/Payloads/MessagePayloads.swift (4)

57-57: LGTM! Proper coding key addition.

The reminder coding key is correctly added to the MessagePayloadsCodingKeys enum following the established pattern.


116-116: LGTM! Consistent property declaration.

The reminder property follows the same optional pattern as other similar properties like draft and poll.


183-183: LGTM! Proper conditional decoding.

The reminder decoding follows the established pattern for optional properties using decodeIfPresent.


224-225: LGTM! Complete initializer integration.

The reminder parameter and property assignment are properly integrated into the convenience initializer, maintaining consistency with other optional properties.

Also applies to: 265-265

Tests/StreamChatTests/Query/MessageReminderListQuery_Tests.swift (4)

10-19: LGTM! Comprehensive default initialization test.

The test properly verifies all default values including filter (nil), pagination (pageSize: 25, offset: 0), and default sorting (remindAt, ascending).


21-36: LGTM! Thorough custom initialization test.

The test validates custom filter, sort, and pageSize parameters are properly applied and accessible through the query object.


38-97: LGTM! Comprehensive encoding test coverage.

The encoding tests cover all important scenarios: with all fields, without filter, and without sort. The use of AssertJSONEqual is appropriate for JSON comparison testing.


99-111: LGTM! Important key validation tests.

Testing the raw values of filter and sorting keys ensures the API contract is maintained and prevents accidental changes to the string values used in network requests.

Sources/StreamChat/Database/DatabaseSession.swift (2)

545-560: LGTM! Well-designed protocol following established patterns.

The ReminderDatabaseSession protocol follows the same design patterns as other database session protocols with proper documentation, parameter types, error handling, and return value annotations.


689-689: LGTM! Proper protocol composition.

The ReminderDatabaseSession is correctly integrated into the main DatabaseSession protocol following the established pattern used by other specialized database session protocols.

Sources/StreamChat/WebSocketClient/EventMiddlewares/ReminderUpdaterMiddleware.swift (1)

7-42: LGTM! Well-structured event middleware implementation.

The middleware correctly follows the established pattern for EventMiddleware implementations. The switch statement appropriately handles all reminder-related events, includes proper error handling with logging, and maintains consistency with the existing codebase architecture.

Sources/StreamChat/Database/DTOs/MessageDTO.swift (3)

91-91: LGTM! Proper Core Data property declaration.

The optional relationship to MessageReminderDTO is correctly declared following Core Data conventions.


1045-1050: LGTM! Robust reminder handling logic.

The implementation correctly handles both reminder creation and deletion scenarios:

  • Creates/updates reminder when present in payload
  • Cleans up existing reminder when not present in payload
  • Includes proper error handling with try-catch

1798-1803: LGTM! Proper domain model mapping.

The reminder mapping correctly transforms the Core Data DTO to the domain model, appropriately handling optional dates with bridgeDate conversion and using optional mapping with .map.

Sources/StreamChat/Database/StreamChatModel.xcdatamodeld/StreamChatModel.xcdatamodel/contents (3)

78-78: LGTM! Properly defined channel-to-reminders relationship.

The to-many relationship from ChannelDTO to MessageReminderDTO with Nullify deletion rule is correctly implemented, ensuring reminders are not automatically deleted when a channel is removed.


265-265: LGTM! Properly defined message-to-reminder relationship.

The to-one relationship from MessageDTO to MessageReminderDTO with Nullify deletion rule is correctly implemented, ensuring the reminder is not automatically deleted when a message is removed.


324-335: Well-designed MessageReminderDTO entity with proper optimization.

The entity definition includes:

  • Appropriate attributes for reminder functionality (createdAt, remindAt, sortingRemindAt)
  • Proper relationships to both channel and message entities
  • Performance optimization with fetch index on id attribute

The sortingRemindAt attribute suggests good consideration for query performance in reminder lists.

TestTools/StreamChatTestTools/Fixtures/JSONs/ReminderPayload.json (1)

1-113: Comprehensive and well-structured test fixture.

The reminder payload fixture includes:

  • Complete channel configuration with user_message_reminders: true enabled
  • Full message payload with user details
  • Proper timestamp formatting and realistic test data
  • remind_at: null which correctly represents "save for later" functionality

This fixture will effectively support testing of reminder payload decoding and processing.

TestTools/StreamChatTestTools/Fixtures/JSONs/Events/Reminder/ReminderCreated.json (1)

1-105: Well-structured reminder created event fixture.

The event fixture correctly represents a reminder.created event with:

  • Proper event metadata (created_at, message_id, type, user_id)
  • Complete nested reminder payload with channel, message, and user details
  • Consistent structure matching the EventPayload expectations
  • remind_at: null appropriately representing save-for-later reminders

This fixture will effectively test the reminder event handling pipeline.

Sources/StreamChat/WebSocketClient/Events/EventPayload.swift (5)

43-43: Consistent addition to CodingKeys enum.

The reminder case is properly added to the CodingKeys enum, following the established pattern for other payload fields.


81-81: Properly declared reminder property.

The reminder: ReminderPayload? property is correctly typed as optional, allowing events to include or exclude reminder data as needed.


114-115: Initializer properly updated with reminder parameter.

The initializer signature correctly includes the reminder parameter with a default value of nil, maintaining backward compatibility while supporting reminder events.


148-148: Proper assignment in initializer.

The reminder property is correctly assigned in the initializer, following the established pattern for other properties.


186-186: Correct reminder decoding implementation.

The reminder field is properly decoded using decodeIfPresent, which appropriately handles cases where reminder data may or may not be present in the event payload.

DemoApp/Screens/AppConfigViewController/AppConfigViewController.swift (3)

25-26: LGTM! Reminder configuration property added correctly.

The new isRemindersEnabled property follows the same pattern as other feature flags in the configuration.


350-353: LGTM! UI configuration handling is consistent.

The switch button configuration for reminders follows the same pattern as other feature toggles.


751-753: LGTM! Base URL update logic is correct.

The logic properly updates the chat client config base URL when an environment with a custom base URL is selected.

Sources/StreamChat/Models/ChatMessage.swift (3)

70-71: LGTM! Reminder property added correctly.

The optional MessageReminderInfo? property is properly documented and follows the existing code patterns.


223-224: LGTM! Initializer parameter added correctly.

The reminder parameter is properly added to the initializer signature.


568-568: LGTM! Equality comparison updated correctly.

The reminder property is properly included in the equality check, ensuring message equality accounts for reminder state.

TestTools/StreamChatTestTools/Mocks/StreamChat/ChatClient_Mock.swift (2)

155-155: LGTM! Mock reminders repository integration is correct.

The remindersRepositoryBuilder is properly added to the mock environment, following the same pattern as other repository builders.


213-215: LGTM! Mock repository accessor follows established pattern.

The computed property provides typed access to the mock reminders repository, consistent with other repository accessors in the extension.

Tests/StreamChatTests/APIClient/Endpoints/ReminderEndpoints_Tests.swift (4)

9-28: LGTM! Create reminder endpoint test is comprehensive.

The test properly validates the endpoint construction for creating reminders, including HTTP method, path, body, and URL structure.


30-48: LGTM! Update reminder endpoint test follows consistent pattern.

The test correctly validates the PATCH method and endpoint structure for updating reminders.


50-66: LGTM! Delete reminder endpoint test is well-structured.

The test properly validates the DELETE method with no body for reminder deletion.


68-88: LGTM! Query reminders endpoint test covers complex query structure.

The test validates the POST method for querying reminders with filter, sort, and pagination parameters, providing comprehensive coverage.

Sources/StreamChat/APIClient/Endpoints/ReminderEndpoints.swift (2)

42-50: Consider the HTTP method choice for queryReminders.

Using POST for a query operation is unusual - typically GET would be used. However, this might be intentional if the query body is complex or contains sensitive data that shouldn't be in URL parameters.

Could you verify if POST is the intended HTTP method for the reminders query endpoint based on the API specification? If the query parameters are simple, consider using GET with query parameters instead.


7-51: Well-structured endpoint definitions.

The endpoint implementations follow good practices with proper HTTP methods, consistent structure, and appropriate response types. The use of requiresConnectionId: false is correct for REST endpoints.

Sources/StreamChat/APIClient/Endpoints/Payloads/ReminderPayloads.swift (2)

47-59: Clean and focused request body design.

The ReminderRequestBody is well-designed with a single responsibility and proper encoding setup.


62-79: Good pagination and response wrapper design.

Both RemindersQueryPayload and ReminderResponsePayload follow good API design patterns with proper pagination support and consistent response wrapping.

Sources/StreamChat/Models/MessageReminder.swift (2)

8-53: Excellent domain model design.

The MessageReminder struct is well-designed with:

  • Clear separation of concerns using internal initializer for encapsulation
  • Proper Hashable implementation using only the unique id
  • Good documentation explaining the purpose of each property
  • Appropriate use of optional remindAt for bookmarks vs timed reminders

55-75: Smart design to avoid circular dependencies.

The MessageReminderInfo struct is a clever solution to provide reminder metadata without creating circular references between ChatMessage and MessageReminder. This design pattern enables clean embedding of reminder data in message models.

Sources/StreamChat/Query/Filter+predicate.swift (2)

7-13: Good refactoring to enable broader filter usage.

Moving the filter predicate functionality from a channel-specific extension to a general Filter extension is a smart refactoring that enables reuse for the new reminder filtering system while maintaining the same logic.


207-226: Properly generalized utility properties.

The mappedValue and mappedArrayValue properties have been correctly moved to the general extension, maintaining their functionality while making them available for other filter scopes like reminders.

Sources/StreamChat/Controllers/MessageController/MessageController.swift (3)

193-194: LGTM! Clean property addition.

The remindersRepository property follows the established pattern for repository dependencies in this controller.


216-216: LGTM! Proper dependency injection.

The reminders repository is correctly initialized from the client, maintaining consistency with other repository initializations in the constructor.


938-994: LGTM! Well-structured reminder API methods.

The three reminder methods (createReminder, updateReminder, deleteReminder) follow excellent patterns:

  • Consistent delegation to the remindersRepository
  • Proper callback queue handling using self.callback
  • Clear method signatures with appropriate parameter types
  • Good documentation explaining the nil remindAt behavior for "save for later" functionality
  • Error handling through completion blocks

The implementation is consistent with other controller methods in the codebase.

Tests/StreamChatTests/Controllers/MessageController/MessageController+Reminders_Tests.swift (4)

9-37: LGTM! Well-structured test class setup.

The test class follows excellent testing practices:

  • Proper mock dependencies setup
  • Clean teardown with cleanUp() calls
  • Appropriate test isolation with unique IDs
  • Uses the established mocking infrastructure

41-98: LGTM! Comprehensive create reminder tests.

The test coverage for createReminder is thorough:

  • Tests both success and failure scenarios
  • Verifies repository method calls with correct parameters
  • Uses proper async testing with expectations
  • Validates both the result type and error propagation
  • Tests both with and without remindAt parameter (lines 61, 86)

102-159: LGTM! Complete update reminder test coverage.

The updateReminder tests mirror the create tests appropriately:

  • Success scenario validates correct parameter passing and result handling
  • Failure scenario ensures proper error propagation
  • Consistent testing pattern with the create tests

163-206: LGTM! Thorough delete reminder test coverage.

The delete reminder tests are well-implemented:

  • Success test verifies nil error return and correct repository method calls
  • Failure test ensures error propagation works correctly
  • Uses the simpler completion signature (Error? vs Result<T, Error>) consistently with the actual API
Tests/StreamChatTests/Controllers/MessageReminderListController/MessageReminderListController+Combine_Tests.swift (3)

11-32: LGTM! Proper Combine test setup.

The test class setup is excellent:

  • Inherits from iOS13TestCase for Combine availability
  • Proper initialization with empty query
  • Clean cancellables management
  • Uses AssertAsync.canBeReleased to verify controller lifecycle

34-51: LGTM! Comprehensive state publisher testing.

The state publisher test demonstrates good testing practices:

  • Uses Record.Recording for capturing publisher output
  • Tests weak reference behavior to ensure publisher keeps controller alive
  • Simulates delegate callback to trigger state change
  • Uses AssertAsync.willBeEqual for async assertion
  • Verifies the expected state sequence: .localDataFetched.remoteDataFetched

53-81: LGTM! Thorough reminders changes publisher testing.

The reminders changes publisher test is well-implemented:

  • Creates a realistic MessageReminder instance with proper mock data
  • Tests ListChange emission correctly
  • Uses the same weak reference pattern as the state test
  • Verifies exact change propagation from delegate to publisher
Sources/StreamChat/WebSocketClient/Events/EventType.swift (2)

158-178: LGTM! Well-organized reminder event types.

The new reminder event types are excellently implemented:

  • Proper organization with // MARK: - Reminders section
  • Consistent naming following the messageReminder* pattern
  • Includes all CRUD operations plus due notifications
  • Event type values follow the established conventions
  • Good documentation comments explaining each event's purpose

249-252: LGTM! Consistent event decoding implementation.

The event decoding logic properly handles all four reminder event types:

  • Maps to the corresponding DTO classes (ReminderCreatedEventDTO, etc.)
  • Follows the established pattern of other event decoders
  • Maintains the same error handling approach
  • Properly positioned in the switch statement
DemoApp/StreamChat/Components/DemoChatMessageContentView.swift (5)

12-37: Well-implemented UI components for "Save for Later" feature.

The implementation follows good practices:

  • Proper use of lazy properties for UI components
  • Consistent styling using the appearance system
  • Clear component hierarchy with HContainer
  • Appropriate sizing constraints

50-57: Conditional layout logic is correctly implemented.

The layout logic properly:

  • Checks for the .saveForLaterInfo option
  • Sets appropriate background color using the appearance system
  • Adds the view to the correct container with proper constraints
  • Uses consistent spacing with the pin info implementation

12-20: Well-structured lazy view initialization with proper constraints.

The saveForLaterView is properly implemented using a horizontal container with appropriate spacing and sizing constraints. The lazy initialization pattern is correct for UI components.


22-28: Icon configuration looks good with proper system image usage.

The bookmark icon with system name "bookmark.fill" is appropriate for the "save for later" functionality. The tint color correctly uses the accent primary color from the appearance palette.


50-57:

❓ Verification inconclusive

Verify the background color and constraint handling.

The implementation looks correct, but ensure that the highlightedAccentBackground1 color provides sufficient contrast for accessibility and that the constraint setup doesn't conflict with existing layout constraints.

Run the following script to check for potential constraint conflicts:


🏁 Script executed:

#!/bin/bash
# Description: Check for other views that might use bubbleThreadFootnoteContainer to identify potential constraint conflicts

rg -A 5 "bubbleThreadFootnoteContainer" --type swift

Length of output: 11640


Constraint handling verified; please confirm color contrast

  • A search for other bubbleThreadFootnoteContainer usages shows no conflicting constraint setups.
  • Ensure that appearance.colorPalette.highlightedAccentBackground1 meets WCAG contrast requirements (≥4.5:1) against any overlaid text or icons in both light and dark modes.
Tests/StreamChatTests/APIClient/Endpoints/Payloads/ReminderPayloads_Tests.swift (3)

9-36: Comprehensive test coverage for ReminderPayload.

The test properly validates:

  • Basic reminder properties (channelCid, messageId, dates)
  • Embedded message object with all required fields
  • Channel properties from the updated JSON structure
  • Proper handling of nil values for optional fields

38-64: Well-structured ReminderResponsePayload test.

The test correctly:

  • Uses inline JSON for clear test data visibility
  • Validates the wrapper structure with duration field
  • Tests all reminder properties within the response
  • Follows consistent assertion patterns

66-112: Thorough RemindersQueryPayload test coverage.

The test effectively validates:

  • Multiple reminders in the response array
  • Pagination token handling
  • Different reminder configurations (with/without remindAt)
  • Array indexing and property verification
Tests/StreamChatTests/WebSocketClient/EventMiddlewares/ReminderUpdaterMiddleware_Tests.swift (5)

9-24: Proper test setup and teardown implementation.

The test class correctly:

  • Uses in-memory database for isolated testing
  • Initializes middleware properly
  • Follows proper teardown pattern to prevent test leakage

25-65: Comprehensive test for reminder creation event.

The test effectively:

  • Sets up required channel and message data before testing
  • Creates realistic event payload and DTO
  • Verifies reminder is properly saved to database
  • Checks reminder ID matches message ID correctly

67-120: Thorough test for reminder update functionality.

The test properly:

  • Creates initial reminder state
  • Updates with new reminder data
  • Verifies the reminder is updated (not recreated)
  • Uses XCTAssertNearlySameDate for appropriate date comparison

122-165: Appropriate test for due notification event.

The test correctly:

  • Sets up existing reminder state
  • Processes due notification event
  • Verifies reminder still exists after notification (not deleted)

167-212: Complete test for reminder deletion event.

The test effectively:

  • Creates reminder state
  • Verifies existence before deletion
  • Processes delete event
  • Confirms reminder is properly removed from database
Sources/StreamChat/Controllers/MessageReminderListController/MessageReminderListController+Combine.swift (3)

8-17: Well-implemented Combine publishers for MessageReminderListController.

The publishers correctly:

  • Use appropriate Combine types (CurrentValueSubject for state, PassthroughSubject for changes)
  • Implement proper keepAlive mechanism for lifecycle management
  • Follow established patterns from other controller extensions
  • Provide clean public API surface

19-34: Proper BasePublishers implementation following established patterns.

The BasePublishers class correctly:

  • Initializes subjects with appropriate initial values
  • Registers itself as a delegate to receive updates
  • Uses multicastDelegate for proper delegation
  • Follows the same pattern as other controller Combine extensions

36-47: Correct delegate implementation forwarding events to publishers.

The delegate methods properly:

  • Forward state changes to the state subject
  • Forward reminders changes to the remindersChanges subject
  • Implement the correct delegate protocol methods
  • Follow established patterns for event forwarding
TestTools/StreamChatTestTools/Mocks/StreamChat/Repositories/RemindersRepository_Mock.swift (2)

10-32: Well-structured mock implementation with comprehensive parameter tracking.

The mock correctly captures all method parameters and provides preset result mechanisms for controlled testing scenarios. This enables thorough testing of repository interactions.


44-65: Good practice: cleanUp method for test isolation.

The cleanUp method properly resets all tracked values, ensuring test isolation and preventing state leakage between tests.

Tests/StreamChatTests/Controllers/MessageReminderListController/MessageReminderListController_Tests.swift (3)

29-45: Excellent test coverage for synchronization scenarios.

The test properly verifies both the repository call and the controller state changes, ensuring complete integration testing.


133-162: Good pagination cursor testing.

This test effectively verifies that the controller correctly uses pagination cursors for subsequent requests, which is crucial for proper pagination behavior.


164-232: Comprehensive delegate integration test.

This test creates a realistic scenario with database persistence and verifies the delegate callback mechanism works correctly. The use of expectations and proper setup/teardown is exemplary.

Tests/StreamChatTests/WebSocketClient/Events/ReminderEvents_Tests.swift (3)

27-36: Thorough event decoding verification.

The test properly validates all key fields from the JSON fixture, ensuring accurate parsing of reminder events.


55-62: Good negative test case coverage.

Testing the scenario where required data is missing ensures robust error handling in the domain event conversion.


28-28:

✅ Verification successful

Verify JSON fixture files exist and contain valid data.

Ensure that the JSON fixture files referenced in these tests contain realistic and up-to-date event payloads.


🏁 Script executed:

#!/bin/bash
# Description: Verify that all reminder event JSON fixtures exist and are valid JSON

echo "Checking for reminder event JSON fixtures..."
fd -e json -p "TestTools/StreamChatTestTools/Fixtures/JSONs/Events/Reminder" | while read -r file; do
    echo "Found fixture: $file"
    if ! jq . "$file" > /dev/null 2>&1; then
        echo "ERROR: Invalid JSON in $file"
        exit 1
    fi
done

echo "Verifying specific fixtures referenced in tests..."
for fixture in "ReminderCreated" "ReminderUpdated" "ReminderDeleted" "ReminderDue"; do
    fixture_path=$(fd "${fixture}.json" TestTools/StreamChatTestTools/Fixtures/JSONs/Events/Reminder/ | head -1)
    if [ -z "$fixture_path" ]; then
        echo "ERROR: Missing fixture for $fixture"
    else
        echo "✓ Found fixture: $fixture_path"
        # Validate JSON structure contains expected fields
        jq -e '.message_id, .created_at, .reminder' "$fixture_path" > /dev/null 2>&1 || echo "WARNING: Missing expected fields in $fixture_path"
    fi
done

Length of output: 4341


All reminder event JSON fixtures are present and valid

All referenced fixtures (ReminderCreated.json, ReminderUpdated.json, ReminderDeleted.json, ReminderDue.json) were found under TestTools/StreamChatTestTools/Fixtures/JSONs/Events/Reminder/, are valid JSON, and include the expected .message_id, .created_at, and .reminder fields. No further changes needed.

DemoApp/Screens/DemoAppTabBarController.swift (9)

9-9: Good protocol conformance for reminder functionality.

Adding MessageReminderListControllerDelegate enables proper badge updates when reminders change.


88-92: Excellent feature flag implementation.

Conditionally including the reminders tab based on configuration allows for flexible feature rollout and testing.


9-9: Good delegation pattern implementation.

The class properly conforms to MessageReminderListControllerDelegate to handle reminder updates.


13-15: Properties are well-named and appropriately scoped.

The new properties reminderListVC and allRemindersListController follow consistent naming conventions with existing properties.


20-35: Constructor properly handles new dependencies.

The initialization correctly includes the new reminder-related parameters and assigns them to instance properties. The pattern is consistent with existing parameters.


64-68: Good feature flag implementation for reminders.

The conditional logic properly checks the feature flag before setting up reminders functionality, which is a good practice for feature rollouts.


84-92: Tab configuration follows consistent patterns.

The reminders tab setup follows the same pattern as other tabs, and the conditional inclusion based on the feature flag is well-implemented.


102-107: Delegate method implementation is correct.

The delegate method properly calls the badge update function when reminders change, maintaining UI consistency.


109-112: Badge update logic is simple and effective.

The badge shows the count when reminders exist and hides when empty, which provides clear visual feedback to users.

Sources/StreamChat/Database/DTOs/MessageReminderDTO.swift (1)

135-138: Reminder id is set to messageId – verify backend contract

payload.messageId is used for both the foreign-key relation and the reminder’s primary id:

reminderDTO.id = payload.messageId

If the backend delivers a dedicated payload.id (common for 1-N message-to-reminder relations), this will silently conflate two distinct identifiers and break deduplication & updates.
Please confirm the spec and, if needed, assign id = payload.id.

Sources/StreamChat/Controllers/MessageReminderListController/MessageReminderListController.swift (1)

168-180: Multiple concurrent loadMoreReminders calls can interleave

Because nextCursor is updated inside the completion block, two quick successive calls will issue the same cursor and duplicate items/requests.

Add a simple guard or serialize calls (e.g. store an isLoadingMore flag).

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 3, 2025

Quality Gate Failed Quality Gate failed

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

See analysis details on SonarQube Cloud

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 🧪 QAing 🌐 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