diff --git a/CHANGELOG.md b/CHANGELOG.md index 8a8183de9..30df0aaf4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - Fix mark unread action not removed when read events are disabled [#823](https://github.com/GetStream/stream-chat-swiftui/pull/823) - Fix user mentions not working when commands are disabled [#826](https://github.com/GetStream/stream-chat-swiftui/pull/826) - Fix edit message action shown when user does not have permissions [#835](https://github.com/GetStream/stream-chat-swiftui/pull/835) +- Fix error indicator not shown when editing a message fails [#840](https://github.com/GetStream/stream-chat-swiftui/pull/840) +- Fix read indicator shown for failed edited messages [#840](https://github.com/GetStream/stream-chat-swiftui/pull/840) +- Fix "clock" pending icon not shown when message is syncing (pending to be edited) [#840](https://github.com/GetStream/stream-chat-swiftui/pull/840) # [4.78.0](https://github.com/GetStream/stream-chat-swiftui/releases/tag/4.78.0) _April 24, 2025_ diff --git a/Sources/StreamChatSwiftUI/ChatChannel/MessageList/MessageListHelperViews.swift b/Sources/StreamChatSwiftUI/ChatChannel/MessageList/MessageListHelperViews.swift index 03f6a8d99..9c978e2e4 100644 --- a/Sources/StreamChatSwiftUI/ChatChannel/MessageList/MessageListHelperViews.swift +++ b/Sources/StreamChatSwiftUI/ChatChannel/MessageList/MessageListHelperViews.swift @@ -124,7 +124,7 @@ public struct MessageReadIndicatorView: View { .customizable() .foregroundColor(!readUsers.isEmpty ? colors.tintColor : Color(colors.textLowEmphasis)) .frame(height: 16) - .opacity(localState == .sendingFailed ? 0.0 : 1) + .opacity(localState == .sendingFailed || localState == .syncingFailed ? 0.0 : 1) .accessibilityLabel( Text( readUsers.isEmpty ? L10n.Message.ReadStatus.seenByNoOne : L10n.Message.ReadStatus.seenByOthers @@ -141,8 +141,7 @@ public struct MessageReadIndicatorView: View { } private var isMessageSending: Bool { - localState == .sending - || localState == .pendingSend + localState == .sending || localState == .pendingSend || localState == .syncing } } diff --git a/Sources/StreamChatSwiftUI/ChatChannel/MessageList/MessageView.swift b/Sources/StreamChatSwiftUI/ChatChannel/MessageList/MessageView.swift index d99591ec0..30dbad471 100644 --- a/Sources/StreamChatSwiftUI/ChatChannel/MessageList/MessageView.swift +++ b/Sources/StreamChatSwiftUI/ChatChannel/MessageList/MessageView.swift @@ -259,16 +259,13 @@ public struct LinkDetectionTextView: View { @Injected(\.utils) var utils var message: ChatMessage - - var text: LocalizedStringKey { - LocalizedStringKey(message.adjustedText) - } // The translations store is used to detect changes so the textContent is re-rendered. // The @Environment(\.messageViewModel) is not reactive like @EnvironmentObject. // TODO: On v5 the TextView should be refactored and not depend directly on the view model. @ObservedObject var originalTranslationsStore = InjectedValues[\.utils].originalTranslationsStore + @State var text: AttributedString? @State var linkDetector = TextLinkDetector() @State var tintColor = InjectedValues[\.colors].tintColor @@ -280,13 +277,14 @@ public struct LinkDetectionTextView: View { public var body: some View { Group { - Text(displayText) + Text(text ?? displayText) } .foregroundColor(textColor(for: message)) .font(fonts.body) .tint(tintColor) .onChange(of: message) { message in messageViewModel?.message = message + text = displayText } } diff --git a/Sources/StreamChatSwiftUI/ChatChannel/MessageList/MessageViewModel.swift b/Sources/StreamChatSwiftUI/ChatChannel/MessageList/MessageViewModel.swift index 103b3740e..b02d42e6a 100644 --- a/Sources/StreamChatSwiftUI/ChatChannel/MessageList/MessageViewModel.swift +++ b/Sources/StreamChatSwiftUI/ChatChannel/MessageList/MessageViewModel.swift @@ -10,7 +10,7 @@ open class MessageViewModel: ObservableObject { @Injected(\.utils) private var utils @Injected(\.chatClient) private var chatClient - public internal(set) var message: ChatMessage + @Published public internal(set) var message: ChatMessage public private(set) var channel: ChatChannel? private var cancellables = Set() @@ -57,7 +57,7 @@ open class MessageViewModel: ObservableObject { } public var failureIndicatorShown: Bool { - ((message.localState == .sendingFailed || message.isBounced) && !message.text.isEmpty) + message.isLastActionFailed && !message.text.isEmpty } open var authorAndDateShown: Bool { diff --git a/Sources/StreamChatSwiftUI/Utils/Common/ChatMessage+Extensions.swift b/Sources/StreamChatSwiftUI/Utils/Common/ChatMessage+Extensions.swift index a44103587..41362caed 100644 --- a/Sources/StreamChatSwiftUI/Utils/Common/ChatMessage+Extensions.swift +++ b/Sources/StreamChatSwiftUI/Utils/Common/ChatMessage+Extensions.swift @@ -18,7 +18,13 @@ public extension ChatMessage { /// A boolean value that checks if the last action (`send`, `edit` or `delete`) on the message failed. var isLastActionFailed: Bool { - guard isDeleted == false else { return false } + guard isDeleted == false else { + return false + } + + if isBounced { + return true + } switch localState { case .sendingFailed, .syncingFailed, .deletingFailed: diff --git a/StreamChatSwiftUITests/Tests/ChatChannel/MessageContainerView_Tests.swift b/StreamChatSwiftUITests/Tests/ChatChannel/MessageContainerView_Tests.swift index 560d91db9..2afea3e2a 100644 --- a/StreamChatSwiftUITests/Tests/ChatChannel/MessageContainerView_Tests.swift +++ b/StreamChatSwiftUITests/Tests/ChatChannel/MessageContainerView_Tests.swift @@ -167,6 +167,42 @@ class MessageContainerView_Tests: StreamChatTestCase { assertSnapshot(matching: view, as: .image(perceptualPrecision: precision)) } + func test_messageContainerView_sendingFailed_snapshot() { + // Given + let message = ChatMessage.mock( + id: .unique, + cid: .unique, + text: "Test message", + author: .mock(id: .unique), + attachments: [], + localState: .sendingFailed + ) + + // When + let view = testMessageViewContainer(message: message, channel: .mockNonDMChannel()) + + // Then + assertSnapshot(matching: view, as: .image(perceptualPrecision: precision)) + } + + func test_messageContainerView_editingFailed_snapshot() { + // Given + let message = ChatMessage.mock( + id: .unique, + cid: .unique, + text: "Test message", + author: .mock(id: .unique), + attachments: [], + localState: .syncingFailed + ) + + // When + let view = testMessageViewContainer(message: message, channel: .mockNonDMChannel()) + + // Then + assertSnapshot(matching: view, as: .image(perceptualPrecision: precision)) + } + func test_videoAttachment_snapshotNoText() { // Given let attachment = ChatChannelTestHelpers.videoAttachment diff --git a/StreamChatSwiftUITests/Tests/ChatChannel/MessageReadIndicatorView_Tests.swift b/StreamChatSwiftUITests/Tests/ChatChannel/MessageReadIndicatorView_Tests.swift index 9d709e433..3f83ef912 100644 --- a/StreamChatSwiftUITests/Tests/ChatChannel/MessageReadIndicatorView_Tests.swift +++ b/StreamChatSwiftUITests/Tests/ChatChannel/MessageReadIndicatorView_Tests.swift @@ -72,6 +72,19 @@ class MessageReadIndicatorView_Tests: StreamChatTestCase { assertSnapshot(matching: view, as: .image(perceptualPrecision: precision)) } + func test_messageReadIndicatorView_snapshotSyncing() { + // Given + let view = MessageReadIndicatorView( + readUsers: [], + showReadCount: false, + localState: .syncing + ) + .frame(width: 50, height: 16) + + // Then + assertSnapshot(matching: view, as: .image(perceptualPrecision: precision)) + } + func test_messageReadIndicatorView_snapshotMessageFailed() { // Given let view = MessageReadIndicatorView( @@ -84,4 +97,17 @@ class MessageReadIndicatorView_Tests: StreamChatTestCase { // Then assertSnapshot(matching: view, as: .image(perceptualPrecision: precision)) } + + func test_messageReadIndicatorView_snapshotMessageEditingFailed() { + // Given + let view = MessageReadIndicatorView( + readUsers: [], + showReadCount: false, + localState: .syncingFailed + ) + .frame(width: 50, height: 16) + + // Then + assertSnapshot(matching: view, as: .image(perceptualPrecision: precision)) + } } diff --git a/StreamChatSwiftUITests/Tests/ChatChannel/__Snapshots__/MessageContainerView_Tests/test_messageContainerView_editingFailed_snapshot.1.png b/StreamChatSwiftUITests/Tests/ChatChannel/__Snapshots__/MessageContainerView_Tests/test_messageContainerView_editingFailed_snapshot.1.png new file mode 100644 index 000000000..0ed7c0c69 Binary files /dev/null and b/StreamChatSwiftUITests/Tests/ChatChannel/__Snapshots__/MessageContainerView_Tests/test_messageContainerView_editingFailed_snapshot.1.png differ diff --git a/StreamChatSwiftUITests/Tests/ChatChannel/__Snapshots__/MessageContainerView_Tests/test_messageContainerView_sendingFailed_snapshot.1.png b/StreamChatSwiftUITests/Tests/ChatChannel/__Snapshots__/MessageContainerView_Tests/test_messageContainerView_sendingFailed_snapshot.1.png new file mode 100644 index 000000000..0ed7c0c69 Binary files /dev/null and b/StreamChatSwiftUITests/Tests/ChatChannel/__Snapshots__/MessageContainerView_Tests/test_messageContainerView_sendingFailed_snapshot.1.png differ diff --git a/StreamChatSwiftUITests/Tests/ChatChannel/__Snapshots__/MessageReadIndicatorView_Tests/test_messageReadIndicatorView_snapshotMessageEditingFailed.1.png b/StreamChatSwiftUITests/Tests/ChatChannel/__Snapshots__/MessageReadIndicatorView_Tests/test_messageReadIndicatorView_snapshotMessageEditingFailed.1.png new file mode 100644 index 000000000..f210a1ecd Binary files /dev/null and b/StreamChatSwiftUITests/Tests/ChatChannel/__Snapshots__/MessageReadIndicatorView_Tests/test_messageReadIndicatorView_snapshotMessageEditingFailed.1.png differ diff --git a/StreamChatSwiftUITests/Tests/ChatChannel/__Snapshots__/MessageReadIndicatorView_Tests/test_messageReadIndicatorView_snapshotSyncing.1.png b/StreamChatSwiftUITests/Tests/ChatChannel/__Snapshots__/MessageReadIndicatorView_Tests/test_messageReadIndicatorView_snapshotSyncing.1.png new file mode 100644 index 000000000..567c11648 Binary files /dev/null and b/StreamChatSwiftUITests/Tests/ChatChannel/__Snapshots__/MessageReadIndicatorView_Tests/test_messageReadIndicatorView_snapshotSyncing.1.png differ