From e379ea0f6650910afee3c292e8d597db16d7b3d0 Mon Sep 17 00:00:00 2001 From: Nuno Vieira Date: Wed, 5 Nov 2025 21:04:37 +0000 Subject: [PATCH 01/10] Fix duplicated symbols for MessageDeliveryCriteriaValidator_Mock (#3871) --- ...essageDeliveryCriteriaValidator_Mock.swift | 36 ------------------- 1 file changed, 36 deletions(-) delete mode 100644 TestTools/StreamChatTestTools/Mocks/StreamChat/Models/MessageDeliveryCriteriaValidator_Mock.swift diff --git a/TestTools/StreamChatTestTools/Mocks/StreamChat/Models/MessageDeliveryCriteriaValidator_Mock.swift b/TestTools/StreamChatTestTools/Mocks/StreamChat/Models/MessageDeliveryCriteriaValidator_Mock.swift deleted file mode 100644 index cb6447923b..0000000000 --- a/TestTools/StreamChatTestTools/Mocks/StreamChat/Models/MessageDeliveryCriteriaValidator_Mock.swift +++ /dev/null @@ -1,36 +0,0 @@ -// -// Copyright © 2025 Stream.io Inc. All rights reserved. -// - -import Foundation -@testable import StreamChat - -/// A mock implementation of `MessageDeliveryCriteriaValidating` for testing purposes. -final class MessageDeliveryCriteriaValidator_Mock: MessageDeliveryCriteriaValidating { - var canMarkMessageAsDeliveredClosure: ((ChatMessage, CurrentChatUser, ChatChannel) -> Bool)? - var canMarkMessageAsDeliveredCallCount = 0 - var canMarkMessageAsDeliveredCalledWithMessage: ChatMessage? - var canMarkMessageAsDeliveredCalledWithCurrentUser: CurrentChatUser? - var canMarkMessageAsDeliveredCalledWithChannel: ChatChannel? - - func canMarkMessageAsDelivered( - _ message: ChatMessage, - for currentUser: CurrentChatUser, - in channel: ChatChannel - ) -> Bool { - canMarkMessageAsDeliveredCallCount += 1 - canMarkMessageAsDeliveredCalledWithMessage = message - canMarkMessageAsDeliveredCalledWithCurrentUser = currentUser - canMarkMessageAsDeliveredCalledWithChannel = channel - - return canMarkMessageAsDeliveredClosure?(message, currentUser, channel) ?? false - } - - func reset() { - canMarkMessageAsDeliveredClosure = nil - canMarkMessageAsDeliveredCallCount = 0 - canMarkMessageAsDeliveredCalledWithMessage = nil - canMarkMessageAsDeliveredCalledWithCurrentUser = nil - canMarkMessageAsDeliveredCalledWithChannel = nil - } -} From ca321e717026b3dc8ce935eade28ec30c1d140c0 Mon Sep 17 00:00:00 2001 From: Nuno Vieira Date: Wed, 5 Nov 2025 22:58:56 +0000 Subject: [PATCH 02/10] Update CHANGELOG.md --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3140c7de0a..864ae6e7bd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). ## StreamChat ### ✅ Added - Add support for message delivered info [#3846](https://github.com/GetStream/stream-chat-swift/pull/3846) - - Add `ChatRemoteNotificationHandler.markMessageAsDelivered(deliveries:)` + - Add `ChatRemoteNotificationHandler.markMessageAsDelivered(message:channel:)` - Add `ChatChannel.reads(message:)` and `ChatChannel.deliveredReads(message:)` - Add `ChatChannelRead.lastDeliveredAt` - Add `ChatChannelRead.lastDeliveredMessageId` From bb94828f40fde2ee779bd58baa0e19a247997276 Mon Sep 17 00:00:00 2001 From: Nuno Vieira Date: Fri, 7 Nov 2025 12:37:42 +0000 Subject: [PATCH 03/10] Add an example to the demo app to upload a global image to change the user avatar (#3870) --- .../UserProfileViewController.swift | 142 +++++++++++++++++- 1 file changed, 140 insertions(+), 2 deletions(-) diff --git a/DemoApp/Screens/UserProfile/UserProfileViewController.swift b/DemoApp/Screens/UserProfile/UserProfileViewController.swift index 73bd527203..c8d1e8d764 100644 --- a/DemoApp/Screens/UserProfile/UserProfileViewController.swift +++ b/DemoApp/Screens/UserProfile/UserProfileViewController.swift @@ -6,9 +6,10 @@ import StreamChat import SwiftUI import UIKit -class UserProfileViewController: UITableViewController, CurrentChatUserControllerDelegate { +class UserProfileViewController: UITableViewController, CurrentChatUserControllerDelegate, UIImagePickerControllerDelegate, UINavigationControllerDelegate { private let imageView = UIImageView() private let updateButton = UIButton() + private let loadingSpinner = UIActivityIndicatorView(style: .medium) var name: String? let properties = UserProperty.allCases @@ -42,28 +43,39 @@ class UserProfileViewController: UITableViewController, CurrentChatUserControlle tableView.allowsSelection = false view.backgroundColor = .systemBackground - [imageView, updateButton].forEach { + [imageView, updateButton, loadingSpinner].forEach { $0.translatesAutoresizingMaskIntoConstraints = false } tableView.tableHeaderView = UIView(frame: .init(origin: .zero, size: .init(width: .zero, height: 80))) tableView.tableHeaderView?.addSubview(imageView) + tableView.tableHeaderView?.addSubview(loadingSpinner) tableView.tableFooterView = UIView(frame: .init(origin: .zero, size: .init(width: .zero, height: 80))) tableView.tableFooterView?.addSubview(updateButton) imageView.contentMode = .scaleAspectFill imageView.layer.cornerRadius = 30 imageView.layer.masksToBounds = true + imageView.isUserInteractionEnabled = true + + let tapGesture = UITapGestureRecognizer(target: self, action: #selector(didTapImageView)) + imageView.addGestureRecognizer(tapGesture) + updateButton.setTitle("Update", for: .normal) updateButton.layer.cornerRadius = 4 updateButton.backgroundColor = .systemBlue updateButton.contentEdgeInsets = UIEdgeInsets(top: 0.0, left: 15, bottom: 0.0, right: 15) updateButton.addTarget(self, action: #selector(didTapUpdateButton), for: .touchUpInside) + + loadingSpinner.hidesWhenStopped = true + loadingSpinner.color = .systemGray NSLayoutConstraint.activate([ imageView.widthAnchor.constraint(equalToConstant: 60), imageView.heightAnchor.constraint(equalTo: imageView.widthAnchor), imageView.centerXAnchor.constraint(equalTo: view.centerXAnchor), + loadingSpinner.centerXAnchor.constraint(equalTo: imageView.centerXAnchor), + loadingSpinner.centerYAnchor.constraint(equalTo: imageView.centerYAnchor), updateButton.centerXAnchor.constraint(equalTo: view.centerXAnchor), updateButton.heightAnchor.constraint(equalToConstant: 35), updateButton.centerYAnchor.constraint(equalTo: updateButton.superview!.centerYAnchor) @@ -240,4 +252,130 @@ class UserProfileViewController: UITableViewController, CurrentChatUserControlle label.sizeToFit() return label } + + // MARK: - Avatar Change + + @objc private func didTapImageView() { + let alertController = UIAlertController(title: "Change Avatar", message: nil, preferredStyle: .actionSheet) + + if UIImagePickerController.isSourceTypeAvailable(.camera) { + alertController.addAction(UIAlertAction(title: "Take Photo", style: .default) { [weak self] _ in + self?.presentImagePicker(sourceType: .camera) + }) + } + + alertController.addAction(UIAlertAction(title: "Choose from Library", style: .default) { [weak self] _ in + self?.presentImagePicker(sourceType: .photoLibrary) + }) + + alertController.addAction(UIAlertAction(title: "Cancel", style: .cancel)) + + if let popover = alertController.popoverPresentationController { + popover.sourceView = imageView + popover.sourceRect = imageView.bounds + } + + present(alertController, animated: true) + } + + private func presentImagePicker(sourceType: UIImagePickerController.SourceType) { + let picker = UIImagePickerController() + picker.sourceType = sourceType + picker.delegate = self + picker.allowsEditing = true + present(picker, animated: true) + } + + // MARK: - UIImagePickerControllerDelegate + + func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey: Any]) { + picker.dismiss(animated: true) + + guard let selectedImage = (info[.editedImage] as? UIImage) ?? (info[.originalImage] as? UIImage) else { + return + } + + loadingSpinner.startAnimating() + + uploadImageAndUpdateProfile(selectedImage) { [weak self] error in + self?.loadingSpinner.stopAnimating() + if let error = error { + self?.showError(error) + } else { + self?.showSuccess() + } + } + } + + func imagePickerControllerDidCancel(_ picker: UIImagePickerController) { + picker.dismiss(animated: true) + } + + private func uploadImageAndUpdateProfile(_ image: UIImage, completion: @escaping (Error?) -> Void) { + guard let imageData = image.pngData() else { + completion(NSError(domain: "UserProfile", code: -1, userInfo: [NSLocalizedDescriptionKey: "Failed to convert image to PNG data"])) + return + } + + // Create temporary file + let imageURL = URL(fileURLWithPath: NSTemporaryDirectory()).appendingPathComponent("avatar_\(UUID().uuidString).png") + + do { + try imageData.write(to: imageURL) + } catch { + completion(error) + return + } + + let uploadingState = AttachmentUploadingState( + localFileURL: imageURL, + state: .pendingUpload, + file: .init(type: .png, size: Int64(imageData.count), mimeType: "image/png") + ) + + let attachment = StreamAttachment( + type: .image, + payload: imageData, + downloadingState: nil, + uploadingState: uploadingState + ) + + // Upload the image + currentUserController.client.upload(attachment, progress: { progress in + print("Upload progress: \(progress)") + }, completion: { [weak self] result in + // Clean up temporary file + try? FileManager.default.removeItem(at: imageURL) + + switch result { + case .success(let file): + // Update user profile with new image URL + self?.currentUserController.updateUserData(imageURL: file.fileURL) { error in + completion(error) + } + case .failure(let error): + completion(error) + } + }) + } + + private func showError(_ error: Error) { + let alert = UIAlertController( + title: "Upload Failed", + message: error.localizedDescription, + preferredStyle: .alert + ) + alert.addAction(UIAlertAction(title: "OK", style: .default)) + present(alert, animated: true) + } + + private func showSuccess() { + let alert = UIAlertController( + title: "Success", + message: "Avatar updated successfully!", + preferredStyle: .alert + ) + alert.addAction(UIAlertAction(title: "OK", style: .default)) + present(alert, animated: true) + } } From 1594c1a542022eab47ddec2d0b37924193ec14ea Mon Sep 17 00:00:00 2001 From: Stream Bot Date: Fri, 7 Nov 2025 13:33:16 +0000 Subject: [PATCH 04/10] Update release version to snapshot --- Sources/StreamChat/Generated/SystemEnvironment+Version.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/StreamChat/Generated/SystemEnvironment+Version.swift b/Sources/StreamChat/Generated/SystemEnvironment+Version.swift index 5db32bbee3..fc8290c558 100644 --- a/Sources/StreamChat/Generated/SystemEnvironment+Version.swift +++ b/Sources/StreamChat/Generated/SystemEnvironment+Version.swift @@ -7,5 +7,5 @@ import Foundation extension SystemEnvironment { /// A Stream Chat version. - public static let version: String = "4.92.0" + public static let version: String = "4.93.0-SNAPSHOT" } From e6c940e3209570998ec1c934da9013dff7b8f72d Mon Sep 17 00:00:00 2001 From: Alexey Alter-Pesotskiy Date: Mon, 10 Nov 2025 12:21:36 +0000 Subject: [PATCH 05/10] [CI] Bump xcsize plugin version (#3872) --- Gemfile.lock | 8 ++++---- fastlane/Pluginfile | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index 8d78597140..83f8a70577 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -212,8 +212,8 @@ GEM fastlane-plugin-stream_actions (0.3.101) xctest_list (= 1.2.1) fastlane-plugin-versioning (0.7.1) - fastlane-plugin-xcsize (1.1.0) - xcsize (= 1.1.0) + fastlane-plugin-xcsize (1.2.0) + xcsize (= 1.2.0) fastlane-sirp (1.0.0) sysrandom (~> 1.0) faye-websocket (0.12.0) @@ -433,7 +433,7 @@ GEM rouge (~> 3.28.0) xcpretty-travis-formatter (1.0.1) xcpretty (~> 0.2, >= 0.0.7) - xcsize (1.1.0) + xcsize (1.2.0) commander (>= 4.6, < 6.0) xctest_list (1.2.1) @@ -451,7 +451,7 @@ DEPENDENCIES fastlane-plugin-sonarcloud_metric_kit fastlane-plugin-stream_actions (= 0.3.101) fastlane-plugin-versioning - fastlane-plugin-xcsize (= 1.1.0) + fastlane-plugin-xcsize (= 1.2.0) faye-websocket json lefthook diff --git a/fastlane/Pluginfile b/fastlane/Pluginfile index 40535271b0..9f8b4d3d92 100644 --- a/fastlane/Pluginfile +++ b/fastlane/Pluginfile @@ -6,4 +6,4 @@ gem 'fastlane-plugin-versioning' gem 'fastlane-plugin-create_xcframework' gem 'fastlane-plugin-sonarcloud_metric_kit' gem 'fastlane-plugin-stream_actions', '0.3.101' -gem 'fastlane-plugin-xcsize', '1.1.0' +gem 'fastlane-plugin-xcsize', '1.2.0' From 1f9b6c3dc9587de1ae9929d13ed6b22542873cf0 Mon Sep 17 00:00:00 2001 From: Nuno Vieira Date: Thu, 13 Nov 2025 16:23:29 +0000 Subject: [PATCH 06/10] Remove timestamp from demo app message reads info view (#3877) --- .../DemoMessageReadsInfoView.swift | 29 ++----------------- 1 file changed, 2 insertions(+), 27 deletions(-) diff --git a/DemoApp/StreamChat/Components/DeliveredMessages/DemoMessageReadsInfoView.swift b/DemoApp/StreamChat/Components/DeliveredMessages/DemoMessageReadsInfoView.swift index bf6386d905..d42fc402a4 100644 --- a/DemoApp/StreamChat/Components/DeliveredMessages/DemoMessageReadsInfoView.swift +++ b/DemoApp/StreamChat/Components/DeliveredMessages/DemoMessageReadsInfoView.swift @@ -29,8 +29,7 @@ struct DemoMessageReadsInfoView: View { ForEach(deliveredUsers, id: \.id) { user in UserReadInfoRow( user: user, - status: .delivered, - timestamp: getDeliveredTimestamp(for: user) + status: .delivered ) } } @@ -41,8 +40,7 @@ struct DemoMessageReadsInfoView: View { ForEach(readUsers, id: \.id) { user in UserReadInfoRow( user: user, - status: .read, - timestamp: getReadTimestamp(for: user) + status: .read ) } } @@ -135,7 +133,6 @@ struct DemoMessageReadsInfoView: View { struct UserReadInfoRow: View { let user: ChatUser let status: ReadStatus - let timestamp: Date? enum ReadStatus { case delivered @@ -184,12 +181,6 @@ struct UserReadInfoRow: View { Text(user.name ?? user.id) .font(.headline) .foregroundColor(.primary) - - if let timestamp = timestamp { - Text(formatTimestamp(timestamp)) - .font(.caption) - .foregroundColor(.secondary) - } } Spacer() @@ -201,20 +192,4 @@ struct UserReadInfoRow: View { } .padding(.vertical, 4) } - - private func formatTimestamp(_ date: Date) -> String { - let formatter = DateFormatter() - formatter.dateStyle = .none - formatter.timeStyle = .short - - let calendar = Calendar.current - if calendar.isDateInToday(date) { - return "Today at \(formatter.string(from: date))" - } else if calendar.isDateInYesterday(date) { - return "Yesterday at \(formatter.string(from: date))" - } else { - formatter.dateStyle = .short - return formatter.string(from: date) - } - } } From 32ddf8100a318ab860c7d0f56276c787d5fce733 Mon Sep 17 00:00:00 2001 From: Alexey Alter-Pesotskiy Date: Fri, 14 Nov 2025 12:08:01 +0000 Subject: [PATCH 07/10] [CI] Introduce Backend Integration Tests (#3876) --- .github/workflows/backend-checks.yml | 41 ++++++++++++ StreamChat.xcodeproj/project.pbxproj | 14 ++++ .../xcschemes/StreamChatUITestsApp.xcscheme | 3 + .../Backend.xctestplan | 28 ++++++++ .../StreamChatUITestsApp.xctestplan | 1 + .../Tests/Backend/Backend_Tests.swift | 67 +++++++++++++++++++ fastlane/Fastfile | 26 +++++++ 7 files changed, 180 insertions(+) create mode 100644 .github/workflows/backend-checks.yml create mode 100644 StreamChatUITestsAppUITests/Backend.xctestplan create mode 100644 StreamChatUITestsAppUITests/Tests/Backend/Backend_Tests.swift diff --git a/.github/workflows/backend-checks.yml b/.github/workflows/backend-checks.yml new file mode 100644 index 0000000000..71be1ae63d --- /dev/null +++ b/.github/workflows/backend-checks.yml @@ -0,0 +1,41 @@ +name: Backend Checks + +on: + workflow_dispatch: + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +env: + HOMEBREW_NO_INSTALL_CLEANUP: 1 # Disable cleanup for homebrew, we don't need it on CI + IOS_SIMULATOR_DEVICE: "iPhone 16 Pro (18.5)" + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + +jobs: + test-backend-integration: + name: Test Backend Integration + runs-on: macos-15 + steps: + - uses: actions/checkout@v4.1.1 + - uses: ./.github/actions/bootstrap + env: + INSTALL_YEETD: true + SKIP_SWIFT_BOOTSTRAP: true + - name: Run UI Tests (Debug) + run: bundle exec fastlane test_e2e device:"${{ env.IOS_SIMULATOR_DEVICE }}" + timeout-minutes: 100 + - name: Parse xcresult + if: failure() + run: | + brew install chargepoint/xcparse/xcparse + xcparse logs fastlane/test_output/StreamChatUITestsApp.xcresult fastlane/test_output/logs/ + - uses: actions/upload-artifact@v4 + if: failure() + with: + name: Test Data Backend Integration + path: | + fastlane/recordings + fastlane/sinatra_log.txt + fastlane/test_output/logs/*/Diagnostics/**/*.txt + fastlane/test_output/logs/*/Diagnostics/simctl_diagnostics/DiagnosticReports/* diff --git a/StreamChat.xcodeproj/project.pbxproj b/StreamChat.xcodeproj/project.pbxproj index 5c5e2b13c0..91f358ec03 100644 --- a/StreamChat.xcodeproj/project.pbxproj +++ b/StreamChat.xcodeproj/project.pbxproj @@ -629,6 +629,7 @@ 8274A7962B7FAC3900D8696B /* ChannelListScrollTime.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8274A7952B7FAC3900D8696B /* ChannelListScrollTime.swift */; }; 8279706F29689680006741A3 /* UserDetails_Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8279706E29689680006741A3 /* UserDetails_Tests.swift */; }; 827DD1A0289D5B3300910AC5 /* MessageActionsVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 827DD19F289D5B3300910AC5 /* MessageActionsVC.swift */; }; + 82865DA42EC4B87B007D7053 /* Backend_Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 82865DA32EC4B874007D7053 /* Backend_Tests.swift */; }; 8292D6DB29B78476007A17D1 /* QuotedReply_Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8292D6DA29B78476007A17D1 /* QuotedReply_Tests.swift */; }; 829762E028C7587500B953E8 /* PushNotification_Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 829762DF28C7587500B953E8 /* PushNotification_Tests.swift */; }; 829CD5C52848C2EA003C3877 /* ParticipantRobot.swift in Sources */ = {isa = PBXBuildFile; fileRef = 829CD5C32848C25F003C3877 /* ParticipantRobot.swift */; }; @@ -3691,6 +3692,8 @@ 8274A7952B7FAC3900D8696B /* ChannelListScrollTime.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChannelListScrollTime.swift; sourceTree = ""; }; 8279706E29689680006741A3 /* UserDetails_Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserDetails_Tests.swift; sourceTree = ""; }; 827DD19F289D5B3300910AC5 /* MessageActionsVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageActionsVC.swift; sourceTree = ""; }; + 82865DA12EC4B84F007D7053 /* Backend.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; name = Backend.xctestplan; path = StreamChatUITestsAppUITests/Backend.xctestplan; sourceTree = ""; }; + 82865DA32EC4B874007D7053 /* Backend_Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Backend_Tests.swift; sourceTree = ""; }; 8292D6DA29B78476007A17D1 /* QuotedReply_Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QuotedReply_Tests.swift; sourceTree = ""; }; 829762DF28C7587500B953E8 /* PushNotification_Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PushNotification_Tests.swift; sourceTree = ""; }; 8298C8E827D22C3E004082D3 /* UserRobot.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserRobot.swift; sourceTree = ""; }; @@ -6312,6 +6315,14 @@ path = Swifter; sourceTree = ""; }; + 82865DA22EC4B86A007D7053 /* Backend */ = { + isa = PBXGroup; + children = ( + 82865DA32EC4B874007D7053 /* Backend_Tests.swift */, + ); + path = Backend; + sourceTree = ""; + }; 829CD5C22848C244003C3877 /* Robots */ = { isa = PBXGroup; children = ( @@ -6370,6 +6381,7 @@ 82AD02BE27D8E453000611B7 /* Tests */ = { isa = PBXGroup; children = ( + 82865DA22EC4B86A007D7053 /* Backend */, 82EBA1822B30A63800B3A048 /* Performance */, A3600B3D283F63C700E1C930 /* Base TestCase */, A3B78F16282A670600348AD1 /* Message Delivery Status */, @@ -6843,6 +6855,7 @@ 8AD5EC8522E9A3E8005CFAC9 = { isa = PBXGroup; children = ( + 82865DA12EC4B84F007D7053 /* Backend.xctestplan */, 4A4E184528D06CA30062378D /* Documentation.docc */, AD9BE32526680E4200A6D284 /* Stream.playground */, 792E3D6A25C97D920040B0C2 /* Package.swift */, @@ -12534,6 +12547,7 @@ 8232B84F28635C4A0032C7DB /* Attachments_Tests.swift in Sources */, 822F266027D9FDB500E454FB /* URLProtocol_Mock.swift in Sources */, 82BA52EF27E1EF7B00951B87 /* MessageList_Tests.swift in Sources */, + 82865DA42EC4B87B007D7053 /* Backend_Tests.swift in Sources */, A33FA818282E559A00DC40E8 /* SlowMode_Tests.swift in Sources */, A39B040B27F196F200D6B18A /* StreamChatUITests.swift in Sources */, 825A32CF27DBB48D000402A9 /* StartPage.swift in Sources */, diff --git a/StreamChat.xcodeproj/xcshareddata/xcschemes/StreamChatUITestsApp.xcscheme b/StreamChat.xcodeproj/xcshareddata/xcschemes/StreamChatUITestsApp.xcscheme index 471c2cbed6..46dea19be2 100644 --- a/StreamChat.xcodeproj/xcshareddata/xcschemes/StreamChatUITestsApp.xcscheme +++ b/StreamChat.xcodeproj/xcshareddata/xcschemes/StreamChatUITestsApp.xcscheme @@ -35,6 +35,9 @@ + + Date: Mon, 17 Nov 2025 16:16:50 +0000 Subject: [PATCH 08/10] Reduce SDK size by converting Events from structs to classes (#3878) * Reduce SDK size by converting Events from struct to classes * Update CHANGELOG.md * Make all events final * Change missing events to classes --- CHANGELOG.md | 4 + .../Events/AITypingEvents.swift | 25 +++- .../Events/ChannelEvents.swift | 43 ++++++- .../Events/ConnectionEvents.swift | 2 +- .../WebSocketClient/Events/DraftEvents.swift | 4 +- .../WebSocketClient/Events/MemberEvents.swift | 26 ++++- .../Events/MessageEvents.swift | 14 +-- .../Events/NotificationEvents.swift | 107 ++++++++++++++++-- .../WebSocketClient/Events/PollsEvents.swift | 52 +++++++-- .../Events/ReactionEvents.swift | 30 ++++- .../Events/ReminderEvents.swift | 8 +- .../WebSocketClient/Events/ThreadEvents.swift | 16 ++- .../WebSocketClient/Events/TypingEvent.swift | 12 +- .../WebSocketClient/Events/UserEvents.swift | 70 ++++++++++-- 14 files changed, 352 insertions(+), 61 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 58a9ec1a05..caa7095bc6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,7 +3,11 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). # Upcoming +## StreamChat +### ⚡️ Performance +- Reduce SDK size by converting Events from structs to classes [#3878](https://github.com/GetStream/stream-chat-swift/pull/3878) ### 🔄 Changed +- Change Events from structs to classes [#3878](https://github.com/GetStream/stream-chat-swift/pull/3878) # [4.92.0](https://github.com/GetStream/stream-chat-swift/releases/tag/4.92.0) _November 05, 2025_ diff --git a/Sources/StreamChat/WebSocketClient/Events/AITypingEvents.swift b/Sources/StreamChat/WebSocketClient/Events/AITypingEvents.swift index 7db59d3e52..c66bd3737a 100644 --- a/Sources/StreamChat/WebSocketClient/Events/AITypingEvents.swift +++ b/Sources/StreamChat/WebSocketClient/Events/AITypingEvents.swift @@ -5,7 +5,7 @@ import Foundation /// An event that provides updates about the state of the AI typing indicator. -public struct AIIndicatorUpdateEvent: Event { +public final class AIIndicatorUpdateEvent: Event { /// The state of the AI typing indicator. public let state: AITypingState /// The channel ID this event is related to. @@ -14,6 +14,13 @@ public struct AIIndicatorUpdateEvent: Event { public let messageId: MessageId? /// Optional server message, usually when an error occurs. public let aiMessage: String? + + init(state: AITypingState, cid: ChannelId?, messageId: MessageId?, aiMessage: String?) { + self.state = state + self.cid = cid + self.messageId = messageId + self.aiMessage = aiMessage + } } class AIIndicatorUpdateEventDTO: EventDTO { @@ -39,9 +46,13 @@ class AIIndicatorUpdateEventDTO: EventDTO { } /// An event that clears the AI typing indicator. -public struct AIIndicatorClearEvent: Event { +public final class AIIndicatorClearEvent: Event { /// The channel ID this event is related to. public let cid: ChannelId? + + init(cid: ChannelId?) { + self.cid = cid + } } class AIIndicatorClearEventDTO: EventDTO { @@ -57,7 +68,7 @@ class AIIndicatorClearEventDTO: EventDTO { } /// An event that indicates the AI has stopped generating the message. -public struct AIIndicatorStopEvent: CustomEventPayload, Event { +public final class AIIndicatorStopEvent: CustomEventPayload, Event { public static var eventType: EventType = .aiTypingIndicatorStop /// The channel ID this event is related to. @@ -66,6 +77,14 @@ public struct AIIndicatorStopEvent: CustomEventPayload, Event { public init(cid: ChannelId?) { self.cid = cid } + + public func hash(into hasher: inout Hasher) { + hasher.combine(cid) + } + + public static func == (lhs: AIIndicatorStopEvent, rhs: AIIndicatorStopEvent) -> Bool { + lhs.cid == rhs.cid + } } class AIIndicatorStopEventDTO: EventDTO { diff --git a/Sources/StreamChat/WebSocketClient/Events/ChannelEvents.swift b/Sources/StreamChat/WebSocketClient/Events/ChannelEvents.swift index bfc09d5007..8bbf8f997c 100644 --- a/Sources/StreamChat/WebSocketClient/Events/ChannelEvents.swift +++ b/Sources/StreamChat/WebSocketClient/Events/ChannelEvents.swift @@ -5,7 +5,7 @@ import Foundation /// Triggered when a channel is updated. -public struct ChannelUpdatedEvent: ChannelSpecificEvent { +public final class ChannelUpdatedEvent: ChannelSpecificEvent { /// The identifier of updated channel. public var cid: ChannelId { channel.cid } @@ -20,6 +20,13 @@ public struct ChannelUpdatedEvent: ChannelSpecificEvent { /// The event timestamp. public let createdAt: Date + + init(channel: ChatChannel, user: ChatUser?, message: ChatMessage?, createdAt: Date) { + self.channel = channel + self.user = user + self.message = message + self.createdAt = createdAt + } } class ChannelUpdatedEventDTO: EventDTO { @@ -53,7 +60,7 @@ class ChannelUpdatedEventDTO: EventDTO { } /// Triggered when a channel is deleted. -public struct ChannelDeletedEvent: ChannelSpecificEvent { +public final class ChannelDeletedEvent: ChannelSpecificEvent { /// The identifier of deleted channel. public var cid: ChannelId { channel.cid } @@ -65,6 +72,12 @@ public struct ChannelDeletedEvent: ChannelSpecificEvent { /// The event timestamp. public let createdAt: Date + + init(channel: ChatChannel, user: ChatUser?, createdAt: Date) { + self.channel = channel + self.user = user + self.createdAt = createdAt + } } class ChannelDeletedEventDTO: EventDTO { @@ -94,7 +107,7 @@ class ChannelDeletedEventDTO: EventDTO { } /// Triggered when a channel is truncated. -public struct ChannelTruncatedEvent: ChannelSpecificEvent { +public final class ChannelTruncatedEvent: ChannelSpecificEvent { /// The identifier of deleted channel. public var cid: ChannelId { channel.cid } @@ -109,6 +122,13 @@ public struct ChannelTruncatedEvent: ChannelSpecificEvent { /// The event timestamp. public let createdAt: Date + + init(channel: ChatChannel, user: ChatUser?, message: ChatMessage?, createdAt: Date) { + self.channel = channel + self.user = user + self.message = message + self.createdAt = createdAt + } } class ChannelTruncatedEventDTO: EventDTO { @@ -142,7 +162,7 @@ class ChannelTruncatedEventDTO: EventDTO { } /// Triggered when a channel is made visible. -public struct ChannelVisibleEvent: ChannelSpecificEvent { +public final class ChannelVisibleEvent: ChannelSpecificEvent { /// The channel identifier. public let cid: ChannelId @@ -151,6 +171,12 @@ public struct ChannelVisibleEvent: ChannelSpecificEvent { /// The event timestamp. public let createdAt: Date + + init(cid: ChannelId, user: ChatUser, createdAt: Date) { + self.cid = cid + self.user = user + self.createdAt = createdAt + } } class ChannelVisibleEventDTO: EventDTO { @@ -178,7 +204,7 @@ class ChannelVisibleEventDTO: EventDTO { } /// Triggered when a channel is hidden. -public struct ChannelHiddenEvent: ChannelSpecificEvent { +public final class ChannelHiddenEvent: ChannelSpecificEvent { /// The hidden channel identifier. public let cid: ChannelId @@ -190,6 +216,13 @@ public struct ChannelHiddenEvent: ChannelSpecificEvent { /// The date a channel was hidden. public let createdAt: Date + + init(cid: ChannelId, user: ChatUser, isHistoryCleared: Bool, createdAt: Date) { + self.cid = cid + self.user = user + self.isHistoryCleared = isHistoryCleared + self.createdAt = createdAt + } } class ChannelHiddenEventDTO: EventDTO { diff --git a/Sources/StreamChat/WebSocketClient/Events/ConnectionEvents.swift b/Sources/StreamChat/WebSocketClient/Events/ConnectionEvents.swift index c8fcb3d998..0eaa70045c 100644 --- a/Sources/StreamChat/WebSocketClient/Events/ConnectionEvents.swift +++ b/Sources/StreamChat/WebSocketClient/Events/ConnectionEvents.swift @@ -36,7 +36,7 @@ public class HealthCheckEvent: ConnectionEvent, EventDTO { /// Emitted when `Client` changes it's connection status. You can listen to this event and indicate the different connection /// states in the UI (banners like "Offline", "Reconnecting"", etc.). -public struct ConnectionStatusUpdated: Event { +public final class ConnectionStatusUpdated: Event { /// The current connection status of `Client` public let connectionStatus: ConnectionStatus diff --git a/Sources/StreamChat/WebSocketClient/Events/DraftEvents.swift b/Sources/StreamChat/WebSocketClient/Events/DraftEvents.swift index 69263ba608..db10dec8f3 100644 --- a/Sources/StreamChat/WebSocketClient/Events/DraftEvents.swift +++ b/Sources/StreamChat/WebSocketClient/Events/DraftEvents.swift @@ -5,7 +5,7 @@ import Foundation /// Triggered when a draft message is updated or created. -public class DraftUpdatedEvent: Event { +public final class DraftUpdatedEvent: Event { /// The channel identifier of the draft. public let cid: ChannelId @@ -55,7 +55,7 @@ class DraftUpdatedEventDTO: EventDTO { } /// Triggered when a draft message is deleted. -public class DraftDeletedEvent: Event { +public final class DraftDeletedEvent: Event { /// The channel identifier of the draft. public let cid: ChannelId diff --git a/Sources/StreamChat/WebSocketClient/Events/MemberEvents.swift b/Sources/StreamChat/WebSocketClient/Events/MemberEvents.swift index efa4b30bff..f2e7d0726a 100644 --- a/Sources/StreamChat/WebSocketClient/Events/MemberEvents.swift +++ b/Sources/StreamChat/WebSocketClient/Events/MemberEvents.swift @@ -5,7 +5,7 @@ import Foundation /// Triggered when a new member is added to a channel. -public struct MemberAddedEvent: MemberEvent, ChannelSpecificEvent { +public final class MemberAddedEvent: MemberEvent, ChannelSpecificEvent { /// The user who added a member to a channel. public let user: ChatUser @@ -17,6 +17,13 @@ public struct MemberAddedEvent: MemberEvent, ChannelSpecificEvent { /// The event timestamp. public let createdAt: Date + + init(user: ChatUser, cid: ChannelId, member: ChatChannelMember, createdAt: Date) { + self.user = user + self.cid = cid + self.member = member + self.createdAt = createdAt + } } class MemberAddedEventDTO: EventDTO { @@ -50,7 +57,7 @@ class MemberAddedEventDTO: EventDTO { } /// Triggered when a channel member is updated. -public struct MemberUpdatedEvent: MemberEvent, ChannelSpecificEvent { +public final class MemberUpdatedEvent: MemberEvent, ChannelSpecificEvent { /// The user who updated a member. public let user: ChatUser @@ -62,6 +69,13 @@ public struct MemberUpdatedEvent: MemberEvent, ChannelSpecificEvent { /// The event timestamp. public let createdAt: Date + + init(user: ChatUser, cid: ChannelId, member: ChatChannelMember, createdAt: Date) { + self.user = user + self.cid = cid + self.member = member + self.createdAt = createdAt + } } class MemberUpdatedEventDTO: EventDTO { @@ -95,7 +109,7 @@ class MemberUpdatedEventDTO: EventDTO { } /// Triggered when a member is removed from a channel. -public struct MemberRemovedEvent: MemberEvent, ChannelSpecificEvent { +public final class MemberRemovedEvent: MemberEvent, ChannelSpecificEvent { /// The user who stopped being a member. public let user: ChatUser @@ -104,6 +118,12 @@ public struct MemberRemovedEvent: MemberEvent, ChannelSpecificEvent { /// The event timestamp. public let createdAt: Date + + init(user: ChatUser, cid: ChannelId, createdAt: Date) { + self.user = user + self.cid = cid + self.createdAt = createdAt + } } class MemberRemovedEventDTO: EventDTO { diff --git a/Sources/StreamChat/WebSocketClient/Events/MessageEvents.swift b/Sources/StreamChat/WebSocketClient/Events/MessageEvents.swift index 4e7edbebf5..56e0002dbc 100644 --- a/Sources/StreamChat/WebSocketClient/Events/MessageEvents.swift +++ b/Sources/StreamChat/WebSocketClient/Events/MessageEvents.swift @@ -5,7 +5,7 @@ import Foundation /// Triggered when a new message is sent to channel. -public class MessageNewEvent: ChannelSpecificEvent, HasUnreadCount { +public final class MessageNewEvent: ChannelSpecificEvent, HasUnreadCount { /// The user who sent a message. public let user: ChatUser @@ -83,7 +83,7 @@ class MessageNewEventDTO: EventDTO { } /// Triggered when a message is updated. -public class MessageUpdatedEvent: ChannelSpecificEvent { +public final class MessageUpdatedEvent: ChannelSpecificEvent { /// The use who updated the message. public let user: ChatUser @@ -144,7 +144,7 @@ class MessageUpdatedEventDTO: EventDTO { } /// Triggered when a new message is deleted. -public class MessageDeletedEvent: ChannelSpecificEvent { +public final class MessageDeletedEvent: ChannelSpecificEvent { /// The user who deleted the message. public let user: ChatUser? @@ -233,7 +233,7 @@ class MessageDeletedEventDTO: EventDTO { public typealias ChannelReadEvent = MessageReadEvent /// `ChannelReadEvent`, this event tells that User has mark read all messages in channel. -public class MessageReadEvent: ChannelSpecificEvent { +public final class MessageReadEvent: ChannelSpecificEvent { /// The user who read the channel. public let user: ChatUser @@ -305,7 +305,7 @@ class MessageReadEventDTO: EventDTO { } // Triggered when the current user creates a new message and is pending to be sent. -public class NewMessagePendingEvent: ChannelSpecificEvent { +public final class NewMessagePendingEvent: ChannelSpecificEvent { public var message: ChatMessage public var cid: ChannelId @@ -316,7 +316,7 @@ public class NewMessagePendingEvent: ChannelSpecificEvent { } // Triggered when a message failed being sent. -public class NewMessageErrorEvent: ChannelSpecificEvent { +public final class NewMessageErrorEvent: ChannelSpecificEvent { public let messageId: MessageId public let cid: ChannelId public let error: Error @@ -329,7 +329,7 @@ public class NewMessageErrorEvent: ChannelSpecificEvent { } /// Triggered when a message is delivered to a user. -public class MessageDeliveredEvent: ChannelSpecificEvent { +public final class MessageDeliveredEvent: ChannelSpecificEvent { /// The user who received the delivered message. public let user: ChatUser diff --git a/Sources/StreamChat/WebSocketClient/Events/NotificationEvents.swift b/Sources/StreamChat/WebSocketClient/Events/NotificationEvents.swift index 27d1c7165b..37b4b24b71 100644 --- a/Sources/StreamChat/WebSocketClient/Events/NotificationEvents.swift +++ b/Sources/StreamChat/WebSocketClient/Events/NotificationEvents.swift @@ -5,7 +5,7 @@ import Foundation /// Triggered when a new message is sent to a channel the current user is member of. -public struct NotificationMessageNewEvent: ChannelSpecificEvent, HasUnreadCount { +public final class NotificationMessageNewEvent: ChannelSpecificEvent, HasUnreadCount { /// The identifier of a channel a message is sent to. public var cid: ChannelId { channel.cid } @@ -20,6 +20,13 @@ public struct NotificationMessageNewEvent: ChannelSpecificEvent, HasUnreadCount /// The unread counts of the current user. public let unreadCount: UnreadCount? + + init(channel: ChatChannel, message: ChatMessage, createdAt: Date, unreadCount: UnreadCount?) { + self.channel = channel + self.message = message + self.createdAt = createdAt + self.unreadCount = unreadCount + } } class NotificationMessageNewEventDTO: EventDTO { @@ -54,7 +61,7 @@ class NotificationMessageNewEventDTO: EventDTO { } /// Triggered when all channels the current user is member of are marked as read. -public struct NotificationMarkAllReadEvent: Event, HasUnreadCount { +public final class NotificationMarkAllReadEvent: Event, HasUnreadCount { /// The current user. public let user: ChatUser @@ -63,6 +70,12 @@ public struct NotificationMarkAllReadEvent: Event, HasUnreadCount { /// The event timestamp. public let createdAt: Date + + init(user: ChatUser, unreadCount: UnreadCount?, createdAt: Date) { + self.user = user + self.unreadCount = unreadCount + self.createdAt = createdAt + } } class NotificationMarkAllReadEventDTO: EventDTO { @@ -91,7 +104,7 @@ class NotificationMarkAllReadEventDTO: EventDTO { } /// Triggered when a channel the current user is member of is marked as read. -public struct NotificationMarkReadEvent: ChannelSpecificEvent, HasUnreadCount { +public final class NotificationMarkReadEvent: ChannelSpecificEvent, HasUnreadCount { /// The current user. public let user: ChatUser @@ -106,10 +119,18 @@ public struct NotificationMarkReadEvent: ChannelSpecificEvent, HasUnreadCount { /// The event timestamp. public let createdAt: Date + + init(user: ChatUser, cid: ChannelId, unreadCount: UnreadCount?, lastReadMessageId: MessageId?, createdAt: Date) { + self.user = user + self.cid = cid + self.unreadCount = unreadCount + self.lastReadMessageId = lastReadMessageId + self.createdAt = createdAt + } } /// Triggered when a channel the current user is member of is marked as unread. -public struct NotificationMarkUnreadEvent: ChannelSpecificEvent { +public final class NotificationMarkUnreadEvent: ChannelSpecificEvent { /// The current user. public let user: ChatUser @@ -133,6 +154,17 @@ public struct NotificationMarkUnreadEvent: ChannelSpecificEvent { /// The number of unread messages for the channel public let unreadMessagesCount: Int + + init(user: ChatUser, cid: ChannelId, createdAt: Date, firstUnreadMessageId: MessageId, lastReadMessageId: MessageId?, lastReadAt: Date, unreadCount: UnreadCount, unreadMessagesCount: Int) { + self.user = user + self.cid = cid + self.createdAt = createdAt + self.firstUnreadMessageId = firstUnreadMessageId + self.lastReadMessageId = lastReadMessageId + self.lastReadAt = lastReadAt + self.unreadCount = unreadCount + self.unreadMessagesCount = unreadMessagesCount + } } class NotificationMarkReadEventDTO: EventDTO { @@ -207,12 +239,17 @@ class NotificationMarkUnreadEventDTO: EventDTO { } /// Triggered when current user mutes/unmutes a user. -public struct NotificationMutesUpdatedEvent: Event { +public final class NotificationMutesUpdatedEvent: Event { /// The current user. public let currentUser: CurrentChatUser /// The event timestamp. public let createdAt: Date + + init(currentUser: CurrentChatUser, createdAt: Date) { + self.currentUser = currentUser + self.createdAt = createdAt + } } class NotificationMutesUpdatedEventDTO: EventDTO { @@ -237,7 +274,7 @@ class NotificationMutesUpdatedEventDTO: EventDTO { } /// Triggered when the current user is added to the channel member list. -public struct NotificationAddedToChannelEvent: ChannelSpecificEvent, HasUnreadCount { +public final class NotificationAddedToChannelEvent: ChannelSpecificEvent, HasUnreadCount { /// The identifier of a channel a message is sent to. public var cid: ChannelId { channel.cid } @@ -252,6 +289,13 @@ public struct NotificationAddedToChannelEvent: ChannelSpecificEvent, HasUnreadCo /// The event timestamp. public let createdAt: Date + + init(channel: ChatChannel, unreadCount: UnreadCount?, member: ChatChannelMember, createdAt: Date) { + self.channel = channel + self.unreadCount = unreadCount + self.member = member + self.createdAt = createdAt + } } class NotificationAddedToChannelEventDTO: EventDTO { @@ -287,7 +331,7 @@ class NotificationAddedToChannelEventDTO: EventDTO { } /// Triggered when the current user is removed from a channel member list. -public struct NotificationRemovedFromChannelEvent: ChannelSpecificEvent { +public final class NotificationRemovedFromChannelEvent: ChannelSpecificEvent { /// The user who removed the current user from channel members. public let user: ChatUser @@ -299,6 +343,13 @@ public struct NotificationRemovedFromChannelEvent: ChannelSpecificEvent { /// The event timestamp. public let createdAt: Date + + init(user: ChatUser, cid: ChannelId, member: ChatChannelMember, createdAt: Date) { + self.user = user + self.cid = cid + self.member = member + self.createdAt = createdAt + } } class NotificationRemovedFromChannelEventDTO: EventDTO { @@ -333,12 +384,17 @@ class NotificationRemovedFromChannelEventDTO: EventDTO { } /// Triggered when current user mutes/unmutes a channel. -public struct NotificationChannelMutesUpdatedEvent: Event { +public final class NotificationChannelMutesUpdatedEvent: Event { /// The current user. public let currentUser: CurrentChatUser /// The event timestamp. public let createdAt: Date + + init(currentUser: CurrentChatUser, createdAt: Date) { + self.currentUser = currentUser + self.createdAt = createdAt + } } class NotificationChannelMutesUpdatedEventDTO: EventDTO { @@ -363,7 +419,7 @@ class NotificationChannelMutesUpdatedEventDTO: EventDTO { } /// Triggered when current user is invited to a channel. -public struct NotificationInvitedEvent: MemberEvent, ChannelSpecificEvent { +public final class NotificationInvitedEvent: MemberEvent, ChannelSpecificEvent { /// The inviter. public let user: ChatUser @@ -375,6 +431,13 @@ public struct NotificationInvitedEvent: MemberEvent, ChannelSpecificEvent { /// The event timestamp. public let createdAt: Date + + init(user: ChatUser, cid: ChannelId, member: ChatChannelMember, createdAt: Date) { + self.user = user + self.cid = cid + self.member = member + self.createdAt = createdAt + } } class NotificationInvitedEventDTO: EventDTO { @@ -409,7 +472,7 @@ class NotificationInvitedEventDTO: EventDTO { } /// Triggered when the current user accepts an invite to a channel. -public struct NotificationInviteAcceptedEvent: MemberEvent, ChannelSpecificEvent { +public final class NotificationInviteAcceptedEvent: MemberEvent, ChannelSpecificEvent { /// The inviter. public let user: ChatUser @@ -424,6 +487,13 @@ public struct NotificationInviteAcceptedEvent: MemberEvent, ChannelSpecificEvent /// The event timestamp. public let createdAt: Date + + init(user: ChatUser, channel: ChatChannel, member: ChatChannelMember, createdAt: Date) { + self.user = user + self.channel = channel + self.member = member + self.createdAt = createdAt + } } class NotificationInviteAcceptedEventDTO: EventDTO { @@ -459,7 +529,7 @@ class NotificationInviteAcceptedEventDTO: EventDTO { } /// Triggered when the current user rejects an invite to a channel. -public struct NotificationInviteRejectedEvent: MemberEvent, ChannelSpecificEvent { +public final class NotificationInviteRejectedEvent: MemberEvent, ChannelSpecificEvent { /// The inviter. public let user: ChatUser @@ -474,6 +544,13 @@ public struct NotificationInviteRejectedEvent: MemberEvent, ChannelSpecificEvent /// The event timestamp. public let createdAt: Date + + init(user: ChatUser, channel: ChatChannel, member: ChatChannelMember, createdAt: Date) { + self.user = user + self.channel = channel + self.member = member + self.createdAt = createdAt + } } class NotificationInviteRejectedEventDTO: EventDTO { @@ -509,7 +586,7 @@ class NotificationInviteRejectedEventDTO: EventDTO { } /// Triggered when a channel is deleted, this event is delivered to all channel members -public struct NotificationChannelDeletedEvent: ChannelSpecificEvent { +public final class NotificationChannelDeletedEvent: ChannelSpecificEvent { /// The cid of the deleted channel public let cid: ChannelId @@ -518,6 +595,12 @@ public struct NotificationChannelDeletedEvent: ChannelSpecificEvent { /// The event timestamp. public let createdAt: Date + + init(cid: ChannelId, channel: ChatChannel, createdAt: Date) { + self.cid = cid + self.channel = channel + self.createdAt = createdAt + } } class NotificationChannelDeletedEventDTO: EventDTO { diff --git a/Sources/StreamChat/WebSocketClient/Events/PollsEvents.swift b/Sources/StreamChat/WebSocketClient/Events/PollsEvents.swift index d8bd4f2d35..d7d61f69cb 100644 --- a/Sources/StreamChat/WebSocketClient/Events/PollsEvents.swift +++ b/Sources/StreamChat/WebSocketClient/Events/PollsEvents.swift @@ -48,13 +48,18 @@ extension PollEventDTO { } /// A model representing an event where a poll was closed. -public struct PollClosedEvent: Event { +public final class PollClosedEvent: Event { /// The poll that was closed. public let poll: Poll /// The date and time when the event was created. /// This property is optional and may be `nil`. public let createdAt: Date? + + init(poll: Poll, createdAt: Date?) { + self.poll = poll + self.createdAt = createdAt + } } struct PollClosedEventDTO: PollEventDTO { @@ -72,13 +77,18 @@ struct PollClosedEventDTO: PollEventDTO { } /// A model representing an event where a poll was created. -public struct PollCreatedEvent: Event { +public final class PollCreatedEvent: Event { /// The poll that was created. public let poll: Poll /// The date and time when the event was created. /// This property is optional and may be `nil`. public let createdAt: Date? + + init(poll: Poll, createdAt: Date?) { + self.poll = poll + self.createdAt = createdAt + } } struct PollCreatedEventDTO: PollEventDTO { @@ -96,13 +106,18 @@ struct PollCreatedEventDTO: PollEventDTO { } /// A model representing an event where a poll was deleted. -public struct PollDeletedEvent: Event { +public final class PollDeletedEvent: Event { /// The poll that was deleted. public let poll: Poll /// The date and time when the event was created. /// This property is optional and may be `nil`. public let createdAt: Date? + + init(poll: Poll, createdAt: Date?) { + self.poll = poll + self.createdAt = createdAt + } } struct PollDeletedEventDTO: PollEventDTO { @@ -120,13 +135,18 @@ struct PollDeletedEventDTO: PollEventDTO { } /// A model representing an event where a poll was updated. -public struct PollUpdatedEvent: Event { +public final class PollUpdatedEvent: Event { /// The poll that was updated. public let poll: Poll /// The date and time when the event was created. /// This property is optional and may be `nil`. public let createdAt: Date? + + init(poll: Poll, createdAt: Date?) { + self.poll = poll + self.createdAt = createdAt + } } struct PollUpdatedEventDTO: PollEventDTO { @@ -144,7 +164,7 @@ struct PollUpdatedEventDTO: PollEventDTO { } /// A model representing an event where a vote was casted in a poll. -public struct PollVoteCastedEvent: Event { +public final class PollVoteCastedEvent: Event { /// The vote that was casted. public let vote: PollVote @@ -154,6 +174,12 @@ public struct PollVoteCastedEvent: Event { /// The date and time when the event was created. /// This property is optional and may be `nil`. public let createdAt: Date? + + init(vote: PollVote, poll: Poll, createdAt: Date?) { + self.vote = vote + self.poll = poll + self.createdAt = createdAt + } } struct PollVoteCastedEventDTO: PollVoteEventDTO { @@ -173,7 +199,7 @@ struct PollVoteCastedEventDTO: PollVoteEventDTO { } /// A model representing an event where a vote was changed in a poll. -public struct PollVoteChangedEvent: Event { +public final class PollVoteChangedEvent: Event { /// The vote that was changed. public let vote: PollVote @@ -183,6 +209,12 @@ public struct PollVoteChangedEvent: Event { /// The date and time when the event was created. /// This property is optional and may be `nil`. public let createdAt: Date? + + init(vote: PollVote, poll: Poll, createdAt: Date?) { + self.vote = vote + self.poll = poll + self.createdAt = createdAt + } } struct PollVoteChangedEventDTO: PollVoteEventDTO { @@ -202,7 +234,7 @@ struct PollVoteChangedEventDTO: PollVoteEventDTO { } /// A model representing an event where a vote was removed from a poll. -public struct PollVoteRemovedEvent: Event { +public final class PollVoteRemovedEvent: Event { /// The vote that was removed. public let vote: PollVote @@ -212,6 +244,12 @@ public struct PollVoteRemovedEvent: Event { /// The date and time when the event was created. /// This property is optional and may be `nil`. public let createdAt: Date? + + init(vote: PollVote, poll: Poll, createdAt: Date?) { + self.vote = vote + self.poll = poll + self.createdAt = createdAt + } } struct PollVoteRemovedEventDTO: PollVoteEventDTO { diff --git a/Sources/StreamChat/WebSocketClient/Events/ReactionEvents.swift b/Sources/StreamChat/WebSocketClient/Events/ReactionEvents.swift index 153d096d97..be35e5084b 100644 --- a/Sources/StreamChat/WebSocketClient/Events/ReactionEvents.swift +++ b/Sources/StreamChat/WebSocketClient/Events/ReactionEvents.swift @@ -5,7 +5,7 @@ import Foundation /// Triggered a new reaction is added. -public struct ReactionNewEvent: ChannelSpecificEvent { +public final class ReactionNewEvent: ChannelSpecificEvent { /// The use who added a reaction. public let user: ChatUser @@ -20,6 +20,14 @@ public struct ReactionNewEvent: ChannelSpecificEvent { /// The event timestamp. public let createdAt: Date + + init(user: ChatUser, cid: ChannelId, message: ChatMessage, reaction: ChatMessageReaction, createdAt: Date) { + self.user = user + self.cid = cid + self.message = message + self.reaction = reaction + self.createdAt = createdAt + } } class ReactionNewEventDTO: EventDTO { @@ -61,7 +69,7 @@ class ReactionNewEventDTO: EventDTO { } /// Triggered when a reaction is updated. -public struct ReactionUpdatedEvent: ChannelSpecificEvent { +public final class ReactionUpdatedEvent: ChannelSpecificEvent { /// The use who updated a reaction. public let user: ChatUser @@ -76,6 +84,14 @@ public struct ReactionUpdatedEvent: ChannelSpecificEvent { /// The event timestamp. public let createdAt: Date + + init(user: ChatUser, cid: ChannelId, message: ChatMessage, reaction: ChatMessageReaction, createdAt: Date) { + self.user = user + self.cid = cid + self.message = message + self.reaction = reaction + self.createdAt = createdAt + } } class ReactionUpdatedEventDTO: EventDTO { @@ -117,7 +133,7 @@ class ReactionUpdatedEventDTO: EventDTO { } /// Triggered when a reaction is deleted. -public struct ReactionDeletedEvent: ChannelSpecificEvent { +public final class ReactionDeletedEvent: ChannelSpecificEvent { /// The use who deleted a reaction. public let user: ChatUser @@ -132,6 +148,14 @@ public struct ReactionDeletedEvent: ChannelSpecificEvent { /// The event timestamp. public let createdAt: Date + + init(user: ChatUser, cid: ChannelId, message: ChatMessage, reaction: ChatMessageReaction, createdAt: Date) { + self.user = user + self.cid = cid + self.message = message + self.reaction = reaction + self.createdAt = createdAt + } } class ReactionDeletedEventDTO: EventDTO { diff --git a/Sources/StreamChat/WebSocketClient/Events/ReminderEvents.swift b/Sources/StreamChat/WebSocketClient/Events/ReminderEvents.swift index 865d6f0e12..79a6e91944 100644 --- a/Sources/StreamChat/WebSocketClient/Events/ReminderEvents.swift +++ b/Sources/StreamChat/WebSocketClient/Events/ReminderEvents.swift @@ -5,7 +5,7 @@ import Foundation /// Triggered when a message reminder is created. -public class MessageReminderCreatedEvent: Event { +public final class MessageReminderCreatedEvent: Event { /// The message ID associated with the reminder. public let messageId: MessageId @@ -53,7 +53,7 @@ class ReminderCreatedEventDTO: EventDTO { } /// Triggered when a message reminder is updated. -public class MessageReminderUpdatedEvent: Event { +public final class MessageReminderUpdatedEvent: Event { /// The message ID associated with the reminder. public let messageId: MessageId @@ -101,7 +101,7 @@ class ReminderUpdatedEventDTO: EventDTO { } /// Triggered when a message reminder is deleted. -public class MessageReminderDeletedEvent: Event { +public final class MessageReminderDeletedEvent: Event { /// The message ID associated with the reminder. public let messageId: MessageId @@ -153,7 +153,7 @@ class ReminderDeletedEventDTO: EventDTO { } /// Triggered when a reminder is due and a notification should be shown. -public class MessageReminderDueEvent: Event { +public final class MessageReminderDueEvent: Event { /// The message ID associated with the reminder. public let messageId: MessageId diff --git a/Sources/StreamChat/WebSocketClient/Events/ThreadEvents.swift b/Sources/StreamChat/WebSocketClient/Events/ThreadEvents.swift index f896b63f90..06292937ce 100644 --- a/Sources/StreamChat/WebSocketClient/Events/ThreadEvents.swift +++ b/Sources/StreamChat/WebSocketClient/Events/ThreadEvents.swift @@ -5,7 +5,7 @@ import Foundation /// Triggered when a new message is sent to a thread. -public struct ThreadMessageNewEvent: Event { +public final class ThreadMessageNewEvent: Event { /// The reply that was sent. public let message: ChatMessage @@ -20,6 +20,13 @@ public struct ThreadMessageNewEvent: Event { /// The event timestamp. public let createdAt: Date + + init(message: ChatMessage, channel: ChatChannel, unreadCount: UnreadCount, createdAt: Date) { + self.message = message + self.channel = channel + self.unreadCount = unreadCount + self.createdAt = createdAt + } } class ThreadMessageNewEventDTO: EventDTO { @@ -56,12 +63,17 @@ class ThreadMessageNewEventDTO: EventDTO { } /// Triggered when a thread is updated -public struct ThreadUpdatedEvent: Event { +public final class ThreadUpdatedEvent: Event { /// The updated user public let thread: ChatThread /// The event timestamp public let createdAt: Date? + + init(thread: ChatThread, createdAt: Date?) { + self.thread = thread + self.createdAt = createdAt + } } class ThreadUpdatedEventDTO: EventDTO { diff --git a/Sources/StreamChat/WebSocketClient/Events/TypingEvent.swift b/Sources/StreamChat/WebSocketClient/Events/TypingEvent.swift index 714b96f2d9..e71abe3fb4 100644 --- a/Sources/StreamChat/WebSocketClient/Events/TypingEvent.swift +++ b/Sources/StreamChat/WebSocketClient/Events/TypingEvent.swift @@ -5,7 +5,7 @@ import Foundation /// Triggered when user starts/stops typing in a channel. -public struct TypingEvent: ChannelSpecificEvent { +public final class TypingEvent: ChannelSpecificEvent { /// The flag saying if typing is started/stopped. public let isTyping: Bool @@ -23,6 +23,14 @@ public struct TypingEvent: ChannelSpecificEvent { /// `true` if typing event happened in the message thread. public var isThread: Bool { parentId != nil } + + init(isTyping: Bool, cid: ChannelId, user: ChatUser, parentId: MessageId?, createdAt: Date) { + self.isTyping = isTyping + self.cid = cid + self.user = user + self.parentId = parentId + self.createdAt = createdAt + } } class TypingEventDTO: EventDTO { @@ -59,7 +67,7 @@ class TypingEventDTO: EventDTO { /// A special event type which is only emitted by the SDK and never the backend. /// This event is emitted by `TypingStartCleanupMiddleware` to signal that a typing event /// must be cleaned up, due to timeout of that event. -public struct CleanUpTypingEvent: Event { +public final class CleanUpTypingEvent: Event { public let cid: ChannelId public let userId: UserId diff --git a/Sources/StreamChat/WebSocketClient/Events/UserEvents.swift b/Sources/StreamChat/WebSocketClient/Events/UserEvents.swift index ee17af1049..dfeb39eb9a 100644 --- a/Sources/StreamChat/WebSocketClient/Events/UserEvents.swift +++ b/Sources/StreamChat/WebSocketClient/Events/UserEvents.swift @@ -5,12 +5,17 @@ import Foundation /// Triggered when user status changes (eg. online, offline, away, etc.) -public struct UserPresenceChangedEvent: Event { +public final class UserPresenceChangedEvent: Event { /// The user the status changed for public let user: ChatUser /// The event timestamp public let createdAt: Date? + + init(user: ChatUser, createdAt: Date?) { + self.user = user + self.createdAt = createdAt + } } class UserPresenceChangedEventDTO: EventDTO { @@ -35,12 +40,17 @@ class UserPresenceChangedEventDTO: EventDTO { } /// Triggered when user is updated -public struct UserUpdatedEvent: Event { +public final class UserUpdatedEvent: Event { /// The updated user public let user: ChatUser /// The event timestamp public let createdAt: Date? + + init(user: ChatUser, createdAt: Date?) { + self.user = user + self.createdAt = createdAt + } } class UserUpdatedEventDTO: EventDTO { @@ -67,7 +77,7 @@ class UserUpdatedEventDTO: EventDTO { // MARK: - User Watching /// Triggered when a user starts/stops watching a channel -public struct UserWatchingEvent: ChannelSpecificEvent { +public final class UserWatchingEvent: ChannelSpecificEvent { /// The channel identifier a user started/stopped watching public let cid: ChannelId @@ -82,6 +92,14 @@ public struct UserWatchingEvent: ChannelSpecificEvent { /// The flag saying if watching was started or stopped public let isStarted: Bool + + init(cid: ChannelId, createdAt: Date, user: ChatUser, watcherCount: Int, isStarted: Bool) { + self.cid = cid + self.createdAt = createdAt + self.user = user + self.watcherCount = watcherCount + self.isStarted = isStarted + } } class UserWatchingEventDTO: EventDTO { @@ -117,15 +135,20 @@ class UserWatchingEventDTO: EventDTO { // MARK: - User Ban /// Triggered when user is banned not in a specific channel but globally. -public struct UserGloballyBannedEvent: Event { +public final class UserGloballyBannedEvent: Event { /// The banned user public let user: ChatUser /// The event timestamp public let createdAt: Date + + init(user: ChatUser, createdAt: Date) { + self.user = user + self.createdAt = createdAt + } } -struct UserGloballyBannedEventDTO: EventDTO { +class UserGloballyBannedEventDTO: EventDTO { let user: UserPayload let createdAt: Date let payload: EventPayload @@ -147,7 +170,7 @@ struct UserGloballyBannedEventDTO: EventDTO { } /// Triggered when user is banned in a specific channel -public struct UserBannedEvent: ChannelSpecificEvent { +public final class UserBannedEvent: ChannelSpecificEvent { /// The channel identifer user is banned at. public let cid: ChannelId @@ -168,6 +191,16 @@ public struct UserBannedEvent: ChannelSpecificEvent { /// A boolean value indicating if the ban is a shadowed ban or not. public let isShadowBan: Bool? + + init(cid: ChannelId, user: ChatUser, ownerId: UserId, createdAt: Date?, reason: String?, expiredAt: Date?, isShadowBan: Bool?) { + self.cid = cid + self.user = user + self.ownerId = ownerId + self.createdAt = createdAt + self.reason = reason + self.expiredAt = expiredAt + self.isShadowBan = isShadowBan + } } class UserBannedEventDTO: EventDTO { @@ -207,15 +240,20 @@ class UserBannedEventDTO: EventDTO { } /// Triggered when user is removed from global ban. -public struct UserGloballyUnbannedEvent: Event { +public final class UserGloballyUnbannedEvent: Event { /// The unbanned user. public let user: ChatUser /// The event timestamp public let createdAt: Date + + init(user: ChatUser, createdAt: Date) { + self.user = user + self.createdAt = createdAt + } } -struct UserGloballyUnbannedEventDTO: EventDTO { +class UserGloballyUnbannedEventDTO: EventDTO { let user: UserPayload let createdAt: Date let payload: EventPayload @@ -237,7 +275,7 @@ struct UserGloballyUnbannedEventDTO: EventDTO { } /// Triggered when banned user is unbanned in a specific channel -public struct UserUnbannedEvent: ChannelSpecificEvent { +public final class UserUnbannedEvent: ChannelSpecificEvent { /// The channel identifer user is unbanned at. public let cid: ChannelId @@ -246,6 +284,12 @@ public struct UserUnbannedEvent: ChannelSpecificEvent { /// The event timestamp public let createdAt: Date? + + init(cid: ChannelId, user: ChatUser, createdAt: Date?) { + self.cid = cid + self.user = user + self.createdAt = createdAt + } } class UserUnbannedEventDTO: EventDTO { @@ -273,7 +317,7 @@ class UserUnbannedEventDTO: EventDTO { } /// Triggered when the messages of a banned user should be deleted. -public struct UserMessagesDeletedEvent: Event { +public final class UserMessagesDeletedEvent: Event { /// The banned user. public let user: ChatUser @@ -282,6 +326,12 @@ public struct UserMessagesDeletedEvent: Event { /// The event timestamp public let createdAt: Date + + init(user: ChatUser, hardDelete: Bool, createdAt: Date) { + self.user = user + self.hardDelete = hardDelete + self.createdAt = createdAt + } } class UserMessagesDeletedEventDTO: EventDTO { From cd730c6fc639f795988b94f67543206e249de0ba Mon Sep 17 00:00:00 2001 From: Toomas Vahter Date: Tue, 18 Nov 2025 12:37:31 +0200 Subject: [PATCH 09/10] Add ChatClientConfig.isAutomaticSyncOnReconnectEnabled for toggling automatic syncing (#3879) --- CHANGELOG.md | 2 + .../StreamChat/Config/ChatClientConfig.swift | 24 +++++++ .../Repositories/SyncRepository.swift | 71 +++++++++++-------- .../Repositories/SyncRepository_Tests.swift | 58 +++++++++++++++ 4 files changed, 124 insertions(+), 31 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index caa7095bc6..68971861e4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). # Upcoming ## StreamChat +### ✅ Added +- Add `ChatClientConfig.isAutomaticSyncOnReconnectEnabled` for toggling automatic syncing [#3879](https://github.com/GetStream/stream-chat-swift/pull/3879) ### ⚡️ Performance - Reduce SDK size by converting Events from structs to classes [#3878](https://github.com/GetStream/stream-chat-swift/pull/3878) ### 🔄 Changed diff --git a/Sources/StreamChat/Config/ChatClientConfig.swift b/Sources/StreamChat/Config/ChatClientConfig.swift index 7ce758736e..3f45045bca 100644 --- a/Sources/StreamChat/Config/ChatClientConfig.swift +++ b/Sources/StreamChat/Config/ChatClientConfig.swift @@ -203,6 +203,30 @@ public struct ChatClientConfig { /// How many hours the unsent actions should be queued for sending when the internet connection is available. public var queuedActionsMaxHoursThreshold: Int = 12 + + /// Specifies, if local data is updated with subscribing to web-socket events after reconnection. + /// + /// When turning this off it is up for SDK user to observe web-socket connection state for reconnection + /// and syncing data by calling, for example, controller's `synchronize` or state-layer's `get` methods. + /// Note that, these calls fetch the latest state and also subscribe to web-socket events if query's `watch` is true. + /// + /// Web-socket connection status can be updated either with subscribe method or through ``ChatConnectionController``. + /// ```swift + /// connectionCancellable = chatClient.subscribe( + /// toEvent: ConnectionStatusUpdated.self, + /// handler: { connectionEvent in + /// switch connectionEvent.connectionStatus { + /// case .connected: + /// // Data can be updated and watching can be resumed + /// default: + /// break + /// } + /// } + /// ) + /// ``` + /// + /// - SeeAlso: Query option``QueryOptions/watch`` used by ``ChannelListQuery`` and ``ChannelQuery``. + public var isAutomaticSyncOnReconnectEnabled = true public init( apiKey: APIKey diff --git a/Sources/StreamChat/Repositories/SyncRepository.swift b/Sources/StreamChat/Repositories/SyncRepository.swift index a3671ff913..0fed57c2e8 100644 --- a/Sources/StreamChat/Repositories/SyncRepository.swift +++ b/Sources/StreamChat/Repositories/SyncRepository.swift @@ -169,7 +169,6 @@ class SyncRepository { let context = SyncContext(lastSyncAt: lastSyncAt) var operations: [Operation] = [] let start = CFAbsoluteTimeGetCurrent() - log.info("Starting to refresh offline state", subsystems: .offlineSupport) // // Recovery mode operations (other API requests are paused) @@ -185,36 +184,46 @@ class SyncRepository { // // Background mode operations // - - /// 1. Collect all the **active** channel ids - operations.append(ActiveChannelIdsOperation(syncRepository: self, context: context)) - - // 2. Refresh channel lists - operations.append(contentsOf: activeChannelLists.allObjects.map { RefreshChannelListOperation(channelList: $0, context: context) }) - operations.append(contentsOf: activeChannelListControllers.allObjects.map { RefreshChannelListOperation(controller: $0, context: context) }) - - // 3. /sync (for channels what not part of active channel lists) - operations.append(SyncEventsOperation(syncRepository: self, context: context, recovery: false)) - - // 4. Re-watch channels what we were watching before disconnect - // Needs to be done explicitly after reconnection, otherwise SDK users need to handle connection changes - operations.append(contentsOf: activeChannelControllers.allObjects.map { - WatchChannelOperation(controller: $0, context: context, recovery: false) - }) - operations.append(contentsOf: activeChats.allObjects.map { - WatchChannelOperation(chat: $0, context: context) - }) - operations.append(contentsOf: activeLivestreamControllers.allObjects.map { - WatchChannelOperation(livestreamController: $0, context: context, recovery: false) - }) - - operations.append(BlockOperation(block: { - let duration = CFAbsoluteTimeGetCurrent() - start - log.info("Finished refreshing offline state (\(context.synchedChannelIds.count) channels in \(String(format: "%.1f", duration)) seconds)", subsystems: .offlineSupport) - DispatchQueue.main.async { - completion() - } - })) + if config.isAutomaticSyncOnReconnectEnabled { + log.info("Starting to refresh offline state", subsystems: .offlineSupport) + + /// 1. Collect all the **active** channel ids + operations.append(ActiveChannelIdsOperation(syncRepository: self, context: context)) + + // 2. Refresh channel lists + operations.append(contentsOf: activeChannelLists.allObjects.map { RefreshChannelListOperation(channelList: $0, context: context) }) + operations.append(contentsOf: activeChannelListControllers.allObjects.map { RefreshChannelListOperation(controller: $0, context: context) }) + + // 3. /sync (for channels what not part of active channel lists) + operations.append(SyncEventsOperation(syncRepository: self, context: context, recovery: false)) + + // 4. Re-watch channels what we were watching before disconnect + // Needs to be done explicitly after reconnection, otherwise SDK users need to handle connection changes + operations.append(contentsOf: activeChannelControllers.allObjects.map { + WatchChannelOperation(controller: $0, context: context, recovery: false) + }) + operations.append(contentsOf: activeChats.allObjects.map { + WatchChannelOperation(chat: $0, context: context) + }) + operations.append(contentsOf: activeLivestreamControllers.allObjects.map { + WatchChannelOperation(livestreamController: $0, context: context, recovery: false) + }) + + operations.append(BlockOperation(block: { + let duration = CFAbsoluteTimeGetCurrent() - start + log.info("Finished refreshing offline state (\(context.synchedChannelIds.count) channels in \(String(format: "%.1f", duration)) seconds)", subsystems: .offlineSupport) + DispatchQueue.main.async { + completion() + } + })) + } else { + // When automatic sync is disabled, still call completion after recovery operations finish + operations.append(BlockOperation(block: { + DispatchQueue.main.async { + completion() + } + })) + } var previousOperation: Operation? operations.reversed().forEach { operation in diff --git a/Tests/StreamChatTests/Repositories/SyncRepository_Tests.swift b/Tests/StreamChatTests/Repositories/SyncRepository_Tests.swift index 0c776d8f8f..7af570801c 100644 --- a/Tests/StreamChatTests/Repositories/SyncRepository_Tests.swift +++ b/Tests/StreamChatTests/Repositories/SyncRepository_Tests.swift @@ -277,6 +277,64 @@ class SyncRepository_Tests: XCTestCase { waitForSyncLocalStateRun() } + func test_syncLocalState_isAutomaticSyncOnReconnectEnabled_false_noOperationsRun() throws { + var config = ChatClientConfig(apiKeyString: .unique) + config.isLocalStorageEnabled = true + config.isAutomaticSyncOnReconnectEnabled = false + let client = ChatClient_Mock(config: config) + repository = SyncRepository( + config: client.config, + offlineRequestsRepository: offlineRequestsRepository, + eventNotificationCenter: repository.eventNotificationCenter, + database: database, + apiClient: apiClient, + channelListUpdater: channelListUpdater + ) + + let cid = ChannelId.unique + try prepareForSyncLocalStorage( + createUser: true, + lastSynchedEventDate: Date().addingTimeInterval(-3600), + createChannel: true, + cid: cid + ) + + // Set up active controllers and chats that would normally trigger sync operations + let chatController = ChatChannelController_Spy(client: client) + chatController.state = .remoteDataFetched + repository.startTrackingChannelController(chatController) + + let chat = Chat_Mock( + chatClient: client, + channelQuery: .init(cid: Chat_Mock.cid), + channelListQuery: nil + ) + repository.startTrackingChat(chat) + + let chatListController = ChatChannelListController_Mock(query: .init(filter: .exists(.cid)), client: client) + chatListController.state_mock = .remoteDataFetched + chatListController.channels_mock = [.mock(cid: cid)] + repository.startTrackingChannelListController(chatListController) + + // WHEN: syncLocalState is called + let expectation = expectation(description: "syncLocalState completion") + repository.syncLocalState { + expectation.fulfill() + } + waitForExpectations(timeout: defaultTimeout, handler: nil) + + // THEN: No background mode operations should run + // No /sync API calls should be made + XCTAssertEqual(apiClient.request_allRecordedCalls.count, 0) + // No refreshLoadedChannels calls should be made + XCTAssertNotCall("refreshLoadedChannels(completion:)", on: chatListController) + // No watch() calls should be made + XCTAssertNotCall("watch()", on: chat) + // Recovery mode operations may still run if isLocalStorageEnabled is true + XCTAssertCall("runQueuedRequests(completion:)", on: offlineRequestsRepository, times: 1) + XCTAssertCall("exitRecoveryMode()", on: apiClient) + } + // MARK: - Queue offline requests func test_queueOfflineRequest_localStorageDisabled() { From 7fcf18dae7ea605c2f9d21750a72b8276ac40892 Mon Sep 17 00:00:00 2001 From: Stream Bot Date: Tue, 18 Nov 2025 11:08:23 +0000 Subject: [PATCH 10/10] Bump 4.93.0 --- CHANGELOG.md | 5 +++++ README.md | 2 +- Sources/StreamChat/Generated/SystemEnvironment+Version.swift | 2 +- Sources/StreamChat/Info.plist | 2 +- Sources/StreamChatUI/Info.plist | 2 +- StreamChat-XCFramework.podspec | 2 +- StreamChat.podspec | 2 +- StreamChatArtifacts.json | 2 +- StreamChatUI-XCFramework.podspec | 2 +- StreamChatUI.podspec | 2 +- 10 files changed, 14 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 68971861e4..84d84be06b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,11 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). # Upcoming +### 🔄 Changed + +# [4.93.0](https://github.com/GetStream/stream-chat-swift/releases/tag/4.93.0) +_November 18, 2025_ + ## StreamChat ### ✅ Added - Add `ChatClientConfig.isAutomaticSyncOnReconnectEnabled` for toggling automatic syncing [#3879](https://github.com/GetStream/stream-chat-swift/pull/3879) diff --git a/README.md b/README.md index b56120f208..f739b83548 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@

- StreamChat + StreamChat StreamChatUI

diff --git a/Sources/StreamChat/Generated/SystemEnvironment+Version.swift b/Sources/StreamChat/Generated/SystemEnvironment+Version.swift index fc8290c558..5d2a7a0017 100644 --- a/Sources/StreamChat/Generated/SystemEnvironment+Version.swift +++ b/Sources/StreamChat/Generated/SystemEnvironment+Version.swift @@ -7,5 +7,5 @@ import Foundation extension SystemEnvironment { /// A Stream Chat version. - public static let version: String = "4.93.0-SNAPSHOT" + public static let version: String = "4.93.0" } diff --git a/Sources/StreamChat/Info.plist b/Sources/StreamChat/Info.plist index 1d5fb98922..a7bad28254 100644 --- a/Sources/StreamChat/Info.plist +++ b/Sources/StreamChat/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType $(PRODUCT_BUNDLE_PACKAGE_TYPE) CFBundleShortVersionString - 4.92.0 + 4.93.0 CFBundleVersion $(CURRENT_PROJECT_VERSION) diff --git a/Sources/StreamChatUI/Info.plist b/Sources/StreamChatUI/Info.plist index 1d5fb98922..a7bad28254 100644 --- a/Sources/StreamChatUI/Info.plist +++ b/Sources/StreamChatUI/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType $(PRODUCT_BUNDLE_PACKAGE_TYPE) CFBundleShortVersionString - 4.92.0 + 4.93.0 CFBundleVersion $(CURRENT_PROJECT_VERSION) diff --git a/StreamChat-XCFramework.podspec b/StreamChat-XCFramework.podspec index 6b29987002..ca004cff99 100644 --- a/StreamChat-XCFramework.podspec +++ b/StreamChat-XCFramework.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |spec| spec.name = 'StreamChat-XCFramework' - spec.version = '4.92.0' + spec.version = '4.93.0' spec.summary = 'StreamChat iOS Client' spec.description = 'stream-chat-swift is the official Swift client for Stream Chat, a service for building chat applications.' diff --git a/StreamChat.podspec b/StreamChat.podspec index 2cf51e5b3d..5769b60ab4 100644 --- a/StreamChat.podspec +++ b/StreamChat.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |spec| spec.name = 'StreamChat' - spec.version = '4.92.0' + spec.version = '4.93.0' spec.summary = 'StreamChat iOS Chat Client' spec.description = 'stream-chat-swift is the official Swift client for Stream Chat, a service for building chat applications.' diff --git a/StreamChatArtifacts.json b/StreamChatArtifacts.json index 22ee5f1ed4..f56eb4699a 100644 --- a/StreamChatArtifacts.json +++ b/StreamChatArtifacts.json @@ -1 +1 @@ -{"4.7.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.7.0/StreamChat-All.zip","4.8.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.8.0/StreamChat-All.zip","4.9.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.9.0/StreamChat-All.zip","4.10.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.10.0/StreamChat-All.zip","4.10.1":"https://github.com/GetStream/stream-chat-swift/releases/download/4.10.1/StreamChat-All.zip","4.11.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.11.0/StreamChat-All.zip","4.12.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.12.0/StreamChat-All.zip","4.13.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.13.0/StreamChat-All.zip","4.13.1":"https://github.com/GetStream/stream-chat-swift/releases/download/4.13.1/StreamChat-All.zip","4.14.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.14.0/StreamChat-All.zip","4.15.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.15.0/StreamChat-All.zip","4.15.1":"https://github.com/GetStream/stream-chat-swift/releases/download/4.15.1/StreamChat-All.zip","4.16.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.16.0/StreamChat-All.zip","4.17.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.17.0/StreamChat-All.zip","4.18.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.18.0/StreamChat-All.zip","4.19.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.19.0/StreamChat-All.zip","4.20.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.20.0/StreamChat-All.zip","4.21.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.21.0/StreamChat-All.zip","4.21.1":"https://github.com/GetStream/stream-chat-swift/releases/download/4.21.1/StreamChat-All.zip","4.21.2":"https://github.com/GetStream/stream-chat-swift/releases/download/4.21.2/StreamChat-All.zip","4.22.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.22.0/StreamChat-All.zip","4.23.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.23.0/StreamChat-All.zip","4.24.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.24.0/StreamChat-All.zip","4.24.1":"https://github.com/GetStream/stream-chat-swift/releases/download/4.24.1/StreamChat-All.zip","4.25.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.25.0/StreamChat-All.zip","4.25.1":"https://github.com/GetStream/stream-chat-swift/releases/download/4.25.1/StreamChat-All.zip","4.26.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.26.0/StreamChat-All.zip","4.27.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.27.0/StreamChat-All.zip","4.27.1":"https://github.com/GetStream/stream-chat-swift/releases/download/4.27.1/StreamChat-All.zip","4.28.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.28.0/StreamChat-All.zip","4.29.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.29.0/StreamChat-All.zip","4.30.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.30.0/StreamChat-All.zip","4.31.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.31.0/StreamChat-All.zip","4.32.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.32.0/StreamChat-All.zip","4.33.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.33.0/StreamChat-All.zip","4.34.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.34.0/StreamChat-All.zip","4.35.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.35.0/StreamChat-All.zip","4.35.1":"https://github.com/GetStream/stream-chat-swift/releases/download/4.35.1/StreamChat-All.zip","4.35.2":"https://github.com/GetStream/stream-chat-swift/releases/download/4.35.2/StreamChat-All.zip","4.36.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.36.0/StreamChat-All.zip","4.37.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.37.0/StreamChat-All.zip","4.37.1":"https://github.com/GetStream/stream-chat-swift/releases/download/4.37.1/StreamChat-All.zip","4.38.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.38.0/StreamChat-All.zip","4.39.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.39.0/StreamChat-All.zip","4.40.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.40.0/StreamChat-All.zip","4.41.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.41.0/StreamChat-All.zip","4.42.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.42.0/StreamChat-All.zip","4.43.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.43.0/StreamChat-All.zip","4.44.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.44.0/StreamChat-All.zip","4.45.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.45.0/StreamChat-All.zip","4.46.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.46.0/StreamChat-All.zip","4.47.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.47.0/StreamChat-All.zip","4.47.1":"https://github.com/GetStream/stream-chat-swift/releases/download/4.47.1/StreamChat-All.zip","4.48.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.48.0/StreamChat-All.zip","4.48.1":"https://github.com/GetStream/stream-chat-swift/releases/download/4.48.1/StreamChat-All.zip","4.49.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.49.0/StreamChat-All.zip","4.50.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.50.0/StreamChat-All.zip","4.51.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.51.0/StreamChat-All.zip","4.52.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.52.0/StreamChat-All.zip","4.53.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.53.0/StreamChat-All.zip","4.54.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.54.0/StreamChat-All.zip","4.55.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.55.0/StreamChat-All.zip","4.56.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.56.0/StreamChat-All.zip","4.56.1":"https://github.com/GetStream/stream-chat-swift/releases/download/4.56.1/StreamChat-All.zip","4.57.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.57.0/StreamChat-All.zip","4.58.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.58.0/StreamChat-All.zip","4.59.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.59.0/StreamChat-All.zip","4.60.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.60.0/StreamChat-All.zip","4.61.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.61.0/StreamChat-All.zip","4.62.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.62.0/StreamChat-All.zip","4.63.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.63.0/StreamChat-All.zip","4.64.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.64.0/StreamChat-All.zip","4.65.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.65.0/StreamChat-All.zip","4.66.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.66.0/StreamChat-All.zip","4.67.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.67.0/StreamChat-All.zip","4.68.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.68.0/StreamChat-All.zip","4.69.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.69.0/StreamChat-All.zip","4.70.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.70.0/StreamChat-All.zip","4.71.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.71.0/StreamChat-All.zip","4.72.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.72.0/StreamChat-All.zip","4.73.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.73.0/StreamChat-All.zip","4.74.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.74.0/StreamChat-All.zip","4.75.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.75.0/StreamChat-All.zip","4.76.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.76.0/StreamChat-All.zip","4.77.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.77.0/StreamChat-All.zip","4.78.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.78.0/StreamChat-All.zip","4.79.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.79.0/StreamChat-All.zip","4.79.1":"https://github.com/GetStream/stream-chat-swift/releases/download/4.79.1/StreamChat-All.zip","4.80.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.80.0/StreamChat-All.zip","4.81.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.81.0/StreamChat-All.zip","4.82.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.82.0/StreamChat-All.zip","4.83.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.83.0/StreamChat-All.zip","4.84.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.84.0/StreamChat-All.zip","4.85.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.85.0/StreamChat-All.zip","4.86.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.86.0/StreamChat-All.zip","4.87.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.87.0/StreamChat-All.zip","4.88.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.88.0/StreamChat-All.zip","4.89.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.89.0/StreamChat-All.zip","4.90.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.90.0/StreamChat-All.zip","4.91.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.91.0/StreamChat-All.zip","4.92.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.92.0/StreamChat-All.zip"} \ No newline at end of file +{"4.7.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.7.0/StreamChat-All.zip","4.8.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.8.0/StreamChat-All.zip","4.9.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.9.0/StreamChat-All.zip","4.10.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.10.0/StreamChat-All.zip","4.10.1":"https://github.com/GetStream/stream-chat-swift/releases/download/4.10.1/StreamChat-All.zip","4.11.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.11.0/StreamChat-All.zip","4.12.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.12.0/StreamChat-All.zip","4.13.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.13.0/StreamChat-All.zip","4.13.1":"https://github.com/GetStream/stream-chat-swift/releases/download/4.13.1/StreamChat-All.zip","4.14.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.14.0/StreamChat-All.zip","4.15.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.15.0/StreamChat-All.zip","4.15.1":"https://github.com/GetStream/stream-chat-swift/releases/download/4.15.1/StreamChat-All.zip","4.16.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.16.0/StreamChat-All.zip","4.17.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.17.0/StreamChat-All.zip","4.18.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.18.0/StreamChat-All.zip","4.19.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.19.0/StreamChat-All.zip","4.20.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.20.0/StreamChat-All.zip","4.21.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.21.0/StreamChat-All.zip","4.21.1":"https://github.com/GetStream/stream-chat-swift/releases/download/4.21.1/StreamChat-All.zip","4.21.2":"https://github.com/GetStream/stream-chat-swift/releases/download/4.21.2/StreamChat-All.zip","4.22.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.22.0/StreamChat-All.zip","4.23.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.23.0/StreamChat-All.zip","4.24.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.24.0/StreamChat-All.zip","4.24.1":"https://github.com/GetStream/stream-chat-swift/releases/download/4.24.1/StreamChat-All.zip","4.25.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.25.0/StreamChat-All.zip","4.25.1":"https://github.com/GetStream/stream-chat-swift/releases/download/4.25.1/StreamChat-All.zip","4.26.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.26.0/StreamChat-All.zip","4.27.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.27.0/StreamChat-All.zip","4.27.1":"https://github.com/GetStream/stream-chat-swift/releases/download/4.27.1/StreamChat-All.zip","4.28.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.28.0/StreamChat-All.zip","4.29.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.29.0/StreamChat-All.zip","4.30.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.30.0/StreamChat-All.zip","4.31.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.31.0/StreamChat-All.zip","4.32.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.32.0/StreamChat-All.zip","4.33.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.33.0/StreamChat-All.zip","4.34.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.34.0/StreamChat-All.zip","4.35.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.35.0/StreamChat-All.zip","4.35.1":"https://github.com/GetStream/stream-chat-swift/releases/download/4.35.1/StreamChat-All.zip","4.35.2":"https://github.com/GetStream/stream-chat-swift/releases/download/4.35.2/StreamChat-All.zip","4.36.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.36.0/StreamChat-All.zip","4.37.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.37.0/StreamChat-All.zip","4.37.1":"https://github.com/GetStream/stream-chat-swift/releases/download/4.37.1/StreamChat-All.zip","4.38.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.38.0/StreamChat-All.zip","4.39.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.39.0/StreamChat-All.zip","4.40.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.40.0/StreamChat-All.zip","4.41.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.41.0/StreamChat-All.zip","4.42.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.42.0/StreamChat-All.zip","4.43.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.43.0/StreamChat-All.zip","4.44.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.44.0/StreamChat-All.zip","4.45.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.45.0/StreamChat-All.zip","4.46.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.46.0/StreamChat-All.zip","4.47.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.47.0/StreamChat-All.zip","4.47.1":"https://github.com/GetStream/stream-chat-swift/releases/download/4.47.1/StreamChat-All.zip","4.48.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.48.0/StreamChat-All.zip","4.48.1":"https://github.com/GetStream/stream-chat-swift/releases/download/4.48.1/StreamChat-All.zip","4.49.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.49.0/StreamChat-All.zip","4.50.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.50.0/StreamChat-All.zip","4.51.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.51.0/StreamChat-All.zip","4.52.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.52.0/StreamChat-All.zip","4.53.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.53.0/StreamChat-All.zip","4.54.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.54.0/StreamChat-All.zip","4.55.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.55.0/StreamChat-All.zip","4.56.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.56.0/StreamChat-All.zip","4.56.1":"https://github.com/GetStream/stream-chat-swift/releases/download/4.56.1/StreamChat-All.zip","4.57.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.57.0/StreamChat-All.zip","4.58.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.58.0/StreamChat-All.zip","4.59.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.59.0/StreamChat-All.zip","4.60.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.60.0/StreamChat-All.zip","4.61.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.61.0/StreamChat-All.zip","4.62.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.62.0/StreamChat-All.zip","4.63.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.63.0/StreamChat-All.zip","4.64.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.64.0/StreamChat-All.zip","4.65.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.65.0/StreamChat-All.zip","4.66.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.66.0/StreamChat-All.zip","4.67.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.67.0/StreamChat-All.zip","4.68.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.68.0/StreamChat-All.zip","4.69.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.69.0/StreamChat-All.zip","4.70.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.70.0/StreamChat-All.zip","4.71.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.71.0/StreamChat-All.zip","4.72.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.72.0/StreamChat-All.zip","4.73.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.73.0/StreamChat-All.zip","4.74.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.74.0/StreamChat-All.zip","4.75.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.75.0/StreamChat-All.zip","4.76.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.76.0/StreamChat-All.zip","4.77.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.77.0/StreamChat-All.zip","4.78.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.78.0/StreamChat-All.zip","4.79.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.79.0/StreamChat-All.zip","4.79.1":"https://github.com/GetStream/stream-chat-swift/releases/download/4.79.1/StreamChat-All.zip","4.80.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.80.0/StreamChat-All.zip","4.81.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.81.0/StreamChat-All.zip","4.82.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.82.0/StreamChat-All.zip","4.83.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.83.0/StreamChat-All.zip","4.84.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.84.0/StreamChat-All.zip","4.85.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.85.0/StreamChat-All.zip","4.86.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.86.0/StreamChat-All.zip","4.87.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.87.0/StreamChat-All.zip","4.88.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.88.0/StreamChat-All.zip","4.89.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.89.0/StreamChat-All.zip","4.90.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.90.0/StreamChat-All.zip","4.91.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.91.0/StreamChat-All.zip","4.92.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.92.0/StreamChat-All.zip","4.93.0":"https://github.com/GetStream/stream-chat-swift/releases/download/4.93.0/StreamChat-All.zip"} \ No newline at end of file diff --git a/StreamChatUI-XCFramework.podspec b/StreamChatUI-XCFramework.podspec index 581e3124d4..435d14e6f1 100644 --- a/StreamChatUI-XCFramework.podspec +++ b/StreamChatUI-XCFramework.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |spec| spec.name = 'StreamChatUI-XCFramework' - spec.version = '4.92.0' + spec.version = '4.93.0' spec.summary = 'StreamChat UI Components' spec.description = 'StreamChatUI SDK offers flexible UI components able to display data provided by StreamChat SDK.' diff --git a/StreamChatUI.podspec b/StreamChatUI.podspec index ea2bb78e39..5bad063de7 100644 --- a/StreamChatUI.podspec +++ b/StreamChatUI.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |spec| spec.name = "StreamChatUI" - spec.version = "4.92.0" + spec.version = "4.93.0" spec.summary = "StreamChat UI Components" spec.description = "StreamChatUI SDK offers flexible UI components able to display data provided by StreamChat SDK."