Skip to content

Commit 3f4b97d

Browse files
authored
Add support for editing attachments (#809)
* Fix scrolling to the last message when editing a message * Add support for editing message attachments * Remove edit action from giphy message * Fix being able to long press an unsent giphy message * Fix being able to swipe to reply an unsent giphy * Fix fill composer function causing main thread warning * Update CHANGELOG.md * Fix typo in deprecated message * Fix double tapping on ephemeral giphy opening message actions * Rename `inputAttachmentsAsPayloads()` -> `convertAddedAssetsToPayloads()` * Add test coverage to swipe to reply * Add test coverage to giphy edit action * Extract attachments convert to a component so that it can be tested and reused * Add test coverage to filling composer with edited message * Update CHANGELOG.md * Added back the fillComposer function again
1 parent 122dafb commit 3f4b97d

18 files changed

+642
-96
lines changed

CHANGELOG.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,15 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
33

44
# Upcoming
55

6+
### ✅ Added
7+
- Add support for editing message attachments [#806](https://github.com/GetStream/stream-chat-swiftui/pull/806)
8+
### 🐞 Fixed
9+
- Fix scrolling to the bottom when editing a message [#806](https://github.com/GetStream/stream-chat-swiftui/pull/806)
10+
- Fix having message edit action on Giphy messages [#806](https://github.com/GetStream/stream-chat-swiftui/pull/806)
11+
- Fix being able to long press an unsent Giphy message [#806](https://github.com/GetStream/stream-chat-swiftui/pull/806)
12+
- Fix being able to swipe to reply an unsent Giphy message [#806](https://github.com/GetStream/stream-chat-swiftui/pull/806)
613
### 🔄 Changed
14+
- Deprecated `ComposerConfig.attachmentPayloadConverter` in favour of `MessageComposerViewModel.convertAddedAssetsToPayloads()` [#806](https://github.com/GetStream/stream-chat-swiftui/pull/806)
715

816
# [4.77.0](https://github.com/GetStream/stream-chat-swiftui/releases/tag/4.77.0)
917
_April 10, 2025_

Sources/StreamChatSwiftUI/ChatChannel/ChatChannelView.swift

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,9 @@ public struct ChatChannelView<Factory: ViewFactory>: View, KeyboardReadable {
115115
messageController: viewModel.messageController,
116116
quotedMessage: $viewModel.quotedMessage,
117117
editedMessage: $viewModel.editedMessage,
118-
onMessageSent: viewModel.scrollToLastMessage
118+
onMessageSent: {
119+
viewModel.messageSentTapped()
120+
}
119121
)
120122
.opacity((
121123
utils.messageListConfig.messagePopoverEnabled && messageDisplayInfo != nil && !viewModel

Sources/StreamChatSwiftUI/ChatChannel/ChatChannelViewModel.swift

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -264,7 +264,15 @@ open class ChatChannelViewModel: ObservableObject, MessagesDataSource {
264264
}
265265
}
266266
}
267-
267+
268+
/// The user tapped on the message sent button.
269+
public func messageSentTapped() {
270+
// only scroll if the message is not being edited
271+
if editedMessage == nil {
272+
scrollToLastMessage()
273+
}
274+
}
275+
268276
public func jumpToMessage(messageId: String) -> Bool {
269277
if messageId == .unknownMessageId {
270278
if firstUnreadMessageId == nil, let lastReadMessageId {

Sources/StreamChatSwiftUI/ChatChannel/Composer/ComposerConfig.swift

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,14 @@ public struct ComposerConfig {
1717
public var inputPaddingsConfig: PaddingsConfig
1818
public var adjustMessageOnSend: (String) -> (String)
1919
public var adjustMessageOnRead: (String) -> (String)
20+
21+
@available(
22+
*,
23+
deprecated,
24+
message: """
25+
Override the MessageComposerViewModel.inputAttachmentsAsPayloads() in order to convert the message attachments to payloads.
26+
"""
27+
)
2028
public var attachmentPayloadConverter: (ChatMessage) -> [AnyAttachmentPayload]
2129

2230
public init(
@@ -44,8 +52,9 @@ public struct ComposerConfig {
4452
self.isVoiceRecordingEnabled = isVoiceRecordingEnabled
4553
}
4654

47-
public static var defaultAttachmentPayloadConverter: (ChatMessage) -> [AnyAttachmentPayload] = { message in
48-
message.allAttachments.toAnyAttachmentPayload()
55+
public static var defaultAttachmentPayloadConverter: (ChatMessage) -> [AnyAttachmentPayload] = { _ in
56+
/// This now returns empty array by default since attachmentPayloadConverter has been deprecated.
57+
[]
4958
}
5059
}
5160

Sources/StreamChatSwiftUI/ChatChannel/Composer/ComposerModels.swift

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -27,25 +27,34 @@ public struct AddedAsset: Identifiable, Equatable {
2727
public let url: URL
2828
public let type: AssetType
2929
public var extraData: [String: RawJSON] = [:]
30-
30+
31+
/// The payload of the attachment, in case the attachment has been uploaded to server already.
32+
/// This is mostly used when editing an existing message that contains attachments.
33+
public var payload: AttachmentPayload?
34+
3135
public init(
3236
image: UIImage,
3337
id: String,
3438
url: URL,
3539
type: AssetType,
36-
extraData: [String: RawJSON] = [:]
40+
extraData: [String: RawJSON] = [:],
41+
payload: AttachmentPayload? = nil
3742
) {
3843
self.image = image
3944
self.id = id
4045
self.url = url
4146
self.type = type
4247
self.extraData = extraData
48+
self.payload = payload
4349
}
4450
}
4551

4652
extension AddedAsset {
4753
func toAttachmentPayload() throws -> AnyAttachmentPayload {
48-
try AnyAttachmentPayload(
54+
if let payload = self.payload {
55+
return AnyAttachmentPayload(payload: payload)
56+
}
57+
return try AnyAttachmentPayload(
4958
localFileURL: url,
5059
attachmentType: type == .video ? .video : .image,
5160
extraData: extraData
@@ -63,7 +72,8 @@ extension AnyChatMessageAttachment {
6372
id: imageAttachment.id.rawValue,
6473
url: imageAttachment.imageURL,
6574
type: .image,
66-
extraData: imageAttachment.extraData ?? [:]
75+
extraData: imageAttachment.extraData ?? [:],
76+
payload: imageAttachment.payload
6777
)
6878
} else if let videoAttachment = attachment(payloadType: VideoAttachmentPayload.self),
6979
let thumbnail = imageThumbnail(for: videoAttachment.payload) {
@@ -72,7 +82,8 @@ extension AnyChatMessageAttachment {
7282
id: videoAttachment.id.rawValue,
7383
url: videoAttachment.videoURL,
7484
type: .video,
75-
extraData: videoAttachment.extraData ?? [:]
85+
extraData: videoAttachment.extraData ?? [:],
86+
payload: videoAttachment.payload
7687
)
7788
}
7889
return nil

Sources/StreamChatSwiftUI/ChatChannel/Composer/MessageComposerView.swift

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -102,9 +102,11 @@ public struct MessageComposerView<Factory: ViewFactory>: View, KeyboardReadable
102102
quotedMessage: quotedMessage,
103103
editedMessage: editedMessage
104104
) {
105+
// Calling onMessageSent() before erasing the edited and quoted message
106+
// so that onMessageSent can use them for state handling.
107+
onMessageSent()
105108
quotedMessage = nil
106109
editedMessage = nil
107-
onMessageSent()
108110
}
109111
}
110112
.environmentObject(viewModel)
@@ -208,11 +210,10 @@ public struct MessageComposerView<Factory: ViewFactory>: View, KeyboardReadable
208210
)
209211
.modifier(factory.makeComposerViewModifier())
210212
.onChange(of: editedMessage) { _ in
211-
viewModel.text = editedMessage?.text ?? ""
213+
viewModel.fillEditedMessage(editedMessage)
212214
if editedMessage != nil {
213215
becomeFirstResponder()
214216
editedMessageWillShow = true
215-
viewModel.selectedRangeLocation = editedMessage?.text.count ?? 0
216217
}
217218
}
218219
.onAppear(perform: {

0 commit comments

Comments
 (0)