Skip to content

V5: message list attachments redesign#1264

Merged
laevandus merged 30 commits intov5from
v5-media-attachment-refresh
Mar 6, 2026
Merged

V5: message list attachments redesign#1264
laevandus merged 30 commits intov5from
v5-media-attachment-refresh

Conversation

@laevandus
Copy link
Contributor

@laevandus laevandus commented Mar 4, 2026

πŸ”— Issue Links

Related: IOS-1441

🎯 Goal

Attachments redesign with new stacked logic for attachments

πŸ“ Summary

  • Replaced ImageAttachmentView and VideoAttachmentView with a unified
    MessageMediaAttachmentsContainerView that renders images and videos in an adaptive gallery grid (1–4+
    items with orientation-aware layouts)
  • Introduced MessageAttachmentsView as the single entry point for rendering all attachment types
    (media, files, voice recordings, links) with quoted messages and text captions
  • Added MessageMediaAttachmentContentView for individual media thumbnails with shimmer placeholders
    and per-cell loading spinners
  • Renamed makeAttachmentTextView/AttachmentTextViewOptions (inner text rendering) to
    makeStreamTextView/StreamTextViewOptions to clarify it's a shared text view reused across message
    layouts
  • Simplified FileAttachmentView, VoiceRecordingContainerView, and LinkAttachmentView by removing
    duplicated quoted message, text, and bubble logic (now handled by MessageAttachmentsView)
  • Added VideoPlayIndicatorView and LoadingSpinnerView as reusable common views

πŸ›  Implementation

The old approach had each attachment type (ImageAttachmentView, VideoAttachmentView,
FileAttachmentsContainer, etc.) independently handling quoted messages, text display, and bubble
backgrounds. This led to duplicated logic and inconsistent behavior.

The new architecture introduces MessageAttachmentsView as an orchestrator that composes attachment
views via the view factory and handles the shared concerns (quoted messages, text captions, bubble
modifiers) in one place. Individual attachment views are now simpler, focused only on rendering their
specific content.

MessageMediaAttachmentsContainerView provides a gallery grid that adapts layout based on item count
(1–4+) and the first image's orientation (landscape/portrait/square). Each cell uses
MessageMediaAttachmentContentView which loads thumbnails asynchronously with shimmer placeholders.

Before After

πŸ§ͺ Manual Testing Notes

  • Send messages with 1–5+ images and verify the gallery grid layout adapts correctly
  • Send messages with videos only, images only, and mixed image+video
  • Verify single media attachments render without a message bubble (no text)
  • Verify media attachments with text render inside a bubble
  • Tap media cells to open the full-screen gallery
  • Verify upload progress indicators appear during file upload

β˜‘οΈ Contributor Checklist

  • I have signed the Stream CLA (required)
  • This change should be manually QAed
  • Changelog is updated with client-facing changes
  • Changelog is updated with new localization keys
  • New code is covered by unit tests
  • Documentation has been updated in the docs-content repo

@laevandus laevandus requested a review from a team as a code owner March 4, 2026 14:42
@coderabbitai
Copy link

coderabbitai bot commented Mar 4, 2026

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

βš™οΈ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: f85e6241-042c-4653-af32-ca2f5ed987f1

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • πŸ” Trigger review
✨ Finishing Touches
πŸ§ͺ Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch v5-media-attachment-refresh

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.

❀️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

import StreamChat
import SwiftUI

