Skip to content

Commit 4a44fbf

Browse files
Merge branch 'feature/edit-message'
2 parents 8ebe964 + e6c51a7 commit 4a44fbf

13 files changed

+232
-41
lines changed

Sources/StreamChatSwiftUI/ChatChannel/ChatChannelView.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ public struct ChatChannelView<Factory: ViewFactory>: View {
7070
with: viewModel.channelController,
7171
messageController: viewModel.messageController,
7272
quotedMessage: $viewModel.quotedMessage,
73+
editedMessage: $viewModel.editedMessage,
7374
onMessageSent: viewModel.scrollToLastMessage
7475
)
7576
}

Sources/StreamChatSwiftUI/ChatChannel/ChatChannelViewModel.swift

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@ public class ChatChannelViewModel: ObservableObject, MessagesDataSource {
8787

8888
@Published var reactionsShown = false
8989
@Published var quotedMessage: ChatMessage?
90+
@Published var editedMessage: ChatMessage?
9091

9192
var channel: ChatChannel {
9293
channelController.channel!
@@ -262,16 +263,16 @@ public class ChatChannelViewModel: ObservableObject, MessagesDataSource {
262263
extension ChatMessage: Identifiable {
263264
var messageId: String {
264265
let statesId = uploadingStatesId
265-
266+
266267
if statesId.isEmpty {
267268
if !reactionScores.isEmpty {
268269
return baseId + reactionScoresId
269270
} else {
270271
return baseId
271272
}
272273
}
273-
274-
return baseId + statesId + reactionScoresId + repliesCountId
274+
275+
return baseId + statesId + reactionScoresId + repliesCountId + "\(updatedAt)"
275276
}
276277

277278
private var baseId: String {

Sources/StreamChatSwiftUI/ChatChannel/Composer/ComposerHelperViews.swift

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

5+
import StreamChat
56
import SwiftUI
67

78
/// View used to indicate that an asset is a video.
@@ -133,3 +134,57 @@ struct AssetsAccessPermissionView: View {
133134
UIApplication.shared.open(url, options: [:], completionHandler: nil)
134135
}
135136
}
137+
138+
/// View for the quoted message header.
139+
struct QuotedMessageHeaderView: View {
140+
141+
@Injected(\.fonts) var fonts
142+
143+
@Binding var quotedMessage: ChatMessage?
144+
145+
var body: some View {
146+
ZStack {
147+
Text(L10n.Composer.Title.reply)
148+
.font(fonts.bodyBold)
149+
150+
HStack {
151+
Spacer()
152+
Button(action: {
153+
withAnimation {
154+
quotedMessage = nil
155+
}
156+
}, label: {
157+
DiscardButtonView()
158+
})
159+
}
160+
}
161+
.frame(height: 32)
162+
}
163+
}
164+
165+
/// View for the edit message header.
166+
struct EditMessageHeaderView: View {
167+
168+
@Injected(\.fonts) var fonts
169+
170+
@Binding var editedMessage: ChatMessage?
171+
172+
var body: some View {
173+
ZStack {
174+
Text(L10n.Composer.Title.edit)
175+
.font(fonts.bodyBold)
176+
177+
HStack {
178+
Spacer()
179+
Button(action: {
180+
withAnimation {
181+
editedMessage = nil
182+
}
183+
}, label: {
184+
DiscardButtonView()
185+
})
186+
}
187+
}
188+
.frame(height: 32)
189+
}
190+
}

Sources/StreamChatSwiftUI/ChatChannel/Composer/MessageComposerView.swift

Lines changed: 15 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,14 @@ public struct MessageComposerView<Factory: ViewFactory>: View, KeyboardReadable
1515

1616
private var factory: Factory
1717
@Binding var quotedMessage: ChatMessage?
18+
@Binding var editedMessage: ChatMessage?
1819

1920
public init(
2021
viewFactory: Factory,
2122
channelController: ChatChannelController,
2223
messageController: ChatMessageController?,
2324
quotedMessage: Binding<ChatMessage?>,
25+
editedMessage: Binding<ChatMessage?>,
2426
onMessageSent: @escaping () -> Void
2527
) {
2628
factory = viewFactory
@@ -31,6 +33,7 @@ public struct MessageComposerView<Factory: ViewFactory>: View, KeyboardReadable
3133
)
3234
)
3335
_quotedMessage = quotedMessage
36+
_editedMessage = editedMessage
3437
self.onMessageSent = onMessageSent
3538
}
3639

@@ -44,6 +47,10 @@ public struct MessageComposerView<Factory: ViewFactory>: View, KeyboardReadable
4447
factory.makeQuotedMessageHeaderView(
4548
quotedMessage: $quotedMessage
4649
)
50+
} else if editedMessage != nil {
51+
factory.makeEditedMessageHeaderView(
52+
editedMessage: $editedMessage
53+
)
4754
}
4855

4956
HStack(alignment: .bottom) {
@@ -61,8 +68,12 @@ public struct MessageComposerView<Factory: ViewFactory>: View, KeyboardReadable
6168
)
6269

6370
factory.makeTrailingComposerView(enabled: viewModel.sendButtonEnabled) {
64-
viewModel.sendMessage(quotedMessage: quotedMessage) {
71+
viewModel.sendMessage(
72+
quotedMessage: quotedMessage,
73+
editedMessage: editedMessage
74+
) {
6575
quotedMessage = nil
76+
editedMessage = nil
6677
onMessageSent()
6778
}
6879
}
@@ -109,6 +120,9 @@ public struct MessageComposerView<Factory: ViewFactory>: View, KeyboardReadable
109120
.alert(isPresented: $viewModel.errorShown) {
110121
Alert.defaultErrorAlert
111122
}
123+
.onChange(of: editedMessage) { _ in
124+
viewModel.text = editedMessage?.text ?? ""
125+
}
112126
}
113127
}
114128

