Skip to content

Commit a28c821

Browse files
Added resend functionality
1 parent 964bb07 commit a28c821

File tree

5 files changed

+299
-13
lines changed

5 files changed

+299
-13
lines changed

Sources/StreamChatSwiftUI/ChatChannel/Composer/AttachmentUploadingStateView.swift

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,13 @@ struct AttachmentUploadingStateView: View {
3737
}
3838

3939
case .uploadingFailed:
40-
Image(uiImage: images.messageListErrorIndicator)
41-
.foregroundColor(Color(colors.alert))
40+
BottomRightView {
41+
Image(uiImage: images.messageListErrorIndicator)
42+
.foregroundColor(Color(colors.alert))
43+
.background(Color.white)
44+
.clipShape(Circle())
45+
.offset(x: -4, y: -4)
46+
}
4247
case .uploaded:
4348
TopRightView {
4449
Image(uiImage: images.confirmCheckmark)

Sources/StreamChatSwiftUI/ChatChannel/MessageList/MessageContainerView.swift

Lines changed: 31 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -71,17 +71,21 @@ struct MessageContainerView<Factory: ViewFactory>: View {
7171
)
7272
.animation(nil)
7373
.overlay(
74-
reactionsShown ?
75-
factory.makeMessageReactionView(
76-
message: message,
77-
onTapGesture: {
78-
handleGestureForMessage(showsMessageActions: false)
79-
},
80-
onLongPressGesture: {
81-
handleGestureForMessage(showsMessageActions: false)
82-
}
83-
)
84-
: nil
74+
ZStack {
75+
reactionsShown ?
76+
factory.makeMessageReactionView(
77+
message: message,
78+
onTapGesture: {
79+
handleGestureForMessage(showsMessageActions: false)
80+
},
81+
onLongPressGesture: {
82+
handleGestureForMessage(showsMessageActions: false)
83+
}
84+
)
85+
: nil
86+
87+
message.localState == .sendingFailed ? SendFailureIndicator() : nil
88+
}
8589
)
8690
.background(
8791
GeometryReader { proxy in
@@ -257,6 +261,22 @@ struct MessageContainerView<Factory: ViewFactory>: View {
257261
}
258262
}
259263

264+
struct SendFailureIndicator: View {
265+
266+
@Injected(\.colors) private var colors
267+
@Injected(\.images) private var images
268+
269+
var body: some View {
270+
BottomRightView {
271+
Image(uiImage: images.messageListErrorIndicator)
272+
.customizable()
273+
.frame(width: 16, height: 16)
274+
.foregroundColor(Color(colors.alert))
275+
.offset(y: 4)
276+
}
277+
}
278+
}
279+
260280
public struct MessageDisplayInfo {
261281
let message: ChatMessage
262282
let frame: CGRect

Sources/StreamChatSwiftUI/ChatChannel/Reactions/MessageActions/DefaultMessageActions.swift

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,27 @@ extension MessageAction {
2323
) -> [MessageAction] {
2424
var messageActions = [MessageAction]()
2525

26+
if message.localState == .sendingFailed {
27+
messageActions = messageNotSentActions(
28+
for: message,
29+
channel: channel,
30+
chatClient: chatClient,
31+
onFinish: onFinish,
32+
onError: onError
33+
)
34+
return messageActions
35+
} else if message.localState == .pendingSend
36+
&& message.messageId.contains("\(LocalAttachmentState.uploadingFailed)") {
37+
messageActions = editAndDeleteActions(
38+
for: message,
39+
channel: channel,
40+
chatClient: chatClient,
41+
onFinish: onFinish,
42+
onError: onError
43+
)
44+
return messageActions
45+
}
46+
2647
if channel.config.repliesEnabled {
2748
let replyAction = replyAction(
2849
for: message,
@@ -453,4 +474,101 @@ extension MessageAction {
453474

454475
return unmuteUser
455476
}
477+
478+
private static func resendMessageAction(
479+
for message: ChatMessage,
480+
channel: ChatChannel,
481+
chatClient: ChatClient,
482+
onFinish: @escaping (MessageActionInfo) -> Void,
483+
onError: @escaping (Error) -> Void
484+
) -> MessageAction {
485+
let messageController = chatClient.messageController(
486+
cid: channel.cid,
487+
messageId: message.id
488+
)
489+
490+
let resendAction = {
491+
messageController.resendMessage { error in
492+
if let error = error {
493+
onError(error)
494+
} else {
495+
onFinish(
496+
MessageActionInfo(
497+
message: message,
498+
identifier: "resend"
499+
)
500+
)
501+
}
502+
}
503+
}
504+
505+
let messageAction = MessageAction(
506+
title: L10n.Message.Actions.resend,
507+
iconName: "icn_resend",
508+
action: resendAction,
509+
confirmationPopup: nil,
510+
isDestructive: false
511+
)
512+
513+
return messageAction
514+
}
515+
516+
private static func messageNotSentActions(
517+
for message: ChatMessage,
518+
channel: ChatChannel,
519+
chatClient: ChatClient,
520+
onFinish: @escaping (MessageActionInfo) -> Void,
521+
onError: @escaping (Error) -> Void
522+
) -> [MessageAction] {
523+
var messageActions = [MessageAction]()
524+
525+
let resendAction = resendMessageAction(
526+
for: message,
527+
channel: channel,
528+
chatClient: chatClient,
529+
onFinish: onFinish,
530+
onError: onError
531+
)
532+
messageActions.append(resendAction)
533+
534+
let editAndDeleteActions = editAndDeleteActions(
535+
for: message,
536+
channel: channel,
537+
chatClient: chatClient,
538+
onFinish: onFinish,
539+
onError: onError
540+
)
541+
messageActions.append(contentsOf: editAndDeleteActions)
542+
543+
return messageActions
544+
}
545+
546+
private static func editAndDeleteActions(
547+
for message: ChatMessage,
548+
channel: ChatChannel,
549+
chatClient: ChatClient,
550+
onFinish: @escaping (MessageActionInfo) -> Void,
551+
onError: @escaping (Error) -> Void
552+
) -> [MessageAction] {
553+
var messageActions = [MessageAction]()
554+
555+
let editAction = editMessageAction(
556+
for: message,
557+
channel: channel,
558+
onFinish: onFinish
559+
)
560+
messageActions.append(editAction)
561+
562+
let deleteAction = deleteMessageAction(
563+
for: message,
564+
channel: channel,
565+
chatClient: chatClient,
566+
onFinish: onFinish,
567+
onError: onError
568+
)
569+
570+
messageActions.append(deleteAction)
571+
572+
return messageActions
573+
}
456574
}

StreamChatSwiftUI.xcodeproj/project.pbxproj

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
objects = {
88

99
/* Begin PBXBuildFile section */
10+
840008BB27E8D64A00282D88 /* MessageActions_Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 840008BA27E8D64A00282D88 /* MessageActions_Tests.swift */; };
1011
8413D90227A9654600A89432 /* SearchResultsView_Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8413D90127A9654600A89432 /* SearchResultsView_Tests.swift */; };
1112
841B2EF4278DB9E500ED619E /* MessageListHelperViews.swift in Sources */ = {isa = PBXBuildFile; fileRef = 841B2EF3278DB9E500ED619E /* MessageListHelperViews.swift */; };
1213
841B2EF6278F108700ED619E /* MessageReadIndicatorView_Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 841B2EF5278F108700ED619E /* MessageReadIndicatorView_Tests.swift */; };
@@ -335,6 +336,7 @@
335336

336337
/* Begin PBXFileReference section */
337338
4A65451E274BA170003C5FA8 /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = "<group>"; };
339+
840008BA27E8D64A00282D88 /* MessageActions_Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageActions_Tests.swift; sourceTree = "<group>"; };
338340
8413D90127A9654600A89432 /* SearchResultsView_Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchResultsView_Tests.swift; sourceTree = "<group>"; };
339341
841B2EF3278DB9E500ED619E /* MessageListHelperViews.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageListHelperViews.swift; sourceTree = "<group>"; };
340342
841B2EF5278F108700ED619E /* MessageReadIndicatorView_Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageReadIndicatorView_Tests.swift; sourceTree = "<group>"; };
@@ -1221,6 +1223,7 @@
12211223
84C94D5027591DE2007FE2B9 /* ChatMessageIDs_Tests.swift */,
12221224
84C94D57275A1B89007FE2B9 /* MessageTypeResolver_Tests.swift */,
12231225
84C94D65275A660B007FE2B9 /* MessageActionsViewModel_Tests.swift */,
1226+
840008BA27E8D64A00282D88 /* MessageActions_Tests.swift */,
12241227
84E6EC22279AEE6B0017207B /* MessageContainerView_Tests.swift */,
12251228
84DEC8DE2760A1D100172876 /* MessageView_Tests.swift */,
12261229
848399F127601231003075E4 /* ReactionsOverlayView_Tests.swift */,
@@ -1705,6 +1708,7 @@
17051708
84D6B55A27DF6EC7009C6D07 /* LoadingView_Tests.swift in Sources */,
17061709
84C94D0427578BF2007FE2B9 /* TestError.swift in Sources */,
17071710
84C94D0A27578BF2007FE2B9 /* TestDataModel.xcdatamodeld in Sources */,
1711+
840008BB27E8D64A00282D88 /* MessageActions_Tests.swift in Sources */,
17081712
84C94D422757C16D007FE2B9 /* ChatChannelListTestHelpers.swift in Sources */,
17091713
84C94D5A275A2E43007FE2B9 /* StreamChat_Utils_Tests.swift in Sources */,
17101714
);
Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
//
2+
// Copyright © 2022 Stream.io Inc. All rights reserved.
3+
//
4+
5+
@testable import StreamChat
6+
@testable import StreamChatSwiftUI
7+
import XCTest
8+
9+
class MessageActions_Tests: StreamChatTestCase {
10+
11+
func test_messageActions_currentUserDefault() {
12+
// Given
13+
let channel = ChatChannel.mockDMChannel()
14+
let message = ChatMessage.mock(
15+
id: .unique,
16+
cid: channel.cid,
17+
text: "Test",
18+
author: .mock(id: chatClient.currentUserId!),
19+
isSentByCurrentUser: true
20+
)
21+
let factory = DefaultViewFactory.shared
22+
23+
// When
24+
let messageActions = MessageAction.defaultActions(
25+
factory: factory,
26+
for: message,
27+
channel: channel,
28+
chatClient: chatClient,
29+
onFinish: { _ in },
30+
onError: { _ in }
31+
)
32+
33+
// Then
34+
XCTAssert(messageActions.count == 6)
35+
XCTAssert(messageActions[0].title == "Reply")
36+
XCTAssert(messageActions[1].title == "Thread Reply")
37+
XCTAssert(messageActions[2].title == "Pin to conversation")
38+
XCTAssert(messageActions[3].title == "Copy Message")
39+
XCTAssert(messageActions[4].title == "Edit Message")
40+
XCTAssert(messageActions[5].title == "Delete Message")
41+
}
42+
43+
func test_messageActions_otherUserDefault() {
44+
// Given
45+
let channel = ChatChannel.mockDMChannel()
46+
let message = ChatMessage.mock(
47+
id: .unique,
48+
cid: channel.cid,
49+
text: "Test",
50+
author: .mock(id: .unique),
51+
isSentByCurrentUser: false
52+
)
53+
let factory = DefaultViewFactory.shared
54+
55+
// When
56+
let messageActions = MessageAction.defaultActions(
57+
factory: factory,
58+
for: message,
59+
channel: channel,
60+
chatClient: chatClient,
61+
onFinish: { _ in },
62+
onError: { _ in }
63+
)
64+
65+
// Then
66+
XCTAssert(messageActions.count == 6)
67+
XCTAssert(messageActions[0].title == "Reply")
68+
XCTAssert(messageActions[1].title == "Thread Reply")
69+
XCTAssert(messageActions[2].title == "Pin to conversation")
70+
XCTAssert(messageActions[3].title == "Copy Message")
71+
XCTAssert(messageActions[4].title == "Flag Message")
72+
XCTAssert(messageActions[5].title == "Mute User")
73+
}
74+
75+
func test_messageActions_messageNotSent() {
76+
// Given
77+
let channel = ChatChannel.mockDMChannel()
78+
let message = ChatMessage.mock(
79+
id: .unique,
80+
cid: channel.cid,
81+
text: "Test",
82+
author: .mock(id: chatClient.currentUserId!),
83+
localState: .sendingFailed
84+
)
85+
let factory = DefaultViewFactory.shared
86+
87+
// When
88+
let messageActions = MessageAction.defaultActions(
89+
factory: factory,
90+
for: message,
91+
channel: channel,
92+
chatClient: chatClient,
93+
onFinish: { _ in },
94+
onError: { _ in }
95+
)
96+
97+
// Then
98+
XCTAssert(messageActions.count == 3)
99+
XCTAssert(messageActions[0].title == "Resend")
100+
XCTAssert(messageActions[1].title == "Edit Message")
101+
XCTAssert(messageActions[2].title == "Delete Message")
102+
}
103+
104+
func test_messageActions_attachmentFailure() {
105+
// Given
106+
let channel = ChatChannel.mockDMChannel()
107+
let attachments = [
108+
ChatMessageImageAttachment.mock(
109+
id: .unique,
110+
localState: .uploadingFailed
111+
)
112+
.asAnyAttachment
113+
]
114+
let message = ChatMessage.mock(
115+
id: .unique,
116+
cid: channel.cid,
117+
text: "Test",
118+
author: .mock(id: chatClient.currentUserId!),
119+
attachments: attachments,
120+
localState: .pendingSend
121+
)
122+
let factory = DefaultViewFactory.shared
123+
124+
// When
125+
let messageActions = MessageAction.defaultActions(
126+
factory: factory,
127+
for: message,
128+
channel: channel,
129+
chatClient: chatClient,
130+
onFinish: { _ in },
131+
onError: { _ in }
132+
)
133+
134+
// Then
135+
XCTAssert(messageActions.count == 2)
136+
XCTAssert(messageActions[0].title == "Edit Message")
137+
XCTAssert(messageActions[1].title == "Delete Message")
138+
}
139+
}

0 commit comments

Comments
 (0)