diff --git a/CHANGELOG.md b/CHANGELOG.md index 34c60c47..e8dbb6da 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,7 +3,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). # Upcoming -### 🔄 Changed +### ✅ Added +- Thread replies shown in channel indicator [#518](https://github.com/GetStream/stream-chat-swiftui/pull/518) # [4.57.0](https://github.com/GetStream/stream-chat-swiftui/releases/tag/4.57.0) _June 07, 2024_ diff --git a/Sources/StreamChatSwiftUI/ChatChannel/ChannelControllerFactory.swift b/Sources/StreamChatSwiftUI/ChatChannel/ChannelControllerFactory.swift index a01b9d33..da9795b8 100644 --- a/Sources/StreamChatSwiftUI/ChatChannel/ChannelControllerFactory.swift +++ b/Sources/StreamChatSwiftUI/ChatChannel/ChannelControllerFactory.swift @@ -10,7 +10,7 @@ class ChannelControllerFactory { @Injected(\.chatClient) var chatClient - private var currentChannelController: ChatChannelController? + var currentChannelController: ChatChannelController? private var messageControllers = [String: ChatMessageController]() /// Creates a channel controller with the provided channel id. diff --git a/Sources/StreamChatSwiftUI/ChatChannel/MessageList/MessageContainerView.swift b/Sources/StreamChatSwiftUI/ChatChannel/MessageList/MessageContainerView.swift index 90ddbf3f..12c2c84c 100644 --- a/Sources/StreamChatSwiftUI/ChatChannel/MessageList/MessageContainerView.swift +++ b/Sources/StreamChatSwiftUI/ChatChannel/MessageList/MessageContainerView.swift @@ -166,14 +166,28 @@ public struct MessageContainerView: View { .accessibilityElement(children: .contain) .accessibilityIdentifier("MessageView") - if message.replyCount > 0 && !isInThread { - factory.makeMessageRepliesView( - channel: channel, - message: message, - replyCount: message.replyCount - ) - .accessibilityElement(children: .contain) - .accessibility(identifier: "MessageRepliesView") + if !isInThread { + if message.replyCount > 0 { + factory.makeMessageRepliesView( + channel: channel, + message: message, + replyCount: message.replyCount + ) + .accessibilityElement(children: .contain) + .accessibility(identifier: "MessageRepliesView") + } else if message.showReplyInChannel, + let parentId = message.parentMessageId, + let controller = utils.channelControllerFactory.currentChannelController, + let parentMessage = controller.dataStore.message(id: parentId) { + factory.makeMessageRepliesShownInChannelView( + channel: channel, + message: message, + parentMessage: parentMessage, + replyCount: parentMessage.replyCount + ) + .accessibilityElement(children: .contain) + .accessibility(identifier: "MessageRepliesView") + } } if bottomReactionsShown { diff --git a/Sources/StreamChatSwiftUI/ChatChannel/MessageList/MessageRepliesView.swift b/Sources/StreamChatSwiftUI/ChatChannel/MessageList/MessageRepliesView.swift index 5e099723..8aec642c 100644 --- a/Sources/StreamChatSwiftUI/ChatChannel/MessageList/MessageRepliesView.swift +++ b/Sources/StreamChatSwiftUI/ChatChannel/MessageList/MessageRepliesView.swift @@ -20,12 +20,23 @@ public struct MessageRepliesView: View { var channel: ChatChannel var message: ChatMessage var replyCount: Int + var isRightAligned: Bool + var showReplyCount: Bool - public init(factory: Factory, channel: ChatChannel, message: ChatMessage, replyCount: Int) { + public init( + factory: Factory, + channel: ChatChannel, + message: ChatMessage, + replyCount: Int, + showReplyCount: Bool = true, + isRightAligned: Bool? = nil + ) { self.factory = factory self.channel = channel self.message = message self.replyCount = replyCount + self.isRightAligned = isRightAligned ?? message.isRightAligned + self.showReplyCount = showReplyCount } public var body: some View { @@ -41,15 +52,15 @@ public struct MessageRepliesView: View { ) } label: { HStack { - if !message.isRightAligned { + if !isRightAligned { MessageAvatarView( avatarURL: message.threadParticipants.first?.imageURL, size: .init(width: 16, height: 16) ) } - Text("\(replyCount) \(repliesText)") + Text(title) .font(fonts.footnoteBold) - if message.isRightAligned { + if isRightAligned { MessageAvatarView( avatarURL: message.threadParticipants.first?.imageURL, size: .init(width: 16, height: 16) @@ -81,13 +92,21 @@ public struct MessageRepliesView: View { ) .offset(y: -24) .rotation3DEffect( - .degrees(message.isRightAligned ? 180 : 0), + .degrees(isRightAligned ? 180 : 0), axis: (x: 0, y: 1, z: 0) ) ) .foregroundColor(colors.tintColor) } } + + var title: String { + if showReplyCount { + return "\(replyCount) \(repliesText)" + } else { + return L10n.Message.Threads.reply + } + } var repliesText: String { if message.replyCount == 1 { diff --git a/Sources/StreamChatSwiftUI/DefaultViewFactory.swift b/Sources/StreamChatSwiftUI/DefaultViewFactory.swift index e556d898..85a90c42 100644 --- a/Sources/StreamChatSwiftUI/DefaultViewFactory.swift +++ b/Sources/StreamChatSwiftUI/DefaultViewFactory.swift @@ -496,6 +496,22 @@ extension ViewFactory { ) } + public func makeMessageRepliesShownInChannelView( + channel: ChatChannel, + message: ChatMessage, + parentMessage: ChatMessage, + replyCount: Int + ) -> some View { + MessageRepliesView( + factory: self, + channel: channel, + message: parentMessage, + replyCount: replyCount, + showReplyCount: false, + isRightAligned: message.isRightAligned + ) + } + public func makeMessageComposerViewType( with channelController: ChatChannelController, messageController: ChatMessageController?, diff --git a/Sources/StreamChatSwiftUI/ViewFactory.swift b/Sources/StreamChatSwiftUI/ViewFactory.swift index f880695e..97c4db83 100644 --- a/Sources/StreamChatSwiftUI/ViewFactory.swift +++ b/Sources/StreamChatSwiftUI/ViewFactory.swift @@ -524,6 +524,21 @@ public protocol ViewFactory: AnyObject { message: ChatMessage, replyCount: Int ) -> MessageRepliesViewType + + associatedtype MessageRepliesShownInChannelViewType: View + /// Creates the message replies view for a reply that is also shown in a channel. + /// - Parameters: + /// - channel: the channel where the message is sent. + /// - message: the message that's being replied to. + /// - parentMessage: the parent message. + /// - replyCount: the current number of replies. + /// - Returns: view displayed in the message replies view slot. + func makeMessageRepliesShownInChannelView( + channel: ChatChannel, + message: ChatMessage, + parentMessage: ChatMessage, + replyCount: Int + ) -> MessageRepliesShownInChannelViewType associatedtype MessageComposerViewType: View /// Creates the message composer view. diff --git a/StreamChatSwiftUITests/Tests/ChatChannel/MessageView_Tests.swift b/StreamChatSwiftUITests/Tests/ChatChannel/MessageView_Tests.swift index 168c9827..7c839310 100644 --- a/StreamChatSwiftUITests/Tests/ChatChannel/MessageView_Tests.swift +++ b/StreamChatSwiftUITests/Tests/ChatChannel/MessageView_Tests.swift @@ -421,6 +421,30 @@ class MessageView_Tests: StreamChatTestCase { // Then assertSnapshot(matching: view, as: .image(perceptualPrecision: precision)) } + + func test_messageRepliesViewShownInChannel_snapshot() { + // Given + let channel = ChatChannel.mockDMChannel() + let message = ChatMessage.mock( + id: .unique, + cid: channel.cid, + text: "Message with replies", + author: .mock(id: .unique) + ) + + // When + let view = MessageRepliesView( + factory: DefaultViewFactory.shared, + channel: channel, + message: message, + replyCount: 3, + showReplyCount: false + ) + .frame(width: 300, height: 60) + + // Then + assertSnapshot(matching: view, as: .image(perceptualPrecision: precision)) + } func test_topLeftView_snapshot() { // Given diff --git a/StreamChatSwiftUITests/Tests/ChatChannel/__Snapshots__/MessageView_Tests/test_messageRepliesViewShownInChannel_snapshot.1.png b/StreamChatSwiftUITests/Tests/ChatChannel/__Snapshots__/MessageView_Tests/test_messageRepliesViewShownInChannel_snapshot.1.png new file mode 100644 index 00000000..1b52ffa3 Binary files /dev/null and b/StreamChatSwiftUITests/Tests/ChatChannel/__Snapshots__/MessageView_Tests/test_messageRepliesViewShownInChannel_snapshot.1.png differ diff --git a/StreamChatSwiftUITests/Tests/Utils/ViewFactory_Tests.swift b/StreamChatSwiftUITests/Tests/Utils/ViewFactory_Tests.swift index 08d3da9a..7fa5705f 100644 --- a/StreamChatSwiftUITests/Tests/Utils/ViewFactory_Tests.swift +++ b/StreamChatSwiftUITests/Tests/Utils/ViewFactory_Tests.swift @@ -760,6 +760,22 @@ class ViewFactory_Tests: StreamChatTestCase { // Then XCTAssert(view is MessageRepliesView) } + + func test_viewFactory_makeMessageRepliesShownInChannelView() { + // Given + let viewFactory = DefaultViewFactory.shared + + // When + let view = viewFactory.makeMessageRepliesShownInChannelView( + channel: ChatChannel.mockDMChannel(), + message: message, + parentMessage: message, + replyCount: 2 + ) + + // Then + XCTAssert(view is MessageRepliesView) + } func test_viewFactory_makeTypingIndicatorBottomView() { // Given