-
Notifications
You must be signed in to change notification settings - Fork 117
V5: message list attachments redesign #1264
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. Weβll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 20 commits
42c025c
d872297
68b164c
88810ec
580e573
3db03d8
b78ed39
bcfc15a
e53cb1a
b7c1d35
90f756f
a1f4338
5c5cbc8
cd98c27
4712642
39ebde7
67f80eb
e7460b7
f4ab851
c58fa00
ba86cb7
ad2c95e
0e21e24
f3584b8
2e76d1f
5a1c10e
0237de7
c7845e2
be8510e
c1f07af
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -9,6 +9,7 @@ | |
| public struct VoiceRecordingContainerView<Factory: ViewFactory>: View { | ||
| @Injected(\.colors) var colors | ||
| @Injected(\.images) var images | ||
| @Injected(\.tokens) var tokens | ||
| @Injected(\.utils) var utils | ||
|
|
||
| let factory: Factory | ||
|
|
@@ -39,41 +40,23 @@ | |
| } | ||
|
|
||
| public var body: some View { | ||
| VStack(spacing: 0) { | ||
| VStack { | ||
| if let quotedMessage = message.quotedMessage { | ||
| factory.makeChatQuotedMessageView( | ||
| options: ChatQuotedMessageViewOptions( | ||
| quotedMessage: quotedMessage, | ||
| parentMessage: message, | ||
| scrolledId: $scrolledId | ||
| ) | ||
| ) | ||
| } | ||
| VStack(spacing: 2) { | ||
| ForEach(message.voiceRecordingAttachments, id: \.self) { attachment in | ||
| VoiceRecordingView( | ||
| handler: handler, | ||
| textColor: textColor(for: message), | ||
| addedVoiceRecording: AddedVoiceRecording( | ||
| url: attachment.payload.voiceRecordingURL, | ||
| duration: attachment.payload.duration ?? 0, | ||
| waveform: attachment.payload.waveformData ?? [] | ||
| ), | ||
| index: index(for: attachment) | ||
| ) | ||
| .padding(.all, 8) | ||
| .background(Color(colors.background8)) | ||
| .roundWithBorder(cornerRadius: 14) | ||
| } | ||
| } | ||
| } | ||
| if !message.text.isEmpty { | ||
| AttachmentTextView(factory: factory, message: message) | ||
| .frame(maxWidth: .infinity) | ||
| VStack(spacing: 2) { | ||
| ForEach(message.voiceRecordingAttachments, id: \.self) { attachment in | ||
|
||
| VoiceRecordingView( | ||
| handler: handler, | ||
| textColor: textColor(for: message), | ||
| addedVoiceRecording: AddedVoiceRecording( | ||
| url: attachment.payload.voiceRecordingURL, | ||
| duration: attachment.payload.duration ?? 0, | ||
| waveform: attachment.payload.waveformData ?? [] | ||
| ), | ||
| index: index(for: attachment) | ||
| ) | ||
| .padding(.all, tokens.spacingXs) | ||
| .background(backgroundColor) | ||
| .roundWithBorder(cornerRadius: tokens.messageBubbleRadiusAttachment) | ||
| } | ||
| } | ||
| .padding(.all, 2) | ||
| .onReceive(handler.$context, perform: { value in | ||
| guard message.voiceRecordingAttachments.count > 1 else { return } | ||
| if value.state == .playing { | ||
|
|
@@ -95,26 +78,26 @@ | |
| .onAppear { | ||
| player.subscribe(handler) | ||
| } | ||
| .modifier( | ||
| factory.styles.makeMessageViewModifier( | ||
| for: MessageModifierInfo( | ||
| message: message, | ||
| isFirst: isFirst, | ||
| cornerRadius: 16 | ||
| ) | ||
| ) | ||
| ) | ||
| } | ||
|
|
||
| private func index(for attachment: ChatMessageVoiceRecordingAttachment) -> Int { | ||
| message.voiceRecordingAttachments.firstIndex(of: attachment) ?? 0 | ||
| } | ||
|
|
||
| private var backgroundColor: Color { | ||
| let attachmentCounts = message.attachmentCounts | ||
| if message.text.isEmpty, attachmentCounts.count == 1, attachmentCounts[.voiceRecording] == 1 { | ||
| return .clear | ||
| } | ||
| return Color(message.isSentByCurrentUser ? colors.chatBackgroundAttachmentOutgoing : colors.chatBackgroundAttachmentIncoming) | ||
| } | ||
| } | ||
|
|
||
| struct VoiceRecordingView: View { | ||
| @Injected(\.utils) var utils | ||
| @Injected(\.colors) var colors | ||
| @Injected(\.images) var images | ||
| @Injected(\.tokens) var tokens | ||
|
|
||
| @State var isPlaying: Bool = false | ||
| @State var loading: Bool = false | ||
|
|
@@ -139,7 +122,7 @@ | |
| } | ||
|
|
||
| var body: some View { | ||
| HStack { | ||
| HStack(spacing: tokens.spacingXs) { | ||
| Button(action: { | ||
| handlePlayTap() | ||
| }, label: { | ||
|
|
@@ -169,7 +152,7 @@ | |
| .lineLimit(1) | ||
| .foregroundColor(textColor) | ||
|
|
||
| HStack { | ||
| HStack(spacing: tokens.spacingXs) { | ||
| RecordingDurationView( | ||
| duration: showContextDuration ? handler.context.currentTime : addedVoiceRecording.duration | ||
| ) | ||
|
|
@@ -256,7 +239,7 @@ | |
| @Published var context: AudioPlaybackContext = .notLoaded | ||
|
|
||
| func audioPlayer( | ||
| _ audioPlayer: AudioPlaying, | ||
|
Check warning on line 242 in Sources/StreamChatSwiftUI/ChatMessageList/AsyncVoiceMessages/VoiceRecordingContainerView.swift
|
||
| didUpdateContext context: AudioPlaybackContext | ||
| ) { | ||
| self.context = context | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,40 @@ | ||
| // | ||
| // Copyright Β© 2026 Stream.io Inc. All rights reserved. | ||
| // | ||
|
|
||
| import StreamChat | ||
| import SwiftUI | ||
|
|
||
| public struct AttachmentTextView<Factory: ViewFactory>: View { | ||
| @Injected(\.colors) private var colors | ||
| @Injected(\.fonts) private var fonts | ||
| @Injected(\.tokens) private var tokens | ||
|
|
||
| var factory: Factory | ||
| var message: ChatMessage | ||
| let injectedBackgroundColor: UIColor? | ||
|
|
||
| public init(factory: Factory = DefaultViewFactory.shared, message: ChatMessage, injectedBackgroundColor: UIColor? = nil) { | ||
| self.factory = factory | ||
| self.message = message | ||
| self.injectedBackgroundColor = injectedBackgroundColor | ||
| } | ||
|
|
||
| public var body: some View { | ||
| HStack { | ||
| factory.makeStreamTextView(options: .init(message: message)) | ||
| .padding(.horizontal, tokens.spacingXxs) | ||
| .fixedSize(horizontal: false, vertical: true) | ||
| Spacer() | ||
martinmitrevski marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| } | ||
| .background(Color(backgroundColor)) | ||
| .accessibilityIdentifier("MessageTextView") | ||
| } | ||
|
|
||
| private var backgroundColor: UIColor { | ||
| if let injectedBackgroundColor { | ||
| return injectedBackgroundColor | ||
| } | ||
| return message.isSentByCurrentUser ? colors.chatBackgroundOutgoing : colors.chatBackgroundIncoming | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -6,6 +6,8 @@ | |
| import SwiftUI | ||
|
|
||
| public struct FileAttachmentsContainer<Factory: ViewFactory>: View { | ||
| @Injected(\.colors) var colors | ||
| @Injected(\.tokens) var tokens | ||
| var factory: Factory | ||
| var message: ChatMessage | ||
| var width: CGFloat | ||
|
|
@@ -27,55 +29,35 @@ | |
| } | ||
|
|
||
| public var body: some View { | ||
| VStack(alignment: message.alignmentInBubble) { | ||
| if let quotedMessage = message.quotedMessage { | ||
| factory.makeChatQuotedMessageView( | ||
| options: ChatQuotedMessageViewOptions( | ||
| quotedMessage: quotedMessage, | ||
| parentMessage: message, | ||
| scrolledId: $scrolledId | ||
| ) | ||
| VStack(spacing: 4) { | ||
nuno-vieira marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| ForEach(message.fileAttachments, id: \.self) { attachment in | ||
|
||
| FileAttachmentView( | ||
| attachment: attachment, | ||
| width: width, | ||
| isFirst: isFirst | ||
| ) | ||
| .background(backgroundColor) | ||
| .roundWithBorder(cornerRadius: tokens.messageBubbleRadiusAttachment) | ||
| } | ||
|
|
||
| VStack(spacing: 0) { | ||
| VStack(spacing: 4) { | ||
| ForEach(message.fileAttachments, id: \.self) { attachment in | ||
| FileAttachmentView( | ||
| attachment: attachment, | ||
| width: width, | ||
| isFirst: isFirst | ||
| ) | ||
| } | ||
| } | ||
| if !message.text.isEmpty { | ||
| HStack { | ||
| Text(message.adjustedText) | ||
| .foregroundColor(textColor(for: message)) | ||
| .standardPadding() | ||
| Spacer() | ||
| } | ||
| } | ||
| } | ||
| .padding(.all, 4) | ||
| } | ||
| .modifier( | ||
| factory.styles.makeMessageViewModifier( | ||
| for: MessageModifierInfo( | ||
| message: message, | ||
| isFirst: isFirst | ||
| ) | ||
| ) | ||
| ) | ||
| .accessibilityIdentifier("FileAttachmentsContainer") | ||
| } | ||
|
|
||
| private var backgroundColor: Color { | ||
| let attachmentCounts = message.attachmentCounts | ||
|
||
| if message.text.isEmpty, attachmentCounts.count == 1, attachmentCounts[.file] == 1 { | ||
| return .clear | ||
| } | ||
| return Color(message.isSentByCurrentUser ? colors.chatBackgroundAttachmentOutgoing : colors.chatBackgroundAttachmentIncoming) | ||
| } | ||
| } | ||
|
|
||
| public struct FileAttachmentView: View { | ||
| @Injected(\.utils) private var utils | ||
| @Injected(\.images) private var images | ||
| @Injected(\.fonts) private var fonts | ||
| @Injected(\.colors) private var colors | ||
| @Injected(\.tokens) private var tokens | ||
| @Injected(\.chatClient) private var chatClient | ||
|
|
||
| @State private var fullScreenShown = false | ||
|
|
@@ -110,10 +92,8 @@ | |
| DownloadShareAttachmentView(attachment: attachment) | ||
| } | ||
| } | ||
| .padding(.all, 8) | ||
| .background(Color(colors.background)) | ||
| .padding(.all, tokens.spacingSm) | ||
| .frame(width: width) | ||
| .roundWithBorder() | ||
| .withUploadingStateIndicator(for: attachment.uploadingState, url: attachment.assetURL) | ||
| .withDownloadingStateIndicator(for: attachment.downloadingState, url: attachment.assetURL) | ||
| .sheet(isPresented: $fullScreenShown) { | ||
|
|
@@ -135,6 +115,7 @@ | |
| @Injected(\.images) private var images | ||
| @Injected(\.fonts) private var fonts | ||
| @Injected(\.colors) private var colors | ||
| @Injected(\.tokens) private var tokens | ||
|
|
||
| var url: URL | ||
| var title: String | ||
|
|
@@ -147,13 +128,13 @@ | |
| } | ||
|
|
||
| public var body: some View { | ||
| HStack { | ||
| HStack(spacing: tokens.spacingSm) { | ||
| Image(uiImage: previewImage) | ||
| .resizable() | ||
| .aspectRatio(contentMode: .fit) | ||
| .frame(width: 34, height: 40) | ||
| .accessibilityHidden(true) | ||
| VStack(alignment: .leading, spacing: 8) { | ||
| VStack(alignment: .leading, spacing: tokens.spacingXxs) { | ||
| Text(title) | ||
| .font(fonts.bodyBold) | ||
| .lineLimit(1) | ||
|
|
@@ -241,5 +222,5 @@ | |
| UIActivityViewController(activityItems: activityItems, applicationActivities: nil) | ||
| } | ||
|
|
||
| func updateUIViewController(_ uiViewController: UIActivityViewController, context: Context) {} | ||
|
Check warning on line 225 in Sources/StreamChatSwiftUI/ChatMessageList/FileAttachmentView.swift
|
||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.