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 @@

- StreamChat + StreamChat StreamChatUI

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 @@ + +