From 01fd3763dc5407d1fe343d47af5e594c0fa98dd6 Mon Sep 17 00:00:00 2001 From: Toomas Vahter Date: Fri, 28 Nov 2025 15:22:28 +0200 Subject: [PATCH 1/9] Add filterTags to ChatChannel, filterTags filter key and to channel update methods --- .swiftlint.yml | 4 ++-- .../Endpoints/Payloads/ChannelCodingKeys.swift | 1 + .../Endpoints/Payloads/ChannelEditDetailPayload.swift | 9 +++++++++ .../Endpoints/Payloads/ChannelListPayload.swift | 2 ++ .../ChannelController/ChannelController.swift | 6 ++++++ .../ChatClient+ChannelController.swift | 4 ++++ Sources/StreamChat/Database/DTOs/ChannelDTO.swift | 5 +++++ .../StreamChatModel.xcdatamodel/contents | 3 ++- Sources/StreamChat/Models/Channel.swift | 8 ++++++++ .../Payload+asModel/ChannelPayload+asModel.swift | 11 ++++++----- Sources/StreamChat/Query/ChannelListQuery.swift | 4 ++++ Sources/StreamChat/StateLayer/Chat.swift | 6 ++++++ .../StreamChat/StateLayer/ChatClient+Factory.swift | 6 ++++++ .../Unique/ChannelEditDetailPayload+Unique.swift | 1 + .../StreamChatTestTools/Fixtures/JSONs/Channel.json | 3 +++ .../Mocks/Models + Extensions/ChatChannel_Mock.swift | 2 ++ .../TestData/DummyData/ChannelDetailPayload.swift | 2 ++ .../TestData/DummyData/XCTestCase+Dummy.swift | 3 +++ .../Payloads/ChannelEditDetailPayload_Tests.swift | 7 ++++++- .../Endpoints/Payloads/ChannelListPayload_Tests.swift | 10 +++++++++- .../Payloads/IdentifiablePayload_Tests.swift | 1 + .../ChannelController/ChannelController_Tests.swift | 3 +++ .../LivestreamChannelController_Tests.swift | 2 ++ .../Query/ChannelListFilterScope_Tests.swift | 2 ++ Tests/StreamChatTests/Query/ChannelQuery_Tests.swift | 3 +++ .../ChannelReadUpdaterMiddleware_Tests.swift | 1 + 26 files changed, 99 insertions(+), 10 deletions(-) diff --git a/.swiftlint.yml b/.swiftlint.yml index 485ef7d1040..2a1dfd6cd69 100644 --- a/.swiftlint.yml +++ b/.swiftlint.yml @@ -67,8 +67,8 @@ file_name_no_space: cyclomatic_complexity: ignores_case_statements: true - warning: 25 - error: 30 + warning: 30 + error: 35 custom_rules: regular_constraints_forbidden: diff --git a/Sources/StreamChat/APIClient/Endpoints/Payloads/ChannelCodingKeys.swift b/Sources/StreamChat/APIClient/Endpoints/Payloads/ChannelCodingKeys.swift index 55d57d980a4..853a6868665 100644 --- a/Sources/StreamChat/APIClient/Endpoints/Payloads/ChannelCodingKeys.swift +++ b/Sources/StreamChat/APIClient/Endpoints/Payloads/ChannelCodingKeys.swift @@ -42,6 +42,7 @@ public enum ChannelCodingKeys: String, CodingKey, CaseIterable { case members /// Invites. case invites + case filterTags = "filter_tags" /// The team the channel belongs to. case team case memberCount = "member_count" diff --git a/Sources/StreamChat/APIClient/Endpoints/Payloads/ChannelEditDetailPayload.swift b/Sources/StreamChat/APIClient/Endpoints/Payloads/ChannelEditDetailPayload.swift index f8a04823dd5..476dd84e23e 100644 --- a/Sources/StreamChat/APIClient/Endpoints/Payloads/ChannelEditDetailPayload.swift +++ b/Sources/StreamChat/APIClient/Endpoints/Payloads/ChannelEditDetailPayload.swift @@ -12,6 +12,7 @@ struct ChannelEditDetailPayload: Encodable { let team: String? let members: Set let invites: Set + let filterTags: Set let extraData: [String: RawJSON] init( @@ -21,6 +22,7 @@ struct ChannelEditDetailPayload: Encodable { team: String?, members: Set, invites: Set, + filterTags: Set, extraData: [String: RawJSON] ) { id = cid.id @@ -30,6 +32,7 @@ struct ChannelEditDetailPayload: Encodable { self.team = team self.members = members self.invites = invites + self.filterTags = filterTags self.extraData = extraData } @@ -40,6 +43,7 @@ struct ChannelEditDetailPayload: Encodable { team: String?, members: Set, invites: Set, + filterTags: Set, extraData: [String: RawJSON] ) { id = nil @@ -49,6 +53,7 @@ struct ChannelEditDetailPayload: Encodable { self.team = team self.members = members self.invites = invites + self.filterTags = filterTags self.extraData = extraData } @@ -63,6 +68,10 @@ struct ChannelEditDetailPayload: Encodable { allMembers = allMembers.union(invites) try container.encode(invites, forKey: .invites) } + + if !filterTags.isEmpty { + try container.encode(filterTags, forKey: .filterTags) + } if !allMembers.isEmpty { try container.encode(allMembers, forKey: .members) diff --git a/Sources/StreamChat/APIClient/Endpoints/Payloads/ChannelListPayload.swift b/Sources/StreamChat/APIClient/Endpoints/Payloads/ChannelListPayload.swift index 720f16212db..914945a9f96 100644 --- a/Sources/StreamChat/APIClient/Endpoints/Payloads/ChannelListPayload.swift +++ b/Sources/StreamChat/APIClient/Endpoints/Payloads/ChannelListPayload.swift @@ -127,6 +127,7 @@ struct ChannelDetailPayload { let createdBy: UserPayload? /// A config. let config: ChannelConfig + let filterTags: [String]? /// The list of actions that the current user can perform in a channel. /// It is optional, since not all events contain the own capabilities property for performance reasons. let ownCapabilities: [String]? @@ -188,6 +189,7 @@ extension ChannelDetailPayload: Decodable { truncatedAt: try container.decodeIfPresent(Date.self, forKey: .truncatedAt), createdBy: try container.decodeIfPresent(UserPayload.self, forKey: .createdBy), config: try container.decode(ChannelConfig.self, forKey: .config), + filterTags: try container.decodeIfPresent([String].self, forKey: .filterTags), ownCapabilities: try container.decodeIfPresent([String].self, forKey: .ownCapabilities), isDisabled: try container.decode(Bool.self, forKey: .disabled), isFrozen: try container.decode(Bool.self, forKey: .frozen), diff --git a/Sources/StreamChat/Controllers/ChannelController/ChannelController.swift b/Sources/StreamChat/Controllers/ChannelController/ChannelController.swift index b6981143078..35b48117bdd 100644 --- a/Sources/StreamChat/Controllers/ChannelController/ChannelController.swift +++ b/Sources/StreamChat/Controllers/ChannelController/ChannelController.swift @@ -253,6 +253,7 @@ public class ChatChannelController: DataController, DelegateCallable, DataStoreP /// - team: New team. /// - members: New members. /// - invites: New invites. + /// - filterTags: New filter tags. /// - extraData: New `ExtraData`. /// - completion: The completion. Will be called on a **callbackQueue** when the network request is finished. /// If request fails, the completion will be called with an error. @@ -263,6 +264,7 @@ public class ChatChannelController: DataController, DelegateCallable, DataStoreP team: String?, members: Set = [], invites: Set = [], + filterTags: Set = [], extraData: [String: RawJSON] = [:], completion: ((Error?) -> Void)? = nil ) { @@ -279,6 +281,7 @@ public class ChatChannelController: DataController, DelegateCallable, DataStoreP team: team, members: members, invites: invites, + filterTags: filterTags, extraData: extraData ) @@ -295,6 +298,7 @@ public class ChatChannelController: DataController, DelegateCallable, DataStoreP /// - team: New team. /// - members: New members. /// - invites: New invites. + /// - filterTags: New filter tags. /// - extraData: New `ExtraData`. /// - unsetProperties: Properties from the channel that are going to be cleared/unset. /// - completion: The completion. Will be called on a **callbackQueue** when the network request is finished. @@ -306,6 +310,7 @@ public class ChatChannelController: DataController, DelegateCallable, DataStoreP team: String? = nil, members: Set = [], invites: Set = [], + filterTags: Set = [], extraData: [String: RawJSON] = [:], unsetProperties: [String] = [], completion: ((Error?) -> Void)? = nil @@ -323,6 +328,7 @@ public class ChatChannelController: DataController, DelegateCallable, DataStoreP team: team, members: members, invites: invites, + filterTags: filterTags, extraData: extraData ) diff --git a/Sources/StreamChat/Controllers/ChannelController/ChatClient+ChannelController.swift b/Sources/StreamChat/Controllers/ChannelController/ChatClient+ChannelController.swift index 9a4d4d103c6..6fd079d736a 100644 --- a/Sources/StreamChat/Controllers/ChannelController/ChatClient+ChannelController.swift +++ b/Sources/StreamChat/Controllers/ChannelController/ChatClient+ChannelController.swift @@ -78,6 +78,7 @@ public extension ChatClient { isCurrentUserMember: Bool = true, messageOrdering: MessageOrdering = .topToBottom, invites: Set = [], + filterTags: Set = [], extraData: [String: RawJSON] = [:], channelListQuery: ChannelListQuery? = nil ) throws -> ChatChannelController { @@ -92,6 +93,7 @@ public extension ChatClient { team: team, members: members.union(isCurrentUserMember ? [currentUserId] : []), invites: invites, + filterTags: filterTags, extraData: extraData ) @@ -134,6 +136,7 @@ public extension ChatClient { name: String? = nil, imageURL: URL? = nil, team: String? = nil, + filterTags: Set = [], extraData: [String: RawJSON], channelListQuery: ChannelListQuery? = nil ) throws -> ChatChannelController { @@ -147,6 +150,7 @@ public extension ChatClient { team: team, members: members.union(isCurrentUserMember ? [currentUserId] : []), invites: [], + filterTags: filterTags, extraData: extraData ) return .init( diff --git a/Sources/StreamChat/Database/DTOs/ChannelDTO.swift b/Sources/StreamChat/Database/DTOs/ChannelDTO.swift index aeee63c7c61..c58513302af 100644 --- a/Sources/StreamChat/Database/DTOs/ChannelDTO.swift +++ b/Sources/StreamChat/Database/DTOs/ChannelDTO.swift @@ -16,6 +16,7 @@ class ChannelDTO: NSManagedObject { @NSManaged var typeRawValue: String @NSManaged var extraData: Data @NSManaged var config: ChannelConfigDTO + @NSManaged var filterTags: [String] @NSManaged var ownCapabilities: [String] @NSManaged var createdAt: DBDate @@ -255,6 +256,9 @@ extension NSManagedObjectContext { dto.typeRawValue = payload.typeRawValue dto.id = payload.cid.id dto.config = payload.config.asDTO(context: self, cid: dto.cid) + if let filterTags = payload.filterTags { + dto.filterTags = filterTags + } if let ownCapabilities = payload.ownCapabilities { dto.ownCapabilities = ownCapabilities } @@ -645,6 +649,7 @@ extension ChatChannel { isHidden: dto.isHidden, createdBy: dto.createdBy?.asModel(), config: dto.config.asModel(), + filterTags: Set(dto.filterTags), ownCapabilities: Set(dto.ownCapabilities.compactMap(ChannelCapability.init(rawValue:))), isFrozen: dto.isFrozen, isDisabled: dto.isDisabled, diff --git a/Sources/StreamChat/Database/StreamChatModel.xcdatamodeld/StreamChatModel.xcdatamodel/contents b/Sources/StreamChat/Database/StreamChatModel.xcdatamodeld/StreamChatModel.xcdatamodel/contents index db02b92fa19..e21132df461 100644 --- a/Sources/StreamChat/Database/StreamChatModel.xcdatamodeld/StreamChatModel.xcdatamodel/contents +++ b/Sources/StreamChat/Database/StreamChatModel.xcdatamodeld/StreamChatModel.xcdatamodel/contents @@ -1,5 +1,5 @@ - + @@ -47,6 +47,7 @@ + diff --git a/Sources/StreamChat/Models/Channel.swift b/Sources/StreamChat/Models/Channel.swift index 92eecb9da1d..b5fdc178c21 100644 --- a/Sources/StreamChat/Models/Channel.swift +++ b/Sources/StreamChat/Models/Channel.swift @@ -41,6 +41,9 @@ public struct ChatChannel { /// A configuration struct of the channel. It contains additional information about the channel settings. public let config: ChannelConfig + /// The list of tags associated with the channel. + public let filterTags: Set + /// The list of actions that the current user can perform in a channel. public let ownCapabilities: Set @@ -192,6 +195,7 @@ public struct ChatChannel { isHidden: Bool, createdBy: ChatUser? = nil, config: ChannelConfig = .init(), + filterTags: Set = [], ownCapabilities: Set = [], isFrozen: Bool = false, isDisabled: Bool = false, @@ -228,6 +232,7 @@ public struct ChatChannel { self.isHidden = isHidden self.createdBy = createdBy self.config = config + self.filterTags = filterTags self.ownCapabilities = ownCapabilities self.isFrozen = isFrozen self.isDisabled = isDisabled @@ -274,6 +279,7 @@ public struct ChatChannel { isHidden: isHidden, createdBy: createdBy, config: config, + filterTags: filterTags, ownCapabilities: ownCapabilities, isFrozen: isFrozen, isDisabled: isDisabled, @@ -341,6 +347,7 @@ public struct ChatChannel { isHidden: isHidden ?? self.isHidden, createdBy: createdBy ?? self.createdBy, config: config ?? self.config, + filterTags: filterTags, ownCapabilities: ownCapabilities ?? self.ownCapabilities, isFrozen: isFrozen ?? self.isFrozen, isDisabled: isDisabled ?? self.isDisabled, @@ -452,6 +459,7 @@ extension ChatChannel: Hashable { guard lhs.membership == rhs.membership else { return false } guard lhs.team == rhs.team else { return false } guard lhs.truncatedAt == rhs.truncatedAt else { return false } + guard lhs.filterTags == rhs.filterTags else { return false } guard lhs.ownCapabilities == rhs.ownCapabilities else { return false } guard lhs.draftMessage == rhs.draftMessage else { return false } guard lhs.activeLiveLocations.count == rhs.activeLiveLocations.count else { return false } diff --git a/Sources/StreamChat/Models/Payload+asModel/ChannelPayload+asModel.swift b/Sources/StreamChat/Models/Payload+asModel/ChannelPayload+asModel.swift index ecdcbd48944..7bdda1e0e66 100644 --- a/Sources/StreamChat/Models/Payload+asModel/ChannelPayload+asModel.swift +++ b/Sources/StreamChat/Models/Payload+asModel/ChannelPayload+asModel.swift @@ -13,22 +13,22 @@ extension ChannelPayload { unreadCount: ChannelUnreadCount? ) -> ChatChannel { let channelPayload = channel - + // Map members let mappedMembers = members.compactMap { $0.asModel(channelId: channelPayload.cid) } - + // Map latest messages let reads = channelReads.map { $0.asModel() } let latestMessages = messages.compactMap { $0.asModel(cid: channel.cid, currentUserId: currentUserId, channelReads: reads) } - + // Map reads let mappedReads = channelReads.map { $0.asModel() } - + // Map watchers let mappedWatchers = watchers?.map { $0.asModel() } ?? [] - + return ChatChannel( cid: channelPayload.cid, name: channelPayload.name, @@ -41,6 +41,7 @@ extension ChannelPayload { isHidden: isHidden ?? false, createdBy: channelPayload.createdBy?.asModel(), config: channelPayload.config, + filterTags: Set(channelPayload.filterTags ?? []), ownCapabilities: Set(channelPayload.ownCapabilities?.compactMap { ChannelCapability(rawValue: $0) } ?? []), isFrozen: channelPayload.isFrozen, isDisabled: channelPayload.isDisabled, diff --git a/Sources/StreamChat/Query/ChannelListQuery.swift b/Sources/StreamChat/Query/ChannelListQuery.swift index 92b17658126..22c018ee491 100644 --- a/Sources/StreamChat/Query/ChannelListQuery.swift +++ b/Sources/StreamChat/Query/ChannelListQuery.swift @@ -291,6 +291,10 @@ public extension FilterKey where Scope == ChannelListFilterScope { /// Filter for checking if the current user has a specific channel role set. /// Supported operatios: `equal`, `in` static var channelRole: FilterKey { .init(rawValue: "channel_role", keyPathString: #keyPath(ChannelDTO.membership.channelRoleRaw)) } + + /// A filter key for matching channel filter tags. + /// Supported operators: `in`, `equal` + static var filterTags: FilterKey { .init(rawValue: "filter_tags", keyPathString: #keyPath(ChannelDTO.filterTags)) } } /// Internal filter queries for the channel list. diff --git a/Sources/StreamChat/StateLayer/Chat.swift b/Sources/StreamChat/StateLayer/Chat.swift index 727966a5ab9..2ce5178c942 100644 --- a/Sources/StreamChat/StateLayer/Chat.swift +++ b/Sources/StreamChat/StateLayer/Chat.swift @@ -1356,6 +1356,7 @@ public class Chat { /// - team: The team for the channel. /// - members: A list of members for the channel. /// - invites: A list of users who will get invites. + /// - filterTags: A list of tags to add for the channel. /// - extraData: Extra data for the new channel. /// /// - Throws: An error while communicating with the Stream API. @@ -1365,6 +1366,7 @@ public class Chat { team: String?, members: Set = [], invites: Set = [], + filterTags: Set = [], extraData: [String: RawJSON] = [:] ) async throws { try await channelUpdater.update( @@ -1375,6 +1377,7 @@ public class Chat { team: team, members: members, invites: invites, + filterTags: filterTags, extraData: extraData ) ) @@ -1391,6 +1394,7 @@ public class Chat { /// - team: The team for the channel. /// - members: A list of members for the channel. /// - invites: A list of users who will get invites. + /// - filterTags: A list of tags to add to the channel. /// - extraData: Extra data for the channel. /// - unsetProperties: A list of properties to reset. /// @@ -1401,6 +1405,7 @@ public class Chat { team: String? = nil, members: [UserId] = [], invites: [UserId] = [], + filterTags: Set = [], extraData: [String: RawJSON] = [:], unsetProperties: [String] = [] ) async throws { @@ -1412,6 +1417,7 @@ public class Chat { team: team, members: Set(members), invites: Set(invites), + filterTags: filterTags, extraData: extraData ), unsetProperties: unsetProperties diff --git a/Sources/StreamChat/StateLayer/ChatClient+Factory.swift b/Sources/StreamChat/StateLayer/ChatClient+Factory.swift index c01ffcf364e..29ec7512eaa 100644 --- a/Sources/StreamChat/StateLayer/ChatClient+Factory.swift +++ b/Sources/StreamChat/StateLayer/ChatClient+Factory.swift @@ -121,6 +121,7 @@ extension ChatClient { /// - members: A list of members for the channel. /// - isCurrentUserMember: If `true`, the current user is added as member. /// - invites: A list of users who will get invites. + /// - filterTags: A list of tags to add to the channel. /// - messageOrdering: Describes the ordering the messages are presented. /// - memberSorting: The sorting order for channel members (the default sorting is by created at in ascending order). /// - channelListQuery: The channel list query the channel belongs to. @@ -136,6 +137,7 @@ extension ChatClient { members: [UserId] = [], isCurrentUserMember: Bool = true, invites: [UserId] = [], + filterTags: Set = [], messageOrdering: MessageOrdering = .topToBottom, memberSorting: [Sorting] = [], channelListQuery: ChannelListQuery? = nil, @@ -149,6 +151,7 @@ extension ChatClient { team: team, members: Set(members).union(isCurrentUserMember ? [currentUserId] : []), invites: Set(invites), + filterTags: filterTags, extraData: extraData ) let channelQuery = ChannelQuery(channelPayload: payload) @@ -175,6 +178,7 @@ extension ChatClient { /// - name: The name of the channel. /// - imageURL: The channel avatar URL. /// - team: The team for the channel. + /// - filterTags: A list of tags to add to the channel. /// - messageOrdering: Describes the ordering the messages are presented. /// - memberSorting: The sorting order for channel members (the default sorting is by created at in ascending order). /// - channelListQuery: The channel list query the channel belongs to. @@ -189,6 +193,7 @@ extension ChatClient { name: String? = nil, imageURL: URL? = nil, team: String? = nil, + filterTags: Set = [], messageOrdering: MessageOrdering = .topToBottom, memberSorting: [Sorting] = [], channelListQuery: ChannelListQuery? = nil, @@ -203,6 +208,7 @@ extension ChatClient { team: team, members: Set(members).union(isCurrentUserMember ? [currentUserId] : []), invites: [], + filterTags: filterTags, extraData: extraData ) let channelQuery = ChannelQuery(channelPayload: payload) diff --git a/TestTools/StreamChatTestTools/Extensions/Unique/ChannelEditDetailPayload+Unique.swift b/TestTools/StreamChatTestTools/Extensions/Unique/ChannelEditDetailPayload+Unique.swift index 31caa31ce08..09aa934e37f 100644 --- a/TestTools/StreamChatTestTools/Extensions/Unique/ChannelEditDetailPayload+Unique.swift +++ b/TestTools/StreamChatTestTools/Extensions/Unique/ChannelEditDetailPayload+Unique.swift @@ -14,6 +14,7 @@ extension ChannelEditDetailPayload { team: .unique, members: [], invites: [], + filterTags: [], extraData: .init() ) } diff --git a/TestTools/StreamChatTestTools/Fixtures/JSONs/Channel.json b/TestTools/StreamChatTestTools/Fixtures/JSONs/Channel.json index 45693ffcba6..2258bf8a5b4 100644 --- a/TestTools/StreamChatTestTools/Fixtures/JSONs/Channel.json +++ b/TestTools/StreamChatTestTools/Fixtures/JSONs/Channel.json @@ -1346,6 +1346,9 @@ "uploads" : true, "name" : "messaging" }, + "filter_tags": [ + "football" + ], "own_capabilities": [ "update-channel-members", "join-channel", diff --git a/TestTools/StreamChatTestTools/Mocks/Models + Extensions/ChatChannel_Mock.swift b/TestTools/StreamChatTestTools/Mocks/Models + Extensions/ChatChannel_Mock.swift index bf01781bbb5..5960dfb66f9 100644 --- a/TestTools/StreamChatTestTools/Mocks/Models + Extensions/ChatChannel_Mock.swift +++ b/TestTools/StreamChatTestTools/Mocks/Models + Extensions/ChatChannel_Mock.swift @@ -87,6 +87,7 @@ public extension ChatChannel { isHidden: Bool = false, createdBy: ChatUser? = nil, config: ChannelConfig = .mock(), + filterTags: Set = [], ownCapabilities: Set = [.sendMessage, .uploadFile], isFrozen: Bool = false, isDisabled: Bool = false, @@ -123,6 +124,7 @@ public extension ChatChannel { isHidden: isHidden, createdBy: createdBy, config: config, + filterTags: filterTags, ownCapabilities: ownCapabilities, isFrozen: isFrozen, isDisabled: isDisabled, diff --git a/TestTools/StreamChatTestTools/TestData/DummyData/ChannelDetailPayload.swift b/TestTools/StreamChatTestTools/TestData/DummyData/ChannelDetailPayload.swift index 9c3b763dfc8..1636f87e60d 100644 --- a/TestTools/StreamChatTestTools/TestData/DummyData/ChannelDetailPayload.swift +++ b/TestTools/StreamChatTestTools/TestData/DummyData/ChannelDetailPayload.swift @@ -20,6 +20,7 @@ extension ChannelDetailPayload { truncatedAt: Date? = nil, createdBy: UserPayload = .dummy(userId: .unique), config: ChannelConfig = .mock(), + filterTags: [String]? = nil, ownCapabilities: [String] = [], isFrozen: Bool = false, isBlocked: Bool? = false, @@ -44,6 +45,7 @@ extension ChannelDetailPayload { truncatedAt: truncatedAt, createdBy: createdBy, config: config, + filterTags: filterTags, ownCapabilities: ownCapabilities, isDisabled: isDisabled, isFrozen: isFrozen, diff --git a/TestTools/StreamChatTestTools/TestData/DummyData/XCTestCase+Dummy.swift b/TestTools/StreamChatTestTools/TestData/DummyData/XCTestCase+Dummy.swift index 6e7ad8191fc..308b5e8558f 100644 --- a/TestTools/StreamChatTestTools/TestData/DummyData/XCTestCase+Dummy.swift +++ b/TestTools/StreamChatTestTools/TestData/DummyData/XCTestCase+Dummy.swift @@ -154,6 +154,7 @@ extension XCTestCase { createdAt: XCTestCase.channelCreatedDate, updatedAt: .unique ), + filterTags: [String]? = nil, ownCapabilities: [String] = [], channelExtraData: [String: RawJSON] = [:], createdAt: Date = XCTestCase.channelCreatedDate, @@ -190,6 +191,7 @@ extension XCTestCase { truncatedAt: truncatedAt, createdBy: dummyUser, config: channelConfig, + filterTags: filterTags, ownCapabilities: ownCapabilities, isDisabled: false, isFrozen: true, @@ -310,6 +312,7 @@ extension XCTestCase { createdAt: XCTestCase.channelCreatedDate, updatedAt: .unique ), + filterTags: nil, ownCapabilities: [], isDisabled: false, isFrozen: true, diff --git a/Tests/StreamChatTests/APIClient/Endpoints/Payloads/ChannelEditDetailPayload_Tests.swift b/Tests/StreamChatTests/APIClient/Endpoints/Payloads/ChannelEditDetailPayload_Tests.swift index 23dc8960a4e..861c98b6c5b 100644 --- a/Tests/StreamChatTests/APIClient/Endpoints/Payloads/ChannelEditDetailPayload_Tests.swift +++ b/Tests/StreamChatTests/APIClient/Endpoints/Payloads/ChannelEditDetailPayload_Tests.swift @@ -14,6 +14,7 @@ final class ChannelEditDetailPayload_Tests: XCTestCase { let imageURL: URL = .unique() let team: String = .unique let invite: UserId = .unique + let filterTags: Set = [.unique] // Create ChannelEditDetailPayload let payload = ChannelEditDetailPayload( @@ -23,6 +24,7 @@ final class ChannelEditDetailPayload_Tests: XCTestCase { team: team, members: [invite], invites: [invite], + filterTags: filterTags, extraData: [:] ) @@ -31,7 +33,8 @@ final class ChannelEditDetailPayload_Tests: XCTestCase { "image": imageURL.absoluteString, "team": team, "members": [invite], - "invites": [invite] + "invites": [invite], + "filter_tags": filterTags ] let encodedJSON = try JSONEncoder.default.encode(payload) @@ -50,6 +53,7 @@ final class ChannelEditDetailPayload_Tests: XCTestCase { team: nil, members: [.unique], invites: [], + filterTags: [], extraData: [:] ) @@ -65,6 +69,7 @@ final class ChannelEditDetailPayload_Tests: XCTestCase { team: nil, members: [], invites: [], + filterTags: [], extraData: [:] ) diff --git a/Tests/StreamChatTests/APIClient/Endpoints/Payloads/ChannelListPayload_Tests.swift b/Tests/StreamChatTests/APIClient/Endpoints/Payloads/ChannelListPayload_Tests.swift index bd5c5b17d54..3ec5279579f 100644 --- a/Tests/StreamChatTests/APIClient/Endpoints/Payloads/ChannelListPayload_Tests.swift +++ b/Tests/StreamChatTests/APIClient/Endpoints/Payloads/ChannelListPayload_Tests.swift @@ -146,7 +146,10 @@ final class ChannelListPayload_Tests: XCTestCase { ], createdAt: channelCreatedDate, updatedAt: .unique - ), ownCapabilities: [ + ), filterTags: [ + "football" + ], + ownCapabilities: [ "join-channel", "delete-channel" ], @@ -349,6 +352,7 @@ final class ChannelPayload_Tests: XCTestCase { XCTAssertEqual(config.updatedAt, "2020-03-17T18:54:09.460881Z".toDate()) XCTAssertEqual(payload.membership?.user?.id, "broken-waterfall-5") + XCTAssertEqual(payload.channel.filterTags, ["football"]) XCTAssertEqual(payload.channel.ownCapabilities?.count, 27) XCTAssertEqual(payload.activeLiveLocations.count, 1) XCTAssertNotNil(payload.pushPreference) @@ -445,6 +449,7 @@ final class ChannelPayload_Tests: XCTestCase { truncatedAt: Date(timeIntervalSince1970: 1_609_459_250), createdBy: createdByPayload, config: ChannelConfig(), + filterTags: ["football"], ownCapabilities: ["send-message", "upload-file"], isDisabled: true, isFrozen: true, @@ -493,6 +498,7 @@ final class ChannelPayload_Tests: XCTestCase { XCTAssertEqual(chatChannel.isHidden, true) XCTAssertEqual(chatChannel.createdBy?.id, "creator-user-id") XCTAssertNotNil(chatChannel.config) + XCTAssertEqual(chatChannel.filterTags, ["football"]) XCTAssertTrue(chatChannel.ownCapabilities.contains(.sendMessage)) XCTAssertTrue(chatChannel.ownCapabilities.contains(.uploadFile)) XCTAssertEqual(chatChannel.isFrozen, true) @@ -542,6 +548,7 @@ final class ChannelPayload_Tests: XCTestCase { truncatedAt: nil, createdBy: nil, config: ChannelConfig(), + filterTags: nil, ownCapabilities: nil, isDisabled: false, isFrozen: false, @@ -587,6 +594,7 @@ final class ChannelPayload_Tests: XCTestCase { XCTAssertEqual(chatChannel.isHidden, false) XCTAssertNil(chatChannel.createdBy) XCTAssertNotNil(chatChannel.config) + XCTAssertTrue(chatChannel.filterTags.isEmpty) XCTAssertTrue(chatChannel.ownCapabilities.isEmpty) XCTAssertEqual(chatChannel.isFrozen, false) XCTAssertEqual(chatChannel.isDisabled, false) diff --git a/Tests/StreamChatTests/APIClient/Endpoints/Payloads/IdentifiablePayload_Tests.swift b/Tests/StreamChatTests/APIClient/Endpoints/Payloads/IdentifiablePayload_Tests.swift index cc9c412eb01..c16486e0fc4 100644 --- a/Tests/StreamChatTests/APIClient/Endpoints/Payloads/IdentifiablePayload_Tests.swift +++ b/Tests/StreamChatTests/APIClient/Endpoints/Payloads/IdentifiablePayload_Tests.swift @@ -311,6 +311,7 @@ final class IdentifiablePayload_Tests: XCTestCase { truncatedAt: nil, createdBy: owner, config: .mock(), + filterTags: nil, ownCapabilities: [], isDisabled: false, isFrozen: true, diff --git a/Tests/StreamChatTests/Controllers/ChannelController/ChannelController_Tests.swift b/Tests/StreamChatTests/Controllers/ChannelController/ChannelController_Tests.swift index 37857fb8dd0..40d335abf3e 100644 --- a/Tests/StreamChatTests/Controllers/ChannelController/ChannelController_Tests.swift +++ b/Tests/StreamChatTests/Controllers/ChannelController/ChannelController_Tests.swift @@ -4719,6 +4719,7 @@ final class ChannelController_Tests: XCTestCase { team: nil, members: Set(), invites: Set(), + filterTags: [], extraData: [:] ) @@ -5835,6 +5836,7 @@ extension ChannelController_Tests { team: nil, members: [currentUserId, otherUserId], invites: [], + filterTags: [], extraData: [:] ) @@ -5874,6 +5876,7 @@ extension ChannelController_Tests { team: nil, members: [], invites: [], + filterTags: [], extraData: [:] ) diff --git a/Tests/StreamChatTests/Controllers/ChannelController/LivestreamChannelController_Tests.swift b/Tests/StreamChatTests/Controllers/ChannelController/LivestreamChannelController_Tests.swift index 3132b6aa87c..60c6e4dc598 100644 --- a/Tests/StreamChatTests/Controllers/ChannelController/LivestreamChannelController_Tests.swift +++ b/Tests/StreamChatTests/Controllers/ChannelController/LivestreamChannelController_Tests.swift @@ -1568,6 +1568,7 @@ extension LivestreamChannelController_Tests { isHidden: true, createdBy: newCreatedBy, config: .mock(), + filterTags: ["football"], ownCapabilities: [.sendMessage, .readEvents], isFrozen: true, isDisabled: true, @@ -1605,6 +1606,7 @@ extension LivestreamChannelController_Tests { XCTAssertEqual(controller.channel?.isHidden, true) XCTAssertEqual(controller.channel?.createdBy?.id, newCreatedBy.id) XCTAssertEqual(controller.channel?.createdBy?.name, newCreatedBy.name) + XCTAssertEqual(controller.channel?.filterTags, ["football"]) XCTAssertTrue(controller.channel?.ownCapabilities.contains(.sendMessage) ?? false) XCTAssertTrue(controller.channel?.ownCapabilities.contains(.readEvents) ?? false) XCTAssertEqual(controller.channel?.isFrozen, true) diff --git a/Tests/StreamChatTests/Query/ChannelListFilterScope_Tests.swift b/Tests/StreamChatTests/Query/ChannelListFilterScope_Tests.swift index 437ac8e6ee8..18fc737cee0 100644 --- a/Tests/StreamChatTests/Query/ChannelListFilterScope_Tests.swift +++ b/Tests/StreamChatTests/Query/ChannelListFilterScope_Tests.swift @@ -24,6 +24,7 @@ final class ChannelListFilterScope_Tests: XCTestCase { XCTAssertEqual(Key.frozen.rawValue, ChannelCodingKeys.frozen.rawValue) XCTAssertEqual(Key.memberCount.rawValue, ChannelCodingKeys.memberCount.rawValue) XCTAssertEqual(Key.team.rawValue, ChannelCodingKeys.team.rawValue) + XCTAssertEqual(Key.filterTags.rawValue, ChannelCodingKeys.filterTags.rawValue) // FilterKeys without corresponding ChannelCodingKeys XCTAssertEqual(Key.createdBy.rawValue, "created_by_id") @@ -60,6 +61,7 @@ final class ChannelListFilterScope_Tests: XCTestCase { XCTAssertEqual(Key.muted.keyPathString, "mute") XCTAssertEqual(Key.archived.keyPathString, "membership.archivedAt") XCTAssertEqual(Key.pinned.keyPathString, "membership.pinnedAt") + XCTAssertEqual(Key.filterTags.keyPathString, "filter_tags") XCTAssertNil(Key.invite.keyPathString) } diff --git a/Tests/StreamChatTests/Query/ChannelQuery_Tests.swift b/Tests/StreamChatTests/Query/ChannelQuery_Tests.swift index 8bd2d1ed1a8..1f97dea71ec 100644 --- a/Tests/StreamChatTests/Query/ChannelQuery_Tests.swift +++ b/Tests/StreamChatTests/Query/ChannelQuery_Tests.swift @@ -47,6 +47,7 @@ final class ChannelQuery_Tests: XCTestCase { team: nil, members: [.unique], invites: [], + filterTags: [], extraData: [:] )) @@ -69,6 +70,7 @@ final class ChannelQuery_Tests: XCTestCase { team: nil, members: [.unique], invites: [], + filterTags: [], extraData: [:] )) XCTAssertEqual(query.apiPath, "custom_type") @@ -82,6 +84,7 @@ final class ChannelQuery_Tests: XCTestCase { team: nil, members: [.unique], invites: [], + filterTags: [], extraData: [:] )) XCTAssertEqual(query.apiPath, "custom_type/id") diff --git a/Tests/StreamChatTests/WebSocketClient/EventMiddlewares/ChannelReadUpdaterMiddleware_Tests.swift b/Tests/StreamChatTests/WebSocketClient/EventMiddlewares/ChannelReadUpdaterMiddleware_Tests.swift index 882cfc6ff00..8d80417f666 100644 --- a/Tests/StreamChatTests/WebSocketClient/EventMiddlewares/ChannelReadUpdaterMiddleware_Tests.swift +++ b/Tests/StreamChatTests/WebSocketClient/EventMiddlewares/ChannelReadUpdaterMiddleware_Tests.swift @@ -1017,6 +1017,7 @@ final class ChannelReadUpdaterMiddleware_Tests: XCTestCase { truncatedAt: nil, createdBy: nil, config: .init(), + filterTags: nil, ownCapabilities: [], isDisabled: false, isFrozen: false, From c9699b8347d8d39a3451108b6854dfffe7fa3dd5 Mon Sep 17 00:00:00 2001 From: Toomas Vahter Date: Fri, 28 Nov 2025 15:31:53 +0200 Subject: [PATCH 2/9] Add CHANGELOG entry --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2df65e9d60a..bb5867bd90b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,12 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - Add `ChatClient.deleteAttachment(remoteUrl:)` [#3883](https://github.com/GetStream/stream-chat-swift/pull/3883) - Add `CDNClient.deleteAttachment(remoteUrl:)` [#3883](https://github.com/GetStream/stream-chat-swift/pull/3883) - Add `heic`, `heif` and `svg` formats to the supported image file types [#3883](https://github.com/GetStream/stream-chat-swift/pull/3883) +- Add support for filter tags in channels [#3886](https://github.com/GetStream/stream-chat-swift/pull/3886) + - Add `ChatChannel.filterTags` + - Add `filterTags` channel list filtering key + - Add `filterTags` argument to `ChatChannelController` and `Chat` factory methods in `ChatClient` + - Add `filterTags` argument to `ChatChannelController.updateChannel` and `ChatChannelController.partialUpdateChannel` + - Add `filterTags` argument to `Chat.update` and `Chat.updatePartial` ### 🐞 Fixed - Fix rare crash in WebSocketClient.connectEndpoint [#3882](https://github.com/GetStream/stream-chat-swift/pull/3882) From ede7829fb11a04d3c000bd875b7ef82c14587e92 Mon Sep 17 00:00:00 2001 From: Toomas Vahter Date: Fri, 28 Nov 2025 15:34:30 +0200 Subject: [PATCH 3/9] Update docs --- .../Controllers/ChannelController/ChannelController.swift | 4 ++-- .../ChannelController/ChatClient+ChannelController.swift | 2 ++ Sources/StreamChat/StateLayer/Chat.swift | 2 +- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/Sources/StreamChat/Controllers/ChannelController/ChannelController.swift b/Sources/StreamChat/Controllers/ChannelController/ChannelController.swift index 35b48117bdd..c1fc1886043 100644 --- a/Sources/StreamChat/Controllers/ChannelController/ChannelController.swift +++ b/Sources/StreamChat/Controllers/ChannelController/ChannelController.swift @@ -253,7 +253,7 @@ public class ChatChannelController: DataController, DelegateCallable, DataStoreP /// - team: New team. /// - members: New members. /// - invites: New invites. - /// - filterTags: New filter tags. + /// - filterTags: A list of tags to add to the channel. /// - extraData: New `ExtraData`. /// - completion: The completion. Will be called on a **callbackQueue** when the network request is finished. /// If request fails, the completion will be called with an error. @@ -298,7 +298,7 @@ public class ChatChannelController: DataController, DelegateCallable, DataStoreP /// - team: New team. /// - members: New members. /// - invites: New invites. - /// - filterTags: New filter tags. + /// - filterTags: A list of tags to add to the channel. /// - extraData: New `ExtraData`. /// - unsetProperties: Properties from the channel that are going to be cleared/unset. /// - completion: The completion. Will be called on a **callbackQueue** when the network request is finished. diff --git a/Sources/StreamChat/Controllers/ChannelController/ChatClient+ChannelController.swift b/Sources/StreamChat/Controllers/ChannelController/ChatClient+ChannelController.swift index 6fd079d736a..0d75d586faa 100644 --- a/Sources/StreamChat/Controllers/ChannelController/ChatClient+ChannelController.swift +++ b/Sources/StreamChat/Controllers/ChannelController/ChatClient+ChannelController.swift @@ -65,6 +65,7 @@ public extension ChatClient { /// - isCurrentUserMember: If set to `true` the current user will be included into the channel. Is `true` by default. /// - messageOrdering: Describes the ordering the messages are presented. /// - invites: IDs for the new channel invitees. + /// - filterTags: A list of tags to add to the channel. /// - extraData: Extra data for the new channel. /// - channelListQuery: The channel list query the channel this controller represents is part of. /// - Throws: `ClientError.CurrentUserDoesNotExist` if there is no currently logged-in user. @@ -121,6 +122,7 @@ public extension ChatClient { /// - name: The new channel name. /// - imageURL: The new channel avatar URL. /// - team: Team for the new channel. + /// - filterTags: A list of tags to add to the channel. /// - extraData: Extra data for the new channel. /// - channelListQuery: The channel list query the channel this controller represents is part of. /// diff --git a/Sources/StreamChat/StateLayer/Chat.swift b/Sources/StreamChat/StateLayer/Chat.swift index 2ce5178c942..7ef3048612e 100644 --- a/Sources/StreamChat/StateLayer/Chat.swift +++ b/Sources/StreamChat/StateLayer/Chat.swift @@ -1356,7 +1356,7 @@ public class Chat { /// - team: The team for the channel. /// - members: A list of members for the channel. /// - invites: A list of users who will get invites. - /// - filterTags: A list of tags to add for the channel. + /// - filterTags: A list of tags to add to the channel. /// - extraData: Extra data for the new channel. /// /// - Throws: An error while communicating with the Stream API. From 7851679a53aedb985aa6c975eb584a73bc5ec0a7 Mon Sep 17 00:00:00 2001 From: Toomas Vahter Date: Fri, 28 Nov 2025 16:46:12 +0200 Subject: [PATCH 4/9] Fix tests --- .../ChannelController/LivestreamChannelController.swift | 1 + Sources/StreamChat/Models/Channel.swift | 3 ++- .../Endpoints/Payloads/ChannelEditDetailPayload_Tests.swift | 6 +++--- .../Query/ChannelListFilterScope_Tests.swift | 2 +- 4 files changed, 7 insertions(+), 5 deletions(-) diff --git a/Sources/StreamChat/Controllers/ChannelController/LivestreamChannelController.swift b/Sources/StreamChat/Controllers/ChannelController/LivestreamChannelController.swift index 0ffafcc0838..0629eacaa2e 100644 --- a/Sources/StreamChat/Controllers/ChannelController/LivestreamChannelController.swift +++ b/Sources/StreamChat/Controllers/ChannelController/LivestreamChannelController.swift @@ -1139,6 +1139,7 @@ public class LivestreamChannelController: DataStoreProvider, EventsControllerDel isHidden: event.channel.isHidden, createdBy: event.channel.createdBy, config: event.channel.config, + filterTags: event.channel.filterTags, ownCapabilities: event.channel.ownCapabilities, isFrozen: event.channel.isFrozen, isDisabled: event.channel.isDisabled, diff --git a/Sources/StreamChat/Models/Channel.swift b/Sources/StreamChat/Models/Channel.swift index b5fdc178c21..5ed7bde35fd 100644 --- a/Sources/StreamChat/Models/Channel.swift +++ b/Sources/StreamChat/Models/Channel.swift @@ -319,6 +319,7 @@ public struct ChatChannel { isHidden: Bool? = nil, createdBy: ChatUser? = nil, config: ChannelConfig? = nil, + filterTags: Set? = nil, ownCapabilities: Set? = nil, isFrozen: Bool? = nil, isDisabled: Bool? = nil, @@ -347,7 +348,7 @@ public struct ChatChannel { isHidden: isHidden ?? self.isHidden, createdBy: createdBy ?? self.createdBy, config: config ?? self.config, - filterTags: filterTags, + filterTags: filterTags ?? self.filterTags, ownCapabilities: ownCapabilities ?? self.ownCapabilities, isFrozen: isFrozen ?? self.isFrozen, isDisabled: isDisabled ?? self.isDisabled, diff --git a/Tests/StreamChatTests/APIClient/Endpoints/Payloads/ChannelEditDetailPayload_Tests.swift b/Tests/StreamChatTests/APIClient/Endpoints/Payloads/ChannelEditDetailPayload_Tests.swift index 861c98b6c5b..26f18ffcef4 100644 --- a/Tests/StreamChatTests/APIClient/Endpoints/Payloads/ChannelEditDetailPayload_Tests.swift +++ b/Tests/StreamChatTests/APIClient/Endpoints/Payloads/ChannelEditDetailPayload_Tests.swift @@ -14,7 +14,7 @@ final class ChannelEditDetailPayload_Tests: XCTestCase { let imageURL: URL = .unique() let team: String = .unique let invite: UserId = .unique - let filterTags: Set = [.unique] + let filterTag: String = .unique // Create ChannelEditDetailPayload let payload = ChannelEditDetailPayload( @@ -24,7 +24,7 @@ final class ChannelEditDetailPayload_Tests: XCTestCase { team: team, members: [invite], invites: [invite], - filterTags: filterTags, + filterTags: [filterTag], extraData: [:] ) @@ -34,7 +34,7 @@ final class ChannelEditDetailPayload_Tests: XCTestCase { "team": team, "members": [invite], "invites": [invite], - "filter_tags": filterTags + "filter_tags": [filterTag] ] let encodedJSON = try JSONEncoder.default.encode(payload) diff --git a/Tests/StreamChatTests/Query/ChannelListFilterScope_Tests.swift b/Tests/StreamChatTests/Query/ChannelListFilterScope_Tests.swift index 18fc737cee0..d01117b40fd 100644 --- a/Tests/StreamChatTests/Query/ChannelListFilterScope_Tests.swift +++ b/Tests/StreamChatTests/Query/ChannelListFilterScope_Tests.swift @@ -61,7 +61,7 @@ final class ChannelListFilterScope_Tests: XCTestCase { XCTAssertEqual(Key.muted.keyPathString, "mute") XCTAssertEqual(Key.archived.keyPathString, "membership.archivedAt") XCTAssertEqual(Key.pinned.keyPathString, "membership.pinnedAt") - XCTAssertEqual(Key.filterTags.keyPathString, "filter_tags") + XCTAssertEqual(Key.filterTags.keyPathString, "filterTags") XCTAssertNil(Key.invite.keyPathString) } From 500de83438ca0b873ac4512d9a278d91ea82e8b3 Mon Sep 17 00:00:00 2001 From: Toomas Vahter Date: Mon, 1 Dec 2025 10:16:58 +0200 Subject: [PATCH 5/9] Add more tests --- .../StreamChat/Query/ChannelListQuery.swift | 2 +- .../Workers/ChannelUpdater_Mock.swift | 10 ++ .../ChannelListController_Tests.swift | 17 +++ .../StateLayer/Chat_Tests.swift | 100 ++++++++++++++++++ 4 files changed, 128 insertions(+), 1 deletion(-) diff --git a/Sources/StreamChat/Query/ChannelListQuery.swift b/Sources/StreamChat/Query/ChannelListQuery.swift index 22c018ee491..cd5c67e77c6 100644 --- a/Sources/StreamChat/Query/ChannelListQuery.swift +++ b/Sources/StreamChat/Query/ChannelListQuery.swift @@ -294,7 +294,7 @@ public extension FilterKey where Scope == ChannelListFilterScope { /// A filter key for matching channel filter tags. /// Supported operators: `in`, `equal` - static var filterTags: FilterKey { .init(rawValue: "filter_tags", keyPathString: #keyPath(ChannelDTO.filterTags)) } + static var filterTags: FilterKey { .init(rawValue: "filter_tags", keyPathString: #keyPath(ChannelDTO.filterTags), isCollectionFilter: true) } } /// Internal filter queries for the channel list. diff --git a/TestTools/StreamChatTestTools/Mocks/StreamChat/Workers/ChannelUpdater_Mock.swift b/TestTools/StreamChatTestTools/Mocks/StreamChat/Workers/ChannelUpdater_Mock.swift index 1311233832c..b703335c86c 100644 --- a/TestTools/StreamChatTestTools/Mocks/StreamChat/Workers/ChannelUpdater_Mock.swift +++ b/TestTools/StreamChatTestTools/Mocks/StreamChat/Workers/ChannelUpdater_Mock.swift @@ -14,10 +14,12 @@ final class ChannelUpdater_Mock: ChannelUpdater { @Atomic var updateChannel_payload: ChannelEditDetailPayload? @Atomic var updateChannel_completion: ((Error?) -> Void)? + @Atomic var updateChannel_completion_result: Result? @Atomic var partialChannelUpdate_updates: ChannelEditDetailPayload? @Atomic var partialChannelUpdate_unsetProperties: [String]? @Atomic var partialChannelUpdate_completion: ((Error?) -> Void)? + @Atomic var partialChannelUpdate_completion_result: Result? @Atomic var muteChannel_cid: ChannelId? @Atomic var muteChannel_expiration: Int? @@ -170,6 +172,12 @@ final class ChannelUpdater_Mock: ChannelUpdater { updateChannel_payload = nil updateChannel_completion = nil + updateChannel_completion_result = nil + + partialChannelUpdate_updates = nil + partialChannelUpdate_unsetProperties = nil + partialChannelUpdate_completion = nil + partialChannelUpdate_completion_result = nil muteChannel_cid = nil muteChannel_expiration = nil @@ -330,12 +338,14 @@ final class ChannelUpdater_Mock: ChannelUpdater { override func updateChannel(channelPayload: ChannelEditDetailPayload, completion: ((Error?) -> Void)? = nil) { updateChannel_payload = channelPayload updateChannel_completion = completion + updateChannel_completion_result?.invoke(with: completion) } override func partialChannelUpdate(updates: ChannelEditDetailPayload, unsetProperties: [String], completion: ((Error?) -> Void)? = nil) { partialChannelUpdate_updates = updates partialChannelUpdate_unsetProperties = unsetProperties partialChannelUpdate_completion = completion + partialChannelUpdate_completion_result?.invoke(with: completion) } override func muteChannel(cid: ChannelId, expiration: Int? = nil, completion: ((Error?) -> Void)? = nil) { diff --git a/Tests/StreamChatTests/Controllers/ChannelListController/ChannelListController_Tests.swift b/Tests/StreamChatTests/Controllers/ChannelListController/ChannelListController_Tests.swift index f53792abca7..5af5f7db53c 100644 --- a/Tests/StreamChatTests/Controllers/ChannelListController/ChannelListController_Tests.swift +++ b/Tests/StreamChatTests/Controllers/ChannelListController/ChannelListController_Tests.swift @@ -1943,6 +1943,23 @@ final class ChannelListController_Tests: XCTestCase { expectedResult: [cid1, cid2] ) } + + func test_filterPredicate_filterTags_returnsExpectedResults() throws { + let cid1 = ChannelId.unique + let cid2 = ChannelId.unique + + try assertFilterPredicate( + .in(.filterTags, values: ["premium"]), + channelsInDB: [ + .dummy(channel: .dummy(cid: cid1, filterTags: ["premium"])), + .dummy(channel: .dummy()), + .dummy(channel: .dummy()), + .dummy(channel: .dummy()), + .dummy(channel: .dummy(cid: cid2, filterTags: ["ai", "premium"])) + ], + expectedResult: [cid1, cid2] + ) + } // MARK: - Private Helpers diff --git a/Tests/StreamChatTests/StateLayer/Chat_Tests.swift b/Tests/StreamChatTests/StateLayer/Chat_Tests.swift index 68764b81951..868283418c5 100644 --- a/Tests/StreamChatTests/StateLayer/Chat_Tests.swift +++ b/Tests/StreamChatTests/StateLayer/Chat_Tests.swift @@ -1308,6 +1308,106 @@ final class Chat_Tests: XCTestCase { await XCTAssertEqual(3, chat.state.channel?.reads.first?.unreadMessagesCount) } + // MARK: - Updating the Channel + + func test_update_whenChannelUpdaterSucceeds_thenUpdateSucceeds() async throws { + env.channelUpdaterMock.updateChannel_completion_result = .success(()) + let name = "Updated Channel Name" + let imageURL = URL(string: "https://example.com/image.png")! + let team = "team123" + let members: Set = [.unique, .unique] + let invites: Set = [.unique] + let filterTags: Set = ["tag1", "tag2"] + let extraData: [String: RawJSON] = ["custom": .string("value")] + + try await chat.update( + name: name, + imageURL: imageURL, + team: team, + members: members, + invites: invites, + filterTags: filterTags, + extraData: extraData + ) + + let payload = try XCTUnwrap(env.channelUpdaterMock.updateChannel_payload) + XCTAssertEqual(payload.name, name) + XCTAssertEqual(payload.imageURL, imageURL) + XCTAssertEqual(payload.team, team) + XCTAssertEqual(payload.members, members) + XCTAssertEqual(payload.invites, invites) + XCTAssertEqual(payload.filterTags, filterTags) + XCTAssertEqual(payload.extraData, extraData) + } + + func test_update_whenChannelUpdaterFails_thenUpdateFails() async throws { + env.channelUpdaterMock.updateChannel_completion_result = .failure(expectedTestError) + + await XCTAssertAsyncFailure( + try await chat.update( + name: "Updated Name", + imageURL: nil, + team: nil, + members: [], + invites: [], + filterTags: [], + extraData: [:] + ), + expectedTestError + ) + } + + func test_updatePartial_whenChannelUpdaterSucceeds_thenUpdatePartialSucceeds() async throws { + env.channelUpdaterMock.partialChannelUpdate_completion_result = .success(()) + let name = "Updated Channel Name" + let imageURL = URL(string: "https://example.com/image.png")! + let team = "team123" + let members: [UserId] = [.unique, .unique] + let invites: [UserId] = [.unique] + let filterTags: Set = ["tag1", "tag2"] + let extraData: [String: RawJSON] = ["custom": .string("value")] + let unsetProperties = ["property1", "property2"] + + try await chat.updatePartial( + name: name, + imageURL: imageURL, + team: team, + members: members, + invites: invites, + filterTags: filterTags, + extraData: extraData, + unsetProperties: unsetProperties + ) + + let payload = try XCTUnwrap(env.channelUpdaterMock.partialChannelUpdate_updates) + XCTAssertEqual(payload.name, name) + XCTAssertEqual(payload.imageURL, imageURL) + XCTAssertEqual(payload.team, team) + XCTAssertEqual(payload.members, Set(members)) + XCTAssertEqual(payload.invites, Set(invites)) + XCTAssertEqual(payload.filterTags, filterTags) + XCTAssertEqual(payload.extraData, extraData) + XCTAssertEqual(env.channelUpdaterMock.partialChannelUpdate_unsetProperties, unsetProperties) + } + + func test_updatePartial_whenChannelUpdaterFails_thenUpdatePartialFails() async throws { + env.channelUpdaterMock.partialChannelUpdate_completion_result = .failure(expectedTestError) + + await XCTAssertAsyncFailure( + try await chat.updatePartial( + name: "Updated Name", + imageURL: nil, + team: nil, + members: [], + invites: [], + filterTags: [], + extraData: [:], + unsetProperties: [] + ), + expectedTestError + ) + } + // MARK: - Message Replies func test_reply_whenAPIRequestSucceeds_thenStateUpdates() async throws { From 02909d12243a5c54f9ee36a738aa6e6cc468ca07 Mon Sep 17 00:00:00 2001 From: Toomas Vahter Date: Mon, 1 Dec 2025 10:31:02 +0200 Subject: [PATCH 6/9] Add channel list filter for premium filter tags --- .../Components/DemoChatChannelListVC.swift | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/DemoApp/StreamChat/Components/DemoChatChannelListVC.swift b/DemoApp/StreamChat/Components/DemoChatChannelListVC.swift index df1f1dfda4f..4ac20e65a4a 100644 --- a/DemoApp/StreamChat/Components/DemoChatChannelListVC.swift +++ b/DemoApp/StreamChat/Components/DemoChatChannelListVC.swift @@ -107,6 +107,8 @@ final class DemoChatChannelListVC: ChatChannelListVC { lazy var equalMembersQuery: ChannelListQuery = .init(filter: .equal(.members, values: [currentUserId, "r2-d2"]) ) + + lazy var premiumTaggedChannelsQuery: ChannelListQuery = .init(filter: .in(.filterTags, values: ["premium"])) var demoRouter: DemoChatChannelListRouter? { router as? DemoChatChannelListRouter @@ -256,6 +258,15 @@ final class DemoChatChannelListVC: ChatChannelListVC { self?.title = "R2-D2 Channels (Equal Members)" self?.setEqualMembersChannelsQuery() } + + let taggedChannelsAction = UIAlertAction( + title: "Premium Tagged Channels", + style: .default, + handler: { [weak self] _ in + self?.title = "Premium Tagged Channels" + self?.setPremiumTaggedChannelsQuery() + } + ) presentAlert( title: "Filter Channels", @@ -271,7 +282,8 @@ final class DemoChatChannelListVC: ChatChannelListVC { pinnedChannelsAction, archivedChannelsAction, equalMembersAction, - channelRoleChannelsAction + channelRoleChannelsAction, + taggedChannelsAction ].sorted(by: { $0.title ?? "" < $1.title ?? "" }), preferredStyle: .actionSheet, sourceView: filterChannelsButton @@ -327,6 +339,10 @@ final class DemoChatChannelListVC: ChatChannelListVC { func setEqualMembersChannelsQuery() { replaceQuery(equalMembersQuery) } + + func setPremiumTaggedChannelsQuery() { + replaceQuery(premiumTaggedChannelsQuery) + } func setInitialChannelsQuery() { replaceQuery(initialQuery) From 33906aa4d53fbb7624795e76bbba8d00d3e2e16e Mon Sep 17 00:00:00 2001 From: Toomas Vahter Date: Mon, 1 Dec 2025 10:36:41 +0200 Subject: [PATCH 7/9] Remove filterTags from update and partial update since it is not supported by backend --- CHANGELOG.md | 2 -- .../Controllers/ChannelController/ChannelController.swift | 8 ++------ Sources/StreamChat/StateLayer/Chat.swift | 8 ++------ Tests/StreamChatTests/StateLayer/Chat_Tests.swift | 8 -------- 4 files changed, 4 insertions(+), 22 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 40012230964..e6e51d4e9ed 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,8 +13,6 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - Add `ChatChannel.filterTags` - Add `filterTags` channel list filtering key - Add `filterTags` argument to `ChatChannelController` and `Chat` factory methods in `ChatClient` - - Add `filterTags` argument to `ChatChannelController.updateChannel` and `ChatChannelController.partialUpdateChannel` - - Add `filterTags` argument to `Chat.update` and `Chat.updatePartial` ### 🐞 Fixed - Fix rare crash in `WebSocketClient.connectEndpoint` [#3882](https://github.com/GetStream/stream-chat-swift/pull/3882) - Fix audio recordings not playing from AirPods automatically [#3884](https://github.com/GetStream/stream-chat-swift/pull/3884) diff --git a/Sources/StreamChat/Controllers/ChannelController/ChannelController.swift b/Sources/StreamChat/Controllers/ChannelController/ChannelController.swift index c1fc1886043..478708ca897 100644 --- a/Sources/StreamChat/Controllers/ChannelController/ChannelController.swift +++ b/Sources/StreamChat/Controllers/ChannelController/ChannelController.swift @@ -253,7 +253,6 @@ public class ChatChannelController: DataController, DelegateCallable, DataStoreP /// - team: New team. /// - members: New members. /// - invites: New invites. - /// - filterTags: A list of tags to add to the channel. /// - extraData: New `ExtraData`. /// - completion: The completion. Will be called on a **callbackQueue** when the network request is finished. /// If request fails, the completion will be called with an error. @@ -264,7 +263,6 @@ public class ChatChannelController: DataController, DelegateCallable, DataStoreP team: String?, members: Set = [], invites: Set = [], - filterTags: Set = [], extraData: [String: RawJSON] = [:], completion: ((Error?) -> Void)? = nil ) { @@ -281,7 +279,7 @@ public class ChatChannelController: DataController, DelegateCallable, DataStoreP team: team, members: members, invites: invites, - filterTags: filterTags, + filterTags: [], extraData: extraData ) @@ -298,7 +296,6 @@ public class ChatChannelController: DataController, DelegateCallable, DataStoreP /// - team: New team. /// - members: New members. /// - invites: New invites. - /// - filterTags: A list of tags to add to the channel. /// - extraData: New `ExtraData`. /// - unsetProperties: Properties from the channel that are going to be cleared/unset. /// - completion: The completion. Will be called on a **callbackQueue** when the network request is finished. @@ -310,7 +307,6 @@ public class ChatChannelController: DataController, DelegateCallable, DataStoreP team: String? = nil, members: Set = [], invites: Set = [], - filterTags: Set = [], extraData: [String: RawJSON] = [:], unsetProperties: [String] = [], completion: ((Error?) -> Void)? = nil @@ -328,7 +324,7 @@ public class ChatChannelController: DataController, DelegateCallable, DataStoreP team: team, members: members, invites: invites, - filterTags: filterTags, + filterTags: [], extraData: extraData ) diff --git a/Sources/StreamChat/StateLayer/Chat.swift b/Sources/StreamChat/StateLayer/Chat.swift index 7ef3048612e..fb98c29ac03 100644 --- a/Sources/StreamChat/StateLayer/Chat.swift +++ b/Sources/StreamChat/StateLayer/Chat.swift @@ -1356,7 +1356,6 @@ public class Chat { /// - team: The team for the channel. /// - members: A list of members for the channel. /// - invites: A list of users who will get invites. - /// - filterTags: A list of tags to add to the channel. /// - extraData: Extra data for the new channel. /// /// - Throws: An error while communicating with the Stream API. @@ -1366,7 +1365,6 @@ public class Chat { team: String?, members: Set = [], invites: Set = [], - filterTags: Set = [], extraData: [String: RawJSON] = [:] ) async throws { try await channelUpdater.update( @@ -1377,7 +1375,7 @@ public class Chat { team: team, members: members, invites: invites, - filterTags: filterTags, + filterTags: [], extraData: extraData ) ) @@ -1394,7 +1392,6 @@ public class Chat { /// - team: The team for the channel. /// - members: A list of members for the channel. /// - invites: A list of users who will get invites. - /// - filterTags: A list of tags to add to the channel. /// - extraData: Extra data for the channel. /// - unsetProperties: A list of properties to reset. /// @@ -1405,7 +1402,6 @@ public class Chat { team: String? = nil, members: [UserId] = [], invites: [UserId] = [], - filterTags: Set = [], extraData: [String: RawJSON] = [:], unsetProperties: [String] = [] ) async throws { @@ -1417,7 +1413,7 @@ public class Chat { team: team, members: Set(members), invites: Set(invites), - filterTags: filterTags, + filterTags: [], extraData: extraData ), unsetProperties: unsetProperties diff --git a/Tests/StreamChatTests/StateLayer/Chat_Tests.swift b/Tests/StreamChatTests/StateLayer/Chat_Tests.swift index 868283418c5..305d9b318bb 100644 --- a/Tests/StreamChatTests/StateLayer/Chat_Tests.swift +++ b/Tests/StreamChatTests/StateLayer/Chat_Tests.swift @@ -1317,7 +1317,6 @@ final class Chat_Tests: XCTestCase { let team = "team123" let members: Set = [.unique, .unique] let invites: Set = [.unique] - let filterTags: Set = ["tag1", "tag2"] let extraData: [String: RawJSON] = ["custom": .string("value")] try await chat.update( @@ -1326,7 +1325,6 @@ final class Chat_Tests: XCTestCase { team: team, members: members, invites: invites, - filterTags: filterTags, extraData: extraData ) @@ -1336,7 +1334,6 @@ final class Chat_Tests: XCTestCase { XCTAssertEqual(payload.team, team) XCTAssertEqual(payload.members, members) XCTAssertEqual(payload.invites, invites) - XCTAssertEqual(payload.filterTags, filterTags) XCTAssertEqual(payload.extraData, extraData) } @@ -1350,7 +1347,6 @@ final class Chat_Tests: XCTestCase { team: nil, members: [], invites: [], - filterTags: [], extraData: [:] ), expectedTestError @@ -1364,7 +1360,6 @@ final class Chat_Tests: XCTestCase { let team = "team123" let members: [UserId] = [.unique, .unique] let invites: [UserId] = [.unique] - let filterTags: Set = ["tag1", "tag2"] let extraData: [String: RawJSON] = ["custom": .string("value")] let unsetProperties = ["property1", "property2"] @@ -1374,7 +1369,6 @@ final class Chat_Tests: XCTestCase { team: team, members: members, invites: invites, - filterTags: filterTags, extraData: extraData, unsetProperties: unsetProperties ) @@ -1385,7 +1379,6 @@ final class Chat_Tests: XCTestCase { XCTAssertEqual(payload.team, team) XCTAssertEqual(payload.members, Set(members)) XCTAssertEqual(payload.invites, Set(invites)) - XCTAssertEqual(payload.filterTags, filterTags) XCTAssertEqual(payload.extraData, extraData) XCTAssertEqual(env.channelUpdaterMock.partialChannelUpdate_unsetProperties, unsetProperties) } @@ -1400,7 +1393,6 @@ final class Chat_Tests: XCTestCase { team: nil, members: [], invites: [], - filterTags: [], extraData: [:], unsetProperties: [] ), From bca70f68528ae8bb3addbfab16559fe91afd3aa4 Mon Sep 17 00:00:00 2001 From: Toomas Vahter Date: Mon, 1 Dec 2025 11:04:01 +0200 Subject: [PATCH 8/9] Revert "Remove filterTags from update and partial update since it is not supported by backend" This reverts commit 33906aa4d53fbb7624795e76bbba8d00d3e2e16e. --- CHANGELOG.md | 2 ++ .../Controllers/ChannelController/ChannelController.swift | 8 ++++++-- Sources/StreamChat/StateLayer/Chat.swift | 8 ++++++-- Tests/StreamChatTests/StateLayer/Chat_Tests.swift | 8 ++++++++ 4 files changed, 22 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e6e51d4e9ed..40012230964 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - Add `ChatChannel.filterTags` - Add `filterTags` channel list filtering key - Add `filterTags` argument to `ChatChannelController` and `Chat` factory methods in `ChatClient` + - Add `filterTags` argument to `ChatChannelController.updateChannel` and `ChatChannelController.partialUpdateChannel` + - Add `filterTags` argument to `Chat.update` and `Chat.updatePartial` ### 🐞 Fixed - Fix rare crash in `WebSocketClient.connectEndpoint` [#3882](https://github.com/GetStream/stream-chat-swift/pull/3882) - Fix audio recordings not playing from AirPods automatically [#3884](https://github.com/GetStream/stream-chat-swift/pull/3884) diff --git a/Sources/StreamChat/Controllers/ChannelController/ChannelController.swift b/Sources/StreamChat/Controllers/ChannelController/ChannelController.swift index 478708ca897..c1fc1886043 100644 --- a/Sources/StreamChat/Controllers/ChannelController/ChannelController.swift +++ b/Sources/StreamChat/Controllers/ChannelController/ChannelController.swift @@ -253,6 +253,7 @@ public class ChatChannelController: DataController, DelegateCallable, DataStoreP /// - team: New team. /// - members: New members. /// - invites: New invites. + /// - filterTags: A list of tags to add to the channel. /// - extraData: New `ExtraData`. /// - completion: The completion. Will be called on a **callbackQueue** when the network request is finished. /// If request fails, the completion will be called with an error. @@ -263,6 +264,7 @@ public class ChatChannelController: DataController, DelegateCallable, DataStoreP team: String?, members: Set = [], invites: Set = [], + filterTags: Set = [], extraData: [String: RawJSON] = [:], completion: ((Error?) -> Void)? = nil ) { @@ -279,7 +281,7 @@ public class ChatChannelController: DataController, DelegateCallable, DataStoreP team: team, members: members, invites: invites, - filterTags: [], + filterTags: filterTags, extraData: extraData ) @@ -296,6 +298,7 @@ public class ChatChannelController: DataController, DelegateCallable, DataStoreP /// - team: New team. /// - members: New members. /// - invites: New invites. + /// - filterTags: A list of tags to add to the channel. /// - extraData: New `ExtraData`. /// - unsetProperties: Properties from the channel that are going to be cleared/unset. /// - completion: The completion. Will be called on a **callbackQueue** when the network request is finished. @@ -307,6 +310,7 @@ public class ChatChannelController: DataController, DelegateCallable, DataStoreP team: String? = nil, members: Set = [], invites: Set = [], + filterTags: Set = [], extraData: [String: RawJSON] = [:], unsetProperties: [String] = [], completion: ((Error?) -> Void)? = nil @@ -324,7 +328,7 @@ public class ChatChannelController: DataController, DelegateCallable, DataStoreP team: team, members: members, invites: invites, - filterTags: [], + filterTags: filterTags, extraData: extraData ) diff --git a/Sources/StreamChat/StateLayer/Chat.swift b/Sources/StreamChat/StateLayer/Chat.swift index fb98c29ac03..7ef3048612e 100644 --- a/Sources/StreamChat/StateLayer/Chat.swift +++ b/Sources/StreamChat/StateLayer/Chat.swift @@ -1356,6 +1356,7 @@ public class Chat { /// - team: The team for the channel. /// - members: A list of members for the channel. /// - invites: A list of users who will get invites. + /// - filterTags: A list of tags to add to the channel. /// - extraData: Extra data for the new channel. /// /// - Throws: An error while communicating with the Stream API. @@ -1365,6 +1366,7 @@ public class Chat { team: String?, members: Set = [], invites: Set = [], + filterTags: Set = [], extraData: [String: RawJSON] = [:] ) async throws { try await channelUpdater.update( @@ -1375,7 +1377,7 @@ public class Chat { team: team, members: members, invites: invites, - filterTags: [], + filterTags: filterTags, extraData: extraData ) ) @@ -1392,6 +1394,7 @@ public class Chat { /// - team: The team for the channel. /// - members: A list of members for the channel. /// - invites: A list of users who will get invites. + /// - filterTags: A list of tags to add to the channel. /// - extraData: Extra data for the channel. /// - unsetProperties: A list of properties to reset. /// @@ -1402,6 +1405,7 @@ public class Chat { team: String? = nil, members: [UserId] = [], invites: [UserId] = [], + filterTags: Set = [], extraData: [String: RawJSON] = [:], unsetProperties: [String] = [] ) async throws { @@ -1413,7 +1417,7 @@ public class Chat { team: team, members: Set(members), invites: Set(invites), - filterTags: [], + filterTags: filterTags, extraData: extraData ), unsetProperties: unsetProperties diff --git a/Tests/StreamChatTests/StateLayer/Chat_Tests.swift b/Tests/StreamChatTests/StateLayer/Chat_Tests.swift index 305d9b318bb..868283418c5 100644 --- a/Tests/StreamChatTests/StateLayer/Chat_Tests.swift +++ b/Tests/StreamChatTests/StateLayer/Chat_Tests.swift @@ -1317,6 +1317,7 @@ final class Chat_Tests: XCTestCase { let team = "team123" let members: Set = [.unique, .unique] let invites: Set = [.unique] + let filterTags: Set = ["tag1", "tag2"] let extraData: [String: RawJSON] = ["custom": .string("value")] try await chat.update( @@ -1325,6 +1326,7 @@ final class Chat_Tests: XCTestCase { team: team, members: members, invites: invites, + filterTags: filterTags, extraData: extraData ) @@ -1334,6 +1336,7 @@ final class Chat_Tests: XCTestCase { XCTAssertEqual(payload.team, team) XCTAssertEqual(payload.members, members) XCTAssertEqual(payload.invites, invites) + XCTAssertEqual(payload.filterTags, filterTags) XCTAssertEqual(payload.extraData, extraData) } @@ -1347,6 +1350,7 @@ final class Chat_Tests: XCTestCase { team: nil, members: [], invites: [], + filterTags: [], extraData: [:] ), expectedTestError @@ -1360,6 +1364,7 @@ final class Chat_Tests: XCTestCase { let team = "team123" let members: [UserId] = [.unique, .unique] let invites: [UserId] = [.unique] + let filterTags: Set = ["tag1", "tag2"] let extraData: [String: RawJSON] = ["custom": .string("value")] let unsetProperties = ["property1", "property2"] @@ -1369,6 +1374,7 @@ final class Chat_Tests: XCTestCase { team: team, members: members, invites: invites, + filterTags: filterTags, extraData: extraData, unsetProperties: unsetProperties ) @@ -1379,6 +1385,7 @@ final class Chat_Tests: XCTestCase { XCTAssertEqual(payload.team, team) XCTAssertEqual(payload.members, Set(members)) XCTAssertEqual(payload.invites, Set(invites)) + XCTAssertEqual(payload.filterTags, filterTags) XCTAssertEqual(payload.extraData, extraData) XCTAssertEqual(env.channelUpdaterMock.partialChannelUpdate_unsetProperties, unsetProperties) } @@ -1393,6 +1400,7 @@ final class Chat_Tests: XCTestCase { team: nil, members: [], invites: [], + filterTags: [], extraData: [:], unsetProperties: [] ), From f25a30e975b1c7435c9eaeae08357590f7313657 Mon Sep 17 00:00:00 2001 From: Toomas Vahter Date: Mon, 1 Dec 2025 11:09:34 +0200 Subject: [PATCH 9/9] Add channel action for updating tags --- .../StreamChat/Components/DemoChatChannelListRouter.swift | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/DemoApp/StreamChat/Components/DemoChatChannelListRouter.swift b/DemoApp/StreamChat/Components/DemoChatChannelListRouter.swift index 45f17681724..21d8ff439a7 100644 --- a/DemoApp/StreamChat/Components/DemoChatChannelListRouter.swift +++ b/DemoApp/StreamChat/Components/DemoChatChannelListRouter.swift @@ -477,6 +477,14 @@ final class DemoChatChannelListRouter: ChatChannelListRouter { } } }), + .init(title: "Add Premium Tag", isEnabled: canUpdateChannel, handler: { [unowned self] _ in + channelController.partialChannelUpdate(filterTags: ["premium"]) { error in + if let error = error { + self.rootViewController.presentAlert(title: "Couldn't make the channel \(cid) premium", message: "\(error)") + } + } + }), + .init(title: "Unmute channel", isEnabled: canMuteChannel, handler: { [unowned self] _ in channelController.unmuteChannel { error in if let error = error {