diff --git a/.github/workflows/backend-checks.yml b/.github/workflows/backend-checks.yml
new file mode 100644
index 00000000000..71be1ae63dc
--- /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/CHANGELOG.md b/CHANGELOG.md
index 58a9ec1a057..84d84be06b1 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,6 +5,17 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
### 🔄 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)
+### ⚡️ 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/DemoApp/Screens/UserProfile/UserProfileViewController.swift b/DemoApp/Screens/UserProfile/UserProfileViewController.swift
index 73bd5272031..c8d1e8d7644 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)
+ }
}
diff --git a/DemoApp/StreamChat/Components/DeliveredMessages/DemoMessageReadsInfoView.swift b/DemoApp/StreamChat/Components/DeliveredMessages/DemoMessageReadsInfoView.swift
index bf6386d905d..d42fc402a48 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)
- }
- }
}
diff --git a/Gemfile.lock b/Gemfile.lock
index 8d78597140b..83f8a705776 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/README.md b/README.md
index b56120f2080..f739b83548b 100644
--- a/README.md
+++ b/README.md
@@ -13,7 +13,7 @@
-
+
diff --git a/Sources/StreamChat/Config/ChatClientConfig.swift b/Sources/StreamChat/Config/ChatClientConfig.swift
index 7ce758736e1..3f45045bca2 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/Generated/SystemEnvironment+Version.swift b/Sources/StreamChat/Generated/SystemEnvironment+Version.swift
index 5db32bbee3f..5d2a7a00173 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"
}
diff --git a/Sources/StreamChat/Info.plist b/Sources/StreamChat/Info.plist
index 1d5fb989221..a7bad28254c 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/StreamChat/Repositories/SyncRepository.swift b/Sources/StreamChat/Repositories/SyncRepository.swift
index a3671ff913a..0fed57c2e8b 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/Sources/StreamChat/WebSocketClient/Events/AITypingEvents.swift b/Sources/StreamChat/WebSocketClient/Events/AITypingEvents.swift
index 7db59d3e52a..c66bd3737a9 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 bfc09d50077..8bbf8f997cf 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 c8fcb3d9983..0eaa70045c9 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 69263ba6087..db10dec8f39 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 efa4b30bffb..f2e7d0726a7 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 4e7edbebf54..56e0002dbc5 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 27d1c7165be..37b4b24b719 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 d8bd4f2d35b..d7d61f69cb3 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 153d096d977..be35e5084be 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 865d6f0e12a..79a6e91944a 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 f896b63f901..06292937ce0 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 714b96f2d9a..e71abe3fb4b 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 ee17af1049a..dfeb39eb9ae 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 {
diff --git a/Sources/StreamChatUI/Info.plist b/Sources/StreamChatUI/Info.plist
index 1d5fb989221..a7bad28254c 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 6b299870021..ca004cff993 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 2cf51e5b3d0..5769b60ab47 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/StreamChat.xcodeproj/project.pbxproj b/StreamChat.xcodeproj/project.pbxproj
index 5c5e2b13c02..91f358ec03d 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 471c2cbed6c..46dea19be24 100644
--- a/StreamChat.xcodeproj/xcshareddata/xcschemes/StreamChatUITestsApp.xcscheme
+++ b/StreamChat.xcodeproj/xcshareddata/xcschemes/StreamChatUITestsApp.xcscheme
@@ -35,6 +35,9 @@
+
+