Skip to content

Fix error and pending states when editing a message #840

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

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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_
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -141,8 +141,7 @@ public struct MessageReadIndicatorView: View {
}

private var isMessageSending: Bool {
localState == .sending
|| localState == .pendingSend
localState == .sending || localState == .pendingSend || localState == .syncing
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<AnyCancellable>()

Expand Down Expand Up @@ -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 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Comment on lines +21 to +27
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We have this duplicated from UIKit, so I copied the most recent version. The weird thing, is that we were not using this property at all. It most likely could be moved to the LLC in the future.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, looks like an oversight. Would be nice to do so indeed.


switch localState {
case .sendingFailed, .syncingFailed, .deletingFailed:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand All @@ -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))
}
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading