diff --git a/CHANGELOG.md b/CHANGELOG.md index 19b6df623..322b1aeb1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). # Upcoming ### ✅ Added +- Add factory methods for gallery and video player view [#808](https://github.com/GetStream/stream-chat-swiftui/pull/808) - Add support for editing message attachments [#806](https://github.com/GetStream/stream-chat-swiftui/pull/806) ### 🐞 Fixed - Fix scrolling to the bottom when editing a message [#806](https://github.com/GetStream/stream-chat-swiftui/pull/806) diff --git a/Sources/StreamChatSwiftUI/ChatChannel/ChannelInfo/MediaAttachmentsView.swift b/Sources/StreamChatSwiftUI/ChatChannel/ChannelInfo/MediaAttachmentsView.swift index 177076522..9cc09d28e 100644 --- a/Sources/StreamChatSwiftUI/ChatChannel/ChannelInfo/MediaAttachmentsView.swift +++ b/Sources/StreamChatSwiftUI/ChatChannel/ChannelInfo/MediaAttachmentsView.swift @@ -58,6 +58,7 @@ public struct MediaAttachmentsView: View { if !mediaItem.isVideo, let imageAttachment = mediaItem.imageAttachment { let index = viewModel.allImageAttachments.firstIndex { $0.id == imageAttachment.id } ?? 0 ImageAttachmentContentView( + factory: factory, mediaItem: mediaItem, imageAttachment: imageAttachment, allImageAttachments: viewModel.allImageAttachments, @@ -66,8 +67,9 @@ public struct MediaAttachmentsView: View { ) } else if let videoAttachment = mediaItem.videoAttachment { VideoAttachmentContentView( + factory: factory, attachment: videoAttachment, - author: mediaItem.author, + message: mediaItem.message, width: Self.itemWidth, ratio: 1, cornerRadius: 0 @@ -78,9 +80,9 @@ public struct MediaAttachmentsView: View { BottomRightView { factory.makeMessageAvatarView( for: UserDisplayInfo( - id: mediaItem.author.id, - name: mediaItem.author.name ?? "", - imageURL: mediaItem.author.imageURL, + id: mediaItem.message.author.id, + name: mediaItem.message.author.name ?? "", + imageURL: mediaItem.message.author.imageURL, size: .init(width: 24, height: 24) ) ) @@ -108,10 +110,11 @@ public struct MediaAttachmentsView: View { } } -struct ImageAttachmentContentView: View { +struct ImageAttachmentContentView: View { @State private var galleryShown = false + let factory: Factory let mediaItem: MediaItem let imageAttachment: ChatMessageImageAttachment let allImageAttachments: [ChatMessageImageAttachment] @@ -134,11 +137,11 @@ struct ImageAttachmentContentView: View { .clipped() } .fullScreenCover(isPresented: $galleryShown) { - GalleryView( - imageAttachments: allImageAttachments, - author: mediaItem.author, + factory.makeGalleryView( + mediaAttachments: allImageAttachments.map { MediaAttachment(from: $0) }, + message: mediaItem.message, isShown: $galleryShown, - selected: index + options: .init(selectedIndex: index) ) } } diff --git a/Sources/StreamChatSwiftUI/ChatChannel/ChannelInfo/MediaAttachmentsViewModel.swift b/Sources/StreamChatSwiftUI/ChatChannel/ChannelInfo/MediaAttachmentsViewModel.swift index bca7fb379..2ce591373 100644 --- a/Sources/StreamChatSwiftUI/ChatChannel/ChannelInfo/MediaAttachmentsViewModel.swift +++ b/Sources/StreamChatSwiftUI/ChatChannel/ChannelInfo/MediaAttachmentsViewModel.swift @@ -84,7 +84,7 @@ class MediaAttachmentsViewModel: ObservableObject, ChatMessageSearchControllerDe let mediaItem = MediaItem( id: imageAttachment.id.rawValue, isVideo: false, - author: message.author, + message: message, videoAttachment: nil, imageAttachment: imageAttachment ) @@ -94,7 +94,7 @@ class MediaAttachmentsViewModel: ObservableObject, ChatMessageSearchControllerDe let mediaItem = MediaItem( id: videoAttachment.id.rawValue, isVideo: true, - author: message.author, + message: message, videoAttachment: videoAttachment, imageAttachment: nil ) @@ -110,7 +110,7 @@ class MediaAttachmentsViewModel: ObservableObject, ChatMessageSearchControllerDe struct MediaItem: Identifiable { let id: String let isVideo: Bool - let author: ChatUser + let message: ChatMessage var videoAttachment: ChatMessageVideoAttachment? var imageAttachment: ChatMessageImageAttachment? diff --git a/Sources/StreamChatSwiftUI/ChatChannel/Gallery/GalleryView.swift b/Sources/StreamChatSwiftUI/ChatChannel/Gallery/GalleryView.swift index 7ecd4762f..6681b401d 100644 --- a/Sources/StreamChatSwiftUI/ChatChannel/Gallery/GalleryView.swift +++ b/Sources/StreamChatSwiftUI/ChatChannel/Gallery/GalleryView.swift @@ -28,19 +28,7 @@ public struct GalleryView: View { isShown: Binding, selected: Int ) { - let mediaAttachments = imageAttachments.map { attachment in - let url: URL - if let state = attachment.uploadingState { - url = state.localFileURL - } else { - url = attachment.imageURL - } - return MediaAttachment( - url: url, - type: .image, - uploadingState: attachment.uploadingState - ) - } + let mediaAttachments = imageAttachments.map { MediaAttachment(from: $0) } self.init( mediaAttachments: mediaAttachments, author: author, @@ -49,7 +37,7 @@ public struct GalleryView: View { ) } - init( + public init( mediaAttachments: [MediaAttachment], author: ChatUser, isShown: Binding, diff --git a/Sources/StreamChatSwiftUI/ChatChannel/MessageList/ImageAttachmentView.swift b/Sources/StreamChatSwiftUI/ChatChannel/MessageList/ImageAttachmentView.swift index 36d1b5c78..eacde8615 100644 --- a/Sources/StreamChatSwiftUI/ChatChannel/MessageList/ImageAttachmentView.swift +++ b/Sources/StreamChatSwiftUI/ChatChannel/MessageList/ImageAttachmentView.swift @@ -62,11 +62,11 @@ public struct ImageAttachmentContainer: View { .fullScreenCover(isPresented: $galleryShown, onDismiss: { self.selectedIndex = 0 }) { - GalleryView( + factory.makeGalleryView( mediaAttachments: sources, - author: message.author, + message: message, isShown: $galleryShown, - selected: selectedIndex + options: .init(selectedIndex: selectedIndex) ) } .accessibilityIdentifier("ImageAttachmentContainer") @@ -431,7 +431,7 @@ extension ChatMessage { } } -struct MediaAttachment { +public struct MediaAttachment { @Injected(\.utils) var utils let url: URL @@ -460,7 +460,29 @@ struct MediaAttachment { } } +extension MediaAttachment { + init(from attachment: ChatMessageImageAttachment) { + let url: URL + if let state = attachment.uploadingState { + url = state.localFileURL + } else { + url = attachment.imageURL + } + self.init( + url: url, + type: .image, + uploadingState: attachment.uploadingState + ) + } +} + enum MediaAttachmentType { case image case video } + +/// Options for the gallery view. +public struct MediaViewsOptions { + /// The index of the selected media item. + public let selectedIndex: Int +} diff --git a/Sources/StreamChatSwiftUI/ChatChannel/MessageList/VideoAttachmentView.swift b/Sources/StreamChatSwiftUI/ChatChannel/MessageList/VideoAttachmentView.swift index c285dfe87..534240fd8 100644 --- a/Sources/StreamChatSwiftUI/ChatChannel/MessageList/VideoAttachmentView.swift +++ b/Sources/StreamChatSwiftUI/ChatChannel/MessageList/VideoAttachmentView.swift @@ -24,6 +24,7 @@ public struct VideoAttachmentsContainer: View { ) VideoAttachmentsList( + factory: factory, message: message, width: width ) @@ -38,6 +39,7 @@ public struct VideoAttachmentsContainer: View { ) } else { VideoAttachmentsList( + factory: factory, message: message, width: width ) @@ -63,12 +65,18 @@ public struct VideoAttachmentsContainer: View { } } -public struct VideoAttachmentsList: View { +public struct VideoAttachmentsList: View { + let factory: Factory let message: ChatMessage let width: CGFloat - public init(message: ChatMessage, width: CGFloat) { + public init( + factory: Factory = DefaultViewFactory.shared, + message: ChatMessage, + width: CGFloat + ) { + self.factory = factory self.message = message self.width = width } @@ -77,6 +85,7 @@ public struct VideoAttachmentsList: View { VStack { ForEach(message.videoAttachments, id: \.self) { attachment in VideoAttachmentView( + factory: factory, attachment: attachment, message: message, width: width @@ -90,8 +99,9 @@ public struct VideoAttachmentsList: View { } } -public struct VideoAttachmentView: View { +public struct VideoAttachmentView: View { + let factory: Factory let attachment: ChatMessageVideoAttachment let message: ChatMessage let width: CGFloat @@ -99,12 +109,14 @@ public struct VideoAttachmentView: View { var cornerRadius: CGFloat = 24 public init( + factory: Factory = DefaultViewFactory.shared, attachment: ChatMessageVideoAttachment, message: ChatMessage, width: CGFloat, ratio: CGFloat = 0.75, cornerRadius: CGFloat = 24 ) { + self.factory = factory self.attachment = attachment self.message = message self.width = width @@ -118,8 +130,9 @@ public struct VideoAttachmentView: View { public var body: some View { VideoAttachmentContentView( + factory: factory, attachment: attachment, - author: message.author, + message: message, width: width, ratio: ratio, cornerRadius: cornerRadius @@ -128,7 +141,7 @@ public struct VideoAttachmentView: View { } } -struct VideoAttachmentContentView: View { +struct VideoAttachmentContentView: View { @Injected(\.utils) private var utils @Injected(\.images) private var images @@ -137,8 +150,9 @@ struct VideoAttachmentContentView: View { utils.videoPreviewLoader } + let factory: Factory let attachment: ChatMessageVideoAttachment - let author: ChatUser + let message: ChatMessage let width: CGFloat var ratio: CGFloat = 0.75 var cornerRadius: CGFloat = 24 @@ -183,10 +197,11 @@ struct VideoAttachmentContentView: View { .frame(width: width, height: width * ratio) .cornerRadius(cornerRadius) .fullScreenCover(isPresented: $fullScreenShown) { - VideoPlayerView( + factory.makeVideoPlayerView( attachment: attachment, - author: author, - isShown: $fullScreenShown + message: message, + isShown: $fullScreenShown, + options: .init(selectedIndex: 0) ) } .onAppear { diff --git a/Sources/StreamChatSwiftUI/DefaultViewFactory.swift b/Sources/StreamChatSwiftUI/DefaultViewFactory.swift index 4f350e9e3..3e14a3829 100644 --- a/Sources/StreamChatSwiftUI/DefaultViewFactory.swift +++ b/Sources/StreamChatSwiftUI/DefaultViewFactory.swift @@ -445,6 +445,33 @@ extension ViewFactory { ) } + public func makeGalleryView( + mediaAttachments: [MediaAttachment], + message: ChatMessage, + isShown: Binding, + options: MediaViewsOptions + ) -> some View { + GalleryView( + mediaAttachments: mediaAttachments, + author: message.author, + isShown: isShown, + selected: options.selectedIndex + ) + } + + public func makeVideoPlayerView( + attachment: ChatMessageVideoAttachment, + message: ChatMessage, + isShown: Binding, + options: MediaViewsOptions + ) -> some View { + VideoPlayerView( + attachment: attachment, + author: message.author, + isShown: isShown + ) + } + public func makeDeletedMessageView( for message: ChatMessage, isFirst: Bool, diff --git a/Sources/StreamChatSwiftUI/ViewFactory.swift b/Sources/StreamChatSwiftUI/ViewFactory.swift index 7f4db11d9..762eaadc8 100644 --- a/Sources/StreamChatSwiftUI/ViewFactory.swift +++ b/Sources/StreamChatSwiftUI/ViewFactory.swift @@ -439,6 +439,36 @@ public protocol ViewFactory: AnyObject { availableWidth: CGFloat, scrolledId: Binding ) -> VideoAttachmentViewType + + associatedtype GalleryViewType: View + /// Creates the gallery view. + /// - Parameters: + /// - mediaAttachments: the media attachments that will be displayed. + /// - message: the message whose attachments will be displayed. + /// - isShown: whether the gallery is shown. + /// - options: additional options used to configure the gallery view. + /// - Returns: view displayed in the gallery slot. + func makeGalleryView( + mediaAttachments: [MediaAttachment], + message: ChatMessage, + isShown: Binding, + options: MediaViewsOptions + ) -> GalleryViewType + + associatedtype VideoPlayerViewType: View + /// Creates the video player view. + /// - Parameters: + /// - attachment: the video attachment that will be displayed. + /// - message: the message whose attachments will be displayed. + /// - isShown: whether the video player is shown. + /// - options: additional options used to configure the gallery view. + /// - Returns: view displayed in the video player slot. + func makeVideoPlayerView( + attachment: ChatMessageVideoAttachment, + message: ChatMessage, + isShown: Binding, + options: MediaViewsOptions + ) -> VideoPlayerViewType associatedtype DeletedMessageViewType: View /// Creates the deleted message view. diff --git a/StreamChatSwiftUITests/Tests/Utils/ViewFactory_Tests.swift b/StreamChatSwiftUITests/Tests/Utils/ViewFactory_Tests.swift index a2fed546e..8387b3faa 100644 --- a/StreamChatSwiftUITests/Tests/Utils/ViewFactory_Tests.swift +++ b/StreamChatSwiftUITests/Tests/Utils/ViewFactory_Tests.swift @@ -968,6 +968,38 @@ class ViewFactory_Tests: StreamChatTestCase { // Then XCTAssert(view is ChannelAvatarView) } + + func test_viewFactory_makeGalleryView() { + // Given + let viewFactory = DefaultViewFactory.shared + + // When + let view = viewFactory.makeGalleryView( + mediaAttachments: [], + message: .mock(), + isShown: .constant(true), + options: .init(selectedIndex: 0) + ) + + // Then + XCTAssert(view is GalleryView) + } + + func test_viewFactory_makeVideoPlayerView() { + // Given + let viewFactory = DefaultViewFactory.shared + + // When + let view = viewFactory.makeVideoPlayerView( + attachment: .mock(id: .unique), + message: .mock(), + isShown: .constant(true), + options: .init(selectedIndex: 0) + ) + + // Then + XCTAssert(view is VideoPlayerView) + } } extension ChannelAction: Equatable {