8000 Add Draft Messages Support (#775) · GetStream/stream-chat-swiftui@960f831 · GitHub
[go: up one dir, main page]

Skip to content

Commit 960f831

Browse files
Add Draft Messages Support (#775)
* WIP w/ channel view model * WIP without channel view model env object * Fill the draft attachments to the composer * Fix commands not showing in draft message * Add draft previews to channel list item and thread list item * Delete draft message when publishing message or erasing input * Add draftMessagesEnabled flag * Update composer draft message when draft is updated from event * Fix forgotten env object from channel view model * Remove unnecessary deleteDraftMessage call since publishing a message will clear the text, which will delete the draft * Simplify the composer view model draft update event logic * Add Message Composer View Model Tests * Add composer view test coverage * Improve the composer view model code by having a fillDraftMessage and a computed draftMessage property * Add test coverage to previews * Update CHANGELOG.md * Fix snapshot tests * Fix broken compilation caused by new formatter * Update CHANGELOG.md * Clear composer input data if draft was deleted from other device * Fix delete draft request being fired multiple times * Fix not removing attachments from the previous draft * Only delete draft message if content is all empty --------- Co-authored-by: Alexey Alter-Pesotskiy <alex@testableapple.com>
1 parent 076f005 commit 960f831

30 files changed

+905
-50
lines changed

CHANGELOG.md

Expand all lines: CHANGELOG.md
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
55

66
### ✅ Added
77
- Add avatar customization in add users popup [#787](https://github.com/GetStream/stream-chat-swiftui/pull/787)
8+
- Add support for Draft Messages when `Utils.messageListConfig.draftMessagesEnabled` is `true` [#775](https://github.com/GetStream/stream-chat-swiftui/pull/775)
9+
- Add draft preview in Channel List and Thread List if drafts are enabled [#775](https://github.com/GetStream/stream-chat-swiftui/pull/775)
810

911
# [4.74.0](https://github.com/GetStream/stream-chat-swiftui/releases/tag/4.74.0)
1012
_March 14, 2025_

DemoAppSwiftUI/AppDelegate.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,8 @@ class AppDelegate: NSObject, UIApplicationDelegate {
6969
bouncedMessagesAlertActionsEnabled: true,
7070
skipEditedMessageLabel: { message in
7171
message.extraData["ai_generated"]?.boolValue == true
72-
}
72+
},
73+
draftMessagesEnabled: true
7374
),
7475
composerConfig: ComposerConfig(isVoiceRecordingEnabled: true)
7576
)

DemoAppSwiftUI/AppleMessageComposerView.swift

Lines changed: 2 additions & 1 deletion
9E12
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,8 @@ struct AppleMessageComposerView<Factory: ViewFactory>: View, KeyboardReadable {
4242
channelConfig = channelController.channel?.config
4343
let vm = viewModel ?? ViewModelsFactory.makeMessageComposerViewModel(
4444
with: channelController,
45-
messageController: messageController
45+
messageController: messageController,
46+
quotedMessage: quotedMessage
4647
)
4748
_viewModel = StateObject(
4849
wrappedValue: vm

DemoAppSwiftUI/PinChannelHelpers.swift

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,16 @@ struct DemoAppChatChannelListItem: View {
8585
TypingIndicatorView()
8686
}
8787
}
88-
SubtitleText(text: injectedChannelInfo?.subtitle ?? channel.subtitleText)
88+
if let draftText = channel.draftMessageText {
89+
HStack(spacing: 2) {
90+
Text("Draft:")
91+
.font(fonts.caption1).bold()
92+
.foregroundColor(Color(colors.highlightedAccentBackground))
93+
SubtitleText(text: draftText)
94+
}
95+
} else {
96+
SubtitleText(text: injectedChannelInfo?.subtitle ?? channel.subtitleText)
97+
}
8998
Spacer()
9099
}
91100
.accessibilityIdentifier("subtitleView")

Sources/StreamChatSwiftUI/ChatChannel/Composer/ComposerModels.swift

Lines changed: 56 additions & 0 deletions
F438
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// Copyright © 2025 Stream.io Inc. All rights reserved.
33
//
44

5+
import AVFoundation
56
import StreamChat
67
import SwiftUI
78

@@ -52,6 +53,48 @@ extension AddedAsset {
5253
}
5354
}
5455

56+
extension AnyChatMessageAttachment {
57+
func toAddedAsset() -> AddedAsset? {
58+
if let imageAttachment = attachment(payloadType: ImageAttachmentPayload.self),
59+
let imageData = try? Data(contentsOf: imageAttachment.imageURL),
60+
let image = UIImage(data: imageData) {
61+
return AddedAsset(
62+
image: image,
63+
id: imageAttachment.id.rawValue,
64+
url: imageAttachment.imageURL,
65+
type: .image,
66+
extraData: imageAttachment.extraData ?? [:]
67+
)
68+
} else if let videoAttachment = attachment(payloadType: VideoAttachmentPayload.self),
69+
let thumbnail = imageThumbnail(for: videoAttachment.payload) {
70+
return AddedAsset(
71+
image: thumbnail,
72+
id: videoAttachment.id.rawValue,
73+
url: videoAttachment.videoURL,
74+
type: .video,
75+
extraData: videoAttachment.extraData ?? [:]
76+
)
77+
}
78+
return nil
79+
}
80+
81+
private func imageThumbnail(for videoAttachmentPayload: VideoAttachmentPayload) -> UIImage? {
82+
if let thumbnailURL = videoAttachmentPayload.thumbnailURL, let data = try? Data(contentsOf: thumbnailURL) {
83+
return UIImage(data: data)
84+
}
85+
let asset = AVURLAsset(url: videoAttachmentPayload.videoURL, options: nil)
86+
let imageGenerator = AVAssetImageGenerator(asset: asset)
87+
imageGenerator.appliesPreferredTrackTransform = true
88+
guard let cgImage = try? imageGenerator.copyCGImage(
89+
at: CMTimeMake(value: 0, timescale: 1),
90+
actualTime: nil
91+
) else {
92+
return nil
93+
}
94+
return UIImage(cgImage: cgImage)
95+
}
96+
}
97+
5598
/// Type of asset added to the composer.
5699
public enum AssetType {
57100
case image
@@ -92,3 +135,16 @@ public struct AddedVoiceRecording: Identifiable, Equatable {
92135
self.waveform = waveform
93136
}
94137
}
138+
139+
extension AnyChatMessageAttachment {
140+
func toAddedVoiceRecording() -> AddedVoiceRecording? {
141+
guard let voiceAttachment = attachment(payloadType: VoiceRecordingAttachmentPayload.self) else { return nil }
142+
guard let duration = voiceAttachment.duration else { return nil }
143+
guard let waveform = voiceAttachment.waveformData else { return nil }
144+
return AddedVoiceRecording(
145+
url: voiceAttachment.voiceRecordingURL,
146+
duration: duration,
147+
waveform: waveform
148+
)
149+
}
150+
}

Sources/StreamChatSwiftUI/ChatChannel/Composer/MessageComposerView.swift

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ public struct MessageComposerView<Factory: ViewFactory>: View, KeyboardReadable
2020
private var channelConfig: ChannelConfig?
2121
@Binding var quotedMessage: ChatMessage?
2222
@Binding var editedMessage: ChatMessage?
23-
23+
2424
private let recordingViewHeight: CGFloat = 80
2525

2626
public init(
@@ -37,7 +37,8 @@ public struct MessageComposerView<Factory: ViewFactory>: View, KeyboardReadable
3737
_viewModel = StateObject(
3838
wrappedValue: viewModel ?? ViewModelsFactory.makeMessageComposerViewModel(
3939
with: channelController,
40-
messageController: messageController
40+
messageController: messageController,
41+
quotedMessage: quotedMessage
4142
)
4243
)
4344
_quotedMessage = quotedMessage
@@ -214,6 +215,12 @@ public struct MessageComposerView<Factory: ViewFactory>: View, KeyboardReadable
214215
viewModel.selectedRangeLocation = editedMessage?.text.count ?? 0
215216
}
216217
}
218+
.onAppear(perform: {
219+
viewModel.fillDraftMessage()
220+
})
221+
.onDisappear(perform: {
222+
viewModel.updateDraftMessage(quotedMessage: quotedMessage)
223+
})
217224
.accessibilityElement(children: .contain)
218225
}
219226
}

0 commit comments

Comments
 (0)
0