Skip to content

Commit

Permalink
Merge branch 'develop' into dependabot/bundler/rexml-3.3.9
Browse files Browse the repository at this point in the history
  • Loading branch information
testableapple authored Oct 31, 2024
2 parents 48be617 + 1555103 commit f682223
Show file tree
Hide file tree
Showing 30 changed files with 434 additions and 16 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
# Upcoming

## StreamChat
### ✅ Added
- Add support for system messages not updating `channel.lastMessageAt` [#3476](https://github.com/GetStream/stream-chat-swift/pull/3476)
- Add support for sending system messages client-side
[#3477](https://github.com/GetStream/stream-chat-swift/pull/3477)
### 🐞 Fixed
- Fix watching channels when performing channel search [#3472](https://github.com/GetStream/stream-chat-swift/pull/3472)

Expand Down
15 changes: 11 additions & 4 deletions DemoApp/StreamChat/Components/DemoChatChannelListRouter.swift
Original file line number Diff line number Diff line change
Expand Up @@ -408,10 +408,8 @@ final class DemoChatChannelListRouter: ChatChannelListRouter {
}
}),
.init(title: "Show Channel Info", handler: { [unowned self] _ in
self.rootViewController.presentAlert(
title: "Channel Info",
message: channelController.channel.debugDescription
)
let debugViewController = DebugObjectViewController(object: channelController.channel)
self.rootViewController.present(debugViewController, animated: true)
}),
.init(title: "Show Channel Members", handler: { [unowned self] _ in
guard let cid = channelController.channel?.cid else { return }
Expand Down Expand Up @@ -482,6 +480,15 @@ final class DemoChatChannelListRouter: ChatChannelListRouter {
channelController.createNewMessage(text: message, skipPush: true)
}
}),
.init(title: "Send system message", isEnabled: canSendMessage, handler: { [unowned self] _ in
self.rootViewController.presentAlert(title: "Enter the message text", textFieldPlaceholder: "Send message") { message in
guard let message = message, !message.isEmpty else {
self.rootViewController.presentAlert(title: "Message is not valid")
return
}
channelController.createSystemMessage(text: message)
}
}),
.init(title: "Send message without url enriching", isEnabled: canSendMessage, handler: { [unowned self] _ in
self.rootViewController.presentAlert(title: "Enter the message text", textFieldPlaceholder: "Send message") { message in
guard let message = message, !message.isEmpty else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,7 @@ public class ChannelConfig: Codable {
case commands
case createdAt = "created_at"
case updatedAt = "updated_at"
case skipLastMsgAtUpdateForSystemMsg = "skip_last_msg_update_for_system_msgs"
}

/// If users are allowed to add reactions to messages. Enabled by default.
Expand Down Expand Up @@ -258,6 +259,8 @@ public class ChannelConfig: Codable {
public let updatedAt: Date
/// Determines if polls are enabled.
public let pollsEnabled: Bool
/// Determines if system messages should not update the last message at date.
public let skipLastMsgAtUpdateForSystemMsg: Bool

public required init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
Expand All @@ -282,6 +285,7 @@ public class ChannelConfig: Codable {
createdAt = try container.decode(Date.self, forKey: .createdAt)
updatedAt = try container.decode(Date.self, forKey: .updatedAt)
pollsEnabled = try container.decodeIfPresent(Bool.self, forKey: .pollsEnabled) ?? false
skipLastMsgAtUpdateForSystemMsg = try container.decodeIfPresent(Bool.self, forKey: .skipLastMsgAtUpdateForSystemMsg) ?? false
}

internal required init(
Expand All @@ -296,6 +300,7 @@ public class ChannelConfig: Codable {
mutesEnabled: Bool = false,
pollsEnabled: Bool = false,
urlEnrichmentEnabled: Bool = false,
skipLastMsgAtUpdateForSystemMsg: Bool = false,
messageRetention: String = "",
maxMessageLength: Int = 0,
commands: [Command] = [],
Expand All @@ -318,5 +323,6 @@ public class ChannelConfig: Codable {
self.createdAt = createdAt
self.updatedAt = updatedAt
self.pollsEnabled = pollsEnabled
self.skipLastMsgAtUpdateForSystemMsg = skipLastMsgAtUpdateForSystemMsg
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,10 @@ struct MessageRequestBody: Encodable {
let id: String
let user: UserRequestBody
let text: String

// Used at the moment only for creating a system a message.
let type: String?

let command: String?
let args: String?
let parentId: String?
Expand All @@ -269,6 +273,7 @@ struct MessageRequestBody: Encodable {
id: String,
user: UserRequestBody,
text: String,
type: String? = nil,
command: String? = nil,
args: String? = nil,
parentId: String? = nil,
Expand All @@ -285,6 +290,7 @@ struct MessageRequestBody: Encodable {
self.id = id
self.user = user
self.text = text
self.type = type
self.command = command
self.args = args
self.parentId = parentId
Expand Down Expand Up @@ -312,6 +318,7 @@ struct MessageRequestBody: Encodable {
try container.encodeIfPresent(pinExpires, forKey: .pinExpires)
try container.encode(isSilent, forKey: .isSilent)
try container.encodeIfPresent(pollId, forKey: .pollId)
try container.encodeIfPresent(type, forKey: .type)

if !attachments.isEmpty {
try container.encode(attachments, forKey: .attachments)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -732,7 +732,53 @@ public class ChatChannelController: DataController, DelegateCallable, DataStoreP
completion: completion
)
}


/// Sends a system message to the channel.
///
/// - Parameters:
/// - text: The text of the system message.
/// - messageId: The id for the sent message. By default, it is automatically generated by Stream.
/// - extraData: The extra data for the message.
/// - completion: Called when saving the message to the local DB finishes.
public func createSystemMessage(
text: String,
messageId: MessageId? = nil,
extraData: [String: RawJSON] = [:],
completion: ((Result<MessageId, Error>) -> Void)? = nil
) {
guard let cid = cid, isChannelAlreadyCreated else {
channelModificationFailed { error in
completion?(.failure(error ?? ClientError.Unknown()))
}
return
}

updater.createNewMessage(
in: cid,
messageId: messageId,
text: text,
pinning: nil,
isSilent: false,
isSystem: true,
command: nil,
arguments: nil,
attachments: [],
mentionedUserIds: [],
quotedMessageId: nil,
skipPush: false,
skipEnrichUrl: false,
poll: nil,
extraData: extraData
) { result in
if let newMessage = try? result.get() {
self.client.eventNotificationCenter.process(NewMessagePendingEvent(message: newMessage))
}
self.callback {
completion?(result.map(\.id))
}
}
}

/// Creates a new poll.
///
/// - Parameters:
Expand Down Expand Up @@ -1285,6 +1331,7 @@ public class ChatChannelController: DataController, DelegateCallable, DataStoreP
text: text,
pinning: pinning,
isSilent: isSilent,
isSystem: false,
command: nil,
arguments: nil,
attachments: attachments,
Expand Down
3 changes: 3 additions & 0 deletions Sources/StreamChat/Database/DTOs/ChannelConfigDTO.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ final class ChannelConfigDTO: NSManagedObject {
@NSManaged var typingEventsEnabled: Bool
@NSManaged var readEventsEnabled: Bool
@NSManaged var connectEventsEnabled: Bool
@NSManaged var skipLastMsgAtUpdateForSystemMsg: Bool
@NSManaged var uploadsEnabled: Bool
@NSManaged var repliesEnabled: Bool
@NSManaged var quotesEnabled: Bool
Expand All @@ -37,6 +38,7 @@ final class ChannelConfigDTO: NSManagedObject {
mutesEnabled: mutesEnabled,
pollsEnabled: pollsEnabled,
urlEnrichmentEnabled: urlEnrichmentEnabled,
skipLastMsgAtUpdateForSystemMsg: skipLastMsgAtUpdateForSystemMsg,
messageRetention: messageRetention,
maxMessageLength: Int(maxMessageLength),
commands: Array(Set(
Expand Down Expand Up @@ -76,6 +78,7 @@ extension ChannelConfig {
dto.updatedAt = updatedAt.bridgeDate
dto.commands = NSOrderedSet(array: commands.map { $0.asDTO(context: context) })
dto.pollsEnabled = pollsEnabled
dto.skipLastMsgAtUpdateForSystemMsg = skipLastMsgAtUpdateForSystemMsg
return dto
}
}
33 changes: 27 additions & 6 deletions Sources/StreamChat/Database/DTOs/MessageDTO.swift
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ class MessageDTO: NSManagedObject {
@NSManaged var replyCount: Int32
@NSManaged var extraData: Data?
@NSManaged var isSilent: Bool

@NSManaged var skipPush: Bool
@NSManaged var skipEnrichUrl: Bool
@NSManaged var isShadowed: Bool
Expand Down Expand Up @@ -621,6 +622,7 @@ extension NSManagedObjectContext: MessageDatabaseSession {
mentionedUserIds: [UserId],
showReplyInChannel: Bool,
isSilent: Bool,
isSystem: Bool,
quotedMessageId: MessageId?,
createdAt: Date?,
skipPush: Bool,
Expand Down Expand Up @@ -654,7 +656,6 @@ extension NSManagedObjectContext: MessageDatabaseSession {
}

message.cid = cid.rawValue
message.type = parentMessageId == nil ? MessageType.regular.rawValue : MessageType.reply.rawValue
message.text = text
message.command = command
message.args = arguments
Expand All @@ -666,7 +667,16 @@ extension NSManagedObjectContext: MessageDatabaseSession {
message.reactionScores = [:]
message.reactionCounts = [:]
message.reactionGroups = []


// Message type
if parentMessageId != nil {
message.type = MessageType.reply.rawValue
} else if isSystem {
message.type = MessageType.system.rawValue
} else {
message.type = MessageType.regular.rawValue
}

if let poll {
message.poll = try? savePoll(payload: poll, cache: nil)
}
Expand All @@ -686,9 +696,12 @@ extension NSManagedObjectContext: MessageDatabaseSession {
message.user = currentUserDTO.user
message.channel = channelDTO

let newLastMessageAt = max(channelDTO.lastMessageAt?.bridgeDate ?? createdAt, createdAt).bridgeDate
channelDTO.lastMessageAt = newLastMessageAt
channelDTO.defaultSortingAt = newLastMessageAt
let shouldNotUpdateLastMessageAt = isSystem && channelDTO.config.skipLastMsgAtUpdateForSystemMsg
if !shouldNotUpdateLastMessageAt {
let newLastMessageAt = max(channelDTO.lastMessageAt?.bridgeDate ?? createdAt, createdAt).bridgeDate
channelDTO.lastMessageAt = newLastMessageAt
channelDTO.defaultSortingAt = newLastMessageAt
}

if let parentMessageId = parentMessageId,
let parentMessageDTO = MessageDTO.load(id: parentMessageId, context: self) {
Expand Down Expand Up @@ -813,7 +826,11 @@ extension NSManagedObjectContext: MessageDatabaseSession {
array: payload.threadParticipants.map { try saveUser(payload: $0) }
)

channelDTO.lastMessageAt = max(channelDTO.lastMessageAt?.bridgeDate ?? payload.createdAt, payload.createdAt).bridgeDate
let isSystemMessage = dto.type == MessageType.system.rawValue
let shouldNotUpdateLastMessageAt = isSystemMessage && channelDTO.config.skipLastMsgAtUpdateForSystemMsg
if !shouldNotUpdateLastMessageAt {
channelDTO.lastMessageAt = max(channelDTO.lastMessageAt?.bridgeDate ?? payload.createdAt, payload.createdAt).bridgeDate
}

dto.channel = channelDTO

Expand Down Expand Up @@ -1241,10 +1258,14 @@ extension MessageDTO {
.sorted { ($0.attachmentID?.index ?? 0) < ($1.attachmentID?.index ?? 0) }
.compactMap { $0.asRequestPayload() }

// At the moment, we only provide the type for system messages when creating a message.
let systemType = type == MessageType.system.rawValue ? type : nil

return .init(
id: id,
user: user.asRequestBody(),
text: text,
type: systemType,
command: command,
args: args,
parentId: parentMessageId,
Expand Down
3 changes: 3 additions & 0 deletions Sources/StreamChat/Database/DatabaseSession.swift
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ protocol MessageDatabaseSession {
mentionedUserIds: [UserId],
showReplyInChannel: Bool,
isSilent: Bool,
isSystem: Bool,
quotedMessageId: MessageId?,
createdAt: Date?,
skipPush: Bool,
Expand Down Expand Up @@ -220,6 +221,7 @@ extension MessageDatabaseSession {
pinning: MessagePinning?,
quotedMessageId: MessageId?,
isSilent: Bool = false,
isSystem: Bool,
skipPush: Bool,
skipEnrichUrl: Bool,
attachments: [AnyAttachmentPayload] = [],
Expand All @@ -239,6 +241,7 @@ extension MessageDatabaseSession {
mentionedUserIds: mentionedUserIds,
showReplyInChannel: false,
isSilent: isSilent,
isSystem: isSystem,
quotedMessageId: quotedMessageId,
createdAt: nil,
skipPush: skipPush,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="23231" systemVersion="24A348" minimumToolsVersion="Automatic" sourceLanguage="Swift" userDefinedModelVersionIdentifier="">
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="23507" systemVersion="23G93" minimumToolsVersion="Automatic" sourceLanguage="Swift" userDefinedModelVersionIdentifier="">
<entity name="AttachmentDTO" representedClassName="AttachmentDTO" syncable="YES">
<attribute name="data" attributeType="Binary"/>
<attribute name="id" attributeType="String"/>
Expand Down Expand Up @@ -28,6 +28,7 @@
<attribute name="readEventsEnabled" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
<attribute name="repliesEnabled" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
<attribute name="searchEnabled" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
<attribute name="skipLastMsgAtUpdateForSystemMsg" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
<attribute name="typingEventsEnabled" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
<attribute name="updatedAt" attributeType="Date" usesScalarValueType="NO"/>
<attribute name="uploadsEnabled" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
Expand Down
37 changes: 36 additions & 1 deletion Sources/StreamChat/StateLayer/Chat.swift
Original file line number Diff line number Diff line change
Expand Up @@ -457,6 +457,7 @@ public class Chat {
text: text,
pinning: pinning,
isSilent: silent,
isSystem: false,
command: nil,
arguments: nil,
attachments: attachments,
Expand All @@ -471,7 +472,41 @@ public class Chat {
eventNotificationCenter.process(NewMessagePendingEvent(message: localMessage))
return try await sentMessage
}


/// Sends a system message to the channel.
///
/// - Parameters:
/// - text: Text of the message.
/// - messageId: A custom id for the sent message. By default, it is automatically generated by Stream.
/// - extraData: Additional extra data of the message object.
@discardableResult
public func sendSystemMessage(
with text: String,
messageId: MessageId? = nil,
extraData: [String: RawJSON] = [:]
) async throws -> ChatMessage {
let localMessage = try await channelUpdater.createNewMessage(
in: cid,
messageId: messageId,
text: text,
pinning: nil,
isSilent: false,
isSystem: true,
command: nil,
arguments: nil,
attachments: [],
mentionedUserIds: [],
quotedMessageId: nil,
skipPush: false,
skipEnrichUrl: false,
extraData: extraData
)
// Important to set up the waiter immediately
async let sentMessage = try await waitForAPIRequest(localMessage: localMessage)
eventNotificationCenter.process(NewMessagePendingEvent(message: localMessage))
return try await sentMessage
}

/// Edits the specified message in the channel.
///
/// - Parameters:
Expand Down
Loading

0 comments on commit f682223

Please sign in to comment.