Skip to content

Commit 294ab79

Browse files
committed
Fix draft not deleted when attachments are removed from the composer
1 parent c79faad commit 294ab79

File tree

2 files changed

+128
-6
lines changed

2 files changed

+128
-6
lines changed

Sources/StreamChatSwiftUI/ChatChannel/Composer/MessageComposerViewModel.swift

+33-6
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,10 @@ open class MessageComposerViewModel: ObservableObject {
3030
@Published public private(set) var addedAssets = [AddedAsset]() {
3131
didSet {
3232
checkPickerSelectionState()
33+
34+
if shouldDeleteDraftMessage(oldValue: oldValue) {
35+
deleteDraftMessage()
36+
}
3337
}
3438
}
3539

@@ -55,7 +59,7 @@ open class MessageComposerViewModel: ObservableObject {
5559
suggestions = [String: Any]()
5660
mentionedUsers = Set<ChatUser>()
5761

58-
if oldValue != "" && !sendButtonEnabled {
62+
if shouldDeleteDraftMessage(oldValue: oldValue) {
5963
deleteDraftMessage()
6064
}
6165
}
@@ -71,18 +75,30 @@ open class MessageComposerViewModel: ObservableObject {
7175
addedFileURLs.removeLast()
7276
}
7377
checkPickerSelectionState()
78+
79+
if shouldDeleteDraftMessage(oldValue: oldValue) {
80+
deleteDraftMessage()
81+
}
7482
}
7583
}
7684

7785
@Published public var addedVoiceRecordings = [AddedVoiceRecording]() {
7886
didSet {
7987
checkPickerSelectionState()
88+
89+
if shouldDeleteDraftMessage(oldValue: oldValue) {
90+
deleteDraftMessage()
91+
}
8092
}
8193
}
8294

8395
@Published public var addedCustomAttachments = [CustomAttachment]() {
8496
didSet {
8597
checkPickerSelectionState()
98+
99+
if shouldDeleteDraftMessage(oldValue: oldValue) {
100+
deleteDraftMessage()
101+
}
86102
}
87103
}
88104

@@ -251,10 +267,10 @@ open class MessageComposerViewModel: ObservableObject {
251267
quotedMessage?.wrappedValue = message.quotedMessage
252268
showReplyInChannel = message.showReplyInChannel
253269

254-
addedAssets.removeAll()
255-
addedFileURLs.removeAll()
256-
addedVoiceRecordings.removeAll()
257-
addedCustomAttachments.removeAll()
270+
var addedAssets: [AddedAsset] = []
271+
var addedFileURLs: [URL] = []
272+
var addedVoiceRecordings: [AddedVoiceRecording] = []
273+
var addedCustomAttachments: [CustomAttachment] = []
258274

259275
message.attachments.forEach { attachment in
260276
switch attachment.type {
@@ -277,6 +293,11 @@ open class MessageComposerViewModel: ObservableObject {
277293
addedCustomAttachments.append(customAttachment)
278294
}
279295
}
296+
297+
self.addedAssets = addedAssets
298+
self.addedFileURLs = addedFileURLs
299+
self.addedVoiceRecordings = addedVoiceRecordings
300+
self.addedCustomAttachments = addedCustomAttachments
280301
}
281302

282303
/// Updates the draft message locally and on the server.
@@ -318,7 +339,8 @@ open class MessageComposerViewModel: ObservableObject {
318339
)
319340
}
320341

321-
private func deleteDraftMessage() {
342+
/// Deletes the draft message locally and on the server if it exists.
343+
public func deleteDraftMessage() {
322344
guard draftMessage != nil else {
323345
return
324346
}
@@ -330,6 +352,11 @@ open class MessageComposerViewModel: ObservableObject {
330352
}
331353
}
332354

355+
/// Checks if the previous value of the content in the composer was not empty and the current value is empty.
356+
private func shouldDeleteDraftMessage(oldValue: any Collection) -> Bool {
357+
!oldValue.isEmpty && !sendButtonEnabled
358+
}
359+
333360
open func sendMessage(
334361
quotedMessage: ChatMessage?,
335362
editedMessage: ChatMessage?,

StreamChatSwiftUITests/Tests/ChatChannel/MessageComposerViewModel_Tests.swift

+95
Original file line numberDiff line numberDiff line change
@@ -984,6 +984,101 @@ class MessageComposerViewModel_Tests: StreamChatTestCase {
984984
XCTAssertEqual(viewModel.text, "Draft")
985985
}
986986

987+
func test_messageComposerVM_whenLastAssetRemoved_shouldDeleteDraft() {
988+
// Given
989+
let channelController = makeChannelController()
990+
let draftMessage = DraftMessage.mock(text: "")
991+
channelController.channel_mock = .mock(cid: .unique, draftMessage: draftMessage)
992+
let viewModel = makeComposerDraftsViewModel(
993+
channelController: channelController,
994+
messageController: nil
995+
)
996+
let asset = defaultAsset
997+
viewModel.imageTapped(asset)
998+
999+
// When
1000+
viewModel.imageTapped(asset) // Remove the asset by tapping again
1001+
1002+
// Then
1003+
XCTAssertEqual(channelController.deleteDraftMessage_callCount, 1)
1004+
}
1005+
1006+
func test_messageComposerVM_whenLastFileRemoved_shouldDeleteDraft() {
1007+
// Given
1008+
let channelController = makeChannelController()
1009+
let draftMessage = DraftMessage.mock(text: "")
1010+
channelController.channel_mock = .mock(cid: .unique, draftMessage: draftMessage)
1011+
let viewModel = makeComposerDraftsViewModel(
1012+
channelController: channelController,
1013+
messageController: nil
1014+
)
1015+
viewModel.addedFileURLs = [mockURL]
1016+
1017+
// When
1018+
viewModel.removeAttachment(with: mockURL.absoluteString)
1019+
1020+
// Then
1021+
XCTAssertEqual(channelController.deleteDraftMessage_callCount, 1)
1022+
}
1023+
1024+
func test_messageComposerVM_whenLastVoiceRecordingRemoved_shouldDeleteDraft() {
1025+
// Given
1026+
let channelController = makeChannelController()
1027+
let draftMessage = DraftMessage.mock(text: "")
1028+
channelController.channel_mock = .mock(cid: .unique, draftMessage: draftMessage)
1029+
let viewModel = makeComposerDraftsViewModel(
1030+
channelController: channelController,
1031+
messageController: nil
1032+
)
1033+
let recording = AddedVoiceRecording(url: mockURL, duration: 1.0, waveform: [0.5])
1034+
viewModel.addedVoiceRecordings = [recording]
1035+
1036+
// When
1037+
viewModel.removeAttachment(with: mockURL.absoluteString)
1038+
1039+
// Then
1040+
XCTAssertEqual(channelController.deleteDraftMessage_callCount, 1)
1041+
}
1042+
1043+
func test_messageComposerVM_whenLastCustomAttachmentRemoved_shouldDeleteDraft() {
1044+
// Given
1045+
let channelController = makeChannelController()
1046+
let draftMessage = DraftMessage.mock(text: "")
1047+
channelController.channel_mock = .mock(cid: .unique, draftMessage: draftMessage)
1048+
let viewModel = makeComposerDraftsViewModel(
1049+
channelController: channelController,
1050+
messageController: nil
1051+
)
1052+
let attachment = CustomAttachment(id: .unique, content: .mockFile)
1053+
viewModel.customAttachmentTapped(attachment)
1054+
1055+
// When
1056+
viewModel.customAttachmentTapped(attachment) // Remove by tapping again
1057+
1058+
// Then
1059+
XCTAssertEqual(channelController.deleteDraftMessage_callCount, 1)
1060+
}
1061+
1062+
func test_messageComposerVM_whenRemovingAttachment_withTextPresent_shouldNotDeleteDraft() {
1063+
// Given
1064+
let channelController = makeChannelController()
1065+
let draftMessage = DraftMessage.mock(text: "Hello")
1066+
channelController.channel_mock = .mock(cid: .unique, draftMessage: draftMessage)
1067+
let viewModel = makeComposerDraftsViewModel(
1068+
channelController: channelController,
1069+
messageController: nil
1070+
)
1071+
viewModel.text = "Hello"
1072+
let asset = defaultAsset
1073+
viewModel.imageTapped(asset)
1074+
1075+
// When
1076+
viewModel.imageTapped(asset) // Remove the asset by tapping again
1077+
1078+
// Then
1079+
XCTAssertEqual(channelController.deleteDraftMessage_callCount, 0)
1080+
}
1081+
9871082
// MARK: - private
9881083

9891084
private func makeComposerDraftsViewModel(

0 commit comments

Comments
 (0)