Skip to content
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

Add Message Reminders #3623

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

Add Message Reminders #3623

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

New APIs:

  • Adds new Filter.isNil to make it easier to filter nil values
  • Adds ChatMessageController.createReminder()
  • Adds ChatMessageController.updateReminder()
  • Adds ChatMessageController.deleteReminder()
  • Adds ChatCurrentUserController.loadReminders() + loadMoreReminders()

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.

🛠 Implementation

TODO

🎨 Showcase

TODO

🧪 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"

TODO

☑️ 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

Sorry, something went wrong.

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

2 Warnings
⚠️ Please be sure to complete the Contributor Checklist in the Pull Request description
⚠️ Big PR
1 Message
📖 There seems to be app changes but CHANGELOG wasn't modified.
Please include an entry if the PR includes user-facing changes.
You can find it at CHANGELOG.md.

Generated by 🚫 Danger

Comment on lines +113 to +118
func eventsController(_ controller: EventsController, didReceiveEvent event: Event) {
if AppConfig.shared.demoAppConfig.isRemindersEnabled,
let reminderDueEvent = event as? ReminderDueEvent {
handleReminderDueEvent(reminderDueEvent)
}
}
Copy link
Member Author

Choose a reason for hiding this comment

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

I'm not sure if this is going to stay the same since the goal is for the backend to send a remote push notification.

Copy link
Contributor

Choose a reason for hiding this comment

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

this doesn't feel complete without PN. Atm, we listen to WS event, and send local notification. But what if there's no active WS, do you know when push notifications would be supported?

Copy link
Member Author

Choose a reason for hiding this comment

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

Yes the Push needs to come from the server, I will delete this one that is done 👍

Copy link
Contributor

Choose a reason for hiding this comment

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

do you know when that would be ready?

@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 🟢

var onLogout: (() -> Void)?
var onDisconnect: (() -> Void)?

private let currentUserController: CurrentChatUserController
Copy link
Member Author

@nuno-vieira nuno-vieira Mar 21, 2025

Choose a reason for hiding this comment

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

At the moment, one controller is managing the different types of reminders. Although this works fine, we could also use a controller per reminder type. This way we would avoid having to many loadReminders requests. Since right now, whenever we switch the filter, we re-refetch the reminders. So yeah I might optimize this. WDYT?

Although this means we will have 4/5 currentUserControllers. Which is not very intuitive though 🤔 Maybe for the reminders feature it could be worth having a MessageReminderListController instead of using the current user controller which will observer unread counts etc..

Copy link
Contributor

Choose a reason for hiding this comment

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

I think it's fine to use one for the demo app - it shows how to use the API. It's up to customers to further optimize it, some might have different / less filters.

Copy link
Member Author

Choose a reason for hiding this comment

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

The thing is that we should show them how to optimize their integration code. This means less calls, and less stress to our servers 🤔

Copy link
Contributor

Choose a reason for hiding this comment

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

I wrote a longer reply to our Slack thread with a summary of supporting the idea of MessageReminderListController (mainly because reminders query has a filter)

Copy link
Contributor

Choose a reason for hiding this comment

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

ok, that's also fine with me 👍

@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.41 MB 7.77 MB +364 KB 🟡
StreamChatUI 4.79 MB 4.84 MB +49 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.

@@ -50,7 +52,8 @@ class AppConfig {
isLocationAttachmentsEnabled: false,
tokenRefreshDetails: nil,
shouldShowConnectionBanner: false,
isPremiumMemberFeatureEnabled: false
isPremiumMemberFeatureEnabled: false,
isRemindersEnabled: false
Copy link
Contributor

Choose a reason for hiding this comment

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

We should have it on by default

Copy link
Member Author

Choose a reason for hiding this comment

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

They are disabled, and enabled by default using our Developers Scheme. Since the design is not approved by our Designers, it is probably not a good idea to make it enabled by default 🤔 Although that applies to drafts as well

Copy link
Contributor

Choose a reason for hiding this comment

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

I would enable it for the default scheme as well. It helps with discoverability, and, let's be honest, you did a great job in the design part!

Copy link
Member Author

Choose a reason for hiding this comment

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

Ahah thanks, okay I will enable it by default 👍

Comment on lines +113 to +118
func eventsController(_ controller: EventsController, didReceiveEvent event: Event) {
if AppConfig.shared.demoAppConfig.isRemindersEnabled,
let reminderDueEvent = event as? ReminderDueEvent {
handleReminderDueEvent(reminderDueEvent)
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

this doesn't feel complete without PN. Atm, we listen to WS event, and send local notification. But what if there's no active WS, do you know when push notifications would be supported?

var onLogout: (() -> Void)?
var onDisconnect: (() -> Void)?

private let currentUserController: CurrentChatUserController
Copy link
Contributor

Choose a reason for hiding this comment

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

I think it's fine to use one for the demo app - it shows how to use the API. It's up to customers to further optimize it, some might have different / less filters.

path: .reminders,
method: .post,
queryItems: nil,
requiresConnectionId: false,
Copy link
Contributor

Choose a reason for hiding this comment

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

how do we receive reminder events if we don't require connection id?

Copy link
Member Author

Choose a reason for hiding this comment

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

Yeah this might need to be true, will need to recheck

Copy link
Member Author

@nuno-vieira nuno-vieira Mar 24, 2025

Choose a reason for hiding this comment

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

Although, I think reminders will be receive events even if you don't query them. Since you might not have a reminders list. But still want to get reminded of your reminders. So this is not needed AFAIK

Copy link
Contributor

Choose a reason for hiding this comment

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

ok, just checking - if it works without the connection id we can resolve this one.

@@ -382,3 +386,68 @@ public struct Command: Codable, Hashable {
self.args = args
}
}

/// An object describing a reminder JSON payload.
struct ReminderPayload: Decodable {
Copy link
Contributor

Choose a reason for hiding this comment

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

As mentioned on Slack, let's try using class here.

static var createdAt: FilterKey<Scope, Date> { .init(rawValue: "created_at", keyPathString: #keyPath(MessageReminderDTO.createdAt)) }
}

public extension Filter where Scope: AnyMessageReminderListFilterScope {
Copy link
Contributor

Choose a reason for hiding this comment

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

how did you come up with these filters?

Copy link
Member Author

Choose a reason for hiding this comment

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

It is in the Notion's Backend Specs

Copy link
Member Author

Choose a reason for hiding this comment

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

Ah, or do you mean the helpers? If you mean the helpers, it is because it will be the most common filters, so I think it should be useful for customers

@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

Quality Gate Failed Quality Gate failed

Failed conditions
4.5% 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
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants