Add message editing feature

This commit is contained in:
Benoit Martins 2025-11-06 16:11:40 +01:00
parent fa1f8386b4
commit 7972fd7c1f
6 changed files with 343 additions and 28 deletions

View file

@ -204,6 +204,8 @@
"conversation_dialog_edit_subject" = "Edit conversation subject";
"conversation_dialog_set_subject" = "Set conversation subject";
"conversation_dialog_subject_hint" = "Conversation subject";
"conversation_editing_message_title" = "Message being edited";
"conversation_message_edited_label" = "Edited";
"conversation_end_to_end_encrypted_bottom_sheet_link" = "https://linphone.org/en/features/#security";
"conversation_end_to_end_encrypted_event_title" = "End-to-end encrypted conversation";
"conversation_end_to_end_encrypted_event_subtitle" = "Messages in this conversation are e2e encrypted. Only your correspondent can decrypt them.";
@ -394,6 +396,7 @@
"menu_delete_selected_item" = "Delete";
"menu_forward_chat_message" = "Forward";
"menu_invite" = "Invite";
"menu_edit_chat_message" = "Edit";
"menu_reply_to_chat_message" = "Reply";
"menu_resend_chat_message" = "Re-send";
"menu_see_existing_contact" = "See contact";

View file

@ -204,6 +204,8 @@
"conversation_dialog_edit_subject" = "Renommer la conversation";
"conversation_dialog_set_subject" = "Nommer la conversation";
"conversation_dialog_subject_hint" = "Nom de la conversation";
"conversation_editing_message_title" = "Modification du message";
"conversation_message_edited_label" = "Modifié";
"conversation_end_to_end_encrypted_bottom_sheet_link" = "https://linphone.org/en/features/#security";
"conversation_end_to_end_encrypted_event_title" = "Conversation chiffrée de bout en bout";
"conversation_end_to_end_encrypted_event_subtitle" = "Les messages de cette conversation sont chiffrés de bout en bout. Seul votre correspondant peut les déchiffrer.";
@ -394,6 +396,7 @@
"menu_delete_selected_item" = "Supprimer";
"menu_forward_chat_message" = "Transférer";
"menu_invite" = "Inviter";
"menu_edit_chat_message" = "Modifier";
"menu_reply_to_chat_message" = "Répondre";
"menu_resend_chat_message" = "Ré-envoyer";
"menu_see_existing_contact" = "Voir le contact";

View file

