diff --git a/CHANGELOG.md b/CHANGELOG.md index 123e67fc..eca98c61 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.94.0](https://github.com/GetStream/stream-chat-swiftui/releases/tag/4.94.0) +_December 02, 2025_ + +### ✅ Added +- Add the `maxGalleryAssetsCount` to the composer config [#1053](https://github.com/GetStream/stream-chat-swiftui/pull/1053) +- Expose `QuotedMessageViewContainer` [#1056](https://github.com/GetStream/stream-chat-swiftui/pull/1056) +- Add `QuotedMessageContentView` and `ViewFactory.makeQuotedMessageContentView()` [#1056](https://github.com/GetStream/stream-chat-swiftui/pull/1056) +- Allow customizing the attachment size and avatar size of the quoted message view [#1056](https://github.com/GetStream/stream-chat-swiftui/pull/1056) +### 🐞 Fixed +- Fix channel list skipping some updates on iPad [#1059](https://github.com/GetStream/stream-chat-swiftui/pull/1059) + # [4.93.0](https://github.com/GetStream/stream-chat-swiftui/releases/tag/4.93.0) _November 18, 2025_ diff --git a/Package.swift b/Package.swift index 28246de0..4a99f760 100644 --- a/Package.swift +++ b/Package.swift @@ -16,7 +16,7 @@ let package = Package( ) ], dependencies: [ - .package(url: "https://github.com/GetStream/stream-chat-swift.git", from: "4.93.0") + .package(url: "https://github.com/GetStream/stream-chat-swift.git", from: "4.94.0") ], targets: [ .target( diff --git a/README.md b/README.md index 070f3eec..d41640d2 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@

- StreamChatSwiftUI + StreamChatSwiftUI

## SwiftUI StreamChat SDK diff --git a/Sources/StreamChatSwiftUI/ChatChannel/Composer/ComposerConfig.swift b/Sources/StreamChatSwiftUI/ChatChannel/Composer/ComposerConfig.swift index 0eb5a7b2..0c4e995f 100644 --- a/Sources/StreamChatSwiftUI/ChatChannel/Composer/ComposerConfig.swift +++ b/Sources/StreamChatSwiftUI/ChatChannel/Composer/ComposerConfig.swift @@ -13,6 +13,7 @@ public struct ComposerConfig { public var inputViewCornerRadius: CGFloat public var inputFont: UIFont public var gallerySupportedTypes: GallerySupportedTypes + public var maxGalleryAssetsCount: Int? public var inputPaddingsConfig: PaddingsConfig public var adjustMessageOnSend: (String) -> (String) public var adjustMessageOnRead: (String) -> (String) @@ -33,6 +34,7 @@ public struct ComposerConfig { inputViewCornerRadius: CGFloat = 20, inputFont: UIFont = UIFont.preferredFont(forTextStyle: .body), gallerySupportedTypes: GallerySupportedTypes = .imagesAndVideo, + maxGalleryAssetsCount: Int? = nil, inputPaddingsConfig: PaddingsConfig = .composerInput, adjustMessageOnSend: @escaping (String) -> (String) = { $0 }, adjustMessageOnRead: @escaping (String) -> (String) = { $0 }, @@ -47,6 +49,7 @@ public struct ComposerConfig { self.adjustMessageOnRead = adjustMessageOnRead self.attachmentPayloadConverter = attachmentPayloadConverter self.gallerySupportedTypes = gallerySupportedTypes + self.maxGalleryAssetsCount = maxGalleryAssetsCount self.inputPaddingsConfig = inputPaddingsConfig self.isVoiceRecordingEnabled = isVoiceRecordingEnabled } diff --git a/Sources/StreamChatSwiftUI/ChatChannel/Composer/MessageComposerViewModel.swift b/Sources/StreamChatSwiftUI/ChatChannel/Composer/MessageComposerViewModel.swift index 813e7796..69b425b2 100644 --- a/Sources/StreamChatSwiftUI/ChatChannel/Composer/MessageComposerViewModel.swift +++ b/Sources/StreamChatSwiftUI/ChatChannel/Composer/MessageComposerViewModel.swift @@ -689,6 +689,9 @@ open class MessageComposerViewModel: ObservableObject { fetchOptions.predicate = predicate } fetchOptions.sortDescriptors = [NSSortDescriptor(key: "creationDate", ascending: false)] + if let maxGalleryAssetsCount = utils.composerConfig.maxGalleryAssetsCount { + fetchOptions.fetchLimit = maxGalleryAssetsCount + } let assets = PHAsset.fetchAssets(with: fetchOptions) DispatchQueue.main.asyncAfter(deadline: .now() + 0.25) { [weak self] in self?.imageAssets = assets diff --git a/Sources/StreamChatSwiftUI/ChatChannel/MessageList/QuotedMessageView.swift b/Sources/StreamChatSwiftUI/ChatChannel/MessageList/QuotedMessageView.swift index 97847ae9..11a46476 100644 --- a/Sources/StreamChatSwiftUI/ChatChannel/MessageList/QuotedMessageView.swift +++ b/Sources/StreamChatSwiftUI/ChatChannel/MessageList/QuotedMessageView.swift @@ -6,40 +6,60 @@ import StreamChat import SwiftUI /// Container showing the quoted message view with the user avatar. -struct QuotedMessageViewContainer: View { - private let avatarSize: CGFloat = 24 +public struct QuotedMessageViewContainer: View { + public var factory: Factory + public var quotedMessage: ChatMessage + public var fillAvailableSpace: Bool + public var forceLeftToRight: Bool + @Binding public var scrolledId: String? + public let attachmentSize: CGSize + public let quotedAuthorAvatarSize: CGSize - var factory: Factory - var quotedMessage: ChatMessage - var fillAvailableSpace: Bool - var forceLeftToRight = false - @Binding var scrolledId: String? + public init( + factory: Factory, + quotedMessage: ChatMessage, + fillAvailableSpace: Bool, + forceLeftToRight: Bool = false, + scrolledId: Binding, + attachmentSize: CGSize = CGSize(width: 36, height: 36), + quotedAuthorAvatarSize: CGSize = CGSize(width: 24, height: 24) + ) { + self.factory = factory + self.quotedMessage = quotedMessage + self.fillAvailableSpace = fillAvailableSpace + self.forceLeftToRight = forceLeftToRight + _scrolledId = scrolledId + self.attachmentSize = attachmentSize + self.quotedAuthorAvatarSize = quotedAuthorAvatarSize + } - var body: some View { + public var body: some View { HStack(alignment: .bottom) { if !quotedMessage.isSentByCurrentUser || forceLeftToRight { factory.makeQuotedMessageAvatarView( for: quotedMessage.authorDisplayInfo, - size: CGSize(width: avatarSize, height: avatarSize) + size: quotedAuthorAvatarSize ) QuotedMessageView( factory: factory, quotedMessage: quotedMessage, fillAvailableSpace: fillAvailableSpace, - forceLeftToRight: forceLeftToRight + forceLeftToRight: forceLeftToRight, + attachmentSize: attachmentSize ) } else { QuotedMessageView( factory: factory, quotedMessage: quotedMessage, fillAvailableSpace: fillAvailableSpace, - forceLeftToRight: forceLeftToRight + forceLeftToRight: forceLeftToRight, + attachmentSize: attachmentSize ) factory.makeQuotedMessageAvatarView( for: quotedMessage.authorDisplayInfo, - size: CGSize(width: avatarSize, height: avatarSize) + size: quotedAuthorAvatarSize ) } } @@ -62,14 +82,13 @@ public struct QuotedMessageView: View { @Injected(\.fonts) private var fonts @Injected(\.colors) private var colors @Injected(\.utils) private var utils - - private let attachmentWidth: CGFloat = 36 public var factory: Factory public var quotedMessage: ChatMessage public var fillAvailableSpace: Bool public var forceLeftToRight: Bool - + public let attachmentSize: CGSize + private var messageTypeResolver: MessageTypeResolving { utils.messageTypeResolver } @@ -78,73 +97,25 @@ public struct QuotedMessageView: View { factory: Factory, quotedMessage: ChatMessage, fillAvailableSpace: Bool, - forceLeftToRight: Bool + forceLeftToRight: Bool, + attachmentSize: CGSize = CGSize(width: 36, height: 36) ) { self.factory = factory self.quotedMessage = quotedMessage self.fillAvailableSpace = fillAvailableSpace self.forceLeftToRight = forceLeftToRight + self.attachmentSize = attachmentSize } public var body: some View { HStack(alignment: .top) { - if !quotedMessage.attachmentCounts.isEmpty { - ZStack { - if messageTypeResolver.hasCustomAttachment(message: quotedMessage) { - factory.makeCustomAttachmentQuotedView(for: quotedMessage) - } else if hasVoiceAttachments { - VoiceRecordingPreview(voiceAttachment: quotedMessage.voiceRecordingAttachments[0].payload) - } else if !quotedMessage.imageAttachments.isEmpty { - LazyLoadingImage( - source: MediaAttachment(url: quotedMessage.imageAttachments[0].imageURL, type: .image), - width: attachmentWidth, - height: attachmentWidth, - resize: false - ) - } else if !quotedMessage.giphyAttachments.isEmpty { - LazyGiphyView( - source: quotedMessage.giphyAttachments[0].previewURL, - width: attachmentWidth - ) - } else if !quotedMessage.fileAttachments.isEmpty { - Image(uiImage: filePreviewImage(for: quotedMessage.fileAttachments[0].assetURL)) - } else if !quotedMessage.videoAttachments.isEmpty { - VideoAttachmentView( - attachment: quotedMessage.videoAttachments[0], - message: quotedMessage, - width: attachmentWidth, - ratio: 1.0, - cornerRadius: 0 - ) - } else if !quotedMessage.linkAttachments.isEmpty { - LazyImage( - imageURL: quotedMessage.linkAttachments[0].previewURL ?? quotedMessage.linkAttachments[0] - .originalURL - ) - .onDisappear(.cancel) - .processors([ImageProcessors.Resize(width: attachmentWidth)]) - .priority(.high) - } - } - .frame(width: hasVoiceAttachments ? nil : attachmentWidth, height: attachmentWidth) - .aspectRatio(1, contentMode: .fill) - .clipShape(RoundedRectangle(cornerRadius: 8)) - .allowsHitTesting(false) - } else if let poll = quotedMessage.poll, !quotedMessage.isDeleted { - Text("📊 \(poll.name)") - } - - if !hasVoiceAttachments { - Text(textForMessage) - .foregroundColor(textColor(for: quotedMessage)) - .lineLimit(3) - .font(fonts.footnote) - .accessibility(identifier: "quotedMessageText") - } - - if fillAvailableSpace { - Spacer() - } + factory.makeQuotedMessageContentView( + options: QuotedMessageContentViewOptions( + quotedMessage: quotedMessage, + fillAvailableSpace: fillAvailableSpace, + attachmentSize: attachmentSize + ) + ) } .id(quotedMessage.messageId) .padding( @@ -174,6 +145,121 @@ public struct QuotedMessageView: View { colors.quotedMessageBackgroundCurrentUser : colors.quotedMessageBackgroundOtherUser return color } + + private var hasVoiceAttachments: Bool { + !quotedMessage.voiceRecordingAttachments.isEmpty + } +} + +/// Options for configuring the quoted message content view. +public struct QuotedMessageContentViewOptions { + /// The quoted message to display. + public let quotedMessage: ChatMessage + /// Whether the quoted container should take all the available space. + public let fillAvailableSpace: Bool + /// The size of the attachment preview. + public let attachmentSize: CGSize + + public init( + quotedMessage: ChatMessage, + fillAvailableSpace: Bool, + attachmentSize: CGSize = CGSize(width: 36, height: 36) + ) { + self.quotedMessage = quotedMessage + self.fillAvailableSpace = fillAvailableSpace + self.attachmentSize = attachmentSize + } +} + +/// The quoted message content view. +/// +/// It is the view that is embedded in quoted message bubble view. +public struct QuotedMessageContentView: View { + @Environment(\.channelTranslationLanguage) var translationLanguage + + @Injected(\.images) private var images + @Injected(\.fonts) private var fonts + @Injected(\.colors) private var colors + @Injected(\.utils) private var utils + + public var factory: Factory + public var options: QuotedMessageContentViewOptions + + private var quotedMessage: ChatMessage { + options.quotedMessage + } + + private var messageTypeResolver: MessageTypeResolving { + utils.messageTypeResolver + } + + public init( + factory: Factory, + options: QuotedMessageContentViewOptions + ) { + self.factory = factory + self.options = options + } + + public var body: some View { + if !quotedMessage.attachmentCounts.isEmpty { + ZStack { + if messageTypeResolver.hasCustomAttachment(message: quotedMessage) { + factory.makeCustomAttachmentQuotedView(for: quotedMessage) + } else if hasVoiceAttachments { + VoiceRecordingPreview(voiceAttachment: quotedMessage.voiceRecordingAttachments[0].payload) + } else if !quotedMessage.imageAttachments.isEmpty { + LazyLoadingImage( + source: MediaAttachment(url: quotedMessage.imageAttachments[0].imageURL, type: .image), + width: options.attachmentSize.width, + height: options.attachmentSize.height, + resize: false + ) + } else if !quotedMessage.giphyAttachments.isEmpty { + LazyGiphyView( + source: quotedMessage.giphyAttachments[0].previewURL, + width: options.attachmentSize.width + ) + } else if !quotedMessage.fileAttachments.isEmpty { + Image(uiImage: filePreviewImage(for: quotedMessage.fileAttachments[0].assetURL)) + } else if !quotedMessage.videoAttachments.isEmpty { + VideoAttachmentView( + attachment: quotedMessage.videoAttachments[0], + message: quotedMessage, + width: options.attachmentSize.width, + ratio: 1.0, + cornerRadius: 0 + ) + } else if !quotedMessage.linkAttachments.isEmpty { + LazyImage( + imageURL: quotedMessage.linkAttachments[0].previewURL ?? quotedMessage.linkAttachments[0] + .originalURL + ) + .onDisappear(.cancel) + .processors([ImageProcessors.Resize(width: options.attachmentSize.width)]) + .priority(.high) + } + } + .frame(width: hasVoiceAttachments ? nil : options.attachmentSize.width, height: options.attachmentSize.height) + .aspectRatio(1, contentMode: .fill) + .clipShape(RoundedRectangle(cornerRadius: 8)) + .allowsHitTesting(false) + } else if let poll = quotedMessage.poll, !quotedMessage.isDeleted { + Text("📊 \(poll.name)") + } + + if !hasVoiceAttachments { + Text(textForMessage) + .foregroundColor(textColor(for: quotedMessage)) + .lineLimit(3) + .font(fonts.footnote) + .accessibility(identifier: "quotedMessageText") + } + + if options.fillAvailableSpace { + Spacer() + } + } private func filePreviewImage(for url: URL) -> UIImage { let iconName = url.pathExtension diff --git a/Sources/StreamChatSwiftUI/ChatChannelList/ChatChannelListViewModel.swift b/Sources/StreamChatSwiftUI/ChatChannelList/ChatChannelListViewModel.swift index d799d56c..c09941c3 100644 --- a/Sources/StreamChatSwiftUI/ChatChannelList/ChatChannelListViewModel.swift +++ b/Sources/StreamChatSwiftUI/ChatChannelList/ChatChannelListViewModel.swift @@ -25,18 +25,24 @@ open class ChatChannelListViewModel: ObservableObject, ChatChannelListController /// Used when screen is shown from a deeplink. private var selectedChannelId: String? - - /// Temporarly holding changes while message list is shown. - private var queuedChannelsChanges = LazyCachedMapCollection() - + private var timer: Timer? /// Controls loading the channels. public private(set) var loadingNextChannels: Bool = false - /// Checks if the queued changes are completely applied. - private var markDirty = false - + /// True, if channel updates were skipped and are applied when selectedChannel is set to nil + private var skippedChannelUpdates = false + + /// True, if channel updates can be skipped for optimizing view refreshes while showing message list. + /// + /// - Important: Only meant for stacked navigation view style. + private var canSkipChannelUpdates: Bool { + guard isIphone || !utils.messageListConfig.iPadSplitViewEnabled else { return false } + guard selectedChannel != nil || !searchText.isEmpty else { return false } + return true + } + /// Index of the selected channel. private var selectedChannelIndex: Int? @@ -44,23 +50,15 @@ open class ChatChannelListViewModel: ObservableObject, ChatChannelListController @Published public var scrolledChannelId: String? /// Published variables. - @Published public var channels = LazyCachedMapCollection() { - didSet { - if !markDirty { - queuedChannelsChanges = [] - } else { - markDirty = false - } - } - } + @Published public var channels = LazyCachedMapCollection() @Published public var selectedChannel: ChannelSelectionInfo? { willSet { hideTabBar = newValue != nil if selectedChannel != nil && newValue == nil { // pop happened, apply the queued changes. - if !queuedChannelsChanges.isEmpty { - channels = queuedChannelsChanges + if skippedChannelUpdates { + updateChannels() } } if newValue == nil { @@ -317,8 +315,8 @@ open class ChatChannelListViewModel: ObservableObject, ChatChannelListController // MARK: - private private func handleChannelListChanges(_ controller: ChatChannelListController) { - if selectedChannel != nil || !searchText.isEmpty { - queuedChannelsChanges = controller.channels + if canSkipChannelUpdates { + skippedChannelUpdates = true updateChannelsIfNeeded() } else { channels = controller.channels @@ -537,15 +535,16 @@ open class ChatChannelListViewModel: ObservableObject, ChatChannelListController } private func updateChannels() { + skippedChannelUpdates = false channels = controller?.channels ?? LazyCachedMapCollection() } private func handleChannelAppearance() { - if !queuedChannelsChanges.isEmpty && selectedChannel == nil { - channels = queuedChannelsChanges - } else if !queuedChannelsChanges.isEmpty { - handleQueuedChanges() - } else if queuedChannelsChanges.isEmpty && selectedChannel != nil { + if skippedChannelUpdates && selectedChannel == nil { + updateChannels() + } else if skippedChannelUpdates { + updateSelectedChannelData() + } else if !skippedChannelUpdates && selectedChannel != nil { if selectedChannel?.injectedChannelInfo == nil { selectedChannel?.injectedChannelInfo = InjectedChannelInfo(unreadCount: 0) } @@ -562,10 +561,10 @@ open class ChatChannelListViewModel: ObservableObject, ChatChannelListController } } - private func handleQueuedChanges() { + private func updateSelectedChannelData() { let selected = selectedChannel?.channel var index: Int? - var temp = Array(queuedChannelsChanges) + var temp = Array(controller?.channels ?? []) for i in 0.. some View { + QuotedMessageContentView( + factory: self, + options: options + ) + } + public func makeCustomAttachmentQuotedView(for message: ChatMessage) -> some View { EmptyView() } diff --git a/Sources/StreamChatSwiftUI/Generated/SystemEnvironment+Version.swift b/Sources/StreamChatSwiftUI/Generated/SystemEnvironment+Version.swift index 3265af9b..4ed7957e 100644 --- a/Sources/StreamChatSwiftUI/Generated/SystemEnvironment+Version.swift +++ b/Sources/StreamChatSwiftUI/Generated/SystemEnvironment+Version.swift @@ -7,5 +7,5 @@ import Foundation enum SystemEnvironment { /// A Stream Chat version. - public static let version: String = "4.93.0" + public static let version: String = "4.94.0" } diff --git a/Sources/StreamChatSwiftUI/Info.plist b/Sources/StreamChatSwiftUI/Info.plist index 3e93a0a5..9b997804 100644 --- a/Sources/StreamChatSwiftUI/Info.plist +++ b/Sources/StreamChatSwiftUI/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType $(PRODUCT_BUNDLE_PACKAGE_TYPE) CFBundleShortVersionString - 4.93.0 + 4.94.0 CFBundleVersion $(CURRENT_PROJECT_VERSION) NSPhotoLibraryUsageDescription diff --git a/Sources/StreamChatSwiftUI/ViewFactory.swift b/Sources/StreamChatSwiftUI/ViewFactory.swift index 0439b8d1..79e13aaf 100644 --- a/Sources/StreamChatSwiftUI/ViewFactory.swift +++ b/Sources/StreamChatSwiftUI/ViewFactory.swift @@ -1015,6 +1015,18 @@ public protocol ViewFactory: AnyObject { scrolledId: Binding ) -> QuotedMessageViewType + associatedtype QuotedMessageContentViewType: View + /// Creates the quoted message content view. + /// + /// It is the view that is embedded in quoted message bubble view. + /// + /// - Parameters: + /// - options: configuration options for the quoted message content view. + /// - Returns: view displayed in the quoted message content slot. + func makeQuotedMessageContentView( + options: QuotedMessageContentViewOptions + ) -> QuotedMessageContentViewType + associatedtype CustomAttachmentQuotedViewType: View /// Creates a quoted view for custom attachments. Returns `EmptyView` by default. /// - Parameter message: the quoted message. diff --git a/StreamChatSwiftUI-XCFramework.podspec b/StreamChatSwiftUI-XCFramework.podspec index 0cf88805..7fa4c141 100644 --- a/StreamChatSwiftUI-XCFramework.podspec +++ b/StreamChatSwiftUI-XCFramework.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |spec| spec.name = 'StreamChatSwiftUI-XCFramework' - spec.version = '4.93.0' + spec.version = '4.94.0' spec.summary = 'StreamChat SwiftUI Chat Components' spec.description = 'StreamChatSwiftUI SDK offers flexible SwiftUI components able to display data provided by StreamChat SDK.' @@ -19,7 +19,7 @@ Pod::Spec.new do |spec| spec.framework = 'Foundation', 'UIKit', 'SwiftUI' - spec.dependency 'StreamChat-XCFramework', '~> 4.93.0' + spec.dependency 'StreamChat-XCFramework', '~> 4.94.0' spec.cocoapods_version = '>= 1.11.0' end diff --git a/StreamChatSwiftUI.podspec b/StreamChatSwiftUI.podspec index 5b25b08f..8c410f61 100644 --- a/StreamChatSwiftUI.podspec +++ b/StreamChatSwiftUI.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |spec| spec.name = 'StreamChatSwiftUI' - spec.version = '4.93.0' + spec.version = '4.94.0' spec.summary = 'StreamChat SwiftUI Chat Components' spec.description = 'StreamChatSwiftUI SDK offers flexible SwiftUI components able to display data provided by StreamChat SDK.' @@ -19,5 +19,5 @@ Pod::Spec.new do |spec| spec.framework = 'Foundation', 'UIKit', 'SwiftUI' - spec.dependency 'StreamChat', '~> 4.93.0' + spec.dependency 'StreamChat', '~> 4.94.0' end diff --git a/StreamChatSwiftUI.xcodeproj/project.pbxproj b/StreamChatSwiftUI.xcodeproj/project.pbxproj index 9eb15a08..bad6912d 100644 --- a/StreamChatSwiftUI.xcodeproj/project.pbxproj +++ b/StreamChatSwiftUI.xcodeproj/project.pbxproj @@ -3946,7 +3946,7 @@ repositoryURL = "https://github.com/GetStream/stream-chat-swift.git"; requirement = { kind = upToNextMajorVersion; - minimumVersion = 4.93.0; + minimumVersion = 4.94.0; }; }; E3A1C01A282BAC66002D1E26 /* XCRemoteSwiftPackageReference "sentry-cocoa" */ = { diff --git a/StreamChatSwiftUIArtifacts.json b/StreamChatSwiftUIArtifacts.json index af7772a8..20670883 100644 --- a/StreamChatSwiftUIArtifacts.json +++ b/StreamChatSwiftUIArtifacts.json @@ -1 +1 @@ -{"4.40.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.40.0/StreamChatSwiftUI.zip","4.41.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.41.0/StreamChatSwiftUI.zip","4.42.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.42.0/StreamChatSwiftUI.zip","4.43.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.43.0/StreamChatSwiftUI.zip","4.44.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.44.0/StreamChatSwiftUI.zip","4.45.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.45.0/StreamChatSwiftUI.zip","4.46.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.46.0/StreamChatSwiftUI.zip","4.47.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.47.0/StreamChatSwiftUI.zip","4.47.1":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.47.1/StreamChatSwiftUI.zip","4.48.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.48.0/StreamChatSwiftUI.zip","4.49.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.49.0/StreamChatSwiftUI.zip","4.50.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.50.0/StreamChatSwiftUI.zip","4.50.1":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.50.1/StreamChatSwiftUI.zip","4.51.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.51.0/StreamChatSwiftUI.zip","4.52.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.52.0/StreamChatSwiftUI.zip","4.53.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.53.0/StreamChatSwiftUI.zip","4.54.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.54.0/StreamChatSwiftUI.zip","4.55.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.55.0/StreamChatSwiftUI.zip","4.56.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.56.0/StreamChatSwiftUI.zip","4.57.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.57.0/StreamChatSwiftUI.zip","4.58.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.58.0/StreamChatSwiftUI.zip","4.59.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.59.0/StreamChatSwiftUI.zip","4.60.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.60.0/StreamChatSwiftUI.zip","4.61.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.61.0/StreamChatSwiftUI.zip","4.62.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.62.0/StreamChatSwiftUI.zip","4.63.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.63.0/StreamChatSwiftUI.zip","4.64.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.64.0/StreamChatSwiftUI.zip","4.65.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.65.0/StreamChatSwiftUI.zip","4.66.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.66.0/StreamChatSwiftUI.zip","4.67.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.67.0/StreamChatSwiftUI.zip","4.68.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.68.0/StreamChatSwiftUI.zip","4.69.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.69.0/StreamChatSwiftUI.zip","4.70.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.70.0/StreamChatSwiftUI.zip","4.71.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.71.0/StreamChatSwiftUI.zip","4.72.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.72.0/StreamChatSwiftUI.zip","4.73.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.73.0/StreamChatSwiftUI.zip","4.74.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.74.0/StreamChatSwiftUI.zip","4.75.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.75.0/StreamChatSwiftUI.zip","4.76.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.76.0/StreamChatSwiftUI.zip","4.77.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.77.0/StreamChatSwiftUI.zip","4.78.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.78.0/StreamChatSwiftUI.zip","4.79.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.79.0/StreamChatSwiftUI.zip","4.79.1":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.79.1/StreamChatSwiftUI.zip","4.80.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.80.0/StreamChatSwiftUI.zip","4.81.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.81.0/StreamChatSwiftUI.zip","4.82.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.82.0/StreamChatSwiftUI.zip","4.83.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.83.0/StreamChatSwiftUI.zip","4.84.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.84.0/StreamChatSwiftUI.zip","4.85.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.85.0/StreamChatSwiftUI.zip","4.86.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.86.0/StreamChatSwiftUI.zip","4.87.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.87.0/StreamChatSwiftUI.zip","4.88.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.88.0/StreamChatSwiftUI.zip","4.89.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.89.0/StreamChatSwiftUI.zip","4.89.1":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.89.1/StreamChatSwiftUI.zip","4.90.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.90.0/StreamChatSwiftUI.zip","4.91.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.91.0/StreamChatSwiftUI.zip","4.92.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.92.0/StreamChatSwiftUI.zip","4.93.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.93.0/StreamChatSwiftUI.zip"} \ No newline at end of file +{"4.40.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.40.0/StreamChatSwiftUI.zip","4.41.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.41.0/StreamChatSwiftUI.zip","4.42.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.42.0/StreamChatSwiftUI.zip","4.43.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.43.0/StreamChatSwiftUI.zip","4.44.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.44.0/StreamChatSwiftUI.zip","4.45.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.45.0/StreamChatSwiftUI.zip","4.46.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.46.0/StreamChatSwiftUI.zip","4.47.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.47.0/StreamChatSwiftUI.zip","4.47.1":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.47.1/StreamChatSwiftUI.zip","4.48.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.48.0/StreamChatSwiftUI.zip","4.49.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.49.0/StreamChatSwiftUI.zip","4.50.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.50.0/StreamChatSwiftUI.zip","4.50.1":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.50.1/StreamChatSwiftUI.zip","4.51.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.51.0/StreamChatSwiftUI.zip","4.52.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.52.0/StreamChatSwiftUI.zip","4.53.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.53.0/StreamChatSwiftUI.zip","4.54.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.54.0/StreamChatSwiftUI.zip","4.55.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.55.0/StreamChatSwiftUI.zip","4.56.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.56.0/StreamChatSwiftUI.zip","4.57.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.57.0/StreamChatSwiftUI.zip","4.58.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.58.0/StreamChatSwiftUI.zip","4.59.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.59.0/StreamChatSwiftUI.zip","4.60.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.60.0/StreamChatSwiftUI.zip","4.61.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.61.0/StreamChatSwiftUI.zip","4.62.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.62.0/StreamChatSwiftUI.zip","4.63.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.63.0/StreamChatSwiftUI.zip","4.64.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.64.0/StreamChatSwiftUI.zip","4.65.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.65.0/StreamChatSwiftUI.zip","4.66.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.66.0/StreamChatSwiftUI.zip","4.67.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.67.0/StreamChatSwiftUI.zip","4.68.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.68.0/StreamChatSwiftUI.zip","4.69.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.69.0/StreamChatSwiftUI.zip","4.70.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.70.0/StreamChatSwiftUI.zip","4.71.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.71.0/StreamChatSwiftUI.zip","4.72.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.72.0/StreamChatSwiftUI.zip","4.73.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.73.0/StreamChatSwiftUI.zip","4.74.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.74.0/StreamChatSwiftUI.zip","4.75.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.75.0/StreamChatSwiftUI.zip","4.76.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.76.0/StreamChatSwiftUI.zip","4.77.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.77.0/StreamChatSwiftUI.zip","4.78.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.78.0/StreamChatSwiftUI.zip","4.79.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.79.0/StreamChatSwiftUI.zip","4.79.1":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.79.1/StreamChatSwiftUI.zip","4.80.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.80.0/StreamChatSwiftUI.zip","4.81.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.81.0/StreamChatSwiftUI.zip","4.82.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.82.0/StreamChatSwiftUI.zip","4.83.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.83.0/StreamChatSwiftUI.zip","4.84.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.84.0/StreamChatSwiftUI.zip","4.85.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.85.0/StreamChatSwiftUI.zip","4.86.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.86.0/StreamChatSwiftUI.zip","4.87.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.87.0/StreamChatSwiftUI.zip","4.88.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.88.0/StreamChatSwiftUI.zip","4.89.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.89.0/StreamChatSwiftUI.zip","4.89.1":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.89.1/StreamChatSwiftUI.zip","4.90.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.90.0/StreamChatSwiftUI.zip","4.91.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.91.0/StreamChatSwiftUI.zip","4.92.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.92.0/StreamChatSwiftUI.zip","4.93.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.93.0/StreamChatSwiftUI.zip","4.94.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.94.0/StreamChatSwiftUI.zip"} \ No newline at end of file diff --git a/StreamChatSwiftUITests/Infrastructure/Mocks/APIClient_Mock.swift b/StreamChatSwiftUITests/Infrastructure/Mocks/APIClient_Mock.swift index b22aa221..a12b986e 100644 --- a/StreamChatSwiftUITests/Infrastructure/Mocks/APIClient_Mock.swift +++ b/StreamChatSwiftUITests/Infrastructure/Mocks/APIClient_Mock.swift @@ -64,7 +64,8 @@ class APIClientMock: APIClient, StreamChatTestTools.Spy { requestEncoder: requestEncoder, requestDecoder: requestDecoder, attachmentDownloader: StreamAttachmentDownloader(sessionConfiguration: sessionConfiguration), - attachmentUploader: attachmentUploader + attachmentUploader: attachmentUploader, + cdnClient: CDNClient ) } diff --git a/StreamChatSwiftUITests/Infrastructure/Mocks/CDNClient_Mock.swift b/StreamChatSwiftUITests/Infrastructure/Mocks/CDNClient_Mock.swift index 37c1cbdd..4ba2b103 100644 --- a/StreamChatSwiftUITests/Infrastructure/Mocks/CDNClient_Mock.swift +++ b/StreamChatSwiftUITests/Infrastructure/Mocks/CDNClient_Mock.swift @@ -6,6 +6,16 @@ import Foundation @testable import StreamChat final class CDNClient_Mock: CDNClient { + lazy var deleteAttachmentMockFunc = MockFunc.mock(for: deleteAttachment) + func deleteAttachment(remoteUrl: URL, completion: @escaping ((any Error)?) -> Void) { + deleteAttachmentMockFunc.callAndReturn( + ( + remoteUrl, + completion + ) + ) + } + static var maxAttachmentSize: Int64 = .max lazy var uploadAttachmentMockFunc = MockFunc.mock(for: uploadAttachment) diff --git a/StreamChatSwiftUITests/Tests/ChatChannel/QuotedMessageView_Tests.swift b/StreamChatSwiftUITests/Tests/ChatChannel/QuotedMessageView_Tests.swift index 5aab0565..00db066f 100644 --- a/StreamChatSwiftUITests/Tests/ChatChannel/QuotedMessageView_Tests.swift +++ b/StreamChatSwiftUITests/Tests/ChatChannel/QuotedMessageView_Tests.swift @@ -113,4 +113,229 @@ class QuotedMessageView_Tests: StreamChatTestCase { // Then assertSnapshot(matching: view, as: .image(perceptualPrecision: precision)) } + + // MARK: - Custom Size Tests + + func test_quotedMessageViewContainer_customAttachmentSize_snapshot() { + // Given + let message = ChatMessage.mock( + id: "test", + cid: .unique, + text: "Image attachment", + author: .mock(id: "test", name: "martin"), + attachments: [ + ChatMessageImageAttachment.mock( + id: .unique, + imageURL: .localYodaImage + ).asAnyAttachment + ] + ) + let customAttachmentSize = CGSize(width: 60, height: 60) + let view = QuotedMessageViewContainer( + factory: DefaultViewFactory.shared, + quotedMessage: message, + fillAvailableSpace: true, + scrolledId: .constant(nil), + attachmentSize: customAttachmentSize + ) + .applyDefaultSize() + + // Then + assertSnapshot(matching: view, as: .image(perceptualPrecision: precision)) + } + + func test_quotedMessageViewContainer_customAvatarSize_snapshot() { + // Given + let customAvatarSize = CGSize(width: 40, height: 40) + let view = QuotedMessageViewContainer( + factory: DefaultViewFactory.shared, + quotedMessage: testMessage, + fillAvailableSpace: true, + scrolledId: .constant(nil), + quotedAuthorAvatarSize: customAvatarSize + ) + .applyDefaultSize() + + // Then + assertSnapshot(matching: view, as: .image(perceptualPrecision: precision)) + } + + func test_quotedMessageView_customAttachmentSize_snapshot() { + // Given + let message = ChatMessage.mock( + id: "test", + cid: .unique, + text: "Image attachment", + author: .mock(id: "test", name: "martin"), + attachments: [ + ChatMessageImageAttachment.mock( + id: .unique, + imageURL: .localYodaImage + ).asAnyAttachment + ] + ) + let customAttachmentSize = CGSize(width: 50, height: 50) + let view = QuotedMessageView( + factory: DefaultViewFactory.shared, + quotedMessage: message, + fillAvailableSpace: true, + forceLeftToRight: true, + attachmentSize: customAttachmentSize + ) + .applyDefaultSize() + + // Then + assertSnapshot(matching: view, as: .image(perceptualPrecision: precision)) + } + + func test_quotedMessageViewContainer_defaultSizes() { + // Given + let container = QuotedMessageViewContainer( + factory: DefaultViewFactory.shared, + quotedMessage: testMessage, + fillAvailableSpace: true, + scrolledId: .constant(nil) + ) + + // Then - Default sizes should be applied + XCTAssertEqual(container.attachmentSize, CGSize(width: 36, height: 36)) + XCTAssertEqual(container.quotedAuthorAvatarSize, CGSize(width: 24, height: 24)) + } + + func test_quotedMessageView_defaultAttachmentSize() { + // Given + let view = QuotedMessageView( + factory: DefaultViewFactory.shared, + quotedMessage: testMessage, + fillAvailableSpace: true, + forceLeftToRight: true + ) + + // Then - Default attachment size should be applied + XCTAssertEqual(view.attachmentSize, CGSize(width: 36, height: 36)) + } + + func test_quotedMessageView_customContentView_snapshot() { + // Given - Create a custom football game result attachment + let footballGamePayload = FootballGameAttachmentPayload( + homeTeam: "Benfica", + awayTeam: "Porto", + homeScore: 2, + awayScore: 0 + ) + + let customAttachment = ChatMessageAttachment( + id: .unique, + type: .init(rawValue: "football_game"), + payload: footballGamePayload, + downloadingState: nil, + uploadingState: nil + ).asAnyAttachment + + let message = ChatMessage.mock( + id: "test", + cid: .unique, + text: "Check out this game result!", + author: .mock(id: "test", name: "martin"), + attachments: [customAttachment] + ) + + let view = QuotedMessageViewContainer( + factory: CustomQuotedContentViewFactory.shared, + quotedMessage: message, + fillAvailableSpace: false, + scrolledId: .constant(nil) + ) + .applyDefaultSize() + + // Then + assertSnapshot(matching: view, as: .image(perceptualPrecision: precision)) + } +} + +// MARK: - Custom Football Game Attachment for Custom Quoted Message + +private struct FootballGameAttachmentPayload: AttachmentPayload { + let homeTeam: String + let awayTeam: String + let homeScore: Int + let awayScore: Int + + static let type: AttachmentType = .init(rawValue: "football_game") +} + +private class CustomQuotedContentViewFactory: ViewFactory { + @Injected(\.chatClient) var chatClient + + private init() {} + + static let shared = CustomQuotedContentViewFactory() + + func makeQuotedMessageContentView( + options: QuotedMessageContentViewOptions + ) -> some View { + Group { + if let footballGameAttachmentPayload = options.quotedMessage + .attachments(payloadType: FootballGameAttachmentPayload.self) + .first? + .payload { + // Show custom football game result view + FootballGameQuotedView(payload: footballGameAttachmentPayload) + } else { + // Fallback to default content view + QuotedMessageContentView( + factory: self, + options: options + ) + } + } + } +} + +private struct FootballGameQuotedView: View { + @Injected(\.colors) private var colors + @Injected(\.fonts) private var fonts + + let payload: FootballGameAttachmentPayload + + var body: some View { + HStack(spacing: 8) { + VStack(alignment: .center, spacing: 4) { + Text("⚽") + .font(.title2) + Text("Match") + .font(fonts.footnoteBold) + .foregroundColor(Color(colors.textLowEmphasis)) + } + + Divider() + .frame(height: 50) + + VStack(spacing: 8) { + HStack { + Text(payload.homeTeam) + .font(fonts.bodyBold) + .foregroundColor(Color(colors.text)) + Spacer() + Text("\(payload.homeScore)") + .font(fonts.title) + .foregroundColor(Color(colors.text)) + .frame(minWidth: 30) + } + + HStack { + Text(payload.awayTeam) + .font(fonts.bodyBold) + .foregroundColor(Color(colors.text)) + Spacer() + Text("\(payload.awayScore)") + .font(fonts.title) + .foregroundColor(Color(colors.text)) + .frame(minWidth: 30) + } + } + } + .padding(8) + .frame(minWidth: 200) + } } diff --git a/StreamChatSwiftUITests/Tests/ChatChannel/__Snapshots__/QuotedMessageView_Tests/test_quotedMessageViewContainer_customAttachmentSize_snapshot.1.png b/StreamChatSwiftUITests/Tests/ChatChannel/__Snapshots__/QuotedMessageView_Tests/test_quotedMessageViewContainer_customAttachmentSize_snapshot.1.png new file mode 100644 index 00000000..91baed12 Binary files /dev/null and b/StreamChatSwiftUITests/Tests/ChatChannel/__Snapshots__/QuotedMessageView_Tests/test_quotedMessageViewContainer_customAttachmentSize_snapshot.1.png differ diff --git a/StreamChatSwiftUITests/Tests/ChatChannel/__Snapshots__/QuotedMessageView_Tests/test_quotedMessageViewContainer_customAvatarSize_snapshot.1.png b/StreamChatSwiftUITests/Tests/ChatChannel/__Snapshots__/QuotedMessageView_Tests/test_quotedMessageViewContainer_customAvatarSize_snapshot.1.png new file mode 100644 index 00000000..2bb05ee4 Binary files /dev/null and b/StreamChatSwiftUITests/Tests/ChatChannel/__Snapshots__/QuotedMessageView_Tests/test_quotedMessageViewContainer_customAvatarSize_snapshot.1.png differ diff --git a/StreamChatSwiftUITests/Tests/ChatChannel/__Snapshots__/QuotedMessageView_Tests/test_quotedMessageView_customAttachmentSize_snapshot.1.png b/StreamChatSwiftUITests/Tests/ChatChannel/__Snapshots__/QuotedMessageView_Tests/test_quotedMessageView_customAttachmentSize_snapshot.1.png new file mode 100644 index 00000000..fb6ef06d Binary files /dev/null and b/StreamChatSwiftUITests/Tests/ChatChannel/__Snapshots__/QuotedMessageView_Tests/test_quotedMessageView_customAttachmentSize_snapshot.1.png differ diff --git a/StreamChatSwiftUITests/Tests/ChatChannel/__Snapshots__/QuotedMessageView_Tests/test_quotedMessageView_customContentView_snapshot.1.png b/StreamChatSwiftUITests/Tests/ChatChannel/__Snapshots__/QuotedMessageView_Tests/test_quotedMessageView_customContentView_snapshot.1.png new file mode 100644 index 00000000..0b354263 Binary files /dev/null and b/StreamChatSwiftUITests/Tests/ChatChannel/__Snapshots__/QuotedMessageView_Tests/test_quotedMessageView_customContentView_snapshot.1.png differ diff --git a/StreamChatSwiftUITests/Tests/ChatChannelList/ChatChannelListViewModel_Tests.swift b/StreamChatSwiftUITests/Tests/ChatChannelList/ChatChannelListViewModel_Tests.swift index ba358e7f..38fea3a3 100644 --- a/StreamChatSwiftUITests/Tests/ChatChannelList/ChatChannelListViewModel_Tests.swift +++ b/StreamChatSwiftUITests/Tests/ChatChannelList/ChatChannelListViewModel_Tests.swift @@ -532,6 +532,36 @@ class ChatChannelListViewModel_Tests: StreamChatTestCase { // Then XCTAssertNil(viewModel.selectedChannel, "selectedChannel should be cleared immediately when opening another channel") } + + // MARK: - Optimized Channel List Updates + + func test_channelListOptimizedUpdates_whenStackedViewAndSelection_thenUpdatesSkipped() { + // Given + let config = MessageListConfig(updateChannelsFromMessageList: false, iPadSplitViewEnabled: false) + streamChat = StreamChat(chatClient: chatClient, utils: Utils(messageListConfig: config)) + let existingChannel = ChatChannel.mockDMChannel() + let channelListController = makeChannelListController(channels: [existingChannel]) + + // When + let viewModel = ChatChannelListViewModel( + channelListController: channelListController, + selectedChannelId: nil + ) + viewModel.selectedChannel = .init(channel: existingChannel, message: nil) + let insertedChannel = ChatChannel.mockDMChannel() + channelListController.simulate( + channels: [insertedChannel, existingChannel], + changes: [.insert(insertedChannel, index: IndexPath(item: 0, section: 0))] + ) + + // Then + XCTAssertEqual(viewModel.channels.count, 1) + + // When selection is popped, changes are applied + viewModel.selectedChannel = nil + + XCTAssertEqual(viewModel.channels.count, 2) + } // MARK: - private