@@ -201,30 +215,3 @@ public struct ComposerInputView<Factory: ViewFactory>: View {
201215
!addedFileURLs.isEmpty || !addedAssets.isEmpty
202216
}
203217
}
204-
205-
/// View for the quoted message header.
206-
struct QuotedMessageHeaderView: View {
207-
208-
@Injected(\.fonts) var fonts
209-
210-
@Binding var quotedMessage: ChatMessage?
211-
212-
var body: some View {
213-
ZStack {
214-
Text(L10n.Composer.Title.reply)
215-
.font(fonts.bodyBold)
216-
217-
HStack {
218-
Spacer()
219-
Button(action: {
220-
withAnimation {
221-
quotedMessage = nil
222-
}
223-
}, label: {
224-
DiscardButtonView()
225-
})
226-
}
227-
}
228-
.frame(height: 32)
229-
}
230-
}

Sources/StreamChatSwiftUI/ChatChannel/Composer/MessageComposerViewModel.swift

Lines changed: 37 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -89,8 +89,14 @@ public class MessageComposerViewModel: ObservableObject {
8989

9090
public func sendMessage(
9191
quotedMessage: ChatMessage?,
92+
editedMessage: ChatMessage?,
9293
completion: @escaping () -> Void
9394
) {
95+
if let editedMessage = editedMessage {
96+
edit(message: editedMessage, completion: completion)
97+
return
98+
}
99+
94100
do {
95101
var attachments = try addedAssets.map { added in
96102
try AnyAttachmentPayload(
@@ -137,10 +143,7 @@ public class MessageComposerViewModel: ObservableObject {
137143
}
138144
}
139145

140-
text = ""
141-
addedAssets = []
142-
addedFileURLs = []
143-
addedCustomAttachments = []
146+
clearInputData()
144147
} catch {
145148
errorShown = true
146149
}
@@ -286,6 +289,36 @@ public class MessageComposerViewModel: ObservableObject {
286289

287290
// MARK: - private
288291

292+
private func edit(
293+
message: ChatMessage,
294+
completion: @escaping () -> Void
295+
) {
296+
guard let channelId = channelController.channel?.cid else {
297+
return
298+
}
299+
let messageController = chatClient.messageController(
300+
cid: channelId,
301+
messageId: message.id
302+
)
303+
304+
messageController.editMessage(text: text) { [weak self] error in
305+
if error != nil {
306+
self?.errorShown = true
307+
} else {
308+
completion()
309+
}
310+
}
311+
312+
clearInputData()
313+
}
314+
315+
private func clearInputData() {
316+
text = ""
317+
addedAssets = []
318+
addedFileURLs = []
319+
addedCustomAttachments = []
320+
}
321+
289322
private func checkPickerSelectionState() {
290323
if (!addedAssets.isEmpty || !addedFileURLs.isEmpty) {
291324
pickerTypeState = .collapsed

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

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,15 @@ extension MessageAction {
3838
)
3939
messageActions.append(replyThread)
4040
}
41-
41+
4242
if message.isSentByCurrentUser {
43+
let editAction = editMessageAction(
44+
for: message,
45+
channel: channel,
46+
onFinish: onFinish
47+
)
48+
messageActions.append(editAction)
49+
4350
let deleteAction = deleteMessageAction(
4451
for: message,
4552
channel: channel,
@@ -66,6 +73,29 @@ extension MessageAction {
6673

6774
// MARK: - private
6875

76+
private static func editMessageAction(
77+
for message: ChatMessage,
78+
channel: ChatChannel,
79+
onFinish: @escaping (MessageActionInfo) -> Void
80+
) -> MessageAction {
81+
let editAction = MessageAction(
82+
title: L10n.Message.Actions.edit,
83+
iconName: "icn_edit",
84+
action: {
85+
onFinish(
86+
MessageActionInfo(
87+
message: message,
88+
identifier: "edit"
89+
)
90+
)
91+
},
92+
confirmationPopup: nil,
93+
isDestructive: false
94+
)
95+
96+
return editAction
97+
}
98+
6999
private static func replyAction(
70100
for message: ChatMessage,
71101
channel: ChatChannel,

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,12 @@ public class MessageActionsResolver: MessageActionsResolving {
3030
if info.identifier == "inlineReply" {
3131
withAnimation {
3232
viewModel.quotedMessage = info.message
33+
viewModel.editedMessage = nil
34+
}
35+
} else if info.identifier == "edit" {
36+
withAnimation {
37+
viewModel.editedMessage = info.message
38+
viewModel.quotedMessage = nil
3339
}
3440
}
3541

Sources/StreamChatSwiftUI/DefaultViewFactory.swift

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -245,13 +245,15 @@ extension ViewFactory {
245245
with channelController: ChatChannelController,
246246
messageController: ChatMessageController?,
247247
quotedMessage: Binding<ChatMessage?>,
248+
editedMessage: Binding<ChatMessage?>,
248249
onMessageSent: @escaping () -> Void
249250
) -> MessageComposerView<Self> {
250251
MessageComposerView(
251252
viewFactory: self,
252253
channelController: channelController,
253254
messageController: messageController,
254255
quotedMessage: quotedMessage,
256+
editedMessage: editedMessage,
255257
onMessageSent: onMessageSent
256258
)
257259
}
@@ -496,6 +498,12 @@ extension ViewFactory {
496498
scrolledId: .constant(nil)
497499
)
498500
}
501+
502+
public func makeEditedMessageHeaderView(
503+
editedMessage: Binding<ChatMessage?>
504+
) -> some View {
505+
EditMessageHeaderView(editedMessage: editedMessage)
506+
}
499507
}
500508

501509
/// Default class conforming to `ViewFactory`, used throughout the SDK.

Sources/StreamChatSwiftUI/ViewFactory.swift

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -228,12 +228,14 @@ public protocol ViewFactory: AnyObject {
228228
/// - channelController: The `ChatChannelController` for the channel.
229229
/// - messageController: Optional `ChatMessageController`, if used in a thread.
230230
/// - quotedMessage: Optional quoted message, shown in the composer input.
231+
/// - editedMessage: Optional message that's being edited.
231232
/// - onMessageSent: Called when a message is sent.
232233
/// - Returns: view displayed in the message composer slot.
233234
func makeMessageComposerViewType(
234235
with channelController: ChatChannelController,
235236
messageController: ChatMessageController?,
236237
quotedMessage: Binding<ChatMessage?>,
238+
editedMessage: Binding<ChatMessage?>,
237239
onMessageSent: @escaping () -> Void
238240
) -> MessageComposerViewType
239241

@@ -469,4 +471,13 @@ public protocol ViewFactory: AnyObject {
469471
func makeQuotedMessageComposerView(
470472
quotedMessage: ChatMessage
471473
) -> QuotedMessageComposerViewType
474+
475+
associatedtype EditedMessageHeaderViewType: View
476+
/// Creates the edited message header view in the composer.
477+
/// - Parameters:
478+
/// - editedMessage: the optional edited message.
479+
/// - Returns: view displayed in the slot for edited message in the composer.
480+
func makeEditedMessageHeaderView(
481+
editedMessage: Binding<ChatMessage?>
482+
) -> EditedMessageHeaderViewType
472483
}

0 commit comments

Comments
 (0)