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