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: 0 additions & 1 deletion .github/actions/setup-ios-runtime/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ runs:
shell: bash
run: |
sudo rm -rfv ~/Library/Developer/CoreSimulator/* || true
brew install blacktop/tap/ipsw
bundle exec fastlane install_runtime ios:${{ inputs.version }}
sudo rm -rfv *.dmg || true
xcrun simctl list runtimes
Expand Down
2 changes: 2 additions & 0 deletions .github/workflows/cron-checks.yml
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ jobs:
env:
INSTALL_ALLURE: true
INSTALL_YEETD: true
INSTALL_IPSW: true
SKIP_MINT_BOOTSTRAP: true
- uses: ./.github/actions/setup-ios-runtime
if: ${{ matrix.setup_runtime }}
Expand Down Expand Up @@ -146,6 +147,7 @@ jobs:
- uses: ./.github/actions/bootstrap
env:
INSTALL_YEETD: true
INSTALL_IPSW: true
- uses: ./.github/actions/setup-ios-runtime
if: ${{ matrix.setup_runtime }}
timeout-minutes: 60
Expand Down
18 changes: 18 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,24 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).

### πŸ”„ Changed

# [4.78.0](https://github.com/GetStream/stream-chat-swift/releases/tag/4.78.0)
_April 24, 2025_

## StreamChat
### βœ… Added
- Add `CurrentUserUnreads.totalUnreadMessagesCount` [#3651](https://github.com/GetStream/stream-chat-swift/pull/3651)
### 🐞 Fixed
- Fix `FilterKey.id` not returning any channels in `ChannelListQuery` [#3643](https://github.com/GetStream/stream-chat-swift/pull/3643)
- Fix incorrect channel list sorting when sorted by `.hasUnread` [#3646](https://github.com/GetStream/stream-chat-swift/pull/3646)
- Fix `CurrentUserUnreads.totalUnreadChannelsCount` with incorrect value [#3651](https://github.com/GetStream/stream-chat-swift/pull/3651)
- Fix `unsetProperties` not having any effect in `CurrentUserController.updateUserData()` [#3650](https://github.com/GetStream/stream-chat-swift/pull/3650)
### πŸ”„ Changed
- Change the `teamsRole` parameter from `[String: String]` to `[TeamId: UserRole]` [#3650](https://github.com/GetStream/stream-chat-swift/pull/3650)

## StreamChatUI
### 🐞 Fixed
- Fix message search with empty avatars [#3644](https://github.com/GetStream/stream-chat-swift/pull/3644)

# [4.77.0](https://github.com/GetStream/stream-chat-swift/releases/tag/4.77.0)
_April 09, 2025_

Expand Down
15 changes: 5 additions & 10 deletions DemoApp/StreamChat/Components/DemoChatChannelListRouter.swift
Original file line number Diff line number Diff line change
Expand Up @@ -651,23 +651,18 @@ final class DemoChatChannelListRouter: ChatChannelListRouter {
}
}),
.init(title: "Reset User Image", handler: { [unowned self] _ in
do {
let connectedUser = try self.rootViewController.controller.client.makeConnectedUser()
Task {
do {
try await connectedUser.update(unset: ["image"])
} catch {
channelController.client.currentUserController()
.updateUserData(unsetProperties: ["image"]) { [unowned self] error in
if let error {
self.rootViewController.presentAlert(title: error.localizedDescription)
}
}
} catch {
self.rootViewController.presentAlert(title: error.localizedDescription)
}
}),
.init(title: "Add a team role for the current user", isEnabled: true, handler: { [unowned self] _ in
self.rootViewController.presentAlert(title: "Enter the team role", textFieldPlaceholder: "Enter role") { role in
if let role, !role.isEmpty {
client.currentUserController().updateUserData(teamsRole: ["ios": role]) { error in
let userRole = UserRole(rawValue: role)
client.currentUserController().updateUserData(teamsRole: ["ios": userRole]) { error in
if let error {
log.error("Couldn't add role to custom team for the current user: \(error)")
}
Expand Down
49 changes: 38 additions & 11 deletions DemoApp/StreamChat/Components/DemoChatChannelListVC.swift
Original file line number Diff line number Diff line change
Expand Up @@ -41,10 +41,23 @@ final class DemoChatChannelListVC: ChatChannelListVC {
.equal(.hidden, to: true)
]))

lazy var unreadChannelsQuery: ChannelListQuery = .init(filter: .and([
.containMembers(userIds: [currentUserId]),
.hasUnread
]), sort: [.init(key: .unreadCount, isAscending: false)])
lazy var unreadCountChannelsQuery: ChannelListQuery = .init(
filter: .and([
.containMembers(userIds: [currentUserId]),
.hasUnread
]),
sort: [.init(key: .unreadCount, isAscending: false)]
)

lazy var sortedByHasUnreadChannelsQuery: ChannelListQuery = .init(
filter: .and([
.containMembers(userIds: [currentUserId])
]),
sort: [
.init(key: .hasUnread, isAscending: false),
.init(key: .updatedAt, isAscending: false)
]
)

lazy var mutedChannelsQuery: ChannelListQuery = .init(filter: .and([
.containMembers(userIds: [currentUserId]),
Expand Down Expand Up @@ -132,12 +145,21 @@ final class DemoChatChannelListVC: ChatChannelListVC {
}
)

let unreadChannelsAction = UIAlertAction(
title: "Unread Channels",
let unreadCountChannelsAction = UIAlertAction(
title: "Unread Count Channels",
style: .default,
handler: { [weak self] _ in
self?.title = "Unread Count Channels"
self?.setUnreadCountChannelsQuery()
}
)

let hasUnreadChannelsAction = UIAlertAction(
title: "Sorted hasUnread Channels",
style: .default,
handler: { [weak self] _ in
self?.title = "Unread Channels"
self?.setUnreadChannelsQuery()
self?.title = "Sorted hasUnread Channels"
self?.setSortedByHasUnreadChannelsQuery()
}
)

Expand Down Expand Up @@ -187,7 +209,8 @@ final class DemoChatChannelListVC: ChatChannelListVC {
title: "Filter Channels",
actions: [
defaultChannelsAction,
unreadChannelsAction,
unreadCountChannelsAction,
hasUnreadChannelsAction,
hiddenChannelsAction,
mutedChannelsAction,
coolChannelsAction,
Expand All @@ -204,8 +227,12 @@ final class DemoChatChannelListVC: ChatChannelListVC {
replaceQuery(hiddenChannelsQuery)
}

func setUnreadChannelsQuery() {
replaceQuery(unreadChannelsQuery)
func setUnreadCountChannelsQuery() {
replaceQuery(unreadCountChannelsQuery)
}

func setSortedByHasUnreadChannelsQuery() {
replaceQuery(sortedByHasUnreadChannelsQuery)
}

func setMutedChannelsQuery() {
Expand Down
2 changes: 1 addition & 1 deletion Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -295,7 +295,7 @@ GEM
netrc (0.11.0)
nio4r (2.7.3)
nkf (0.2.0)
nokogiri (1.18.4)
nokogiri (1.18.8)
mini_portile2 (~> 2.8.2)
racc (~> 1.4)
octokit (9.1.0)
Expand Down
1 change: 1 addition & 0 deletions Githubfile
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@ export YEETD_VERSION='1.0'
export GCLOUD_VERSION='464.0.0'
export MINT_VERSION='0.17.5'
export SONAR_VERSION='6.2.1.4610'
export IPSW_VERSION='3.1.592'
11 changes: 10 additions & 1 deletion Scripts/bootstrap.sh
Original file line number Diff line number Diff line change
Expand Up @@ -87,4 +87,13 @@ if [[ ${INSTALL_GCLOUD-default} == true ]]; then
gcloud auth activate-service-account --key-file="./fastlane/gcloud-service-account-key.json"
gcloud config set project stream-chat-swift
gcloud services enable toolresults.googleapis.com
fi
fi

if [[ ${INSTALL_IPSW-default} == true ]]; then
puts "Install ipsw v${IPSW_VERSION}"
FILE="ipsw_${IPSW_VERSION}_macOS_universal.tar.gz"
wget "https://github.com/blacktop/ipsw/releases/download/v${IPSW_VERSION}/${FILE}"
tar -xzf "$FILE"
chmod +x ipsw
sudo mv ipsw /usr/local/bin/
fi
Original file line number Diff line number Diff line change
Expand Up @@ -173,14 +173,14 @@ struct UserUpdateRequestBody: Encodable {
let privacySettings: UserPrivacySettingsPayload?
let role: UserRole?
let extraData: [String: RawJSON]?
let teamsRole: [String: String]?
let teamsRole: [TeamId: UserRole]?

init(
name: String?,
imageURL: URL?,
privacySettings: UserPrivacySettingsPayload?,
role: UserRole?,
teamsRole: [String: String]?,
teamsRole: [TeamId: UserRole]?,
extraData: [String: RawJSON]?
) {
self.name = name
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,7 @@ public extension CurrentChatUserController {
///
/// By default all data is `nil`, and it won't be updated unless a value is provided.
///
/// - Note: This operation does a partial user update which keeps existing data if not modified. Use ``unset`` for clearing the existing state.
/// - Note: This operation does a partial user update which keeps existing data if not modified. Use ``unsetProperties`` for clearing the existing state.
///
/// - Parameters:
/// - name: Optionally provide a new name to be updated.
Expand All @@ -194,14 +194,14 @@ public extension CurrentChatUserController {
/// - role: The role for the user.
/// - teamsRole: The role for the user in a specific team. Example: `["teamId": "role"]`.
/// - userExtraData: Optionally provide new user extra data to be updated.
/// - unset: Existing values for specified properties are removed. For example, `image` or `name`.
/// - unsetProperties: Remove existing properties from the user. For example, `image` or `name`.
/// - completion: Called when user is successfuly updated, or with error.
func updateUserData(
name: String? = nil,
imageURL: URL? = nil,
privacySettings: UserPrivacySettings? = nil,
role: UserRole? = nil,
teamsRole: [String: String]? = nil,
teamsRole: [TeamId: UserRole]? = nil,
userExtraData: [String: RawJSON] = [:],
unsetProperties: Set<String> = [],
completion: ((Error?) -> Void)? = nil
Expand All @@ -218,7 +218,8 @@ public extension CurrentChatUserController {
privacySettings: privacySettings,
role: role,
teamsRole: teamsRole,
userExtraData: userExtraData
userExtraData: userExtraData,
unset: unsetProperties
) { error in
self.callback {
completion?(error)
Expand All @@ -232,7 +233,7 @@ public extension CurrentChatUserController {
///
/// - Parameters:
/// - extraData: The additional data to be added to the member object.
/// - unsetProperties: The custom properties to be removed from the member object.
/// - unsetProperties: The properties to be removed from the member object.
/// - channelId: The channel where the member data is updated.
/// - completion: Returns the updated member object or an error if the update fails.
func updateMemberData(
Expand Down
13 changes: 11 additions & 2 deletions Sources/StreamChat/Database/DTOs/ChannelDTO.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ import Foundation

@objc(ChannelDTO)
class ChannelDTO: NSManagedObject {
// The cid without the channel type.
@NSManaged var id: String?
// The channel id which includes channelType:channelId.
@NSManaged var cid: String
@NSManaged var name: String?
@NSManaged var imageURL: URL?
Expand Down Expand Up @@ -62,7 +65,11 @@ class ChannelDTO: NSManagedObject {
@NSManaged var messages: Set<MessageDTO>
@NSManaged var pinnedMessages: Set<MessageDTO>
@NSManaged var reads: Set<ChannelReadDTO>

/// Helper properties used for sorting channels with unread counts of the current user.
@NSManaged var currentUserUnreadMessagesCount: Int32
@NSManaged var hasUnreadSorting: Int16

@NSManaged var watchers: Set<UserDTO>
@NSManaged var memberListQueries: Set<ChannelMemberListQueryDTO>
@NSManaged var previewMessage: MessageDTO?
Expand All @@ -79,12 +86,13 @@ class ChannelDTO: NSManagedObject {
}

// Update the unreadMessagesCount for the current user.
// At the moment this computed property is used for `hasUnread` automatic channel list filtering.
// At the moment this computed property is used for `hasUnread` and `unreadCount` automatic channel list filtering.
if let currentUserId = managedObjectContext?.currentUser?.user.id {
let currentUserUnread = reads.first(where: { $0.user.id == currentUserId })
let newUnreadCount = currentUserUnread?.unreadMessageCount ?? 0
if newUnreadCount != currentUserUnreadMessagesCount {
currentUserUnreadMessagesCount = newUnreadCount
hasUnreadSorting = newUnreadCount > 0 ? 1 : 0
}
}

Expand All @@ -105,7 +113,7 @@ class ChannelDTO: NSManagedObject {
newestMessageAt = nil
}
}

// Update the date for sorting every time new message in this channel arrive.
// This will ensure that the channel list is updated/sorted when new message arrives.
// Note: If a channel is truncated, the server will update the lastMessageAt to a minimum value, and not remove it.
Expand Down Expand Up @@ -240,6 +248,7 @@ extension NSManagedObjectContext {
dto.extraData = Data()
}
dto.typeRawValue = payload.typeRawValue
dto.id = payload.cid.id
dto.config = payload.config.asDTO(context: self, cid: dto.cid)
if let ownCapabilities = payload.ownCapabilities {
dto.ownCapabilities = ownCapabilities
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="23605" systemVersion="24A348" minimumToolsVersion="Automatic" sourceLanguage="Swift" userDefinedModelVersionIdentifier="">
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="23507" systemVersion="24B83" 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 @@ -44,6 +44,8 @@
<attribute name="defaultSortingAt" attributeType="Date" usesScalarValueType="NO" spotlightIndexingEnabled="YES"/>
<attribute name="deletedAt" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
<attribute name="extraData" attributeType="Binary"/>
<attribute name="hasUnreadSorting" attributeType="Integer 16" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="id" optional="YES" attributeType="String"/>
<attribute name="imageURL" optional="YES" attributeType="URI"/>
<attribute name="isBlocked" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
<attribute name="isDisabled" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,5 @@ import Foundation

extension SystemEnvironment {
/// A Stream Chat version.
public static let version: String = "4.77.0"
public static let version: String = "4.78.0"
}
2 changes: 1 addition & 1 deletion Sources/StreamChat/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
<key>CFBundlePackageType</key>
<string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
<key>CFBundleShortVersionString</key>
<string>4.77.0</string>
<string>4.78.0</string>
<key>CFBundleVersion</key>
<string>$(CURRENT_PROJECT_VERSION)</string>
</dict>
Expand Down
18 changes: 11 additions & 7 deletions Sources/StreamChat/Models/CurrentUser.swift
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,8 @@ public class CurrentChatUser: ChatUser {

/// The total unread information from the current user.
public struct CurrentUserUnreads {
/// The total number of unread messages.
public let totalUnreadMessagesCount: Int
/// The total number of unread channels.
public let totalUnreadChannelsCount: Int
/// The total number of unread threads.
Expand Down Expand Up @@ -166,14 +168,16 @@ public struct UnreadThread {

extension CurrentUserUnreadsPayload {
func asModel() -> CurrentUserUnreads {
CurrentUserUnreads(
totalUnreadChannelsCount: totalUnreadCount,
let unreadChannels: [UnreadChannel] = channels.map { .init(
channelId: $0.channelId,
unreadMessagesCount: $0.unreadCount,
lastRead: $0.lastRead
) }
return CurrentUserUnreads(
totalUnreadMessagesCount: totalUnreadCount,
totalUnreadChannelsCount: unreadChannels.count,
totalUnreadThreadsCount: totalUnreadThreadsCount,
unreadChannels: channels.map { .init(
channelId: $0.channelId,
unreadMessagesCount: $0.unreadCount,
lastRead: $0.lastRead
) },
unreadChannels: unreadChannels,
unreadThreads: threads.map { .init(
parentMessageId: $0.parentMessageId,
unreadRepliesCount: $0.unreadCount,
Expand Down
5 changes: 4 additions & 1 deletion Sources/StreamChat/Query/ChannelListQuery.swift
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,10 @@ public extension FilterKey where Scope: AnyChannelListFilterScope {
/// Supported operators: `in`, `equal`
/// - Warning: Querying by the channel Identifier should be done using the `cid` field as much as possible to optimize API performance.
/// As the full channel ID, `cid`s are indexed everywhere in Stream database where `id` is not.
static var id: FilterKey<Scope, String> { .init(rawValue: "id", keyPathString: #keyPath(ChannelDTO.cid)) }
static var id: FilterKey<Scope, String> { .init(
rawValue: "id",
keyPathString: #keyPath(ChannelDTO.id)
) }

/// A filter key for matching the `name` value.
static var name: FilterKey<Scope, String> { .init(rawValue: "name", keyPathString: #keyPath(ChannelDTO.name)) }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ public struct ChannelListSortingKey: SortingKey, Equatable {
/// **Note:** If you want to sort by number of unreads, you should use the `unreadCount` sorting key.
public static let hasUnread = Self(
keyPath: \.hasUnread,
localKey: nil,
localKey: #keyPath(ChannelDTO.hasUnreadSorting),
remoteKey: "has_unread"
)

Expand Down
2 changes: 1 addition & 1 deletion Sources/StreamChat/StateLayer/ConnectedUser.swift
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ public final class ConnectedUser {
imageURL: URL? = nil,
privacySettings: UserPrivacySettings? = nil,
role: UserRole? = nil,
teamRoles: [String: String]? = nil,
teamRoles: [TeamId: UserRole]? = nil,
extraData: [String: RawJSON] = [:],
unset: Set<String> = []
) async throws {
Expand Down
Loading
Loading