Conversation
Updated the README to reflect the release status of the SwiftUI SDK v5 and clarified the availability of v4 releases.
* Make CDNRequester a constructor dependency of StreamMediaLoader Align with LLC changes: CDNRequester is now provided once via StreamMediaLoader's init instead of being passed through options on every call. - Deprecate Utils.cdnRequester; CDN config flows through mediaLoader - Update all call sites to use simplified option constructors - Route StreamAsyncImage/NukeImageLoader CDN through mediaLoader - Use mediaLoader.resolveFileURL for file downloads and previews - Update tests and mocks for new protocol requirement * Route NukeImageLoader through MediaLoader for image downloads NukeImageLoader now delegates the actual image download to mediaLoader.loadImage() instead of duplicating CDN resolution and Nuke pipeline work. It keeps only the synchronous Nuke cache lookup for instant initial phases and the onCacheMiss orchestration. StreamImageDownloader now populates isAnimated and animatedImageData from Nuke's response so the data flows through MediaLoaderImage. * Remove CDNRequester cast from NukeImageLoader StreamAsyncImage now calls mediaLoader.loadImage() directly instead of going through NukeImageLoader.loadImage(). NukeImageLoader is reduced to a synchronous Nuke cache lookup helper with no CDNRequester dependency. The cachingKey flows through MediaLoaderImage so StreamAsyncImage can populate the NukeImageLoader cache map after each successful load. * Use loadFile(at:options:) for file downloads and previews Align with LLC rename: resolveFileURL β loadFile(at:options:) returning MediaLoaderFile. Update FileAttachmentView, FileAttachmentPreview, and all test mocks. * Remove deprecated Utils.cdnRequester property Breaking change: CDNRequester is now only accessible through StreamMediaLoader.cdnRequester. Configure it via StreamMediaLoader(cdnRequester:downloader:) and set Utils.mediaLoader. * Use loadFileRequest and URLRequest for file downloads and previews Updates FileAttachmentView, FileAttachmentPreview, and WebView to use the new loadFileRequest API returning MediaLoaderFileRequest. WebView now accepts a URLRequest directly, ensuring CDN headers are forwarded to the web view. * Short-circuit loadImage when cache hit exists in StreamAsyncImage * Only set animatedImageData for GIF images in StreamImageDownloader * Fix NukeImageLoader animatedImageData for non-GIF and update doc comment * Add unit tests for StreamMediaLoader.loadFileRequest * Use convenience MediaLoader methods without empty options * Fix WebView_Tests to use URLRequest initializer * Update changelog for CDNRequester as StreamMediaLoader dependency * Remove isAnimated property from StreamAsyncImageResult and DownloadedImage * Reorder StreamMediaLoader init: downloader first, cdnRequester second * Fix StreamMediaLoader init argument order in loadFileRequest tests * Update stream-chat-swift dependency to develop branch
* Fix tapping images on messages, showing the gallery screen * Make it so that only message bubble sallows the dismiss of reactions overlay view * Fix opening gallery sometimes when long pressing the message view hovering attachments * Simplify long-press gallery fix using highPriorityGesture Replace the environment-based coordination with a single-modifier change: switching the message long-press from `simultaneousGesture` to `highPriorityGesture` cancels pending attachment tap recognizers when the long-press is recognized, preventing the gallery from opening on finger release. * Update CHANGELOG * Fix changelog * Fix gesture taking a bit longer is the message spacing * Use StreamAsyncImage in Media Content View * Fix snapshot tests * Update changelog
* Force @mainactor on MediaLoader completion closures Swift 6.1 (Xcode 16.4) doesn't always infer that a trailing closure passed to an `@escaping @mainactor (...) -> Void` parameter is itself `@MainActor`-isolated, especially when the call site is nested inside SwiftUI view-builder / `.onAppear` / `.task` closures. Swift 6.2 (Xcode 26.x) relaxed this via improved closure isolation inference, so the same code compiles there but fails the Xcode 16.x builds (`Build Demo App (16.4)` and `Build SDKs (Old Xcode)`). Add explicit `@MainActor` to the trailing closures passed to the new `MediaLoader` API, `MediaAttachment.generateThumbnail`, and `MessageController.downloadAttachment` so isolation is unambiguous on every supported Swift version. * [CI] Disable xcbeautify/xcpretty in test_ui and build_demo lanes The previous commit's @mainactor fix still leaves the Xcode 16.4 `Build Demo App (16.4)` and Xcode 16.1 `Build SDKs (Old Xcode)` jobs red, and we can't see the actual Swift compiler diagnostic because fastlane scan's formatter (xcbeautify / xcpretty) is swallowing the `error:` lines emitted by xcodebuild. Pass `output_style: 'raw'`, `disable_xcpretty: true`, and an empty `xcodebuild_formatter` to both scan invocations so the raw xcodebuild output β including the Swift error β makes it into the GH Actions log. * Work around Swift 6.0 SILGen crash on Utils() default argument Raw Xcode 16.1 logs show the Swift compiler crashes in SILGen while synthesizing the default-argument thunk for `utils: Utils = Utils()` on `StreamChat.init`. The stack trace bottoms out at `emitRawApply` from `SILGen default arg initializer` at StreamChat.swift:18 (`"Utils("`), an assertion that was silently fixed in Swift 6.2. The crash is triggered by `Utils.init` now accepting `mediaLoader: MediaLoader? = nil` β a protocol-existential parameter from an external module β combined with `@MainActor` isolation on `Utils` and the fact that the `Utils()` expression itself lives in another default-argument slot of a different `@MainActor` initializer. Move the `Utils()` construction out of the default-argument slot and into the initializer body by switching `utils` to an optional with a `nil` default. This is source-compatible: every existing caller of `StreamChat(chatClient:)`, `StreamChat(chatClient:appearance:)`, and `StreamChat(chatClient:appearance:utils:)` still type-checks without changes (`Utils` coerces to `Utils?`). * Alternative fix * Revert "[CI] Disable xcbeautify/xcpretty in test_ui and build_demo lanes" This reverts commit 935a350. * Revert "Force @mainactor on MediaLoader completion closures" This reverts commit 57adcb4. * Update CHANGELOG for #1430
* Fix double grey checkmarks not showing for delivered messages The v5 migration of factory methods to options-based signatures dropped the `showDelivered` argument from `DefaultViewFactory.makeMessageReadIndicatorView`, which caused delivered messages to render a single (sent) checkmark on the message list. Forward `message.deliveryStatus(for: channel) == .delivered` to the read indicator and add unit tests covering both states. * Add PR link to changelog entry
β¦1431) * Fix swipe-to-reply gesture blocking message list scrolling The swipe-to-reply drag recognizer was attached with `.gesture()` and only compared total translation magnitude against `minimumSwipeDistance`, with no axis awareness. This caused it to sporadically win the gesture competition against the enclosing `ScrollView`'s pan recognizer on diagonal or slow vertical drags, freezing the list until the finger lifted. Switch to `.simultaneousGesture(DragGesture())` and gate `offsetX` updates on a per-event horizontal-dominance check (`abs(dx) > abs(dy) * 1.5`). The scroll view's pan recognizer always runs alongside uncontested, and the bubble only moves when the motion is clearly horizontal. Direction is re-evaluated on every event so no persistent state can leak across gestures. * Simplify fix * Prevent swipe-to-reply from competing with list scrolling Track the message list scroll phase on newer SwiftUI versions and disable the swipe-to-reply modifier while scrolling is active. This keeps vertical list scrolling in control once a real scroll begins, instead of letting row-level swipe gestures stay attached and interfere. * Update CHANGELOG * Revert the min swipe gesture to avoid conflicting with navigation pop * Revert "Prevent swipe-to-reply from competing with list scrolling" This reverts commit 693c7a2. * Update min swipe gestur
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the βοΈ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
β¨ Finishing Touchesπ§ͺ Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Generated by π« Danger |
Public Interface public final class MessageDisplayOptions
- public init(showIncomingMessageAvatar: Bool = true,showOutgoingMessageAvatar: Bool = false,showAvatarsInGroups: Bool = true,showMessageDate: Bool = true,showAuthorName: Bool = true,animateChanges: Bool = true,overlayDateLabelSize: CGFloat = 40,lastInGroupHeaderSize: CGFloat = 0,newMessagesSeparatorSize: CGFloat = 50,minimumSwipeGestureDistance: CGFloat = 20,currentUserMessageTransition: AnyTransition = .identity,otherUserMessageTransition: AnyTransition = .identity,shouldAnimateReactions: Bool = true,reactionsPlacement: ReactionsPlacement = .top,reactionsStyle: ReactionsStyle = .segmented,showOriginalTranslatedButton: Bool = true,messageLinkDisplayResolver: @escaping @MainActor (ChatMessage) -> [NSAttributedString.Key: Any] = MessageDisplayOptions
+ public init(showIncomingMessageAvatar: Bool = true,showOutgoingMessageAvatar: Bool = false,showAvatarsInGroups: Bool = true,showMessageDate: Bool = true,showAuthorName: Bool = true,animateChanges: Bool = true,overlayDateLabelSize: CGFloat = 40,lastInGroupHeaderSize: CGFloat = 0,newMessagesSeparatorSize: CGFloat = 50,minimumSwipeGestureDistance: CGFloat = 30,currentUserMessageTransition: AnyTransition = .identity,otherUserMessageTransition: AnyTransition = .identity,shouldAnimateReactions: Bool = true,reactionsPlacement: ReactionsPlacement = .top,reactionsStyle: ReactionsStyle = .segmented,showOriginalTranslatedButton: Bool = true,messageLinkDisplayResolver: @escaping @MainActor (ChatMessage) -> [NSAttributedString.Key: Any] = MessageDisplayOptions
public struct StreamAsyncImageResult
- public let isAnimated: Bool
+ public let animatedImageData: Data?
- public let animatedImageData: Data?
@MainActor public class Utils
- public var cdnRequester: CDNRequester
+ public var mediaLoader: MediaLoader
- public var mediaLoader: MediaLoader
+ public var channelNameFormatter: ChannelNameFormatter
- public var channelNameFormatter: ChannelNameFormatter
+ public var avPlayerProvider: AVPlayerProvider
- public var avPlayerProvider: AVPlayerProvider
+ public var chatUserNamer: ChatUserNamer
- public var chatUserNamer: ChatUserNamer
+ public var messageTypeResolver: MessageTypeResolving
- public var messageTypeResolver: MessageTypeResolving
+ public var messageActionsResolver: MessageActionsResolving
- public var messageActionsResolver: MessageActionsResolving
+ public var messageAttachmentPreviewIconProvider: MessageAttachmentPreviewIconProvider
- public var messageAttachmentPreviewIconProvider: MessageAttachmentPreviewIconProvider
+ public var messagePreviewFormatter: MessagePreviewFormatter
- public var messagePreviewFormatter: MessagePreviewFormatter
+ public var commandsConfig: CommandsConfig
- public var commandsConfig: CommandsConfig
+ public var channelListConfig: ChannelListConfig
- public var channelListConfig: ChannelListConfig
+ public var messageListConfig: MessageListConfig
- public var messageListConfig: MessageListConfig
+ public var composerConfig: ComposerConfig
- public var composerConfig: ComposerConfig
+ public var pollsConfig: PollsConfig
- public var pollsConfig: PollsConfig
+ public var shouldSyncChannelControllerOnAppear: (ChatChannelController) -> Bool
- public var shouldSyncChannelControllerOnAppear: (ChatChannelController) -> Bool
+ public var snapshotCreator: SnapshotCreator
- public var snapshotCreator: SnapshotCreator
+ public var messageIdBuilder: MessageIdBuilder
- public var messageIdBuilder: MessageIdBuilder
+ public var sortReactions: (MessageReactionType, MessageReactionType) -> Bool
- public var sortReactions: (MessageReactionType, MessageReactionType) -> Bool
+ public var videoDurationFormatter: VideoDurationFormatter
- public var videoDurationFormatter: VideoDurationFormatter
+ public var mediaBadgeDurationFormatter: MediaBadgeDurationFormatter
- public var mediaBadgeDurationFormatter: MediaBadgeDurationFormatter
+ public var audioRecordingNameFormatter: AudioRecordingNameFormatter
- public var audioRecordingNameFormatter: AudioRecordingNameFormatter
+ public var messageRemindersFormatter: any MessageRemindersFormatter
- public var messageRemindersFormatter: any MessageRemindersFormatter
+ public var audioPlayerBuilder: () -> AudioPlaying
- public var audioPlayerBuilder: () -> AudioPlaying
+ public var audioPlayer: AudioPlaying
- public var audioPlayer: AudioPlaying
+ public var audioRecorderBuilder: () -> AudioRecording
- public var audioRecorderBuilder: () -> AudioRecording
+ public var audioRecorder: AudioRecording
- public var audioRecorder: AudioRecording
+ @MainActor public lazy var audioSessionFeedbackGenerator: AudioSessionFeedbackGenerator
- @MainActor public lazy var audioSessionFeedbackGenerator: AudioSessionFeedbackGenerator
+ public var originalTranslationsStore
- public var originalTranslationsStore
+ public static var defaultSortReactions: (MessageReactionType, MessageReactionType) -> Bool
- public static var defaultSortReactions: (MessageReactionType, MessageReactionType) -> Bool
+
-
+
-
+ public init(markdownFormatter: MarkdownFormatter = DefaultMarkdownFormatter(),dateFormatter: DateFormatter = .makeDefault(),messageTimestampFormatter: MessageTimestampFormatter = ChannelListMessageTimestampFormatter(),galleryHeaderViewDateFormatter: GalleryHeaderViewDateFormatter = DefaultGalleryHeaderViewDateFormatter(),messageDateSeparatorFormatter: MessageDateSeparatorFormatter = DefaultMessageDateSeparatorFormatter(),mediaLoader: MediaLoader = StreamMediaLoader(downloader: StreamImageDownloader()),avPlayerProvider: AVPlayerProvider = DefaultAVPlayerProvider(),messageTypeResolver: MessageTypeResolving = MessageTypeResolver(),messageActionResolver: MessageActionsResolving = MessageActionsResolver(),messageAttachmentPreviewIconProvider: MessageAttachmentPreviewIconProvider = DefaultMessageAttachmentPreviewIconProvider(),messagePreviewFormatter: MessagePreviewFormatter = MessagePreviewFormatter(),commandsConfig: CommandsConfig = DefaultCommandsConfig(),channelListConfig: ChannelListConfig = ChannelListConfig(),messageListConfig: MessageListConfig = MessageListConfig(),composerConfig: ComposerConfig = ComposerConfig(),pollsConfig: PollsConfig = PollsConfig(),channelNameFormatter: ChannelNameFormatter = DefaultChannelNameFormatter(),chatUserNamer: ChatUserNamer = DefaultChatUserNamer(),snapshotCreator: SnapshotCreator = DefaultSnapshotCreator(),messageIdBuilder: MessageIdBuilder = DefaultMessageIdBuilder(),videoDurationFormatter: VideoDurationFormatter = DefaultVideoDurationFormatter(),mediaBadgeDurationFormatter: MediaBadgeDurationFormatter = DefaultMediaBadgeDurationFormatter(),audioRecordingNameFormatter: AudioRecordingNameFormatter = DefaultAudioRecordingNameFormatter(),messageRemindersFormatter: any MessageRemindersFormatter = DefaultMessageRemindersFormatter(),sortReactions: @escaping (MessageReactionType, MessageReactionType) -> Bool = Utils.defaultSortReactions,shouldSyncChannelControllerOnAppear: @escaping (ChatChannelController) -> Bool = { _ in true })
- public init(markdownFormatter: MarkdownFormatter = DefaultMarkdownFormatter(),dateFormatter: DateFormatter = .makeDefault(),messageTimestampFormatter: MessageTimestampFormatter = ChannelListMessageTimestampFormatter(),galleryHeaderViewDateFormatter: GalleryHeaderViewDateFormatter = DefaultGalleryHeaderViewDateFormatter(),messageDateSeparatorFormatter: MessageDateSeparatorFormatter = DefaultMessageDateSeparatorFormatter(),cdnRequester: CDNRequester = StreamCDNRequester(),mediaLoader: MediaLoader? = nil,avPlayerProvider: AVPlayerProvider = DefaultAVPlayerProvider(),messageTypeResolver: MessageTypeResolving = MessageTypeResolver(),messageActionResolver: MessageActionsResolving = MessageActionsResolver(),messageAttachmentPreviewIconProvider: MessageAttachmentPreviewIconProvider = DefaultMessageAttachmentPreviewIconProvider(),messagePreviewFormatter: MessagePreviewFormatter = MessagePreviewFormatter(),commandsConfig: CommandsConfig = DefaultCommandsConfig(),channelListConfig: ChannelListConfig = ChannelListConfig(),messageListConfig: MessageListConfig = MessageListConfig(),composerConfig: ComposerConfig = ComposerConfig(),pollsConfig: PollsConfig = PollsConfig(),channelNameFormatter: ChannelNameFormatter = DefaultChannelNameFormatter(),chatUserNamer: ChatUserNamer = DefaultChatUserNamer(),snapshotCreator: SnapshotCreator = DefaultSnapshotCreator(),messageIdBuilder: MessageIdBuilder = DefaultMessageIdBuilder(),videoDurationFormatter: VideoDurationFormatter = DefaultVideoDurationFormatter(),mediaBadgeDurationFormatter: MediaBadgeDurationFormatter = DefaultMediaBadgeDurationFormatter(),audioRecordingNameFormatter: AudioRecordingNameFormatter = DefaultAudioRecordingNameFormatter(),messageRemindersFormatter: any MessageRemindersFormatter = DefaultMessageRemindersFormatter(),sortReactions: @escaping (MessageReactionType, MessageReactionType) -> Bool = Utils.defaultSortReactions,shouldSyncChannelControllerOnAppear: @escaping (ChatChannelController) -> Bool = { _ in true })
@MainActor open class MessageComposerViewModel: ObservableObject
- public var shouldShowRecordingGestureOverlay: Bool
+ public var composerInputState: MessageComposerInputState
- public var sendInChannelShown: Bool
+ public var shouldShowRecordingGestureOverlay: Bool
- public var isDirectChannel: Bool
+ public var sendInChannelShown: Bool
- public var showSuggestionsOverlay: Bool
+ public var isDirectChannel: Bool
-
+ public var showSuggestionsOverlay: Bool
-
+
- public init(channelController: ChatChannelController,messageController: ChatMessageController?,eventsController: EventsController? = nil,quotedMessage: Binding<ChatMessage?>? = nil,editedMessage: Binding<ChatMessage?>? = nil,willSendMessage: (() -> Void)? = nil)
+
-
+ public init(channelController: ChatChannelController,messageController: ChatMessageController?,eventsController: EventsController? = nil,quotedMessage: Binding<ChatMessage?>? = nil,editedMessage: Binding<ChatMessage?>? = nil,willSendMessage: (() -> Void)? = nil)
-
+
- public func addFileURLs(_ urls: [URL])
+
- public func fillEditedMessage(_ editedMessage: ChatMessage?)
+ public func addFileURLs(_ urls: [URL])
- public func fillDraftMessage()
+ public func fillEditedMessage(_ editedMessage: ChatMessage?)
- public func updateDraftMessage(quotedMessage: ChatMessage?,isSilent: Bool = false,extraData: [String: RawJSON] = [:])
+ public func fillDraftMessage()
- public func deleteDraftMessage()
+ public func updateDraftMessage(quotedMessage: ChatMessage?,isSilent: Bool = false,extraData: [String: RawJSON] = [:])
- open func sendMessage(isSilent: Bool = false,skipPush: Bool = false,skipEnrichUrl: Bool = false,extraData: [String: RawJSON] = [:],completion: (@MainActor () -> Void)? = nil)
+ public func deleteDraftMessage()
- public func change(pickerState: AttachmentPickerState)
+ open func sendMessage(isSilent: Bool = false,skipPush: Bool = false,skipEnrichUrl: Bool = false,extraData: [String: RawJSON] = [:],completion: (@MainActor () -> Void)? = nil)
- public func imageTapped(_ addedAsset: AddedAsset)
+ public func change(pickerState: AttachmentPickerState)
- public func imagePasted(_ image: UIImage)
+ public func imageTapped(_ addedAsset: AddedAsset)
- public func removeAttachment(with id: String)
+ public func imagePasted(_ image: UIImage)
- public func cameraImageAdded(_ image: AddedAsset)
+ public func removeAttachment(with id: String)
- public func isImageSelected(with id: String)-> Bool
+ public func cameraImageAdded(_ image: AddedAsset)
- public func customAttachmentTapped(_ attachment: CustomAttachment)
+ public func isImageSelected(with id: String)-> Bool
- public func isCustomAttachmentSelected(_ attachment: CustomAttachment)-> Bool
+ public func customAttachmentTapped(_ attachment: CustomAttachment)
- public func askForPhotosPermission()
+ public func isCustomAttachmentSelected(_ attachment: CustomAttachment)-> Bool
- public func handleCommand(for text: Binding<String>,selectedRangeLocation: Binding<Int>,command: Binding<ComposerCommand?>,extraData: [String: Any])
+ public func askForPhotosPermission()
- open func convertAddedAssetsToPayloads()throws -> [AnyAttachmentPayload]
+ public func handleCommand(for text: Binding<String>,selectedRangeLocation: Binding<Int>,command: Binding<ComposerCommand?>,extraData: [String: Any])
- public func checkForMentionedUsers(commandId: String?,extraData: [String: Any])
+ open func convertAddedAssetsToPayloads()throws -> [AnyAttachmentPayload]
- public func clearRemovedMentions()
+ public func checkForMentionedUsers(commandId: String?,extraData: [String: Any])
- public func clearInputData()
+ public func clearRemovedMentions()
- public func checkChannelCooldown()
+ public func clearInputData()
- public func updateAddedAssets(_ assets: [AddedAsset])
+ public func checkChannelCooldown()
+ public func updateAddedAssets(_ assets: [AddedAsset]) |
SDK Size
|
StreamChatSwiftUI XCSize
Show 15 more objects
|
|
Build for regression testing β156 has been uploaded to TestFlight π |
|
|
/merge release |
|
Publication of the release has been launched π |



π Changed
CDNRequesteris now passed in the constructor ofStreamMediaLoaderinstead ofUtils#1425π Fixed