@ -325,6 +325,14 @@ struct ChatBubbleView: View {
.padding(.top, 1)
}
if eventLogMessage.message.isEdited && eventLogMessage.message.isOutgoing {
Text("conversation_message_edited_label")
.foregroundStyle(Color.grayMain2c500)
.default_text_style_300(styleSize: 12)
.padding(.top, 1)
.padding(.trailing, -4)
}
Text(conversationViewModel.getMessageTime(startDate: eventLogMessage.message.dateReceived))
.foregroundStyle(Color.grayMain2c500)
.default_text_style_300(styleSize: 12)
@ -349,6 +357,14 @@ struct ChatBubbleView: View {
}
}
if eventLogMessage.message.isEdited && !eventLogMessage.message.isOutgoing {
Text("conversation_message_edited_label")
.foregroundStyle(Color.grayMain2c500)
.default_text_style_300(styleSize: 12)
.padding(.top, 1)
.padding(.trailing, -4)
}
if eventLogMessage.message.isEphemeral && !eventLogMessage.message.isOutgoing {
Image("clock-countdown")
.renderingMode(.template)

View file

@ -620,6 +620,43 @@ struct ConversationFragment: View {
}
}
.transition(.move(edge: .bottom))
} else if conversationViewModel.messageToEdit != nil {
ZStack(alignment: .top) {
HStack {
VStack {
Text("conversation_editing_message_title")
.default_text_style_300(styleSize: 15)
.frame(maxWidth: .infinity, alignment: .leading)
.padding(.bottom, 1)
.lineLimit(1)
Text("\(conversationViewModel.messageToEdit!.message.text)")
.default_text_style_300(styleSize: 15)
.frame(maxWidth: .infinity, alignment: .leading)
.lineLimit(1)
}
}
.frame(maxWidth: .infinity)
.padding(.all, 20)
.background(Color.gray100)
HStack {
Spacer()
Button(action: {
messageText = ""
withAnimation {
conversationViewModel.messageToEdit = nil
}
}, label: {
Image("x")
.resizable()
.frame(width: 30, height: 30, alignment: .leading)
.padding(.all, 10)
})
}
}
.transition(.move(edge: .bottom))
}
if !conversationViewModel.mediasToSend.isEmpty || mediasIsLoading {
@ -879,43 +916,66 @@ struct ConversationFragment: View {
}
}
if messageText.isEmpty && conversationViewModel.mediasToSend.isEmpty {
Button {
voiceRecordingInProgress = true
} label: {
Image("microphone")
.renderingMode(.template)
.resizable()
.foregroundStyle(Color.grayMain2c500)
.frame(width: 28, height: 28, alignment: .leading)
.padding(.all, 6)
.padding(.top, 4)
if conversationViewModel.messageToEdit == nil {
if messageText.isEmpty && conversationViewModel.mediasToSend.isEmpty {
Button {
voiceRecordingInProgress = true
} label: {
Image("microphone")
.renderingMode(.template)
.resizable()
.foregroundStyle(Color.grayMain2c500)
.frame(width: 28, height: 28, alignment: .leading)
.padding(.all, 6)
.padding(.top, 4)
}
} else {
Button {
if conversationViewModel.displayedConversationHistorySize > 1 {
NotificationCenter.default.post(name: .onScrollToBottom, object: nil)
}
let messageTextTmp = self.messageText
messageText = " "
DispatchQueue.main.asyncAfter(deadline: .now() + 0.01) {
messageText = ""
isMessageTextFocused = true
conversationViewModel.sendMessage(messageText: messageTextTmp)
}
} label: {
Image("paper-plane-tilt")
.renderingMode(.template)
.resizable()
.foregroundStyle(Color.orangeMain500)
.frame(width: 28, height: 28, alignment: .leading)
.padding(.all, 6)
.padding(.top, 4)
.rotationEffect(.degrees(45))
}
.padding(.trailing, 4)
}
} else {
Button {
if conversationViewModel.displayedConversationHistorySize > 1 {
NotificationCenter.default.post(name: .onScrollToBottom, object: nil)
}
let messageTextTmp = self.messageText
messageText = " "
DispatchQueue.main.asyncAfter(deadline: .now() + 0.01) {
messageText = ""
isMessageTextFocused = true
messageText = " "
DispatchQueue.main.asyncAfter(deadline: .now() + 0.01) {
messageText = ""
isMessageTextFocused = true
conversationViewModel.sendMessage(messageText: messageTextTmp)
}
conversationViewModel.sendMessage(messageText: messageTextTmp)
}
} label: {
Image("paper-plane-tilt")
Image("pencil-simple")
.renderingMode(.template)
.resizable()
.foregroundStyle(Color.orangeMain500)
.foregroundStyle(messageText.isEmpty ? Color.gray300 : Color.orangeMain500)
.frame(width: 28, height: 28, alignment: .leading)
.padding(.all, 6)
.padding(.top, 4)
.rotationEffect(.degrees(45))
}
.padding(.trailing, 4)
.disabled(messageText.isEmpty)
}
}
.padding(.leading, 15)
@ -1097,6 +1157,43 @@ struct ConversationFragment: View {
Divider()
}
if conversationViewModel.selectedMessage!.message.isOutgoing
&& !(SharedMainViewModel.shared.displayedConversation?.isReadOnly ?? cachedConversation!.isReadOnly)
&& conversationViewModel.selectedMessage!.message.isEditable {
Button {
if let chatMessage = conversationViewModel.selectedMessage {
if voiceRecordingInProgress {
voiceRecordingInProgress = false
}
messageText = chatMessage.message.text
conversationViewModel.selectedMessage = nil
conversationViewModel.editMessage(
chatMessage: chatMessage,
isMessageTextFocused: Binding(
get: { isMessageTextFocused },
set: { isMessageTextFocused = $0 }
)
)
}
} label: {
HStack {
Text("menu_edit_chat_message")
.default_text_style(styleSize: 15)
Spacer()
Image("pencil-simple")
.renderingMode(.template)
.resizable()
.foregroundStyle(Color.grayMain2c600)
.frame(width: 20, height: 20, alignment: .leading)
}
.padding(.vertical, 5)
.padding(.horizontal, 20)
}
Divider()
}
Button {
let indexMessage = conversationViewModel.conversationMessagesSection[0].rows.firstIndex(where: {$0.message.id == conversationViewModel.selectedMessage!.message.id})
conversationViewModel.selectedMessage = nil
@ -1211,6 +1308,9 @@ struct ConversationFragment: View {
}
.onAppear {
touchFeedback()
if isMessageTextFocused {
isMessageTextFocused = false
}
}
.onDisappear {
if conversationViewModel.selectedMessage != nil {

View file

@ -68,6 +68,8 @@ public struct Message: Identifiable, Hashable {
public var status: Status?
public var createdAt: Date
public var isOutgoing: Bool
public var isEditable: Bool
public var isEdited: Bool
public var dateReceived: time_t
public var address: String
@ -94,6 +96,8 @@ public struct Message: Identifiable, Hashable {
status: Status? = nil,
createdAt: Date = Date(),
isOutgoing: Bool,
isEditable: Bool,
isEdited: Bool,
dateReceived: time_t,
address: String,
isFirstMessage: Bool = false,
@ -116,6 +120,8 @@ public struct Message: Identifiable, Hashable {
self.status = status
self.createdAt = createdAt
self.isOutgoing = isOutgoing
self.isEditable = isEditable
self.isEdited = isEdited
self.dateReceived = dateReceived
self.isFirstMessage = isFirstMessage
self.address = address
@ -163,6 +169,8 @@ public struct Message: Identifiable, Hashable {
status: status,
createdAt: draft.createdAt,
isOutgoing: draft.isOutgoing,
isEditable: draft.isEditable,
isEdited: draft.isEdited,
dateReceived: draft.dateReceived,
address: draft.address,
isFirstMessage: draft.isFirstMessage,
@ -184,7 +192,7 @@ extension Message {
extension Message: Equatable {
public static func == (lhs: Message, rhs: Message) -> Bool {
lhs.id == rhs.id && lhs.status == rhs.status && lhs.isFirstMessage == rhs.isFirstMessage && lhs.ownReaction == rhs.ownReaction && lhs.reactions == rhs.reactions && lhs.ephemeralExpireTime == rhs.ephemeralExpireTime && lhs.attachments == rhs.attachments
lhs.id == rhs.id && lhs.status == rhs.status && lhs.isEdited == rhs.isEdited && lhs.isFirstMessage == rhs.isFirstMessage && lhs.text == rhs.text && lhs.attachments == rhs.attachments && lhs.replyMessage?.text == rhs.replyMessage?.text && lhs.ownReaction == rhs.ownReaction && lhs.reactions == rhs.reactions && lhs.ephemeralExpireTime == rhs.ephemeralExpireTime
}
}
@ -211,6 +219,8 @@ public struct ReplyMessage: Codable, Identifiable, Hashable {
public var isFirstMessage: Bool
public var text: String
public var isOutgoing: Bool
public var isEditable: Bool
public var isEdited: Bool
public var dateReceived: time_t
public var attachmentsNames: String
public var attachments: [Attachment]
@ -221,6 +231,8 @@ public struct ReplyMessage: Codable, Identifiable, Hashable {
isFirstMessage: Bool = false,
text: String = "",
isOutgoing: Bool,
isEditable: Bool,
isEdited: Bool,
dateReceived: time_t,
attachmentsNames: String = "",
attachments: [Attachment] = [],
@ -231,6 +243,8 @@ public struct ReplyMessage: Codable, Identifiable, Hashable {
self.isFirstMessage = isFirstMessage
self.text = text
self.isOutgoing = isOutgoing
self.isEditable = isEditable
self.isEdited = isEdited
self.dateReceived = dateReceived
self.attachmentsNames = attachmentsNames
self.attachments = attachments
@ -238,20 +252,22 @@ public struct ReplyMessage: Codable, Identifiable, Hashable {
}
func toMessage() -> Message {
Message(id: id, isOutgoing: isOutgoing, dateReceived: dateReceived, address: address, isFirstMessage: isFirstMessage, text: text, attachments: attachments, recording: recording)
Message(id: id, isOutgoing: isOutgoing, isEditable: isEditable, isEdited: isEdited, dateReceived: dateReceived, address: address, isFirstMessage: isFirstMessage, text: text, attachments: attachments, recording: recording)
}
}
public extension Message {
func toReplyMessage() -> ReplyMessage {
ReplyMessage(id: id, address: address, isFirstMessage: isFirstMessage, text: text, isOutgoing: isOutgoing, dateReceived: dateReceived, attachments: attachments, recording: recording)
ReplyMessage(id: id, address: address, isFirstMessage: isFirstMessage, text: text, isOutgoing: isOutgoing, isEditable: isEditable, isEdited: isEdited, dateReceived: dateReceived, attachments: attachments, recording: recording)
}
}
public struct DraftMessage {
public var id: String?
public let isOutgoing: Bool
public let isEditable: Bool
public let isEdited: Bool
public var dateReceived: time_t
public let address: String
public let isFirstMessage: Bool
@ -265,6 +281,8 @@ public struct DraftMessage {
public init(id: String? = nil,
isOutgoing: Bool,
isEditable: Bool,
isEdited: Bool,
dateReceived: time_t,
address: String,
isFirstMessage: Bool,
@ -278,6 +296,8 @@ public struct DraftMessage {
) {
self.id = id
self.isOutgoing = isOutgoing
self.isEditable = isEditable
self.isEdited = isEdited
self.dateReceived = dateReceived
self.address = address
self.isFirstMessage = isFirstMessage

View file

@ -93,6 +93,7 @@ class ConversationViewModel: ObservableObject {
@Published var selectedMessageToPlayVoiceRecording: EventLogMessage?
@Published var selectedMessage: EventLogMessage?
@Published var messageToReply: EventLogMessage?
@Published var messageToEdit: EventLogMessage?
@Published var sheetCategories: [SheetCategory] = []
@ -171,7 +172,127 @@ class ConversationViewModel: ObservableObject {
self.getEventMessage(eventLog: eventLog)
}, onEphemeralMessageDeleted: {(_: ChatRoom, eventLog: EventLog) in
self.removeMessage(eventLog)
}, onMessageContentEdited: {(chatRoom: ChatRoom, message: ChatMessage) in
let indexMessage = self.conversationMessagesSection[0].rows.firstIndex(where: {$0.eventModel.eventLogId == message.messageId})
var attachmentNameList: String = ""
var attachmentList: [Attachment] = []
var contentText = ""
if !message.contents.isEmpty {
message.contents.forEach { content in
if content.isText && content.name == nil {
contentText = content.utf8Text ?? ""
} else if content.name != nil && !content.name!.isEmpty {
if content.filePath == nil || content.filePath!.isEmpty {
let path = URL(string: self.getNewFilePath(name: content.name ?? ""))
if path != nil {
let attachment =
Attachment(
id: UUID().uuidString,
name: content.name!,
url: path!,
type: .fileTransfer,
size: content.fileSize,
transferProgressIndication: content.filePath != nil && !content.filePath!.isEmpty ? 100 : -1
)
attachmentNameList += ", \(content.name!)"
attachmentList.append(attachment)
}
} else {
if content.type != "video" {
let filePathSep = content.filePath!.components(separatedBy: "/Library/Images/")
let path = URL(string: self.getNewFilePath(name: filePathSep[1]))
var typeTmp: AttachmentType = .other
switch content.type {
case "image":
typeTmp = (content.name?.lowercased().hasSuffix("gif"))! ? .gif : .image
case "audio":
typeTmp = content.isVoiceRecording ? .voiceRecording : .audio
case "application":
typeTmp = content.subtype.lowercased() == "pdf" ? .pdf : .other
case "text":
typeTmp = .text
default:
typeTmp = .other
}
if path != nil {
let attachment =
Attachment(
id: UUID().uuidString,
name: content.name!,
url: path!,
type: typeTmp,
duration: typeTmp == .voiceRecording ? content.fileDuration : 0,
size: content.fileSize,
transferProgressIndication: content.filePath != nil && !content.filePath!.isEmpty ? 100 : -1
)
attachmentNameList += ", \(content.name!)"
attachmentList.append(attachment)
if typeTmp != .voiceRecording {
DispatchQueue.main.async {
if !attachment.full.pathExtension.isEmpty {
self.attachments.append(attachment)
}
}
}
}
} else if content.type == "video" {
let filePathSep = content.filePath!.components(separatedBy: "/Library/Images/")
let path = URL(string: self.getNewFilePath(name: filePathSep[1]))
let pathThumbnail = URL(string: self.generateThumbnail(name: filePathSep[1]))
if path != nil && pathThumbnail != nil {
let attachment =
Attachment(
id: UUID().uuidString,
name: content.name!,
thumbnail: pathThumbnail!,
full: path!,
type: .video,
size: content.fileSize,
transferProgressIndication: content.filePath != nil && !content.filePath!.isEmpty ? 100 : -1
)
attachmentNameList += ", \(content.name!)"
attachmentList.append(attachment)
DispatchQueue.main.async {
if !attachment.full.pathExtension.isEmpty {
self.attachments.append(attachment)
}
}
}
}
}
}
}
}
if !attachmentNameList.isEmpty {
attachmentNameList = String(attachmentNameList.dropFirst(2))
}
let indexReplyMessage = self.conversationMessagesSection[0].rows.firstIndex(where: {$0.message.replyMessage?.id == message.messageId})
DispatchQueue.main.async {
if indexMessage != nil {
self.conversationMessagesSection[0].rows[indexMessage!].message.text = contentText
self.conversationMessagesSection[0].rows[indexMessage!].message.isEdited = true
self.conversationMessagesSection[0].rows[indexMessage!].message.attachments = attachmentList
self.conversationMessagesSection[0].rows[indexMessage!].message.attachmentsNames = attachmentNameList
}
if indexReplyMessage != nil {
self.conversationMessagesSection[0].rows[indexReplyMessage!].message.replyMessage?.text = contentText
}
}
}, onMessageRetracted: {(chatRoom: ChatRoom, message: ChatMessage) in
// TODO
})
self.chatRoomDelegateHolder = ChatRoomDelegateHolder(chatroom: chatRoom, delegate: chatRoomDelegate)
}
@ -544,6 +665,8 @@ class ConversationViewModel: ObservableObject {
id: UUID().uuidString,
status: nil,
isOutgoing: false,
isEditable: false,
isEdited: false,
dateReceived: 0,
address: "",
isFirstMessage: false,
@ -722,6 +845,8 @@ class ConversationViewModel: ObservableObject {
isFirstMessage: false,
text: contentReplyText,
isOutgoing: false,
isEditable: false,
isEdited: false,
dateReceived: 0,
attachmentsNames: attachmentNameReplyList,
attachments: []
@ -735,6 +860,8 @@ class ConversationViewModel: ObservableObject {
id: !chatMessage.messageId.isEmpty ? chatMessage.messageId : UUID().uuidString,
status: statusTmp,
isOutgoing: chatMessage.isOutgoing,
isEditable: chatMessage.isOutgoing ? chatMessage.isEditable : false,
isEdited: chatMessage.isEdited,
dateReceived: chatMessage.time,
address: addressCleaned?.asStringUriOnly() ?? "",
isFirstMessage: isFirstMessageTmp,
@ -788,6 +915,8 @@ class ConversationViewModel: ObservableObject {
id: UUID().uuidString,
status: nil,
isOutgoing: false,
isEditable: false,
isEdited: false,
dateReceived: 0,
address: "",
isFirstMessage: false,
@ -965,6 +1094,8 @@ class ConversationViewModel: ObservableObject {
isFirstMessage: false,
text: contentReplyText,
isOutgoing: false,
isEditable: false,
isEdited: false,
dateReceived: 0,
attachmentsNames: attachmentNameReplyList,
attachments: []
@ -978,6 +1109,8 @@ class ConversationViewModel: ObservableObject {
id: !chatMessage.messageId.isEmpty ? chatMessage.messageId : UUID().uuidString,
status: statusTmp,
isOutgoing: chatMessage.isOutgoing,
isEditable: chatMessage.isOutgoing ? chatMessage.isEditable : false,
isEdited: chatMessage.isEdited,
dateReceived: chatMessage.time,
address: addressCleaned?.asStringUriOnly() ?? "",
isFirstMessage: isFirstMessageTmp,
@ -1048,6 +1181,8 @@ class ConversationViewModel: ObservableObject {
id: UUID().uuidString,
status: nil,
isOutgoing: false,
isEditable: false,
isEdited: false,
dateReceived: 0,
address: "",
isFirstMessage: false,
@ -1239,6 +1374,8 @@ class ConversationViewModel: ObservableObject {
isFirstMessage: false,
text: contentReplyText,
isOutgoing: false,
isEditable: false,
isEdited: false,
dateReceived: 0,
attachmentsNames: attachmentNameReplyList,
attachments: []
@ -1253,6 +1390,8 @@ class ConversationViewModel: ObservableObject {
appData: chatMessage.appdata ?? "",
status: statusTmp,
isOutgoing: chatMessage.isOutgoing,
isEditable: chatMessage.isOutgoing ? chatMessage.isEditable : false,
isEdited: chatMessage.isEdited,
dateReceived: chatMessage.time,
address: addressCleaned != nil ? addressCleaned!.asStringUriOnly() : "",
isFirstMessage: isFirstMessageTmp,
@ -1471,6 +1610,8 @@ class ConversationViewModel: ObservableObject {
isFirstMessage: false,
text: contentReplyText,
isOutgoing: false,
isEditable: false,
isEdited: false,
dateReceived: 0,
attachmentsNames: attachmentNameReplyList,
attachments: []
@ -1485,6 +1626,8 @@ class ConversationViewModel: ObservableObject {
appData: chatMessage.appdata ?? "",
status: statusTmp,
isOutgoing: chatMessage.isOutgoing,
isEditable: chatMessage.isOutgoing ? chatMessage.isEditable : false,
isEdited: chatMessage.isEdited,
dateReceived: chatMessage.time,
address: addressCleaned != nil ? addressCleaned!.asStringUriOnly() : "",
isFirstMessage: isFirstMessageTmp,
@ -1526,6 +1669,8 @@ class ConversationViewModel: ObservableObject {
id: UUID().uuidString,
status: nil,
isOutgoing: false,
isEditable: false,
isEdited: false,
dateReceived: 0,
address: "",
isFirstMessage: false,
@ -1553,6 +1698,9 @@ class ConversationViewModel: ObservableObject {
}
func replyToMessage(index: Int, isMessageTextFocused: Binding<Bool>) {
if self.messageToEdit != nil {
self.messageToEdit = nil
}
coreContext.doOnCoreQueue { _ in
let messageToReplyTmp = self.conversationMessagesSection[0].rows[index]
DispatchQueue.main.async {
@ -1564,6 +1712,21 @@ class ConversationViewModel: ObservableObject {
}
}
func editMessage(chatMessage: EventLogMessage, isMessageTextFocused: Binding<Bool>) {
if self.messageToReply != nil {
self.messageToReply = nil
}
coreContext.doOnCoreQueue { _ in
let messageToEditTmp = chatMessage
DispatchQueue.main.async {
withAnimation(.linear(duration: 0.15)) {
self.messageToEdit = messageToEditTmp
}
isMessageTextFocused.wrappedValue = true
}
}
}
func resendMessage(chatMessage: EventLogMessage) {
coreContext.doOnCoreQueue { _ in
if let message = chatMessage.eventModel.eventLog.chatMessage {
@ -1619,6 +1782,8 @@ class ConversationViewModel: ObservableObject {
id: UUID().uuidString,
status: nil,
isOutgoing: false,
isEditable: false,
isEdited: false,
dateReceived: 0,
address: "",
isFirstMessage: false,
@ -1796,6 +1961,8 @@ class ConversationViewModel: ObservableObject {
isFirstMessage: false,
text: contentReplyText,
isOutgoing: false,
isEditable: false,
isEdited: false,
dateReceived: 0,
attachmentsNames: attachmentNameReplyList,
attachments: []
@ -1809,6 +1976,8 @@ class ConversationViewModel: ObservableObject {
id: !chatMessage.messageId.isEmpty ? chatMessage.messageId : UUID().uuidString,
status: statusTmp,
isOutgoing: chatMessage.isOutgoing,
isEditable: chatMessage.isOutgoing ? chatMessage.isEditable : false,
isEdited: chatMessage.isEdited,
dateReceived: chatMessage.time,
address: addressCleaned?.asStringUriOnly() ?? "",
isFirstMessage: isFirstMessageTmp,
@ -1873,6 +2042,8 @@ class ConversationViewModel: ObservableObject {
if chatMessageToReply != nil {
message = try self.sharedMainViewModel.displayedConversation!.chatRoom.createReplyMessage(message: chatMessageToReply!)
}
} else if let chatMessage = self.messageToEdit?.eventModel.eventLog.chatMessage {
message = try self.sharedMainViewModel.displayedConversation!.chatRoom.createReplacesMessage(message: chatMessage)
} else {
message = try self.sharedMainViewModel.displayedConversation!.chatRoom.createEmptyMessage()
}
@ -1948,12 +2119,14 @@ class ConversationViewModel: ObservableObject {
if message != nil && !message!.contents.isEmpty {
Log.info("[ConversationViewModel] Sending message")
message!.send()
self.sharedMainViewModel.displayedConversation!.chatRoom.stopComposing()
}
Log.info("[ConversationViewModel] Message sent, re-setting defaults")
DispatchQueue.main.async {
self.messageToReply = nil
self.messageToEdit = nil
withAnimation {
self.mediasToSend.removeAll()
}