Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/smoke-checks.yml
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ jobs:
- run: bundle exec fastlane rubocop
- run: bundle exec fastlane run_swift_format strict:true
- run: bundle exec fastlane validate_public_interface
- run: bundle exec fastlane validate_generated_code

build-old-xcode:
name: Build SDKs (Old Xcode)
Expand Down
1 change: 1 addition & 0 deletions .swiftlint.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
excluded:
- Scripts
- Sources/StreamChat/Generated
- Sources/StreamChatCommonUI/Generated
- Sources/StreamChatUI/StreamSwiftyGif
- Sources/StreamChatUI/StreamNuke
Expand Down
12 changes: 10 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,23 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).

### 🔄 Changed

# [5.5.0](https://github.com/GetStream/stream-chat-swift/releases/tag/5.5.0)
_June 03, 2026_

## StreamChat
### ✅ Added
- Add `ChannelListQuery(predefinedFilter:filterValues:sortValues:)` for creating channel list queries with predefined filters [#4113](https://github.com/GetStream/stream-chat-swift/pull/4113)
- Add `ChatClient.queryGroupedChannels(groups:limit:presence:watch:)` to fetch grouped channels with per-group unread counts [#4076](https://github.com/GetStream/stream-chat-swift/pull/4076)
- Add `ChatClient.makeChannelList(with:)` overload for observing a single grouped channels group in the state layer [#4076](https://github.com/GetStream/stream-chat-swift/pull/4076)
- Add `unreadChannelCountsByGroup` to `CurrentChatUser`, observable for changes via `ConnectedUser` [#4076](https://github.com/GetStream/stream-chat-swift/pull/4076)

# [5.4.1](https://github.com/GetStream/stream-chat-swift/releases/tag/5.4.1)
_June 01, 2026_

## StreamChat
### 🐞 Fixed
- Respect `ChatClientConfig.shouldShowShadowedMessages` in `LivestreamChat` and `LivestreamChannelController` [#4118](https://github.com/GetStream/stream-chat-swift/pull/4118)

### 🔄 Changed

# [5.4.0](https://github.com/GetStream/stream-chat-swift/releases/tag/5.4.0)
_May 28, 2026_

Expand Down
70 changes: 70 additions & 0 deletions DemoApp/StreamChat/Components/DemoChatChannelListVC.swift
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,34 @@ final class DemoChatChannelListVC: ChatChannelListVC {

lazy var premiumTaggedChannelsQuery: ChannelListQuery = .init(filter: .in(.filterTags, values: ["premium"]))

lazy var predefinedMessagingChannelsQuery: ChannelListQuery = .init(
predefinedFilter: "user_per_channel_type_channels",
filterValues: ["channel_type": .string(ChannelType.messaging.rawValue), "user_id": .string(currentUserId)],
sortValues: nil
)

lazy var predefinedArchivedChannelsQuery: ChannelListQuery = .init(
predefinedFilter: "user_per_channel_type_archived_hidden",
filterValues: [
"channel_type": .string(ChannelType.messaging.rawValue),
"hidden": .bool(false),
"user_id": .string(currentUserId),
"archived": .bool(true)
],
sortValues: nil
)

lazy var predefinedHiddenChannelsQuery: ChannelListQuery = .init(
predefinedFilter: "user_per_channel_type_archived_hidden",
filterValues: [
"channel_type": .string(ChannelType.messaging.rawValue),
"hidden": .bool(true),
"user_id": .string(currentUserId),
"archived": .bool(false)
],
sortValues: nil
)

lazy var livestreamChannelsQuery: ChannelListQuery = .init(filter: .equal(.type, to: .livestream))

var demoRouter: DemoChatChannelListRouter? {
Expand Down Expand Up @@ -270,6 +298,33 @@ final class DemoChatChannelListVC: ChatChannelListVC {
}
)

let predefinedMessagingChannelsAction = UIAlertAction(
title: "Messaging (Predefined)",
style: .default,
handler: { [weak self] _ in
self?.title = "Messaging (Predefined)"
self?.setPredefinedMessagingChannelsQuery()
}
)

let predefinedArchivedChannelsAction = UIAlertAction(
title: "Archived (Predefined)",
style: .default,
handler: { [weak self] _ in
self?.title = "Archived (Predefined)"
self?.setPredefinedArchivedChannelsQuery()
}
)

let predefinedHiddenChannelsAction = UIAlertAction(
title: "Hidden (Predefined)",
style: .default,
handler: { [weak self] _ in
self?.title = "Hidden (Predefined)"
self?.setPredefinedHiddenChannelsQuery()
}
)

let livestreamChannelsAction = UIAlertAction(
title: "Livestream Channels",
style: .default
Expand All @@ -294,6 +349,9 @@ final class DemoChatChannelListVC: ChatChannelListVC {
equalMembersAction,
channelRoleChannelsAction,
taggedChannelsAction,
predefinedMessagingChannelsAction,
predefinedArchivedChannelsAction,
predefinedHiddenChannelsAction,
livestreamChannelsAction
].sorted(by: { $0.title ?? "" < $1.title ?? "" }),
preferredStyle: .actionSheet,
Expand Down Expand Up @@ -355,6 +413,18 @@ final class DemoChatChannelListVC: ChatChannelListVC {
replaceQuery(premiumTaggedChannelsQuery)
}

func setPredefinedMessagingChannelsQuery() {
replaceQuery(predefinedMessagingChannelsQuery)
}

func setPredefinedArchivedChannelsQuery() {
replaceQuery(predefinedArchivedChannelsQuery)
}

func setPredefinedHiddenChannelsQuery() {
replaceQuery(predefinedHiddenChannelsQuery)
}

func setLivestreamChannelsQuery() {
replaceQuery(livestreamChannelsQuery)
}
Expand Down
1 change: 1 addition & 0 deletions Githubfile
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,4 @@ export INTERFACE_ANALYZER_VERSION='1.0.7'
export SWIFT_LINT_VERSION='0.59.1'
export SWIFT_FORMAT_VERSION='0.58.2'
export SWIFT_GEN_VERSION='6.5.1'
export SOURCERY_VERSION='2.3.0'
3 changes: 3 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@ MAKEFLAGS += --silent
bootstrap:
./Scripts/bootstrap.sh

generate:
sourcery --config Sources/StreamChat/.sourcery.yml

update_dependencies:
echo "👉 Updating Nuke"
make update_nuke version=10.3.3
Expand Down
2 changes: 1 addition & 1 deletion Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ let package = Package(
dependencies: [
.product(name: "StreamCore", package: "stream-core-swift")
],
exclude: ["Info.plist"],
exclude: ["Info.plist", "Generated/PredefinedFilter.stencil"],
resources: [.copy("Database/StreamChatModel.xcdatamodeld")]
),
.target(
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
<a href="https://sonarcloud.io/summary/new_code?id=GetStream_stream-chat-swift"><img src="https://sonarcloud.io/api/project_badges/measure?project=GetStream_stream-chat-swift&metric=coverage" /></a>
</p>
<p align="center">
<img id="stream-chat-label" alt="StreamChat" src="https://img.shields.io/badge/StreamChat-6.85%20MB-blue"/>
<img id="stream-chat-label" alt="StreamChat" src="https://img.shields.io/badge/StreamChat-6.96%20MB-blue"/>
<img id="stream-chat-ui-label" alt="StreamChatUI" src="https://img.shields.io/badge/StreamChatUI-4.25%20MB-blue"/>
<img id="stream-chat-common-ui-label" alt="StreamChatCommonUI" src="https://img.shields.io/badge/StreamChatCommonUI-0.84%20MB-blue"/>
</p>
Expand Down
13 changes: 13 additions & 0 deletions Scripts/bootstrap.sh
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,19 @@ if [ "${SKIP_SWIFT_BOOTSTRAP:-}" != true ]; then
sudo sudo rm -f "$BIN_PATH"
sudo sudo ln -s "$INSTALL_DIR/bin/swiftgen" "$BIN_PATH"
swiftgen --version

puts "Install Sourcery v${SOURCERY_VERSION}"
DOWNLOAD_URL="https://github.com/krzysztofzablocki/Sourcery/releases/download/${SOURCERY_VERSION}/sourcery-${SOURCERY_VERSION}.zip"
DOWNLOAD_PATH="/tmp/sourcery-${SOURCERY_VERSION}.zip"
INSTALL_DIR="/usr/local/lib/sourcery"
BIN_PATH="/usr/local/bin/sourcery"
wget "$DOWNLOAD_URL" -O "$DOWNLOAD_PATH"
sudo rm -rf "$INSTALL_DIR"
sudo mkdir -p "$INSTALL_DIR"
sudo unzip -o "$DOWNLOAD_PATH" -d "$INSTALL_DIR"
sudo rm -f "$BIN_PATH"
sudo ln -s "$INSTALL_DIR/bin/sourcery" "$BIN_PATH"
sourcery --version
fi

if [[ ${INSTALL_SONAR-default} == true ]]; then
Expand Down
7 changes: 7 additions & 0 deletions Sources/StreamChat/.sourcery.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
sources:
- ./Query/ChannelListQuery.swift
- ./Query/Sorting/ChannelListSortingKey.swift
templates:
- ./Generated/PredefinedFilter.stencil
output:
./Generated/PredefinedFilter+Generated.swift
12 changes: 12 additions & 0 deletions Sources/StreamChat/APIClient/Endpoints/ChannelEndpoints.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,18 @@ extension Endpoint {
)
}

static func groupedChannels(
request: GroupedQueryChannelsRequestBody
) -> Endpoint<GroupedQueryChannelsPayload> {
.init(
path: .groupedChannels,
method: .post,
queryItems: nil,
requiresConnectionId: request.watch || request.presence,
body: request
)
}

static func createChannel(query: ChannelQuery) -> Endpoint<ChannelPayload> {
createOrUpdateChannel(path: .createChannel(query.apiPath), query: query)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ extension EndpointPath {
switch self {
case .sendMessage, .editMessage, .deleteMessage, .pinMessage, .unpinMessage, .addReaction, .deleteReaction, .draftMessage:
return true
case .createChannel, .connect, .sync, .users, .guest, .members, .partialMemberUpdate, .search, .devices, .channels, .updateChannel,
case .createChannel, .connect, .sync, .users, .guest, .members, .partialMemberUpdate, .search, .devices, .channels, .groupedChannels, .updateChannel,
.deleteChannel, .channelUpdate, .muteChannel, .showChannel, .truncateChannel, .markChannelRead, .markChannelUnread,
.markAllChannelsRead, .markChannelsDelivered, .channelEvent, .stopWatchingChannel, .pinnedMessages, .uploadChannelAttachment, .message,
.replies, .reactions, .messageAction, .banMember, .flagUser, .flagMessage, .muteUser, .translateMessage,
Expand Down
2 changes: 2 additions & 0 deletions Sources/StreamChat/APIClient/Endpoints/EndpointPath.swift
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ enum EndpointPath: Codable {
case markThreadUnread(cid: ChannelId)

case channels
case groupedChannels
case createChannel(String)
case updateChannel(String)
case deleteChannel(String)
Expand Down Expand Up @@ -116,6 +117,7 @@ enum EndpointPath: Codable {
case .liveLocations: return "users/live_locations"

case .channels: return "channels"
case .groupedChannels: return "channels/grouped"
case let .createChannel(queryString): return "channels/\(queryString)/query"
case let .updateChannel(queryString): return "channels/\(queryString)/query"
case let .deleteChannel(payloadPath): return "channels/\(payloadPath)"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,24 +7,128 @@ import Foundation
struct ChannelListPayload {
/// A list of channels response (see `ChannelQuery`).
let channels: [ChannelPayload]

/// Server-resolved predefined filter, present only when the query was made with a predefined filter.
let predefinedFilter: PredefinedFilterPayload?

init(channels: [ChannelPayload], predefinedFilter: PredefinedFilterPayload? = nil) {
self.channels = channels
self.predefinedFilter = predefinedFilter
}
}

extension ChannelListPayload: Decodable {
enum CodingKeys: String, CodingKey {
case channels
case predefinedFilter = "predefined_filter"
}

init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let channels = try container
.decodeArrayIgnoringFailures([ChannelPayload].self, forKey: .channels)
let predefinedFilter = try container
.decodeIfPresent(PredefinedFilterPayload.self, forKey: .predefinedFilter)

self.init(
channels: channels
channels: channels,
predefinedFilter: predefinedFilter
)
}
}

final class PredefinedFilterPayload: Decodable, Sendable {
let name: String
let filter: [String: RawJSON]
let sort: [[String: RawJSON]]

init(name: String, filter: [String: RawJSON], sort: [[String: RawJSON]]) {
self.name = name
self.filter = filter
self.sort = sort
}
}

final class GroupedQueryChannelsRequestBody: Encodable, Sendable {
let limit: Int?
let groups: [String: GroupedQueryChannelsRequestGroup]?
let watch: Bool
let presence: Bool

init(
limit: Int?,
groups: [String: GroupedQueryChannelsRequestGroup]?,
watch: Bool,
presence: Bool
) {
self.limit = limit
self.groups = groups
self.watch = watch
self.presence = presence
}
}

final class GroupedQueryChannelsRequestGroup: Encodable, Sendable {
let limit: Int?
let next: String?

init(limit: Int?, next: String?) {
self.limit = limit
self.next = next
}
}

final class GroupedQueryChannelsPayload: Decodable, Sendable {
let groups: [String: GroupedQueryChannelsGroupPayload]

init(groups: [String: GroupedQueryChannelsGroupPayload]) {
self.groups = groups
}

enum CodingKeys: String, CodingKey {
case groups
}

required init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
groups = try container.decode([String: GroupedQueryChannelsGroupPayload].self, forKey: .groups)
}
}

final class GroupedQueryChannelsGroupPayload: Decodable, Sendable {
let channels: [ChannelPayload]
let unreadChannels: Int
let next: String?
let prev: String?

init(
channels: [ChannelPayload],
unreadChannels: Int,
next: String? = nil,
prev: String? = nil
) {
self.channels = channels
self.unreadChannels = unreadChannels
self.next = next
self.prev = prev
}

enum CodingKeys: String, CodingKey {
case channels
case unreadChannels = "unread_channels"
case next
case prev
}

required init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
channels = try container.decodeArrayIgnoringFailures([ChannelPayload].self, forKey: .channels)
unreadChannels = try container.decodeIfPresent(Int.self, forKey: .unreadChannels) ?? 0
next = try container.decodeIfPresent(String.self, forKey: .next)
prev = try container.decodeIfPresent(String.self, forKey: .prev)
}
}

struct ChannelPayload {
let channel: ChannelDetailPayload

Expand Down
Loading
Loading