struct LazyLoadingImage: View {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was just moved to a separate file, implementation is unchanged.

import StreamChat
import SwiftUI

public final class MediaAttachment: Identifiable, Equatable, Sendable {
Copy link
Contributor Author

@laevandus laevandus Mar 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was just moved to a separate file, but with addition of originalWidth and height needed for layout. It used to be in ImageAttachmentView.swift but that has been deleted.

Comment on lines +60 to +70
// Giphy
if messageTypeResolver.hasGiphyAttachment(message: message) {
factory.makeGiphyAttachmentView(
options: GiphyAttachmentViewOptions(
message: message,
isFirst: isFirst,
availableWidth: width,
scrolledId: $scrolledId
)
)
}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should not be here, delete

@github-actions
Copy link

github-actions bot commented Mar 4, 2026

1 Warning
⚠️ Big PR
1 Message
πŸ“– There seems to be app changes but CHANGELOG wasn't modified.
Please include an entry if the PR includes user-facing changes.
You can find it at CHANGELOG.md.

Generated by 🚫 Danger

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is actually OK. I guess I should change the test since it looks odd.

@laevandus laevandus marked this pull request as draft March 4, 2026 14:50
@laevandus laevandus marked this pull request as ready for review March 5, 2026 12:26
Copy link
Contributor

@martinmitrevski martinmitrevski left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good to me, left few smaller comments. Also, good to re-check the spacing between first row/column in the image attachment picker when there are 4 of them, I think it's too small?

AttachmentTextView(factory: factory, message: message)
.frame(maxWidth: .infinity)
VStack(spacing: 2) {
ForEach(message.voiceRecordingAttachments, id: \.self) { attachment in
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we need to teach AI to not do this :)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if these are not identifiable, we can do it in a follow up PR

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is actually existing code and not AI generated.

scrolledId: $scrolledId
)
VStack(spacing: 4) {
ForEach(message.fileAttachments, id: \.self) { attachment in
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another one here

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is also existing code, just moved.

}

private var backgroundColor: Color {
let attachmentCounts = message.attachmentCounts
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

seems like we're repeating this stuff, maybe we can centralize this logic?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, something similar to MessagePreviewResolver πŸ€”

injectedBackgroundColor: colors.highlightedAccentBackground1
)
public var body: some View {
if !message.linkAttachments.isEmpty {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

feels weird to have this without the container - shouldn't this be handled somewhere up? But on the other hand, good to have protection against crashes.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah agree, it feels like the view should already accept the link attachments directly to avoid having empty link attachments πŸ€”

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is existing code and I think it is because attachment views have inits where it just takes message. Should be refactored separately. Same issue with giphy attachment view.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

guard showsBubble else { return 0 }
// Single voice and file don't have extra padding
let attachmentCounts = message.attachmentCounts
if message.text.isEmpty, attachmentCounts.count == 1, attachmentCounts[.file] == 1 || attachmentCounts[.voiceRecording] == 1 {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this one is slightly different, but still applicable to the other case, I think we can easily make mistakes having this spread in multiple places.

return videoSources + imageSources
}

private func containerSize(for itemCount: Int) -> CGSize {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we need to test this on ipads

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Image

Copy link
Member

@nuno-vieira nuno-vieira left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks great overall, added some comments πŸ‘

/// - Parameters:
/// - factory: The view factory to create the quoted message view.
/// - quotedMessage: The quoted message to display.
/// - availableWidth: The available width for the quoted message view.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we need this?

Copy link
Contributor Author

@laevandus laevandus Mar 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Image

Otherwise it refused to grow to fill in the size so I applied the same width handling here like in other attachments views.

}

private var backgroundColor: Color {
let attachmentCounts = message.attachmentCounts
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, something similar to MessagePreviewResolver πŸ€”

injectedBackgroundColor: colors.highlightedAccentBackground1
)
public var body: some View {
if !message.linkAttachments.isEmpty {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah agree, it feels like the view should already accept the link attachments directly to avoid having empty link attachments πŸ€”

# Conflicts:
#	Sources/StreamChatSwiftUI/ChatMessageList/AsyncVoiceMessages/VoiceRecordingContainerView.swift
#	StreamChatSwiftUITests/Tests/ChatChannel/__Snapshots__/MessageComposerView_Tests/test_composerView_draftWithVoiceRecordingAttachment.default-light.png
#	StreamChatSwiftUITests/Tests/ChatChannel/__Snapshots__/MessageComposerView_Tests/test_composerView_editingMessageWithVoiceRecording.default-light.png
#	StreamChatSwiftUITests/Tests/ChatChannel/__Snapshots__/MessageComposerView_Tests/test_messageComposerView_addedVoiceRecording.1.png
#	StreamChatSwiftUITests/Tests/ChatChannel/__Snapshots__/MessageView_Tests/test_messageViewVoiceRecordingFromMeTheming_snapshot.default-light.png
#	StreamChatSwiftUITests/Tests/ChatChannel/__Snapshots__/MessageView_Tests/test_messageViewVoiceRecordingFromMe_snapshot.default-light.png
#	StreamChatSwiftUITests/Tests/ChatChannel/__Snapshots__/MessageView_Tests/test_messageViewVoiceRecordingFromMe_snapshot.extraExtraExtraLarge-light.png
#	StreamChatSwiftUITests/Tests/ChatChannel/__Snapshots__/MessageView_Tests/test_messageViewVoiceRecordingFromMe_snapshot.rightToLeftLayout-default.png
#	StreamChatSwiftUITests/Tests/ChatChannel/__Snapshots__/MessageView_Tests/test_messageViewVoiceRecordingFromMe_snapshot.small-dark.png
#	StreamChatSwiftUITests/Tests/ChatChannel/__Snapshots__/MessageView_Tests/test_messageViewVoiceRecordingFromParticipant_snapshot.default-light.png
#	StreamChatSwiftUITests/Tests/ChatChannel/__Snapshots__/MessageView_Tests/test_messageViewVoiceRecordingFromParticipant_snapshot.extraExtraExtraLarge-light.png
#	StreamChatSwiftUITests/Tests/ChatChannel/__Snapshots__/MessageView_Tests/test_messageViewVoiceRecordingFromParticipant_snapshot.rightToLeftLayout-default.png
#	StreamChatSwiftUITests/Tests/ChatChannel/__Snapshots__/MessageView_Tests/test_messageViewVoiceRecordingFromParticipant_snapshot.small-dark.png
#	StreamChatSwiftUITests/Tests/ChatChannel/__Snapshots__/MessageView_Tests/test_messageViewVoiceRecordingWithTextFromMeMultiple_snapshot.default-light.png
#	StreamChatSwiftUITests/Tests/ChatChannel/__Snapshots__/MessageView_Tests/test_messageViewVoiceRecordingWithTextFromMeMultiple_snapshot.extraExtraExtraLarge-light.png
#	StreamChatSwiftUITests/Tests/ChatChannel/__Snapshots__/MessageView_Tests/test_messageViewVoiceRecordingWithTextFromMeMultiple_snapshot.rightToLeftLayout-default.png
#	StreamChatSwiftUITests/Tests/ChatChannel/__Snapshots__/MessageView_Tests/test_messageViewVoiceRecordingWithTextFromMeMultiple_snapshot.small-dark.png
#	StreamChatSwiftUITests/Tests/ChatChannel/__Snapshots__/MessageView_Tests/test_messageViewVoiceRecordingWithTextFromMe_snapshot.default-light.png
#	StreamChatSwiftUITests/Tests/ChatChannel/__Snapshots__/MessageView_Tests/test_messageViewVoiceRecordingWithTextFromMe_snapshot.extraExtraExtraLarge-light.png
#	StreamChatSwiftUITests/Tests/ChatChannel/__Snapshots__/MessageView_Tests/test_messageViewVoiceRecordingWithTextFromMe_snapshot.rightToLeftLayout-default.png
#	StreamChatSwiftUITests/Tests/ChatChannel/__Snapshots__/MessageView_Tests/test_messageViewVoiceRecordingWithTextFromMe_snapshot.small-dark.png
#	StreamChatSwiftUITests/Tests/ChatChannel/__Snapshots__/MessageView_Tests/test_messageViewVoiceRecordingWithTextFromParticipantMultiple_snapshot.default-light.png
#	StreamChatSwiftUITests/Tests/ChatChannel/__Snapshots__/MessageView_Tests/test_messageViewVoiceRecordingWithTextFromParticipantMultiple_snapshot.extraExtraExtraLarge-light.png
#	StreamChatSwiftUITests/Tests/ChatChannel/__Snapshots__/MessageView_Tests/test_messageViewVoiceRecordingWithTextFromParticipantMultiple_snapshot.rightToLeftLayout-default.png
#	StreamChatSwiftUITests/Tests/ChatChannel/__Snapshots__/MessageView_Tests/test_messageViewVoiceRecordingWithTextFromParticipantMultiple_snapshot.small-dark.png
#	StreamChatSwiftUITests/Tests/ChatChannel/__Snapshots__/MessageView_Tests/test_messageViewVoiceRecordingWithTextFromParticipant_snapshot.default-light.png
#	StreamChatSwiftUITests/Tests/ChatChannel/__Snapshots__/MessageView_Tests/test_messageViewVoiceRecordingWithTextFromParticipant_snapshot.extraExtraExtraLarge-light.png
#	StreamChatSwiftUITests/Tests/ChatChannel/__Snapshots__/MessageView_Tests/test_messageViewVoiceRecordingWithTextFromParticipant_snapshot.rightToLeftLayout-default.png
#	StreamChatSwiftUITests/Tests/ChatChannel/__Snapshots__/MessageView_Tests/test_messageViewVoiceRecordingWithTextFromParticipant_snapshot.small-dark.png
}

/// Bubble styling configuration for stacked attachments.
enum MessageAttachmentsBubbleConfiguration {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Moved this special bubble and attachment background logic to here.

)
)
} else if !message.attachmentCounts.isEmpty || message.quotedMessage != nil {
factory.makeMessageAttachmentsView(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't really understand this - so we have everything except giphy attachments under this message attachments view? And why don't we have a way to customize image/video attachments separately as before?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the new design quoted and other attachments share a single bubble where as before it was not a case always. This view handles the centralised bubble logic for attachments + text. Before it used to be that each attachment had its own quoted message handling and bubble handling.

I'll add back the video attachment factory for flexibility. New design does not make any difference between image and video (except the play indicator).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I reverted some of the view factory methods to look the same we had before.

Copy link
Member

@nuno-vieira nuno-vieira left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM! βœ…

@github-actions
Copy link

github-actions bot commented Mar 6, 2026

Public Interface

+ public struct MessageMediaAttachmentsContainerView: View  
+ 
+   public var body: some View
+   
+ 
+   public init(factory: Factory,message: ChatMessage,width: CGFloat)

+ public struct MessageMediaAttachmentContentView: View  
+ 
+   public var body: some View
+   
+ 
+   public init(factory: Factory,source: MediaAttachment,width: CGFloat,height: CGFloat,cornerRadius: CGFloat? = nil,isOutgoing: Bool = false)

+ public enum LoadingSpinnerSize: Sendable  
+ 
+   @MainActor public static var large: CGFloat
+   @MainActor public static var small: CGFloat
+   @MainActor public static var extraSmall: CGFloat

+ public class StreamTextViewOptions  
+ 
+   public let message: ChatMessage
+   
+ 
+   public init(message: ChatMessage)

+ public enum VideoPlayIndicatorSize  
+ 
+   @MainActor public static var extraLarge: CGFloat
+   @MainActor public static var large: CGFloat
+   @MainActor public static var medium: CGFloat
+   @MainActor public static var small: CGFloat

+ public final class MessageAttachmentsViewOptions: Sendable  
+ 
+   public let message: ChatMessage
+   public let isFirst: Bool
+   public let availableWidth: CGFloat
+   public let scrolledId: Binding<String?>
+   
+ 
+   public init(message: ChatMessage,isFirst: Bool,availableWidth: CGFloat,scrolledId: Binding<String?>)

+ public enum MediaGalleryOrientation: Sendable  
+ 
+   case landscape
+   case portrait
+   case square
+   
+ 
+   public init(width: Double?,height: Double?)

+ public struct MessageAttachmentsView: View  
+ 
+   public var body: some View
+   
+ 
+   public init(factory: Factory,message: ChatMessage,width: CGFloat,isFirst: Bool,scrolledId: Binding<String?>)

+ public struct VideoPlayIndicatorView: View  
+ 
+   public var body: some View
+   
+ 
+   public init(size: CGFloat,playing: Bool = false)

+ public struct LoadingSpinnerView: View  
+ 
+   public var body: some View
+   
+ 
+   public init(size: CGFloat,bordered: Bool)

- public struct VideoAttachmentsList: View  
- 
-   public var body: some View
-   
- 
-   public init(factory: Factory = DefaultViewFactory.shared,message: ChatMessage,width: CGFloat)

- public struct ImageAttachmentContainer: View  
- 
-   public var body: some View
-   
- 
-   public init(factory: Factory,message: ChatMessage,width: CGFloat,isFirst: Bool,scrolledId: Binding<String?>)

- public struct VideoAttachmentsContainer: View  
- 
-   public var body: some View

- public struct VideoAttachmentView: View  
- 
-   public var body: some View
-   
- 
-   public init(factory: Factory = DefaultViewFactory.shared,attachment: ChatMessageVideoAttachment,message: ChatMessage,width: CGFloat,ratio: CGFloat = 0.75,cornerRadius: CGFloat = 24)

 public struct ChatQuotedMessageView: View  
-   public init(factory: Factory,quotedMessage: ChatMessage,parentMessage: ChatMessage,scrolledId: Binding<String?>)
+   public init(factory: Factory,quotedMessage: ChatMessage,parentMessage: ChatMessage,availableWidth: CGFloat? = nil,scrolledId: Binding<String?>)

 public final class ChatQuotedMessageViewOptions: Sendable  
-   public let scrolledId: Binding<String?>
+   public let availableWidth: CGFloat?
-   
+   public let scrolledId: Binding<String?>
- 
+   
-   public init(quotedMessage: ChatMessage,parentMessage: ChatMessage,scrolledId: Binding<String?>)
+ 
+   public init(quotedMessage: ChatMessage,parentMessage: ChatMessage,availableWidth: CGFloat? = nil,scrolledId: Binding<String?>)

 public final class MediaAttachment: Identifiable, Equatable, Sendable  
-   public var id: String
+   public let originalWidth: Double?
-   
+   public let originalHeight: Double?
- 
+   public var id: String
-   public init(url: URL,type: MediaAttachmentType,uploadingState: AttachmentUploadingState? = nil)
+   
-   
+ 
- 
+   public init(url: URL,type: MediaAttachmentType,uploadingState: AttachmentUploadingState? = nil,originalWidth: Double? = nil,originalHeight: Double? = nil)
-   public static func ==(lhs: MediaAttachment,rhs: MediaAttachment)-> Bool
+   
+ 
+   public static func ==(lhs: MediaAttachment,rhs: MediaAttachment)-> Bool

 public class AttachmentTextViewOptions  
-   public init(mesage: ChatMessage)
+   public init(message: ChatMessage)

 extension ViewFactory  
-   public func makeImageAttachmentView(options: ImageAttachmentViewOptions)-> some View
+   public func makeMessageAttachmentsView(options: MessageAttachmentsViewOptions)-> some View
-   public func makeGiphyAttachmentView(options: GiphyAttachmentViewOptions)-> some View
+   public func makeImageAttachmentView(options: ImageAttachmentViewOptions)-> some View
-   public func makeLinkAttachmentView(options: LinkAttachmentViewOptions)-> some View
+   public func makeGiphyAttachmentView(options: GiphyAttachmentViewOptions)-> some View
-   public func makeFileAttachmentView(options: FileAttachmentViewOptions)-> some View
+   public func makeLinkAttachmentView(options: LinkAttachmentViewOptions)-> some View
-   public func makeVideoAttachmentView(options: VideoAttachmentViewOptions)-> some View
+   public func makeFileAttachmentView(options: FileAttachmentViewOptions)-> some View
-   public func makeGalleryView(options: GalleryViewOptions)-> some View
+   public func makeVideoAttachmentView(options: VideoAttachmentViewOptions)-> some View
-   public func makeGalleryHeaderView(options: GalleryHeaderViewOptions)-> some View
+   public func makeGalleryView(options: GalleryViewOptions)-> some View
-   public func makeVideoPlayerView(options: VideoPlayerViewOptions)-> some View
+   public func makeGalleryHeaderView(options: GalleryHeaderViewOptions)-> some View
-   public func makeVideoPlayerHeaderView(options: VideoPlayerHeaderViewOptions)-> some View
+   public func makeVideoPlayerView(options: VideoPlayerViewOptions)-> some View
-   public func makeVideoPlayerFooterView(options: VideoPlayerFooterViewOptions)-> some View
+   public func makeVideoPlayerHeaderView(options: VideoPlayerHeaderViewOptions)-> some View
-   public func makeDeletedMessageView(options: DeletedMessageViewOptions)-> some View
+   public func makeVideoPlayerFooterView(options: VideoPlayerFooterViewOptions)-> some View
-   public func makeSystemMessageView(options: SystemMessageViewOptions)-> some View
+   public func makeDeletedMessageView(options: DeletedMessageViewOptions)-> some View
-   public func makeEmojiTextView(options: EmojiTextViewOptions)-> some View
+   public func makeSystemMessageView(options: SystemMessageViewOptions)-> some View
-   public func makeCustomAttachmentViewType(options: CustomAttachmentViewTypeOptions)-> some View
+   public func makeEmojiTextView(options: EmojiTextViewOptions)-> some View
-   public func makeScrollToBottomButton(options: ScrollToBottomButtonOptions)-> some View
+   public func makeCustomAttachmentViewType(options: CustomAttachmentViewTypeOptions)-> some View
-   public func makeDateIndicatorView(options: DateIndicatorViewOptions)-> some View
+   public func makeScrollToBottomButton(options: ScrollToBottomButtonOptions)-> some View
-   public func makeMessageListDateIndicator(options: MessageListDateIndicatorViewOptions)-> some View
+   public func makeDateIndicatorView(options: DateIndicatorViewOptions)-> some View
-   public func makeInlineTypingIndicatorView(options: TypingIndicatorViewOptions)-> some View
+   public func makeMessageListDateIndicator(options: MessageListDateIndicatorViewOptions)-> some View
-   public func makeSubtitleTypingIndicatorView(options: SubtitleTypingIndicatorViewOptions)-> some View
+   public func makeInlineTypingIndicatorView(options: TypingIndicatorViewOptions)-> some View
-   public func makeGiphyBadgeViewType(options: GiphyBadgeViewTypeOptions)-> some View
+   public func makeSubtitleTypingIndicatorView(options: SubtitleTypingIndicatorViewOptions)-> some View
-   public func makeMessageRepliesView(options: MessageRepliesViewOptions)-> some View
+   public func makeGiphyBadgeViewType(options: GiphyBadgeViewTypeOptions)-> some View
-   public func makeMessageComposerViewType(options: MessageComposerViewTypeOptions)-> MessageComposerView<Self>
+   public func makeMessageRepliesView(options: MessageRepliesViewOptions)-> some View
-   public func makeLeadingComposerView(options: LeadingComposerViewOptions)-> some View
+   public func makeMessageComposerViewType(options: MessageComposerViewTypeOptions)-> MessageComposerView<Self>
-   @ViewBuilder public func makeComposerInputView(options: ComposerInputViewOptions)-> some View
+   public func makeLeadingComposerView(options: LeadingComposerViewOptions)-> some View
-   public func makeComposerTextInputView(options: ComposerTextInputViewOptions)-> some View
+   @ViewBuilder public func makeComposerInputView(options: ComposerInputViewOptions)-> some View
-   public func makeComposerInputTrailingView(options: ComposerInputTrailingViewOptions)-> some View
+   public func makeComposerTextInputView(options: ComposerTextInputViewOptions)-> some View
-   public func makeTrailingComposerView(options: TrailingComposerViewOptions)-> some View
+   public func makeComposerInputTrailingView(options: ComposerInputTrailingViewOptions)-> some View
-   public func makeSendMessageButton(options: SendMessageButtonOptions)-> some View
+   public func makeTrailingComposerView(options: TrailingComposerViewOptions)-> some View
-   public func makeConfirmEditButton(options: ConfirmEditButtonOptions)-> some View
+   public func makeSendMessageButton(options: SendMessageButtonOptions)-> some View
-   public func makeComposerVoiceRecordingInputView(options: ComposerVoiceRecordingInputViewOptions)-> some View
+   public func makeConfirmEditButton(options: ConfirmEditButtonOptions)-> some View
-   public func makeAttachmentPickerView(options: AttachmentPickerViewOptions)-> some View
+   public func makeComposerVoiceRecordingInputView(options: ComposerVoiceRecordingInputViewOptions)-> some View
-   public func makeAttachmentCommandsPickerView(options: AttachmentCommandsPickerViewOptions)-> some View
+   public func makeAttachmentPickerView(options: AttachmentPickerViewOptions)-> some View
-   public func makeVoiceRecordingView(options: VoiceRecordingViewOptions)-> some View
+   public func makeAttachmentCommandsPickerView(options: AttachmentCommandsPickerViewOptions)-> some View
-   public func makeCustomAttachmentPickerView(options: CustomAttachmentPickerViewOptions)-> some View
+   public func makeVoiceRecordingView(options: VoiceRecordingViewOptions)-> some View
-   public func makeCustomAttachmentPreviewView(options: CustomAttachmentPreviewViewOptions)-> some View
+   public func makeCustomAttachmentPickerView(options: CustomAttachmentPickerViewOptions)-> some View
-   public func makeAttachmentTypePickerView(options: AttachmentTypePickerViewOptions)-> some View
+   public func makeCustomAttachmentPreviewView(options: CustomAttachmentPreviewViewOptions)-> some View
-   public func makeAttachmentMediaPickerView(options: AttachmentMediaPickerViewOptions)-> some View
+   public func makeAttachmentTypePickerView(options: AttachmentTypePickerViewOptions)-> some View
-   public func makeAttachmentFilePickerView(options: AttachmentFilePickerViewOptions)-> some View
+   public func makeAttachmentMediaPickerView(options: AttachmentMediaPickerViewOptions)-> some View
-   public func makeAttachmentCameraPickerView(options: AttachmentCameraPickerViewOptions)-> some View
+   public func makeAttachmentFilePickerView(options: AttachmentFilePickerViewOptions)-> some View
-   public func makeAttachmentPollPickerView(options: AttachmentPollPickerViewOptions)-> some View
+   public func makeAttachmentCameraPickerView(options: AttachmentCameraPickerViewOptions)-> some View
-   public func makeSendInChannelView(options: SendInChannelViewOptions)-> some View
+   public func makeAttachmentPollPickerView(options: AttachmentPollPickerViewOptions)-> some View
-   public func makeMessageActionsView(options: MessageActionsViewOptions)-> some View
+   public func makeSendInChannelView(options: SendInChannelViewOptions)-> some View
-   public func makeBottomReactionsView(options: ReactionsBottomViewOptions)-> some View
+   public func makeMessageActionsView(options: MessageActionsViewOptions)-> some View
-   public func makeMessageReactionView(options: MessageReactionViewOptions)-> some View
+   public func makeBottomReactionsView(options: ReactionsBottomViewOptions)-> some View
-   public func makeReactionsOverlayView(options: ReactionsOverlayViewOptions)-> some View
+   public func makeMessageReactionView(options: MessageReactionViewOptions)-> some View
-   public func makeReactionsContentView(options: ReactionsContentViewOptions)-> some View
+   public func makeReactionsOverlayView(options: ReactionsOverlayViewOptions)-> some View
-   public func makeReactionsBackgroundView(options: ReactionsBackgroundOptions)-> some View
+   public func makeReactionsContentView(options: ReactionsContentViewOptions)-> some View
-   public func makeMoreReactionsView(options: MoreReactionsViewOptions)-> some View
+   public func makeReactionsBackgroundView(options: ReactionsBackgroundOptions)-> some View
-   public func makeReactionsDetailView(options: ReactionsDetailViewOptions)-> some View
+   public func makeMoreReactionsView(options: MoreReactionsViewOptions)-> some View
-   public func makeComposerQuotedMessageView(options: ComposerQuotedMessageViewOptions)-> some View
+   public func makeReactionsDetailView(options: ReactionsDetailViewOptions)-> some View
-   public func makeChatQuotedMessageView(options: ChatQuotedMessageViewOptions)-> some View
+   public func makeComposerQuotedMessageView(options: ComposerQuotedMessageViewOptions)-> some View
-   public func makeQuotedMessageView(options: QuotedMessageViewOptions)-> some View
+   public func makeChatQuotedMessageView(options: ChatQuotedMessageViewOptions)-> some View
-   public func makeComposerEditedMessageView(options: ComposerEditedMessageViewOptions)-> some View
+   public func makeQuotedMessageView(options: QuotedMessageViewOptions)-> some View
-   public func makeMessageAttachmentPreviewThumbnailView(options: MessageAttachmentPreviewViewOptions)-> some View
+   public func makeComposerEditedMessageView(options: ComposerEditedMessageViewOptions)-> some View
-   public func makeMessageAttachmentPreviewIconView(options: MessageAttachmentPreviewIconViewOptions)-> some View
+   public func makeMessageAttachmentPreviewThumbnailView(options: MessageAttachmentPreviewViewOptions)-> some View
-   public func makeSuggestionsContainerView(options: SuggestionsContainerViewOptions)-> some View
+   public func makeMessageAttachmentPreviewIconView(options: MessageAttachmentPreviewIconViewOptions)-> some View
-   public func makeMessageReadIndicatorView(options: MessageReadIndicatorViewOptions)-> some View
+   public func makeSuggestionsContainerView(options: SuggestionsContainerViewOptions)-> some View
-   public func makeNewMessagesIndicatorView(options: NewMessagesIndicatorViewOptions)-> some View
+   public func makeMessageReadIndicatorView(options: MessageReadIndicatorViewOptions)-> some View
-   public func makeJumpToUnreadButton(options: JumpToUnreadButtonOptions)-> some View
+   public func makeNewMessagesIndicatorView(options: NewMessagesIndicatorViewOptions)-> some View
-   public func makeComposerPollView(options: ComposerPollViewOptions)-> some View
+   public func makeJumpToUnreadButton(options: JumpToUnreadButtonOptions)-> some View
-   public func makePollView(options: PollViewOptions)-> some View
+   public func makeComposerPollView(options: ComposerPollViewOptions)-> some View
-   public func makeThreadDestination(options: ThreadDestinationOptions)-> @MainActor (ChatThread) -> ChatChannelView<Self>
+   public func makePollView(options: PollViewOptions)-> some View
-   public func makeThreadListItem(options: ThreadListItemOptions<ThreadDestination>)-> some View
+   public func makeThreadDestination(options: ThreadDestinationOptions)-> @MainActor (ChatThread) -> ChatChannelView<Self>
-   public func makeNoThreadsView(options: NoThreadsViewOptions)-> some View
+   public func makeThreadListItem(options: ThreadListItemOptions<ThreadDestination>)-> some View
-   public func makeThreadListLoadingView(options: ThreadListLoadingViewOptions)-> some View
+   public func makeNoThreadsView(options: NoThreadsViewOptions)-> some View
-   public func makeThreadListContainerViewModifier(options: ThreadListContainerModifierOptions)-> some ViewModifier
+   public func makeThreadListLoadingView(options: ThreadListLoadingViewOptions)-> some View
-   public func makeThreadListHeaderViewModifier(options: ThreadListHeaderViewModifierOptions)-> some ViewModifier
+   public func makeThreadListContainerViewModifier(options: ThreadListContainerModifierOptions)-> some ViewModifier
-   public func makeThreadListHeaderView(options: ThreadListHeaderViewOptions)-> some View
+   public func makeThreadListHeaderViewModifier(options: ThreadListHeaderViewModifierOptions)-> some ViewModifier
-   public func makeThreadListFooterView(options: ThreadListFooterViewOptions)-> some View
+   public func makeThreadListHeaderView(options: ThreadListHeaderViewOptions)-> some View
-   public func makeThreadListBackground(options: ThreadListBackgroundOptions)-> some View
+   public func makeThreadListFooterView(options: ThreadListFooterViewOptions)-> some View
-   public func makeThreadListItemBackground(options: ThreadListItemBackgroundOptions)-> some View
+   public func makeThreadListBackground(options: ThreadListBackgroundOptions)-> some View
-   public func makeThreadListDividerItem(options: ThreadListDividerItemOptions)-> some View
+   public func makeThreadListItemBackground(options: ThreadListItemBackgroundOptions)-> some View
-   public func makeAddUsersView(options: AddUsersViewOptions)-> some View
+   public func makeThreadListDividerItem(options: ThreadListDividerItemOptions)-> some View
-   public func makeAttachmentTextView(options: AttachmentTextViewOptions)-> some View
+   public func makeAddUsersView(options: AddUsersViewOptions)-> some View
+   public func makeAttachmentTextView(options: AttachmentTextViewOptions)-> some View
+   public func makeStreamTextView(options: StreamTextViewOptions)-> some View

@Stream-SDK-Bot
Copy link
Collaborator

SDK Size

title v5 branch diff status
StreamChatSwiftUI 10.07 MB 9.91 MB -169 KB πŸš€

@Stream-SDK-Bot
Copy link
Collaborator

StreamChatSwiftUI XCSize

Object Diff (bytes)
VideoAttachmentView.o -142479
ImageAttachmentView.o -127825
MessageMediaAttachmentsContainerView.o +70800
LinkTextView.o -29760
AttachmentTextView.o +27576
Show 26 more objects
Object Diff (bytes)
MessageAttachmentsView.o +16229
LazyLoadingImage.o +14764
MessageMediaAttachmentContentView.o +9211
LinkAttachmentView.o -7804
MessageView.o -7075
FileAttachmentView.o -6412
LoadingSpinnerView.o +6255
VideoPlayIndicatorView.o +6239
VoiceRecordingContainerView.o -6064
MediaAttachment.o +6002
MessageViewFactoryOptions.o +1550
DefaultViewFactory.o +1068
AttachmentViewFactoryOptions.o +1021
ChatQuotedMessageView.o +413
GiphyAttachmentView.o +342
AlertBannerViewModifier.o -192
ComposerViewFactoryOptions.o +187
ViewFactory.o +182
TitleWithCloseButton.o +168
GalleryView.o +128
InputTextView.o +124
ReactionsViewFactoryOptions.o +108
FileAttachmentsView.o +76
MessageActionsView.o -72
SelectionBadgeView.o -58
ReactionsIconProvider.o -52

@sonarqubecloud
Copy link

sonarqubecloud bot commented Mar 6, 2026

Quality Gate Failed Quality Gate failed

Failed conditions
1 Security Hotspot
78.2% Coverage on New Code (required β‰₯ 80%)

See analysis details on SonarQube Cloud

@laevandus laevandus merged commit 2b11603 into v5 Mar 6, 2026
11 of 12 checks passed
@laevandus laevandus deleted the v5-media-attachment-refresh branch March 6, 2026 12:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants