-
Notifications
You must be signed in to change notification settings - Fork 212
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 support for transforming Messages and Channels #3564
base: develop
Are you sure you want to change the base?
Changes from all commits
f8d62d2
206c580
c2307b6
0193707
bb83c9c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,79 @@ | ||
// | ||
// Copyright © 2025 Stream.io Inc. All rights reserved. | ||
// | ||
|
||
import Foundation | ||
|
||
/// An object that provides a way to transform Stream Chat models. | ||
/// | ||
/// Only some data can be changed. The method `replacing()` is used to create a new object from existing data. | ||
/// All transform functions have default implementation, so you can override only the ones you need. | ||
/// | ||
/// - Note: When using `replacing()` method, you need to provide all the properties of the existing object. | ||
/// Or you can pass `nil` and that will erase the existing value. | ||
/// | ||
nuno-vieira marked this conversation as resolved.
Show resolved
Hide resolved
|
||
/// - Important: Transform methods can be called often and therefore, it must be performant. | ||
/// | ||
/// Example: | ||
/// ``` | ||
/// class CustomStreamModelsTransformer: StreamModelsTransformer { | ||
/// func transform(channel: ChatChannel) -> ChatChannel { | ||
/// channel.replacing( | ||
/// name: "Hey!", | ||
/// imageURL: channel.imageURL, | ||
/// extraData: channel.extraData | ||
/// ) | ||
/// } | ||
/// ``` | ||
public protocol StreamModelsTransformer { | ||
/// Transforms the given `ChatChannel` object. | ||
func transform(channel: ChatChannel) -> ChatChannel | ||
/// Transforms the given `ChatMessage` object. | ||
func transform(message: ChatMessage) -> ChatMessage | ||
/// Transforms the given `NewMessageTransformableInfo` object when creating a new message. | ||
func transform(newMessageInfo: NewMessageTransformableInfo) -> NewMessageTransformableInfo | ||
} | ||
|
||
/// Default implementations. | ||
extension StreamModelsTransformer { | ||
func transform(channel: ChatChannel) -> ChatChannel { | ||
channel | ||
} | ||
|
||
func transform(message: ChatMessage) -> ChatMessage { | ||
message | ||
} | ||
|
||
func transform(newMessageInfo: NewMessageTransformableInfo) -> NewMessageTransformableInfo { | ||
newMessageInfo | ||
} | ||
} | ||
|
||
/// The information that can be transformed when creating a new message. | ||
public struct NewMessageTransformableInfo { | ||
public var text: String | ||
public var attachments: [AnyAttachmentPayload] | ||
public var extraData: [String: RawJSON] | ||
|
||
init( | ||
text: String, | ||
attachments: [AnyAttachmentPayload], | ||
extraData: [String: RawJSON] | ||
) { | ||
self.text = text | ||
self.attachments = attachments | ||
self.extraData = extraData | ||
} | ||
|
||
public func replacing( | ||
text: String, | ||
attachments: [AnyAttachmentPayload], | ||
extraData: [String: RawJSON] | ||
) -> NewMessageTransformableInfo { | ||
.init( | ||
text: text, | ||
attachments: attachments, | ||
extraData: extraData | ||
) | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -763,17 +763,26 @@ public class ChatChannelController: DataController, DelegateCallable, DataStoreP | |
extraData: [String: RawJSON] = [:], | ||
completion: ((Result<MessageId, Error>) -> Void)? = nil | ||
) { | ||
var transformableInfo = NewMessageTransformableInfo( | ||
text: text, | ||
attachments: attachments, | ||
extraData: extraData | ||
) | ||
if let transformer = client.config.modelsTransformer { | ||
transformableInfo = transformer.transform(newMessageInfo: transformableInfo) | ||
} | ||
|
||
createNewMessage( | ||
messageId: messageId, | ||
text: text, | ||
text: transformableInfo.text, | ||
pinning: pinning, | ||
isSilent: isSilent, | ||
attachments: attachments, | ||
attachments: transformableInfo.attachments, | ||
mentionedUserIds: mentionedUserIds, | ||
quotedMessageId: quotedMessageId, | ||
skipPush: skipPush, | ||
skipEnrichUrl: skipEnrichUrl, | ||
extraData: extraData, | ||
extraData: transformableInfo.extraData, | ||
poll: nil, | ||
completion: completion | ||
) | ||
|
@@ -1638,7 +1647,11 @@ private extension ChatChannelController { | |
let observer = BackgroundEntityDatabaseObserver( | ||
database: self.client.databaseContainer, | ||
fetchRequest: ChannelDTO.fetchRequest(for: cid), | ||
itemCreator: { try $0.asModel() as ChatChannel } | ||
itemCreator: { | ||
try $0.asModel( | ||
transformer: self.client.config.modelsTransformer | ||
) as ChatChannel | ||
Comment on lines
+1651
to
+1653
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. When thinking ahead to cleaning up There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah I'm not sure how that will look like, but we can improve it once we work on that 👍 But I don't think we need to pass the whole config to the asModel though 🤔 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sure, can be changed later if needed. When I briefly checked it then There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I see, in that case I will probably change this yet 👍 |
||
} | ||
).onChange { [weak self] change in | ||
self?.delegateCallback { [weak self] in | ||
guard let self = self else { | ||
|
@@ -1682,7 +1695,9 @@ private extension ChatChannelController { | |
deletedMessagesVisibility: deletedMessageVisibility, | ||
shouldShowShadowedMessages: shouldShowShadowedMessages | ||
), | ||
itemCreator: { try $0.asModel() as ChatMessage }, | ||
itemCreator: { | ||
try $0.asModel(transformer: self.client.config.modelsTransformer) as ChatMessage | ||
}, | ||
itemReuseKeyPaths: (\ChatMessage.id, \MessageDTO.id) | ||
) | ||
observer.onDidChange = { [weak self] changes in | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: maybe just uppercase all the letter of the channel name? Would feel weird otherwise to see only "Hey!" everywhere.
Same for messages, some small transformation of the original text would be nice.