From cfb4b2ed9eeee59c066a54f47577e5c17b52f097 Mon Sep 17 00:00:00 2001 From: Nuno Vieira Date: Thu, 5 Dec 2024 17:25:39 +0000 Subject: [PATCH 01/10] First solution: Provide the reply in the parentMessage parameter and do workaround --- .../MessageList/MessageContainerView.swift | 6 +-- .../MessageList/MessageRepliesView.swift | 42 +++++++++++++------ .../DefaultViewFactory.swift | 1 + Sources/StreamChatSwiftUI/ViewFactory.swift | 3 ++ 4 files changed, 37 insertions(+), 15 deletions(-) diff --git a/Sources/StreamChatSwiftUI/ChatChannel/MessageList/MessageContainerView.swift b/Sources/StreamChatSwiftUI/ChatChannel/MessageList/MessageContainerView.swift index ea39a48d..56d68807 100644 --- a/Sources/StreamChatSwiftUI/ChatChannel/MessageList/MessageContainerView.swift +++ b/Sources/StreamChatSwiftUI/ChatChannel/MessageList/MessageContainerView.swift @@ -175,12 +175,12 @@ public struct MessageContainerView: View { .accessibility(identifier: "MessageRepliesView") } else if message.showReplyInChannel, let parentId = message.parentMessageId, - let controller = utils.channelControllerFactory.currentChannelController, - let parentMessage = controller.dataStore.message(id: parentId) { + let controller = utils.channelControllerFactory.currentChannelController { + let parentMessage = controller.dataStore.message(id: parentId) ?? message factory.makeMessageRepliesShownInChannelView( channel: channel, message: message, - parentMessage: parentMessage, + parentMessage: controller.dataStore.message(id: parentId) ?? message, replyCount: parentMessage.replyCount ) .accessibilityElement(children: .contain) diff --git a/Sources/StreamChatSwiftUI/ChatChannel/MessageList/MessageRepliesView.swift b/Sources/StreamChatSwiftUI/ChatChannel/MessageList/MessageRepliesView.swift index 8aec642c..c9f4469c 100644 --- a/Sources/StreamChatSwiftUI/ChatChannel/MessageList/MessageRepliesView.swift +++ b/Sources/StreamChatSwiftUI/ChatChannel/MessageList/MessageRepliesView.swift @@ -12,13 +12,14 @@ enum MessageRepliesConstants { /// View shown below a message, when there are replies to it. public struct MessageRepliesView: View { - + @Injected(\.chatClient) private var chatClient @Injected(\.fonts) private var fonts @Injected(\.colors) private var colors var factory: Factory var channel: ChatChannel - var message: ChatMessage + var message: ChatMessage? + var parentMessageId: MessageId? var replyCount: Int var isRightAligned: Bool var showReplyCount: Bool @@ -26,8 +27,9 @@ public struct MessageRepliesView: View { public init( factory: Factory, channel: ChatChannel, - message: ChatMessage, + message: ChatMessage?, replyCount: Int, + parentMessageId: MessageId? = nil, showReplyCount: Bool = true, isRightAligned: Bool? = nil ) { @@ -35,7 +37,8 @@ public struct MessageRepliesView: View { self.channel = channel self.message = message self.replyCount = replyCount - self.isRightAligned = isRightAligned ?? message.isRightAligned + self.parentMessageId = parentMessageId + self.isRightAligned = isRightAligned ?? message?.isRightAligned ?? false self.showReplyCount = showReplyCount } @@ -45,16 +48,31 @@ public struct MessageRepliesView: View { resignFirstResponder() // NOTE: this is used to avoid breaking changes. // Will be updated in a major release. - NotificationCenter.default.post( - name: NSNotification.Name(MessageRepliesConstants.selectedMessageThread), - object: nil, - userInfo: [MessageRepliesConstants.selectedMessage: message] - ) + if let parentMessageId = self.parentMessageId { + let messageController = chatClient.messageController(cid: channel.cid, messageId: parentMessageId) + messageController.synchronize { error in + if error != nil { + return + } + guard let message = messageController.message else { return } + NotificationCenter.default.post( + name: NSNotification.Name(MessageRepliesConstants.selectedMessageThread), + object: nil, + userInfo: [MessageRepliesConstants.selectedMessage: message] + ) + } + } else if let message = self.message { + NotificationCenter.default.post( + name: NSNotification.Name(MessageRepliesConstants.selectedMessageThread), + object: nil, + userInfo: [MessageRepliesConstants.selectedMessage: message] + ) + } } label: { HStack { if !isRightAligned { MessageAvatarView( - avatarURL: message.threadParticipants.first?.imageURL, + avatarURL: message?.threadParticipants.first?.imageURL, size: .init(width: 16, height: 16) ) } @@ -62,7 +80,7 @@ public struct MessageRepliesView: View { .font(fonts.footnoteBold) if isRightAligned { MessageAvatarView( - avatarURL: message.threadParticipants.first?.imageURL, + avatarURL: message?.threadParticipants.first?.imageURL, size: .init(width: 16, height: 16) ) } @@ -109,7 +127,7 @@ public struct MessageRepliesView: View { } var repliesText: String { - if message.replyCount == 1 { + if let message = self.message, message.replyCount == 1 { return L10n.Message.Threads.reply } else { return L10n.Message.Threads.replies diff --git a/Sources/StreamChatSwiftUI/DefaultViewFactory.swift b/Sources/StreamChatSwiftUI/DefaultViewFactory.swift index bce2b7b6..9b0daca9 100644 --- a/Sources/StreamChatSwiftUI/DefaultViewFactory.swift +++ b/Sources/StreamChatSwiftUI/DefaultViewFactory.swift @@ -519,6 +519,7 @@ extension ViewFactory { channel: channel, message: parentMessage, replyCount: replyCount, + parentMessageId: parentMessage.id == message.id ? message.parentMessageId : nil, showReplyCount: false, isRightAligned: message.isRightAligned ) diff --git a/Sources/StreamChatSwiftUI/ViewFactory.swift b/Sources/StreamChatSwiftUI/ViewFactory.swift index 68418b51..6ddee8c4 100644 --- a/Sources/StreamChatSwiftUI/ViewFactory.swift +++ b/Sources/StreamChatSwiftUI/ViewFactory.swift @@ -537,6 +537,9 @@ public protocol ViewFactory: AnyObject { associatedtype MessageRepliesShownInChannelViewType: View /// Creates the message replies view for a reply that is also shown in a channel. + /// + /// **Important Note:** The `parentMessage` can be equal to the `message` in case the `parentMessage` is `nil`. + /// In order to avoid breaking changes, the `parentMessage` is not optional, although it should. /// - Parameters: /// - channel: the channel where the message is sent. /// - message: the message that's being replied to. From 9ac0aef6099d20d3a122ee07cb22b090a537b99a Mon Sep 17 00:00:00 2001 From: Nuno Vieira Date: Thu, 5 Dec 2024 18:12:45 +0000 Subject: [PATCH 02/10] Revert "First solution: Provide the reply in the parentMessage parameter and do workaround" This reverts commit cfb4b2ed9eeee59c066a54f47577e5c17b52f097. --- .../MessageList/MessageContainerView.swift | 6 +-- .../MessageList/MessageRepliesView.swift | 42 ++++++------------- .../DefaultViewFactory.swift | 1 - Sources/StreamChatSwiftUI/ViewFactory.swift | 3 -- 4 files changed, 15 insertions(+), 37 deletions(-) diff --git a/Sources/StreamChatSwiftUI/ChatChannel/MessageList/MessageContainerView.swift b/Sources/StreamChatSwiftUI/ChatChannel/MessageList/MessageContainerView.swift index 56d68807..ea39a48d 100644 --- a/Sources/StreamChatSwiftUI/ChatChannel/MessageList/MessageContainerView.swift +++ b/Sources/StreamChatSwiftUI/ChatChannel/MessageList/MessageContainerView.swift @@ -175,12 +175,12 @@ public struct MessageContainerView: View { .accessibility(identifier: "MessageRepliesView") } else if message.showReplyInChannel, let parentId = message.parentMessageId, - let controller = utils.channelControllerFactory.currentChannelController { - let parentMessage = controller.dataStore.message(id: parentId) ?? message + let controller = utils.channelControllerFactory.currentChannelController, + let parentMessage = controller.dataStore.message(id: parentId) { factory.makeMessageRepliesShownInChannelView( channel: channel, message: message, - parentMessage: controller.dataStore.message(id: parentId) ?? message, + parentMessage: parentMessage, replyCount: parentMessage.replyCount ) .accessibilityElement(children: .contain) diff --git a/Sources/StreamChatSwiftUI/ChatChannel/MessageList/MessageRepliesView.swift b/Sources/StreamChatSwiftUI/ChatChannel/MessageList/MessageRepliesView.swift index c9f4469c..8aec642c 100644 --- a/Sources/StreamChatSwiftUI/ChatChannel/MessageList/MessageRepliesView.swift +++ b/Sources/StreamChatSwiftUI/ChatChannel/MessageList/MessageRepliesView.swift @@ -12,14 +12,13 @@ enum MessageRepliesConstants { /// View shown below a message, when there are replies to it. public struct MessageRepliesView: View { - @Injected(\.chatClient) private var chatClient + @Injected(\.fonts) private var fonts @Injected(\.colors) private var colors var factory: Factory var channel: ChatChannel - var message: ChatMessage? - var parentMessageId: MessageId? + var message: ChatMessage var replyCount: Int var isRightAligned: Bool var showReplyCount: Bool @@ -27,9 +26,8 @@ public struct MessageRepliesView: View { public init( factory: Factory, channel: ChatChannel, - message: ChatMessage?, + message: ChatMessage, replyCount: Int, - parentMessageId: MessageId? = nil, showReplyCount: Bool = true, isRightAligned: Bool? = nil ) { @@ -37,8 +35,7 @@ public struct MessageRepliesView: View { self.channel = channel self.message = message self.replyCount = replyCount - self.parentMessageId = parentMessageId - self.isRightAligned = isRightAligned ?? message?.isRightAligned ?? false + self.isRightAligned = isRightAligned ?? message.isRightAligned self.showReplyCount = showReplyCount } @@ -48,31 +45,16 @@ public struct MessageRepliesView: View { resignFirstResponder() // NOTE: this is used to avoid breaking changes. // Will be updated in a major release. - if let parentMessageId = self.parentMessageId { - let messageController = chatClient.messageController(cid: channel.cid, messageId: parentMessageId) - messageController.synchronize { error in - if error != nil { - return - } - guard let message = messageController.message else { return } - NotificationCenter.default.post( - name: NSNotification.Name(MessageRepliesConstants.selectedMessageThread), - object: nil, - userInfo: [MessageRepliesConstants.selectedMessage: message] - ) - } - } else if let message = self.message { - NotificationCenter.default.post( - name: NSNotification.Name(MessageRepliesConstants.selectedMessageThread), - object: nil, - userInfo: [MessageRepliesConstants.selectedMessage: message] - ) - } + NotificationCenter.default.post( + name: NSNotification.Name(MessageRepliesConstants.selectedMessageThread), + object: nil, + userInfo: [MessageRepliesConstants.selectedMessage: message] + ) } label: { HStack { if !isRightAligned { MessageAvatarView( - avatarURL: message?.threadParticipants.first?.imageURL, + avatarURL: message.threadParticipants.first?.imageURL, size: .init(width: 16, height: 16) ) } @@ -80,7 +62,7 @@ public struct MessageRepliesView: View { .font(fonts.footnoteBold) if isRightAligned { MessageAvatarView( - avatarURL: message?.threadParticipants.first?.imageURL, + avatarURL: message.threadParticipants.first?.imageURL, size: .init(width: 16, height: 16) ) } @@ -127,7 +109,7 @@ public struct MessageRepliesView: View { } var repliesText: String { - if let message = self.message, message.replyCount == 1 { + if message.replyCount == 1 { return L10n.Message.Threads.reply } else { return L10n.Message.Threads.replies diff --git a/Sources/StreamChatSwiftUI/DefaultViewFactory.swift b/Sources/StreamChatSwiftUI/DefaultViewFactory.swift index 9b0daca9..bce2b7b6 100644 --- a/Sources/StreamChatSwiftUI/DefaultViewFactory.swift +++ b/Sources/StreamChatSwiftUI/DefaultViewFactory.swift @@ -519,7 +519,6 @@ extension ViewFactory { channel: channel, message: parentMessage, replyCount: replyCount, - parentMessageId: parentMessage.id == message.id ? message.parentMessageId : nil, showReplyCount: false, isRightAligned: message.isRightAligned ) diff --git a/Sources/StreamChatSwiftUI/ViewFactory.swift b/Sources/StreamChatSwiftUI/ViewFactory.swift index 6ddee8c4..68418b51 100644 --- a/Sources/StreamChatSwiftUI/ViewFactory.swift +++ b/Sources/StreamChatSwiftUI/ViewFactory.swift @@ -537,9 +537,6 @@ public protocol ViewFactory: AnyObject { associatedtype MessageRepliesShownInChannelViewType: View /// Creates the message replies view for a reply that is also shown in a channel. - /// - /// **Important Note:** The `parentMessage` can be equal to the `message` in case the `parentMessage` is `nil`. - /// In order to avoid breaking changes, the `parentMessage` is not optional, although it should. /// - Parameters: /// - channel: the channel where the message is sent. /// - message: the message that's being replied to. From ec13e93923b4fff6f98f0df51dc3da0fa806d917 Mon Sep 17 00:00:00 2001 From: Nuno Vieira Date: Thu, 5 Dec 2024 18:34:36 +0000 Subject: [PATCH 03/10] Final solution: Create a Lazy view that waits for the parent message to be fetched This solution requires the least amount of changes and there is also no breaking change --- .../MessageList/MessageContainerView.swift | 14 ++++++ .../MessageList/MessageRepliesView.swift | 43 +++++++++++++++++++ 2 files changed, 57 insertions(+) diff --git a/Sources/StreamChatSwiftUI/ChatChannel/MessageList/MessageContainerView.swift b/Sources/StreamChatSwiftUI/ChatChannel/MessageList/MessageContainerView.swift index ea39a48d..594fb3bf 100644 --- a/Sources/StreamChatSwiftUI/ChatChannel/MessageList/MessageContainerView.swift +++ b/Sources/StreamChatSwiftUI/ChatChannel/MessageList/MessageContainerView.swift @@ -185,6 +185,20 @@ public struct MessageContainerView: View { ) .accessibilityElement(children: .contain) .accessibility(identifier: "MessageRepliesView") + } else if message.showReplyInChannel, let parentId = message.parentMessageId { + /// In case the parent message is not available in the local cache, we need to fetch it from the remote server. + /// The lazy view uses the `factory.makeMessageRepliesShownInChannelView` internally once the parent message is fetched. + LazyMessageRepliesView( + factory: factory, + channel: channel, + message: message, + parentMessageController: chatClient.messageController( + cid: channel.cid, + messageId: parentId + ) + ) + .accessibilityElement(children: .contain) + .accessibility(identifier: "MessageRepliesView") } } diff --git a/Sources/StreamChatSwiftUI/ChatChannel/MessageList/MessageRepliesView.swift b/Sources/StreamChatSwiftUI/ChatChannel/MessageList/MessageRepliesView.swift index 8aec642c..7031dff6 100644 --- a/Sources/StreamChatSwiftUI/ChatChannel/MessageList/MessageRepliesView.swift +++ b/Sources/StreamChatSwiftUI/ChatChannel/MessageList/MessageRepliesView.swift @@ -116,3 +116,46 @@ public struct MessageRepliesView: View { } } } + +/// Lazy view that uses the message controller to fetch the parent message before creating message replies view. +/// This is need when the parent message is not available in the local cache. +/// Changing the `parentMessage` to `nil` in the `MessageRepliesView` would case multiple changes including breaking changes. +struct LazyMessageRepliesView: View { + + @Injected(\.chatClient) private var chatClient + @Injected(\.fonts) private var fonts + @Injected(\.colors) private var colors + + @ObservedObject private var parentMessageObserver: ChatMessageController.ObservableObject + var factory: Factory + var channel: ChatChannel + var message: ChatMessage + + init( + factory: Factory, + channel: ChatChannel, + message: ChatMessage, + parentMessageController: ChatMessageController + ) { + parentMessageObserver = parentMessageController.observableObject + self.factory = factory + self.channel = channel + self.message = message + parentMessageObserver.controller.synchronize() + } + + var body: some View { + Group { + if let parentMessage = parentMessageObserver.message { + factory.makeMessageRepliesShownInChannelView( + channel: channel, + message: message, + parentMessage: parentMessage, + replyCount: parentMessage.replyCount + ) + } else { + EmptyView() + } + } + } +} From 4ddf3a4a1da1f92406e0e95522f2075476b3f84e Mon Sep 17 00:00:00 2001 From: Nuno Vieira Date: Thu, 5 Dec 2024 19:11:31 +0000 Subject: [PATCH 04/10] Update CHANGELOG.md --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 83acba62..8cf27ff8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). # Upcoming +### 🐞 Fixed +- Fix message thread reply footnote view not shown if parent message not in cache [#681](https://github.com/GetStream/stream-chat-swiftui/pull/681) ### ⚡ Performance - Improve message search performance [#680](https://github.com/GetStream/stream-chat-swiftui/pull/680) From 6c96cc1fd9c86bbd76c03446691b5b2c5ca0f005 Mon Sep 17 00:00:00 2001 From: Nuno Vieira Date: Thu, 5 Dec 2024 19:12:32 +0000 Subject: [PATCH 05/10] Remove unused properties --- .../ChatChannel/MessageList/MessageRepliesView.swift | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/Sources/StreamChatSwiftUI/ChatChannel/MessageList/MessageRepliesView.swift b/Sources/StreamChatSwiftUI/ChatChannel/MessageList/MessageRepliesView.swift index 7031dff6..7df6262f 100644 --- a/Sources/StreamChatSwiftUI/ChatChannel/MessageList/MessageRepliesView.swift +++ b/Sources/StreamChatSwiftUI/ChatChannel/MessageList/MessageRepliesView.swift @@ -121,12 +121,8 @@ public struct MessageRepliesView: View { /// This is need when the parent message is not available in the local cache. /// Changing the `parentMessage` to `nil` in the `MessageRepliesView` would case multiple changes including breaking changes. struct LazyMessageRepliesView: View { - - @Injected(\.chatClient) private var chatClient - @Injected(\.fonts) private var fonts - @Injected(\.colors) private var colors - @ObservedObject private var parentMessageObserver: ChatMessageController.ObservableObject + var factory: Factory var channel: ChatChannel var message: ChatMessage From 51543d2bcf6bb30ddffc02dbacc423ca8b45fde5 Mon Sep 17 00:00:00 2001 From: Nuno Vieira Date: Fri, 6 Dec 2024 12:05:37 +0000 Subject: [PATCH 06/10] PR feedback --- .../ChatChannel/MessageList/MessageRepliesView.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Sources/StreamChatSwiftUI/ChatChannel/MessageList/MessageRepliesView.swift b/Sources/StreamChatSwiftUI/ChatChannel/MessageList/MessageRepliesView.swift index 7df6262f..ec20e717 100644 --- a/Sources/StreamChatSwiftUI/ChatChannel/MessageList/MessageRepliesView.swift +++ b/Sources/StreamChatSwiftUI/ChatChannel/MessageList/MessageRepliesView.swift @@ -118,10 +118,10 @@ public struct MessageRepliesView: View { } /// Lazy view that uses the message controller to fetch the parent message before creating message replies view. -/// This is need when the parent message is not available in the local cache. +/// This is needed when the parent message is not available in the local cache. /// Changing the `parentMessage` to `nil` in the `MessageRepliesView` would case multiple changes including breaking changes. struct LazyMessageRepliesView: View { - @ObservedObject private var parentMessageObserver: ChatMessageController.ObservableObject + @State private var parentMessageObserver: ChatMessageController.ObservableObject var factory: Factory var channel: ChatChannel From 3f11dfa70a2d80bb375b6dc125176d5b72b9ea35 Mon Sep 17 00:00:00 2001 From: Nuno Vieira Date: Fri, 6 Dec 2024 13:32:42 +0000 Subject: [PATCH 07/10] Fix Xcode 15 From a9247a8f747ef065b7fe1abfc9fec2aeb5878d75 Mon Sep 17 00:00:00 2001 From: Nuno Vieira Date: Fri, 6 Dec 2024 14:35:04 +0000 Subject: [PATCH 08/10] Fix Xcode 15 --- .../ChatChannel/MessageList/MessageRepliesView.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Sources/StreamChatSwiftUI/ChatChannel/MessageList/MessageRepliesView.swift b/Sources/StreamChatSwiftUI/ChatChannel/MessageList/MessageRepliesView.swift index ec20e717..263136d5 100644 --- a/Sources/StreamChatSwiftUI/ChatChannel/MessageList/MessageRepliesView.swift +++ b/Sources/StreamChatSwiftUI/ChatChannel/MessageList/MessageRepliesView.swift @@ -133,11 +133,11 @@ struct LazyMessageRepliesView: View { message: ChatMessage, parentMessageController: ChatMessageController ) { - parentMessageObserver = parentMessageController.observableObject + self.parentMessageObserver = parentMessageController.observableObject self.factory = factory self.channel = channel self.message = message - parentMessageObserver.controller.synchronize() + self.parentMessageObserver.controller.synchronize() } var body: some View { From 5e2ba52e89c961c83c27bb73be4a5eb28a0fb841 Mon Sep 17 00:00:00 2001 From: Nuno Vieira Date: Fri, 6 Dec 2024 14:53:02 +0000 Subject: [PATCH 09/10] Use on appear instead --- .../ChatChannel/MessageList/MessageRepliesView.swift | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/Sources/StreamChatSwiftUI/ChatChannel/MessageList/MessageRepliesView.swift b/Sources/StreamChatSwiftUI/ChatChannel/MessageList/MessageRepliesView.swift index 263136d5..d1e2ad6f 100644 --- a/Sources/StreamChatSwiftUI/ChatChannel/MessageList/MessageRepliesView.swift +++ b/Sources/StreamChatSwiftUI/ChatChannel/MessageList/MessageRepliesView.swift @@ -133,15 +133,14 @@ struct LazyMessageRepliesView: View { message: ChatMessage, parentMessageController: ChatMessageController ) { - self.parentMessageObserver = parentMessageController.observableObject + parentMessageObserver = parentMessageController.observableObject self.factory = factory self.channel = channel self.message = message - self.parentMessageObserver.controller.synchronize() } var body: some View { - Group { + VStack { if let parentMessage = parentMessageObserver.message { factory.makeMessageRepliesShownInChannelView( channel: channel, @@ -152,6 +151,10 @@ struct LazyMessageRepliesView: View { } else { EmptyView() } + }.onAppear { + if parentMessageObserver.message == nil { + parentMessageObserver.controller.synchronize() + } } } } From f81cf5d14c948adf40595d960e325a9b49578b8e Mon Sep 17 00:00:00 2001 From: Nuno Vieira Date: Fri, 6 Dec 2024 15:22:19 +0000 Subject: [PATCH 10/10] Please.... --- .../ChatChannel/MessageList/MessageRepliesView.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Sources/StreamChatSwiftUI/ChatChannel/MessageList/MessageRepliesView.swift b/Sources/StreamChatSwiftUI/ChatChannel/MessageList/MessageRepliesView.swift index d1e2ad6f..69dcd7ab 100644 --- a/Sources/StreamChatSwiftUI/ChatChannel/MessageList/MessageRepliesView.swift +++ b/Sources/StreamChatSwiftUI/ChatChannel/MessageList/MessageRepliesView.swift @@ -121,7 +121,7 @@ public struct MessageRepliesView: View { /// This is needed when the parent message is not available in the local cache. /// Changing the `parentMessage` to `nil` in the `MessageRepliesView` would case multiple changes including breaking changes. struct LazyMessageRepliesView: View { - @State private var parentMessageObserver: ChatMessageController.ObservableObject + @StateObject private var parentMessageObserver: ChatMessageController.ObservableObject var factory: Factory var channel: ChatChannel @@ -133,7 +133,7 @@ struct LazyMessageRepliesView: View { message: ChatMessage, parentMessageController: ChatMessageController ) { - parentMessageObserver = parentMessageController.observableObject + _parentMessageObserver = StateObject(wrappedValue: parentMessageController.observableObject) self.factory = factory self.channel = channel self.message = message